Source code

Revision control

Other Tools

/* -*- 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"
# include "WinUtils.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
#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"
#include "mozilla/XREAppData.h"
using namespace mozilla;
extern const char gToolkitBuildID[];
#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),
mMaybeLockProfile(false),
mUpdateChannel(MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL)),
mProfileDBExists(false),
mProfileDBFileSize(0),
mProfileDBModifiedTime(0) {
#ifdef MOZ_DEV_EDITION
mUseDevEditionProfile = true;
#endif
gService = this;
}
nsToolkitProfileService::~nsToolkitProfileService() {
gService = nullptr;
mProfiles.clear();
}
void nsToolkitProfileService::CompleteStartup() {
if (!mStartupProfileSelected) {
return;
}
ScalarSet(mozilla::Telemetry::ScalarID::STARTUP_PROFILE_SELECTION_REASON,
mStartupReason);
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", buffer);
if (NS_FAILED(rv)) {
// This is a profiles.ini written by an older version. We must restore
// any install data from the backup.
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;
}
/**
* 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) {
ar = CheckArg(*aArgc, aArgv, "osint");
if (ar == ARG_FOUND) {
PR_fprintf(
PR_STDERR,
"Error: argument -p is invalid when argument --osint is specified\n");
return NS_ERROR_FAILURE;
}
return NS_ERROR_SHOW_PROFILE_MANAGER;
}
if (ar) {
ar = CheckArg(*aArgc, aArgv, "osint");
if (ar == ARG_FOUND) {
PR_fprintf(
PR_STDERR,
"Error: argument -p is invalid when argument --osint is specified\n");
return NS_ERROR_FAILURE;
}
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;
}
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 (IsWinPackageEnvironment() && mIsFirstRun && !mProfiles.isEmpty()) {
// Unlike with Snap packages, Windows packages do use dedicated profiles,
// so we don't need any special behavior to create a profile for them.
// However, it's likely that a user of an app package was previously using
// a non-packaged installation, and so has a dedicated profile for that
// installation, or possibly a "normal" default profile, and we want that
// user to have some level of continuity when they make the switch. What
// that means is that we need to identify the profile that is most likely to
// be the one the user was last working in, and run a profile migration into
// our new dedicated profile for this installation. This code handles the
// first part, identifying the profile, and then hands off to the regular
// profile cleanup code in XREMain to run the actual migration.
nsCOMPtr<nsIToolkitProfile> oldProfile;
// Packages aren't really associated with any non-packaged installation, so
// we have no way to look up any specific dedicated profile. The next best
// way we have to identify the most relevant profile is to find the one
// that was used most recently.
PRTime latestTime = 0;
// We'll also check whether each profile would be a downgrade, unless
// downgrades are being allowed.
bool allowDowngrade =
EnvHasValue("MOZ_ALLOW_DOWNGRADE") ||
CheckArg(*aArgc, aArgv, "allow-downgrade",
static_cast<const char**>(nullptr), CheckArgFlag::None);
for (nsCOMPtr<nsIToolkitProfile> profile : mProfiles) {
// Get this profile's last used time by checking the modified time on
// its lock file. This isn't an ideal way to decide when the profile was
// used last, but that time doesn't seem to be reliably written anywhere
// else that we can read from here.
nsCOMPtr<nsIFile> rootDir;
profile->GetRootDir(getter_AddRefs(rootDir));
nsCOMPtr<nsIFile> lockFile;
rootDir->Clone(getter_AddRefs(lockFile));
lockFile->Append(u"parent.lock"_ns);