Coverage Report

Created: 2018-09-25 14:53

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