/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AppShutdown.h" #ifdef XP_WIN # include #else # include #endif #include "GeckoProfiler.h" #include "mozilla/CmdLineAndEnvUtils.h" #include "mozilla/PoisonIOInterposer.h" #include "mozilla/Printf.h" #include "mozilla/scache/StartupCache.h" #include "mozilla/SpinEventLoopUntil.h" #include "mozilla/StartupTimeline.h" #include "mozilla/StaticPrefs_toolkit.h" #include "mozilla/LateWriteChecks.h" #include "nsAppDirectoryServiceDefs.h" #include "nsAppRunner.h" #include "nsDirectoryServiceUtils.h" #include "nsICertStorage.h" #include "nsThreadUtils.h" #include "prenv.h" #ifdef MOZ_NEW_XULSTORE # include "mozilla/XULStore.h" #endif namespace mozilla { static ShutdownPhase sFastShutdownPhase = ShutdownPhase::NotInShutdown; static ShutdownPhase sLateWriteChecksPhase = ShutdownPhase::NotInShutdown; static AppShutdownMode sShutdownMode = AppShutdownMode::Normal; static Atomic sIsShuttingDown; static int sExitCode = 0; // These environment variable strings are all deliberately copied and leaked // due to requirements of PR_SetEnv and similar. static char* sSavedXulAppFile = nullptr; #ifdef XP_WIN static wchar_t* sSavedProfDEnvVar = nullptr; static wchar_t* sSavedProfLDEnvVar = nullptr; #else static char* sSavedProfDEnvVar = nullptr; static char* sSavedProfLDEnvVar = nullptr; #endif ShutdownPhase GetShutdownPhaseFromPrefValue(int32_t aPrefValue) { switch (aPrefValue) { case 1: return ShutdownPhase::ShutdownPostLastCycleCollection; case 2: return ShutdownPhase::ShutdownThreads; case 3: return ShutdownPhase::Shutdown; // NOTE: the remaining values from the ShutdownPhase enum will be added // when we're at least reasonably confident that the world won't come // crashing down if we do a fast shutdown at that point. } return ShutdownPhase::NotInShutdown; } bool AppShutdown::IsShuttingDown() { return sIsShuttingDown; } int AppShutdown::GetExitCode() { return sExitCode; } void AppShutdown::SaveEnvVarsForPotentialRestart() { const char* s = PR_GetEnv("XUL_APP_FILE"); if (s) { sSavedXulAppFile = Smprintf("%s=%s", "XUL_APP_FILE", s).release(); MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedXulAppFile); } } void AppShutdown::MaybeDoRestart() { if (sShutdownMode == AppShutdownMode::Restart) { StopLateWriteChecks(); // Since we'll be launching our child while we're still alive, make sure // we've unlocked the profile first, otherwise the child could hit its // profile lock check before we've exited and thus released our lock. UnlockProfile(); if (sSavedXulAppFile) { PR_SetEnv(sSavedXulAppFile); } #ifdef XP_WIN if (sSavedProfDEnvVar && !EnvHasValue("XRE_PROFILE_PATH")) { SetEnvironmentVariableW(L"XRE_PROFILE_PATH", sSavedProfDEnvVar); } if (sSavedProfLDEnvVar && !EnvHasValue("XRE_PROFILE_LOCAL_PATH")) { SetEnvironmentVariableW(L"XRE_PROFILE_LOCAL_PATH", sSavedProfLDEnvVar); } #else if (sSavedProfDEnvVar && !EnvHasValue("XRE_PROFILE_PATH")) { PR_SetEnv(sSavedProfDEnvVar); } if (sSavedProfLDEnvVar && !EnvHasValue("XRE_PROFILE_LOCAL_PATH")) { PR_SetEnv(sSavedProfLDEnvVar); } #endif LaunchChild(true); } } #ifdef XP_WIN wchar_t* CopyPathIntoNewWCString(nsIFile* aFile) { wchar_t* result = nullptr; nsAutoString resStr; aFile->GetPath(resStr); if (resStr.Length() > 0) { result = (wchar_t*)malloc((resStr.Length() + 1) * sizeof(wchar_t)); if (result) { wcscpy(result, resStr.get()); result[resStr.Length()] = 0; } } return result; } #endif void AppShutdown::Init(AppShutdownMode aMode, int aExitCode) { if (sShutdownMode == AppShutdownMode::Normal) { sShutdownMode = aMode; } sExitCode = aExitCode; // Late-write checks needs to find the profile directory, so it has to // be initialized before services::Shutdown or (because of // xpcshell tests replacing the service) modules being unloaded. InitLateWriteChecks(); int32_t fastShutdownPref = StaticPrefs::toolkit_shutdown_fastShutdownStage(); sFastShutdownPhase = GetShutdownPhaseFromPrefValue(fastShutdownPref); int32_t lateWriteChecksPref = StaticPrefs::toolkit_shutdown_lateWriteChecksStage(); sLateWriteChecksPhase = GetShutdownPhaseFromPrefValue(lateWriteChecksPref); // Very early shutdowns can happen before the startup cache is even // initialized; don't bother initializing it during shutdown. if (auto* cache = scache::StartupCache::GetSingletonNoInit()) { cache->MaybeInitShutdownWrite(); } } void AppShutdown::MaybeFastShutdown(ShutdownPhase aPhase) { // For writes which we want to ensure are recorded, we don't want to trip // the late write checking code. Anything that writes to disk and which // we don't want to skip should be listed out explicitly in this section. if (aPhase == sFastShutdownPhase || aPhase == sLateWriteChecksPhase) { if (auto* cache = scache::StartupCache::GetSingletonNoInit()) { cache->EnsureShutdownWriteComplete(); } nsresult rv; #ifdef MOZ_NEW_XULSTORE rv = XULStore::Shutdown(); NS_ASSERTION(NS_SUCCEEDED(rv), "XULStore::Shutdown() failed."); #endif nsCOMPtr certStorage = do_GetService("@mozilla.org/security/certstorage;1", &rv); if (NS_SUCCEEDED(rv)) { SpinEventLoopUntil([&]() { int32_t remainingOps; nsresult rv = certStorage->GetRemainingOperationCount(&remainingOps); NS_ASSERTION(NS_SUCCEEDED(rv), "nsICertStorage::getRemainingOperationCount failed during " "shutdown"); return NS_FAILED(rv) || remainingOps <= 0; }); } } if (aPhase == sFastShutdownPhase) { StopLateWriteChecks(); RecordShutdownEndTimeStamp(); MaybeDoRestart(); #ifdef MOZ_GECKO_PROFILER profiler_shutdown(IsFastShutdown::Yes); #endif DoImmediateExit(sExitCode); } else if (aPhase == sLateWriteChecksPhase) { #ifdef XP_MACOSX OnlyReportDirtyWrites(); #endif /* XP_MACOSX */ BeginLateWriteChecks(); } } void AppShutdown::OnShutdownConfirmed() { sIsShuttingDown = true; // If we're restarting, we need to save environment variables correctly // while everything is still alive to do so. if (sShutdownMode == AppShutdownMode::Restart) { nsCOMPtr profD; nsCOMPtr profLD; NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profD)); NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, getter_AddRefs(profLD)); #ifdef XP_WIN sSavedProfDEnvVar = CopyPathIntoNewWCString(profD); sSavedProfLDEnvVar = CopyPathIntoNewWCString(profLD); #else nsAutoCString profDStr; profD->GetNativePath(profDStr); sSavedProfDEnvVar = Smprintf("XRE_PROFILE_PATH=%s", profDStr.get()).release(); nsAutoCString profLDStr; profLD->GetNativePath(profLDStr); sSavedProfLDEnvVar = Smprintf("XRE_PROFILE_LOCAL_PATH=%s", profLDStr.get()).release(); #endif MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedProfDEnvVar); MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedProfLDEnvVar); } } void AppShutdown::DoImmediateExit(int aExitCode) { #ifdef XP_WIN HANDLE process = ::GetCurrentProcess(); if (::TerminateProcess(process, aExitCode)) { ::WaitForSingleObject(process, INFINITE); } MOZ_CRASH("TerminateProcess failed."); #else _exit(aExitCode); #endif } bool AppShutdown::IsRestarting() { return sShutdownMode == AppShutdownMode::Restart; } } // namespace mozilla