Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / toolkit / profile / nsToolkitProfileService.cpp
blobbe2892fed19286af7aa8d9a646657e3d11143bfa
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/ArrayUtils.h"
7 #include "mozilla/ScopeExit.h"
8 #include "mozilla/UniquePtr.h"
9 #include "mozilla/UniquePtrExtensions.h"
10 #include "mozilla/WidgetUtils.h"
11 #include "nsProfileLock.h"
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <prprf.h>
16 #include <prtime.h>
18 #ifdef XP_WIN
19 # include <windows.h>
20 # include <shlobj.h>
21 # include "mozilla/PolicyChecks.h"
22 #endif
23 #ifdef XP_UNIX
24 # include <unistd.h>
25 #endif
27 #include "nsToolkitProfileService.h"
28 #include "CmdLineAndEnvUtils.h"
29 #include "nsIFile.h"
31 #ifdef XP_MACOSX
32 # include <CoreFoundation/CoreFoundation.h>
33 # include "nsILocalFileMac.h"
34 #endif
36 #ifdef MOZ_WIDGET_GTK
37 # include "mozilla/WidgetUtilsGtk.h"
38 #endif
40 #include "nsAppDirectoryServiceDefs.h"
41 #include "nsDirectoryServiceDefs.h"
42 #include "nsNetCID.h"
43 #include "nsXULAppAPI.h"
44 #include "nsThreadUtils.h"
46 #include "nsIRunnable.h"
47 #include "nsXREDirProvider.h"
48 #include "nsAppRunner.h"
49 #include "nsString.h"
50 #include "nsReadableUtils.h"
51 #include "nsNativeCharsetUtils.h"
52 #include "mozilla/Attributes.h"
53 #include "mozilla/Sprintf.h"
54 #include "nsPrintfCString.h"
55 #include "mozilla/UniquePtr.h"
56 #include "nsIToolkitShellService.h"
57 #include "mozilla/Telemetry.h"
58 #include "nsProxyRelease.h"
59 #include "prinrval.h"
60 #include "prthread.h"
61 #ifdef MOZ_BACKGROUNDTASKS
62 # include "mozilla/BackgroundTasks.h"
63 # include "SpecialSystemDirectory.h"
64 #endif
66 using namespace mozilla;
68 #define DEV_EDITION_NAME "dev-edition-default"
69 #define DEFAULT_NAME "default"
70 #define COMPAT_FILE u"compatibility.ini"_ns
71 #define PROFILE_DB_VERSION "2"
72 #define INSTALL_PREFIX "Install"
73 #define INSTALL_PREFIX_LENGTH 7
75 struct KeyValue {
76 KeyValue(const char* aKey, const char* aValue) : key(aKey), value(aValue) {}
78 nsCString key;
79 nsCString value;
82 static bool GetStrings(const char* aString, const char* aValue,
83 void* aClosure) {
84 nsTArray<UniquePtr<KeyValue>>* array =
85 static_cast<nsTArray<UniquePtr<KeyValue>>*>(aClosure);
86 array->AppendElement(MakeUnique<KeyValue>(aString, aValue));
88 return true;
91 /**
92 * Returns an array of the strings inside a section of an ini file.
94 nsTArray<UniquePtr<KeyValue>> GetSectionStrings(nsINIParser* aParser,
95 const char* aSection) {
96 nsTArray<UniquePtr<KeyValue>> result;
97 aParser->GetStrings(aSection, &GetStrings, &result);
98 return result;
101 void RemoveProfileRecursion(const nsCOMPtr<nsIFile>& aDirectoryOrFile,
102 bool aIsIgnoreRoot, bool aIsIgnoreLockfile,
103 nsTArray<nsCOMPtr<nsIFile>>& aOutUndeletedFiles) {
104 auto guardDeletion = MakeScopeExit(
105 [&] { aOutUndeletedFiles.AppendElement(aDirectoryOrFile); });
107 // We actually would not expect to see links in our profiles, but still.
108 bool isLink = false;
109 NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->IsSymlink(&isLink));
111 // Only check to see if we have a directory if it isn't a link.
112 bool isDir = false;
113 if (!isLink) {
114 NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->IsDirectory(&isDir));
117 if (isDir) {
118 nsCOMPtr<nsIDirectoryEnumerator> dirEnum;
119 NS_ENSURE_SUCCESS_VOID(
120 aDirectoryOrFile->GetDirectoryEntries(getter_AddRefs(dirEnum)));
122 bool more = false;
123 while (NS_SUCCEEDED(dirEnum->HasMoreElements(&more)) && more) {
124 nsCOMPtr<nsISupports> item;
125 dirEnum->GetNext(getter_AddRefs(item));
126 nsCOMPtr<nsIFile> file = do_QueryInterface(item);
127 if (file) {
128 // Do not delete the profile lock.
129 if (aIsIgnoreLockfile && nsProfileLock::IsMaybeLockFile(file)) continue;
130 // If some children's remove fails, we still continue the loop.
131 RemoveProfileRecursion(file, false, false, aOutUndeletedFiles);
135 // Do not delete the root directory (yet).
136 if (!aIsIgnoreRoot) {
137 NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->Remove(false));
139 guardDeletion.release();
142 void RemoveProfileFiles(nsIToolkitProfile* aProfile, bool aInBackground) {
143 nsCOMPtr<nsIFile> rootDir;
144 aProfile->GetRootDir(getter_AddRefs(rootDir));
145 nsCOMPtr<nsIFile> localDir;
146 aProfile->GetLocalDir(getter_AddRefs(localDir));
148 // XXX If we get here with an active quota manager,
149 // something went very wrong. We want to assert this.
151 // Just lock the directories, don't mark the profile as locked or the lock
152 // will attempt to release its reference to the profile on the background
153 // thread which will assert.
154 nsCOMPtr<nsIProfileLock> lock;
155 NS_ENSURE_SUCCESS_VOID(
156 NS_LockProfilePath(rootDir, localDir, nullptr, getter_AddRefs(lock)));
158 nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
159 "nsToolkitProfile::RemoveProfileFiles",
160 [rootDir, localDir, lock]() mutable {
161 // We try to remove every single file and directory and collect
162 // those whose removal failed.
163 nsTArray<nsCOMPtr<nsIFile>> undeletedFiles;
164 // The root dir might contain the temp dir, so remove the temp dir
165 // first.
166 bool equals;
167 nsresult rv = rootDir->Equals(localDir, &equals);
168 if (NS_SUCCEEDED(rv) && !equals) {
169 RemoveProfileRecursion(localDir,
170 /* aIsIgnoreRoot */ false,
171 /* aIsIgnoreLockfile */ false, undeletedFiles);
173 // Now remove the content of the profile dir (except lockfile)
174 RemoveProfileRecursion(rootDir,
175 /* aIsIgnoreRoot */ true,
176 /* aIsIgnoreLockfile */ true, undeletedFiles);
178 // Retry loop if something was not deleted
179 if (undeletedFiles.Length() > 0) {
180 uint32_t retries = 1;
181 // XXX: Until bug 1716291 is fixed we just make one retry
182 while (undeletedFiles.Length() > 0 && retries <= 1) {
183 Unused << PR_Sleep(PR_MillisecondsToInterval(10 * retries));
184 for (auto&& file :
185 std::exchange(undeletedFiles, nsTArray<nsCOMPtr<nsIFile>>{})) {
186 RemoveProfileRecursion(file,
187 /* aIsIgnoreRoot */ false,
188 /* aIsIgnoreLockfile */ true,
189 undeletedFiles);
191 retries++;
195 #ifdef DEBUG
196 // XXX: Until bug 1716291 is fixed, we do not want to spam release
197 if (undeletedFiles.Length() > 0) {
198 NS_WARNING("Unable to remove all files from the profile directory:");
199 // Log the file names of those we could not remove
200 for (auto&& file : undeletedFiles) {
201 nsAutoString leafName;
202 if (NS_SUCCEEDED(file->GetLeafName(leafName))) {
203 NS_WARNING(NS_LossyConvertUTF16toASCII(leafName).get());
207 #endif
208 // XXX: Activate this assert once bug 1716291 is fixed
209 // MOZ_ASSERT(undeletedFiles.Length() == 0);
211 // Now we can unlock the profile safely.
212 lock->Unlock();
213 // nsIProfileLock is not threadsafe so release our reference to it on
214 // the main thread.
215 NS_ReleaseOnMainThread("nsToolkitProfile::RemoveProfileFiles::Unlock",
216 lock.forget());
218 if (undeletedFiles.Length() == 0) {
219 // We can safely remove the (empty) remaining profile directory
220 // and lockfile, no other files are here.
221 // As we do this only if we had no other blockers, this is as safe
222 // as deleting the lockfile explicitely after unlocking.
223 Unused << rootDir->Remove(true);
227 if (aInBackground) {
228 nsCOMPtr<nsIEventTarget> target =
229 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
230 target->Dispatch(runnable, NS_DISPATCH_NORMAL);
231 } else {
232 runnable->Run();
236 nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
237 nsIFile* aLocalDir, bool aFromDB)
238 : mName(aName),
239 mRootDir(aRootDir),
240 mLocalDir(aLocalDir),
241 mLock(nullptr),
242 mIndex(0),
243 mSection("Profile") {
244 NS_ASSERTION(aRootDir, "No file!");
246 RefPtr<nsToolkitProfile> prev =
247 nsToolkitProfileService::gService->mProfiles.getLast();
248 if (prev) {
249 mIndex = prev->mIndex + 1;
251 mSection.AppendInt(mIndex);
253 nsToolkitProfileService::gService->mProfiles.insertBack(this);
255 // If this profile isn't in the database already add it.
256 if (!aFromDB) {
257 nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
258 db->SetString(mSection.get(), "Name", mName.get());
260 bool isRelative = false;
261 nsCString descriptor;
262 nsToolkitProfileService::gService->GetProfileDescriptor(this, descriptor,
263 &isRelative);
265 db->SetString(mSection.get(), "IsRelative", isRelative ? "1" : "0");
266 db->SetString(mSection.get(), "Path", descriptor.get());
270 NS_IMPL_ISUPPORTS(nsToolkitProfile, nsIToolkitProfile)
272 NS_IMETHODIMP
273 nsToolkitProfile::GetRootDir(nsIFile** aResult) {
274 NS_ADDREF(*aResult = mRootDir);
275 return NS_OK;
278 NS_IMETHODIMP
279 nsToolkitProfile::GetLocalDir(nsIFile** aResult) {
280 NS_ADDREF(*aResult = mLocalDir);
281 return NS_OK;
284 NS_IMETHODIMP
285 nsToolkitProfile::GetName(nsACString& aResult) {
286 aResult = mName;
287 return NS_OK;
290 NS_IMETHODIMP
291 nsToolkitProfile::SetName(const nsACString& aName) {
292 NS_ASSERTION(nsToolkitProfileService::gService, "Where did my service go?");
294 if (mName.Equals(aName)) {
295 return NS_OK;
298 // Changing the name from the dev-edition default profile name makes this
299 // profile no longer the dev-edition default.
300 if (mName.EqualsLiteral(DEV_EDITION_NAME) &&
301 nsToolkitProfileService::gService->mDevEditionDefault == this) {
302 nsToolkitProfileService::gService->mDevEditionDefault = nullptr;
305 mName = aName;
307 nsresult rv = nsToolkitProfileService::gService->mProfileDB.SetString(
308 mSection.get(), "Name", mName.get());
309 NS_ENSURE_SUCCESS(rv, rv);
311 // Setting the name to the dev-edition default profile name will cause this
312 // profile to become the dev-edition default.
313 if (aName.EqualsLiteral(DEV_EDITION_NAME) &&
314 !nsToolkitProfileService::gService->mDevEditionDefault) {
315 nsToolkitProfileService::gService->mDevEditionDefault = this;
318 return NS_OK;
321 nsresult nsToolkitProfile::RemoveInternal(bool aRemoveFiles,
322 bool aInBackground) {
323 NS_ASSERTION(nsToolkitProfileService::gService, "Whoa, my service is gone.");
325 if (mLock) return NS_ERROR_FILE_IS_LOCKED;
327 if (!isInList()) {
328 return NS_ERROR_NOT_INITIALIZED;
331 if (aRemoveFiles) {
332 RemoveProfileFiles(this, aInBackground);
335 nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
336 db->DeleteSection(mSection.get());
338 // We make some assumptions that the profile's index in the database is based
339 // on its position in the linked list. Removing a profile means we have to fix
340 // the index of later profiles in the list. The easiest way to do that is just
341 // to move the last profile into the profile's position and just update its
342 // index.
343 RefPtr<nsToolkitProfile> last =
344 nsToolkitProfileService::gService->mProfiles.getLast();
345 if (last != this) {
346 // Update the section in the db.
347 last->mIndex = mIndex;
348 db->RenameSection(last->mSection.get(), mSection.get());
349 last->mSection = mSection;
351 if (last != getNext()) {
352 last->remove();
353 setNext(last);
357 remove();
359 if (nsToolkitProfileService::gService->mNormalDefault == this) {
360 nsToolkitProfileService::gService->mNormalDefault = nullptr;
362 if (nsToolkitProfileService::gService->mDevEditionDefault == this) {
363 nsToolkitProfileService::gService->mDevEditionDefault = nullptr;
365 if (nsToolkitProfileService::gService->mDedicatedProfile == this) {
366 nsToolkitProfileService::gService->SetDefaultProfile(nullptr);
369 return NS_OK;
372 NS_IMETHODIMP
373 nsToolkitProfile::Remove(bool removeFiles) {
374 return RemoveInternal(removeFiles, false /* in background */);
377 NS_IMETHODIMP
378 nsToolkitProfile::RemoveInBackground(bool removeFiles) {
379 return RemoveInternal(removeFiles, true /* in background */);
382 NS_IMETHODIMP
383 nsToolkitProfile::Lock(nsIProfileUnlocker** aUnlocker,
384 nsIProfileLock** aResult) {
385 if (mLock) {
386 NS_ADDREF(*aResult = mLock);
387 return NS_OK;
390 RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
392 nsresult rv = lock->Init(this, aUnlocker);
393 if (NS_FAILED(rv)) return rv;
395 NS_ADDREF(*aResult = lock);
396 return NS_OK;
399 NS_IMPL_ISUPPORTS(nsToolkitProfileLock, nsIProfileLock)
401 nsresult nsToolkitProfileLock::Init(nsToolkitProfile* aProfile,
402 nsIProfileUnlocker** aUnlocker) {
403 nsresult rv;
404 rv = Init(aProfile->mRootDir, aProfile->mLocalDir, aUnlocker);
405 if (NS_SUCCEEDED(rv)) mProfile = aProfile;
407 return rv;
410 nsresult nsToolkitProfileLock::Init(nsIFile* aDirectory,
411 nsIFile* aLocalDirectory,
412 nsIProfileUnlocker** aUnlocker) {
413 nsresult rv;
415 rv = mLock.Lock(aDirectory, aUnlocker);
417 if (NS_SUCCEEDED(rv)) {
418 mDirectory = aDirectory;
419 mLocalDirectory = aLocalDirectory;
422 return rv;
425 NS_IMETHODIMP
426 nsToolkitProfileLock::GetDirectory(nsIFile** aResult) {
427 if (!mDirectory) {
428 NS_ERROR("Not initialized, or unlocked!");
429 return NS_ERROR_NOT_INITIALIZED;
432 NS_ADDREF(*aResult = mDirectory);
433 return NS_OK;
436 NS_IMETHODIMP
437 nsToolkitProfileLock::GetLocalDirectory(nsIFile** aResult) {
438 if (!mLocalDirectory) {
439 NS_ERROR("Not initialized, or unlocked!");
440 return NS_ERROR_NOT_INITIALIZED;
443 NS_ADDREF(*aResult = mLocalDirectory);
444 return NS_OK;
447 NS_IMETHODIMP
448 nsToolkitProfileLock::Unlock() {
449 if (!mDirectory) {
450 NS_ERROR("Unlocking a never-locked nsToolkitProfileLock!");
451 return NS_ERROR_UNEXPECTED;
454 // XXX If we get here with an active quota manager,
455 // something went very wrong. We want to assert this.
457 mLock.Unlock();
459 if (mProfile) {
460 mProfile->mLock = nullptr;
461 mProfile = nullptr;
463 mDirectory = nullptr;
464 mLocalDirectory = nullptr;
466 return NS_OK;
469 NS_IMETHODIMP
470 nsToolkitProfileLock::GetReplacedLockTime(PRTime* aResult) {
471 mLock.GetReplacedLockTime(aResult);
472 return NS_OK;
475 nsToolkitProfileLock::~nsToolkitProfileLock() {
476 if (mDirectory) {
477 Unlock();
481 nsToolkitProfileService* nsToolkitProfileService::gService = nullptr;
483 NS_IMPL_ISUPPORTS(nsToolkitProfileService, nsIToolkitProfileService)
485 nsToolkitProfileService::nsToolkitProfileService()
486 : mStartupProfileSelected(false),
487 mStartWithLast(true),
488 mIsFirstRun(true),
489 mUseDevEditionProfile(false),
490 #ifdef MOZ_DEDICATED_PROFILES
491 mUseDedicatedProfile(!IsSnapEnvironment() && !UseLegacyProfiles()),
492 #else
493 mUseDedicatedProfile(false),
494 #endif
495 mStartupReason(u"unknown"_ns),
496 mMaybeLockProfile(false),
497 mUpdateChannel(MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL)),
498 mProfileDBExists(false),
499 mProfileDBFileSize(0),
500 mProfileDBModifiedTime(0) {
501 #ifdef MOZ_DEV_EDITION
502 mUseDevEditionProfile = true;
503 #endif
506 nsToolkitProfileService::~nsToolkitProfileService() {
507 gService = nullptr;
508 mProfiles.clear();
511 void nsToolkitProfileService::CompleteStartup() {
512 if (!mStartupProfileSelected) {
513 return;
516 ScalarSet(mozilla::Telemetry::ScalarID::STARTUP_PROFILE_SELECTION_REASON,
517 mStartupReason);
519 if (mMaybeLockProfile) {
520 nsCOMPtr<nsIToolkitShellService> shell =
521 do_GetService(NS_TOOLKITSHELLSERVICE_CONTRACTID);
522 if (!shell) {
523 return;
526 bool isDefaultApp;
527 nsresult rv = shell->IsDefaultApplication(&isDefaultApp);
528 NS_ENSURE_SUCCESS_VOID(rv);
530 if (isDefaultApp) {
531 mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
533 // There is a very small chance that this could fail if something else
534 // overwrote the profiles database since we started up, probably less than
535 // a second ago. There isn't really a sane response here, all the other
536 // profile changes are already flushed so whether we fail to flush here or
537 // force quit the app makes no difference.
538 NS_ENSURE_SUCCESS_VOID(Flush());
543 // Tests whether the passed profile was last used by this install.
544 bool nsToolkitProfileService::IsProfileForCurrentInstall(
545 nsIToolkitProfile* aProfile) {
546 nsCOMPtr<nsIFile> profileDir;
547 nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir));
548 NS_ENSURE_SUCCESS(rv, false);
550 nsCOMPtr<nsIFile> compatFile;
551 rv = profileDir->Clone(getter_AddRefs(compatFile));
552 NS_ENSURE_SUCCESS(rv, false);
554 rv = compatFile->Append(COMPAT_FILE);
555 NS_ENSURE_SUCCESS(rv, false);
557 nsINIParser compatData;
558 rv = compatData.Init(compatFile);
559 NS_ENSURE_SUCCESS(rv, false);
562 * In xpcshell gDirServiceProvider doesn't have all the correct directories
563 * set so using NS_GetSpecialDirectory works better there. But in a normal
564 * app launch the component registry isn't initialized so
565 * NS_GetSpecialDirectory doesn't work. So we have to use two different
566 * paths to support testing.
568 nsCOMPtr<nsIFile> currentGreDir;
569 rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(currentGreDir));
570 if (rv == NS_ERROR_NOT_INITIALIZED) {
571 currentGreDir = gDirServiceProvider->GetGREDir();
572 MOZ_ASSERT(currentGreDir, "No GRE dir found.");
573 } else if (NS_FAILED(rv)) {
574 return false;
577 nsCString lastGreDirStr;
578 rv = compatData.GetString("Compatibility", "LastPlatformDir", lastGreDirStr);
579 // If this string is missing then this profile is from an ancient version.
580 // We'll opt to use it in this case.
581 if (NS_FAILED(rv)) {
582 return true;
585 nsCOMPtr<nsIFile> lastGreDir;
586 rv = NS_NewNativeLocalFile(""_ns, false, getter_AddRefs(lastGreDir));
587 NS_ENSURE_SUCCESS(rv, false);
589 rv = lastGreDir->SetPersistentDescriptor(lastGreDirStr);
590 NS_ENSURE_SUCCESS(rv, false);
592 #ifdef XP_WIN
593 # if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
594 mozilla::PathString lastGreDirPath, currentGreDirPath;
595 lastGreDirPath = lastGreDir->NativePath();
596 currentGreDirPath = currentGreDir->NativePath();
597 if (lastGreDirPath.Equals(currentGreDirPath,
598 nsCaseInsensitiveStringComparator)) {
599 return true;
602 // Convert a 64-bit install path to what would have been the 32-bit install
603 // path to allow users to migrate their profiles from one to the other.
604 PWSTR pathX86 = nullptr;
605 HRESULT hres =
606 SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, 0, nullptr, &pathX86);
607 if (SUCCEEDED(hres)) {
608 nsDependentString strPathX86(pathX86);
609 if (!StringBeginsWith(currentGreDirPath, strPathX86,
610 nsCaseInsensitiveStringComparator)) {
611 PWSTR path = nullptr;
612 hres = SHGetKnownFolderPath(FOLDERID_ProgramFiles, 0, nullptr, &path);
613 if (SUCCEEDED(hres)) {
614 if (StringBeginsWith(currentGreDirPath, nsDependentString(path),
615 nsCaseInsensitiveStringComparator)) {
616 currentGreDirPath.Replace(0, wcslen(path), strPathX86);
619 CoTaskMemFree(path);
622 CoTaskMemFree(pathX86);
624 return lastGreDirPath.Equals(currentGreDirPath,
625 nsCaseInsensitiveStringComparator);
626 # endif
627 #endif
629 bool equal;
630 rv = lastGreDir->Equals(currentGreDir, &equal);
631 NS_ENSURE_SUCCESS(rv, false);
633 return equal;
637 * Used the first time an install with dedicated profile support runs. Decides
638 * whether to mark the passed profile as the default for this install.
640 * The goal is to reduce disruption but ideally end up with the OS default
641 * install using the old default profile.
643 * If the decision is to use the profile then it will be unassigned as the
644 * dedicated default for other installs.
646 * We won't attempt to use the profile if it was last used by a different
647 * install.
649 * If the profile is currently in use by an install that was either the OS
650 * default install or the profile has been explicitely chosen by some other
651 * means then we won't use it.
653 * aResult will be set to true if we chose to make the profile the new dedicated
654 * default.
656 nsresult nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
657 nsIToolkitProfile* aProfile, bool* aResult) {
658 nsresult rv;
659 *aResult = false;
661 // If the profile was last used by a different install then we won't use it.
662 if (!IsProfileForCurrentInstall(aProfile)) {
663 return NS_OK;
666 nsCString descriptor;
667 rv = GetProfileDescriptor(aProfile, descriptor, nullptr);
668 NS_ENSURE_SUCCESS(rv, rv);
670 // Get a list of all the installs.
671 nsTArray<nsCString> installs = GetKnownInstalls();
673 // Cache the installs that use the profile.
674 nsTArray<nsCString> inUseInstalls;
676 // See if the profile is already in use by an install that hasn't locked it.
677 for (uint32_t i = 0; i < installs.Length(); i++) {
678 const nsCString& install = installs[i];
680 nsCString path;
681 rv = mProfileDB.GetString(install.get(), "Default", path);
682 if (NS_FAILED(rv)) {
683 continue;
686 // Is this install using the profile we care about?
687 if (!descriptor.Equals(path)) {
688 continue;
691 // Is this profile locked to this other install?
692 nsCString isLocked;
693 rv = mProfileDB.GetString(install.get(), "Locked", isLocked);
694 if (NS_SUCCEEDED(rv) && isLocked.Equals("1")) {
695 return NS_OK;
698 inUseInstalls.AppendElement(install);
701 // At this point we've decided to take the profile. Strip it from other
702 // installs.
703 for (uint32_t i = 0; i < inUseInstalls.Length(); i++) {
704 // Removing the default setting entirely will make the install go through
705 // the first run process again at startup and create itself a new profile.
706 mProfileDB.DeleteString(inUseInstalls[i].get(), "Default");
709 // Set this as the default profile for this install.
710 SetDefaultProfile(aProfile);
712 // SetDefaultProfile will have locked this profile to this install so no
713 // other installs will steal it, but this was auto-selected so we want to
714 // unlock it so that other installs can potentially take it.
715 mProfileDB.DeleteString(mInstallSection.get(), "Locked");
717 // Persist the changes.
718 rv = Flush();
719 NS_ENSURE_SUCCESS(rv, rv);
721 // Once XPCOM is available check if this is the default application and if so
722 // lock the profile again.
723 mMaybeLockProfile = true;
724 *aResult = true;
726 return NS_OK;
729 bool IsFileOutdated(nsIFile* aFile, bool aExists, PRTime aLastModified,
730 int64_t aLastSize) {
731 nsCOMPtr<nsIFile> file;
732 nsresult rv = aFile->Clone(getter_AddRefs(file));
733 if (NS_FAILED(rv)) {
734 return false;
737 bool exists;
738 rv = aFile->Exists(&exists);
739 if (NS_FAILED(rv) || exists != aExists) {
740 return true;
743 if (!exists) {
744 return false;
747 int64_t size;
748 rv = aFile->GetFileSize(&size);
749 if (NS_FAILED(rv) || size != aLastSize) {
750 return true;
753 PRTime time;
754 rv = aFile->GetLastModifiedTime(&time);
755 if (NS_FAILED(rv) || time != aLastModified) {
756 return true;
759 return false;
762 nsresult UpdateFileStats(nsIFile* aFile, bool* aExists, PRTime* aLastModified,
763 int64_t* aLastSize) {
764 nsCOMPtr<nsIFile> file;
765 nsresult rv = aFile->Clone(getter_AddRefs(file));
766 NS_ENSURE_SUCCESS(rv, rv);
768 rv = file->Exists(aExists);
769 NS_ENSURE_SUCCESS(rv, rv);
771 if (!(*aExists)) {
772 *aLastModified = 0;
773 *aLastSize = 0;
774 return NS_OK;
777 rv = file->GetFileSize(aLastSize);
778 NS_ENSURE_SUCCESS(rv, rv);
780 rv = file->GetLastModifiedTime(aLastModified);
781 NS_ENSURE_SUCCESS(rv, rv);
783 return NS_OK;
786 NS_IMETHODIMP
787 nsToolkitProfileService::GetIsListOutdated(bool* aResult) {
788 if (IsFileOutdated(mProfileDBFile, mProfileDBExists, mProfileDBModifiedTime,
789 mProfileDBFileSize)) {
790 *aResult = true;
791 return NS_OK;
794 *aResult = false;
795 return NS_OK;
798 struct ImportInstallsClosure {
799 nsINIParser* backupData;
800 nsINIParser* profileDB;
803 static bool ImportInstalls(const char* aSection, void* aClosure) {
804 ImportInstallsClosure* closure =
805 static_cast<ImportInstallsClosure*>(aClosure);
807 nsTArray<UniquePtr<KeyValue>> strings =
808 GetSectionStrings(closure->backupData, aSection);
809 if (strings.IsEmpty()) {
810 return true;
813 nsCString newSection(INSTALL_PREFIX);
814 newSection.Append(aSection);
815 nsCString buffer;
817 for (uint32_t i = 0; i < strings.Length(); i++) {
818 closure->profileDB->SetString(newSection.get(), strings[i]->key.get(),
819 strings[i]->value.get());
822 return true;
825 nsresult nsToolkitProfileService::Init() {
826 NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!");
827 nsresult rv;
829 rv = nsXREDirProvider::GetUserAppDataDirectory(getter_AddRefs(mAppData));
830 NS_ENSURE_SUCCESS(rv, rv);
832 rv = nsXREDirProvider::GetUserLocalDataDirectory(getter_AddRefs(mTempData));
833 NS_ENSURE_SUCCESS(rv, rv);
835 rv = mAppData->Clone(getter_AddRefs(mProfileDBFile));
836 NS_ENSURE_SUCCESS(rv, rv);
838 rv = mProfileDBFile->AppendNative("profiles.ini"_ns);
839 NS_ENSURE_SUCCESS(rv, rv);
841 rv = mAppData->Clone(getter_AddRefs(mInstallDBFile));
842 NS_ENSURE_SUCCESS(rv, rv);
844 rv = mInstallDBFile->AppendNative("installs.ini"_ns);
845 NS_ENSURE_SUCCESS(rv, rv);
847 nsAutoCString buffer;
849 rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
850 &mProfileDBModifiedTime, &mProfileDBFileSize);
851 if (NS_SUCCEEDED(rv) && mProfileDBExists) {
852 rv = mProfileDB.Init(mProfileDBFile);
853 // Init does not fail on parsing errors, only on OOM/really unexpected
854 // conditions.
855 if (NS_FAILED(rv)) {
856 return rv;
859 rv = mProfileDB.GetString("General", "StartWithLastProfile", buffer);
860 if (NS_SUCCEEDED(rv)) {
861 mStartWithLast = !buffer.EqualsLiteral("0");
864 rv = mProfileDB.GetString("General", "Version", buffer);
865 if (NS_FAILED(rv)) {
866 // This is a profiles.ini written by an older version. We must restore
867 // any install data from the backup.
868 nsINIParser installDB;
870 if (NS_SUCCEEDED(installDB.Init(mInstallDBFile))) {
871 // There is install data to import.
872 ImportInstallsClosure closure = {&installDB, &mProfileDB};
873 installDB.GetSections(&ImportInstalls, &closure);
876 rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
877 NS_ENSURE_SUCCESS(rv, rv);
879 } else {
880 rv = mProfileDB.SetString("General", "StartWithLastProfile",
881 mStartWithLast ? "1" : "0");
882 NS_ENSURE_SUCCESS(rv, rv);
883 rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
884 NS_ENSURE_SUCCESS(rv, rv);
887 nsCString installProfilePath;
889 if (mUseDedicatedProfile) {
890 nsString installHash;
891 rv = gDirServiceProvider->GetInstallHash(installHash);
892 NS_ENSURE_SUCCESS(rv, rv);
893 CopyUTF16toUTF8(installHash, mInstallSection);
894 mInstallSection.Insert(INSTALL_PREFIX, 0);
896 // Try to find the descriptor for the default profile for this install.
897 rv = mProfileDB.GetString(mInstallSection.get(), "Default",
898 installProfilePath);
900 // Not having a value means this install doesn't appear in installs.ini so
901 // this is the first run for this install.
902 if (NS_FAILED(rv)) {
903 mIsFirstRun = true;
905 // Gets the install section that would have been created if the install
906 // path has incorrect casing (see bug 1555319). We use this later during
907 // profile selection.
908 rv = gDirServiceProvider->GetLegacyInstallHash(installHash);
909 NS_ENSURE_SUCCESS(rv, rv);
910 CopyUTF16toUTF8(installHash, mLegacyInstallSection);
911 mLegacyInstallSection.Insert(INSTALL_PREFIX, 0);
912 } else {
913 mIsFirstRun = false;
917 nsToolkitProfile* currentProfile = nullptr;
919 #ifdef MOZ_DEV_EDITION
920 nsCOMPtr<nsIFile> ignoreDevEditionProfile;
921 rv = mAppData->Clone(getter_AddRefs(ignoreDevEditionProfile));
922 if (NS_FAILED(rv)) {
923 return rv;
926 rv = ignoreDevEditionProfile->AppendNative("ignore-dev-edition-profile"_ns);
927 if (NS_FAILED(rv)) {
928 return rv;
931 bool shouldIgnoreSeparateProfile;
932 rv = ignoreDevEditionProfile->Exists(&shouldIgnoreSeparateProfile);
933 if (NS_FAILED(rv)) return rv;
935 mUseDevEditionProfile = !shouldIgnoreSeparateProfile;
936 #endif
938 nsCOMPtr<nsIToolkitProfile> autoSelectProfile;
940 unsigned int nonDevEditionProfiles = 0;
941 unsigned int c = 0;
942 for (c = 0; true; ++c) {
943 nsAutoCString profileID("Profile");
944 profileID.AppendInt(c);
946 rv = mProfileDB.GetString(profileID.get(), "IsRelative", buffer);
947 if (NS_FAILED(rv)) break;
949 bool isRelative = buffer.EqualsLiteral("1");
951 nsAutoCString filePath;
953 rv = mProfileDB.GetString(profileID.get(), "Path", filePath);
954 if (NS_FAILED(rv)) {
955 NS_ERROR("Malformed profiles.ini: Path= not found");
956 continue;
959 nsAutoCString name;
961 rv = mProfileDB.GetString(profileID.get(), "Name", name);
962 if (NS_FAILED(rv)) {
963 NS_ERROR("Malformed profiles.ini: Name= not found");
964 continue;
967 nsCOMPtr<nsIFile> rootDir;
968 rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(rootDir));
969 NS_ENSURE_SUCCESS(rv, rv);
971 if (isRelative) {
972 rv = rootDir->SetRelativeDescriptor(mAppData, filePath);
973 } else {
974 rv = rootDir->SetPersistentDescriptor(filePath);
976 if (NS_FAILED(rv)) continue;
978 nsCOMPtr<nsIFile> localDir;
979 if (isRelative) {
980 rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localDir));
981 NS_ENSURE_SUCCESS(rv, rv);
983 rv = localDir->SetRelativeDescriptor(mTempData, filePath);
984 } else {
985 localDir = rootDir;
988 currentProfile = new nsToolkitProfile(name, rootDir, localDir, true);
990 // If a user has modified the ini file path it may make for a valid profile
991 // path but not match what we would have serialised and so may not match
992 // the path in the install section. Re-serialise it to get it in the
993 // expected form again.
994 bool nowRelative;
995 nsCString descriptor;
996 GetProfileDescriptor(currentProfile, descriptor, &nowRelative);
998 if (isRelative != nowRelative || !descriptor.Equals(filePath)) {
999 mProfileDB.SetString(profileID.get(), "IsRelative",
1000 nowRelative ? "1" : "0");
1001 mProfileDB.SetString(profileID.get(), "Path", descriptor.get());
1003 // Should we flush now? It costs some startup time and we will fix it on
1004 // the next startup anyway. If something else causes a flush then it will
1005 // be fixed in the ini file then.
1008 rv = mProfileDB.GetString(profileID.get(), "Default", buffer);
1009 if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1")) {
1010 mNormalDefault = currentProfile;
1013 // Is this the default profile for this install?
1014 if (mUseDedicatedProfile && !mDedicatedProfile &&
1015 installProfilePath.Equals(descriptor)) {
1016 // Found a profile for this install.
1017 mDedicatedProfile = currentProfile;
1020 if (name.EqualsLiteral(DEV_EDITION_NAME)) {
1021 mDevEditionDefault = currentProfile;
1022 } else {
1023 nonDevEditionProfiles++;
1024 autoSelectProfile = currentProfile;
1028 // If there is only one non-dev-edition profile then mark it as the default.
1029 if (!mNormalDefault && nonDevEditionProfiles == 1) {
1030 SetNormalDefault(autoSelectProfile);
1033 if (!mUseDedicatedProfile) {
1034 if (mUseDevEditionProfile) {
1035 // When using the separate dev-edition profile not finding it means this
1036 // is a first run.
1037 mIsFirstRun = !mDevEditionDefault;
1038 } else {
1039 // If there are no normal profiles then this is a first run.
1040 mIsFirstRun = nonDevEditionProfiles == 0;
1044 return NS_OK;
1047 NS_IMETHODIMP
1048 nsToolkitProfileService::SetStartWithLastProfile(bool aValue) {
1049 if (mStartWithLast != aValue) {
1050 // Note: the skeleton ui (see PreXULSkeletonUI.cpp) depends on this
1051 // having this name and being under General. If that ever changes,
1052 // the skeleton UI will just need to be updated. If it changes frequently,
1053 // it's probably best we just mirror the value to the registry here.
1054 nsresult rv = mProfileDB.SetString("General", "StartWithLastProfile",
1055 aValue ? "1" : "0");
1056 NS_ENSURE_SUCCESS(rv, rv);
1057 mStartWithLast = aValue;
1059 return NS_OK;
1062 NS_IMETHODIMP
1063 nsToolkitProfileService::GetStartWithLastProfile(bool* aResult) {
1064 *aResult = mStartWithLast;
1065 return NS_OK;
1068 NS_IMETHODIMP
1069 nsToolkitProfileService::GetProfiles(nsISimpleEnumerator** aResult) {
1070 *aResult = new ProfileEnumerator(mProfiles.getFirst());
1072 NS_ADDREF(*aResult);
1073 return NS_OK;
1076 NS_IMETHODIMP
1077 nsToolkitProfileService::ProfileEnumerator::HasMoreElements(bool* aResult) {
1078 *aResult = mCurrent ? true : false;
1079 return NS_OK;
1082 NS_IMETHODIMP
1083 nsToolkitProfileService::ProfileEnumerator::GetNext(nsISupports** aResult) {
1084 if (!mCurrent) return NS_ERROR_FAILURE;
1086 NS_ADDREF(*aResult = mCurrent);
1088 mCurrent = mCurrent->getNext();
1089 return NS_OK;
1092 NS_IMETHODIMP
1093 nsToolkitProfileService::GetCurrentProfile(nsIToolkitProfile** aResult) {
1094 NS_IF_ADDREF(*aResult = mCurrent);
1095 return NS_OK;
1098 NS_IMETHODIMP
1099 nsToolkitProfileService::GetDefaultProfile(nsIToolkitProfile** aResult) {
1100 if (mUseDedicatedProfile) {
1101 NS_IF_ADDREF(*aResult = mDedicatedProfile);
1102 return NS_OK;
1105 if (mUseDevEditionProfile) {
1106 NS_IF_ADDREF(*aResult = mDevEditionDefault);
1107 return NS_OK;
1110 NS_IF_ADDREF(*aResult = mNormalDefault);
1111 return NS_OK;
1114 void nsToolkitProfileService::SetNormalDefault(nsIToolkitProfile* aProfile) {
1115 if (mNormalDefault == aProfile) {
1116 return;
1119 if (mNormalDefault) {
1120 nsToolkitProfile* profile =
1121 static_cast<nsToolkitProfile*>(mNormalDefault.get());
1122 mProfileDB.DeleteString(profile->mSection.get(), "Default");
1125 mNormalDefault = aProfile;
1127 if (mNormalDefault) {
1128 nsToolkitProfile* profile =
1129 static_cast<nsToolkitProfile*>(mNormalDefault.get());
1130 mProfileDB.SetString(profile->mSection.get(), "Default", "1");
1134 NS_IMETHODIMP
1135 nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) {
1136 if (mUseDedicatedProfile) {
1137 if (mDedicatedProfile != aProfile) {
1138 if (!aProfile) {
1139 // Setting this to the empty string means no profile will be found on
1140 // startup but we'll recognise that this install has been used
1141 // previously.
1142 mProfileDB.SetString(mInstallSection.get(), "Default", "");
1143 } else {
1144 nsCString profilePath;
1145 nsresult rv = GetProfileDescriptor(aProfile, profilePath, nullptr);
1146 NS_ENSURE_SUCCESS(rv, rv);
1148 mProfileDB.SetString(mInstallSection.get(), "Default",
1149 profilePath.get());
1151 mDedicatedProfile = aProfile;
1153 // Some kind of choice has happened here, lock this profile to this
1154 // install.
1155 mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
1157 return NS_OK;
1160 if (mUseDevEditionProfile && aProfile != mDevEditionDefault) {
1161 // The separate profile is hardcoded.
1162 return NS_ERROR_FAILURE;
1165 SetNormalDefault(aProfile);
1167 return NS_OK;
1170 // Gets the profile root directory descriptor for storing in profiles.ini or
1171 // installs.ini.
1172 nsresult nsToolkitProfileService::GetProfileDescriptor(
1173 nsIToolkitProfile* aProfile, nsACString& aDescriptor, bool* aIsRelative) {
1174 nsCOMPtr<nsIFile> profileDir;
1175 nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir));
1176 NS_ENSURE_SUCCESS(rv, rv);
1178 // if the profile dir is relative to appdir...
1179 bool isRelative;
1180 rv = mAppData->Contains(profileDir, &isRelative);
1182 nsCString profilePath;
1183 if (NS_SUCCEEDED(rv) && isRelative) {
1184 // we use a relative descriptor
1185 rv = profileDir->GetRelativeDescriptor(mAppData, profilePath);
1186 } else {
1187 // otherwise, a persistent descriptor
1188 rv = profileDir->GetPersistentDescriptor(profilePath);
1190 NS_ENSURE_SUCCESS(rv, rv);
1192 aDescriptor.Assign(profilePath);
1193 if (aIsRelative) {
1194 *aIsRelative = isRelative;
1197 return NS_OK;
1200 nsresult nsToolkitProfileService::CreateDefaultProfile(
1201 nsIToolkitProfile** aResult) {
1202 // Create a new default profile
1203 nsAutoCString name;
1204 if (mUseDevEditionProfile) {
1205 name.AssignLiteral(DEV_EDITION_NAME);
1206 } else if (mUseDedicatedProfile) {
1207 name.AppendPrintf("default-%s", mUpdateChannel.get());
1208 } else {
1209 name.AssignLiteral(DEFAULT_NAME);
1212 nsresult rv = CreateUniqueProfile(nullptr, name, aResult);
1213 NS_ENSURE_SUCCESS(rv, rv);
1215 if (mUseDedicatedProfile) {
1216 SetDefaultProfile(mCurrent);
1217 } else if (mUseDevEditionProfile) {
1218 mDevEditionDefault = mCurrent;
1219 } else {
1220 SetNormalDefault(mCurrent);
1223 return NS_OK;
1227 * An implementation of SelectStartupProfile callable from JavaScript via XPCOM.
1228 * See nsIToolkitProfileService.idl.
1230 NS_IMETHODIMP
1231 nsToolkitProfileService::SelectStartupProfile(
1232 const nsTArray<nsCString>& aArgv, bool aIsResetting,
1233 const nsACString& aUpdateChannel, const nsACString& aLegacyInstallHash,
1234 nsIFile** aRootDir, nsIFile** aLocalDir, nsIToolkitProfile** aProfile,
1235 bool* aDidCreate) {
1236 int argc = aArgv.Length();
1237 // Our command line handling expects argv to be null-terminated so construct
1238 // an appropriate array.
1239 auto argv = MakeUnique<char*[]>(argc + 1);
1240 // Also, our command line handling removes things from the array without
1241 // freeing them so keep track of what we've created separately.
1242 auto allocated = MakeUnique<UniqueFreePtr<char>[]>(argc);
1244 for (int i = 0; i < argc; i++) {
1245 allocated[i].reset(ToNewCString(aArgv[i]));
1246 argv[i] = allocated[i].get();
1248 argv[argc] = nullptr;
1250 mUpdateChannel = aUpdateChannel;
1251 if (!aLegacyInstallHash.IsEmpty()) {
1252 mLegacyInstallSection.Assign(aLegacyInstallHash);
1253 mLegacyInstallSection.Insert(INSTALL_PREFIX, 0);
1256 bool wasDefault;
1257 nsresult rv =
1258 SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir, aLocalDir,
1259 aProfile, aDidCreate, &wasDefault);
1261 // Since we were called outside of the normal startup path complete any
1262 // startup tasks.
1263 if (NS_SUCCEEDED(rv)) {
1264 CompleteStartup();
1267 return rv;
1270 static void SaltProfileName(nsACString& aName);
1273 * Selects or creates a profile to use based on the profiles database, any
1274 * environment variables and any command line arguments. Will not create
1275 * a profile if aIsResetting is true. The profile is selected based on this
1276 * order of preference:
1277 * * Environment variables (set when restarting the application).
1278 * * --profile command line argument.
1279 * * --createprofile command line argument (this also causes the app to exit).
1280 * * -p command line argument.
1281 * * A new profile created if this is the first run of the application.
1282 * * The default profile.
1283 * aRootDir and aLocalDir are set to the data and local directories for the
1284 * profile data. If a profile from the database was selected it will be
1285 * returned in aProfile.
1286 * aDidCreate will be set to true if a new profile was created.
1287 * This function should be called once at startup and will fail if called again.
1288 * aArgv should be an array of aArgc + 1 strings, the last element being null.
1289 * Both aArgv and aArgc will be mutated.
1291 nsresult nsToolkitProfileService::SelectStartupProfile(
1292 int* aArgc, char* aArgv[], bool aIsResetting, nsIFile** aRootDir,
1293 nsIFile** aLocalDir, nsIToolkitProfile** aProfile, bool* aDidCreate,
1294 bool* aWasDefaultSelection) {
1295 if (mStartupProfileSelected) {
1296 return NS_ERROR_ALREADY_INITIALIZED;
1299 mStartupProfileSelected = true;
1300 *aDidCreate = false;
1301 *aWasDefaultSelection = false;
1303 nsresult rv;
1304 const char* arg;
1306 // Use the profile specified in the environment variables (generally from an
1307 // app initiated restart).
1308 nsCOMPtr<nsIFile> lf = GetFileFromEnv("XRE_PROFILE_PATH");
1309 if (lf) {
1310 nsCOMPtr<nsIFile> localDir = GetFileFromEnv("XRE_PROFILE_LOCAL_PATH");
1311 if (!localDir) {
1312 localDir = lf;
1315 // Clear out flags that we handled (or should have handled!) last startup.
1316 const char* dummy;
1317 CheckArg(*aArgc, aArgv, "p", &dummy);
1318 CheckArg(*aArgc, aArgv, "profile", &dummy);
1319 CheckArg(*aArgc, aArgv, "profilemanager");
1321 nsCOMPtr<nsIToolkitProfile> profile;
1322 GetProfileByDir(lf, localDir, getter_AddRefs(profile));
1324 if (profile && mIsFirstRun && mUseDedicatedProfile) {
1325 if (profile ==
1326 (mUseDevEditionProfile ? mDevEditionDefault : mNormalDefault)) {
1327 // This is the first run of a dedicated profile build where the selected
1328 // profile is the previous default so we should either make it the
1329 // default profile for this install or push the user to a new profile.
1331 bool result;
1332 rv = MaybeMakeDefaultDedicatedProfile(profile, &result);
1333 NS_ENSURE_SUCCESS(rv, rv);
1334 if (result) {
1335 mStartupReason = u"restart-claimed-default"_ns;
1337 mCurrent = profile;
1338 } else {
1339 rv = CreateDefaultProfile(getter_AddRefs(mCurrent));
1340 if (NS_FAILED(rv)) {
1341 *aProfile = nullptr;
1342 return rv;
1345 rv = Flush();
1346 NS_ENSURE_SUCCESS(rv, rv);
1348 mStartupReason = u"restart-skipped-default"_ns;
1349 *aDidCreate = true;
1352 NS_IF_ADDREF(*aProfile = mCurrent);
1353 mCurrent->GetRootDir(aRootDir);
1354 mCurrent->GetLocalDir(aLocalDir);
1356 return NS_OK;
1360 if (EnvHasValue("XRE_RESTARTED_BY_PROFILE_MANAGER")) {
1361 mStartupReason = u"profile-manager"_ns;
1362 } else if (aIsResetting) {
1363 mStartupReason = u"profile-reset"_ns;
1364 } else {
1365 mStartupReason = u"restart"_ns;
1368 mCurrent = profile;
1369 lf.forget(aRootDir);
1370 localDir.forget(aLocalDir);
1371 NS_IF_ADDREF(*aProfile = profile);
1372 return NS_OK;
1375 // Check the -profile command line argument. It accepts a single argument that
1376 // gives the path to use for the profile.
1377 ArgResult ar = CheckArg(*aArgc, aArgv, "profile", &arg);
1378 if (ar == ARG_BAD) {
1379 PR_fprintf(PR_STDERR, "Error: argument --profile requires a path\n");
1380 return NS_ERROR_FAILURE;
1382 if (ar) {
1383 nsCOMPtr<nsIFile> lf;
1384 rv = XRE_GetFileFromPath(arg, getter_AddRefs(lf));
1385 NS_ENSURE_SUCCESS(rv, rv);
1387 // Make sure that the profile path exists and it's a directory.
1388 bool exists;
1389 rv = lf->Exists(&exists);
1390 NS_ENSURE_SUCCESS(rv, rv);
1391 if (!exists) {
1392 rv = lf->Create(nsIFile::DIRECTORY_TYPE, 0700);
1393 NS_ENSURE_SUCCESS(rv, rv);
1394 } else {
1395 bool isDir;
1396 rv = lf->IsDirectory(&isDir);
1397 NS_ENSURE_SUCCESS(rv, rv);
1398 if (!isDir) {
1399 PR_fprintf(
1400 PR_STDERR,
1401 "Error: argument --profile requires a path to a directory\n");
1402 return NS_ERROR_FAILURE;
1406 mStartupReason = u"argument-profile"_ns;
1408 GetProfileByDir(lf, nullptr, getter_AddRefs(mCurrent));
1409 NS_ADDREF(*aRootDir = lf);
1410 // If the root dir matched a profile then use its local dir, otherwise use
1411 // the root dir as the local dir.
1412 if (mCurrent) {
1413 mCurrent->GetLocalDir(aLocalDir);
1414 } else {
1415 lf.forget(aLocalDir);
1418 NS_IF_ADDREF(*aProfile = mCurrent);
1419 return NS_OK;
1422 // Check the -createprofile command line argument. It accepts a single
1423 // argument that is either the name for the new profile or the name followed
1424 // by the path to use.
1425 ar = CheckArg(*aArgc, aArgv, "createprofile", &arg, CheckArgFlag::RemoveArg);
1426 if (ar == ARG_BAD) {
1427 PR_fprintf(PR_STDERR,
1428 "Error: argument --createprofile requires a profile name\n");
1429 return NS_ERROR_FAILURE;
1431 if (ar) {
1432 const char* delim = strchr(arg, ' ');
1433 nsCOMPtr<nsIToolkitProfile> profile;
1434 if (delim) {
1435 nsCOMPtr<nsIFile> lf;
1436 rv = NS_NewNativeLocalFile(nsDependentCString(delim + 1), true,
1437 getter_AddRefs(lf));
1438 if (NS_FAILED(rv)) {
1439 PR_fprintf(PR_STDERR, "Error: profile path not valid.\n");
1440 return rv;
1443 // As with --profile, assume that the given path will be used for the
1444 // main profile directory.
1445 rv = CreateProfile(lf, nsDependentCSubstring(arg, delim),
1446 getter_AddRefs(profile));
1447 } else {
1448 rv = CreateProfile(nullptr, nsDependentCString(arg),
1449 getter_AddRefs(profile));
1451 // Some pathological arguments can make it this far
1452 if (NS_FAILED(rv) || NS_FAILED(Flush())) {
1453 PR_fprintf(PR_STDERR, "Error creating profile.\n");
1455 return NS_ERROR_ABORT;
1458 // Check the -p command line argument. It either accepts a profile name and
1459 // uses that named profile or without a name it opens the profile manager.
1460 ar = CheckArg(*aArgc, aArgv, "p", &arg);
1461 if (ar == ARG_BAD) {
1462 return NS_ERROR_SHOW_PROFILE_MANAGER;
1464 if (ar) {
1465 rv = GetProfileByName(nsDependentCString(arg), getter_AddRefs(mCurrent));
1466 if (NS_SUCCEEDED(rv)) {
1467 mStartupReason = u"argument-p"_ns;
1469 mCurrent->GetRootDir(aRootDir);
1470 mCurrent->GetLocalDir(aLocalDir);
1472 NS_ADDREF(*aProfile = mCurrent);
1473 return NS_OK;
1476 return NS_ERROR_SHOW_PROFILE_MANAGER;
1479 ar = CheckArg(*aArgc, aArgv, "profilemanager");
1480 if (ar == ARG_FOUND) {
1481 return NS_ERROR_SHOW_PROFILE_MANAGER;
1484 #ifdef MOZ_BACKGROUNDTASKS
1485 if (BackgroundTasks::IsBackgroundTaskMode()) {
1486 // There are two cases:
1487 // 1. ephemeral profile: create a new one in temporary directory.
1488 // 2. non-ephemeral (persistent) profile:
1489 // a. if no salted profile is known, create a new one in
1490 // background task-specific directory.
1491 // b. if salted profile is know, use salted path.
1492 nsString installHash;
1493 rv = gDirServiceProvider->GetInstallHash(installHash);
1494 NS_ENSURE_SUCCESS(rv, rv);
1496 nsCString profilePrefix(BackgroundTasks::GetProfilePrefix(
1497 NS_LossyConvertUTF16toASCII(installHash)));
1499 nsCString taskName(BackgroundTasks::GetBackgroundTasks().ref());
1501 nsCOMPtr<nsIFile> file;
1503 if (BackgroundTasks::IsEphemeralProfileTaskName(taskName)) {
1504 // Background task mode does not enable legacy telemetry, so this is for
1505 // completeness and testing only.
1506 mStartupReason = u"backgroundtask-ephemeral"_ns;
1508 nsCOMPtr<nsIFile> rootDir;
1509 rv = GetSpecialSystemDirectory(OS_TemporaryDirectory,
1510 getter_AddRefs(rootDir));
1511 NS_ENSURE_SUCCESS(rv, rv);
1513 nsresult rv = BackgroundTasks::CreateEphemeralProfileDirectory(
1514 rootDir, profilePrefix, getter_AddRefs(file));
1515 if (NS_WARN_IF(NS_FAILED(rv))) {
1516 // In background task mode, NS_ERROR_UNEXPECTED is handled specially to
1517 // exit with a non-zero exit code.
1518 return NS_ERROR_UNEXPECTED;
1520 *aDidCreate = true;
1521 } else {
1522 // Background task mode does not enable legacy telemetry, so this is for
1523 // completeness and testing only.
1524 mStartupReason = u"backgroundtask-not-ephemeral"_ns;
1526 // A non-ephemeral profile is required.
1527 nsCOMPtr<nsIFile> rootDir;
1528 nsresult rv = gDirServiceProvider->GetBackgroundTasksProfilesRootDir(
1529 getter_AddRefs(rootDir));
1530 NS_ENSURE_SUCCESS(rv, rv);
1532 nsAutoCString buffer;
1533 rv = mProfileDB.GetString("BackgroundTasksProfiles", profilePrefix.get(),
1534 buffer);
1535 if (NS_SUCCEEDED(rv)) {
1536 // We have a record of one! Use it.
1537 rv = rootDir->Clone(getter_AddRefs(file));
1538 NS_ENSURE_SUCCESS(rv, rv);
1540 rv = file->AppendNative(buffer);
1541 NS_ENSURE_SUCCESS(rv, rv);
1542 } else {
1543 nsCString saltedProfilePrefix = profilePrefix;
1544 SaltProfileName(saltedProfilePrefix);
1546 nsresult rv = BackgroundTasks::CreateNonEphemeralProfileDirectory(
1547 rootDir, saltedProfilePrefix, getter_AddRefs(file));
1548 if (NS_WARN_IF(NS_FAILED(rv))) {
1549 // In background task mode, NS_ERROR_UNEXPECTED is handled specially
1550 // to exit with a non-zero exit code.
1551 return NS_ERROR_UNEXPECTED;
1553 *aDidCreate = true;
1555 // Keep a record of the salted name. It's okay if this doesn't succeed:
1556 // not great, but it's better for tasks (particularly,
1557 // `backgroundupdate`) to run and not persist state correctly than to
1558 // not run at all.
1559 rv =
1560 mProfileDB.SetString("BackgroundTasksProfiles", profilePrefix.get(),
1561 saltedProfilePrefix.get());
1562 Unused << NS_WARN_IF(NS_FAILED(rv));
1564 if (NS_SUCCEEDED(rv)) {
1565 rv = Flush();
1566 Unused << NS_WARN_IF(NS_FAILED(rv));
1571 nsCOMPtr<nsIFile> localDir = file;
1572 file.forget(aRootDir);
1573 localDir.forget(aLocalDir);
1575 // Background tasks never use profiles known to the profile service.
1576 *aProfile = nullptr;
1578 return NS_OK;
1580 #endif
1582 if (mIsFirstRun && mUseDedicatedProfile &&
1583 !mInstallSection.Equals(mLegacyInstallSection)) {
1584 // The default profile could be assigned to a hash generated from an
1585 // incorrectly cased version of the installation directory (see bug
1586 // 1555319). Ideally we'd do all this while loading profiles.ini but we
1587 // can't override the legacy section value before that for tests.
1588 nsCString defaultDescriptor;
1589 rv = mProfileDB.GetString(mLegacyInstallSection.get(), "Default",
1590 defaultDescriptor);
1592 if (NS_SUCCEEDED(rv)) {
1593 // There is a default here, need to see if it matches any profiles.
1594 bool isRelative;
1595 nsCString descriptor;
1597 for (RefPtr<nsToolkitProfile> profile : mProfiles) {
1598 GetProfileDescriptor(profile, descriptor, &isRelative);
1600 if (descriptor.Equals(defaultDescriptor)) {
1601 // Found the default profile. Copy the install section over to
1602 // the correct location. We leave the old info in place for older
1603 // versions of Firefox to use.
1604 nsTArray<UniquePtr<KeyValue>> strings =
1605 GetSectionStrings(&mProfileDB, mLegacyInstallSection.get());
1606 for (const auto& kv : strings) {
1607 mProfileDB.SetString(mInstallSection.get(), kv->key.get(),
1608 kv->value.get());
1611 // Flush now. This causes a small blip in startup but it should be
1612 // one time only whereas not flushing means we have to do this search
1613 // on every startup.
1614 Flush();
1616 // Now start up with the found profile.
1617 mDedicatedProfile = profile;
1618 mIsFirstRun = false;
1619 break;
1625 // If this is a first run then create a new profile.
1626 if (mIsFirstRun) {
1627 // If we're configured to always show the profile manager then don't create
1628 // a new profile to use.
1629 if (!mStartWithLast) {
1630 return NS_ERROR_SHOW_PROFILE_MANAGER;
1633 bool skippedDefaultProfile = false;
1635 if (mUseDedicatedProfile) {
1636 // This is the first run of a dedicated profile install. We have to decide
1637 // whether to use the default profile used by non-dedicated-profile
1638 // installs or to create a new profile.
1640 // Find what would have been the default profile for old installs.
1641 nsCOMPtr<nsIToolkitProfile> profile = mNormalDefault;
1642 if (mUseDevEditionProfile) {
1643 profile = mDevEditionDefault;
1646 if (profile) {
1647 nsCOMPtr<nsIFile> rootDir;
1648 profile->GetRootDir(getter_AddRefs(rootDir));
1650 nsCOMPtr<nsIFile> compat;
1651 rootDir->Clone(getter_AddRefs(compat));
1652 compat->Append(COMPAT_FILE);
1654 bool exists;
1655 rv = compat->Exists(&exists);
1656 NS_ENSURE_SUCCESS(rv, rv);
1658 // If the file is missing then either this is an empty profile (likely
1659 // generated by bug 1518591) or it is from an ancient version. We'll opt
1660 // to leave it for older versions in this case.
1661 if (exists) {
1662 bool result;
1663 rv = MaybeMakeDefaultDedicatedProfile(profile, &result);
1664 NS_ENSURE_SUCCESS(rv, rv);
1665 if (result) {
1666 mStartupReason = u"firstrun-claimed-default"_ns;
1668 mCurrent = profile;
1669 rootDir.forget(aRootDir);
1670 profile->GetLocalDir(aLocalDir);
1671 profile.forget(aProfile);
1672 return NS_OK;
1675 // We're going to create a new profile for this install even though
1676 // another default exists.
1677 skippedDefaultProfile = true;
1682 rv = CreateDefaultProfile(getter_AddRefs(mCurrent));
1683 if (NS_SUCCEEDED(rv)) {
1684 #ifdef MOZ_CREATE_LEGACY_PROFILE
1685 // If there is only one profile and it isn't meant to be the profile that
1686 // older versions of Firefox use then we must create a default profile
1687 // for older versions of Firefox to avoid the existing profile being
1688 // auto-selected.
1689 if ((mUseDedicatedProfile || mUseDevEditionProfile) &&
1690 mProfiles.getFirst() == mProfiles.getLast()) {
1691 nsCOMPtr<nsIToolkitProfile> newProfile;
1692 CreateProfile(nullptr, nsLiteralCString(DEFAULT_NAME),
1693 getter_AddRefs(newProfile));
1694 SetNormalDefault(newProfile);
1696 #endif
1698 rv = Flush();
1699 NS_ENSURE_SUCCESS(rv, rv);
1701 if (skippedDefaultProfile) {
1702 mStartupReason = u"firstrun-skipped-default"_ns;
1703 } else {
1704 mStartupReason = u"firstrun-created-default"_ns;
1707 // Use the new profile.
1708 mCurrent->GetRootDir(aRootDir);
1709 mCurrent->GetLocalDir(aLocalDir);
1710 NS_ADDREF(*aProfile = mCurrent);
1712 *aDidCreate = true;
1713 return NS_OK;
1717 GetDefaultProfile(getter_AddRefs(mCurrent));
1719 // None of the profiles was marked as default (generally only happens if the
1720 // user modifies profiles.ini manually). Let the user choose.
1721 if (!mCurrent) {
1722 return NS_ERROR_SHOW_PROFILE_MANAGER;
1725 // Let the caller know that the profile was selected by default.
1726 *aWasDefaultSelection = true;
1727 mStartupReason = u"default"_ns;
1729 // Use the selected profile.
1730 mCurrent->GetRootDir(aRootDir);
1731 mCurrent->GetLocalDir(aLocalDir);
1732 NS_ADDREF(*aProfile = mCurrent);
1734 return NS_OK;
1738 * Creates a new profile for reset and mark it as the current profile.
1740 nsresult nsToolkitProfileService::CreateResetProfile(
1741 nsIToolkitProfile** aNewProfile) {
1742 nsAutoCString oldProfileName;
1743 mCurrent->GetName(oldProfileName);
1745 nsCOMPtr<nsIToolkitProfile> newProfile;
1746 // Make the new profile name the old profile (or "default-") + the time in
1747 // seconds since epoch for uniqueness.
1748 nsAutoCString newProfileName;
1749 if (!oldProfileName.IsEmpty()) {
1750 newProfileName.Assign(oldProfileName);
1751 newProfileName.Append("-");
1752 } else {
1753 newProfileName.AssignLiteral("default-");
1755 newProfileName.AppendPrintf("%" PRId64, PR_Now() / 1000);
1756 nsresult rv = CreateProfile(nullptr, // choose a default dir for us
1757 newProfileName, getter_AddRefs(newProfile));
1758 if (NS_FAILED(rv)) return rv;
1760 mCurrent = newProfile;
1761 newProfile.forget(aNewProfile);
1763 // Don't flush the changes yet. That will happen once the migration
1764 // successfully completes.
1765 return NS_OK;
1769 * This is responsible for deleting the old profile, copying its name to the
1770 * current profile and if the old profile was default making the new profile
1771 * default as well.
1773 nsresult nsToolkitProfileService::ApplyResetProfile(
1774 nsIToolkitProfile* aOldProfile) {
1775 // If the old profile would have been the default for old installs then mark
1776 // the new profile as such.
1777 if (mNormalDefault == aOldProfile) {
1778 SetNormalDefault(mCurrent);
1781 if (mUseDedicatedProfile && mDedicatedProfile == aOldProfile) {
1782 bool wasLocked = false;
1783 nsCString val;
1784 if (NS_SUCCEEDED(
1785 mProfileDB.GetString(mInstallSection.get(), "Locked", val))) {
1786 wasLocked = val.Equals("1");
1789 SetDefaultProfile(mCurrent);
1791 // Make the locked state match if necessary.
1792 if (!wasLocked) {
1793 mProfileDB.DeleteString(mInstallSection.get(), "Locked");
1797 nsCString name;
1798 nsresult rv = aOldProfile->GetName(name);
1799 NS_ENSURE_SUCCESS(rv, rv);
1801 // Don't remove the old profile's files until after we've successfully flushed
1802 // the profile changes to disk.
1803 rv = aOldProfile->Remove(false);
1804 NS_ENSURE_SUCCESS(rv, rv);
1806 // Switching the name will make this the default for dev-edition if
1807 // appropriate.
1808 rv = mCurrent->SetName(name);
1809 NS_ENSURE_SUCCESS(rv, rv);
1811 rv = Flush();
1812 NS_ENSURE_SUCCESS(rv, rv);
1814 // Now that the profile changes are flushed, try to remove the old profile's
1815 // files. If we fail the worst that will happen is that an orphan directory is
1816 // left. Let this run in the background while we start up.
1817 RemoveProfileFiles(aOldProfile, true);
1819 return NS_OK;
1822 NS_IMETHODIMP
1823 nsToolkitProfileService::GetProfileByName(const nsACString& aName,
1824 nsIToolkitProfile** aResult) {
1825 for (RefPtr<nsToolkitProfile> profile : mProfiles) {
1826 if (profile->mName.Equals(aName)) {
1827 NS_ADDREF(*aResult = profile);
1828 return NS_OK;
1832 return NS_ERROR_FAILURE;
1836 * Finds a profile from the database that uses the given root and local
1837 * directories.
1839 void nsToolkitProfileService::GetProfileByDir(nsIFile* aRootDir,
1840 nsIFile* aLocalDir,
1841 nsIToolkitProfile** aResult) {
1842 for (RefPtr<nsToolkitProfile> profile : mProfiles) {
1843 bool equal;
1844 nsresult rv = profile->mRootDir->Equals(aRootDir, &equal);
1845 if (NS_SUCCEEDED(rv) && equal) {
1846 if (!aLocalDir) {
1847 // If no local directory was given then we will just use the normal
1848 // local directory for the profile.
1849 profile.forget(aResult);
1850 return;
1853 rv = profile->mLocalDir->Equals(aLocalDir, &equal);
1854 if (NS_SUCCEEDED(rv) && equal) {
1855 profile.forget(aResult);
1856 return;
1862 nsresult NS_LockProfilePath(nsIFile* aPath, nsIFile* aTempPath,
1863 nsIProfileUnlocker** aUnlocker,
1864 nsIProfileLock** aResult) {
1865 RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
1867 nsresult rv = lock->Init(aPath, aTempPath, aUnlocker);
1868 if (NS_FAILED(rv)) return rv;
1870 lock.forget(aResult);
1871 return NS_OK;
1874 static void SaltProfileName(nsACString& aName) {
1875 char salt[9];
1876 NS_MakeRandomString(salt, 8);
1877 salt[8] = '.';
1879 aName.Insert(salt, 0, 9);
1882 NS_IMETHODIMP
1883 nsToolkitProfileService::CreateUniqueProfile(nsIFile* aRootDir,
1884 const nsACString& aNamePrefix,
1885 nsIToolkitProfile** aResult) {
1886 nsCOMPtr<nsIToolkitProfile> profile;
1887 nsresult rv = GetProfileByName(aNamePrefix, getter_AddRefs(profile));
1888 if (NS_FAILED(rv)) {
1889 return CreateProfile(aRootDir, aNamePrefix, aResult);
1892 uint32_t suffix = 1;
1893 while (true) {
1894 nsPrintfCString name("%s-%d", PromiseFlatCString(aNamePrefix).get(),
1895 suffix);
1896 rv = GetProfileByName(name, getter_AddRefs(profile));
1897 if (NS_FAILED(rv)) {
1898 return CreateProfile(aRootDir, name, aResult);
1900 suffix++;
1904 NS_IMETHODIMP
1905 nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
1906 const nsACString& aName,
1907 nsIToolkitProfile** aResult) {
1908 nsresult rv = GetProfileByName(aName, aResult);
1909 if (NS_SUCCEEDED(rv)) {
1910 return rv;
1913 nsCOMPtr<nsIFile> rootDir(aRootDir);
1915 nsAutoCString dirName;
1916 if (!rootDir) {
1917 rv = gDirServiceProvider->GetUserProfilesRootDir(getter_AddRefs(rootDir));
1918 NS_ENSURE_SUCCESS(rv, rv);
1920 dirName = aName;
1921 SaltProfileName(dirName);
1923 if (NS_IsNativeUTF8()) {
1924 rootDir->AppendNative(dirName);
1925 } else {
1926 rootDir->Append(NS_ConvertUTF8toUTF16(dirName));
1930 nsCOMPtr<nsIFile> localDir;
1932 bool isRelative;
1933 rv = mAppData->Contains(rootDir, &isRelative);
1934 if (NS_SUCCEEDED(rv) && isRelative) {
1935 nsAutoCString path;
1936 rv = rootDir->GetRelativeDescriptor(mAppData, path);
1937 NS_ENSURE_SUCCESS(rv, rv);
1939 rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localDir));
1940 NS_ENSURE_SUCCESS(rv, rv);
1942 rv = localDir->SetRelativeDescriptor(mTempData, path);
1943 } else {
1944 localDir = rootDir;
1947 bool exists;
1948 rv = rootDir->Exists(&exists);
1949 NS_ENSURE_SUCCESS(rv, rv);
1951 if (exists) {
1952 rv = rootDir->IsDirectory(&exists);
1953 NS_ENSURE_SUCCESS(rv, rv);
1955 if (!exists) return NS_ERROR_FILE_NOT_DIRECTORY;
1956 } else {
1957 nsCOMPtr<nsIFile> profileDirParent;
1958 nsAutoString profileDirName;
1960 rv = rootDir->GetParent(getter_AddRefs(profileDirParent));
1961 NS_ENSURE_SUCCESS(rv, rv);
1963 rv = rootDir->GetLeafName(profileDirName);
1964 NS_ENSURE_SUCCESS(rv, rv);
1966 // let's ensure that the profile directory exists.
1967 rv = rootDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
1968 NS_ENSURE_SUCCESS(rv, rv);
1969 rv = rootDir->SetPermissions(0700);
1970 #ifndef ANDROID
1971 // If the profile is on the sdcard, this will fail but its non-fatal
1972 NS_ENSURE_SUCCESS(rv, rv);
1973 #endif
1976 rv = localDir->Exists(&exists);
1977 NS_ENSURE_SUCCESS(rv, rv);
1979 if (!exists) {
1980 rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
1981 NS_ENSURE_SUCCESS(rv, rv);
1984 // We created a new profile dir. Let's store a creation timestamp.
1985 // Note that this code path does not apply if the profile dir was
1986 // created prior to launching.
1987 rv = CreateTimesInternal(rootDir);
1988 NS_ENSURE_SUCCESS(rv, rv);
1990 nsCOMPtr<nsIToolkitProfile> profile =
1991 new nsToolkitProfile(aName, rootDir, localDir, false);
1993 if (aName.Equals(DEV_EDITION_NAME)) {
1994 mDevEditionDefault = profile;
1997 profile.forget(aResult);
1998 return NS_OK;
2002 * Snaps (https://snapcraft.io/) use a different installation directory for
2003 * every version of an application. Since dedicated profiles uses the
2004 * installation directory to determine which profile to use this would lead
2005 * snap users getting a new profile on every application update.
2007 * However the only way to have multiple installation of a snap is to install
2008 * a new snap instance. Different snap instances have different user data
2009 * directories and so already will not share profiles, in fact one instance
2010 * will not even be able to see the other instance's profiles since
2011 * profiles.ini will be stored in different places.
2013 * So we can just disable dedicated profile support in this case and revert
2014 * back to the old method of just having a single default profile and still
2015 * get essentially the same benefits as dedicated profiles provides.
2017 bool nsToolkitProfileService::IsSnapEnvironment() {
2018 #ifdef MOZ_WIDGET_GTK
2019 return widget::IsRunningUnderSnap();
2020 #else
2021 return false;
2022 #endif
2026 * In some situations dedicated profile support does not work well. This
2027 * includes a handful of linux distributions which always install different
2028 * application versions to different locations, some application sandboxing
2029 * systems as well as enterprise deployments. This environment variable provides
2030 * a way to opt out of dedicated profiles for these cases.
2032 * For Windows, we provide a policy to accomplish the same thing.
2034 bool nsToolkitProfileService::UseLegacyProfiles() {
2035 bool legacyProfiles = !!PR_GetEnv("MOZ_LEGACY_PROFILES");
2036 #ifdef XP_WIN
2037 legacyProfiles |= PolicyCheckBoolean(L"LegacyProfiles");
2038 #endif
2039 return legacyProfiles;
2042 struct FindInstallsClosure {
2043 nsINIParser* installData;
2044 nsTArray<nsCString>* installs;
2047 static bool FindInstalls(const char* aSection, void* aClosure) {
2048 FindInstallsClosure* closure = static_cast<FindInstallsClosure*>(aClosure);
2050 // Check if the section starts with "Install"
2051 if (strncmp(aSection, INSTALL_PREFIX, INSTALL_PREFIX_LENGTH) != 0) {
2052 return true;
2055 nsCString install(aSection);
2056 closure->installs->AppendElement(install);
2058 return true;
2061 nsTArray<nsCString> nsToolkitProfileService::GetKnownInstalls() {
2062 nsTArray<nsCString> result;
2063 FindInstallsClosure closure = {&mProfileDB, &result};
2065 mProfileDB.GetSections(&FindInstalls, &closure);
2067 return result;
2070 nsresult nsToolkitProfileService::CreateTimesInternal(nsIFile* aProfileDir) {
2071 nsresult rv = NS_ERROR_FAILURE;
2072 nsCOMPtr<nsIFile> creationLog;
2073 rv = aProfileDir->Clone(getter_AddRefs(creationLog));
2074 NS_ENSURE_SUCCESS(rv, rv);
2076 rv = creationLog->AppendNative("times.json"_ns);
2077 NS_ENSURE_SUCCESS(rv, rv);
2079 bool exists = false;
2080 creationLog->Exists(&exists);
2081 if (exists) {
2082 return NS_OK;
2085 rv = creationLog->Create(nsIFile::NORMAL_FILE_TYPE, 0700);
2086 NS_ENSURE_SUCCESS(rv, rv);
2088 // We don't care about microsecond resolution.
2089 int64_t msec = PR_Now() / PR_USEC_PER_MSEC;
2091 // Write it out.
2092 PRFileDesc* writeFile;
2093 rv = creationLog->OpenNSPRFileDesc(PR_WRONLY, 0700, &writeFile);
2094 NS_ENSURE_SUCCESS(rv, rv);
2096 PR_fprintf(writeFile, "{\n\"created\": %lld,\n\"firstUse\": null\n}\n", msec);
2097 PR_Close(writeFile);
2098 return NS_OK;
2101 NS_IMETHODIMP
2102 nsToolkitProfileService::GetProfileCount(uint32_t* aResult) {
2103 *aResult = 0;
2104 for (nsToolkitProfile* profile : mProfiles) {
2105 Unused << profile;
2106 (*aResult)++;
2109 return NS_OK;
2112 NS_IMETHODIMP
2113 nsToolkitProfileService::Flush() {
2114 if (GetIsListOutdated()) {
2115 return NS_ERROR_DATABASE_CHANGED;
2118 nsresult rv;
2120 // If we aren't using dedicated profiles then nothing about the list of
2121 // installs can have changed, so no need to update the backup.
2122 if (mUseDedicatedProfile) {
2123 // Export the installs to the backup.
2124 nsTArray<nsCString> installs = GetKnownInstalls();
2126 if (!installs.IsEmpty()) {
2127 nsCString data;
2128 nsCString buffer;
2130 for (uint32_t i = 0; i < installs.Length(); i++) {
2131 nsTArray<UniquePtr<KeyValue>> strings =
2132 GetSectionStrings(&mProfileDB, installs[i].get());
2133 if (strings.IsEmpty()) {
2134 continue;
2137 // Strip "Install" from the start.
2138 const nsDependentCSubstring& install =
2139 Substring(installs[i], INSTALL_PREFIX_LENGTH);
2140 data.AppendPrintf("[%s]\n", PromiseFlatCString(install).get());
2142 for (uint32_t j = 0; j < strings.Length(); j++) {
2143 data.AppendPrintf("%s=%s\n", strings[j]->key.get(),
2144 strings[j]->value.get());
2147 data.Append("\n");
2150 FILE* writeFile;
2151 rv = mInstallDBFile->OpenANSIFileDesc("w", &writeFile);
2152 NS_ENSURE_SUCCESS(rv, rv);
2154 uint32_t length = data.Length();
2155 if (fwrite(data.get(), sizeof(char), length, writeFile) != length) {
2156 fclose(writeFile);
2157 return NS_ERROR_UNEXPECTED;
2160 fclose(writeFile);
2161 } else {
2162 rv = mInstallDBFile->Remove(false);
2163 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
2164 return rv;
2169 rv = mProfileDB.WriteToFile(mProfileDBFile);
2170 NS_ENSURE_SUCCESS(rv, rv);
2172 rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
2173 &mProfileDBModifiedTime, &mProfileDBFileSize);
2174 NS_ENSURE_SUCCESS(rv, rv);
2176 return NS_OK;
2179 already_AddRefed<nsToolkitProfileService> NS_GetToolkitProfileService() {
2180 if (!nsToolkitProfileService::gService) {
2181 nsToolkitProfileService::gService = new nsToolkitProfileService();
2182 nsresult rv = nsToolkitProfileService::gService->Init();
2183 if (NS_FAILED(rv)) {
2184 NS_ERROR("nsToolkitProfileService::Init failed!");
2185 delete nsToolkitProfileService::gService;
2186 return nullptr;
2190 return do_AddRef(nsToolkitProfileService::gService);
2193 nsresult XRE_GetFileFromPath(const char* aPath, nsIFile** aResult) {
2194 #if defined(XP_MACOSX)
2195 int32_t pathLen = strlen(aPath);
2196 if (pathLen > MAXPATHLEN) return NS_ERROR_INVALID_ARG;
2198 CFURLRef fullPath = CFURLCreateFromFileSystemRepresentation(
2199 nullptr, (const UInt8*)aPath, pathLen, true);
2200 if (!fullPath) return NS_ERROR_FAILURE;
2202 nsCOMPtr<nsIFile> lf;
2203 nsresult rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(lf));
2204 if (NS_SUCCEEDED(rv)) {
2205 nsCOMPtr<nsILocalFileMac> lfMac = do_QueryInterface(lf, &rv);
2206 if (NS_SUCCEEDED(rv)) {
2207 rv = lfMac->InitWithCFURL(fullPath);
2208 if (NS_SUCCEEDED(rv)) {
2209 lf.forget(aResult);
2213 CFRelease(fullPath);
2214 return rv;
2216 #elif defined(XP_UNIX)
2217 char fullPath[MAXPATHLEN];
2219 if (!realpath(aPath, fullPath)) return NS_ERROR_FAILURE;
2221 return NS_NewNativeLocalFile(nsDependentCString(fullPath), true, aResult);
2222 #elif defined(XP_WIN)
2223 WCHAR fullPath[MAXPATHLEN];
2225 if (!_wfullpath(fullPath, NS_ConvertUTF8toUTF16(aPath).get(), MAXPATHLEN))
2226 return NS_ERROR_FAILURE;
2228 return NS_NewLocalFile(nsDependentString(fullPath), true, aResult);
2230 #else
2231 # error Platform-specific logic needed here.
2232 #endif