Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / xpcom / base / nsDumpUtils.cpp
blobc7bbbf4eb38a13fa5e6932b79a080024fd717927
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 <errno.h>
11 #include "prenv.h"
12 #include "mozilla/Services.h"
13 #include "nsIObserverService.h"
14 #include "mozilla/ClearOnShutdown.h"
15 #include "mozilla/Unused.h"
16 #include "SpecialSystemDirectory.h"
18 #ifdef XP_UNIX // {
19 # include "mozilla/Preferences.h"
20 # include <fcntl.h>
21 # include <unistd.h>
22 # include <sys/stat.h>
24 using namespace mozilla;
27 * The following code supports triggering a registered callback upon
28 * receiving a specific signal.
30 * Take about:memory for example, we register
31 * 1. doGCCCDump for doMemoryReport
32 * 2. doMemoryReport for sDumpAboutMemorySignum(SIGRTMIN)
33 * and sDumpAboutMemoryAfterMMUSignum(SIGRTMIN+1).
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.
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.
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).
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);
52 const char FifoWatcher::kPrefName[] = "memory_info_dumper.watch_fifo.enabled";
54 static void DumpSignalHandler(int aSignum) {
55 // This is a signal handler, so everything in here needs to be
56 // async-signal-safe. Be careful!
58 if (sDumpPipeWriteFd != -1) {
59 uint8_t signum = static_cast<int>(aSignum);
60 Unused << write(sDumpPipeWriteFd, &signum, sizeof(signum));
64 NS_IMPL_ISUPPORTS(FdWatcher, nsIObserver);
66 void FdWatcher::Init() {
67 MOZ_ASSERT(NS_IsMainThread());
69 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
70 os->AddObserver(this, "xpcom-shutdown", /* ownsWeak = */ false);
72 XRE_GetIOMessageLoop()->PostTask(NewRunnableMethod(
73 "FdWatcher::StartWatching", this, &FdWatcher::StartWatching));
76 // Implementations may call this function multiple times if they ensure that
77 // it's safe to call OpenFd() multiple times and they call StopWatching()
78 // first.
79 void FdWatcher::StartWatching() {
80 MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
81 MOZ_ASSERT(mFd == -1);
83 mFd = OpenFd();
84 if (mFd == -1) {
85 LOG("FdWatcher: OpenFd failed.");
86 return;
89 MessageLoopForIO::current()->WatchFileDescriptor(mFd, /* persistent = */ true,
90 MessageLoopForIO::WATCH_READ,
91 &mReadWatcher, this);
94 // Since implementations can call StartWatching() multiple times, they can of
95 // course call StopWatching() multiple times.
96 void FdWatcher::StopWatching() {
97 MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
99 mReadWatcher.StopWatchingFileDescriptor();
100 if (mFd != -1) {
101 close(mFd);
102 mFd = -1;
106 StaticRefPtr<SignalPipeWatcher> SignalPipeWatcher::sSingleton;
108 /* static */
109 SignalPipeWatcher* SignalPipeWatcher::GetSingleton() {
110 if (!sSingleton) {
111 sSingleton = new SignalPipeWatcher();
112 sSingleton->Init();
113 ClearOnShutdown(&sSingleton);
115 return sSingleton;
118 void SignalPipeWatcher::RegisterCallback(uint8_t aSignal,
119 PipeCallback aCallback) {
120 MutexAutoLock lock(mSignalInfoLock);
122 for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); ++i) {
123 if (mSignalInfo[i].mSignal == aSignal) {
124 LOG("Register Signal(%d) callback failed! (DUPLICATE)", aSignal);
125 return;
128 SignalInfo signalInfo = {aSignal, aCallback};
129 mSignalInfo.AppendElement(signalInfo);
130 RegisterSignalHandler(signalInfo.mSignal);
133 void SignalPipeWatcher::RegisterSignalHandler(uint8_t aSignal) {
134 struct sigaction action;
135 memset(&action, 0, sizeof(action));
136 sigemptyset(&action.sa_mask);
137 action.sa_handler = DumpSignalHandler;
139 if (aSignal) {
140 if (sigaction(aSignal, &action, nullptr)) {
141 LOG("SignalPipeWatcher failed to register sig %d.", aSignal);
143 } else {
144 MutexAutoLock lock(mSignalInfoLock);
145 for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) {
146 if (sigaction(mSignalInfo[i].mSignal, &action, nullptr)) {
147 LOG("SignalPipeWatcher failed to register signal(%d) "
148 "dump signal handler.",
149 mSignalInfo[i].mSignal);
155 SignalPipeWatcher::~SignalPipeWatcher() {
156 if (sDumpPipeWriteFd != -1) {
157 StopWatching();
161 int SignalPipeWatcher::OpenFd() {
162 MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
164 // Create a pipe. When we receive a signal in our signal handler, we'll
165 // write the signum to the write-end of this pipe.
166 int pipeFds[2];
167 if (pipe(pipeFds)) {
168 LOG("SignalPipeWatcher failed to create pipe.");
169 return -1;
172 // Close this pipe on calls to exec().
173 fcntl(pipeFds[0], F_SETFD, FD_CLOEXEC);
174 fcntl(pipeFds[1], F_SETFD, FD_CLOEXEC);
176 int readFd = pipeFds[0];
177 sDumpPipeWriteFd = pipeFds[1];
179 RegisterSignalHandler();
180 return readFd;
183 void SignalPipeWatcher::StopWatching() {
184 MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
186 // Close sDumpPipeWriteFd /after/ setting the fd to -1.
187 // Otherwise we have the (admittedly far-fetched) race where we
189 // 1) close sDumpPipeWriteFd
190 // 2) open a new fd with the same number as sDumpPipeWriteFd
191 // had.
192 // 3) receive a signal, then write to the fd.
193 int pipeWriteFd = sDumpPipeWriteFd.exchange(-1);
194 close(pipeWriteFd);
196 FdWatcher::StopWatching();
199 void SignalPipeWatcher::OnFileCanReadWithoutBlocking(int aFd) {
200 MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
202 uint8_t signum;
203 ssize_t numReceived = read(aFd, &signum, sizeof(signum));
204 if (numReceived != sizeof(signum)) {
205 LOG("Error reading from buffer in "
206 "SignalPipeWatcher::OnFileCanReadWithoutBlocking.");
207 return;
211 MutexAutoLock lock(mSignalInfoLock);
212 for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) {
213 if (signum == mSignalInfo[i].mSignal) {
214 mSignalInfo[i].mCallback(signum);
215 return;
219 LOG("SignalPipeWatcher got unexpected signum.");
222 StaticRefPtr<FifoWatcher> FifoWatcher::sSingleton;
224 /* static */
225 FifoWatcher* FifoWatcher::GetSingleton() {
226 if (!sSingleton) {
227 nsAutoCString dirPath;
228 Preferences::GetCString("memory_info_dumper.watch_fifo.directory", dirPath);
229 sSingleton = new FifoWatcher(dirPath);
230 sSingleton->Init();
231 ClearOnShutdown(&sSingleton);
233 return sSingleton;
236 /* static */
237 bool FifoWatcher::MaybeCreate() {
238 MOZ_ASSERT(NS_IsMainThread());
240 if (!XRE_IsParentProcess()) {
241 // We want this to be main-process only, since two processes can't listen
242 // to the same fifo.
243 return false;
246 if (!Preferences::GetBool(kPrefName, false)) {
247 LOG("Fifo watcher disabled via pref.");
248 return false;
251 // The FifoWatcher is held alive by the observer service.
252 if (!sSingleton) {
253 GetSingleton();
255 return true;
258 void FifoWatcher::RegisterCallback(const nsCString& aCommand,
259 FifoCallback aCallback) {
260 MutexAutoLock lock(mFifoInfoLock);
262 for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); ++i) {
263 if (mFifoInfo[i].mCommand.Equals(aCommand)) {
264 LOG("Register command(%s) callback failed! (DUPLICATE)", aCommand.get());
265 return;
268 FifoInfo aFifoInfo = {aCommand, aCallback};
269 mFifoInfo.AppendElement(aFifoInfo);
272 FifoWatcher::~FifoWatcher() = default;
274 int FifoWatcher::OpenFd() {
275 // If the memory_info_dumper.directory pref is specified, put the fifo
276 // there. Otherwise, put it into the system's tmp directory.
278 nsCOMPtr<nsIFile> file;
280 nsresult rv;
281 if (mDirPath.Length() > 0) {
282 rv = XRE_GetFileFromPath(mDirPath.get(), getter_AddRefs(file));
283 if (NS_FAILED(rv)) {
284 LOG("FifoWatcher failed to open file \"%s\"", mDirPath.get());
285 return -1;
287 } else {
288 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file));
289 if (NS_WARN_IF(NS_FAILED(rv))) {
290 return -1;
294 rv = file->AppendNative("debug_info_trigger"_ns);
295 if (NS_WARN_IF(NS_FAILED(rv))) {
296 return -1;
299 nsAutoCString path;
300 rv = file->GetNativePath(path);
301 if (NS_WARN_IF(NS_FAILED(rv))) {
302 return -1;
305 // unlink might fail because the file doesn't exist, or for other reasons.
306 // But we don't care it fails; any problems will be detected later, when we
307 // try to mkfifo or open the file.
308 if (unlink(path.get())) {
309 LOG("FifoWatcher::OpenFifo unlink failed; errno=%d. "
310 "Continuing despite error.",
311 errno);
314 if (mkfifo(path.get(), 0766)) {
315 LOG("FifoWatcher::OpenFifo mkfifo failed; errno=%d", errno);
316 return -1;
319 # ifdef ANDROID
320 // Android runs with a umask, so we need to chmod our fifo to make it
321 // world-writable.
322 chmod(path.get(), 0666);
323 # endif
325 int fd;
326 do {
327 // The fifo will block until someone else has written to it. In
328 // particular, open() will block until someone else has opened it for
329 // writing! We want open() to succeed and read() to block, so we open
330 // with NONBLOCK and then fcntl that away.
331 fd = open(path.get(), O_RDONLY | O_NONBLOCK);
332 } while (fd == -1 && errno == EINTR);
334 if (fd == -1) {
335 LOG("FifoWatcher::OpenFifo open failed; errno=%d", errno);
336 return -1;
339 // Make fd blocking now that we've opened it.
340 if (fcntl(fd, F_SETFL, 0)) {
341 close(fd);
342 return -1;
345 return fd;
348 void FifoWatcher::OnFileCanReadWithoutBlocking(int aFd) {
349 MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
351 char buf[1024];
352 int nread;
353 do {
354 // sizeof(buf) - 1 to leave space for the null-terminator.
355 nread = read(aFd, buf, sizeof(buf));
356 } while (nread == -1 && errno == EINTR);
358 if (nread == -1) {
359 // We want to avoid getting into a situation where
360 // OnFileCanReadWithoutBlocking is called in an infinite loop, so when
361 // something goes wrong, stop watching the fifo altogether.
362 LOG("FifoWatcher hit an error (%d) and is quitting.", errno);
363 StopWatching();
364 return;
367 if (nread == 0) {
368 // If we get EOF, that means that the other side closed the fifo. We need
369 // to close and re-open the fifo; if we don't,
370 // OnFileCanWriteWithoutBlocking will be called in an infinite loop.
372 LOG("FifoWatcher closing and re-opening fifo.");
373 StopWatching();
374 StartWatching();
375 return;
378 nsAutoCString inputStr;
379 inputStr.Append(buf, nread);
381 // Trimming whitespace is important because if you do
382 // |echo "foo" >> debug_info_trigger|,
383 // it'll actually write "foo\n" to the fifo.
384 inputStr.Trim("\b\t\r\n");
387 MutexAutoLock lock(mFifoInfoLock);
389 for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); i++) {
390 const nsCString commandStr = mFifoInfo[i].mCommand;
391 if (inputStr == commandStr.get()) {
392 mFifoInfo[i].mCallback(inputStr);
393 return;
397 LOG("Got unexpected value from fifo; ignoring it.");
400 #endif // XP_UNIX }
402 // In Android case, this function will open a file named aFilename under
403 // /data/local/tmp/"aFoldername".
404 // Otherwise, it will open a file named aFilename under "NS_OS_TEMP_DIR".
405 /* static */
406 nsresult nsDumpUtils::OpenTempFile(const nsACString& aFilename, nsIFile** aFile,
407 const nsACString& aFoldername, Mode aMode) {
408 #ifdef ANDROID
409 // For Android, first try the downloads directory which is world-readable
410 // rather than the temp directory which is not.
411 if (!*aFile) {
412 char* env = PR_GetEnv("DOWNLOADS_DIRECTORY");
413 if (env) {
414 NS_NewNativeLocalFile(nsCString(env), /* followLinks = */ true, aFile);
417 #endif
418 nsresult rv;
419 if (!*aFile) {
420 if (NS_IsMainThread()) {
421 // This allows tests to override, but isn't safe off-mainthread.
422 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, aFile);
423 } else {
424 rv = GetSpecialSystemDirectory(OS_TemporaryDirectory, aFile);
426 if (NS_WARN_IF(NS_FAILED(rv))) {
427 return rv;
431 #ifdef ANDROID
432 // /data/local/tmp is a true tmp directory; anyone can create a file there,
433 // but only the user which created the file can remove it. We want non-root
434 // users to be able to remove these files, so we write them into a
435 // subdirectory of the temp directory and chmod 777 that directory.
436 if (!aFoldername.IsEmpty()) {
437 rv = (*aFile)->AppendNative(aFoldername);
438 if (NS_WARN_IF(NS_FAILED(rv))) {
439 return rv;
442 // It's OK if this fails; that probably just means that the directory
443 // already exists.
444 Unused << (*aFile)->Create(nsIFile::DIRECTORY_TYPE, 0777);
446 nsAutoCString dirPath;
447 rv = (*aFile)->GetNativePath(dirPath);
448 if (NS_WARN_IF(NS_FAILED(rv))) {
449 return rv;
452 while (chmod(dirPath.get(), 0777) == -1 && errno == EINTR) {
455 #endif
457 nsCOMPtr<nsIFile> file(*aFile);
459 rv = file->AppendNative(aFilename);
460 if (NS_WARN_IF(NS_FAILED(rv))) {
461 return rv;
464 if (aMode == CREATE_UNIQUE) {
465 rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666);
466 } else {
467 rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
469 if (NS_WARN_IF(NS_FAILED(rv))) {
470 return rv;
473 #ifdef ANDROID
474 // Make this file world-read/writable; the permissions passed to the
475 // CreateUnique call above are not sufficient on Android, which runs with a
476 // umask.
477 nsAutoCString path;
478 rv = file->GetNativePath(path);
479 if (NS_WARN_IF(NS_FAILED(rv))) {
480 return rv;
483 while (chmod(path.get(), 0666) == -1 && errno == EINTR) {
485 #endif
487 return NS_OK;