/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 | } |