summaryrefslogtreecommitdiffstats
path: root/toolkit/profile/nsToolkitProfileService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/profile/nsToolkitProfileService.cpp')
-rw-r--r--toolkit/profile/nsToolkitProfileService.cpp2240
1 files changed, 2240 insertions, 0 deletions
diff --git a/toolkit/profile/nsToolkitProfileService.cpp b/toolkit/profile/nsToolkitProfileService.cpp
new file mode 100644
index 0000000000..aeab25c61f
--- /dev/null
+++ b/toolkit/profile/nsToolkitProfileService.cpp
@@ -0,0 +1,2240 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/WidgetUtils.h"
+#include "nsProfileLock.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <prprf.h>
+#include <prtime.h>
+
+#ifdef XP_WIN
+# include <windows.h>
+# include <shlobj.h>
+# include "mozilla/PolicyChecks.h"
+#endif
+#ifdef XP_UNIX
+# include <unistd.h>
+#endif
+
+#include "nsToolkitProfileService.h"
+#include "CmdLineAndEnvUtils.h"
+#include "nsIFile.h"
+
+#ifdef XP_MACOSX
+# include <CoreFoundation/CoreFoundation.h>
+# include "nsILocalFileMac.h"
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+# include "mozilla/WidgetUtilsGtk.h"
+#endif
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsNetCID.h"
+#include "nsXULAppAPI.h"
+#include "nsThreadUtils.h"
+
+#include "nsIRunnable.h"
+#include "nsXREDirProvider.h"
+#include "nsAppRunner.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsNativeCharsetUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Sprintf.h"
+#include "nsPrintfCString.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIToolkitShellService.h"
+#include "mozilla/Telemetry.h"
+#include "nsProxyRelease.h"
+#include "prinrval.h"
+#include "prthread.h"
+#ifdef MOZ_BACKGROUNDTASKS
+# include "mozilla/BackgroundTasks.h"
+# include "SpecialSystemDirectory.h"
+#endif
+
+using namespace mozilla;
+
+#define DEV_EDITION_NAME "dev-edition-default"
+#define DEFAULT_NAME "default"
+#define COMPAT_FILE u"compatibility.ini"_ns
+#define PROFILE_DB_VERSION "2"
+#define INSTALL_PREFIX "Install"
+#define INSTALL_PREFIX_LENGTH 7
+
+struct KeyValue {
+ KeyValue(const char* aKey, const char* aValue) : key(aKey), value(aValue) {}
+
+ nsCString key;
+ nsCString value;
+};
+
+static bool GetStrings(const char* aString, const char* aValue,
+ void* aClosure) {
+ nsTArray<UniquePtr<KeyValue>>* array =
+ static_cast<nsTArray<UniquePtr<KeyValue>>*>(aClosure);
+ array->AppendElement(MakeUnique<KeyValue>(aString, aValue));
+
+ return true;
+}
+
+/**
+ * Returns an array of the strings inside a section of an ini file.
+ */
+nsTArray<UniquePtr<KeyValue>> GetSectionStrings(nsINIParser* aParser,
+ const char* aSection) {
+ nsTArray<UniquePtr<KeyValue>> result;
+ aParser->GetStrings(aSection, &GetStrings, &result);
+ return result;
+}
+
+void RemoveProfileRecursion(const nsCOMPtr<nsIFile>& aDirectoryOrFile,
+ bool aIsIgnoreRoot, bool aIsIgnoreLockfile,
+ nsTArray<nsCOMPtr<nsIFile>>& aOutUndeletedFiles) {
+ auto guardDeletion = MakeScopeExit(
+ [&] { aOutUndeletedFiles.AppendElement(aDirectoryOrFile); });
+
+ // We actually would not expect to see links in our profiles, but still.
+ bool isLink = false;
+ NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->IsSymlink(&isLink));
+
+ // Only check to see if we have a directory if it isn't a link.
+ bool isDir = false;
+ if (!isLink) {
+ NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->IsDirectory(&isDir));
+ }
+
+ if (isDir) {
+ nsCOMPtr<nsIDirectoryEnumerator> dirEnum;
+ NS_ENSURE_SUCCESS_VOID(
+ aDirectoryOrFile->GetDirectoryEntries(getter_AddRefs(dirEnum)));
+
+ bool more = false;
+ while (NS_SUCCEEDED(dirEnum->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsISupports> item;
+ dirEnum->GetNext(getter_AddRefs(item));
+ nsCOMPtr<nsIFile> file = do_QueryInterface(item);
+ if (file) {
+ // Do not delete the profile lock.
+ if (aIsIgnoreLockfile && nsProfileLock::IsMaybeLockFile(file)) continue;
+ // If some children's remove fails, we still continue the loop.
+ RemoveProfileRecursion(file, false, false, aOutUndeletedFiles);
+ }
+ }
+ }
+ // Do not delete the root directory (yet).
+ if (!aIsIgnoreRoot) {
+ NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->Remove(false));
+ }
+ guardDeletion.release();
+}
+
+void RemoveProfileFiles(nsIToolkitProfile* aProfile, bool aInBackground) {
+ nsCOMPtr<nsIFile> rootDir;
+ aProfile->GetRootDir(getter_AddRefs(rootDir));
+ nsCOMPtr<nsIFile> localDir;
+ aProfile->GetLocalDir(getter_AddRefs(localDir));
+
+ // XXX If we get here with an active quota manager,
+ // something went very wrong. We want to assert this.
+
+ // Just lock the directories, don't mark the profile as locked or the lock
+ // will attempt to release its reference to the profile on the background
+ // thread which will assert.
+ nsCOMPtr<nsIProfileLock> lock;
+ NS_ENSURE_SUCCESS_VOID(
+ NS_LockProfilePath(rootDir, localDir, nullptr, getter_AddRefs(lock)));
+
+ nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
+ "nsToolkitProfile::RemoveProfileFiles",
+ [rootDir, localDir, lock]() mutable {
+ // We try to remove every single file and directory and collect
+ // those whose removal failed.
+ nsTArray<nsCOMPtr<nsIFile>> undeletedFiles;
+ // The root dir might contain the temp dir, so remove the temp dir
+ // first.
+ bool equals;
+ nsresult rv = rootDir->Equals(localDir, &equals);
+ if (NS_SUCCEEDED(rv) && !equals) {
+ RemoveProfileRecursion(localDir,
+ /* aIsIgnoreRoot */ false,
+ /* aIsIgnoreLockfile */ false, undeletedFiles);
+ }
+ // Now remove the content of the profile dir (except lockfile)
+ RemoveProfileRecursion(rootDir,
+ /* aIsIgnoreRoot */ true,
+ /* aIsIgnoreLockfile */ true, undeletedFiles);
+
+ // Retry loop if something was not deleted
+ if (undeletedFiles.Length() > 0) {
+ uint32_t retries = 1;
+ // XXX: Until bug 1716291 is fixed we just make one retry
+ while (undeletedFiles.Length() > 0 && retries <= 1) {
+ Unused << PR_Sleep(PR_MillisecondsToInterval(10 * retries));
+ for (auto&& file :
+ std::exchange(undeletedFiles, nsTArray<nsCOMPtr<nsIFile>>{})) {
+ RemoveProfileRecursion(file,
+ /* aIsIgnoreRoot */ false,
+ /* aIsIgnoreLockfile */ true,
+ undeletedFiles);
+ }
+ retries++;
+ }
+ }
+
+#ifdef DEBUG
+ // XXX: Until bug 1716291 is fixed, we do not want to spam release
+ if (undeletedFiles.Length() > 0) {
+ NS_WARNING("Unable to remove all files from the profile directory:");
+ // Log the file names of those we could not remove
+ for (auto&& file : undeletedFiles) {
+ nsAutoString leafName;
+ if (NS_SUCCEEDED(file->GetLeafName(leafName))) {
+ NS_WARNING(NS_LossyConvertUTF16toASCII(leafName).get());
+ }
+ }
+ }
+#endif
+ // XXX: Activate this assert once bug 1716291 is fixed
+ // MOZ_ASSERT(undeletedFiles.Length() == 0);
+
+ // Now we can unlock the profile safely.
+ lock->Unlock();
+ // nsIProfileLock is not threadsafe so release our reference to it on
+ // the main thread.
+ NS_ReleaseOnMainThread("nsToolkitProfile::RemoveProfileFiles::Unlock",
+ lock.forget());
+
+ if (undeletedFiles.Length() == 0) {
+ // We can safely remove the (empty) remaining profile directory
+ // and lockfile, no other files are here.
+ // As we do this only if we had no other blockers, this is as safe
+ // as deleting the lockfile explicitely after unlocking.
+ Unused << rootDir->Remove(true);
+ }
+ });
+
+ if (aInBackground) {
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ target->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ } else {
+ runnable->Run();
+ }
+}
+
+nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
+ nsIFile* aLocalDir, bool aFromDB)
+ : mName(aName),
+ mRootDir(aRootDir),
+ mLocalDir(aLocalDir),
+ mLock(nullptr),
+ mIndex(0),
+ mSection("Profile") {
+ NS_ASSERTION(aRootDir, "No file!");
+
+ RefPtr<nsToolkitProfile> prev =
+ nsToolkitProfileService::gService->mProfiles.getLast();
+ if (prev) {
+ mIndex = prev->mIndex + 1;
+ }
+ mSection.AppendInt(mIndex);
+
+ nsToolkitProfileService::gService->mProfiles.insertBack(this);
+
+ // If this profile isn't in the database already add it.
+ if (!aFromDB) {
+ nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
+ db->SetString(mSection.get(), "Name", mName.get());
+
+ bool isRelative = false;
+ nsCString descriptor;
+ nsToolkitProfileService::gService->GetProfileDescriptor(this, descriptor,
+ &isRelative);
+
+ db->SetString(mSection.get(), "IsRelative", isRelative ? "1" : "0");
+ db->SetString(mSection.get(), "Path", descriptor.get());
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsToolkitProfile, nsIToolkitProfile)
+
+NS_IMETHODIMP
+nsToolkitProfile::GetRootDir(nsIFile** aResult) {
+ NS_ADDREF(*aResult = mRootDir);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::GetLocalDir(nsIFile** aResult) {
+ NS_ADDREF(*aResult = mLocalDir);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::GetName(nsACString& aResult) {
+ aResult = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::SetName(const nsACString& aName) {
+ NS_ASSERTION(nsToolkitProfileService::gService, "Where did my service go?");
+
+ if (mName.Equals(aName)) {
+ return NS_OK;
+ }
+
+ // Changing the name from the dev-edition default profile name makes this
+ // profile no longer the dev-edition default.
+ if (mName.EqualsLiteral(DEV_EDITION_NAME) &&
+ nsToolkitProfileService::gService->mDevEditionDefault == this) {
+ nsToolkitProfileService::gService->mDevEditionDefault = nullptr;
+ }
+
+ mName = aName;
+
+ nsresult rv = nsToolkitProfileService::gService->mProfileDB.SetString(
+ mSection.get(), "Name", mName.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Setting the name to the dev-edition default profile name will cause this
+ // profile to become the dev-edition default.
+ if (aName.EqualsLiteral(DEV_EDITION_NAME) &&
+ !nsToolkitProfileService::gService->mDevEditionDefault) {
+ nsToolkitProfileService::gService->mDevEditionDefault = this;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsToolkitProfile::RemoveInternal(bool aRemoveFiles,
+ bool aInBackground) {
+ NS_ASSERTION(nsToolkitProfileService::gService, "Whoa, my service is gone.");
+
+ if (mLock) return NS_ERROR_FILE_IS_LOCKED;
+
+ if (!isInList()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (aRemoveFiles) {
+ RemoveProfileFiles(this, aInBackground);
+ }
+
+ nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
+ db->DeleteSection(mSection.get());
+
+ // We make some assumptions that the profile's index in the database is based
+ // on its position in the linked list. Removing a profile means we have to fix
+ // the index of later profiles in the list. The easiest way to do that is just
+ // to move the last profile into the profile's position and just update its
+ // index.
+ RefPtr<nsToolkitProfile> last =
+ nsToolkitProfileService::gService->mProfiles.getLast();
+ if (last != this) {
+ // Update the section in the db.
+ last->mIndex = mIndex;
+ db->RenameSection(last->mSection.get(), mSection.get());
+ last->mSection = mSection;
+
+ if (last != getNext()) {
+ last->remove();
+ setNext(last);
+ }
+ }
+
+ remove();
+
+ if (nsToolkitProfileService::gService->mNormalDefault == this) {
+ nsToolkitProfileService::gService->mNormalDefault = nullptr;
+ }
+ if (nsToolkitProfileService::gService->mDevEditionDefault == this) {
+ nsToolkitProfileService::gService->mDevEditionDefault = nullptr;
+ }
+ if (nsToolkitProfileService::gService->mDedicatedProfile == this) {
+ nsToolkitProfileService::gService->SetDefaultProfile(nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::Remove(bool removeFiles) {
+ return RemoveInternal(removeFiles, false /* in background */);
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::RemoveInBackground(bool removeFiles) {
+ return RemoveInternal(removeFiles, true /* in background */);
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::Lock(nsIProfileUnlocker** aUnlocker,
+ nsIProfileLock** aResult) {
+ if (mLock) {
+ NS_ADDREF(*aResult = mLock);
+ return NS_OK;
+ }
+
+ RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
+
+ nsresult rv = lock->Init(this, aUnlocker);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ADDREF(*aResult = lock);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsToolkitProfileLock, nsIProfileLock)
+
+nsresult nsToolkitProfileLock::Init(nsToolkitProfile* aProfile,
+ nsIProfileUnlocker** aUnlocker) {
+ nsresult rv;
+ rv = Init(aProfile->mRootDir, aProfile->mLocalDir, aUnlocker);
+ if (NS_SUCCEEDED(rv)) mProfile = aProfile;
+
+ return rv;
+}
+
+nsresult nsToolkitProfileLock::Init(nsIFile* aDirectory,
+ nsIFile* aLocalDirectory,
+ nsIProfileUnlocker** aUnlocker) {
+ nsresult rv;
+
+ rv = mLock.Lock(aDirectory, aUnlocker);
+
+ if (NS_SUCCEEDED(rv)) {
+ mDirectory = aDirectory;
+ mLocalDirectory = aLocalDirectory;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileLock::GetDirectory(nsIFile** aResult) {
+ if (!mDirectory) {
+ NS_ERROR("Not initialized, or unlocked!");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ NS_ADDREF(*aResult = mDirectory);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileLock::GetLocalDirectory(nsIFile** aResult) {
+ if (!mLocalDirectory) {
+ NS_ERROR("Not initialized, or unlocked!");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ NS_ADDREF(*aResult = mLocalDirectory);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileLock::Unlock() {
+ if (!mDirectory) {
+ NS_ERROR("Unlocking a never-locked nsToolkitProfileLock!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // XXX If we get here with an active quota manager,
+ // something went very wrong. We want to assert this.
+
+ mLock.Unlock();
+
+ if (mProfile) {
+ mProfile->mLock = nullptr;
+ mProfile = nullptr;
+ }
+ mDirectory = nullptr;
+ mLocalDirectory = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileLock::GetReplacedLockTime(PRTime* aResult) {
+ mLock.GetReplacedLockTime(aResult);
+ return NS_OK;
+}
+
+nsToolkitProfileLock::~nsToolkitProfileLock() {
+ if (mDirectory) {
+ Unlock();
+ }
+}
+
+nsToolkitProfileService* nsToolkitProfileService::gService = nullptr;
+
+NS_IMPL_ISUPPORTS(nsToolkitProfileService, nsIToolkitProfileService)
+
+nsToolkitProfileService::nsToolkitProfileService()
+ : mStartupProfileSelected(false),
+ mStartWithLast(true),
+ mIsFirstRun(true),
+ mUseDevEditionProfile(false),
+#ifdef MOZ_DEDICATED_PROFILES
+ mUseDedicatedProfile(!IsSnapEnvironment() && !UseLegacyProfiles()),
+#else
+ mUseDedicatedProfile(false),
+#endif
+ mStartupReason(u"unknown"_ns),
+ mStartupFileVersion("0"_ns),
+ mMaybeLockProfile(false),
+ mUpdateChannel(MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL)),
+ mProfileDBExists(false),
+ mProfileDBFileSize(0),
+ mProfileDBModifiedTime(0) {
+#ifdef MOZ_DEV_EDITION
+ mUseDevEditionProfile = true;
+#endif
+}
+
+nsToolkitProfileService::~nsToolkitProfileService() {
+ gService = nullptr;
+ mProfiles.clear();
+}
+
+void nsToolkitProfileService::CompleteStartup() {
+ if (!mStartupProfileSelected) {
+ return;
+ }
+
+ ScalarSet(mozilla::Telemetry::ScalarID::STARTUP_PROFILE_SELECTION_REASON,
+ mStartupReason);
+ ScalarSet(mozilla::Telemetry::ScalarID::STARTUP_PROFILE_DATABASE_VERSION,
+ NS_ConvertUTF8toUTF16(mStartupFileVersion));
+ ScalarSet(mozilla::Telemetry::ScalarID::STARTUP_PROFILE_COUNT,
+ static_cast<uint32_t>(mProfiles.length()));
+
+ if (mMaybeLockProfile) {
+ nsCOMPtr<nsIToolkitShellService> shell =
+ do_GetService(NS_TOOLKITSHELLSERVICE_CONTRACTID);
+ if (!shell) {
+ return;
+ }
+
+ bool isDefaultApp;
+ nsresult rv = shell->IsDefaultApplication(&isDefaultApp);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (isDefaultApp) {
+ mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
+
+ // There is a very small chance that this could fail if something else
+ // overwrote the profiles database since we started up, probably less than
+ // a second ago. There isn't really a sane response here, all the other
+ // profile changes are already flushed so whether we fail to flush here or
+ // force quit the app makes no difference.
+ NS_ENSURE_SUCCESS_VOID(Flush());
+ }
+ }
+}
+
+// Tests whether the passed profile was last used by this install.
+bool nsToolkitProfileService::IsProfileForCurrentInstall(
+ nsIToolkitProfile* aProfile) {
+ nsCOMPtr<nsIFile> profileDir;
+ nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIFile> compatFile;
+ rv = profileDir->Clone(getter_AddRefs(compatFile));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = compatFile->Append(COMPAT_FILE);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsINIParser compatData;
+ rv = compatData.Init(compatFile);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ /**
+ * In xpcshell gDirServiceProvider doesn't have all the correct directories
+ * set so using NS_GetSpecialDirectory works better there. But in a normal
+ * app launch the component registry isn't initialized so
+ * NS_GetSpecialDirectory doesn't work. So we have to use two different
+ * paths to support testing.
+ */
+ nsCOMPtr<nsIFile> currentGreDir;
+ rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(currentGreDir));
+ if (rv == NS_ERROR_NOT_INITIALIZED) {
+ currentGreDir = gDirServiceProvider->GetGREDir();
+ MOZ_ASSERT(currentGreDir, "No GRE dir found.");
+ } else if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsCString lastGreDirStr;
+ rv = compatData.GetString("Compatibility", "LastPlatformDir", lastGreDirStr);
+ // If this string is missing then this profile is from an ancient version.
+ // We'll opt to use it in this case.
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ nsCOMPtr<nsIFile> lastGreDir;
+ rv = NS_NewNativeLocalFile(""_ns, false, getter_AddRefs(lastGreDir));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = lastGreDir->SetPersistentDescriptor(lastGreDirStr);
+ NS_ENSURE_SUCCESS(rv, false);
+
+#ifdef XP_WIN
+# if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+ mozilla::PathString lastGreDirPath, currentGreDirPath;
+ lastGreDirPath = lastGreDir->NativePath();
+ currentGreDirPath = currentGreDir->NativePath();
+ if (lastGreDirPath.Equals(currentGreDirPath,
+ nsCaseInsensitiveStringComparator)) {
+ return true;
+ }
+
+ // Convert a 64-bit install path to what would have been the 32-bit install
+ // path to allow users to migrate their profiles from one to the other.
+ PWSTR pathX86 = nullptr;
+ HRESULT hres =
+ SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, 0, nullptr, &pathX86);
+ if (SUCCEEDED(hres)) {
+ nsDependentString strPathX86(pathX86);
+ if (!StringBeginsWith(currentGreDirPath, strPathX86,
+ nsCaseInsensitiveStringComparator)) {
+ PWSTR path = nullptr;
+ hres = SHGetKnownFolderPath(FOLDERID_ProgramFiles, 0, nullptr, &path);
+ if (SUCCEEDED(hres)) {
+ if (StringBeginsWith(currentGreDirPath, nsDependentString(path),
+ nsCaseInsensitiveStringComparator)) {
+ currentGreDirPath.Replace(0, wcslen(path), strPathX86);
+ }
+ }
+ CoTaskMemFree(path);
+ }
+ }
+ CoTaskMemFree(pathX86);
+
+ return lastGreDirPath.Equals(currentGreDirPath,
+ nsCaseInsensitiveStringComparator);
+# endif
+#endif
+
+ bool equal;
+ rv = lastGreDir->Equals(currentGreDir, &equal);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return equal;
+}
+
+/**
+ * Used the first time an install with dedicated profile support runs. Decides
+ * whether to mark the passed profile as the default for this install.
+ *
+ * The goal is to reduce disruption but ideally end up with the OS default
+ * install using the old default profile.
+ *
+ * If the decision is to use the profile then it will be unassigned as the
+ * dedicated default for other installs.
+ *
+ * We won't attempt to use the profile if it was last used by a different
+ * install.
+ *
+ * If the profile is currently in use by an install that was either the OS
+ * default install or the profile has been explicitely chosen by some other
+ * means then we won't use it.
+ *
+ * aResult will be set to true if we chose to make the profile the new dedicated
+ * default.
+ */
+nsresult nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
+ nsIToolkitProfile* aProfile, bool* aResult) {
+ nsresult rv;
+ *aResult = false;
+
+ // If the profile was last used by a different install then we won't use it.
+ if (!IsProfileForCurrentInstall(aProfile)) {
+ return NS_OK;
+ }
+
+ nsCString descriptor;
+ rv = GetProfileDescriptor(aProfile, descriptor, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get a list of all the installs.
+ nsTArray<nsCString> installs = GetKnownInstalls();
+
+ // Cache the installs that use the profile.
+ nsTArray<nsCString> inUseInstalls;
+
+ // See if the profile is already in use by an install that hasn't locked it.
+ for (uint32_t i = 0; i < installs.Length(); i++) {
+ const nsCString& install = installs[i];
+
+ nsCString path;
+ rv = mProfileDB.GetString(install.get(), "Default", path);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ // Is this install using the profile we care about?
+ if (!descriptor.Equals(path)) {
+ continue;
+ }
+
+ // Is this profile locked to this other install?
+ nsCString isLocked;
+ rv = mProfileDB.GetString(install.get(), "Locked", isLocked);
+ if (NS_SUCCEEDED(rv) && isLocked.Equals("1")) {
+ return NS_OK;
+ }
+
+ inUseInstalls.AppendElement(install);
+ }
+
+ // At this point we've decided to take the profile. Strip it from other
+ // installs.
+ for (uint32_t i = 0; i < inUseInstalls.Length(); i++) {
+ // Removing the default setting entirely will make the install go through
+ // the first run process again at startup and create itself a new profile.
+ mProfileDB.DeleteString(inUseInstalls[i].get(), "Default");
+ }
+
+ // Set this as the default profile for this install.
+ SetDefaultProfile(aProfile);
+
+ // SetDefaultProfile will have locked this profile to this install so no
+ // other installs will steal it, but this was auto-selected so we want to
+ // unlock it so that other installs can potentially take it.
+ mProfileDB.DeleteString(mInstallSection.get(), "Locked");
+
+ // Persist the changes.
+ rv = Flush();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Once XPCOM is available check if this is the default application and if so
+ // lock the profile again.
+ mMaybeLockProfile = true;
+ *aResult = true;
+
+ return NS_OK;
+}
+
+bool IsFileOutdated(nsIFile* aFile, bool aExists, PRTime aLastModified,
+ int64_t aLastSize) {
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aFile->Clone(getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ bool exists;
+ rv = aFile->Exists(&exists);
+ if (NS_FAILED(rv) || exists != aExists) {
+ return true;
+ }
+
+ if (!exists) {
+ return false;
+ }
+
+ int64_t size;
+ rv = aFile->GetFileSize(&size);
+ if (NS_FAILED(rv) || size != aLastSize) {
+ return true;
+ }
+
+ PRTime time;
+ rv = aFile->GetLastModifiedTime(&time);
+ if (NS_FAILED(rv) || time != aLastModified) {
+ return true;
+ }
+
+ return false;
+}
+
+nsresult UpdateFileStats(nsIFile* aFile, bool* aExists, PRTime* aLastModified,
+ int64_t* aLastSize) {
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aFile->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->Exists(aExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!(*aExists)) {
+ *aLastModified = 0;
+ *aLastSize = 0;
+ return NS_OK;
+ }
+
+ rv = file->GetFileSize(aLastSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->GetLastModifiedTime(aLastModified);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetIsListOutdated(bool* aResult) {
+ if (IsFileOutdated(mProfileDBFile, mProfileDBExists, mProfileDBModifiedTime,
+ mProfileDBFileSize)) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ *aResult = false;
+ return NS_OK;
+}
+
+struct ImportInstallsClosure {
+ nsINIParser* backupData;
+ nsINIParser* profileDB;
+};
+
+static bool ImportInstalls(const char* aSection, void* aClosure) {
+ ImportInstallsClosure* closure =
+ static_cast<ImportInstallsClosure*>(aClosure);
+
+ nsTArray<UniquePtr<KeyValue>> strings =
+ GetSectionStrings(closure->backupData, aSection);
+ if (strings.IsEmpty()) {
+ return true;
+ }
+
+ nsCString newSection(INSTALL_PREFIX);
+ newSection.Append(aSection);
+ nsCString buffer;
+
+ for (uint32_t i = 0; i < strings.Length(); i++) {
+ closure->profileDB->SetString(newSection.get(), strings[i]->key.get(),
+ strings[i]->value.get());
+ }
+
+ return true;
+}
+
+nsresult nsToolkitProfileService::Init() {
+ NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!");
+ nsresult rv;
+
+ rv = nsXREDirProvider::GetUserAppDataDirectory(getter_AddRefs(mAppData));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsXREDirProvider::GetUserLocalDataDirectory(getter_AddRefs(mTempData));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mAppData->Clone(getter_AddRefs(mProfileDBFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mProfileDBFile->AppendNative("profiles.ini"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mAppData->Clone(getter_AddRefs(mInstallDBFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mInstallDBFile->AppendNative("installs.ini"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString buffer;
+
+ rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
+ &mProfileDBModifiedTime, &mProfileDBFileSize);
+ if (NS_SUCCEEDED(rv) && mProfileDBExists) {
+ rv = mProfileDB.Init(mProfileDBFile);
+ // Init does not fail on parsing errors, only on OOM/really unexpected
+ // conditions.
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = mProfileDB.GetString("General", "StartWithLastProfile", buffer);
+ if (NS_SUCCEEDED(rv)) {
+ mStartWithLast = !buffer.EqualsLiteral("0");
+ }
+
+ rv = mProfileDB.GetString("General", "Version", mStartupFileVersion);
+ if (NS_FAILED(rv)) {
+ // This is a profiles.ini written by an older version. We must restore
+ // any install data from the backup. We consider this old format to be
+ // a version 1 file.
+ mStartupFileVersion.AssignLiteral("1");
+ nsINIParser installDB;
+
+ if (NS_SUCCEEDED(installDB.Init(mInstallDBFile))) {
+ // There is install data to import.
+ ImportInstallsClosure closure = {&installDB, &mProfileDB};
+ installDB.GetSections(&ImportInstalls, &closure);
+ }
+
+ rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ rv = mProfileDB.SetString("General", "StartWithLastProfile",
+ mStartWithLast ? "1" : "0");
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCString installProfilePath;
+
+ if (mUseDedicatedProfile) {
+ nsString installHash;
+ rv = gDirServiceProvider->GetInstallHash(installHash);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF16toUTF8(installHash, mInstallSection);
+ mInstallSection.Insert(INSTALL_PREFIX, 0);
+
+ // Try to find the descriptor for the default profile for this install.
+ rv = mProfileDB.GetString(mInstallSection.get(), "Default",
+ installProfilePath);
+
+ // Not having a value means this install doesn't appear in installs.ini so
+ // this is the first run for this install.
+ if (NS_FAILED(rv)) {
+ mIsFirstRun = true;
+
+ // Gets the install section that would have been created if the install
+ // path has incorrect casing (see bug 1555319). We use this later during
+ // profile selection.
+ rv = gDirServiceProvider->GetLegacyInstallHash(installHash);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF16toUTF8(installHash, mLegacyInstallSection);
+ mLegacyInstallSection.Insert(INSTALL_PREFIX, 0);
+ } else {
+ mIsFirstRun = false;
+ }
+ }
+
+ nsToolkitProfile* currentProfile = nullptr;
+
+#ifdef MOZ_DEV_EDITION
+ nsCOMPtr<nsIFile> ignoreDevEditionProfile;
+ rv = mAppData->Clone(getter_AddRefs(ignoreDevEditionProfile));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ignoreDevEditionProfile->AppendNative("ignore-dev-edition-profile"_ns);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool shouldIgnoreSeparateProfile;
+ rv = ignoreDevEditionProfile->Exists(&shouldIgnoreSeparateProfile);
+ if (NS_FAILED(rv)) return rv;
+
+ mUseDevEditionProfile = !shouldIgnoreSeparateProfile;
+#endif
+
+ nsCOMPtr<nsIToolkitProfile> autoSelectProfile;
+
+ unsigned int nonDevEditionProfiles = 0;
+ unsigned int c = 0;
+ for (c = 0; true; ++c) {
+ nsAutoCString profileID("Profile");
+ profileID.AppendInt(c);
+
+ rv = mProfileDB.GetString(profileID.get(), "IsRelative", buffer);
+ if (NS_FAILED(rv)) break;
+
+ bool isRelative = buffer.EqualsLiteral("1");
+
+ nsAutoCString filePath;
+
+ rv = mProfileDB.GetString(profileID.get(), "Path", filePath);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Malformed profiles.ini: Path= not found");
+ continue;
+ }
+
+ nsAutoCString name;
+
+ rv = mProfileDB.GetString(profileID.get(), "Name", name);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Malformed profiles.ini: Name= not found");
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> rootDir;
+ rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(rootDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isRelative) {
+ rv = rootDir->SetRelativeDescriptor(mAppData, filePath);
+ } else {
+ rv = rootDir->SetPersistentDescriptor(filePath);
+ }
+ if (NS_FAILED(rv)) continue;
+
+ nsCOMPtr<nsIFile> localDir;
+ if (isRelative) {
+ rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = localDir->SetRelativeDescriptor(mTempData, filePath);
+ } else {
+ localDir = rootDir;
+ }
+
+ currentProfile = new nsToolkitProfile(name, rootDir, localDir, true);
+
+ // If a user has modified the ini file path it may make for a valid profile
+ // path but not match what we would have serialised and so may not match
+ // the path in the install section. Re-serialise it to get it in the
+ // expected form again.
+ bool nowRelative;
+ nsCString descriptor;
+ GetProfileDescriptor(currentProfile, descriptor, &nowRelative);
+
+ if (isRelative != nowRelative || !descriptor.Equals(filePath)) {
+ mProfileDB.SetString(profileID.get(), "IsRelative",
+ nowRelative ? "1" : "0");
+ mProfileDB.SetString(profileID.get(), "Path", descriptor.get());
+
+ // Should we flush now? It costs some startup time and we will fix it on
+ // the next startup anyway. If something else causes a flush then it will
+ // be fixed in the ini file then.
+ }
+
+ rv = mProfileDB.GetString(profileID.get(), "Default", buffer);
+ if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1")) {
+ mNormalDefault = currentProfile;
+ }
+
+ // Is this the default profile for this install?
+ if (mUseDedicatedProfile && !mDedicatedProfile &&
+ installProfilePath.Equals(descriptor)) {
+ // Found a profile for this install.
+ mDedicatedProfile = currentProfile;
+ }
+
+ if (name.EqualsLiteral(DEV_EDITION_NAME)) {
+ mDevEditionDefault = currentProfile;
+ } else {
+ nonDevEditionProfiles++;
+ autoSelectProfile = currentProfile;
+ }
+ }
+
+ // If there is only one non-dev-edition profile then mark it as the default.
+ if (!mNormalDefault && nonDevEditionProfiles == 1) {
+ SetNormalDefault(autoSelectProfile);
+ }
+
+ if (!mUseDedicatedProfile) {
+ if (mUseDevEditionProfile) {
+ // When using the separate dev-edition profile not finding it means this
+ // is a first run.
+ mIsFirstRun = !mDevEditionDefault;
+ } else {
+ // If there are no normal profiles then this is a first run.
+ mIsFirstRun = nonDevEditionProfiles == 0;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::SetStartWithLastProfile(bool aValue) {
+ if (mStartWithLast != aValue) {
+ // Note: the skeleton ui (see PreXULSkeletonUI.cpp) depends on this
+ // having this name and being under General. If that ever changes,
+ // the skeleton UI will just need to be updated. If it changes frequently,
+ // it's probably best we just mirror the value to the registry here.
+ nsresult rv = mProfileDB.SetString("General", "StartWithLastProfile",
+ aValue ? "1" : "0");
+ NS_ENSURE_SUCCESS(rv, rv);
+ mStartWithLast = aValue;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetStartWithLastProfile(bool* aResult) {
+ *aResult = mStartWithLast;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetProfiles(nsISimpleEnumerator** aResult) {
+ *aResult = new ProfileEnumerator(mProfiles.getFirst());
+
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::ProfileEnumerator::HasMoreElements(bool* aResult) {
+ *aResult = mCurrent ? true : false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::ProfileEnumerator::GetNext(nsISupports** aResult) {
+ if (!mCurrent) return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*aResult = mCurrent);
+
+ mCurrent = mCurrent->getNext();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetCurrentProfile(nsIToolkitProfile** aResult) {
+ NS_IF_ADDREF(*aResult = mCurrent);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetDefaultProfile(nsIToolkitProfile** aResult) {
+ if (mUseDedicatedProfile) {
+ NS_IF_ADDREF(*aResult = mDedicatedProfile);
+ return NS_OK;
+ }
+
+ if (mUseDevEditionProfile) {
+ NS_IF_ADDREF(*aResult = mDevEditionDefault);
+ return NS_OK;
+ }
+
+ NS_IF_ADDREF(*aResult = mNormalDefault);
+ return NS_OK;
+}
+
+void nsToolkitProfileService::SetNormalDefault(nsIToolkitProfile* aProfile) {
+ if (mNormalDefault == aProfile) {
+ return;
+ }
+
+ if (mNormalDefault) {
+ nsToolkitProfile* profile =
+ static_cast<nsToolkitProfile*>(mNormalDefault.get());
+ mProfileDB.DeleteString(profile->mSection.get(), "Default");
+ }
+
+ mNormalDefault = aProfile;
+
+ if (mNormalDefault) {
+ nsToolkitProfile* profile =
+ static_cast<nsToolkitProfile*>(mNormalDefault.get());
+ mProfileDB.SetString(profile->mSection.get(), "Default", "1");
+ }
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) {
+ if (mUseDedicatedProfile) {
+ if (mDedicatedProfile != aProfile) {
+ if (!aProfile) {
+ // Setting this to the empty string means no profile will be found on
+ // startup but we'll recognise that this install has been used
+ // previously.
+ mProfileDB.SetString(mInstallSection.get(), "Default", "");
+ } else {
+ nsCString profilePath;
+ nsresult rv = GetProfileDescriptor(aProfile, profilePath, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mProfileDB.SetString(mInstallSection.get(), "Default",
+ profilePath.get());
+ }
+ mDedicatedProfile = aProfile;
+
+ // Some kind of choice has happened here, lock this profile to this
+ // install.
+ mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
+ }
+ return NS_OK;
+ }
+
+ if (mUseDevEditionProfile && aProfile != mDevEditionDefault) {
+ // The separate profile is hardcoded.
+ return NS_ERROR_FAILURE;
+ }
+
+ SetNormalDefault(aProfile);
+
+ return NS_OK;
+}
+
+// Gets the profile root directory descriptor for storing in profiles.ini or
+// installs.ini.
+nsresult nsToolkitProfileService::GetProfileDescriptor(
+ nsIToolkitProfile* aProfile, nsACString& aDescriptor, bool* aIsRelative) {
+ nsCOMPtr<nsIFile> profileDir;
+ nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if the profile dir is relative to appdir...
+ bool isRelative;
+ rv = mAppData->Contains(profileDir, &isRelative);
+
+ nsCString profilePath;
+ if (NS_SUCCEEDED(rv) && isRelative) {
+ // we use a relative descriptor
+ rv = profileDir->GetRelativeDescriptor(mAppData, profilePath);
+ } else {
+ // otherwise, a persistent descriptor
+ rv = profileDir->GetPersistentDescriptor(profilePath);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aDescriptor.Assign(profilePath);
+ if (aIsRelative) {
+ *aIsRelative = isRelative;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsToolkitProfileService::CreateDefaultProfile(
+ nsIToolkitProfile** aResult) {
+ // Create a new default profile
+ nsAutoCString name;
+ if (mUseDevEditionProfile) {
+ name.AssignLiteral(DEV_EDITION_NAME);
+ } else if (mUseDedicatedProfile) {
+ name.AppendPrintf("default-%s", mUpdateChannel.get());
+ } else {
+ name.AssignLiteral(DEFAULT_NAME);
+ }
+
+ nsresult rv = CreateUniqueProfile(nullptr, name, aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mUseDedicatedProfile) {
+ SetDefaultProfile(mCurrent);
+ } else if (mUseDevEditionProfile) {
+ mDevEditionDefault = mCurrent;
+ } else {
+ SetNormalDefault(mCurrent);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * An implementation of SelectStartupProfile callable from JavaScript via XPCOM.
+ * See nsIToolkitProfileService.idl.
+ */
+NS_IMETHODIMP
+nsToolkitProfileService::SelectStartupProfile(
+ const nsTArray<nsCString>& aArgv, bool aIsResetting,
+ const nsACString& aUpdateChannel, const nsACString& aLegacyInstallHash,
+ nsIFile** aRootDir, nsIFile** aLocalDir, nsIToolkitProfile** aProfile,
+ bool* aDidCreate) {
+ int argc = aArgv.Length();
+ // Our command line handling expects argv to be null-terminated so construct
+ // an appropriate array.
+ auto argv = MakeUnique<char*[]>(argc + 1);
+ // Also, our command line handling removes things from the array without
+ // freeing them so keep track of what we've created separately.
+ auto allocated = MakeUnique<UniqueFreePtr<char>[]>(argc);
+
+ for (int i = 0; i < argc; i++) {
+ allocated[i].reset(ToNewCString(aArgv[i]));
+ argv[i] = allocated[i].get();
+ }
+ argv[argc] = nullptr;
+
+ mUpdateChannel = aUpdateChannel;
+ if (!aLegacyInstallHash.IsEmpty()) {
+ mLegacyInstallSection.Assign(aLegacyInstallHash);
+ mLegacyInstallSection.Insert(INSTALL_PREFIX, 0);
+ }
+
+ bool wasDefault;
+ nsresult rv =
+ SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir, aLocalDir,
+ aProfile, aDidCreate, &wasDefault);
+
+ // Since we were called outside of the normal startup path complete any
+ // startup tasks.
+ if (NS_SUCCEEDED(rv)) {
+ CompleteStartup();
+ }
+
+ return rv;
+}
+
+static void SaltProfileName(nsACString& aName);
+
+/**
+ * Selects or creates a profile to use based on the profiles database, any
+ * environment variables and any command line arguments. Will not create
+ * a profile if aIsResetting is true. The profile is selected based on this
+ * order of preference:
+ * * Environment variables (set when restarting the application).
+ * * --profile command line argument.
+ * * --createprofile command line argument (this also causes the app to exit).
+ * * -p command line argument.
+ * * A new profile created if this is the first run of the application.
+ * * The default profile.
+ * aRootDir and aLocalDir are set to the data and local directories for the
+ * profile data. If a profile from the database was selected it will be
+ * returned in aProfile.
+ * aDidCreate will be set to true if a new profile was created.
+ * This function should be called once at startup and will fail if called again.
+ * aArgv should be an array of aArgc + 1 strings, the last element being null.
+ * Both aArgv and aArgc will be mutated.
+ */
+nsresult nsToolkitProfileService::SelectStartupProfile(
+ int* aArgc, char* aArgv[], bool aIsResetting, nsIFile** aRootDir,
+ nsIFile** aLocalDir, nsIToolkitProfile** aProfile, bool* aDidCreate,
+ bool* aWasDefaultSelection) {
+ if (mStartupProfileSelected) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ mStartupProfileSelected = true;
+ *aDidCreate = false;
+ *aWasDefaultSelection = false;
+
+ nsresult rv;
+ const char* arg;
+
+ // Use the profile specified in the environment variables (generally from an
+ // app initiated restart).
+ nsCOMPtr<nsIFile> lf = GetFileFromEnv("XRE_PROFILE_PATH");
+ if (lf) {
+ nsCOMPtr<nsIFile> localDir = GetFileFromEnv("XRE_PROFILE_LOCAL_PATH");
+ if (!localDir) {
+ localDir = lf;
+ }
+
+ // Clear out flags that we handled (or should have handled!) last startup.
+ const char* dummy;
+ CheckArg(*aArgc, aArgv, "p", &dummy);
+ CheckArg(*aArgc, aArgv, "profile", &dummy);
+ CheckArg(*aArgc, aArgv, "profilemanager");
+
+ nsCOMPtr<nsIToolkitProfile> profile;
+ GetProfileByDir(lf, localDir, getter_AddRefs(profile));
+
+ if (profile && mIsFirstRun && mUseDedicatedProfile) {
+ if (profile ==
+ (mUseDevEditionProfile ? mDevEditionDefault : mNormalDefault)) {
+ // This is the first run of a dedicated profile build where the selected
+ // profile is the previous default so we should either make it the
+ // default profile for this install or push the user to a new profile.
+
+ bool result;
+ rv = MaybeMakeDefaultDedicatedProfile(profile, &result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (result) {
+ mStartupReason = u"restart-claimed-default"_ns;
+
+ mCurrent = profile;
+ } else {
+ rv = CreateDefaultProfile(getter_AddRefs(mCurrent));
+ if (NS_FAILED(rv)) {
+ *aProfile = nullptr;
+ return rv;
+ }
+
+ rv = Flush();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStartupReason = u"restart-skipped-default"_ns;
+ *aDidCreate = true;
+ }
+
+ NS_IF_ADDREF(*aProfile = mCurrent);
+ mCurrent->GetRootDir(aRootDir);
+ mCurrent->GetLocalDir(aLocalDir);
+
+ return NS_OK;
+ }
+ }
+
+ if (EnvHasValue("XRE_RESTARTED_BY_PROFILE_MANAGER")) {
+ mStartupReason = u"profile-manager"_ns;
+ } else if (aIsResetting) {
+ mStartupReason = u"profile-reset"_ns;
+ } else {
+ mStartupReason = u"restart"_ns;
+ }
+
+ mCurrent = profile;
+ lf.forget(aRootDir);
+ localDir.forget(aLocalDir);
+ NS_IF_ADDREF(*aProfile = profile);
+ return NS_OK;
+ }
+
+ // Check the -profile command line argument. It accepts a single argument that
+ // gives the path to use for the profile.
+ ArgResult ar = CheckArg(*aArgc, aArgv, "profile", &arg);
+ if (ar == ARG_BAD) {
+ PR_fprintf(PR_STDERR, "Error: argument --profile requires a path\n");
+ return NS_ERROR_FAILURE;
+ }
+ if (ar) {
+ nsCOMPtr<nsIFile> lf;
+ rv = XRE_GetFileFromPath(arg, getter_AddRefs(lf));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make sure that the profile path exists and it's a directory.
+ bool exists;
+ rv = lf->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) {
+ rv = lf->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ bool isDir;
+ rv = lf->IsDirectory(&isDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isDir) {
+ PR_fprintf(
+ PR_STDERR,
+ "Error: argument --profile requires a path to a directory\n");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ mStartupReason = u"argument-profile"_ns;
+
+ GetProfileByDir(lf, nullptr, getter_AddRefs(mCurrent));
+ NS_ADDREF(*aRootDir = lf);
+ // If the root dir matched a profile then use its local dir, otherwise use
+ // the root dir as the local dir.
+ if (mCurrent) {
+ mCurrent->GetLocalDir(aLocalDir);
+ } else {
+ lf.forget(aLocalDir);
+ }
+
+ NS_IF_ADDREF(*aProfile = mCurrent);
+ return NS_OK;
+ }
+
+ // Check the -createprofile command line argument. It accepts a single
+ // argument that is either the name for the new profile or the name followed
+ // by the path to use.
+ ar = CheckArg(*aArgc, aArgv, "createprofile", &arg, CheckArgFlag::RemoveArg);
+ if (ar == ARG_BAD) {
+ PR_fprintf(PR_STDERR,
+ "Error: argument --createprofile requires a profile name\n");
+ return NS_ERROR_FAILURE;
+ }
+ if (ar) {
+ const char* delim = strchr(arg, ' ');
+ nsCOMPtr<nsIToolkitProfile> profile;
+ if (delim) {
+ nsCOMPtr<nsIFile> lf;
+ rv = NS_NewNativeLocalFile(nsDependentCString(delim + 1), true,
+ getter_AddRefs(lf));
+ if (NS_FAILED(rv)) {
+ PR_fprintf(PR_STDERR, "Error: profile path not valid.\n");
+ return rv;
+ }
+
+ // As with --profile, assume that the given path will be used for the
+ // main profile directory.
+ rv = CreateProfile(lf, nsDependentCSubstring(arg, delim),
+ getter_AddRefs(profile));
+ } else {
+ rv = CreateProfile(nullptr, nsDependentCString(arg),
+ getter_AddRefs(profile));
+ }
+ // Some pathological arguments can make it this far
+ if (NS_FAILED(rv) || NS_FAILED(Flush())) {
+ PR_fprintf(PR_STDERR, "Error creating profile.\n");
+ }
+ return NS_ERROR_ABORT;
+ }
+
+ // Check the -p command line argument. It either accepts a profile name and
+ // uses that named profile or without a name it opens the profile manager.
+ ar = CheckArg(*aArgc, aArgv, "p", &arg);
+ if (ar == ARG_BAD) {
+ return NS_ERROR_SHOW_PROFILE_MANAGER;
+ }
+ if (ar) {
+ rv = GetProfileByName(nsDependentCString(arg), getter_AddRefs(mCurrent));
+ if (NS_SUCCEEDED(rv)) {
+ mStartupReason = u"argument-p"_ns;
+
+ mCurrent->GetRootDir(aRootDir);
+ mCurrent->GetLocalDir(aLocalDir);
+
+ NS_ADDREF(*aProfile = mCurrent);
+ return NS_OK;
+ }
+
+ return NS_ERROR_SHOW_PROFILE_MANAGER;
+ }
+
+ ar = CheckArg(*aArgc, aArgv, "profilemanager");
+ if (ar == ARG_FOUND) {
+ return NS_ERROR_SHOW_PROFILE_MANAGER;
+ }
+
+#ifdef MOZ_BACKGROUNDTASKS
+ if (BackgroundTasks::IsBackgroundTaskMode()) {
+ // There are two cases:
+ // 1. ephemeral profile: create a new one in temporary directory.
+ // 2. non-ephemeral (persistent) profile:
+ // a. if no salted profile is known, create a new one in
+ // background task-specific directory.
+ // b. if salted profile is know, use salted path.
+ nsString installHash;
+ rv = gDirServiceProvider->GetInstallHash(installHash);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString profilePrefix(BackgroundTasks::GetProfilePrefix(
+ NS_LossyConvertUTF16toASCII(installHash)));
+
+ nsCString taskName(BackgroundTasks::GetBackgroundTasks().ref());
+
+ nsCOMPtr<nsIFile> file;
+
+ if (BackgroundTasks::IsEphemeralProfileTaskName(taskName)) {
+ // Background task mode does not enable legacy telemetry, so this is for
+ // completeness and testing only.
+ mStartupReason = u"backgroundtask-ephemeral"_ns;
+
+ nsCOMPtr<nsIFile> rootDir;
+ rv = GetSpecialSystemDirectory(OS_TemporaryDirectory,
+ getter_AddRefs(rootDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsresult rv = BackgroundTasks::CreateEphemeralProfileDirectory(
+ rootDir, profilePrefix, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // In background task mode, NS_ERROR_UNEXPECTED is handled specially to
+ // exit with a non-zero exit code.
+ return NS_ERROR_UNEXPECTED;
+ }
+ *aDidCreate = true;
+ } else {
+ // Background task mode does not enable legacy telemetry, so this is for
+ // completeness and testing only.
+ mStartupReason = u"backgroundtask-not-ephemeral"_ns;
+
+ // A non-ephemeral profile is required.
+ nsCOMPtr<nsIFile> rootDir;
+ nsresult rv = gDirServiceProvider->GetBackgroundTasksProfilesRootDir(
+ getter_AddRefs(rootDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString buffer;
+ rv = mProfileDB.GetString("BackgroundTasksProfiles", profilePrefix.get(),
+ buffer);
+ if (NS_SUCCEEDED(rv)) {
+ // We have a record of one! Use it.
+ rv = rootDir->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->AppendNative(buffer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsCString saltedProfilePrefix = profilePrefix;
+ SaltProfileName(saltedProfilePrefix);
+
+ nsresult rv = BackgroundTasks::CreateNonEphemeralProfileDirectory(
+ rootDir, saltedProfilePrefix, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // In background task mode, NS_ERROR_UNEXPECTED is handled specially
+ // to exit with a non-zero exit code.
+ return NS_ERROR_UNEXPECTED;
+ }
+ *aDidCreate = true;
+
+ // Keep a record of the salted name. It's okay if this doesn't succeed:
+ // not great, but it's better for tasks (particularly,
+ // `backgroundupdate`) to run and not persist state correctly than to
+ // not run at all.
+ rv =
+ mProfileDB.SetString("BackgroundTasksProfiles", profilePrefix.get(),
+ saltedProfilePrefix.get());
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = Flush();
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+ }
+ }
+
+ nsCOMPtr<nsIFile> localDir = file;
+ file.forget(aRootDir);
+ localDir.forget(aLocalDir);
+
+ // Background tasks never use profiles known to the profile service.
+ *aProfile = nullptr;
+
+ return NS_OK;
+ }
+#endif
+
+ if (mIsFirstRun && mUseDedicatedProfile &&
+ !mInstallSection.Equals(mLegacyInstallSection)) {
+ // The default profile could be assigned to a hash generated from an
+ // incorrectly cased version of the installation directory (see bug
+ // 1555319). Ideally we'd do all this while loading profiles.ini but we
+ // can't override the legacy section value before that for tests.
+ nsCString defaultDescriptor;
+ rv = mProfileDB.GetString(mLegacyInstallSection.get(), "Default",
+ defaultDescriptor);
+
+ if (NS_SUCCEEDED(rv)) {
+ // There is a default here, need to see if it matches any profiles.
+ bool isRelative;
+ nsCString descriptor;
+
+ for (RefPtr<nsToolkitProfile> profile : mProfiles) {
+ GetProfileDescriptor(profile, descriptor, &isRelative);
+
+ if (descriptor.Equals(defaultDescriptor)) {
+ // Found the default profile. Copy the install section over to
+ // the correct location. We leave the old info in place for older
+ // versions of Firefox to use.
+ nsTArray<UniquePtr<KeyValue>> strings =
+ GetSectionStrings(&mProfileDB, mLegacyInstallSection.get());
+ for (const auto& kv : strings) {
+ mProfileDB.SetString(mInstallSection.get(), kv->key.get(),
+ kv->value.get());
+ }
+
+ // Flush now. This causes a small blip in startup but it should be
+ // one time only whereas not flushing means we have to do this search
+ // on every startup.
+ Flush();
+
+ // Now start up with the found profile.
+ mDedicatedProfile = profile;
+ mIsFirstRun = false;
+ break;
+ }
+ }
+ }
+ }
+
+ // If this is a first run then create a new profile.
+ if (mIsFirstRun) {
+ // If we're configured to always show the profile manager then don't create
+ // a new profile to use.
+ if (!mStartWithLast) {
+ return NS_ERROR_SHOW_PROFILE_MANAGER;
+ }
+
+ bool skippedDefaultProfile = false;
+
+ if (mUseDedicatedProfile) {
+ // This is the first run of a dedicated profile install. We have to decide
+ // whether to use the default profile used by non-dedicated-profile
+ // installs or to create a new profile.
+
+ // Find what would have been the default profile for old installs.
+ nsCOMPtr<nsIToolkitProfile> profile = mNormalDefault;
+ if (mUseDevEditionProfile) {
+ profile = mDevEditionDefault;
+ }
+
+ if (profile) {
+ nsCOMPtr<nsIFile> rootDir;
+ profile->GetRootDir(getter_AddRefs(rootDir));
+
+ nsCOMPtr<nsIFile> compat;
+ rootDir->Clone(getter_AddRefs(compat));
+ compat->Append(COMPAT_FILE);
+
+ bool exists;
+ rv = compat->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the file is missing then either this is an empty profile (likely
+ // generated by bug 1518591) or it is from an ancient version. We'll opt
+ // to leave it for older versions in this case.
+ if (exists) {
+ bool result;
+ rv = MaybeMakeDefaultDedicatedProfile(profile, &result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (result) {
+ mStartupReason = u"firstrun-claimed-default"_ns;
+
+ mCurrent = profile;
+ rootDir.forget(aRootDir);
+ profile->GetLocalDir(aLocalDir);
+ profile.forget(aProfile);
+ return NS_OK;
+ }
+
+ // We're going to create a new profile for this install even though
+ // another default exists.
+ skippedDefaultProfile = true;
+ }
+ }
+ }
+
+ rv = CreateDefaultProfile(getter_AddRefs(mCurrent));
+ if (NS_SUCCEEDED(rv)) {
+#ifdef MOZ_CREATE_LEGACY_PROFILE
+ // If there is only one profile and it isn't meant to be the profile that
+ // older versions of Firefox use then we must create a default profile
+ // for older versions of Firefox to avoid the existing profile being
+ // auto-selected.
+ if ((mUseDedicatedProfile || mUseDevEditionProfile) &&
+ mProfiles.getFirst() == mProfiles.getLast()) {
+ nsCOMPtr<nsIToolkitProfile> newProfile;
+ CreateProfile(nullptr, nsLiteralCString(DEFAULT_NAME),
+ getter_AddRefs(newProfile));
+ SetNormalDefault(newProfile);
+ }
+#endif
+
+ rv = Flush();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (skippedDefaultProfile) {
+ mStartupReason = u"firstrun-skipped-default"_ns;
+ } else {
+ mStartupReason = u"firstrun-created-default"_ns;
+ }
+
+ // Use the new profile.
+ mCurrent->GetRootDir(aRootDir);
+ mCurrent->GetLocalDir(aLocalDir);
+ NS_ADDREF(*aProfile = mCurrent);
+
+ *aDidCreate = true;
+ return NS_OK;
+ }
+ }
+
+ GetDefaultProfile(getter_AddRefs(mCurrent));
+
+ // None of the profiles was marked as default (generally only happens if the
+ // user modifies profiles.ini manually). Let the user choose.
+ if (!mCurrent) {
+ return NS_ERROR_SHOW_PROFILE_MANAGER;
+ }
+
+ // Let the caller know that the profile was selected by default.
+ *aWasDefaultSelection = true;
+ mStartupReason = u"default"_ns;
+
+ // Use the selected profile.
+ mCurrent->GetRootDir(aRootDir);
+ mCurrent->GetLocalDir(aLocalDir);
+ NS_ADDREF(*aProfile = mCurrent);
+
+ return NS_OK;
+}
+
+/**
+ * Creates a new profile for reset and mark it as the current profile.
+ */
+nsresult nsToolkitProfileService::CreateResetProfile(
+ nsIToolkitProfile** aNewProfile) {
+ nsAutoCString oldProfileName;
+ mCurrent->GetName(oldProfileName);
+
+ nsCOMPtr<nsIToolkitProfile> newProfile;
+ // Make the new profile name the old profile (or "default-") + the time in
+ // seconds since epoch for uniqueness.
+ nsAutoCString newProfileName;
+ if (!oldProfileName.IsEmpty()) {
+ newProfileName.Assign(oldProfileName);
+ newProfileName.Append("-");
+ } else {
+ newProfileName.AssignLiteral("default-");
+ }
+ newProfileName.AppendPrintf("%" PRId64, PR_Now() / 1000);
+ nsresult rv = CreateProfile(nullptr, // choose a default dir for us
+ newProfileName, getter_AddRefs(newProfile));
+ if (NS_FAILED(rv)) return rv;
+
+ mCurrent = newProfile;
+ newProfile.forget(aNewProfile);
+
+ // Don't flush the changes yet. That will happen once the migration
+ // successfully completes.
+ return NS_OK;
+}
+
+/**
+ * This is responsible for deleting the old profile, copying its name to the
+ * current profile and if the old profile was default making the new profile
+ * default as well.
+ */
+nsresult nsToolkitProfileService::ApplyResetProfile(
+ nsIToolkitProfile* aOldProfile) {
+ // If the old profile would have been the default for old installs then mark
+ // the new profile as such.
+ if (mNormalDefault == aOldProfile) {
+ SetNormalDefault(mCurrent);
+ }
+
+ if (mUseDedicatedProfile && mDedicatedProfile == aOldProfile) {
+ bool wasLocked = false;
+ nsCString val;
+ if (NS_SUCCEEDED(
+ mProfileDB.GetString(mInstallSection.get(), "Locked", val))) {
+ wasLocked = val.Equals("1");
+ }
+
+ SetDefaultProfile(mCurrent);
+
+ // Make the locked state match if necessary.
+ if (!wasLocked) {
+ mProfileDB.DeleteString(mInstallSection.get(), "Locked");
+ }
+ }
+
+ nsCString name;
+ nsresult rv = aOldProfile->GetName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't remove the old profile's files until after we've successfully flushed
+ // the profile changes to disk.
+ rv = aOldProfile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Switching the name will make this the default for dev-edition if
+ // appropriate.
+ rv = mCurrent->SetName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = Flush();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now that the profile changes are flushed, try to remove the old profile's
+ // files. If we fail the worst that will happen is that an orphan directory is
+ // left. Let this run in the background while we start up.
+ RemoveProfileFiles(aOldProfile, true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetProfileByName(const nsACString& aName,
+ nsIToolkitProfile** aResult) {
+ for (RefPtr<nsToolkitProfile> profile : mProfiles) {
+ if (profile->mName.Equals(aName)) {
+ NS_ADDREF(*aResult = profile);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+/**
+ * Finds a profile from the database that uses the given root and local
+ * directories.
+ */
+void nsToolkitProfileService::GetProfileByDir(nsIFile* aRootDir,
+ nsIFile* aLocalDir,
+ nsIToolkitProfile** aResult) {
+ for (RefPtr<nsToolkitProfile> profile : mProfiles) {
+ bool equal;
+ nsresult rv = profile->mRootDir->Equals(aRootDir, &equal);
+ if (NS_SUCCEEDED(rv) && equal) {
+ if (!aLocalDir) {
+ // If no local directory was given then we will just use the normal
+ // local directory for the profile.
+ profile.forget(aResult);
+ return;
+ }
+
+ rv = profile->mLocalDir->Equals(aLocalDir, &equal);
+ if (NS_SUCCEEDED(rv) && equal) {
+ profile.forget(aResult);
+ return;
+ }
+ }
+ }
+}
+
+nsresult NS_LockProfilePath(nsIFile* aPath, nsIFile* aTempPath,
+ nsIProfileUnlocker** aUnlocker,
+ nsIProfileLock** aResult) {
+ RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
+
+ nsresult rv = lock->Init(aPath, aTempPath, aUnlocker);
+ if (NS_FAILED(rv)) return rv;
+
+ lock.forget(aResult);
+ return NS_OK;
+}
+
+static void SaltProfileName(nsACString& aName) {
+ char salt[9];
+ NS_MakeRandomString(salt, 8);
+ salt[8] = '.';
+
+ aName.Insert(salt, 0, 9);
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::CreateUniqueProfile(nsIFile* aRootDir,
+ const nsACString& aNamePrefix,
+ nsIToolkitProfile** aResult) {
+ nsCOMPtr<nsIToolkitProfile> profile;
+ nsresult rv = GetProfileByName(aNamePrefix, getter_AddRefs(profile));
+ if (NS_FAILED(rv)) {
+ return CreateProfile(aRootDir, aNamePrefix, aResult);
+ }
+
+ uint32_t suffix = 1;
+ while (true) {
+ nsPrintfCString name("%s-%d", PromiseFlatCString(aNamePrefix).get(),
+ suffix);
+ rv = GetProfileByName(name, getter_AddRefs(profile));
+ if (NS_FAILED(rv)) {
+ return CreateProfile(aRootDir, name, aResult);
+ }
+ suffix++;
+ }
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
+ const nsACString& aName,
+ nsIToolkitProfile** aResult) {
+ nsresult rv = GetProfileByName(aName, aResult);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> rootDir(aRootDir);
+
+ nsAutoCString dirName;
+ if (!rootDir) {
+ rv = gDirServiceProvider->GetUserProfilesRootDir(getter_AddRefs(rootDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dirName = aName;
+ SaltProfileName(dirName);
+
+ if (NS_IsNativeUTF8()) {
+ rootDir->AppendNative(dirName);
+ } else {
+ rootDir->Append(NS_ConvertUTF8toUTF16(dirName));
+ }
+ }
+
+ nsCOMPtr<nsIFile> localDir;
+
+ bool isRelative;
+ rv = mAppData->Contains(rootDir, &isRelative);
+ if (NS_SUCCEEDED(rv) && isRelative) {
+ nsAutoCString path;
+ rv = rootDir->GetRelativeDescriptor(mAppData, path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = localDir->SetRelativeDescriptor(mTempData, path);
+ } else {
+ localDir = rootDir;
+ }
+
+ bool exists;
+ rv = rootDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ rv = rootDir->IsDirectory(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) return NS_ERROR_FILE_NOT_DIRECTORY;
+ } else {
+ nsCOMPtr<nsIFile> profileDirParent;
+ nsAutoString profileDirName;
+
+ rv = rootDir->GetParent(getter_AddRefs(profileDirParent));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = rootDir->GetLeafName(profileDirName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // let's ensure that the profile directory exists.
+ rv = rootDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = rootDir->SetPermissions(0700);
+#ifndef ANDROID
+ // If the profile is on the sdcard, this will fail but its non-fatal
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+ }
+
+ rv = localDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) {
+ rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // We created a new profile dir. Let's store a creation timestamp.
+ // Note that this code path does not apply if the profile dir was
+ // created prior to launching.
+ rv = CreateTimesInternal(rootDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIToolkitProfile> profile =
+ new nsToolkitProfile(aName, rootDir, localDir, false);
+
+ if (aName.Equals(DEV_EDITION_NAME)) {
+ mDevEditionDefault = profile;
+ }
+
+ profile.forget(aResult);
+ return NS_OK;
+}
+
+/**
+ * Snaps (https://snapcraft.io/) use a different installation directory for
+ * every version of an application. Since dedicated profiles uses the
+ * installation directory to determine which profile to use this would lead
+ * snap users getting a new profile on every application update.
+ *
+ * However the only way to have multiple installation of a snap is to install
+ * a new snap instance. Different snap instances have different user data
+ * directories and so already will not share profiles, in fact one instance
+ * will not even be able to see the other instance's profiles since
+ * profiles.ini will be stored in different places.
+ *
+ * So we can just disable dedicated profile support in this case and revert
+ * back to the old method of just having a single default profile and still
+ * get essentially the same benefits as dedicated profiles provides.
+ */
+bool nsToolkitProfileService::IsSnapEnvironment() {
+#ifdef MOZ_WIDGET_GTK
+ return widget::IsRunningUnderSnap();
+#else
+ return false;
+#endif
+}
+
+/**
+ * In some situations dedicated profile support does not work well. This
+ * includes a handful of linux distributions which always install different
+ * application versions to different locations, some application sandboxing
+ * systems as well as enterprise deployments. This environment variable provides
+ * a way to opt out of dedicated profiles for these cases.
+ *
+ * For Windows, we provide a policy to accomplish the same thing.
+ */
+bool nsToolkitProfileService::UseLegacyProfiles() {
+ bool legacyProfiles = !!PR_GetEnv("MOZ_LEGACY_PROFILES");
+#ifdef XP_WIN
+ legacyProfiles |= PolicyCheckBoolean(L"LegacyProfiles");
+#endif
+ return legacyProfiles;
+}
+
+struct FindInstallsClosure {
+ nsINIParser* installData;
+ nsTArray<nsCString>* installs;
+};
+
+static bool FindInstalls(const char* aSection, void* aClosure) {
+ FindInstallsClosure* closure = static_cast<FindInstallsClosure*>(aClosure);
+
+ // Check if the section starts with "Install"
+ if (strncmp(aSection, INSTALL_PREFIX, INSTALL_PREFIX_LENGTH) != 0) {
+ return true;
+ }
+
+ nsCString install(aSection);
+ closure->installs->AppendElement(install);
+
+ return true;
+}
+
+nsTArray<nsCString> nsToolkitProfileService::GetKnownInstalls() {
+ nsTArray<nsCString> result;
+ FindInstallsClosure closure = {&mProfileDB, &result};
+
+ mProfileDB.GetSections(&FindInstalls, &closure);
+
+ return result;
+}
+
+nsresult nsToolkitProfileService::CreateTimesInternal(nsIFile* aProfileDir) {
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCOMPtr<nsIFile> creationLog;
+ rv = aProfileDir->Clone(getter_AddRefs(creationLog));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = creationLog->AppendNative("times.json"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ creationLog->Exists(&exists);
+ if (exists) {
+ return NS_OK;
+ }
+
+ rv = creationLog->Create(nsIFile::NORMAL_FILE_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We don't care about microsecond resolution.
+ int64_t msec = PR_Now() / PR_USEC_PER_MSEC;
+
+ // Write it out.
+ PRFileDesc* writeFile;
+ rv = creationLog->OpenNSPRFileDesc(PR_WRONLY, 0700, &writeFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PR_fprintf(writeFile, "{\n\"created\": %lld,\n\"firstUse\": null\n}\n", msec);
+ PR_Close(writeFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetProfileCount(uint32_t* aResult) {
+ *aResult = 0;
+ for (nsToolkitProfile* profile : mProfiles) {
+ Unused << profile;
+ (*aResult)++;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::Flush() {
+ if (GetIsListOutdated()) {
+ return NS_ERROR_DATABASE_CHANGED;
+ }
+
+ nsresult rv;
+
+ // If we aren't using dedicated profiles then nothing about the list of
+ // installs can have changed, so no need to update the backup.
+ if (mUseDedicatedProfile) {
+ // Export the installs to the backup.
+ nsTArray<nsCString> installs = GetKnownInstalls();
+
+ if (!installs.IsEmpty()) {
+ nsCString data;
+ nsCString buffer;
+
+ for (uint32_t i = 0; i < installs.Length(); i++) {
+ nsTArray<UniquePtr<KeyValue>> strings =
+ GetSectionStrings(&mProfileDB, installs[i].get());
+ if (strings.IsEmpty()) {
+ continue;
+ }
+
+ // Strip "Install" from the start.
+ const nsDependentCSubstring& install =
+ Substring(installs[i], INSTALL_PREFIX_LENGTH);
+ data.AppendPrintf("[%s]\n", PromiseFlatCString(install).get());
+
+ for (uint32_t j = 0; j < strings.Length(); j++) {
+ data.AppendPrintf("%s=%s\n", strings[j]->key.get(),
+ strings[j]->value.get());
+ }
+
+ data.Append("\n");
+ }
+
+ FILE* writeFile;
+ rv = mInstallDBFile->OpenANSIFileDesc("w", &writeFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t length = data.Length();
+ if (fwrite(data.get(), sizeof(char), length, writeFile) != length) {
+ fclose(writeFile);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ fclose(writeFile);
+ } else {
+ rv = mInstallDBFile->Remove(false);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
+ return rv;
+ }
+ }
+ }
+
+ rv = mProfileDB.WriteToFile(mProfileDBFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
+ &mProfileDBModifiedTime, &mProfileDBFileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+already_AddRefed<nsToolkitProfileService> NS_GetToolkitProfileService() {
+ if (!nsToolkitProfileService::gService) {
+ nsToolkitProfileService::gService = new nsToolkitProfileService();
+ nsresult rv = nsToolkitProfileService::gService->Init();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsToolkitProfileService::Init failed!");
+ delete nsToolkitProfileService::gService;
+ return nullptr;
+ }
+ }
+
+ return do_AddRef(nsToolkitProfileService::gService);
+}
+
+nsresult XRE_GetFileFromPath(const char* aPath, nsIFile** aResult) {
+#if defined(XP_MACOSX)
+ int32_t pathLen = strlen(aPath);
+ if (pathLen > MAXPATHLEN) return NS_ERROR_INVALID_ARG;
+
+ CFURLRef fullPath = CFURLCreateFromFileSystemRepresentation(
+ nullptr, (const UInt8*)aPath, pathLen, true);
+ if (!fullPath) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIFile> lf;
+ nsresult rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(lf));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsILocalFileMac> lfMac = do_QueryInterface(lf, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = lfMac->InitWithCFURL(fullPath);
+ if (NS_SUCCEEDED(rv)) {
+ lf.forget(aResult);
+ }
+ }
+ }
+ CFRelease(fullPath);
+ return rv;
+
+#elif defined(XP_UNIX)
+ char fullPath[MAXPATHLEN];
+
+ if (!realpath(aPath, fullPath)) return NS_ERROR_FAILURE;
+
+ return NS_NewNativeLocalFile(nsDependentCString(fullPath), true, aResult);
+#elif defined(XP_WIN)
+ WCHAR fullPath[MAXPATHLEN];
+
+ if (!_wfullpath(fullPath, NS_ConvertUTF8toUTF16(aPath).get(), MAXPATHLEN))
+ return NS_ERROR_FAILURE;
+
+ return NS_NewLocalFile(nsDependentString(fullPath), true, aResult);
+
+#else
+# error Platform-specific logic needed here.
+#endif
+}