1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/xpcom/base/nsDumpUtils.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,493 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "nsDumpUtils.h" 1.11 +#include "nsDirectoryServiceDefs.h" 1.12 +#include "nsDirectoryServiceUtils.h" 1.13 +#include "prenv.h" 1.14 +#include <errno.h> 1.15 +#include "mozilla/Services.h" 1.16 +#include "nsIObserverService.h" 1.17 +#include "mozilla/ClearOnShutdown.h" 1.18 + 1.19 +#ifdef XP_UNIX // { 1.20 +#include "mozilla/Preferences.h" 1.21 +#include <fcntl.h> 1.22 +#include <unistd.h> 1.23 +#include <sys/types.h> 1.24 +#include <sys/stat.h> 1.25 + 1.26 +using namespace mozilla; 1.27 + 1.28 +/* 1.29 + * The following code supports triggering a registered callback upon 1.30 + * receiving a specific signal. 1.31 + * 1.32 + * Take about:memory for example, we register 1.33 + * 1. doGCCCDump for doMemoryReport 1.34 + * 2. doMemoryReport for sDumpAboutMemorySignum(SIGRTMIN) 1.35 + * and sDumpAboutMemoryAfterMMUSignum(SIGRTMIN+1). 1.36 + * 1.37 + * When we receive one of these signals, we write the signal number to a pipe. 1.38 + * The IO thread then notices that the pipe has been written to, and kicks off 1.39 + * the appropriate task on the main thread. 1.40 + * 1.41 + * This scheme is similar to using signalfd(), except it's portable and it 1.42 + * doesn't require the use of sigprocmask, which is problematic because it 1.43 + * masks signals received by child processes. 1.44 + * 1.45 + * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this. 1.46 + * But that uses libevent, which does not handle the realtime signals (bug 1.47 + * 794074). 1.48 + */ 1.49 + 1.50 +// This is the write-end of a pipe that we use to notice when a 1.51 +// specific signal occurs. 1.52 +static Atomic<int> sDumpPipeWriteFd(-1); 1.53 + 1.54 +const char* const FifoWatcher::kPrefName = 1.55 + "memory_info_dumper.watch_fifo.enabled"; 1.56 + 1.57 +static void 1.58 +DumpSignalHandler(int aSignum) 1.59 +{ 1.60 + // This is a signal handler, so everything in here needs to be 1.61 + // async-signal-safe. Be careful! 1.62 + 1.63 + if (sDumpPipeWriteFd != -1) { 1.64 + uint8_t signum = static_cast<int>(aSignum); 1.65 + write(sDumpPipeWriteFd, &signum, sizeof(signum)); 1.66 + } 1.67 +} 1.68 + 1.69 +NS_IMPL_ISUPPORTS(FdWatcher, nsIObserver); 1.70 + 1.71 +void FdWatcher::Init() 1.72 +{ 1.73 + MOZ_ASSERT(NS_IsMainThread()); 1.74 + 1.75 + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 1.76 + os->AddObserver(this, "xpcom-shutdown", /* ownsWeak = */ false); 1.77 + 1.78 + XRE_GetIOMessageLoop()->PostTask( 1.79 + FROM_HERE, 1.80 + NewRunnableMethod(this, &FdWatcher::StartWatching)); 1.81 +} 1.82 + 1.83 +// Implementations may call this function multiple times if they ensure that 1.84 +// it's safe to call OpenFd() multiple times and they call StopWatching() 1.85 +// first. 1.86 +void FdWatcher::StartWatching() 1.87 +{ 1.88 + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); 1.89 + MOZ_ASSERT(mFd == -1); 1.90 + 1.91 + mFd = OpenFd(); 1.92 + if (mFd == -1) { 1.93 + LOG("FdWatcher: OpenFd failed."); 1.94 + return; 1.95 + } 1.96 + 1.97 + MessageLoopForIO::current()->WatchFileDescriptor( 1.98 + mFd, /* persistent = */ true, 1.99 + MessageLoopForIO::WATCH_READ, 1.100 + &mReadWatcher, this); 1.101 +} 1.102 + 1.103 +// Since implementations can call StartWatching() multiple times, they can of 1.104 +// course call StopWatching() multiple times. 1.105 +void FdWatcher::StopWatching() 1.106 +{ 1.107 + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); 1.108 + 1.109 + mReadWatcher.StopWatchingFileDescriptor(); 1.110 + if (mFd != -1) { 1.111 + close(mFd); 1.112 + mFd = -1; 1.113 + } 1.114 +} 1.115 + 1.116 +StaticRefPtr<SignalPipeWatcher> SignalPipeWatcher::sSingleton; 1.117 + 1.118 +/* static */ SignalPipeWatcher* 1.119 +SignalPipeWatcher::GetSingleton() 1.120 +{ 1.121 + if (!sSingleton) { 1.122 + sSingleton = new SignalPipeWatcher(); 1.123 + sSingleton->Init(); 1.124 + ClearOnShutdown(&sSingleton); 1.125 + } 1.126 + return sSingleton; 1.127 +} 1.128 + 1.129 +void 1.130 +SignalPipeWatcher::RegisterCallback(uint8_t aSignal, 1.131 + PipeCallback aCallback) 1.132 +{ 1.133 + MutexAutoLock lock(mSignalInfoLock); 1.134 + 1.135 + for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) 1.136 + { 1.137 + if (mSignalInfo[i].mSignal == aSignal) { 1.138 + LOG("Register Signal(%d) callback failed! (DUPLICATE)", aSignal); 1.139 + return; 1.140 + } 1.141 + } 1.142 + SignalInfo signalInfo = { aSignal, aCallback }; 1.143 + mSignalInfo.AppendElement(signalInfo); 1.144 + RegisterSignalHandler(signalInfo.mSignal); 1.145 +} 1.146 + 1.147 +void 1.148 +SignalPipeWatcher::RegisterSignalHandler(uint8_t aSignal) 1.149 +{ 1.150 + struct sigaction action; 1.151 + memset(&action, 0, sizeof(action)); 1.152 + sigemptyset(&action.sa_mask); 1.153 + action.sa_handler = DumpSignalHandler; 1.154 + 1.155 + if (aSignal) { 1.156 + if (sigaction(aSignal, &action, nullptr)) { 1.157 + LOG("SignalPipeWatcher failed to register sig %d.", aSignal); 1.158 + } 1.159 + } else { 1.160 + MutexAutoLock lock(mSignalInfoLock); 1.161 + for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) { 1.162 + if (sigaction(mSignalInfo[i].mSignal, &action, nullptr)) { 1.163 + LOG("SignalPipeWatcher failed to register signal(%d) " 1.164 + "dump signal handler.", mSignalInfo[i].mSignal); 1.165 + } 1.166 + } 1.167 + } 1.168 +} 1.169 + 1.170 +SignalPipeWatcher::~SignalPipeWatcher() 1.171 +{ 1.172 + if (sDumpPipeWriteFd != -1) { 1.173 + StopWatching(); 1.174 + } 1.175 +} 1.176 + 1.177 +int SignalPipeWatcher::OpenFd() 1.178 +{ 1.179 + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); 1.180 + 1.181 + // Create a pipe. When we receive a signal in our signal handler, we'll 1.182 + // write the signum to the write-end of this pipe. 1.183 + int pipeFds[2]; 1.184 + if (pipe(pipeFds)) { 1.185 + LOG("SignalPipeWatcher failed to create pipe."); 1.186 + return -1; 1.187 + } 1.188 + 1.189 + // Close this pipe on calls to exec(). 1.190 + fcntl(pipeFds[0], F_SETFD, FD_CLOEXEC); 1.191 + fcntl(pipeFds[1], F_SETFD, FD_CLOEXEC); 1.192 + 1.193 + int readFd = pipeFds[0]; 1.194 + sDumpPipeWriteFd = pipeFds[1]; 1.195 + 1.196 + RegisterSignalHandler(); 1.197 + return readFd; 1.198 +} 1.199 + 1.200 +void SignalPipeWatcher::StopWatching() 1.201 +{ 1.202 + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); 1.203 + 1.204 + // Close sDumpPipeWriteFd /after/ setting the fd to -1. 1.205 + // Otherwise we have the (admittedly far-fetched) race where we 1.206 + // 1.207 + // 1) close sDumpPipeWriteFd 1.208 + // 2) open a new fd with the same number as sDumpPipeWriteFd 1.209 + // had. 1.210 + // 3) receive a signal, then write to the fd. 1.211 + int pipeWriteFd = sDumpPipeWriteFd.exchange(-1); 1.212 + close(pipeWriteFd); 1.213 + 1.214 + FdWatcher::StopWatching(); 1.215 +} 1.216 + 1.217 +void SignalPipeWatcher::OnFileCanReadWithoutBlocking(int aFd) 1.218 +{ 1.219 + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); 1.220 + 1.221 + uint8_t signum; 1.222 + ssize_t numReceived = read(aFd, &signum, sizeof(signum)); 1.223 + if (numReceived != sizeof(signum)) { 1.224 + LOG("Error reading from buffer in " 1.225 + "SignalPipeWatcher::OnFileCanReadWithoutBlocking."); 1.226 + return; 1.227 + } 1.228 + 1.229 + { 1.230 + MutexAutoLock lock(mSignalInfoLock); 1.231 + for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) { 1.232 + if(signum == mSignalInfo[i].mSignal) { 1.233 + mSignalInfo[i].mCallback(signum); 1.234 + return; 1.235 + } 1.236 + } 1.237 + } 1.238 + LOG("SignalPipeWatcher got unexpected signum."); 1.239 +} 1.240 + 1.241 +StaticRefPtr<FifoWatcher> FifoWatcher::sSingleton; 1.242 + 1.243 +/* static */ FifoWatcher* 1.244 +FifoWatcher::GetSingleton() 1.245 +{ 1.246 + if (!sSingleton) { 1.247 + nsAutoCString dirPath; 1.248 + Preferences::GetCString( 1.249 + "memory_info_dumper.watch_fifo.directory", &dirPath); 1.250 + sSingleton = new FifoWatcher(dirPath); 1.251 + sSingleton->Init(); 1.252 + ClearOnShutdown(&sSingleton); 1.253 + } 1.254 + return sSingleton; 1.255 +} 1.256 + 1.257 +/* static */ bool 1.258 +FifoWatcher::MaybeCreate() 1.259 +{ 1.260 + MOZ_ASSERT(NS_IsMainThread()); 1.261 + 1.262 + if (XRE_GetProcessType() != GeckoProcessType_Default) { 1.263 + // We want this to be main-process only, since two processes can't listen 1.264 + // to the same fifo. 1.265 + return false; 1.266 + } 1.267 + 1.268 + if (!Preferences::GetBool(kPrefName, false)) { 1.269 + LOG("Fifo watcher disabled via pref."); 1.270 + return false; 1.271 + } 1.272 + 1.273 + // The FifoWatcher is held alive by the observer service. 1.274 + if (!sSingleton) { 1.275 + GetSingleton(); 1.276 + } 1.277 + return true; 1.278 +} 1.279 + 1.280 +void 1.281 +FifoWatcher::RegisterCallback(const nsCString& aCommand, FifoCallback aCallback) 1.282 +{ 1.283 + MutexAutoLock lock(mFifoInfoLock); 1.284 + 1.285 + for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); i++) 1.286 + { 1.287 + if (mFifoInfo[i].mCommand.Equals(aCommand)) { 1.288 + LOG("Register command(%s) callback failed! (DUPLICATE)", aCommand.get()); 1.289 + return; 1.290 + } 1.291 + } 1.292 + FifoInfo aFifoInfo = { aCommand, aCallback }; 1.293 + mFifoInfo.AppendElement(aFifoInfo); 1.294 +} 1.295 + 1.296 +FifoWatcher::~FifoWatcher() 1.297 +{ 1.298 +} 1.299 + 1.300 +int FifoWatcher::OpenFd() 1.301 +{ 1.302 + // If the memory_info_dumper.directory pref is specified, put the fifo 1.303 + // there. Otherwise, put it into the system's tmp directory. 1.304 + 1.305 + nsCOMPtr<nsIFile> file; 1.306 + 1.307 + nsresult rv; 1.308 + if (mDirPath.Length() > 0) { 1.309 + rv = XRE_GetFileFromPath(mDirPath.get(), getter_AddRefs(file)); 1.310 + if (NS_FAILED(rv)) { 1.311 + LOG("FifoWatcher failed to open file \"%s\"", mDirPath.get()); 1.312 + return -1; 1.313 + } 1.314 + } else { 1.315 + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file)); 1.316 + if (NS_WARN_IF(NS_FAILED(rv))) 1.317 + return -1; 1.318 + } 1.319 + 1.320 + rv = file->AppendNative(NS_LITERAL_CSTRING("debug_info_trigger")); 1.321 + if (NS_WARN_IF(NS_FAILED(rv))) 1.322 + return -1; 1.323 + 1.324 + nsAutoCString path; 1.325 + rv = file->GetNativePath(path); 1.326 + if (NS_WARN_IF(NS_FAILED(rv))) 1.327 + return -1; 1.328 + 1.329 + // unlink might fail because the file doesn't exist, or for other reasons. 1.330 + // But we don't care it fails; any problems will be detected later, when we 1.331 + // try to mkfifo or open the file. 1.332 + if (unlink(path.get())) { 1.333 + LOG("FifoWatcher::OpenFifo unlink failed; errno=%d. " 1.334 + "Continuing despite error.", errno); 1.335 + } 1.336 + 1.337 + if (mkfifo(path.get(), 0766)) { 1.338 + LOG("FifoWatcher::OpenFifo mkfifo failed; errno=%d", errno); 1.339 + return -1; 1.340 + } 1.341 + 1.342 +#ifdef ANDROID 1.343 + // Android runs with a umask, so we need to chmod our fifo to make it 1.344 + // world-writable. 1.345 + chmod(path.get(), 0666); 1.346 +#endif 1.347 + 1.348 + int fd; 1.349 + do { 1.350 + // The fifo will block until someone else has written to it. In 1.351 + // particular, open() will block until someone else has opened it for 1.352 + // writing! We want open() to succeed and read() to block, so we open 1.353 + // with NONBLOCK and then fcntl that away. 1.354 + fd = open(path.get(), O_RDONLY | O_NONBLOCK); 1.355 + } while (fd == -1 && errno == EINTR); 1.356 + 1.357 + if (fd == -1) { 1.358 + LOG("FifoWatcher::OpenFifo open failed; errno=%d", errno); 1.359 + return -1; 1.360 + } 1.361 + 1.362 + // Make fd blocking now that we've opened it. 1.363 + if (fcntl(fd, F_SETFL, 0)) { 1.364 + close(fd); 1.365 + return -1; 1.366 + } 1.367 + 1.368 + return fd; 1.369 +} 1.370 + 1.371 +void FifoWatcher::OnFileCanReadWithoutBlocking(int aFd) 1.372 +{ 1.373 + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); 1.374 + 1.375 + char buf[1024]; 1.376 + int nread; 1.377 + do { 1.378 + // sizeof(buf) - 1 to leave space for the null-terminator. 1.379 + nread = read(aFd, buf, sizeof(buf)); 1.380 + } while(nread == -1 && errno == EINTR); 1.381 + 1.382 + if (nread == -1) { 1.383 + // We want to avoid getting into a situation where 1.384 + // OnFileCanReadWithoutBlocking is called in an infinite loop, so when 1.385 + // something goes wrong, stop watching the fifo altogether. 1.386 + LOG("FifoWatcher hit an error (%d) and is quitting.", errno); 1.387 + StopWatching(); 1.388 + return; 1.389 + } 1.390 + 1.391 + if (nread == 0) { 1.392 + // If we get EOF, that means that the other side closed the fifo. We need 1.393 + // to close and re-open the fifo; if we don't, 1.394 + // OnFileCanWriteWithoutBlocking will be called in an infinite loop. 1.395 + 1.396 + LOG("FifoWatcher closing and re-opening fifo."); 1.397 + StopWatching(); 1.398 + StartWatching(); 1.399 + return; 1.400 + } 1.401 + 1.402 + nsAutoCString inputStr; 1.403 + inputStr.Append(buf, nread); 1.404 + 1.405 + // Trimming whitespace is important because if you do 1.406 + // |echo "foo" >> debug_info_trigger|, 1.407 + // it'll actually write "foo\n" to the fifo. 1.408 + inputStr.Trim("\b\t\r\n"); 1.409 + 1.410 + { 1.411 + MutexAutoLock lock(mFifoInfoLock); 1.412 + 1.413 + for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); i++) { 1.414 + const nsCString commandStr = mFifoInfo[i].mCommand; 1.415 + if(inputStr == commandStr.get()) { 1.416 + mFifoInfo[i].mCallback(inputStr); 1.417 + return; 1.418 + } 1.419 + } 1.420 + } 1.421 + LOG("Got unexpected value from fifo; ignoring it."); 1.422 +} 1.423 + 1.424 +#endif // XP_UNIX } 1.425 + 1.426 +// In Android case, this function will open a file named aFilename under 1.427 +// /data/local/tmp/"aFoldername". 1.428 +// Otherwise, it will open a file named aFilename under "NS_OS_TEMP_DIR". 1.429 +/* static */ nsresult 1.430 +nsDumpUtils::OpenTempFile(const nsACString& aFilename, nsIFile** aFile, 1.431 + const nsACString& aFoldername) 1.432 +{ 1.433 +#ifdef ANDROID 1.434 + // For Android, first try the downloads directory which is world-readable 1.435 + // rather than the temp directory which is not. 1.436 + if (!*aFile) { 1.437 + char *env = PR_GetEnv("DOWNLOADS_DIRECTORY"); 1.438 + if (env) { 1.439 + NS_NewNativeLocalFile(nsCString(env), /* followLinks = */ true, aFile); 1.440 + } 1.441 + } 1.442 +#endif 1.443 + nsresult rv; 1.444 + if (!*aFile) { 1.445 + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, aFile); 1.446 + if (NS_WARN_IF(NS_FAILED(rv))) 1.447 + return rv; 1.448 + } 1.449 + 1.450 +#ifdef ANDROID 1.451 + // /data/local/tmp is a true tmp directory; anyone can create a file there, 1.452 + // but only the user which created the file can remove it. We want non-root 1.453 + // users to be able to remove these files, so we write them into a 1.454 + // subdirectory of the temp directory and chmod 777 that directory. 1.455 + if (aFoldername != EmptyCString()) { 1.456 + rv = (*aFile)->AppendNative(aFoldername); 1.457 + if (NS_WARN_IF(NS_FAILED(rv))) 1.458 + return rv; 1.459 + 1.460 + // It's OK if this fails; that probably just means that the directory already 1.461 + // exists. 1.462 + (*aFile)->Create(nsIFile::DIRECTORY_TYPE, 0777); 1.463 + 1.464 + nsAutoCString dirPath; 1.465 + rv = (*aFile)->GetNativePath(dirPath); 1.466 + if (NS_WARN_IF(NS_FAILED(rv))) 1.467 + return rv; 1.468 + 1.469 + while (chmod(dirPath.get(), 0777) == -1 && errno == EINTR) {} 1.470 + } 1.471 +#endif 1.472 + 1.473 + nsCOMPtr<nsIFile> file(*aFile); 1.474 + 1.475 + rv = file->AppendNative(aFilename); 1.476 + if (NS_WARN_IF(NS_FAILED(rv))) 1.477 + return rv; 1.478 + 1.479 + rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666); 1.480 + if (NS_WARN_IF(NS_FAILED(rv))) 1.481 + return rv; 1.482 + 1.483 +#ifdef ANDROID 1.484 + // Make this file world-read/writable; the permissions passed to the 1.485 + // CreateUnique call above are not sufficient on Android, which runs with a 1.486 + // umask. 1.487 + nsAutoCString path; 1.488 + rv = file->GetNativePath(path); 1.489 + if (NS_WARN_IF(NS_FAILED(rv))) 1.490 + return rv; 1.491 + 1.492 + while (chmod(path.get(), 0666) == -1 && errno == EINTR) {} 1.493 +#endif 1.494 + 1.495 + return NS_OK; 1.496 +}