LCOV - code coverage report
Current view: top level - xpcom/base - nsDumpUtils.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 55 174 31.6 %
Date: 2017-07-14 16:53:18 Functions: 9 23 39.1 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "nsDumpUtils.h"
       8             : #include "nsDirectoryServiceDefs.h"
       9             : #include "nsDirectoryServiceUtils.h"
      10             : #include "prenv.h"
      11             : #include <errno.h>
      12             : #include "mozilla/Services.h"
      13             : #include "nsIObserverService.h"
      14             : #include "mozilla/ClearOnShutdown.h"
      15             : #include "mozilla/Unused.h"
      16             : 
      17             : #ifdef XP_UNIX // {
      18             : #include "mozilla/Preferences.h"
      19             : #include <fcntl.h>
      20             : #include <unistd.h>
      21             : #include <sys/types.h>
      22             : #include <sys/stat.h>
      23             : 
      24             : using namespace mozilla;
      25             : 
      26             : /*
      27             :  * The following code supports triggering a registered callback upon
      28             :  * receiving a specific signal.
      29             :  *
      30             :  * Take about:memory for example, we register
      31             :  * 1. doGCCCDump for doMemoryReport
      32             :  * 2. doMemoryReport for sDumpAboutMemorySignum(SIGRTMIN)
      33             :  *                       and sDumpAboutMemoryAfterMMUSignum(SIGRTMIN+1).
      34             :  *
      35             :  * When we receive one of these signals, we write the signal number to a pipe.
      36             :  * The IO thread then notices that the pipe has been written to, and kicks off
      37             :  * the appropriate task on the main thread.
      38             :  *
      39             :  * This scheme is similar to using signalfd(), except it's portable and it
      40             :  * doesn't require the use of sigprocmask, which is problematic because it
      41             :  * masks signals received by child processes.
      42             :  *
      43             :  * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this.
      44             :  * But that uses libevent, which does not handle the realtime signals (bug
      45             :  * 794074).
      46             :  */
      47             : 
      48             : // This is the write-end of a pipe that we use to notice when a
      49             : // specific signal occurs.
      50             : static Atomic<int> sDumpPipeWriteFd(-1);
      51             : 
      52             : const char* const FifoWatcher::kPrefName =
      53             :   "memory_info_dumper.watch_fifo.enabled";
      54             : 
      55             : static void
      56           0 : DumpSignalHandler(int aSignum)
      57             : {
      58             :   // This is a signal handler, so everything in here needs to be
      59             :   // async-signal-safe.  Be careful!
      60             : 
      61           0 :   if (sDumpPipeWriteFd != -1) {
      62           0 :     uint8_t signum = static_cast<int>(aSignum);
      63           0 :     Unused << write(sDumpPipeWriteFd, &signum, sizeof(signum));
      64             :   }
      65           0 : }
      66             : 
      67          12 : NS_IMPL_ISUPPORTS(FdWatcher, nsIObserver);
      68             : 
      69             : void
      70           3 : FdWatcher::Init()
      71             : {
      72           3 :   MOZ_ASSERT(NS_IsMainThread());
      73             : 
      74           6 :   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
      75           3 :   os->AddObserver(this, "xpcom-shutdown", /* ownsWeak = */ false);
      76             : 
      77           6 :   XRE_GetIOMessageLoop()->PostTask(NewRunnableMethod(
      78           3 :     "FdWatcher::StartWatching", this, &FdWatcher::StartWatching));
      79           3 : }
      80             : 
      81             : // Implementations may call this function multiple times if they ensure that
      82             : // it's safe to call OpenFd() multiple times and they call StopWatching()
      83             : // first.
      84             : void
      85           3 : FdWatcher::StartWatching()
      86             : {
      87           3 :   MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
      88           3 :   MOZ_ASSERT(mFd == -1);
      89             : 
      90           3 :   mFd = OpenFd();
      91           3 :   if (mFd == -1) {
      92             :     LOG("FdWatcher: OpenFd failed.");
      93           0 :     return;
      94             :   }
      95             : 
      96           3 :   MessageLoopForIO::current()->WatchFileDescriptor(
      97             :     mFd, /* persistent = */ true,
      98             :     MessageLoopForIO::WATCH_READ,
      99           3 :     &mReadWatcher, this);
     100             : }
     101             : 
     102             : // Since implementations can call StartWatching() multiple times, they can of
     103             : // course call StopWatching() multiple times.
     104             : void
     105           0 : FdWatcher::StopWatching()
     106             : {
     107           0 :   MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
     108             : 
     109           0 :   mReadWatcher.StopWatchingFileDescriptor();
     110           0 :   if (mFd != -1) {
     111           0 :     close(mFd);
     112           0 :     mFd = -1;
     113             :   }
     114           0 : }
     115             : 
     116           3 : StaticRefPtr<SignalPipeWatcher> SignalPipeWatcher::sSingleton;
     117             : 
     118             : /* static */ SignalPipeWatcher*
     119           3 : SignalPipeWatcher::GetSingleton()
     120             : {
     121           3 :   if (!sSingleton) {
     122           3 :     sSingleton = new SignalPipeWatcher();
     123           3 :     sSingleton->Init();
     124           3 :     ClearOnShutdown(&sSingleton);
     125             :   }
     126           3 :   return sSingleton;
     127             : }
     128             : 
     129             : void
     130           9 : SignalPipeWatcher::RegisterCallback(uint8_t aSignal,
     131             :                                     PipeCallback aCallback)
     132             : {
     133          18 :   MutexAutoLock lock(mSignalInfoLock);
     134             : 
     135          18 :   for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); ++i) {
     136           9 :     if (mSignalInfo[i].mSignal == aSignal) {
     137             :       LOG("Register Signal(%d) callback failed! (DUPLICATE)", aSignal);
     138           0 :       return;
     139             :     }
     140             :   }
     141           9 :   SignalInfo signalInfo = { aSignal, aCallback };
     142           9 :   mSignalInfo.AppendElement(signalInfo);
     143           9 :   RegisterSignalHandler(signalInfo.mSignal);
     144             : }
     145             : 
     146             : void
     147          12 : SignalPipeWatcher::RegisterSignalHandler(uint8_t aSignal)
     148             : {
     149             :   struct sigaction action;
     150          12 :   memset(&action, 0, sizeof(action));
     151          12 :   sigemptyset(&action.sa_mask);
     152          12 :   action.sa_handler = DumpSignalHandler;
     153             : 
     154          12 :   if (aSignal) {
     155           9 :     if (sigaction(aSignal, &action, nullptr)) {
     156             :       LOG("SignalPipeWatcher failed to register sig %d.", aSignal);
     157             :     }
     158             :   } else {
     159           6 :     MutexAutoLock lock(mSignalInfoLock);
     160          12 :     for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) {
     161           9 :       if (sigaction(mSignalInfo[i].mSignal, &action, nullptr)) {
     162             :         LOG("SignalPipeWatcher failed to register signal(%d) "
     163             :             "dump signal handler.", mSignalInfo[i].mSignal);
     164             :       }
     165             :     }
     166             :   }
     167          12 : }
     168             : 
     169           0 : SignalPipeWatcher::~SignalPipeWatcher()
     170             : {
     171           0 :   if (sDumpPipeWriteFd != -1) {
     172           0 :     StopWatching();
     173             :   }
     174           0 : }
     175             : 
     176             : int
     177           3 : SignalPipeWatcher::OpenFd()
     178             : {
     179           3 :   MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
     180             : 
     181             :   // Create a pipe.  When we receive a signal in our signal handler, we'll
     182             :   // write the signum to the write-end of this pipe.
     183             :   int pipeFds[2];
     184           3 :   if (pipe(pipeFds)) {
     185             :     LOG("SignalPipeWatcher failed to create pipe.");
     186           0 :     return -1;
     187             :   }
     188             : 
     189             :   // Close this pipe on calls to exec().
     190           3 :   fcntl(pipeFds[0], F_SETFD, FD_CLOEXEC);
     191           3 :   fcntl(pipeFds[1], F_SETFD, FD_CLOEXEC);
     192             : 
     193           3 :   int readFd = pipeFds[0];
     194           3 :   sDumpPipeWriteFd = pipeFds[1];
     195             : 
     196           3 :   RegisterSignalHandler();
     197           3 :   return readFd;
     198             : }
     199             : 
     200             : void
     201           0 : SignalPipeWatcher::StopWatching()
     202             : {
     203           0 :   MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
     204             : 
     205             :   // Close sDumpPipeWriteFd /after/ setting the fd to -1.
     206             :   // Otherwise we have the (admittedly far-fetched) race where we
     207             :   //
     208             :   //  1) close sDumpPipeWriteFd
     209             :   //  2) open a new fd with the same number as sDumpPipeWriteFd
     210             :   //     had.
     211             :   //  3) receive a signal, then write to the fd.
     212           0 :   int pipeWriteFd = sDumpPipeWriteFd.exchange(-1);
     213           0 :   close(pipeWriteFd);
     214             : 
     215           0 :   FdWatcher::StopWatching();
     216           0 : }
     217             : 
     218             : void
     219           0 : SignalPipeWatcher::OnFileCanReadWithoutBlocking(int aFd)
     220             : {
     221           0 :   MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
     222             : 
     223             :   uint8_t signum;
     224           0 :   ssize_t numReceived = read(aFd, &signum, sizeof(signum));
     225           0 :   if (numReceived != sizeof(signum)) {
     226             :     LOG("Error reading from buffer in "
     227             :         "SignalPipeWatcher::OnFileCanReadWithoutBlocking.");
     228           0 :     return;
     229             :   }
     230             : 
     231             :   {
     232           0 :     MutexAutoLock lock(mSignalInfoLock);
     233           0 :     for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) {
     234           0 :       if (signum == mSignalInfo[i].mSignal) {
     235           0 :         mSignalInfo[i].mCallback(signum);
     236           0 :         return;
     237             :       }
     238             :     }
     239             :   }
     240             :   LOG("SignalPipeWatcher got unexpected signum.");
     241             : }
     242             : 
     243           3 : StaticRefPtr<FifoWatcher> FifoWatcher::sSingleton;
     244             : 
     245             : /* static */ FifoWatcher*
     246           0 : FifoWatcher::GetSingleton()
     247             : {
     248           0 :   if (!sSingleton) {
     249           0 :     nsAutoCString dirPath;
     250             :     Preferences::GetCString(
     251           0 :       "memory_info_dumper.watch_fifo.directory", &dirPath);
     252           0 :     sSingleton = new FifoWatcher(dirPath);
     253           0 :     sSingleton->Init();
     254           0 :     ClearOnShutdown(&sSingleton);
     255             :   }
     256           0 :   return sSingleton;
     257             : }
     258             : 
     259             : /* static */ bool
     260           3 : FifoWatcher::MaybeCreate()
     261             : {
     262           3 :   MOZ_ASSERT(NS_IsMainThread());
     263             : 
     264           3 :   if (!XRE_IsParentProcess()) {
     265             :     // We want this to be main-process only, since two processes can't listen
     266             :     // to the same fifo.
     267           2 :     return false;
     268             :   }
     269             : 
     270           1 :   if (!Preferences::GetBool(kPrefName, false)) {
     271             :     LOG("Fifo watcher disabled via pref.");
     272           1 :     return false;
     273             :   }
     274             : 
     275             :   // The FifoWatcher is held alive by the observer service.
     276           0 :   if (!sSingleton) {
     277           0 :     GetSingleton();
     278             :   }
     279           0 :   return true;
     280             : }
     281             : 
     282             : void
     283           0 : FifoWatcher::RegisterCallback(const nsCString& aCommand, FifoCallback aCallback)
     284             : {
     285           0 :   MutexAutoLock lock(mFifoInfoLock);
     286             : 
     287           0 :   for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); ++i) {
     288           0 :     if (mFifoInfo[i].mCommand.Equals(aCommand)) {
     289             :       LOG("Register command(%s) callback failed! (DUPLICATE)", aCommand.get());
     290           0 :       return;
     291             :     }
     292             :   }
     293           0 :   FifoInfo aFifoInfo = { aCommand, aCallback };
     294           0 :   mFifoInfo.AppendElement(aFifoInfo);
     295             : }
     296             : 
     297           0 : FifoWatcher::~FifoWatcher()
     298             : {
     299           0 : }
     300             : 
     301             : int
     302           0 : FifoWatcher::OpenFd()
     303             : {
     304             :   // If the memory_info_dumper.directory pref is specified, put the fifo
     305             :   // there.  Otherwise, put it into the system's tmp directory.
     306             : 
     307           0 :   nsCOMPtr<nsIFile> file;
     308             : 
     309             :   nsresult rv;
     310           0 :   if (mDirPath.Length() > 0) {
     311           0 :     rv = XRE_GetFileFromPath(mDirPath.get(), getter_AddRefs(file));
     312           0 :     if (NS_FAILED(rv)) {
     313             :       LOG("FifoWatcher failed to open file \"%s\"", mDirPath.get());
     314           0 :       return -1;
     315             :     }
     316             :   } else {
     317           0 :     rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file));
     318           0 :     if (NS_WARN_IF(NS_FAILED(rv))) {
     319           0 :       return -1;
     320             :     }
     321             :   }
     322             : 
     323           0 :   rv = file->AppendNative(NS_LITERAL_CSTRING("debug_info_trigger"));
     324           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     325           0 :     return -1;
     326             :   }
     327             : 
     328           0 :   nsAutoCString path;
     329           0 :   rv = file->GetNativePath(path);
     330           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     331           0 :     return -1;
     332             :   }
     333             : 
     334             :   // unlink might fail because the file doesn't exist, or for other reasons.
     335             :   // But we don't care it fails; any problems will be detected later, when we
     336             :   // try to mkfifo or open the file.
     337           0 :   if (unlink(path.get())) {
     338             :     LOG("FifoWatcher::OpenFifo unlink failed; errno=%d.  "
     339             :         "Continuing despite error.", errno);
     340             :   }
     341             : 
     342           0 :   if (mkfifo(path.get(), 0766)) {
     343             :     LOG("FifoWatcher::OpenFifo mkfifo failed; errno=%d", errno);
     344           0 :     return -1;
     345             :   }
     346             : 
     347             : #ifdef ANDROID
     348             :   // Android runs with a umask, so we need to chmod our fifo to make it
     349             :   // world-writable.
     350             :   chmod(path.get(), 0666);
     351             : #endif
     352             : 
     353             :   int fd;
     354           0 :   do {
     355             :     // The fifo will block until someone else has written to it.  In
     356             :     // particular, open() will block until someone else has opened it for
     357             :     // writing!  We want open() to succeed and read() to block, so we open
     358             :     // with NONBLOCK and then fcntl that away.
     359           0 :     fd = open(path.get(), O_RDONLY | O_NONBLOCK);
     360           0 :   } while (fd == -1 && errno == EINTR);
     361             : 
     362           0 :   if (fd == -1) {
     363             :     LOG("FifoWatcher::OpenFifo open failed; errno=%d", errno);
     364           0 :     return -1;
     365             :   }
     366             : 
     367             :   // Make fd blocking now that we've opened it.
     368           0 :   if (fcntl(fd, F_SETFL, 0)) {
     369           0 :     close(fd);
     370           0 :     return -1;
     371             :   }
     372             : 
     373           0 :   return fd;
     374             : }
     375             : 
     376             : void
     377           0 : FifoWatcher::OnFileCanReadWithoutBlocking(int aFd)
     378             : {
     379           0 :   MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
     380             : 
     381             :   char buf[1024];
     382             :   int nread;
     383           0 :   do {
     384             :     // sizeof(buf) - 1 to leave space for the null-terminator.
     385           0 :     nread = read(aFd, buf, sizeof(buf));
     386           0 :   } while (nread == -1 && errno == EINTR);
     387             : 
     388           0 :   if (nread == -1) {
     389             :     // We want to avoid getting into a situation where
     390             :     // OnFileCanReadWithoutBlocking is called in an infinite loop, so when
     391             :     // something goes wrong, stop watching the fifo altogether.
     392             :     LOG("FifoWatcher hit an error (%d) and is quitting.", errno);
     393           0 :     StopWatching();
     394           0 :     return;
     395             :   }
     396             : 
     397           0 :   if (nread == 0) {
     398             :     // If we get EOF, that means that the other side closed the fifo.  We need
     399             :     // to close and re-open the fifo; if we don't,
     400             :     // OnFileCanWriteWithoutBlocking will be called in an infinite loop.
     401             : 
     402             :     LOG("FifoWatcher closing and re-opening fifo.");
     403           0 :     StopWatching();
     404           0 :     StartWatching();
     405           0 :     return;
     406             :   }
     407             : 
     408           0 :   nsAutoCString inputStr;
     409           0 :   inputStr.Append(buf, nread);
     410             : 
     411             :   // Trimming whitespace is important because if you do
     412             :   //   |echo "foo" >> debug_info_trigger|,
     413             :   // it'll actually write "foo\n" to the fifo.
     414           0 :   inputStr.Trim("\b\t\r\n");
     415             : 
     416             :   {
     417           0 :     MutexAutoLock lock(mFifoInfoLock);
     418             : 
     419           0 :     for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); i++) {
     420           0 :       const nsCString commandStr = mFifoInfo[i].mCommand;
     421           0 :       if (inputStr == commandStr.get()) {
     422           0 :         mFifoInfo[i].mCallback(inputStr);
     423           0 :         return;
     424             :       }
     425             :     }
     426             :   }
     427             :   LOG("Got unexpected value from fifo; ignoring it.");
     428             : }
     429             : 
     430             : #endif // XP_UNIX }
     431             : 
     432             : // In Android case, this function will open a file named aFilename under
     433             : // /data/local/tmp/"aFoldername".
     434             : // Otherwise, it will open a file named aFilename under "NS_OS_TEMP_DIR".
     435             : /* static */ nsresult
     436           0 : nsDumpUtils::OpenTempFile(const nsACString& aFilename, nsIFile** aFile,
     437             :                           const nsACString& aFoldername, Mode aMode)
     438             : {
     439             : #ifdef ANDROID
     440             :   // For Android, first try the downloads directory which is world-readable
     441             :   // rather than the temp directory which is not.
     442             :   if (!*aFile) {
     443             :     char* env = PR_GetEnv("DOWNLOADS_DIRECTORY");
     444             :     if (env) {
     445             :       NS_NewNativeLocalFile(nsCString(env), /* followLinks = */ true, aFile);
     446             :     }
     447             :   }
     448             : #endif
     449             :   nsresult rv;
     450           0 :   if (!*aFile) {
     451           0 :     rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, aFile);
     452           0 :     if (NS_WARN_IF(NS_FAILED(rv))) {
     453           0 :       return rv;
     454             :     }
     455             :   }
     456             : 
     457             : #ifdef ANDROID
     458             :   // /data/local/tmp is a true tmp directory; anyone can create a file there,
     459             :   // but only the user which created the file can remove it.  We want non-root
     460             :   // users to be able to remove these files, so we write them into a
     461             :   // subdirectory of the temp directory and chmod 777 that directory.
     462             :   if (aFoldername != EmptyCString()) {
     463             :     rv = (*aFile)->AppendNative(aFoldername);
     464             :     if (NS_WARN_IF(NS_FAILED(rv))) {
     465             :       return rv;
     466             :     }
     467             : 
     468             :     // It's OK if this fails; that probably just means that the directory
     469             :     // already exists.
     470             :     Unused << (*aFile)->Create(nsIFile::DIRECTORY_TYPE, 0777);
     471             : 
     472             :     nsAutoCString dirPath;
     473             :     rv = (*aFile)->GetNativePath(dirPath);
     474             :     if (NS_WARN_IF(NS_FAILED(rv))) {
     475             :       return rv;
     476             :     }
     477             : 
     478             :     while (chmod(dirPath.get(), 0777) == -1 && errno == EINTR) {
     479             :     }
     480             :   }
     481             : #endif
     482             : 
     483           0 :   nsCOMPtr<nsIFile> file(*aFile);
     484             : 
     485           0 :   rv = file->AppendNative(aFilename);
     486           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     487           0 :     return rv;
     488             :   }
     489             : 
     490           0 :   if (aMode == CREATE_UNIQUE) {
     491           0 :     rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666);
     492             :   } else {
     493           0 :     rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
     494             :   }
     495           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     496           0 :     return rv;
     497             :   }
     498             : 
     499             : #ifdef ANDROID
     500             :   // Make this file world-read/writable; the permissions passed to the
     501             :   // CreateUnique call above are not sufficient on Android, which runs with a
     502             :   // umask.
     503             :   nsAutoCString path;
     504             :   rv = file->GetNativePath(path);
     505             :   if (NS_WARN_IF(NS_FAILED(rv))) {
     506             :     return rv;
     507             :   }
     508             : 
     509             :   while (chmod(path.get(), 0666) == -1 && errno == EINTR) {
     510             :   }
     511             : #endif
     512             : 
     513           0 :   return NS_OK;
     514             : }

Generated by: LCOV version 1.13