Backed out 4 changesets (bug 993282) for bringing back bug 1008357 on a CLOSED TREE.
[gecko.git] / xpcom / base / nsDumpUtils.cpp
bloba34a494172a2f723c0d8cdf96903b5e3f1bdb975
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/. */
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"
16 #ifdef XP_UNIX // {
17 #include "mozilla/Preferences.h"
18 #include <fcntl.h>
19 #include <unistd.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
23 using namespace mozilla;
26 * The following code supports triggering a registered callback upon
27 * receiving a specific signal.
29 * Take about:memory for example, we register
30 * 1. doGCCCDump for doMemoryReport
31 * 2. doMemoryReport for sDumpAboutMemorySignum(SIGRTMIN)
32 * and sDumpAboutMemoryAfterMMUSignum(SIGRTMIN+1).
34 * When we receive one of these signals, we write the signal number to a pipe.
35 * The IO thread then notices that the pipe has been written to, and kicks off
36 * the appropriate task on the main thread.
38 * This scheme is similar to using signalfd(), except it's portable and it
39 * doesn't require the use of sigprocmask, which is problematic because it
40 * masks signals received by child processes.
42 * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this.
43 * But that uses libevent, which does not handle the realtime signals (bug
44 * 794074).
47 // This is the write-end of a pipe that we use to notice when a
48 // specific signal occurs.
49 static Atomic<int> sDumpPipeWriteFd(-1);
51 const char* const FifoWatcher::kPrefName =
52 "memory_info_dumper.watch_fifo.enabled";
54 static void
55 DumpSignalHandler(int aSignum)
57 // This is a signal handler, so everything in here needs to be
58 // async-signal-safe. Be careful!
60 if (sDumpPipeWriteFd != -1) {
61 uint8_t signum = static_cast<int>(aSignum);
62 write(sDumpPipeWriteFd, &signum, sizeof(signum));
66 NS_IMPL_ISUPPORTS(FdWatcher, nsIObserver);
68 void
69 FdWatcher::Init()
71 MOZ_ASSERT(NS_IsMainThread());
73 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
74 os->AddObserver(this, "xpcom-shutdown", /* ownsWeak = */ false);
76 XRE_GetIOMessageLoop()->PostTask(
77 FROM_HERE,
78 NewRunnableMethod(this, &FdWatcher::StartWatching));
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 FdWatcher::StartWatching()
87 MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
88 MOZ_ASSERT(mFd == -1);
90 mFd = OpenFd();
91 if (mFd == -1) {
92 LOG("FdWatcher: OpenFd failed.");
93 return;
96 MessageLoopForIO::current()->WatchFileDescriptor(
97 mFd, /* persistent = */ true,
98 MessageLoopForIO::WATCH_READ,
99 &mReadWatcher, this);
102 // Since implementations can call StartWatching() multiple times, they can of
103 // course call StopWatching() multiple times.
104 void
105 FdWatcher::StopWatching()
107 MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
109 mReadWatcher.StopWatchingFileDescriptor();
110 if (mFd != -1) {
111 close(mFd);
112 mFd = -1;
116 StaticRefPtr<SignalPipeWatcher> SignalPipeWatcher::sSingleton;
118 /* static */ SignalPipeWatcher*
119 SignalPipeWatcher::GetSingleton()
121 if (!sSingleton) {
122 sSingleton = new SignalPipeWatcher();
123 sSingleton->Init();
124 ClearOnShutdown(&sSingleton);
126 return sSingleton;
129 void
130 SignalPipeWatcher::RegisterCallback(uint8_t aSignal,
131 PipeCallback aCallback)
133 MutexAutoLock lock(mSignalInfoLock);
135 for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); ++i) {
136 if (mSignalInfo[i].mSignal == aSignal) {
137 LOG("Register Signal(%d) callback failed! (DUPLICATE)", aSignal);
138 return;
141 SignalInfo signalInfo = { aSignal, aCallback };
142 mSignalInfo.AppendElement(signalInfo);
143 RegisterSignalHandler(signalInfo.mSignal);
146 void
147 SignalPipeWatcher::RegisterSignalHandler(uint8_t aSignal)
149 struct sigaction action;
150 memset(&action, 0, sizeof(action));
151 sigemptyset(&action.sa_mask);
152 action.sa_handler = DumpSignalHandler;
154 if (aSignal) {
155 if (sigaction(aSignal, &action, nullptr)) {
156 LOG("SignalPipeWatcher failed to register sig %d.", aSignal);
158 } else {
159 MutexAutoLock lock(mSignalInfoLock);
160 for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) {
161 if (sigaction(mSignalInfo[i].mSignal, &action, nullptr)) {
162 LOG("SignalPipeWatcher failed to register signal(%d) "
163 "dump signal handler.", mSignalInfo[i].mSignal);
169 SignalPipeWatcher::~SignalPipeWatcher()
171 if (sDumpPipeWriteFd != -1) {
172 StopWatching();
177 SignalPipeWatcher::OpenFd()
179 MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
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 if (pipe(pipeFds)) {
185 LOG("SignalPipeWatcher failed to create pipe.");
186 return -1;
189 // Close this pipe on calls to exec().
190 fcntl(pipeFds[0], F_SETFD, FD_CLOEXEC);
191 fcntl(pipeFds[1], F_SETFD, FD_CLOEXEC);
193 int readFd = pipeFds[0];
194 sDumpPipeWriteFd = pipeFds[1];
196 RegisterSignalHandler();
197 return readFd;
200 void
201 SignalPipeWatcher::StopWatching()
203 MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
205 // Close sDumpPipeWriteFd /after/ setting the fd to -1.
206 // Otherwise we have the (admittedly far-fetched) race where we
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 int pipeWriteFd = sDumpPipeWriteFd.exchange(-1);
213 close(pipeWriteFd);
215 FdWatcher::StopWatching();
218 void
219 SignalPipeWatcher::OnFileCanReadWithoutBlocking(int aFd)
221 MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
223 uint8_t signum;
224 ssize_t numReceived = read(aFd, &signum, sizeof(signum));
225 if (numReceived != sizeof(signum)) {
226 LOG("Error reading from buffer in "
227 "SignalPipeWatcher::OnFileCanReadWithoutBlocking.");
228 return;
232 MutexAutoLock lock(mSignalInfoLock);
233 for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) {
234 if (signum == mSignalInfo[i].mSignal) {
235 mSignalInfo[i].mCallback(signum);
236 return;
240 LOG("SignalPipeWatcher got unexpected signum.");
243 StaticRefPtr<FifoWatcher> FifoWatcher::sSingleton;
245 /* static */ FifoWatcher*
246 FifoWatcher::GetSingleton()
248 if (!sSingleton) {
249 nsAutoCString dirPath;
250 Preferences::GetCString(
251 "memory_info_dumper.watch_fifo.directory", &dirPath);
252 sSingleton = new FifoWatcher(dirPath);
253 sSingleton->Init();
254 ClearOnShutdown(&sSingleton);
256 return sSingleton;
259 /* static */ bool
260 FifoWatcher::MaybeCreate()
262 MOZ_ASSERT(NS_IsMainThread());
264 if (XRE_GetProcessType() != GeckoProcessType_Default) {
265 // We want this to be main-process only, since two processes can't listen
266 // to the same fifo.
267 return false;
270 if (!Preferences::GetBool(kPrefName, false)) {
271 LOG("Fifo watcher disabled via pref.");
272 return false;
275 // The FifoWatcher is held alive by the observer service.
276 if (!sSingleton) {
277 GetSingleton();
279 return true;
282 void
283 FifoWatcher::RegisterCallback(const nsCString& aCommand, FifoCallback aCallback)
285 MutexAutoLock lock(mFifoInfoLock);
287 for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); ++i) {
288 if (mFifoInfo[i].mCommand.Equals(aCommand)) {
289 LOG("Register command(%s) callback failed! (DUPLICATE)", aCommand.get());
290 return;
293 FifoInfo aFifoInfo = { aCommand, aCallback };
294 mFifoInfo.AppendElement(aFifoInfo);
297 FifoWatcher::~FifoWatcher()
302 FifoWatcher::OpenFd()
304 // If the memory_info_dumper.directory pref is specified, put the fifo
305 // there. Otherwise, put it into the system's tmp directory.
307 nsCOMPtr<nsIFile> file;
309 nsresult rv;
310 if (mDirPath.Length() > 0) {
311 rv = XRE_GetFileFromPath(mDirPath.get(), getter_AddRefs(file));
312 if (NS_FAILED(rv)) {
313 LOG("FifoWatcher failed to open file \"%s\"", mDirPath.get());
314 return -1;
316 } else {
317 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file));
318 if (NS_WARN_IF(NS_FAILED(rv))) {
319 return -1;
323 rv = file->AppendNative(NS_LITERAL_CSTRING("debug_info_trigger"));
324 if (NS_WARN_IF(NS_FAILED(rv))) {
325 return -1;
328 nsAutoCString path;
329 rv = file->GetNativePath(path);
330 if (NS_WARN_IF(NS_FAILED(rv))) {
331 return -1;
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 if (unlink(path.get())) {
338 LOG("FifoWatcher::OpenFifo unlink failed; errno=%d. "
339 "Continuing despite error.", errno);
342 if (mkfifo(path.get(), 0766)) {
343 LOG("FifoWatcher::OpenFifo mkfifo failed; errno=%d", errno);
344 return -1;
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
353 int fd;
354 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 fd = open(path.get(), O_RDONLY | O_NONBLOCK);
360 } while (fd == -1 && errno == EINTR);
362 if (fd == -1) {
363 LOG("FifoWatcher::OpenFifo open failed; errno=%d", errno);
364 return -1;
367 // Make fd blocking now that we've opened it.
368 if (fcntl(fd, F_SETFL, 0)) {
369 close(fd);
370 return -1;
373 return fd;
376 void
377 FifoWatcher::OnFileCanReadWithoutBlocking(int aFd)
379 MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
381 char buf[1024];
382 int nread;
383 do {
384 // sizeof(buf) - 1 to leave space for the null-terminator.
385 nread = read(aFd, buf, sizeof(buf));
386 } while (nread == -1 && errno == EINTR);
388 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 StopWatching();
394 return;
397 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.
402 LOG("FifoWatcher closing and re-opening fifo.");
403 StopWatching();
404 StartWatching();
405 return;
408 nsAutoCString inputStr;
409 inputStr.Append(buf, nread);
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 inputStr.Trim("\b\t\r\n");
417 MutexAutoLock lock(mFifoInfoLock);
419 for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); i++) {
420 const nsCString commandStr = mFifoInfo[i].mCommand;
421 if (inputStr == commandStr.get()) {
422 mFifoInfo[i].mCallback(inputStr);
423 return;
427 LOG("Got unexpected value from fifo; ignoring it.");
430 #endif // XP_UNIX }
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 nsDumpUtils::OpenTempFile(const nsACString& aFilename, nsIFile** aFile,
437 const nsACString& aFoldername)
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);
448 #endif
449 nsresult rv;
450 if (!*aFile) {
451 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, aFile);
452 if (NS_WARN_IF(NS_FAILED(rv))) {
453 return rv;
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;
468 // It's OK if this fails; that probably just means that the directory already
469 // exists.
470 (*aFile)->Create(nsIFile::DIRECTORY_TYPE, 0777);
472 nsAutoCString dirPath;
473 rv = (*aFile)->GetNativePath(dirPath);
474 if (NS_WARN_IF(NS_FAILED(rv))) {
475 return rv;
478 while (chmod(dirPath.get(), 0777) == -1 && errno == EINTR)
482 #endif
484 nsCOMPtr<nsIFile> file(*aFile);
486 rv = file->AppendNative(aFilename);
487 if (NS_WARN_IF(NS_FAILED(rv))) {
488 return rv;
491 rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666);
492 if (NS_WARN_IF(NS_FAILED(rv))) {
493 return rv;
496 #ifdef ANDROID
497 // Make this file world-read/writable; the permissions passed to the
498 // CreateUnique call above are not sufficient on Android, which runs with a
499 // umask.
500 nsAutoCString path;
501 rv = file->GetNativePath(path);
502 if (NS_WARN_IF(NS_FAILED(rv))) {
503 return rv;
506 while (chmod(path.get(), 0666) == -1 && errno == EINTR)
509 #endif
511 return NS_OK;