xpcom/base/nsDumpUtils.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial