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 "ShutdownPhase.h"
8 #ifdef XP_WIN
9 #  include <windows.h>
10 #  include "mozilla/PreXULSkeletonUI.h"
11 #else
12 #  include <unistd.h>
13 #endif
14 
15 #include "GeckoProfiler.h"
16 #include "mozilla/ClearOnShutdown.h"
17 #include "mozilla/CmdLineAndEnvUtils.h"
18 #include "mozilla/PoisonIOInterposer.h"
19 #include "mozilla/Printf.h"
20 #include "mozilla/scache/StartupCache.h"
21 #include "mozilla/SpinEventLoopUntil.h"
22 #include "mozilla/StartupTimeline.h"
23 #include "mozilla/StaticPrefs_toolkit.h"
24 #include "mozilla/LateWriteChecks.h"
25 #include "mozilla/Services.h"
26 #include "nsAppDirectoryServiceDefs.h"
27 #include "nsAppRunner.h"
28 #include "nsDirectoryServiceUtils.h"
29 #include "nsICertStorage.h"
30 #include "nsThreadUtils.h"
31 
32 #include "AppShutdown.h"
33 
34 // TODO: understand why on Android we cannot include this and if we should
35 #ifndef ANDROID
36 #  include "nsTerminator.h"
37 #endif
38 #include "prenv.h"
39 
40 #ifdef MOZ_NEW_XULSTORE
41 #  include "mozilla/XULStore.h"
42 #endif
43 
44 #ifdef MOZ_BACKGROUNDTASKS
45 #  include "mozilla/BackgroundTasks.h"
46 #endif
47 
48 namespace mozilla {
49 
50 const char* sPhaseObserverKeys[] = {
51     nullptr,                            // NotInShutdown
52     "quit-application",                 // AppShutdownConfirmed
53     "profile-change-net-teardown",      // AppShutdownNetTeardown
54     "profile-change-teardown",          // AppShutdownTeardown
55     "profile-before-change",            // AppShutdown
56     "profile-before-change-qm",         // AppShutdownQM
57     "profile-before-change-telemetry",  // AppShutdownTelemetry
58     "xpcom-will-shutdown",              // XPCOMWillShutdown
59     "xpcom-shutdown",                   // XPCOMShutdown
60     "xpcom-shutdown-threads",           // XPCOMShutdownThreads
61     nullptr,                            // XPCOMShutdownLoaders
62     nullptr,                            // XPCOMShutdownFinal
63     nullptr                             // CCPostLastCycleCollection
64 };
65 
66 static_assert(sizeof(sPhaseObserverKeys) / sizeof(sPhaseObserverKeys[0]) ==
67               (size_t)ShutdownPhase::ShutdownPhase_Length);
68 
69 #ifndef ANDROID
70 static nsTerminator* sTerminator = nullptr;
71 #endif
72 
73 static ShutdownPhase sFastShutdownPhase = ShutdownPhase::NotInShutdown;
74 static ShutdownPhase sLateWriteChecksPhase = ShutdownPhase::NotInShutdown;
75 static AppShutdownMode sShutdownMode = AppShutdownMode::Normal;
76 static Atomic<bool, MemoryOrdering::Relaxed> sIsShuttingDown;
77 static Atomic<ShutdownPhase> sCurrentShutdownPhase(
78     ShutdownPhase::NotInShutdown);
79 static int sExitCode = 0;
80 
81 // These environment variable strings are all deliberately copied and leaked
82 // due to requirements of PR_SetEnv and similar.
83 static char* sSavedXulAppFile = nullptr;
84 #ifdef XP_WIN
85 static wchar_t* sSavedProfDEnvVar = nullptr;
86 static wchar_t* sSavedProfLDEnvVar = nullptr;
87 #else
88 static char* sSavedProfDEnvVar = nullptr;
89 static char* sSavedProfLDEnvVar = nullptr;
90 #endif
91 
GetShutdownPhaseFromPrefValue(int32_t aPrefValue)92 ShutdownPhase GetShutdownPhaseFromPrefValue(int32_t aPrefValue) {
93   switch (aPrefValue) {
94     case 1:
95       return ShutdownPhase::CCPostLastCycleCollection;
96     case 2:
97       return ShutdownPhase::XPCOMShutdownThreads;
98     case 3:
99       return ShutdownPhase::XPCOMShutdown;
100       // NOTE: the remaining values from the ShutdownPhase enum will be added
101       // when we're at least reasonably confident that the world won't come
102       // crashing down if we do a fast shutdown at that point.
103   }
104   return ShutdownPhase::NotInShutdown;
105 }
106 
IsShuttingDown()107 bool AppShutdown::IsShuttingDown() { return sIsShuttingDown; }
108 
GetCurrentShutdownPhase()109 ShutdownPhase AppShutdown::GetCurrentShutdownPhase() {
110   return sCurrentShutdownPhase;
111 }
112 
IsInOrBeyond(ShutdownPhase aPhase)113 bool AppShutdown::IsInOrBeyond(ShutdownPhase aPhase) {
114   return (sCurrentShutdownPhase >= aPhase);
115 }
116 
GetExitCode()117 int AppShutdown::GetExitCode() { return sExitCode; }
118 
SaveEnvVarsForPotentialRestart()119 void AppShutdown::SaveEnvVarsForPotentialRestart() {
120   const char* s = PR_GetEnv("XUL_APP_FILE");
121   if (s) {
122     sSavedXulAppFile = Smprintf("%s=%s", "XUL_APP_FILE", s).release();
123     MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedXulAppFile);
124   }
125 }
126 
GetObserverKey(ShutdownPhase aPhase)127 const char* AppShutdown::GetObserverKey(ShutdownPhase aPhase) {
128   return sPhaseObserverKeys[static_cast<std::underlying_type_t<ShutdownPhase>>(
129       aPhase)];
130 }
131 
MaybeDoRestart()132 void AppShutdown::MaybeDoRestart() {
133   if (sShutdownMode == AppShutdownMode::Restart) {
134     StopLateWriteChecks();
135 
136     // Since we'll be launching our child while we're still alive, make sure
137     // we've unlocked the profile first, otherwise the child could hit its
138     // profile lock check before we've exited and thus released our lock.
139     UnlockProfile();
140 
141     if (sSavedXulAppFile) {
142       PR_SetEnv(sSavedXulAppFile);
143     }
144 
145 #ifdef XP_WIN
146     if (sSavedProfDEnvVar && !EnvHasValue("XRE_PROFILE_PATH")) {
147       SetEnvironmentVariableW(L"XRE_PROFILE_PATH", sSavedProfDEnvVar);
148     }
149     if (sSavedProfLDEnvVar && !EnvHasValue("XRE_PROFILE_LOCAL_PATH")) {
150       SetEnvironmentVariableW(L"XRE_PROFILE_LOCAL_PATH", sSavedProfLDEnvVar);
151     }
152     Unused << NotePreXULSkeletonUIRestarting();
153 #else
154     if (sSavedProfDEnvVar && !EnvHasValue("XRE_PROFILE_PATH")) {
155       PR_SetEnv(sSavedProfDEnvVar);
156     }
157     if (sSavedProfLDEnvVar && !EnvHasValue("XRE_PROFILE_LOCAL_PATH")) {
158       PR_SetEnv(sSavedProfLDEnvVar);
159     }
160 #endif
161 
162     LaunchChild(true);
163   }
164 }
165 
166 #ifdef XP_WIN
CopyPathIntoNewWCString(nsIFile * aFile)167 wchar_t* CopyPathIntoNewWCString(nsIFile* aFile) {
168   wchar_t* result = nullptr;
169   nsAutoString resStr;
170   aFile->GetPath(resStr);
171   if (resStr.Length() > 0) {
172     result = (wchar_t*)malloc((resStr.Length() + 1) * sizeof(wchar_t));
173     if (result) {
174       wcscpy(result, resStr.get());
175       result[resStr.Length()] = 0;
176     }
177   }
178 
179   return result;
180 }
181 #endif
182 
Init(AppShutdownMode aMode,int aExitCode)183 void AppShutdown::Init(AppShutdownMode aMode, int aExitCode) {
184   if (sShutdownMode == AppShutdownMode::Normal) {
185     sShutdownMode = aMode;
186   }
187 
188   sExitCode = aExitCode;
189 
190 #ifndef ANDROID
191   sTerminator = new nsTerminator();
192 #endif
193 
194   // Late-write checks needs to find the profile directory, so it has to
195   // be initialized before services::Shutdown or (because of
196   // xpcshell tests replacing the service) modules being unloaded.
197   InitLateWriteChecks();
198 
199   int32_t fastShutdownPref = StaticPrefs::toolkit_shutdown_fastShutdownStage();
200   sFastShutdownPhase = GetShutdownPhaseFromPrefValue(fastShutdownPref);
201   int32_t lateWriteChecksPref =
202       StaticPrefs::toolkit_shutdown_lateWriteChecksStage();
203   sLateWriteChecksPhase = GetShutdownPhaseFromPrefValue(lateWriteChecksPref);
204 
205   // Very early shutdowns can happen before the startup cache is even
206   // initialized; don't bother initializing it during shutdown.
207   if (auto* cache = scache::StartupCache::GetSingletonNoInit()) {
208     cache->MaybeInitShutdownWrite();
209   }
210 }
211 
MaybeFastShutdown(ShutdownPhase aPhase)212 void AppShutdown::MaybeFastShutdown(ShutdownPhase aPhase) {
213   // For writes which we want to ensure are recorded, we don't want to trip
214   // the late write checking code. Anything that writes to disk and which
215   // we don't want to skip should be listed out explicitly in this section.
216   if (aPhase == sFastShutdownPhase || aPhase == sLateWriteChecksPhase) {
217     if (auto* cache = scache::StartupCache::GetSingletonNoInit()) {
218       cache->EnsureShutdownWriteComplete();
219     }
220 
221     nsresult rv;
222 #ifdef MOZ_NEW_XULSTORE
223     rv = XULStore::Shutdown();
224     NS_ASSERTION(NS_SUCCEEDED(rv), "XULStore::Shutdown() failed.");
225 #endif
226 
227     nsCOMPtr<nsICertStorage> certStorage =
228         do_GetService("@mozilla.org/security/certstorage;1", &rv);
229     if (NS_SUCCEEDED(rv)) {
230       SpinEventLoopUntil([&]() {
231         int32_t remainingOps;
232         nsresult rv = certStorage->GetRemainingOperationCount(&remainingOps);
233         NS_ASSERTION(NS_SUCCEEDED(rv),
234                      "nsICertStorage::getRemainingOperationCount failed during "
235                      "shutdown");
236         return NS_FAILED(rv) || remainingOps <= 0;
237       });
238     }
239   }
240   if (aPhase == sFastShutdownPhase) {
241     StopLateWriteChecks();
242     RecordShutdownEndTimeStamp();
243     MaybeDoRestart();
244 
245 #ifdef MOZ_GECKO_PROFILER
246     profiler_shutdown(IsFastShutdown::Yes);
247 #endif
248 
249 #ifdef MOZ_BACKGROUNDTASKS
250     // We must unlock the profile, or else the lock file `parent.lock` will
251     // prevent removing the directory, allowing additional writes (including
252     // `ShutdownDuration.json{.tmp}`) to succeed.  But `UnlockProfile()` is not
253     // idempotent so we can't push the unlock into `Shutdown()` directly.
254     if (mozilla::BackgroundTasks::IsUsingTemporaryProfile()) {
255       UnlockProfile();
256     }
257     mozilla::BackgroundTasks::Shutdown();
258 #endif
259 
260     DoImmediateExit(sExitCode);
261   } else if (aPhase == sLateWriteChecksPhase) {
262 #ifdef XP_MACOSX
263     OnlyReportDirtyWrites();
264 #endif /* XP_MACOSX */
265     BeginLateWriteChecks();
266   }
267 }
268 
OnShutdownConfirmed()269 void AppShutdown::OnShutdownConfirmed() {
270   sIsShuttingDown = true;
271   // If we're restarting, we need to save environment variables correctly
272   // while everything is still alive to do so.
273   if (sShutdownMode == AppShutdownMode::Restart) {
274     nsCOMPtr<nsIFile> profD;
275     nsCOMPtr<nsIFile> profLD;
276     NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profD));
277     NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
278                            getter_AddRefs(profLD));
279 #ifdef XP_WIN
280     sSavedProfDEnvVar = CopyPathIntoNewWCString(profD);
281     sSavedProfLDEnvVar = CopyPathIntoNewWCString(profLD);
282 #else
283     nsAutoCString profDStr;
284     profD->GetNativePath(profDStr);
285     sSavedProfDEnvVar =
286         Smprintf("XRE_PROFILE_PATH=%s", profDStr.get()).release();
287     nsAutoCString profLDStr;
288     profLD->GetNativePath(profLDStr);
289     sSavedProfLDEnvVar =
290         Smprintf("XRE_PROFILE_LOCAL_PATH=%s", profLDStr.get()).release();
291 #endif
292     MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedProfDEnvVar);
293     MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedProfLDEnvVar);
294   }
295 }
296 
DoImmediateExit(int aExitCode)297 void AppShutdown::DoImmediateExit(int aExitCode) {
298 #ifdef XP_WIN
299   HANDLE process = ::GetCurrentProcess();
300   if (::TerminateProcess(process, aExitCode)) {
301     ::WaitForSingleObject(process, INFINITE);
302   }
303   MOZ_CRASH("TerminateProcess failed.");
304 #else
305   _exit(aExitCode);
306 #endif
307 }
308 
IsRestarting()309 bool AppShutdown::IsRestarting() {
310   return sShutdownMode == AppShutdownMode::Restart;
311 }
312 
313 #ifdef DEBUG
314 static bool sNotifyingShutdownObservers = false;
315 
IsNoOrLegalShutdownTopic(const char * aTopic)316 bool AppShutdown::IsNoOrLegalShutdownTopic(const char* aTopic) {
317   if (!XRE_IsParentProcess()) {
318     // Until we know what to do with AppShutdown for child processes,
319     // we ignore them for now. See bug 1697745.
320     return true;
321   }
322   ShutdownPhase phase = GetShutdownPhaseFromTopic(aTopic);
323   return phase == ShutdownPhase::NotInShutdown ||
324          (sNotifyingShutdownObservers && phase == sCurrentShutdownPhase);
325 }
326 #endif
327 
AdvanceShutdownPhaseInternal(ShutdownPhase aPhase,bool doNotify,const char16_t * aNotificationData,const nsCOMPtr<nsISupports> & aNotificationSubject)328 void AdvanceShutdownPhaseInternal(
329     ShutdownPhase aPhase, bool doNotify, const char16_t* aNotificationData,
330     const nsCOMPtr<nsISupports>& aNotificationSubject) {
331   AssertIsOnMainThread();
332 
333   // We ensure that we can move only forward. We cannot
334   // MOZ_ASSERT here as there are some tests that fire
335   // notifications out of shutdown order.
336   // See for example test_sss_sanitizeOnShutdown.js
337   if (sCurrentShutdownPhase >= aPhase) {
338     return;
339   }
340   sCurrentShutdownPhase = aPhase;
341 
342 #ifndef ANDROID
343   if (sTerminator) {
344     sTerminator->AdvancePhase(aPhase);
345   }
346 #endif
347 
348   mozilla::KillClearOnShutdown(aPhase);
349 
350   AppShutdown::MaybeFastShutdown(aPhase);
351 
352   if (doNotify) {
353     const char* aTopic = AppShutdown::GetObserverKey(aPhase);
354     if (aTopic) {
355       nsCOMPtr<nsIObserverService> obsService =
356           mozilla::services::GetObserverService();
357       if (obsService) {
358 #ifdef DEBUG
359         sNotifyingShutdownObservers = true;
360         auto reset = MakeScopeExit([] { sNotifyingShutdownObservers = false; });
361 #endif
362         obsService->NotifyObservers(aNotificationSubject, aTopic,
363                                     aNotificationData);
364       }
365     }
366   }
367 }
368 
369 /**
370  * XXX: Before tackling bug 1697745 we need the
371  * possibility to advance the phase without notification
372  * in the content process.
373  */
AdvanceShutdownPhaseWithoutNotify(ShutdownPhase aPhase)374 void AppShutdown::AdvanceShutdownPhaseWithoutNotify(ShutdownPhase aPhase) {
375   AdvanceShutdownPhaseInternal(aPhase, /* doNotify */ false, nullptr, nullptr);
376 }
377 
AdvanceShutdownPhase(ShutdownPhase aPhase,const char16_t * aNotificationData,const nsCOMPtr<nsISupports> & aNotificationSubject)378 void AppShutdown::AdvanceShutdownPhase(
379     ShutdownPhase aPhase, const char16_t* aNotificationData,
380     const nsCOMPtr<nsISupports>& aNotificationSubject) {
381   AdvanceShutdownPhaseInternal(aPhase, /* doNotify */ true, aNotificationData,
382                                aNotificationSubject);
383 }
384 
GetShutdownPhaseFromTopic(const char * aTopic)385 ShutdownPhase AppShutdown::GetShutdownPhaseFromTopic(const char* aTopic) {
386   for (size_t i = 0; i < ArrayLength(sPhaseObserverKeys); ++i) {
387     if (sPhaseObserverKeys[i] && !strcmp(sPhaseObserverKeys[i], aTopic)) {
388       return static_cast<ShutdownPhase>(i);
389     }
390   }
391   return ShutdownPhase::NotInShutdown;
392 }
393 
394 }  // namespace mozilla
395