forked from mirrors/gecko-dev
		
	 fd1720100c
			
		
	
	
		fd1720100c
		
	
	
	
	
		
			
			Backed out changeset 199d3ecfe13c (bug 1658072) Backed out changeset a942be3d053d (bug 1658072) Backed out changeset e0e98ee85f98 (bug 1658072)
		
			
				
	
	
		
			488 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			488 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "nsDumpUtils.h"
 | |
| #include "nsDirectoryServiceDefs.h"
 | |
| #include "nsDirectoryServiceUtils.h"
 | |
| #include <errno.h>
 | |
| #include "prenv.h"
 | |
| #include "mozilla/Services.h"
 | |
| #include "nsIObserverService.h"
 | |
| #include "mozilla/ClearOnShutdown.h"
 | |
| #include "mozilla/Unused.h"
 | |
| #include "SpecialSystemDirectory.h"
 | |
| 
 | |
| #ifdef XP_UNIX  // {
 | |
| #  include "mozilla/Preferences.h"
 | |
| #  include <fcntl.h>
 | |
| #  include <unistd.h>
 | |
| #  include <sys/stat.h>
 | |
| 
 | |
| using namespace mozilla;
 | |
| 
 | |
| /*
 | |
|  * The following code supports triggering a registered callback upon
 | |
|  * receiving a specific signal.
 | |
|  *
 | |
|  * Take about:memory for example, we register
 | |
|  * 1. doGCCCDump for doMemoryReport
 | |
|  * 2. doMemoryReport for sDumpAboutMemorySignum(SIGRTMIN)
 | |
|  *                       and sDumpAboutMemoryAfterMMUSignum(SIGRTMIN+1).
 | |
|  *
 | |
|  * When we receive one of these signals, we write the signal number to a pipe.
 | |
|  * The IO thread then notices that the pipe has been written to, and kicks off
 | |
|  * the appropriate task on the main thread.
 | |
|  *
 | |
|  * This scheme is similar to using signalfd(), except it's portable and it
 | |
|  * doesn't require the use of sigprocmask, which is problematic because it
 | |
|  * masks signals received by child processes.
 | |
|  *
 | |
|  * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this.
 | |
|  * But that uses libevent, which does not handle the realtime signals (bug
 | |
|  * 794074).
 | |
|  */
 | |
| 
 | |
| // This is the write-end of a pipe that we use to notice when a
 | |
| // specific signal occurs.
 | |
| static Atomic<int> sDumpPipeWriteFd(-1);
 | |
| 
 | |
| const char FifoWatcher::kPrefName[] = "memory_info_dumper.watch_fifo.enabled";
 | |
| 
 | |
| static void DumpSignalHandler(int aSignum) {
 | |
|   // This is a signal handler, so everything in here needs to be
 | |
|   // async-signal-safe.  Be careful!
 | |
| 
 | |
|   if (sDumpPipeWriteFd != -1) {
 | |
|     uint8_t signum = static_cast<int>(aSignum);
 | |
|     Unused << write(sDumpPipeWriteFd, &signum, sizeof(signum));
 | |
|   }
 | |
| }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(FdWatcher, nsIObserver);
 | |
| 
 | |
| void FdWatcher::Init() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
 | |
|   os->AddObserver(this, "xpcom-shutdown", /* ownsWeak = */ false);
 | |
| 
 | |
|   XRE_GetIOMessageLoop()->PostTask(NewRunnableMethod(
 | |
|       "FdWatcher::StartWatching", this, &FdWatcher::StartWatching));
 | |
| }
 | |
| 
 | |
| // Implementations may call this function multiple times if they ensure that
 | |
| // it's safe to call OpenFd() multiple times and they call StopWatching()
 | |
| // first.
 | |
| void FdWatcher::StartWatching() {
 | |
|   MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
 | |
|   MOZ_ASSERT(mFd == -1);
 | |
| 
 | |
|   mFd = OpenFd();
 | |
|   if (mFd == -1) {
 | |
|     LOG("FdWatcher: OpenFd failed.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MessageLoopForIO::current()->WatchFileDescriptor(mFd, /* persistent = */ true,
 | |
|                                                    MessageLoopForIO::WATCH_READ,
 | |
|                                                    &mReadWatcher, this);
 | |
| }
 | |
| 
 | |
| // Since implementations can call StartWatching() multiple times, they can of
 | |
| // course call StopWatching() multiple times.
 | |
| void FdWatcher::StopWatching() {
 | |
|   MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
 | |
| 
 | |
|   mReadWatcher.StopWatchingFileDescriptor();
 | |
|   if (mFd != -1) {
 | |
|     close(mFd);
 | |
|     mFd = -1;
 | |
|   }
 | |
| }
 | |
| 
 | |
| StaticRefPtr<SignalPipeWatcher> SignalPipeWatcher::sSingleton;
 | |
| 
 | |
| /* static */
 | |
| SignalPipeWatcher* SignalPipeWatcher::GetSingleton() {
 | |
|   if (!sSingleton) {
 | |
|     sSingleton = new SignalPipeWatcher();
 | |
|     sSingleton->Init();
 | |
|     ClearOnShutdown(&sSingleton);
 | |
|   }
 | |
|   return sSingleton;
 | |
| }
 | |
| 
 | |
| void SignalPipeWatcher::RegisterCallback(uint8_t aSignal,
 | |
|                                          PipeCallback aCallback) {
 | |
|   MutexAutoLock lock(mSignalInfoLock);
 | |
| 
 | |
|   for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); ++i) {
 | |
|     if (mSignalInfo[i].mSignal == aSignal) {
 | |
|       LOG("Register Signal(%d) callback failed! (DUPLICATE)", aSignal);
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
|   SignalInfo signalInfo = {aSignal, aCallback};
 | |
|   mSignalInfo.AppendElement(signalInfo);
 | |
|   RegisterSignalHandler(signalInfo.mSignal);
 | |
| }
 | |
| 
 | |
| void SignalPipeWatcher::RegisterSignalHandler(uint8_t aSignal) {
 | |
|   struct sigaction action;
 | |
|   memset(&action, 0, sizeof(action));
 | |
|   sigemptyset(&action.sa_mask);
 | |
|   action.sa_handler = DumpSignalHandler;
 | |
| 
 | |
|   if (aSignal) {
 | |
|     if (sigaction(aSignal, &action, nullptr)) {
 | |
|       LOG("SignalPipeWatcher failed to register sig %d.", aSignal);
 | |
|     }
 | |
|   } else {
 | |
|     MutexAutoLock lock(mSignalInfoLock);
 | |
|     for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) {
 | |
|       if (sigaction(mSignalInfo[i].mSignal, &action, nullptr)) {
 | |
|         LOG("SignalPipeWatcher failed to register signal(%d) "
 | |
|             "dump signal handler.",
 | |
|             mSignalInfo[i].mSignal);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| SignalPipeWatcher::~SignalPipeWatcher() {
 | |
|   if (sDumpPipeWriteFd != -1) {
 | |
|     StopWatching();
 | |
|   }
 | |
| }
 | |
| 
 | |
| int SignalPipeWatcher::OpenFd() {
 | |
|   MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
 | |
| 
 | |
|   // Create a pipe.  When we receive a signal in our signal handler, we'll
 | |
|   // write the signum to the write-end of this pipe.
 | |
|   int pipeFds[2];
 | |
|   if (pipe(pipeFds)) {
 | |
|     LOG("SignalPipeWatcher failed to create pipe.");
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
|   // Close this pipe on calls to exec().
 | |
|   fcntl(pipeFds[0], F_SETFD, FD_CLOEXEC);
 | |
|   fcntl(pipeFds[1], F_SETFD, FD_CLOEXEC);
 | |
| 
 | |
|   int readFd = pipeFds[0];
 | |
|   sDumpPipeWriteFd = pipeFds[1];
 | |
| 
 | |
|   RegisterSignalHandler();
 | |
|   return readFd;
 | |
| }
 | |
| 
 | |
| void SignalPipeWatcher::StopWatching() {
 | |
|   MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
 | |
| 
 | |
|   // Close sDumpPipeWriteFd /after/ setting the fd to -1.
 | |
|   // Otherwise we have the (admittedly far-fetched) race where we
 | |
|   //
 | |
|   //  1) close sDumpPipeWriteFd
 | |
|   //  2) open a new fd with the same number as sDumpPipeWriteFd
 | |
|   //     had.
 | |
|   //  3) receive a signal, then write to the fd.
 | |
|   int pipeWriteFd = sDumpPipeWriteFd.exchange(-1);
 | |
|   close(pipeWriteFd);
 | |
| 
 | |
|   FdWatcher::StopWatching();
 | |
| }
 | |
| 
 | |
| void SignalPipeWatcher::OnFileCanReadWithoutBlocking(int aFd) {
 | |
|   MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
 | |
| 
 | |
|   uint8_t signum;
 | |
|   ssize_t numReceived = read(aFd, &signum, sizeof(signum));
 | |
|   if (numReceived != sizeof(signum)) {
 | |
|     LOG("Error reading from buffer in "
 | |
|         "SignalPipeWatcher::OnFileCanReadWithoutBlocking.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   {
 | |
|     MutexAutoLock lock(mSignalInfoLock);
 | |
|     for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) {
 | |
|       if (signum == mSignalInfo[i].mSignal) {
 | |
|         mSignalInfo[i].mCallback(signum);
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   LOG("SignalPipeWatcher got unexpected signum.");
 | |
| }
 | |
| 
 | |
| StaticRefPtr<FifoWatcher> FifoWatcher::sSingleton;
 | |
| 
 | |
| /* static */
 | |
| FifoWatcher* FifoWatcher::GetSingleton() {
 | |
|   if (!sSingleton) {
 | |
|     nsAutoCString dirPath;
 | |
|     Preferences::GetCString("memory_info_dumper.watch_fifo.directory", dirPath);
 | |
|     sSingleton = new FifoWatcher(dirPath);
 | |
|     sSingleton->Init();
 | |
|     ClearOnShutdown(&sSingleton);
 | |
|   }
 | |
|   return sSingleton;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| bool FifoWatcher::MaybeCreate() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (!XRE_IsParentProcess()) {
 | |
|     // We want this to be main-process only, since two processes can't listen
 | |
|     // to the same fifo.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (!Preferences::GetBool(kPrefName, false)) {
 | |
|     LOG("Fifo watcher disabled via pref.");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // The FifoWatcher is held alive by the observer service.
 | |
|   if (!sSingleton) {
 | |
|     GetSingleton();
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void FifoWatcher::RegisterCallback(const nsCString& aCommand,
 | |
|                                    FifoCallback aCallback) {
 | |
|   MutexAutoLock lock(mFifoInfoLock);
 | |
| 
 | |
|   for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); ++i) {
 | |
|     if (mFifoInfo[i].mCommand.Equals(aCommand)) {
 | |
|       LOG("Register command(%s) callback failed! (DUPLICATE)", aCommand.get());
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
|   FifoInfo aFifoInfo = {aCommand, aCallback};
 | |
|   mFifoInfo.AppendElement(aFifoInfo);
 | |
| }
 | |
| 
 | |
| FifoWatcher::~FifoWatcher() = default;
 | |
| 
 | |
| int FifoWatcher::OpenFd() {
 | |
|   // If the memory_info_dumper.directory pref is specified, put the fifo
 | |
|   // there.  Otherwise, put it into the system's tmp directory.
 | |
| 
 | |
|   nsCOMPtr<nsIFile> file;
 | |
| 
 | |
|   nsresult rv;
 | |
|   if (mDirPath.Length() > 0) {
 | |
|     rv = XRE_GetFileFromPath(mDirPath.get(), getter_AddRefs(file));
 | |
|     if (NS_FAILED(rv)) {
 | |
|       LOG("FifoWatcher failed to open file \"%s\"", mDirPath.get());
 | |
|       return -1;
 | |
|     }
 | |
|   } else {
 | |
|     rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file));
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return -1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   rv = file->AppendNative("debug_info_trigger"_ns);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString path;
 | |
|   rv = file->GetNativePath(path);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
|   // unlink might fail because the file doesn't exist, or for other reasons.
 | |
|   // But we don't care it fails; any problems will be detected later, when we
 | |
|   // try to mkfifo or open the file.
 | |
|   if (unlink(path.get())) {
 | |
|     LOG("FifoWatcher::OpenFifo unlink failed; errno=%d.  "
 | |
|         "Continuing despite error.",
 | |
|         errno);
 | |
|   }
 | |
| 
 | |
|   if (mkfifo(path.get(), 0766)) {
 | |
|     LOG("FifoWatcher::OpenFifo mkfifo failed; errno=%d", errno);
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
| #  ifdef ANDROID
 | |
|   // Android runs with a umask, so we need to chmod our fifo to make it
 | |
|   // world-writable.
 | |
|   chmod(path.get(), 0666);
 | |
| #  endif
 | |
| 
 | |
|   int fd;
 | |
|   do {
 | |
|     // The fifo will block until someone else has written to it.  In
 | |
|     // particular, open() will block until someone else has opened it for
 | |
|     // writing!  We want open() to succeed and read() to block, so we open
 | |
|     // with NONBLOCK and then fcntl that away.
 | |
|     fd = open(path.get(), O_RDONLY | O_NONBLOCK);
 | |
|   } while (fd == -1 && errno == EINTR);
 | |
| 
 | |
|   if (fd == -1) {
 | |
|     LOG("FifoWatcher::OpenFifo open failed; errno=%d", errno);
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
|   // Make fd blocking now that we've opened it.
 | |
|   if (fcntl(fd, F_SETFL, 0)) {
 | |
|     close(fd);
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
|   return fd;
 | |
| }
 | |
| 
 | |
| void FifoWatcher::OnFileCanReadWithoutBlocking(int aFd) {
 | |
|   MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
 | |
| 
 | |
|   char buf[1024];
 | |
|   int nread;
 | |
|   do {
 | |
|     // sizeof(buf) - 1 to leave space for the null-terminator.
 | |
|     nread = read(aFd, buf, sizeof(buf));
 | |
|   } while (nread == -1 && errno == EINTR);
 | |
| 
 | |
|   if (nread == -1) {
 | |
|     // We want to avoid getting into a situation where
 | |
|     // OnFileCanReadWithoutBlocking is called in an infinite loop, so when
 | |
|     // something goes wrong, stop watching the fifo altogether.
 | |
|     LOG("FifoWatcher hit an error (%d) and is quitting.", errno);
 | |
|     StopWatching();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (nread == 0) {
 | |
|     // If we get EOF, that means that the other side closed the fifo.  We need
 | |
|     // to close and re-open the fifo; if we don't,
 | |
|     // OnFileCanWriteWithoutBlocking will be called in an infinite loop.
 | |
| 
 | |
|     LOG("FifoWatcher closing and re-opening fifo.");
 | |
|     StopWatching();
 | |
|     StartWatching();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString inputStr;
 | |
|   inputStr.Append(buf, nread);
 | |
| 
 | |
|   // Trimming whitespace is important because if you do
 | |
|   //   |echo "foo" >> debug_info_trigger|,
 | |
|   // it'll actually write "foo\n" to the fifo.
 | |
|   inputStr.Trim("\b\t\r\n");
 | |
| 
 | |
|   {
 | |
|     MutexAutoLock lock(mFifoInfoLock);
 | |
| 
 | |
|     for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); i++) {
 | |
|       const nsCString commandStr = mFifoInfo[i].mCommand;
 | |
|       if (inputStr == commandStr.get()) {
 | |
|         mFifoInfo[i].mCallback(inputStr);
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   LOG("Got unexpected value from fifo; ignoring it.");
 | |
| }
 | |
| 
 | |
| #endif  // XP_UNIX }
 | |
| 
 | |
| // In Android case, this function will open a file named aFilename under
 | |
| // /data/local/tmp/"aFoldername".
 | |
| // Otherwise, it will open a file named aFilename under "NS_OS_TEMP_DIR".
 | |
| /* static */
 | |
| nsresult nsDumpUtils::OpenTempFile(const nsACString& aFilename, nsIFile** aFile,
 | |
|                                    const nsACString& aFoldername, Mode aMode) {
 | |
| #ifdef ANDROID
 | |
|   // For Android, first try the downloads directory which is world-readable
 | |
|   // rather than the temp directory which is not.
 | |
|   if (!*aFile) {
 | |
|     char* env = PR_GetEnv("DOWNLOADS_DIRECTORY");
 | |
|     if (env) {
 | |
|       NS_NewNativeLocalFile(nsCString(env), /* followLinks = */ true, aFile);
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
|   nsresult rv;
 | |
|   if (!*aFile) {
 | |
|     if (NS_IsMainThread()) {
 | |
|       // This allows tests to override, but isn't safe off-mainthread.
 | |
|       rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, aFile);
 | |
|     } else {
 | |
|       rv = GetSpecialSystemDirectory(OS_TemporaryDirectory, aFile);
 | |
|     }
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| #ifdef ANDROID
 | |
|   // /data/local/tmp is a true tmp directory; anyone can create a file there,
 | |
|   // but only the user which created the file can remove it.  We want non-root
 | |
|   // users to be able to remove these files, so we write them into a
 | |
|   // subdirectory of the temp directory and chmod 777 that directory.
 | |
|   if (!aFoldername.IsEmpty()) {
 | |
|     rv = (*aFile)->AppendNative(aFoldername);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     // It's OK if this fails; that probably just means that the directory
 | |
|     // already exists.
 | |
|     Unused << (*aFile)->Create(nsIFile::DIRECTORY_TYPE, 0777);
 | |
| 
 | |
|     nsAutoCString dirPath;
 | |
|     rv = (*aFile)->GetNativePath(dirPath);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     while (chmod(dirPath.get(), 0777) == -1 && errno == EINTR) {
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   nsCOMPtr<nsIFile> file(*aFile);
 | |
| 
 | |
|   rv = file->AppendNative(aFilename);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (aMode == CREATE_UNIQUE) {
 | |
|     rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666);
 | |
|   } else {
 | |
|     rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
 | |
|   }
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
| #ifdef ANDROID
 | |
|   // Make this file world-read/writable; the permissions passed to the
 | |
|   // CreateUnique call above are not sufficient on Android, which runs with a
 | |
|   // umask.
 | |
|   nsAutoCString path;
 | |
|   rv = file->GetNativePath(path);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   while (chmod(path.get(), 0666) == -1 && errno == EINTR) {
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 |