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