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