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