1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "mozilla/BackgroundTasks.h"
7 #include "nsIBackgroundTasksManager.h"
8 #include "nsICommandLine.h"
10 #include "nsImportModule.h"
11 #include "nsPrintfCString.h"
12 #include "nsProfileLock.h"
13 #include "nsTSubstring.h"
14 #include "nsThreadUtils.h"
15 #include "nsXULAppAPI.h"
19 #include "mozilla/CmdLineAndEnvUtils.h"
20 #include "mozilla/LateWriteChecks.h"
21 #include "mozilla/UniquePtr.h"
22 #include "mozilla/Unused.h"
26 NS_IMPL_ISUPPORTS(BackgroundTasks
, nsIBackgroundTasks
);
28 BackgroundTasks::BackgroundTasks(Maybe
<nsCString
> aBackgroundTask
)
29 : mBackgroundTask(std::move(aBackgroundTask
)), mIsEphemeralProfile(false) {
30 // Log when a background task is created.
31 if (mBackgroundTask
.isSome()) {
32 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Info
,
33 ("Created background task: %s", mBackgroundTask
->get()));
37 void BackgroundTasks::Init(Maybe
<nsCString
> aBackgroundTask
) {
38 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
40 MOZ_RELEASE_ASSERT(!sSingleton
,
41 "BackgroundTasks singleton already initialized");
42 // The singleton will be cleaned up by `Shutdown()`.
43 sSingleton
= new BackgroundTasks(std::move(aBackgroundTask
));
46 void BackgroundTasks::Shutdown() {
47 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
49 MOZ_LOG(sBackgroundTasksLog
, LogLevel::Info
, ("Shutdown"));
55 if (sSingleton
->mProfD
&&
56 !EnvHasValue("MOZ_BACKGROUNDTASKS_NO_REMOVE_PROFILE")) {
57 AutoSuspendLateWriteChecks suspend
;
59 if (sSingleton
->mIsEphemeralProfile
) {
60 // Log that the ephemeral profile is being removed.
61 if (MOZ_LOG_TEST(sBackgroundTasksLog
, mozilla::LogLevel::Info
)) {
63 if (NS_SUCCEEDED(sSingleton
->mProfD
->GetPath(path
))) {
64 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Info
,
65 ("Removing profile: %s",
66 NS_LossyConvertUTF16toASCII(path
).get()));
70 Unused
<< sSingleton
->mProfD
->Remove(/* aRecursive */ true);
72 // Log that the non-ephemeral profile is not being removed.
73 if (MOZ_LOG_TEST(sBackgroundTasksLog
, mozilla::LogLevel::Debug
)) {
75 if (NS_SUCCEEDED(sSingleton
->mProfD
->GetPath(path
))) {
76 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Debug
,
77 ("Not removing non-ephemeral profile: %s",
78 NS_LossyConvertUTF16toASCII(path
).get()));
87 BackgroundTasks
* BackgroundTasks::GetSingleton() {
89 // xpcshell doesn't set up background tasks: default to no background
94 MOZ_RELEASE_ASSERT(sSingleton
,
95 "BackgroundTasks singleton should have been initialized");
97 return sSingleton
.get();
100 already_AddRefed
<BackgroundTasks
> BackgroundTasks::GetSingletonAddRefed() {
101 return RefPtr
<BackgroundTasks
>(GetSingleton()).forget();
104 Maybe
<nsCString
> BackgroundTasks::GetBackgroundTasks() {
105 if (!XRE_IsParentProcess()) {
109 return GetSingleton()->mBackgroundTask
;
112 bool BackgroundTasks::IsBackgroundTaskMode() {
113 if (!XRE_IsParentProcess()) {
117 return GetBackgroundTasks().isSome();
120 nsresult
BackgroundTasks::CreateEphemeralProfileDirectory(
121 nsIFile
* aRootDir
, const nsCString
& aProfilePrefix
, nsIFile
** aFile
) {
122 if (!XRE_IsParentProcess()) {
123 return NS_ERROR_NOT_AVAILABLE
;
126 Maybe
<nsCString
> task
= GetBackgroundTasks();
127 sSingleton
->mIsEphemeralProfile
=
128 task
.isSome() && IsEphemeralProfileTaskName(task
.ref());
130 MOZ_RELEASE_ASSERT(sSingleton
->mIsEphemeralProfile
);
132 nsresult rv
= sSingleton
->CreateEphemeralProfileDirectoryImpl(
133 aRootDir
, aProfilePrefix
, aFile
);
135 // Log whether the ephemeral profile was created.
136 if (NS_WARN_IF(NS_FAILED(rv
))) {
137 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Warning
,
138 ("Failed to create ephemeral profile directory!"));
140 if (MOZ_LOG_TEST(sBackgroundTasksLog
, mozilla::LogLevel::Info
)) {
142 if (aFile
&& *aFile
&& NS_SUCCEEDED((*aFile
)->GetPath(path
))) {
143 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Info
,
144 ("Created ephemeral profile directory: %s",
145 NS_LossyConvertUTF16toASCII(path
).get()));
153 nsresult
BackgroundTasks::CreateNonEphemeralProfileDirectory(
154 nsIFile
* aRootDir
, const nsCString
& aProfilePrefix
, nsIFile
** aFile
) {
155 if (!XRE_IsParentProcess()) {
156 return NS_ERROR_NOT_AVAILABLE
;
159 Maybe
<nsCString
> task
= GetBackgroundTasks();
160 sSingleton
->mIsEphemeralProfile
=
161 task
.isSome() && IsEphemeralProfileTaskName(task
.ref());
163 MOZ_RELEASE_ASSERT(!sSingleton
->mIsEphemeralProfile
);
165 nsresult rv
= sSingleton
->CreateNonEphemeralProfileDirectoryImpl(
166 aRootDir
, aProfilePrefix
, aFile
);
168 // Log whether the non-ephemeral profile was created.
169 if (NS_WARN_IF(NS_FAILED(rv
))) {
170 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Warning
,
171 ("Failed to create non-ephemeral profile directory!"));
173 if (MOZ_LOG_TEST(sBackgroundTasksLog
, mozilla::LogLevel::Info
)) {
175 if (aFile
&& *aFile
&& NS_SUCCEEDED((*aFile
)->GetPath(path
))) {
176 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Info
,
177 ("Non-ephemeral profile directory existed or was created: %s",
178 NS_LossyConvertUTF16toASCII(path
).get()));
186 bool BackgroundTasks::IsEphemeralProfile() {
187 return sSingleton
&& sSingleton
->mIsEphemeralProfile
&& sSingleton
->mProfD
;
190 class BackgroundTaskLaunchRunnable
: public Runnable
{
192 explicit BackgroundTaskLaunchRunnable(nsIBackgroundTasksManager
* aManager
,
193 const char* aTaskName
,
194 nsICommandLine
* aCmdLine
)
195 : Runnable("BackgroundTaskLaunchRunnable"),
197 mTaskName(aTaskName
),
198 mCmdLine(aCmdLine
) {}
200 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
202 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
{
203 return mManager
->RunBackgroundTaskNamed(mTaskName
, mCmdLine
);
207 nsCOMPtr
<nsIBackgroundTasksManager
> mManager
;
208 NS_ConvertASCIItoUTF16 mTaskName
;
209 nsCOMPtr
<nsICommandLine
> mCmdLine
;
212 nsresult
BackgroundTasks::RunBackgroundTask(nsICommandLine
* aCmdLine
) {
213 Maybe
<nsCString
> task
= GetBackgroundTasks();
214 if (task
.isNothing()) {
215 return NS_ERROR_NOT_AVAILABLE
;
218 nsCOMPtr
<nsIBackgroundTasksManager
> manager
=
219 do_GetService("@mozilla.org/backgroundtasksmanager;1");
221 MOZ_RELEASE_ASSERT(manager
, "Could not get background tasks manager service");
223 // Give the initial storm of startup runnables a chance to run before our
224 // payload is going to potentially block the main thread for a while.
225 auto r
= MakeRefPtr
<BackgroundTaskLaunchRunnable
>(manager
, task
.ref().get(),
227 return GetCurrentSerialEventTarget()->DelayedDispatch(r
.forget(), 100);
230 bool BackgroundTasks::IsUpdatingTaskName(const nsCString
& aName
) {
231 return aName
.EqualsLiteral("backgroundupdate") ||
232 aName
.EqualsLiteral("shouldprocessupdates");
235 bool BackgroundTasks::IsEphemeralProfileTaskName(const nsCString
& aName
) {
236 return !(aName
.EqualsLiteral("backgroundupdate") ||
237 aName
.EqualsLiteral("defaultagent") ||
238 aName
.EqualsLiteral("message") || // Just for development.
239 aName
.EqualsLiteral("not_ephemeral_profile")); // Just for testing.
242 bool BackgroundTasks::IsNoOutputTaskName(const nsCString
& aName
) {
243 return aName
.EqualsLiteral("pingsender") ||
244 aName
.EqualsLiteral("removeDirectory") ||
245 aName
.EqualsLiteral("removeProfileFiles") ||
246 aName
.EqualsLiteral("no_output"); // Just for testing.
249 nsCString
BackgroundTasks::GetProfilePrefix(const nsCString
& aInstallHash
) {
250 return nsPrintfCString("%sBackgroundTask-%s-%s", MOZ_APP_VENDOR
,
251 aInstallHash
.get(), GetBackgroundTasks().ref().get());
254 nsresult
BackgroundTasks::CreateNonEphemeralProfileDirectoryImpl(
255 nsIFile
* aRootDir
, const nsCString
& aProfilePrefix
, nsIFile
** aFile
) {
256 if (mBackgroundTask
.isNothing()) {
257 return NS_ERROR_NOT_AVAILABLE
;
262 nsCOMPtr
<nsIFile
> file
;
264 rv
= mProfD
->Clone(getter_AddRefs(file
));
265 NS_ENSURE_SUCCESS(rv
, rv
);
270 // /{UAppData}/Background Tasks
271 // Profiles/[salt].[vendor]BackgroundTask-[pathHash]-[taskName].
272 rv
= file
->AppendNative(aProfilePrefix
);
273 NS_ENSURE_SUCCESS(rv
, rv
);
275 // Create the persistent profile directory if it does not exist.
277 rv
= file
->Exists(&exists
);
278 NS_ENSURE_SUCCESS(rv
, rv
);
281 rv
= file
->Create(nsIFile::DIRECTORY_TYPE
, 0700);
282 NS_ENSURE_SUCCESS(rv
, rv
);
285 rv
= file
->Clone(getter_AddRefs(mProfD
));
286 NS_ENSURE_SUCCESS(rv
, rv
);
293 nsresult
BackgroundTasks::CreateEphemeralProfileDirectoryImpl(
294 nsIFile
* aRootDir
, const nsCString
& aProfilePrefix
, nsIFile
** aFile
) {
295 if (mBackgroundTask
.isNothing()) {
296 return NS_ERROR_NOT_AVAILABLE
;
301 nsCOMPtr
<nsIFile
> file
;
303 rv
= mProfD
->Clone(getter_AddRefs(file
));
304 NS_ENSURE_SUCCESS(rv
, rv
);
308 // Windows file cleanup is unreliable, so let's take a moment to clean up
309 // any prior background task profiles. We can continue if there was an error
310 // as creating a new ephemeral profile does not require cleaning up the old.
311 rv
= RemoveStaleEphemeralProfileDirectories(file
, aProfilePrefix
);
312 if (NS_WARN_IF(NS_FAILED(rv
))) {
313 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Warning
,
314 ("Error cleaning up stale ephemeral profile directories."));
317 // The base path is /tmp/[vendor]BackgroundTask-[pathHash]-[taskName].
318 rv
= file
->AppendNative(aProfilePrefix
);
319 NS_ENSURE_SUCCESS(rv
, rv
);
321 // Create a unique profile directory. This can fail if there are too many
322 // (thousands) of existing directories, which is unlikely to happen.
323 rv
= file
->CreateUnique(nsIFile::DIRECTORY_TYPE
, 0700);
324 NS_ENSURE_SUCCESS(rv
, rv
);
326 rv
= file
->Clone(getter_AddRefs(mProfD
));
327 NS_ENSURE_SUCCESS(rv
, rv
);
334 nsresult
BackgroundTasks::RemoveStaleEphemeralProfileDirectories(
335 nsIFile
* const aRoot
, const nsCString
& aPrefix
) {
338 if (MOZ_LOG_TEST(sBackgroundTasksLog
, LogLevel::Info
)) {
340 if (NS_SUCCEEDED(aRoot
->GetPath(path
))) {
341 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Info
,
342 ("Checking \"%s\" for stale profiles matching \"%s\".",
343 NS_LossyConvertUTF16toASCII(path
.get()).get(), aPrefix
.get()));
347 // Check how old a background task should be before being deleted.
348 PRTime timeoutMillis
= 60 * 1000; // Default to 1 minute.
349 bool deleteAll
= false;
350 // MOZ_BACKGROUNDTASKS_PURGE_STALE_PROFILES = ["0"+ in ms, "always", "never"]
351 nsAutoCString
envTimeoutStr(
352 PR_GetEnv("MOZ_BACKGROUNDTASKS_PURGE_STALE_PROFILES"));
354 if (!envTimeoutStr
.IsEmpty()) {
355 int64_t envTimeoutMillis
= envTimeoutStr
.ToInteger64(&rv
);
356 if (NS_SUCCEEDED(rv
)) {
357 if (envTimeoutMillis
>= 0) {
358 timeoutMillis
= envTimeoutMillis
;
359 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Info
,
360 ("Setting stale profile age to %sms", envTimeoutStr
.get()));
362 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Warning
,
363 ("MOZ_BACKGROUNDTASKS_PURGE_STALE_PROFILES is set less than 0, "
364 "using default timeout instead."));
367 if (envTimeoutStr
.Equals("always")) {
369 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Info
,
370 ("Deleting profiles regardless of age."));
371 } else if (envTimeoutStr
.Equals("never")) {
372 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Info
,
373 ("Skipping cleanup of stale background task profiles."));
376 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Warning
,
377 ("MOZ_BACKGROUNDTASKS_PURGE_STALE_PROFILES is set to invalid "
378 "value, using default timeout instead."));
383 nsCOMPtr
<nsIDirectoryEnumerator
> entries
;
384 rv
= aRoot
->GetDirectoryEntries(getter_AddRefs(entries
));
385 NS_ENSURE_SUCCESS(rv
, rv
);
387 nsCOMPtr
<nsIFile
> entry
;
388 int removedProfiles
= 0;
389 // Limit the number of stale ephemeral profiles we clean up so that we don't
390 // timeout the background task.
391 const int kMaxRemovedProfiles
= 5;
393 // Loop over the ephemeral directory entries, deleting folders matching our
394 // profile prefix. Continue if there is an error interacting with the entry to
395 // more reliably make progress on cleaning up stale ephemeral profiles.
396 while (removedProfiles
< kMaxRemovedProfiles
&&
397 NS_SUCCEEDED(rv
= entries
->GetNextFile(getter_AddRefs(entry
))) &&
400 rv
= entry
->GetNativeLeafName(entryName
);
405 // Find profile folders matching our prefix.
406 if (!StringBeginsWith(entryName
, aPrefix
)) {
411 // Skip profiles that were recently created to prevent deleting a profile
412 // after creating the directory but before creating the lockfile.
413 PRTime profileModifyTime
;
414 if (NS_FAILED(entry
->GetLastModifiedTime(&profileModifyTime
))) {
415 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Warning
,
416 ("Skipping deletion of %s, unable to retrieve when profile was "
421 PRTime now
= PR_Now() / PR_USEC_PER_MSEC
;
422 // Timeout only needs to be large enough to prevent deleting a ephemeral
423 // profile between it being created and locked.
424 if (now
- profileModifyTime
< timeoutMillis
) {
425 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Debug
,
426 ("Skipping deletion of %s, profile is not yet stale.",
432 // Check if the profile is locked. If successful drop the lock so we can
433 // delete the folder. Background tasks' ephemeral profiles are not reused or
434 // remembered once released, so we don't need to hold this lock while
437 if (NS_FAILED(lock
.Lock(entry
, nullptr)) || NS_FAILED(lock
.Unlock())) {
438 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Warning
,
439 ("Skipping deletion of %s, unable to lock/unlock profile.",
444 rv
= entry
->Remove(true);
446 if (MOZ_LOG_TEST(sBackgroundTasksLog
, mozilla::LogLevel::Warning
)) {
448 if (NS_SUCCEEDED(entry
->GetPath(path
))) {
449 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Warning
,
450 ("Error removing stale ephemeral profile directory: %s",
451 NS_LossyConvertUTF16toASCII(path
).get()));
458 if (MOZ_LOG_TEST(sBackgroundTasksLog
, mozilla::LogLevel::Info
)) {
460 if (NS_SUCCEEDED(entry
->GetPath(path
))) {
461 MOZ_LOG(sBackgroundTasksLog
, mozilla::LogLevel::Info
,
462 ("Removed stale ephemeral profile directory: %s",
463 NS_LossyConvertUTF16toASCII(path
).get()));
473 nsresult
BackgroundTasks::GetIsBackgroundTaskMode(bool* result
) {
474 *result
= mBackgroundTask
.isSome();
478 nsresult
BackgroundTasks::BackgroundTaskName(nsAString
& name
) {
479 name
.SetIsVoid(true);
480 if (mBackgroundTask
.isSome()) {
481 name
.AssignASCII(mBackgroundTask
.ref());
486 nsresult
BackgroundTasks::OverrideBackgroundTaskNameForTesting(
487 const nsAString
& name
) {
489 mBackgroundTask
= Nothing();
491 mBackgroundTask
= Some(NS_LossyConvertUTF16toASCII(name
));
496 StaticRefPtr
<BackgroundTasks
> BackgroundTasks::sSingleton
;
498 LazyLogModule
BackgroundTasks::sBackgroundTasksLog("BackgroundTasks");
500 } // namespace mozilla