mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +00:00
d3577c0995
Differential Revision: https://phabricator.services.mozilla.com/D222058
2862 lines
88 KiB
C++
2862 lines
88 KiB
C++
/* -*- 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/Preferences.h"
|
|
#include "mozilla/ErrorResult.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/dom/DOMMozPromiseRequestHolder.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "nsIToolkitShellService.h"
|
|
#include "mozilla/glean/GleanMetrics.h"
|
|
#include "nsProxyRelease.h"
|
|
#ifdef MOZ_HAS_REMOTE
|
|
# include "nsRemoteService.h"
|
|
#endif
|
|
#include "prinrval.h"
|
|
#include "prthread.h"
|
|
#include "xpcpublic.h"
|
|
#include "nsProxyRelease.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
|
|
#define STORE_ID_PREF "toolkit.profiles.storeID"
|
|
|
|
struct KeyValue {
|
|
KeyValue(const char* aKey, const char* aValue) : key(aKey), value(aValue) {}
|
|
|
|
nsCString key;
|
|
nsCString value;
|
|
};
|
|
|
|
/**
|
|
* 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>> strings;
|
|
aParser->GetStrings(
|
|
aSection, [&strings](const char* aString, const char* aValue) {
|
|
strings.AppendElement(MakeUnique<KeyValue>(aString, aValue));
|
|
return true;
|
|
});
|
|
|
|
return strings;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
/**
|
|
* `aLockTimeout` is the number of seconds to wait to obtain the profile lock
|
|
* before failing. Set to 0 to not wait at all and immediately fail if not lock
|
|
* was obtained.
|
|
*/
|
|
nsresult RemoveProfileFiles(nsIFile* aRootDir, nsIFile* aLocalDir,
|
|
uint32_t aLockTimeout) {
|
|
// XXX If we get here with an active quota manager,
|
|
// something went very wrong. We want to assert this.
|
|
|
|
// Attempt to acquire the profile lock.
|
|
nsresult rv;
|
|
nsCOMPtr<nsIProfileLock> lock;
|
|
const mozilla::TimeStamp epoch = mozilla::TimeStamp::Now();
|
|
do {
|
|
rv = NS_LockProfilePath(aRootDir, aLocalDir, nullptr, getter_AddRefs(lock));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
break;
|
|
}
|
|
|
|
// If we don't want to delay at all then bail immediately.
|
|
if (aLockTimeout == 0) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// Check twice a second.
|
|
PR_Sleep(500);
|
|
} while ((mozilla::TimeStamp::Now() - epoch) <
|
|
mozilla::TimeDuration::FromSeconds(aLockTimeout));
|
|
|
|
// If we failed to acquire the lock then give up.
|
|
if (!lock) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// 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;
|
|
rv = aRootDir->Equals(aLocalDir, &equals);
|
|
if (NS_SUCCEEDED(rv) && !equals) {
|
|
RemoveProfileRecursion(aLocalDir,
|
|
/* aIsIgnoreRoot */ false,
|
|
/* aIsIgnoreLockfile */ false, undeletedFiles);
|
|
}
|
|
// Now remove the content of the profile dir (except lockfile)
|
|
RemoveProfileRecursion(aRootDir,
|
|
/* 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();
|
|
|
|
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 << aRootDir->Remove(true);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
|
|
nsIFile* aLocalDir, bool aFromDB,
|
|
const nsACString& aStoreID = VoidCString(),
|
|
bool aShowProfileSelector = false)
|
|
: mName(aName),
|
|
mRootDir(aRootDir),
|
|
mLocalDir(aLocalDir),
|
|
mStoreID(aStoreID),
|
|
mShowProfileSelector(aShowProfileSelector),
|
|
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());
|
|
if (!mStoreID.IsVoid()) {
|
|
db->SetString(mSection.get(), "StoreID",
|
|
PromiseFlatCString(mStoreID).get());
|
|
db->SetString(mSection.get(), "ShowSelector",
|
|
aShowProfileSelector ? "1" : "0");
|
|
}
|
|
}
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(nsToolkitProfile, nsIToolkitProfile)
|
|
|
|
NS_IMETHODIMP
|
|
nsToolkitProfile::GetRootDir(nsIFile** aResult) {
|
|
NS_ADDREF(*aResult = mRootDir);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsToolkitProfile::SetRootDir(nsIFile* aRootDir) {
|
|
NS_ASSERTION(nsToolkitProfileService::gService, "Where did my service go?");
|
|
|
|
// If the new path is the old path, we're done.
|
|
bool equals;
|
|
nsresult rv = mRootDir->Equals(aRootDir, &equals);
|
|
if (NS_SUCCEEDED(rv) && equals) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Calculate the new paths.
|
|
nsCString newPath;
|
|
bool isRelative;
|
|
rv = nsToolkitProfileService::gService->GetProfileDescriptor(
|
|
aRootDir, newPath, &isRelative);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIFile> localDir;
|
|
rv = nsToolkitProfileService::gService->GetLocalDirFromRootDir(
|
|
aRootDir, getter_AddRefs(localDir));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Update the database entry for the current profile.
|
|
nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
|
|
rv = db->SetString(mSection.get(), "Path", newPath.get());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = db->SetString(mSection.get(), "IsRelative", isRelative ? "1" : "0");
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// If this profile is the dedicated default, also update the database entry
|
|
// for the install.
|
|
if (nsToolkitProfileService::gService->mDedicatedProfile == this) {
|
|
rv = db->SetString(nsToolkitProfileService::gService->mInstallSection.get(),
|
|
"Default", newPath.get());
|
|
}
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Finally, set the new paths on the local object.
|
|
mRootDir = aRootDir;
|
|
mLocalDir = localDir;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsToolkitProfile::GetStoreID(nsACString& aResult) {
|
|
aResult = mStoreID;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsToolkitProfile::SetStoreID(const nsACString& aStoreID) {
|
|
#ifdef MOZ_SELECTABLE_PROFILES
|
|
NS_ASSERTION(nsToolkitProfileService::gService, "Where did my service go?");
|
|
|
|
if (mStoreID.Equals(aStoreID)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
|
|
|
if (!aStoreID.IsVoid()) {
|
|
rv = nsToolkitProfileService::gService->mProfileDB.SetString(
|
|
mSection.get(), "StoreID", PromiseFlatCString(aStoreID).get());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = nsToolkitProfileService::gService->mProfileDB.SetString(
|
|
mSection.get(), "ShowSelector", mShowProfileSelector ? "1" : "0");
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (nsToolkitProfileService::gService->mCurrent == this) {
|
|
rv = prefs->SetCharPref(STORE_ID_PREF, aStoreID);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsToolkitProfileService::gService->mGroupProfile = this;
|
|
}
|
|
} else {
|
|
// If the string was not present in the ini file, just ignore the error.
|
|
nsToolkitProfileService::gService->mProfileDB.DeleteString(mSection.get(),
|
|
"StoreID");
|
|
|
|
// We need a StoreID to show the profile selector, so if StoreID has been
|
|
// removed, then remove ShowSelector also.
|
|
mShowProfileSelector = false;
|
|
|
|
// If the string was not present in the ini file, just ignore the error.
|
|
nsToolkitProfileService::gService->mProfileDB.DeleteString(mSection.get(),
|
|
"ShowSelector");
|
|
|
|
if (nsToolkitProfileService::gService->mCurrent == this) {
|
|
rv = prefs->ClearUserPref(STORE_ID_PREF);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsToolkitProfileService::gService->mGroupProfile = nullptr;
|
|
}
|
|
}
|
|
mStoreID = aStoreID;
|
|
|
|
return NS_OK;
|
|
#else
|
|
return NS_ERROR_FAILURE;
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsToolkitProfile::GetShowProfileSelector(bool* aShowProfileSelector) {
|
|
#ifdef MOZ_SELECTABLE_PROFILES
|
|
*aShowProfileSelector = mShowProfileSelector;
|
|
#else
|
|
*aShowProfileSelector = false;
|
|
#endif
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsToolkitProfile::SetShowProfileSelector(bool aShowProfileSelector) {
|
|
#ifdef MOZ_SELECTABLE_PROFILES
|
|
NS_ASSERTION(nsToolkitProfileService::gService, "Where did my service go?");
|
|
|
|
// We need a StoreID to show the profile selector; bail out if it's missing.
|
|
if (mStoreID.IsVoid()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (mShowProfileSelector == aShowProfileSelector) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult rv = nsToolkitProfileService::gService->mProfileDB.SetString(
|
|
mSection.get(), "ShowSelector", aShowProfileSelector ? "1" : "0");
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mShowProfileSelector = aShowProfileSelector;
|
|
return NS_OK;
|
|
#else
|
|
return NS_ERROR_FAILURE;
|
|
#endif
|
|
}
|
|
|
|
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) {
|
|
if (aInBackground) {
|
|
NS_DispatchBackgroundTask(NS_NewRunnableFunction(
|
|
__func__, [rootDir = mRootDir, localDir = mLocalDir]() mutable {
|
|
RemoveProfileFiles(rootDir, localDir, 5);
|
|
}));
|
|
} else {
|
|
// Failure is ignored here.
|
|
RemoveProfileFiles(mRootDir, mLocalDir, 0);
|
|
}
|
|
}
|
|
|
|
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("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;
|
|
}
|
|
|
|
glean::startup::profile_selection_reason.Set(mStartupReason);
|
|
glean::startup::profile_database_version.Set(mStartupFileVersion);
|
|
glean::startup::profile_count.Set(static_cast<uint32_t>(mProfiles.length()));
|
|
|
|
nsresult rv;
|
|
bool needsFlush = false;
|
|
|
|
// If we started into an unmanaged profile in a profile group, set the group
|
|
// profile to be the managed profile belonging to the group.
|
|
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
|
if (!mCurrent) {
|
|
nsCString storeID;
|
|
rv = prefs->GetCharPref(STORE_ID_PREF, storeID);
|
|
if (NS_SUCCEEDED(rv) && !storeID.IsEmpty()) {
|
|
mGroupProfile = GetProfileByStoreID(storeID);
|
|
}
|
|
} else {
|
|
// Otherwise, if the current profile has a storeID, then it must be the
|
|
// profile for some group.
|
|
if (!mCurrent->mStoreID.IsVoid()) {
|
|
mGroupProfile = mCurrent;
|
|
rv = prefs->SetCharPref(STORE_ID_PREF, mCurrent->mStoreID);
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
} else {
|
|
// Otherwise if the current profile has a store ID set in prefs but not in
|
|
// the database then restore it. This can happen if a version of Firefox
|
|
// prior to 67 has overwritten the database.
|
|
nsCString storeID;
|
|
rv = prefs->GetCharPref(STORE_ID_PREF, storeID);
|
|
if (NS_SUCCEEDED(rv) && !storeID.IsEmpty()) {
|
|
rv = mCurrent->SetStoreID(storeID);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
needsFlush = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mMaybeLockProfile) {
|
|
nsCOMPtr<nsIToolkitShellService> shell =
|
|
do_GetService(NS_TOOLKITSHELLSERVICE_CONTRACTID);
|
|
if (shell) {
|
|
bool isDefaultApp;
|
|
rv = shell->IsDefaultApplication(&isDefaultApp);
|
|
if (NS_SUCCEEDED(rv) && isDefaultApp) {
|
|
mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
|
|
|
|
needsFlush = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (needsFlush) {
|
|
// 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(
|
|
nsToolkitProfile* aProfile) {
|
|
nsCOMPtr<nsIFile> compatFile;
|
|
nsresult rv = aProfile->mRootDir->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, 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(
|
|
nsToolkitProfile* 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);
|
|
return NS_FAILED(rv) || time != aLastModified;
|
|
}
|
|
|
|
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) {
|
|
*aResult = IsFileOutdated(mProfileDBFile, mProfileDBExists,
|
|
mProfileDBModifiedTime, mProfileDBFileSize);
|
|
return NS_OK;
|
|
}
|
|
|
|
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.
|
|
installDB.GetSections([installDB = &installDB,
|
|
profileDB = &mProfileDB](const char* aSection) {
|
|
nsTArray<UniquePtr<KeyValue>> strings =
|
|
GetSectionStrings(installDB, aSection);
|
|
if (strings.IsEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
nsCString newSection(INSTALL_PREFIX);
|
|
newSection.Append(aSection);
|
|
|
|
for (uint32_t i = 0; i < strings.Length(); i++) {
|
|
profileDB->SetString(newSection.get(), strings[i]->key.get(),
|
|
strings[i]->value.get());
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
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
|
|
|
|
RefPtr<nsToolkitProfile> 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, 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;
|
|
rv = nsToolkitProfileService::gService->GetLocalDirFromRootDir(
|
|
rootDir, getter_AddRefs(localDir));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCString storeID;
|
|
bool showProfileSelector = false;
|
|
|
|
rv = mProfileDB.GetString(profileID.get(), "StoreID", storeID);
|
|
|
|
// If the StoreID was not found, just set it to an empty string.
|
|
if (NS_FAILED(rv) && rv == NS_ERROR_FAILURE) {
|
|
storeID = VoidCString();
|
|
}
|
|
|
|
// Only get the ShowSelector value if StoreID is nonempty.
|
|
if (!storeID.IsVoid()) {
|
|
rv = mProfileDB.GetString(profileID.get(), "ShowSelector", buffer);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
showProfileSelector = buffer.EqualsLiteral("1");
|
|
}
|
|
}
|
|
|
|
currentProfile = new nsToolkitProfile(name, rootDir, localDir, true,
|
|
storeID, showProfileSelector);
|
|
|
|
// 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::GetGroupProfile(nsIToolkitProfile** aResult) {
|
|
NS_IF_ADDREF(*aResult = mGroupProfile);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsToolkitProfileService::GetDefaultProfile(nsIToolkitProfile** aResult) {
|
|
RefPtr<nsToolkitProfile> profile = GetDefaultProfile();
|
|
profile.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
already_AddRefed<nsToolkitProfile>
|
|
nsToolkitProfileService::GetDefaultProfile() {
|
|
if (mUseDedicatedProfile) {
|
|
return do_AddRef(mDedicatedProfile);
|
|
}
|
|
|
|
if (mUseDevEditionProfile) {
|
|
return do_AddRef(mDevEditionDefault);
|
|
}
|
|
|
|
return do_AddRef(mNormalDefault);
|
|
}
|
|
|
|
void nsToolkitProfileService::SetNormalDefault(nsToolkitProfile* aProfile) {
|
|
if (mNormalDefault == aProfile) {
|
|
return;
|
|
}
|
|
|
|
if (mNormalDefault) {
|
|
mProfileDB.DeleteString(mNormalDefault->mSection.get(), "Default");
|
|
}
|
|
|
|
mNormalDefault = aProfile;
|
|
|
|
if (mNormalDefault) {
|
|
mProfileDB.SetString(mNormalDefault->mSection.get(), "Default", "1");
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) {
|
|
nsToolkitProfile* profile = static_cast<nsToolkitProfile*>(aProfile);
|
|
|
|
if (mUseDedicatedProfile) {
|
|
if (mDedicatedProfile != profile) {
|
|
if (!profile) {
|
|
// 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(profile, profilePath, nullptr);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mProfileDB.SetString(mInstallSection.get(), "Default",
|
|
profilePath.get());
|
|
}
|
|
mDedicatedProfile = profile;
|
|
|
|
// Some kind of choice has happened here, lock this profile to this
|
|
// install.
|
|
mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
if (mUseDevEditionProfile && profile != mDevEditionDefault) {
|
|
// The separate profile is hardcoded.
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
SetNormalDefault(profile);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Gets the profile root directory descriptor for storing in profiles.ini or
|
|
// installs.ini.
|
|
nsresult nsToolkitProfileService::GetProfileDescriptor(
|
|
nsToolkitProfile* aProfile, nsACString& aDescriptor, bool* aIsRelative) {
|
|
return GetProfileDescriptor(aProfile->mRootDir, aDescriptor, aIsRelative);
|
|
}
|
|
|
|
nsresult nsToolkitProfileService::GetProfileDescriptor(nsIFile* aRootDir,
|
|
nsACString& aDescriptor,
|
|
bool* aIsRelative) {
|
|
// if the profile dir is relative to appdir...
|
|
bool isRelative;
|
|
nsresult rv = mAppData->Contains(aRootDir, &isRelative);
|
|
|
|
nsCString profilePath;
|
|
if (NS_SUCCEEDED(rv) && isRelative) {
|
|
// we use a relative descriptor
|
|
rv = aRootDir->GetRelativeDescriptor(mAppData, profilePath);
|
|
} else {
|
|
// otherwise, a persistent descriptor
|
|
rv = aRootDir->GetPersistentDescriptor(profilePath);
|
|
}
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
aDescriptor.Assign(profilePath);
|
|
if (aIsRelative) {
|
|
*aIsRelative = isRelative;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsToolkitProfileService::CreateDefaultProfile(
|
|
nsToolkitProfile** 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);
|
|
|
|
nsresult EnsureDirExists(nsIFile* aPath) {
|
|
bool isDir;
|
|
nsresult rv = aPath->IsDirectory(&isDir);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
return isDir ? NS_OK : NS_ERROR_FILE_NOT_DIRECTORY;
|
|
}
|
|
if (rv != NS_ERROR_FILE_NOT_FOUND) {
|
|
return rv;
|
|
}
|
|
return aPath->Create(nsIFile::DIRECTORY_TYPE, 0700);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
rv = nsToolkitProfileService::gService->GetLocalDirFromRootDir(
|
|
lf, getter_AddRefs(localDir));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
// 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");
|
|
|
|
RefPtr<nsToolkitProfile> 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 = "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 = "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 = "profile-manager"_ns;
|
|
} else if (EnvHasValue("XRE_RESTARTED_BY_PROFILE_SELECTOR")) {
|
|
mStartupReason = "profile-selector"_ns;
|
|
} else if (aIsResetting) {
|
|
mStartupReason = "profile-reset"_ns;
|
|
} else {
|
|
mStartupReason = "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.
|
|
rv = EnsureDirExists(lf);
|
|
if (NS_FAILED(rv)) {
|
|
PR_fprintf(PR_STDERR,
|
|
"Error: argument --profile requires a path to a directory\n");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mStartupReason = "argument-profile"_ns;
|
|
|
|
GetProfileByDir(lf, nullptr, getter_AddRefs(mCurrent));
|
|
NS_ADDREF(*aRootDir = lf);
|
|
|
|
nsCOMPtr<nsIFile> localDir;
|
|
rv = nsToolkitProfileService::gService->GetLocalDirFromRootDir(
|
|
lf, getter_AddRefs(localDir));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
NS_IF_ADDREF(*aProfile = mCurrent);
|
|
|
|
localDir.forget(aLocalDir);
|
|
|
|
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),
|
|
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) {
|
|
mCurrent = GetProfileByName(nsDependentCString(arg));
|
|
if (mCurrent) {
|
|
mStartupReason = "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 = "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 = "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);
|
|
bool exists = false;
|
|
|
|
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);
|
|
|
|
rv = file->Exists(&exists);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!exists) {
|
|
printf_stderr(
|
|
"Profile directory does not exist, create a new directory");
|
|
}
|
|
}
|
|
|
|
if (!exists) {
|
|
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.
|
|
RefPtr<nsToolkitProfile> profile = mNormalDefault;
|
|
if (mUseDevEditionProfile) {
|
|
profile = mDevEditionDefault;
|
|
}
|
|
|
|
if (profile) {
|
|
nsCOMPtr<nsIFile> rootDir = profile->GetRootDir();
|
|
|
|
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 = "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()) {
|
|
RefPtr<nsToolkitProfile> newProfile;
|
|
CreateProfile(nullptr, nsLiteralCString(DEFAULT_NAME),
|
|
getter_AddRefs(newProfile));
|
|
SetNormalDefault(newProfile);
|
|
}
|
|
#endif
|
|
|
|
rv = Flush();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (skippedDefaultProfile) {
|
|
mStartupReason = "firstrun-skipped-default"_ns;
|
|
} else {
|
|
mStartupReason = "firstrun-created-default"_ns;
|
|
}
|
|
|
|
// Use the new profile.
|
|
mCurrent->GetRootDir(aRootDir);
|
|
mCurrent->GetLocalDir(aLocalDir);
|
|
NS_ADDREF(*aProfile = mCurrent);
|
|
|
|
*aDidCreate = true;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
mCurrent = GetDefaultProfile();
|
|
|
|
// 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 = "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);
|
|
|
|
RefPtr<nsToolkitProfile> 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.
|
|
nsCOMPtr<nsIFile> rootDir = aOldProfile->GetRootDir();
|
|
nsCOMPtr<nsIFile> localDir = aOldProfile->GetLocalDir();
|
|
NS_DispatchBackgroundTask(NS_NewRunnableFunction(
|
|
__func__, [rootDir = rootDir, localDir = localDir]() mutable {
|
|
RemoveProfileFiles(rootDir, localDir, 5);
|
|
}));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsToolkitProfileService::GetProfileByName(const nsACString& aName,
|
|
nsIToolkitProfile** aResult) {
|
|
RefPtr<nsToolkitProfile> profile = GetProfileByName(aName);
|
|
if (profile) {
|
|
profile.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
already_AddRefed<nsToolkitProfile> nsToolkitProfileService::GetProfileByName(
|
|
const nsACString& aName) {
|
|
for (RefPtr<nsToolkitProfile> profile : mProfiles) {
|
|
if (profile->mName.Equals(aName)) {
|
|
return profile.forget();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
already_AddRefed<nsToolkitProfile> nsToolkitProfileService::GetProfileByStoreID(
|
|
const nsACString& aStoreID) {
|
|
if (aStoreID.IsVoid()) {
|
|
return nullptr;
|
|
}
|
|
|
|
for (RefPtr<nsToolkitProfile> profile : mProfiles) {
|
|
if (profile->mStoreID.Equals(aStoreID)) {
|
|
return profile.forget();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Finds a profile from the database that uses the given root and local
|
|
* directories.
|
|
*/
|
|
void nsToolkitProfileService::GetProfileByDir(nsIFile* aRootDir,
|
|
nsIFile* aLocalDir,
|
|
nsToolkitProfile** 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsToolkitProfileService::GetProfileByDir(nsIFile* aRootDir, nsIFile* aLocalDir,
|
|
nsIToolkitProfile** aResult) {
|
|
RefPtr<nsToolkitProfile> result;
|
|
GetProfileByDir(aRootDir, aLocalDir, getter_AddRefs(result));
|
|
result.forget(aResult);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
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) {
|
|
RefPtr<nsToolkitProfile> profile;
|
|
nsresult rv =
|
|
CreateUniqueProfile(aRootDir, aNamePrefix, getter_AddRefs(profile));
|
|
profile.forget(aResult);
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsToolkitProfileService::CreateUniqueProfile(
|
|
nsIFile* aRootDir, const nsACString& aNamePrefix,
|
|
nsToolkitProfile** 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) {
|
|
RefPtr<nsToolkitProfile> profile;
|
|
nsresult rv = CreateProfile(aRootDir, aName, getter_AddRefs(profile));
|
|
profile.forget(aResult);
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
|
|
const nsACString& aName,
|
|
nsToolkitProfile** aResult) {
|
|
RefPtr<nsToolkitProfile> profile = GetProfileByName(aName);
|
|
if (profile) {
|
|
profile.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult 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;
|
|
rv = nsToolkitProfileService::gService->GetLocalDirFromRootDir(
|
|
rootDir, getter_AddRefs(localDir));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = EnsureDirExists(rootDir);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
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);
|
|
|
|
rv = EnsureDirExists(localDir);
|
|
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);
|
|
|
|
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;
|
|
}
|
|
|
|
nsTArray<nsCString> nsToolkitProfileService::GetKnownInstalls() {
|
|
nsTArray<nsCString> installs;
|
|
|
|
mProfileDB.GetSections([&installs](const char* aSection) {
|
|
// Check if the section starts with "Install"
|
|
if (strncmp(aSection, INSTALL_PREFIX, INSTALL_PREFIX_LENGTH) != 0) {
|
|
return true;
|
|
}
|
|
|
|
installs.AppendElement(aSection);
|
|
|
|
return true;
|
|
});
|
|
|
|
return installs;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Attempts to merge the given profile data into the on-disk versions which may
|
|
// have changed since rthey were loaded.
|
|
nsresult WriteProfileInfo(nsIFile* profilesDBFile, nsIFile* installDBFile,
|
|
const nsCString& installSection,
|
|
const CurrentProfileData* profileInfo) {
|
|
nsINIParser profilesIni;
|
|
nsresult rv = profilesIni.Init(profilesDBFile);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// The INI data may have changed on disk so we cannot guarantee the section
|
|
// mapping remains the same. So we attempt to find the current profile's info
|
|
// by path or store ID.
|
|
nsCString iniSection;
|
|
profilesIni.GetSections(
|
|
[&profileInfo, &profilesIni, &iniSection](const char* section) {
|
|
nsCString value;
|
|
nsresult rv = profilesIni.GetString(section, "StoreID", value);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
if (profileInfo->mStoreID.Equals(value)) {
|
|
iniSection = section;
|
|
// This is definitely the right one so no need to continue.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (iniSection.IsEmpty()) {
|
|
rv = profilesIni.GetString(section, "Path", value);
|
|
if (NS_SUCCEEDED(rv) && profileInfo->mPath.Equals(value)) {
|
|
// This might be right but we would prefer to find by store ID.
|
|
iniSection = section;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
if (iniSection.IsEmpty()) {
|
|
// No section found. Should we write a new one?
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
bool changed = false;
|
|
nsCString oldValue;
|
|
rv = profilesIni.GetString(iniSection.get(), "StoreID", oldValue);
|
|
if (NS_FAILED(rv) || !oldValue.Equals(profileInfo->mStoreID)) {
|
|
rv = profilesIni.SetString(iniSection.get(), "StoreID",
|
|
profileInfo->mStoreID.get());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
changed = true;
|
|
}
|
|
|
|
rv = profilesIni.GetString(iniSection.get(), "ShowSelector", oldValue);
|
|
if (NS_FAILED(rv) ||
|
|
!oldValue.Equals(profileInfo->mShowSelector ? "1" : "0")) {
|
|
rv = profilesIni.SetString(iniSection.get(), "ShowSelector",
|
|
profileInfo->mShowSelector ? "1" : "0");
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
changed = true;
|
|
}
|
|
|
|
profilesIni.GetString(iniSection.get(), "Path", oldValue);
|
|
if (NS_FAILED(rv) || !oldValue.Equals(profileInfo->mPath)) {
|
|
rv = profilesIni.SetString(iniSection.get(), "Path",
|
|
profileInfo->mPath.get());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
changed = true;
|
|
|
|
// We must update the install default profile if it matches the old profile.
|
|
|
|
nsCString oldDefault;
|
|
rv = profilesIni.GetString(installSection.get(), "Default", oldDefault);
|
|
if (NS_SUCCEEDED(rv) && oldDefault.Equals(oldValue)) {
|
|
rv = profilesIni.SetString(installSection.get(), "Default",
|
|
profileInfo->mPath.get());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// We don't care so much if we fail to update the backup DB.
|
|
const nsDependentCSubstring& installHash =
|
|
Substring(installSection, INSTALL_PREFIX_LENGTH);
|
|
|
|
nsINIParser installsIni;
|
|
rv = installsIni.Init(installDBFile);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = installsIni.SetString(PromiseFlatCString(installHash).get(),
|
|
"Default", profileInfo->mPath.get());
|
|
if (NS_SUCCEEDED(rv)) {
|
|
installsIni.WriteToFile(installDBFile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
rv = profilesIni.WriteToFile(profilesDBFile);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsISerialEventTarget* nsToolkitProfileService::AsyncQueue() {
|
|
if (!mAsyncQueue) {
|
|
MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
|
|
"nsToolkitProfileService", getter_AddRefs(mAsyncQueue)));
|
|
}
|
|
|
|
return mAsyncQueue;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsToolkitProfileService::AsyncFlushCurrentProfile(JSContext* aCx,
|
|
dom::Promise** aPromise) {
|
|
#ifndef MOZ_HAS_REMOTE
|
|
return NS_ERROR_FAILURE;
|
|
#else
|
|
if (!mCurrent) {
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
}
|
|
|
|
nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
|
|
|
|
if (!global) {
|
|
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
|
}
|
|
|
|
ErrorResult result;
|
|
RefPtr<dom::Promise> promise = dom::Promise::Create(global, result);
|
|
|
|
if (MOZ_UNLIKELY(result.Failed())) {
|
|
return result.StealNSResult();
|
|
}
|
|
|
|
UniquePtr<CurrentProfileData> profileData = MakeUnique<CurrentProfileData>();
|
|
profileData->mStoreID = mCurrent->mStoreID;
|
|
profileData->mShowSelector = mCurrent->mShowProfileSelector;
|
|
|
|
bool isRelative;
|
|
GetProfileDescriptor(mCurrent, profileData->mPath, &isRelative);
|
|
|
|
nsCOMPtr<nsIRemoteService> rs = GetRemoteService();
|
|
RefPtr<nsRemoteService> remoteService =
|
|
static_cast<nsRemoteService*>(rs.get());
|
|
|
|
RefPtr<AsyncFlushPromise> p = remoteService->AsyncLockStartup(5000)->Then(
|
|
AsyncQueue(), __func__,
|
|
[self = RefPtr{this}, this, profileData = std::move(profileData)](
|
|
const nsRemoteService::StartupLockPromise::ResolveOrRejectValue&
|
|
aValue) {
|
|
if (aValue.IsReject()) {
|
|
// Locking failed.
|
|
return AsyncFlushPromise::CreateAndReject(aValue.RejectValue(),
|
|
__func__);
|
|
}
|
|
|
|
nsresult rv = WriteProfileInfo(mProfileDBFile, mInstallDBFile,
|
|
mInstallSection, profileData.get());
|
|
|
|
if (NS_FAILED(rv)) {
|
|
return AsyncFlushPromise::CreateAndReject(rv, __func__);
|
|
}
|
|
|
|
return AsyncFlushPromise::CreateAndResolve(true, __func__);
|
|
});
|
|
|
|
// This is responsible for cancelling the MozPromise if the global goes
|
|
// away.
|
|
auto requestHolder =
|
|
MakeRefPtr<dom::DOMMozPromiseRequestHolder<AsyncFlushPromise>>(global);
|
|
|
|
// This keeps the promise alive after this method returns.
|
|
nsMainThreadPtrHandle<dom::Promise> promiseHolder(
|
|
new nsMainThreadPtrHolder<dom::Promise>(
|
|
"nsToolkitProfileService::AsyncFlushCurrentProfile", promise));
|
|
|
|
p->Then(GetCurrentSerialEventTarget(), __func__,
|
|
[requestHolder, promiseHolder](
|
|
const AsyncFlushPromise::ResolveOrRejectValue& result) {
|
|
requestHolder->Complete();
|
|
|
|
if (result.IsReject()) {
|
|
promiseHolder->MaybeReject(result.RejectValue());
|
|
} else {
|
|
promiseHolder->MaybeResolveWithUndefined();
|
|
}
|
|
})
|
|
->Track(*requestHolder);
|
|
|
|
promise.forget(aPromise);
|
|
|
|
return NS_OK;
|
|
#endif
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsToolkitProfileService::AsyncFlush(JSContext* aCx, dom::Promise** aPromise) {
|
|
#ifndef MOZ_HAS_REMOTE
|
|
return NS_ERROR_FAILURE;
|
|
#else
|
|
nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
|
|
|
|
if (!global) {
|
|
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
|
}
|
|
|
|
ErrorResult result;
|
|
RefPtr<dom::Promise> promise = dom::Promise::Create(global, result);
|
|
|
|
if (MOZ_UNLIKELY(result.Failed())) {
|
|
return result.StealNSResult();
|
|
}
|
|
|
|
UniquePtr<IniData> iniData = MakeUnique<IniData>();
|
|
BuildIniData(iniData->mProfiles, iniData->mInstalls);
|
|
|
|
nsCOMPtr<nsIRemoteService> rs = GetRemoteService();
|
|
RefPtr<nsRemoteService> remoteService =
|
|
static_cast<nsRemoteService*>(rs.get());
|
|
|
|
RefPtr<AsyncFlushPromise> p = remoteService->AsyncLockStartup(5000)->Then(
|
|
AsyncQueue(), __func__,
|
|
[self = RefPtr{this}, this, iniData = std::move(iniData)](
|
|
const nsRemoteService::StartupLockPromise::ResolveOrRejectValue&
|
|
aValue) {
|
|
if (aValue.IsReject()) {
|
|
// Locking failed.
|
|
return AsyncFlushPromise::CreateAndReject(aValue.RejectValue(),
|
|
__func__);
|
|
}
|
|
|
|
nsresult rv = FlushData(iniData->mProfiles, iniData->mInstalls);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
return AsyncFlushPromise::CreateAndReject(rv, __func__);
|
|
}
|
|
|
|
return AsyncFlushPromise::CreateAndResolve(true, __func__);
|
|
});
|
|
|
|
// This is responsible for cancelling the MozPromise if the global goes
|
|
// away.
|
|
auto requestHolder =
|
|
MakeRefPtr<dom::DOMMozPromiseRequestHolder<AsyncFlushPromise>>(global);
|
|
|
|
// This keeps the promise alive after this method returns.
|
|
nsMainThreadPtrHandle<dom::Promise> promiseHolder(
|
|
new nsMainThreadPtrHolder<dom::Promise>(
|
|
"nsToolkitProfileService::AsyncFlushCurrentProfile", promise));
|
|
|
|
p->Then(GetCurrentSerialEventTarget(), __func__,
|
|
[requestHolder, promiseHolder](
|
|
const AsyncFlushPromise::ResolveOrRejectValue& result) {
|
|
requestHolder->Complete();
|
|
|
|
if (result.IsReject()) {
|
|
promiseHolder->MaybeReject(result.RejectValue());
|
|
} else {
|
|
promiseHolder->MaybeResolveWithUndefined();
|
|
}
|
|
})
|
|
->Track(*requestHolder);
|
|
|
|
promise.forget(aPromise);
|
|
|
|
return NS_OK;
|
|
#endif
|
|
}
|
|
|
|
nsresult nsToolkitProfileService::FlushData(const nsCString& aProfilesIniData,
|
|
const nsCString& aInstallsIniData) {
|
|
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) {
|
|
if (!aInstallsIniData.IsEmpty()) {
|
|
FILE* writeFile;
|
|
rv = mInstallDBFile->OpenANSIFileDesc("w", &writeFile);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
uint32_t length = aInstallsIniData.Length();
|
|
if (fwrite(aInstallsIniData.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;
|
|
}
|
|
}
|
|
}
|
|
|
|
FILE* writeFile;
|
|
rv = mProfileDBFile->OpenANSIFileDesc("w", &writeFile);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
uint32_t length = aProfilesIniData.Length();
|
|
if (fwrite(aProfilesIniData.get(), sizeof(char), length, writeFile) !=
|
|
length) {
|
|
fclose(writeFile);
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
fclose(writeFile);
|
|
|
|
rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
|
|
&mProfileDBModifiedTime, &mProfileDBFileSize);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsToolkitProfileService::BuildIniData(nsCString& aProfilesIniData,
|
|
nsCString& aInstallsIniData) {
|
|
// 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 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);
|
|
aInstallsIniData.AppendPrintf("[%s]\n",
|
|
PromiseFlatCString(install).get());
|
|
|
|
for (uint32_t j = 0; j < strings.Length(); j++) {
|
|
aInstallsIniData.AppendPrintf("%s=%s\n", strings[j]->key.get(),
|
|
strings[j]->value.get());
|
|
}
|
|
|
|
aInstallsIniData.Append("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
mProfileDB.WriteToString(aProfilesIniData);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsToolkitProfileService::RemoveProfileFilesByPath(nsIFile* aRootDir,
|
|
nsIFile* aLocalDir,
|
|
uint32_t aTimeout,
|
|
JSContext* aCx,
|
|
dom::Promise** aPromise) {
|
|
nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
|
|
|
|
if (!global) {
|
|
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
|
}
|
|
|
|
ErrorResult result;
|
|
RefPtr<dom::Promise> promise = dom::Promise::Create(global, result);
|
|
|
|
if (MOZ_UNLIKELY(result.Failed())) {
|
|
return result.StealNSResult();
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> localDir = aLocalDir;
|
|
if (!localDir) {
|
|
GetLocalDirFromRootDir(aRootDir, getter_AddRefs(localDir));
|
|
}
|
|
|
|
using RemoveProfilesPromise = MozPromise<bool, nsresult, false>;
|
|
// This is responsible for cancelling the MozPromise if the global goes
|
|
// away.
|
|
auto requestHolder =
|
|
MakeRefPtr<dom::DOMMozPromiseRequestHolder<RemoveProfilesPromise>>(
|
|
global);
|
|
|
|
// This keeps the promise alive after this method returns.
|
|
nsMainThreadPtrHandle<dom::Promise> promiseHolder(
|
|
new nsMainThreadPtrHolder<dom::Promise>(
|
|
"nsToolkitProfileService::AsyncFlushCurrentProfile", promise));
|
|
|
|
InvokeAsync(AsyncQueue(), __func__,
|
|
[rootDir = nsCOMPtr{aRootDir}, localDir = nsCOMPtr{localDir},
|
|
aTimeout]() {
|
|
nsresult rv = RemoveProfileFiles(rootDir, localDir, aTimeout);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
return RemoveProfilesPromise::CreateAndResolve(true,
|
|
__func__);
|
|
}
|
|
|
|
return RemoveProfilesPromise::CreateAndReject(rv, __func__);
|
|
})
|
|
->Then(GetCurrentSerialEventTarget(), __func__,
|
|
[requestHolder, promiseHolder](
|
|
const RemoveProfilesPromise::ResolveOrRejectValue& result) {
|
|
requestHolder->Complete();
|
|
|
|
if (result.IsReject()) {
|
|
promiseHolder->MaybeReject(result.RejectValue());
|
|
} else {
|
|
promiseHolder->MaybeResolveWithUndefined();
|
|
}
|
|
})
|
|
->Track(*requestHolder);
|
|
|
|
promise.forget(aPromise);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsToolkitProfileService::Flush() {
|
|
nsCString profilesIniData;
|
|
nsCString installsIniData;
|
|
|
|
BuildIniData(profilesIniData, installsIniData);
|
|
return FlushData(profilesIniData, installsIniData);
|
|
}
|
|
|
|
nsresult nsToolkitProfileService::GetLocalDirFromRootDir(nsIFile* aRootDir,
|
|
nsIFile** aResult) {
|
|
NS_ASSERTION(nsToolkitProfileService::gService, "Where did my service go?");
|
|
nsCString path;
|
|
bool isRelative;
|
|
nsresult rv = nsToolkitProfileService::gService->GetProfileDescriptor(
|
|
aRootDir, path, &isRelative);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIFile> localDir;
|
|
if (isRelative) {
|
|
rv = NS_NewNativeLocalFile(""_ns, getter_AddRefs(localDir));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = localDir->SetRelativeDescriptor(
|
|
nsToolkitProfileService::gService->mTempData, path);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
} else {
|
|
localDir = aRootDir;
|
|
}
|
|
|
|
localDir.forget(aResult);
|
|
|
|
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, 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), 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), aResult);
|
|
|
|
#else
|
|
# error Platform-specific logic needed here.
|
|
#endif
|
|
}
|