forked from mirrors/gecko-dev
		
	Backed out changeset aa7807c7e488 (bug 1555319) Backed out changeset 1a595782402c (bug 1555319)
		
			
				
	
	
		
			2051 lines
		
	
	
	
		
			61 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2051 lines
		
	
	
	
		
			61 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/UniquePtr.h"
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <prprf.h>
 | 
						|
#include <prtime.h>
 | 
						|
 | 
						|
#ifdef XP_WIN
 | 
						|
#  include <windows.h>
 | 
						|
#  include <shlobj.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"
 | 
						|
 | 
						|
using namespace mozilla;
 | 
						|
 | 
						|
#define DEV_EDITION_NAME "dev-edition-default"
 | 
						|
#define DEFAULT_NAME "default"
 | 
						|
#define COMPAT_FILE NS_LITERAL_STRING("compatibility.ini")
 | 
						|
#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 RemoveProfileFiles(nsIToolkitProfile* aProfile, bool aInBackground) {
 | 
						|
  nsCOMPtr<nsIFile> rootDir;
 | 
						|
  aProfile->GetRootDir(getter_AddRefs(rootDir));
 | 
						|
  nsCOMPtr<nsIFile> localDir;
 | 
						|
  aProfile->GetLocalDir(getter_AddRefs(localDir));
 | 
						|
 | 
						|
  // 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;
 | 
						|
  nsresult rv =
 | 
						|
      NS_LockProfilePath(rootDir, localDir, nullptr, getter_AddRefs(lock));
 | 
						|
  NS_ENSURE_SUCCESS_VOID(rv);
 | 
						|
 | 
						|
  nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
 | 
						|
      "nsToolkitProfile::RemoveProfileFiles",
 | 
						|
      [rootDir, localDir, lock]() mutable {
 | 
						|
        bool equals;
 | 
						|
        nsresult rv = rootDir->Equals(localDir, &equals);
 | 
						|
        // The root dir might contain the temp dir, so remove
 | 
						|
        // the temp dir first.
 | 
						|
        if (NS_SUCCEEDED(rv) && !equals) {
 | 
						|
          localDir->Remove(true);
 | 
						|
        }
 | 
						|
 | 
						|
        // Ideally we'd unlock after deleting but since the lock is a file
 | 
						|
        // in the profile we must unlock before removing.
 | 
						|
        lock->Unlock();
 | 
						|
        // nsIProfileLock is not threadsafe so release our reference to it on
 | 
						|
        // the main thread.
 | 
						|
        NS_ReleaseOnMainThreadSystemGroup(
 | 
						|
            "nsToolkitProfile::RemoveProfileFiles::Unlock", lock.forget());
 | 
						|
 | 
						|
        rv = rootDir->Remove(true);
 | 
						|
        NS_ENSURE_SUCCESS_VOID(rv);
 | 
						|
      });
 | 
						|
 | 
						|
  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();
 | 
						|
  if (!lock) return NS_ERROR_OUT_OF_MEMORY;
 | 
						|
 | 
						|
  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;
 | 
						|
  }
 | 
						|
 | 
						|
  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
 | 
						|
      mCreatedAlternateProfile(false),
 | 
						|
      mStartupReason(NS_LITERAL_STRING("unknown")),
 | 
						|
      mMaybeLockProfile(false),
 | 
						|
      mUpdateChannel(NS_STRINGIFY(MOZ_UPDATE_CHANNEL)),
 | 
						|
      mProfileDBExists(false),
 | 
						|
      mProfileDBFileSize(0),
 | 
						|
      mProfileDBModifiedTime(0),
 | 
						|
      mInstallDBExists(false),
 | 
						|
      mInstallDBFileSize(0),
 | 
						|
      mInstallDBModifiedTime(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 greDirPath;
 | 
						|
  rv = compatData.GetString("Compatibility", "LastPlatformDir", greDirPath);
 | 
						|
  // 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> greDir;
 | 
						|
  rv = NS_NewNativeLocalFile(EmptyCString(), false, getter_AddRefs(greDir));
 | 
						|
  NS_ENSURE_SUCCESS(rv, false);
 | 
						|
 | 
						|
  rv = greDir->SetPersistentDescriptor(greDirPath);
 | 
						|
  NS_ENSURE_SUCCESS(rv, false);
 | 
						|
 | 
						|
  bool equal;
 | 
						|
  rv = greDir->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;
 | 
						|
  }
 | 
						|
 | 
						|
  if (IsFileOutdated(mInstallDBFile, mInstallDBExists, mInstallDBModifiedTime,
 | 
						|
                     mInstallDBFileSize)) {
 | 
						|
    *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(NS_LITERAL_CSTRING("profiles.ini"));
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  rv = mAppData->Clone(getter_AddRefs(mInstallDBFile));
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  rv = mInstallDBFile->AppendNative(NS_LITERAL_CSTRING("installs.ini"));
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  rv = UpdateFileStats(mInstallDBFile, &mInstallDBExists,
 | 
						|
                       &mInstallDBModifiedTime, &mInstallDBFileSize);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  nsAutoCString buffer;
 | 
						|
 | 
						|
  rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
 | 
						|
                       &mProfileDBModifiedTime, &mProfileDBFileSize);
 | 
						|
  if (NS_SUCCEEDED(rv) && mProfileDBExists) {
 | 
						|
    mProfileDBFile->GetFileSize(&mProfileDBFileSize);
 | 
						|
    mProfileDBFile->GetLastModifiedTime(&mProfileDBModifiedTime);
 | 
						|
 | 
						|
    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 (mInstallDBExists && 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(
 | 
						|
      NS_LITERAL_CSTRING("ignore-dev-edition-profile"));
 | 
						|
  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(EmptyCString(), 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(EmptyCString(), true, getter_AddRefs(localDir));
 | 
						|
      NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
      rv = localDir->SetRelativeDescriptor(mTempData, filePath);
 | 
						|
    } else {
 | 
						|
      localDir = rootDir;
 | 
						|
    }
 | 
						|
 | 
						|
    currentProfile = new nsToolkitProfile(name, rootDir, localDir, true);
 | 
						|
    NS_ENSURE_TRUE(currentProfile, NS_ERROR_OUT_OF_MEMORY);
 | 
						|
 | 
						|
    // 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) {
 | 
						|
    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());
 | 
						|
  if (!*aResult) return NS_ERROR_OUT_OF_MEMORY;
 | 
						|
 | 
						|
  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;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
nsToolkitProfileService::GetCreatedAlternateProfile(bool* aResult) {
 | 
						|
  *aResult = mCreatedAlternateProfile;
 | 
						|
  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 = NS_LITERAL_STRING("restart-claimed-default");
 | 
						|
 | 
						|
          mCurrent = profile;
 | 
						|
        } else {
 | 
						|
          rv = CreateDefaultProfile(getter_AddRefs(mCurrent));
 | 
						|
          if (NS_FAILED(rv)) {
 | 
						|
            *aProfile = nullptr;
 | 
						|
            return rv;
 | 
						|
          }
 | 
						|
 | 
						|
          rv = Flush();
 | 
						|
          NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
          mStartupReason = NS_LITERAL_STRING("restart-skipped-default");
 | 
						|
          *aDidCreate = true;
 | 
						|
          mCreatedAlternateProfile = true;
 | 
						|
        }
 | 
						|
 | 
						|
        NS_IF_ADDREF(*aProfile = mCurrent);
 | 
						|
        mCurrent->GetRootDir(aRootDir);
 | 
						|
        mCurrent->GetLocalDir(aLocalDir);
 | 
						|
 | 
						|
        return NS_OK;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (EnvHasValue("XRE_RESTARTED_BY_PROFILE_MANAGER")) {
 | 
						|
      mStartupReason = NS_LITERAL_STRING("profile-manager");
 | 
						|
    } else if (aIsResetting) {
 | 
						|
      mStartupReason = NS_LITERAL_STRING("profile-reset");
 | 
						|
    } else {
 | 
						|
      mStartupReason = NS_LITERAL_STRING("restart");
 | 
						|
    }
 | 
						|
 | 
						|
    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,
 | 
						|
                          CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg);
 | 
						|
  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 = NS_LITERAL_STRING("argument-profile");
 | 
						|
 | 
						|
    // If a profile path is specified directly on the command line, then
 | 
						|
    // assume that the temp directory is the same as the given directory.
 | 
						|
    GetProfileByDir(lf, lf, getter_AddRefs(mCurrent));
 | 
						|
    NS_ADDREF(*aRootDir = lf);
 | 
						|
    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::CheckOSInt | 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 = NS_LITERAL_STRING("argument-p");
 | 
						|
 | 
						|
      mCurrent->GetRootDir(aRootDir);
 | 
						|
      mCurrent->GetLocalDir(aLocalDir);
 | 
						|
 | 
						|
      NS_ADDREF(*aProfile = mCurrent);
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    return NS_ERROR_SHOW_PROFILE_MANAGER;
 | 
						|
  }
 | 
						|
 | 
						|
  ar = CheckArg(*aArgc, aArgv, "profilemanager", (const char**)nullptr,
 | 
						|
                CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg);
 | 
						|
  if (ar == ARG_BAD) {
 | 
						|
    PR_fprintf(PR_STDERR,
 | 
						|
               "Error: argument --profilemanager is invalid when argument "
 | 
						|
               "--osint is specified\n");
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  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 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;
 | 
						|
    }
 | 
						|
 | 
						|
    if (mUseDedicatedProfile) {
 | 
						|
      // This is the first run of a dedicated profile install. We have to decide
 | 
						|
      // whether to use the default profile used by non-dedicated-profile
 | 
						|
      // installs or to create a new profile.
 | 
						|
 | 
						|
      // Find what would have been the default profile for old installs.
 | 
						|
      nsCOMPtr<nsIToolkitProfile> profile = mNormalDefault;
 | 
						|
      if (mUseDevEditionProfile) {
 | 
						|
        profile = mDevEditionDefault;
 | 
						|
      }
 | 
						|
 | 
						|
      if (profile) {
 | 
						|
        nsCOMPtr<nsIFile> rootDir;
 | 
						|
        profile->GetRootDir(getter_AddRefs(rootDir));
 | 
						|
 | 
						|
        nsCOMPtr<nsIFile> compat;
 | 
						|
        rootDir->Clone(getter_AddRefs(compat));
 | 
						|
        compat->Append(COMPAT_FILE);
 | 
						|
 | 
						|
        bool exists;
 | 
						|
        rv = compat->Exists(&exists);
 | 
						|
        NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
        // If the file is missing then either this is an empty profile (likely
 | 
						|
        // generated by bug 1518591) or it is from an ancient version. We'll opt
 | 
						|
        // to leave it for older versions in this case.
 | 
						|
        if (exists) {
 | 
						|
          bool result;
 | 
						|
          rv = MaybeMakeDefaultDedicatedProfile(profile, &result);
 | 
						|
          NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
          if (result) {
 | 
						|
            mStartupReason = NS_LITERAL_STRING("firstrun-claimed-default");
 | 
						|
 | 
						|
            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. If there was
 | 
						|
          // a potential previous default to use then the user may be confused
 | 
						|
          // over why we're not using that anymore so set a flag for the front
 | 
						|
          // end to use to notify the user about what has happened.
 | 
						|
          mCreatedAlternateProfile = true;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    rv = CreateDefaultProfile(getter_AddRefs(mCurrent));
 | 
						|
    if (NS_SUCCEEDED(rv)) {
 | 
						|
      // If there is only one profile and it isn't meant to be the profile that
 | 
						|
      // older versions of Firefox use then we must create a default profile
 | 
						|
      // for older versions of Firefox to avoid the existing profile being
 | 
						|
      // auto-selected.
 | 
						|
      if ((mUseDedicatedProfile || mUseDevEditionProfile) &&
 | 
						|
          mProfiles.getFirst() == mProfiles.getLast()) {
 | 
						|
        nsCOMPtr<nsIToolkitProfile> newProfile;
 | 
						|
        CreateProfile(nullptr, NS_LITERAL_CSTRING(DEFAULT_NAME),
 | 
						|
                      getter_AddRefs(newProfile));
 | 
						|
        SetNormalDefault(newProfile);
 | 
						|
      }
 | 
						|
 | 
						|
      rv = Flush();
 | 
						|
      NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
      if (mCreatedAlternateProfile) {
 | 
						|
        mStartupReason = NS_LITERAL_STRING("firstrun-skipped-default");
 | 
						|
      } else {
 | 
						|
        mStartupReason = NS_LITERAL_STRING("firstrun-created-default");
 | 
						|
      }
 | 
						|
 | 
						|
      // Use the new profile.
 | 
						|
      mCurrent->GetRootDir(aRootDir);
 | 
						|
      mCurrent->GetLocalDir(aLocalDir);
 | 
						|
      NS_ADDREF(*aProfile = mCurrent);
 | 
						|
 | 
						|
      *aDidCreate = true;
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  GetDefaultProfile(getter_AddRefs(mCurrent));
 | 
						|
 | 
						|
  // None of the profiles was marked as default (generally only happens if the
 | 
						|
  // user modifies profiles.ini manually). Let the user choose.
 | 
						|
  if (!mCurrent) {
 | 
						|
    return NS_ERROR_SHOW_PROFILE_MANAGER;
 | 
						|
  }
 | 
						|
 | 
						|
  // Let the caller know that the profile was selected by default.
 | 
						|
  *aWasDefaultSelection = true;
 | 
						|
  mStartupReason = NS_LITERAL_STRING("default");
 | 
						|
 | 
						|
  // Use the selected profile.
 | 
						|
  mCurrent->GetRootDir(aRootDir);
 | 
						|
  mCurrent->GetLocalDir(aLocalDir);
 | 
						|
  NS_ADDREF(*aProfile = mCurrent);
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Creates a new profile for reset and mark it as the current profile.
 | 
						|
 */
 | 
						|
nsresult nsToolkitProfileService::CreateResetProfile(
 | 
						|
    nsIToolkitProfile** aNewProfile) {
 | 
						|
  nsAutoCString oldProfileName;
 | 
						|
  mCurrent->GetName(oldProfileName);
 | 
						|
 | 
						|
  nsCOMPtr<nsIToolkitProfile> newProfile;
 | 
						|
  // Make the new profile name the old profile (or "default-") + the time in
 | 
						|
  // seconds since epoch for uniqueness.
 | 
						|
  nsAutoCString newProfileName;
 | 
						|
  if (!oldProfileName.IsEmpty()) {
 | 
						|
    newProfileName.Assign(oldProfileName);
 | 
						|
    newProfileName.Append("-");
 | 
						|
  } else {
 | 
						|
    newProfileName.AssignLiteral("default-");
 | 
						|
  }
 | 
						|
  newProfileName.AppendPrintf("%" PRId64, PR_Now() / 1000);
 | 
						|
  nsresult rv = CreateProfile(nullptr,  // choose a default dir for us
 | 
						|
                              newProfileName, getter_AddRefs(newProfile));
 | 
						|
  if (NS_FAILED(rv)) return rv;
 | 
						|
 | 
						|
  mCurrent = newProfile;
 | 
						|
  newProfile.forget(aNewProfile);
 | 
						|
 | 
						|
  // Don't flush the changes yet. That will happen once the migration
 | 
						|
  // successfully completes.
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * This is responsible for deleting the old profile, copying its name to the
 | 
						|
 * current profile and if the old profile was default making the new profile
 | 
						|
 * default as well.
 | 
						|
 */
 | 
						|
nsresult nsToolkitProfileService::ApplyResetProfile(
 | 
						|
    nsIToolkitProfile* aOldProfile) {
 | 
						|
  // If the old profile would have been the default for old installs then mark
 | 
						|
  // the new profile as such.
 | 
						|
  if (mNormalDefault == aOldProfile) {
 | 
						|
    SetNormalDefault(mCurrent);
 | 
						|
  }
 | 
						|
 | 
						|
  if (mUseDedicatedProfile && mDedicatedProfile == aOldProfile) {
 | 
						|
    bool wasLocked = false;
 | 
						|
    nsCString val;
 | 
						|
    if (NS_SUCCEEDED(
 | 
						|
            mProfileDB.GetString(mInstallSection.get(), "Locked", val))) {
 | 
						|
      wasLocked = val.Equals("1");
 | 
						|
    }
 | 
						|
 | 
						|
    SetDefaultProfile(mCurrent);
 | 
						|
 | 
						|
    // Make the locked state match if necessary.
 | 
						|
    if (!wasLocked) {
 | 
						|
      mProfileDB.DeleteString(mInstallSection.get(), "Locked");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  nsCString name;
 | 
						|
  nsresult rv = aOldProfile->GetName(name);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  // Don't remove the old profile's files until after we've successfully flushed
 | 
						|
  // the profile changes to disk.
 | 
						|
  rv = aOldProfile->Remove(false);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  // Switching the name will make this the default for dev-edition if
 | 
						|
  // appropriate.
 | 
						|
  rv = mCurrent->SetName(name);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  rv = Flush();
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  // Now that the profile changes are flushed, try to remove the old profile's
 | 
						|
  // files. If we fail the worst that will happen is that an orphan directory is
 | 
						|
  // left. Let this run in the background while we start up.
 | 
						|
  RemoveProfileFiles(aOldProfile, true);
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
nsToolkitProfileService::GetProfileByName(const nsACString& aName,
 | 
						|
                                          nsIToolkitProfile** aResult) {
 | 
						|
  for (RefPtr<nsToolkitProfile> profile : mProfiles) {
 | 
						|
    if (profile->mName.Equals(aName)) {
 | 
						|
      NS_ADDREF(*aResult = profile);
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_ERROR_FAILURE;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Finds a profile from the database that uses the given root and local
 | 
						|
 * directories.
 | 
						|
 */
 | 
						|
void nsToolkitProfileService::GetProfileByDir(nsIFile* aRootDir,
 | 
						|
                                              nsIFile* aLocalDir,
 | 
						|
                                              nsIToolkitProfile** aResult) {
 | 
						|
  for (RefPtr<nsToolkitProfile> profile : mProfiles) {
 | 
						|
    bool equal;
 | 
						|
    nsresult rv = profile->mRootDir->Equals(aRootDir, &equal);
 | 
						|
    if (NS_SUCCEEDED(rv) && equal) {
 | 
						|
      rv = profile->mLocalDir->Equals(aLocalDir, &equal);
 | 
						|
      if (NS_SUCCEEDED(rv) && equal) {
 | 
						|
        NS_ADDREF(*aResult = profile);
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
nsresult NS_LockProfilePath(nsIFile* aPath, nsIFile* aTempPath,
 | 
						|
                            nsIProfileUnlocker** aUnlocker,
 | 
						|
                            nsIProfileLock** aResult) {
 | 
						|
  RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
 | 
						|
  if (!lock) return NS_ERROR_OUT_OF_MEMORY;
 | 
						|
 | 
						|
  nsresult rv = lock->Init(aPath, aTempPath, aUnlocker);
 | 
						|
  if (NS_FAILED(rv)) return rv;
 | 
						|
 | 
						|
  lock.forget(aResult);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
static void SaltProfileName(nsACString& aName) {
 | 
						|
  char salt[9];
 | 
						|
  NS_MakeRandomString(salt, 8);
 | 
						|
  salt[8] = '.';
 | 
						|
 | 
						|
  aName.Insert(salt, 0, 9);
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
nsToolkitProfileService::CreateUniqueProfile(nsIFile* aRootDir,
 | 
						|
                                             const nsACString& aNamePrefix,
 | 
						|
                                             nsIToolkitProfile** aResult) {
 | 
						|
  nsCOMPtr<nsIToolkitProfile> profile;
 | 
						|
  nsresult rv = GetProfileByName(aNamePrefix, getter_AddRefs(profile));
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    return CreateProfile(aRootDir, aNamePrefix, aResult);
 | 
						|
  }
 | 
						|
 | 
						|
  uint32_t suffix = 1;
 | 
						|
  while (true) {
 | 
						|
    nsPrintfCString name("%s-%d", PromiseFlatCString(aNamePrefix).get(),
 | 
						|
                         suffix);
 | 
						|
    rv = GetProfileByName(name, getter_AddRefs(profile));
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      return CreateProfile(aRootDir, name, aResult);
 | 
						|
    }
 | 
						|
    suffix++;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
 | 
						|
                                       const nsACString& aName,
 | 
						|
                                       nsIToolkitProfile** aResult) {
 | 
						|
  nsresult rv = GetProfileByName(aName, aResult);
 | 
						|
  if (NS_SUCCEEDED(rv)) {
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  nsCOMPtr<nsIFile> rootDir(aRootDir);
 | 
						|
 | 
						|
  nsAutoCString dirName;
 | 
						|
  if (!rootDir) {
 | 
						|
    rv = gDirServiceProvider->GetUserProfilesRootDir(getter_AddRefs(rootDir));
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
    dirName = aName;
 | 
						|
    SaltProfileName(dirName);
 | 
						|
 | 
						|
    if (NS_IsNativeUTF8()) {
 | 
						|
      rootDir->AppendNative(dirName);
 | 
						|
    } else {
 | 
						|
      rootDir->Append(NS_ConvertUTF8toUTF16(dirName));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  nsCOMPtr<nsIFile> localDir;
 | 
						|
 | 
						|
  bool isRelative;
 | 
						|
  rv = mAppData->Contains(rootDir, &isRelative);
 | 
						|
  if (NS_SUCCEEDED(rv) && isRelative) {
 | 
						|
    nsAutoCString path;
 | 
						|
    rv = rootDir->GetRelativeDescriptor(mAppData, path);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
    rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(localDir));
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
    rv = localDir->SetRelativeDescriptor(mTempData, path);
 | 
						|
  } else {
 | 
						|
    localDir = rootDir;
 | 
						|
  }
 | 
						|
 | 
						|
  bool exists;
 | 
						|
  rv = rootDir->Exists(&exists);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  if (exists) {
 | 
						|
    rv = rootDir->IsDirectory(&exists);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
    if (!exists) return NS_ERROR_FILE_NOT_DIRECTORY;
 | 
						|
  } else {
 | 
						|
    nsCOMPtr<nsIFile> profileDirParent;
 | 
						|
    nsAutoString profileDirName;
 | 
						|
 | 
						|
    rv = rootDir->GetParent(getter_AddRefs(profileDirParent));
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
    rv = rootDir->GetLeafName(profileDirName);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
    // let's ensure that the profile directory exists.
 | 
						|
    rv = rootDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
    rv = rootDir->SetPermissions(0700);
 | 
						|
#ifndef ANDROID
 | 
						|
    // If the profile is on the sdcard, this will fail but its non-fatal
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
#endif
 | 
						|
  }
 | 
						|
 | 
						|
  rv = localDir->Exists(&exists);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  if (!exists) {
 | 
						|
    rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  }
 | 
						|
 | 
						|
  // We created a new profile dir. Let's store a creation timestamp.
 | 
						|
  // Note that this code path does not apply if the profile dir was
 | 
						|
  // created prior to launching.
 | 
						|
  rv = CreateTimesInternal(rootDir);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  nsCOMPtr<nsIToolkitProfile> profile =
 | 
						|
      new nsToolkitProfile(aName, rootDir, localDir, false);
 | 
						|
  if (!profile) return NS_ERROR_OUT_OF_MEMORY;
 | 
						|
 | 
						|
  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() {
 | 
						|
  return !!PR_GetEnv("SNAP_NAME");
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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.
 | 
						|
 */
 | 
						|
bool nsToolkitProfileService::UseLegacyProfiles() {
 | 
						|
  return !!PR_GetEnv("MOZ_LEGACY_PROFILES");
 | 
						|
}
 | 
						|
 | 
						|
struct FindInstallsClosure {
 | 
						|
  nsINIParser* installData;
 | 
						|
  nsTArray<nsCString>* installs;
 | 
						|
};
 | 
						|
 | 
						|
static bool FindInstalls(const char* aSection, void* aClosure) {
 | 
						|
  FindInstallsClosure* closure = static_cast<FindInstallsClosure*>(aClosure);
 | 
						|
 | 
						|
  // Check if the section starts with "Install"
 | 
						|
  if (strncmp(aSection, INSTALL_PREFIX, INSTALL_PREFIX_LENGTH) != 0) {
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  nsCString install(aSection);
 | 
						|
  closure->installs->AppendElement(install);
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
nsTArray<nsCString> nsToolkitProfileService::GetKnownInstalls() {
 | 
						|
  nsTArray<nsCString> result;
 | 
						|
  FindInstallsClosure closure = {&mProfileDB, &result};
 | 
						|
 | 
						|
  mProfileDB.GetSections(&FindInstalls, &closure);
 | 
						|
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
nsresult nsToolkitProfileService::CreateTimesInternal(nsIFile* aProfileDir) {
 | 
						|
  nsresult rv = NS_ERROR_FAILURE;
 | 
						|
  nsCOMPtr<nsIFile> creationLog;
 | 
						|
  rv = aProfileDir->Clone(getter_AddRefs(creationLog));
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  rv = creationLog->AppendNative(NS_LITERAL_CSTRING("times.json"));
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  bool exists = false;
 | 
						|
  creationLog->Exists(&exists);
 | 
						|
  if (exists) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  rv = creationLog->Create(nsIFile::NORMAL_FILE_TYPE, 0700);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  // We don't care about microsecond resolution.
 | 
						|
  int64_t msec = PR_Now() / PR_USEC_PER_MSEC;
 | 
						|
 | 
						|
  // Write it out.
 | 
						|
  PRFileDesc* writeFile;
 | 
						|
  rv = creationLog->OpenNSPRFileDesc(PR_WRONLY, 0700, &writeFile);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  PR_fprintf(writeFile, "{\n\"created\": %lld,\n\"firstUse\": null\n}\n", msec);
 | 
						|
  PR_Close(writeFile);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
nsToolkitProfileService::GetProfileCount(uint32_t* aResult) {
 | 
						|
  *aResult = 0;
 | 
						|
  for (nsToolkitProfile* profile : mProfiles) {
 | 
						|
    Unused << profile;
 | 
						|
    (*aResult)++;
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
nsToolkitProfileService::Flush() {
 | 
						|
  if (GetIsListOutdated()) {
 | 
						|
    return NS_ERROR_DATABASE_CHANGED;
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult rv;
 | 
						|
 | 
						|
  // If we aren't using dedicated profiles then nothing about the list of
 | 
						|
  // installs can have changed, so no need to update the backup.
 | 
						|
  if (mUseDedicatedProfile) {
 | 
						|
    // Export the installs to the backup.
 | 
						|
    nsTArray<nsCString> installs = GetKnownInstalls();
 | 
						|
 | 
						|
    if (!installs.IsEmpty()) {
 | 
						|
      nsCString data;
 | 
						|
      nsCString buffer;
 | 
						|
 | 
						|
      for (uint32_t i = 0; i < installs.Length(); i++) {
 | 
						|
        nsTArray<UniquePtr<KeyValue>> strings =
 | 
						|
            GetSectionStrings(&mProfileDB, installs[i].get());
 | 
						|
        if (strings.IsEmpty()) {
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
 | 
						|
        // Strip "Install" from the start.
 | 
						|
        const nsDependentCSubstring& install =
 | 
						|
            Substring(installs[i], INSTALL_PREFIX_LENGTH);
 | 
						|
        data.AppendPrintf("[%s]\n", PromiseFlatCString(install).get());
 | 
						|
 | 
						|
        for (uint32_t j = 0; j < strings.Length(); j++) {
 | 
						|
          data.AppendPrintf("%s=%s\n", strings[j]->key.get(),
 | 
						|
                            strings[j]->value.get());
 | 
						|
        }
 | 
						|
 | 
						|
        data.Append("\n");
 | 
						|
      }
 | 
						|
 | 
						|
      FILE* writeFile;
 | 
						|
      rv = mInstallDBFile->OpenANSIFileDesc("w", &writeFile);
 | 
						|
      NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
      uint32_t length = data.Length();
 | 
						|
      if (fwrite(data.get(), sizeof(char), length, writeFile) != length) {
 | 
						|
        fclose(writeFile);
 | 
						|
        return NS_ERROR_UNEXPECTED;
 | 
						|
      }
 | 
						|
 | 
						|
      fclose(writeFile);
 | 
						|
 | 
						|
      rv = UpdateFileStats(mInstallDBFile, &mInstallDBExists,
 | 
						|
                           &mInstallDBModifiedTime, &mInstallDBFileSize);
 | 
						|
      NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
    } else {
 | 
						|
      rv = mInstallDBFile->Remove(false);
 | 
						|
      if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
 | 
						|
          rv != NS_ERROR_FILE_NOT_FOUND) {
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
      mInstallDBExists = false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  rv = mProfileDB.WriteToFile(mProfileDBFile);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
 | 
						|
                       &mProfileDBModifiedTime, &mProfileDBFileSize);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMPL_ISUPPORTS(nsToolkitProfileFactory, nsIFactory)
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
nsToolkitProfileFactory::CreateInstance(nsISupports* aOuter, const nsID& aIID,
 | 
						|
                                        void** aResult) {
 | 
						|
  if (aOuter) return NS_ERROR_NO_AGGREGATION;
 | 
						|
 | 
						|
  RefPtr<nsToolkitProfileService> profileService =
 | 
						|
      nsToolkitProfileService::gService;
 | 
						|
  if (!profileService) {
 | 
						|
    nsresult rv = NS_NewToolkitProfileService(getter_AddRefs(profileService));
 | 
						|
    if (NS_FAILED(rv)) return rv;
 | 
						|
  }
 | 
						|
  return profileService->QueryInterface(aIID, aResult);
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
nsToolkitProfileFactory::LockFactory(bool aVal) { return NS_OK; }
 | 
						|
 | 
						|
nsresult NS_NewToolkitProfileFactory(nsIFactory** aResult) {
 | 
						|
  *aResult = new nsToolkitProfileFactory();
 | 
						|
  if (!*aResult) return NS_ERROR_OUT_OF_MEMORY;
 | 
						|
 | 
						|
  NS_ADDREF(*aResult);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult NS_NewToolkitProfileService(nsToolkitProfileService** aResult) {
 | 
						|
  nsToolkitProfileService* profileService = new nsToolkitProfileService();
 | 
						|
  if (!profileService) return NS_ERROR_OUT_OF_MEMORY;
 | 
						|
  nsresult rv = profileService->Init();
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_ERROR("nsToolkitProfileService::Init failed!");
 | 
						|
    delete profileService;
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  NS_ADDREF(*aResult = profileService);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
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(EmptyCString(), true, getter_AddRefs(lf));
 | 
						|
  if (NS_SUCCEEDED(rv)) {
 | 
						|
    nsCOMPtr<nsILocalFileMac> lfMac = do_QueryInterface(lf, &rv);
 | 
						|
    if (NS_SUCCEEDED(rv)) {
 | 
						|
      rv = lfMac->InitWithCFURL(fullPath);
 | 
						|
      if (NS_SUCCEEDED(rv)) {
 | 
						|
        lf.forget(aResult);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  CFRelease(fullPath);
 | 
						|
  return rv;
 | 
						|
 | 
						|
#elif defined(XP_UNIX)
 | 
						|
  char fullPath[MAXPATHLEN];
 | 
						|
 | 
						|
  if (!realpath(aPath, fullPath)) return NS_ERROR_FAILURE;
 | 
						|
 | 
						|
  return NS_NewNativeLocalFile(nsDependentCString(fullPath), true, aResult);
 | 
						|
#elif defined(XP_WIN)
 | 
						|
  WCHAR fullPath[MAXPATHLEN];
 | 
						|
 | 
						|
  if (!_wfullpath(fullPath, NS_ConvertUTF8toUTF16(aPath).get(), MAXPATHLEN))
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
 | 
						|
  return NS_NewLocalFile(nsDependentString(fullPath), true, aResult);
 | 
						|
 | 
						|
#else
 | 
						|
#  error Platform-specific logic needed here.
 | 
						|
#endif
 | 
						|
}
 |