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