Line data Source code
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* const FifoWatcher::kPrefName =
53 : "memory_info_dumper.watch_fifo.enabled";
54 :
55 : static void
56 0 : DumpSignalHandler(int aSignum)
57 : {
58 : // This is a signal handler, so everything in here needs to be
59 : // async-signal-safe. Be careful!
60 :
61 0 : if (sDumpPipeWriteFd != -1) {
62 0 : uint8_t signum = static_cast<int>(aSignum);
63 0 : Unused << write(sDumpPipeWriteFd, &signum, sizeof(signum));
64 : }
65 0 : }
66 :
67 12 : NS_IMPL_ISUPPORTS(FdWatcher, nsIObserver);
68 :
69 : void
70 3 : FdWatcher::Init()
71 : {
72 3 : MOZ_ASSERT(NS_IsMainThread());
73 :
74 6 : nsCOMPtr<nsIObserverService> os = services::GetObserverService();
75 3 : os->AddObserver(this, "xpcom-shutdown", /* ownsWeak = */ false);
76 :
77 6 : 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 3 : FdWatcher::StartWatching()
86 : {
87 3 : MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
88 3 : MOZ_ASSERT(mFd == -1);
89 :
90 3 : mFd = OpenFd();
91 3 : if (mFd == -1) {
92 : LOG("FdWatcher: OpenFd failed.");
93 0 : return;
94 : }
95 :
96 3 : MessageLoopForIO::current()->WatchFileDescriptor(
97 : mFd, /* persistent = */ true,
98 : MessageLoopForIO::WATCH_READ,
99 3 : &mReadWatcher, this);
100 : }
101 :
102 : // Since implementations can call StartWatching() multiple times, they can of
103 : // course call StopWatching() multiple times.
104 : void
105 0 : FdWatcher::StopWatching()
106 : {
107 0 : MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
108 :
109 0 : mReadWatcher.StopWatchingFileDescriptor();
110 0 : if (mFd != -1) {
111 0 : close(mFd);
112 0 : mFd = -1;
113 : }
114 0 : }
115 :
116 3 : StaticRefPtr<SignalPipeWatcher> SignalPipeWatcher::sSingleton;
117 :
118 : /* static */ SignalPipeWatcher*
119 3 : SignalPipeWatcher::GetSingleton()
120 : {
121 3 : if (!sSingleton) {
122 3 : sSingleton = new SignalPipeWatcher();
123 3 : sSingleton->Init();
124 3 : ClearOnShutdown(&sSingleton);
125 : }
126 3 : return sSingleton;
127 : }
128 :
129 : void
130 9 : SignalPipeWatcher::RegisterCallback(uint8_t aSignal,
131 : PipeCallback aCallback)
132 : {
133 18 : MutexAutoLock lock(mSignalInfoLock);
134 :
135 18 : for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); ++i) {
136 9 : if (mSignalInfo[i].mSignal == aSignal) {
137 : LOG("Register Signal(%d) callback failed! (DUPLICATE)", aSignal);
138 0 : return;
139 : }
140 : }
141 9 : SignalInfo signalInfo = { aSignal, aCallback };
142 9 : mSignalInfo.AppendElement(signalInfo);
143 9 : RegisterSignalHandler(signalInfo.mSignal);
144 : }
145 :
146 : void
147 12 : SignalPipeWatcher::RegisterSignalHandler(uint8_t aSignal)
148 : {
149 : struct sigaction action;
150 12 : memset(&action, 0, sizeof(action));
151 12 : sigemptyset(&action.sa_mask);
152 12 : action.sa_handler = DumpSignalHandler;
153 :
154 12 : if (aSignal) {
155 9 : if (sigaction(aSignal, &action, nullptr)) {
156 : LOG("SignalPipeWatcher failed to register sig %d.", aSignal);
157 : }
158 : } else {
159 6 : 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 : LOG("SignalPipeWatcher failed to register signal(%d) "
163 : "dump signal handler.", mSignalInfo[i].mSignal);
164 : }
165 : }
166 : }
167 12 : }
168 :
169 0 : SignalPipeWatcher::~SignalPipeWatcher()
170 : {
171 0 : if (sDumpPipeWriteFd != -1) {
172 0 : StopWatching();
173 : }
174 0 : }
175 :
176 : int
177 3 : SignalPipeWatcher::OpenFd()
178 : {
179 3 : MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
180 :
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 3 : if (pipe(pipeFds)) {
185 : LOG("SignalPipeWatcher failed to create pipe.");
186 0 : return -1;
187 : }
188 :
189 : // 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 :
193 3 : int readFd = pipeFds[0];
194 3 : sDumpPipeWriteFd = pipeFds[1];
195 :
196 3 : RegisterSignalHandler();
197 3 : return readFd;
198 : }
199 :
200 : void
201 0 : SignalPipeWatcher::StopWatching()
202 : {
203 0 : MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
204 :
205 : // Close sDumpPipeWriteFd /after/ setting the fd to -1.
206 : // Otherwise we have the (admittedly far-fetched) race where we
207 : //
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 0 : int pipeWriteFd = sDumpPipeWriteFd.exchange(-1);
213 0 : close(pipeWriteFd);
214 :
215 0 : FdWatcher::StopWatching();
216 0 : }
217 :
218 : void
219 0 : SignalPipeWatcher::OnFileCanReadWithoutBlocking(int aFd)
220 : {
221 0 : MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
222 :
223 : uint8_t signum;
224 0 : ssize_t numReceived = read(aFd, &signum, sizeof(signum));
225 0 : if (numReceived != sizeof(signum)) {
226 : LOG("Error reading from buffer in "
227 : "SignalPipeWatcher::OnFileCanReadWithoutBlocking.");
228 0 : return;
229 : }
230 :
231 : {
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 : }
238 : }
239 : }
240 : LOG("SignalPipeWatcher got unexpected signum.");
241 : }
242 :
243 3 : StaticRefPtr<FifoWatcher> FifoWatcher::sSingleton;
244 :
245 : /* static */ FifoWatcher*
246 0 : FifoWatcher::GetSingleton()
247 : {
248 0 : if (!sSingleton) {
249 0 : nsAutoCString dirPath;
250 : Preferences::GetCString(
251 0 : "memory_info_dumper.watch_fifo.directory", &dirPath);
252 0 : sSingleton = new FifoWatcher(dirPath);
253 0 : sSingleton->Init();
254 0 : ClearOnShutdown(&sSingleton);
255 : }
256 0 : return sSingleton;
257 : }
258 :
259 : /* static */ bool
260 3 : FifoWatcher::MaybeCreate()
261 : {
262 3 : MOZ_ASSERT(NS_IsMainThread());
263 :
264 3 : if (!XRE_IsParentProcess()) {
265 : // We want this to be main-process only, since two processes can't listen
266 : // to the same fifo.
267 2 : return false;
268 : }
269 :
270 1 : if (!Preferences::GetBool(kPrefName, false)) {
271 : LOG("Fifo watcher disabled via pref.");
272 1 : return false;
273 : }
274 :
275 : // The FifoWatcher is held alive by the observer service.
276 0 : if (!sSingleton) {
277 0 : GetSingleton();
278 : }
279 0 : return true;
280 : }
281 :
282 : void
283 0 : FifoWatcher::RegisterCallback(const nsCString& aCommand, FifoCallback aCallback)
284 : {
285 0 : MutexAutoLock lock(mFifoInfoLock);
286 :
287 0 : for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); ++i) {
288 0 : if (mFifoInfo[i].mCommand.Equals(aCommand)) {
289 : LOG("Register command(%s) callback failed! (DUPLICATE)", aCommand.get());
290 0 : return;
291 : }
292 : }
293 0 : FifoInfo aFifoInfo = { aCommand, aCallback };
294 0 : mFifoInfo.AppendElement(aFifoInfo);
295 : }
296 :
297 0 : FifoWatcher::~FifoWatcher()
298 : {
299 0 : }
300 :
301 : int
302 0 : FifoWatcher::OpenFd()
303 : {
304 : // If the memory_info_dumper.directory pref is specified, put the fifo
305 : // there. Otherwise, put it into the system's tmp directory.
306 :
307 0 : nsCOMPtr<nsIFile> file;
308 :
309 : nsresult rv;
310 0 : if (mDirPath.Length() > 0) {
311 0 : rv = XRE_GetFileFromPath(mDirPath.get(), getter_AddRefs(file));
312 0 : if (NS_FAILED(rv)) {
313 : LOG("FifoWatcher failed to open file \"%s\"", mDirPath.get());
314 0 : return -1;
315 : }
316 : } else {
317 0 : rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file));
318 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
319 0 : return -1;
320 : }
321 : }
322 :
323 0 : rv = file->AppendNative(NS_LITERAL_CSTRING("debug_info_trigger"));
324 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
325 0 : return -1;
326 : }
327 :
328 0 : nsAutoCString path;
329 0 : rv = file->GetNativePath(path);
330 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
331 0 : return -1;
332 : }
333 :
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 0 : if (unlink(path.get())) {
338 : LOG("FifoWatcher::OpenFifo unlink failed; errno=%d. "
339 : "Continuing despite error.", errno);
340 : }
341 :
342 0 : if (mkfifo(path.get(), 0766)) {
343 : LOG("FifoWatcher::OpenFifo mkfifo failed; errno=%d", errno);
344 0 : return -1;
345 : }
346 :
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
352 :
353 : int fd;
354 0 : 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 0 : fd = open(path.get(), O_RDONLY | O_NONBLOCK);
360 0 : } while (fd == -1 && errno == EINTR);
361 :
362 0 : if (fd == -1) {
363 : LOG("FifoWatcher::OpenFifo open failed; errno=%d", errno);
364 0 : return -1;
365 : }
366 :
367 : // Make fd blocking now that we've opened it.
368 0 : if (fcntl(fd, F_SETFL, 0)) {
369 0 : close(fd);
370 0 : return -1;
371 : }
372 :
373 0 : return fd;
374 : }
375 :
376 : void
377 0 : FifoWatcher::OnFileCanReadWithoutBlocking(int aFd)
378 : {
379 0 : MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
380 :
381 : char buf[1024];
382 : int nread;
383 0 : do {
384 : // sizeof(buf) - 1 to leave space for the null-terminator.
385 0 : nread = read(aFd, buf, sizeof(buf));
386 0 : } while (nread == -1 && errno == EINTR);
387 :
388 0 : 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 0 : StopWatching();
394 0 : return;
395 : }
396 :
397 0 : 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.
401 :
402 : LOG("FifoWatcher closing and re-opening fifo.");
403 0 : StopWatching();
404 0 : StartWatching();
405 0 : return;
406 : }
407 :
408 0 : nsAutoCString inputStr;
409 0 : inputStr.Append(buf, nread);
410 :
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 0 : inputStr.Trim("\b\t\r\n");
415 :
416 : {
417 0 : MutexAutoLock lock(mFifoInfoLock);
418 :
419 0 : for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); i++) {
420 0 : const nsCString commandStr = mFifoInfo[i].mCommand;
421 0 : if (inputStr == commandStr.get()) {
422 0 : mFifoInfo[i].mCallback(inputStr);
423 0 : return;
424 : }
425 : }
426 : }
427 : LOG("Got unexpected value from fifo; ignoring it.");
428 : }
429 :
430 : #endif // XP_UNIX }
431 :
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 0 : nsDumpUtils::OpenTempFile(const nsACString& aFilename, nsIFile** aFile,
437 : const nsACString& aFoldername, Mode aMode)
438 : {
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);
446 : }
447 : }
448 : #endif
449 : nsresult rv;
450 0 : if (!*aFile) {
451 0 : rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, aFile);
452 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
453 0 : return rv;
454 : }
455 : }
456 :
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;
466 : }
467 :
468 : // It's OK if this fails; that probably just means that the directory
469 : // already exists.
470 : Unused << (*aFile)->Create(nsIFile::DIRECTORY_TYPE, 0777);
471 :
472 : nsAutoCString dirPath;
473 : rv = (*aFile)->GetNativePath(dirPath);
474 : if (NS_WARN_IF(NS_FAILED(rv))) {
475 : return rv;
476 : }
477 :
478 : while (chmod(dirPath.get(), 0777) == -1 && errno == EINTR) {
479 : }
480 : }
481 : #endif
482 :
483 0 : nsCOMPtr<nsIFile> file(*aFile);
484 :
485 0 : rv = file->AppendNative(aFilename);
486 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
487 0 : return rv;
488 : }
489 :
490 0 : if (aMode == CREATE_UNIQUE) {
491 0 : rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666);
492 : } else {
493 0 : rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
494 : }
495 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
496 0 : return rv;
497 : }
498 :
499 : #ifdef ANDROID
500 : // Make this file world-read/writable; the permissions passed to the
501 : // CreateUnique call above are not sufficient on Android, which runs with a
502 : // umask.
503 : nsAutoCString path;
504 : rv = file->GetNativePath(path);
505 : if (NS_WARN_IF(NS_FAILED(rv))) {
506 : return rv;
507 : }
508 :
509 : while (chmod(path.get(), 0666) == -1 && errno == EINTR) {
510 : }
511 : #endif
512 :
513 0 : return NS_OK;
514 : }
|