summaryrefslogtreecommitdiff
path: root/components/profile/src
diff options
context:
space:
mode:
Diffstat (limited to 'components/profile/src')
-rw-r--r--components/profile/src/ProfileUnlockerWin.cpp277
-rw-r--r--components/profile/src/ProfileUnlockerWin.h59
-rw-r--r--components/profile/src/nsProfileLock.cpp576
-rw-r--r--components/profile/src/nsProfileLock.h95
-rw-r--r--components/profile/src/nsProfileStringTypes.h32
-rw-r--r--components/profile/src/nsToolkitProfileService.cpp1043
6 files changed, 2082 insertions, 0 deletions
diff --git a/components/profile/src/ProfileUnlockerWin.cpp b/components/profile/src/ProfileUnlockerWin.cpp
new file mode 100644
index 000000000..4e0ed7196
--- /dev/null
+++ b/components/profile/src/ProfileUnlockerWin.cpp
@@ -0,0 +1,277 @@
+/* -*- 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 "ProfileUnlockerWin.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+#include "nsXPCOM.h"
+
+namespace mozilla {
+
+/**
+ * RAII class to obtain and manage a handle to a Restart Manager session.
+ * It opens a new handle upon construction and releases it upon destruction.
+ */
+class MOZ_STACK_CLASS ScopedRestartManagerSession
+{
+public:
+ explicit ScopedRestartManagerSession(ProfileUnlockerWin& aUnlocker)
+ : mError(ERROR_INVALID_HANDLE)
+ , mHandle((DWORD)-1) // 0 is a valid restart manager handle
+ , mUnlocker(aUnlocker)
+ {
+ mError = mUnlocker.StartSession(mHandle);
+ }
+
+ ~ScopedRestartManagerSession()
+ {
+ if (mError == ERROR_SUCCESS) {
+ mUnlocker.EndSession(mHandle);
+ }
+ }
+
+ /**
+ * @return true if the handle is a valid Restart Ranager handle.
+ */
+ inline bool
+ ok()
+ {
+ return mError == ERROR_SUCCESS;
+ }
+
+ /**
+ * @return the Restart Manager handle to pass to other Restart Manager APIs.
+ */
+ inline DWORD
+ handle()
+ {
+ return mHandle;
+ }
+
+private:
+ DWORD mError;
+ DWORD mHandle;
+ ProfileUnlockerWin& mUnlocker;
+};
+
+ProfileUnlockerWin::ProfileUnlockerWin(const nsAString& aFileName)
+ : mRmStartSession(nullptr)
+ , mRmRegisterResources(nullptr)
+ , mRmGetList(nullptr)
+ , mRmEndSession(nullptr)
+ , mQueryFullProcessImageName(nullptr)
+ , mFileName(aFileName)
+{
+}
+
+ProfileUnlockerWin::~ProfileUnlockerWin()
+{
+}
+
+NS_IMPL_ISUPPORTS(ProfileUnlockerWin, nsIProfileUnlocker)
+
+nsresult
+ProfileUnlockerWin::Init()
+{
+ MOZ_ASSERT(!mRestartMgrModule);
+ if (mFileName.IsEmpty()) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsModuleHandle module(::LoadLibraryW(L"Rstrtmgr.dll"));
+ if (!module) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ mRmStartSession =
+ reinterpret_cast<RMSTARTSESSION>(::GetProcAddress(module, "RmStartSession"));
+ if (!mRmStartSession) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mRmRegisterResources =
+ reinterpret_cast<RMREGISTERRESOURCES>(::GetProcAddress(module,
+ "RmRegisterResources"));
+ if (!mRmRegisterResources) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mRmGetList = reinterpret_cast<RMGETLIST>(::GetProcAddress(module,
+ "RmGetList"));
+ if (!mRmGetList) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mRmEndSession = reinterpret_cast<RMENDSESSION>(::GetProcAddress(module,
+ "RmEndSession"));
+ if (!mRmEndSession) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mQueryFullProcessImageName =
+ reinterpret_cast<QUERYFULLPROCESSIMAGENAME>(::GetProcAddress(
+ ::GetModuleHandleW(L"kernel32.dll"),
+ "QueryFullProcessImageNameW"));
+ if (!mQueryFullProcessImageName) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mRestartMgrModule.steal(module);
+ return NS_OK;
+}
+
+DWORD
+ProfileUnlockerWin::StartSession(DWORD& aHandle)
+{
+ WCHAR sessionKey[CCH_RM_SESSION_KEY + 1] = {0};
+ return mRmStartSession(&aHandle, 0, sessionKey);
+}
+
+void
+ProfileUnlockerWin::EndSession(DWORD aHandle)
+{
+ mRmEndSession(aHandle);
+}
+
+NS_IMETHODIMP
+ProfileUnlockerWin::Unlock(uint32_t aSeverity)
+{
+ if (!mRestartMgrModule) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (aSeverity != FORCE_QUIT) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ ScopedRestartManagerSession session(*this);
+ if (!session.ok()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LPCWSTR resources[] = { mFileName.get() };
+ DWORD error = mRmRegisterResources(session.handle(), 1, resources, 0, nullptr,
+ 0, nullptr);
+ if (error != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Using a AutoTArray here because we expect the required size to be 1.
+ AutoTArray<RM_PROCESS_INFO, 1> info;
+ UINT numEntries;
+ UINT numEntriesNeeded = 1;
+ error = ERROR_MORE_DATA;
+ DWORD reason = RmRebootReasonNone;
+ while (error == ERROR_MORE_DATA) {
+ info.SetLength(numEntriesNeeded);
+ numEntries = numEntriesNeeded;
+ error = mRmGetList(session.handle(), &numEntriesNeeded, &numEntries,
+ &info[0], &reason);
+ }
+ if (error != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ if (numEntries == 0) {
+ // Nobody else is locking the file; the other process must have terminated
+ return NS_OK;
+ }
+
+ nsresult rv = NS_ERROR_FAILURE;
+ for (UINT i = 0; i < numEntries; ++i) {
+ rv = TryToTerminate(info[i].Process);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ }
+
+ // If nothing could be unlocked then we return the error code of the last
+ // failure that was returned.
+ return rv;
+}
+
+nsresult
+ProfileUnlockerWin::TryToTerminate(RM_UNIQUE_PROCESS& aProcess)
+{
+ // Subtle: If the target process terminated before this call to OpenProcess,
+ // this call will still succeed. This is because the restart manager session
+ // internally retains a handle to the target process. The rules for Windows
+ // PIDs state that the PID of a terminated process remains valid as long as
+ // at least one handle to that process remains open, so when we reach this
+ // point the PID is still valid and the process will open successfully.
+ DWORD accessRights = PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE;
+ nsAutoHandle otherProcess(::OpenProcess(accessRights, FALSE,
+ aProcess.dwProcessId));
+ if (!otherProcess) {
+ return NS_ERROR_FAILURE;
+ }
+
+ FILETIME creationTime, exitTime, kernelTime, userTime;
+ if (!::GetProcessTimes(otherProcess, &creationTime, &exitTime, &kernelTime,
+ &userTime)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (::CompareFileTime(&aProcess.ProcessStartTime, &creationTime)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ WCHAR imageName[MAX_PATH];
+ DWORD imageNameLen = MAX_PATH;
+ if (!mQueryFullProcessImageName(otherProcess, 0, imageName, &imageNameLen)) {
+ // The error codes for this function are not very descriptive. There are
+ // actually two failure cases here: Either the call failed because the
+ // process is no longer running, or it failed for some other reason. We
+ // need to know which case that is.
+ DWORD otherProcessExitCode;
+ if (!::GetExitCodeProcess(otherProcess, &otherProcessExitCode) ||
+ otherProcessExitCode == STILL_ACTIVE) {
+ // The other process is still running.
+ return NS_ERROR_FAILURE;
+ }
+ // The other process must have terminated. We should return NS_OK so that
+ // this process may proceed with startup.
+ return NS_OK;
+ }
+ nsCOMPtr<nsIFile> otherProcessImageName;
+ if (NS_FAILED(NS_NewLocalFile(nsDependentString(imageName, imageNameLen),
+ false, getter_AddRefs(otherProcessImageName)))) {
+ return NS_ERROR_FAILURE;
+ }
+ nsAutoString otherProcessLeafName;
+ if (NS_FAILED(otherProcessImageName->GetLeafName(otherProcessLeafName))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ imageNameLen = MAX_PATH;
+ if (!mQueryFullProcessImageName(::GetCurrentProcess(), 0, imageName,
+ &imageNameLen)) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIFile> thisProcessImageName;
+ if (NS_FAILED(NS_NewLocalFile(nsDependentString(imageName, imageNameLen),
+ false, getter_AddRefs(thisProcessImageName)))) {
+ return NS_ERROR_FAILURE;
+ }
+ nsAutoString thisProcessLeafName;
+ if (NS_FAILED(thisProcessImageName->GetLeafName(thisProcessLeafName))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Make sure the image leaf names match
+ if (_wcsicmp(otherProcessLeafName.get(), thisProcessLeafName.get())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // We know that another process holds the lock and that it shares the same
+ // image name as our process. Let's kill it.
+ // Subtle: TerminateProcess returning ERROR_ACCESS_DENIED is actually an
+ // indicator that the target process managed to shut down on its own. In that
+ // case we should return NS_OK since we may proceed with startup.
+ if (!::TerminateProcess(otherProcess, 1) &&
+ ::GetLastError() != ERROR_ACCESS_DENIED) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
+
diff --git a/components/profile/src/ProfileUnlockerWin.h b/components/profile/src/ProfileUnlockerWin.h
new file mode 100644
index 000000000..0a7dff527
--- /dev/null
+++ b/components/profile/src/ProfileUnlockerWin.h
@@ -0,0 +1,59 @@
+/* -*- 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/. */
+
+#ifndef ProfileUnlockerWin_h
+#define ProfileUnlockerWin_h
+
+#include <windows.h>
+#include <restartmanager.h>
+
+#include "nsIProfileUnlocker.h"
+#include "nsProfileStringTypes.h"
+#include "nsWindowsHelpers.h"
+
+namespace mozilla {
+
+class ProfileUnlockerWin final : public nsIProfileUnlocker
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROFILEUNLOCKER
+
+ explicit ProfileUnlockerWin(const nsAString& aFileName);
+
+ nsresult Init();
+
+ DWORD StartSession(DWORD& aHandle);
+ void EndSession(DWORD aHandle);
+
+private:
+ ~ProfileUnlockerWin();
+ nsresult TryToTerminate(RM_UNIQUE_PROCESS& aProcess);
+
+private:
+ typedef DWORD (WINAPI *RMSTARTSESSION)(DWORD*, DWORD, WCHAR[]);
+ typedef DWORD (WINAPI *RMREGISTERRESOURCES)(DWORD, UINT, LPCWSTR[], UINT,
+ RM_UNIQUE_PROCESS[], UINT,
+ LPCWSTR[]);
+ typedef DWORD (WINAPI *RMGETLIST)(DWORD, UINT*, UINT*, RM_PROCESS_INFO[],
+ LPDWORD);
+ typedef DWORD (WINAPI *RMENDSESSION)(DWORD);
+ typedef BOOL (WINAPI *QUERYFULLPROCESSIMAGENAME)(HANDLE, DWORD, LPWSTR, PDWORD);
+
+private:
+ nsModuleHandle mRestartMgrModule;
+ RMSTARTSESSION mRmStartSession;
+ RMREGISTERRESOURCES mRmRegisterResources;
+ RMGETLIST mRmGetList;
+ RMENDSESSION mRmEndSession;
+ QUERYFULLPROCESSIMAGENAME mQueryFullProcessImageName;
+
+ nsString mFileName;
+};
+
+} // namespace mozilla
+
+#endif // ProfileUnlockerWin_h
+
diff --git a/components/profile/src/nsProfileLock.cpp b/components/profile/src/nsProfileLock.cpp
new file mode 100644
index 000000000..5b6fbe0dc
--- /dev/null
+++ b/components/profile/src/nsProfileLock.cpp
@@ -0,0 +1,576 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsProfileStringTypes.h"
+#include "nsProfileLock.h"
+#include "nsCOMPtr.h"
+#include "nsQueryObject.h"
+
+#if defined(XP_WIN)
+#include "ProfileUnlockerWin.h"
+#include "nsAutoPtr.h"
+#endif
+
+#ifdef XP_UNIX
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include "prnetdb.h"
+#include "prsystem.h"
+#include "prprf.h"
+#include "prenv.h"
+#endif
+
+// **********************************************************************
+// class nsProfileLock
+//
+// This code was moved from profile/src/nsProfileAccess.
+// **********************************************************************
+
+#if defined (XP_UNIX)
+static bool sDisableSignalHandling = false;
+#endif
+
+nsProfileLock::nsProfileLock() :
+ mHaveLock(false),
+ mReplacedLockTime(0)
+#if defined (XP_WIN)
+ ,mLockFileHandle(INVALID_HANDLE_VALUE)
+#elif defined (XP_UNIX)
+ ,mPidLockFileName(nullptr)
+ ,mLockFileDesc(-1)
+#endif
+{
+#if defined (XP_UNIX)
+ next = prev = this;
+ sDisableSignalHandling = PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ? true : false;
+#endif
+}
+
+
+nsProfileLock::nsProfileLock(nsProfileLock& src)
+{
+ *this = src;
+}
+
+
+nsProfileLock& nsProfileLock::operator=(nsProfileLock& rhs)
+{
+ Unlock();
+
+ mHaveLock = rhs.mHaveLock;
+ rhs.mHaveLock = false;
+
+#if defined (XP_WIN)
+ mLockFileHandle = rhs.mLockFileHandle;
+ rhs.mLockFileHandle = INVALID_HANDLE_VALUE;
+#elif defined (XP_UNIX)
+ mLockFileDesc = rhs.mLockFileDesc;
+ rhs.mLockFileDesc = -1;
+ mPidLockFileName = rhs.mPidLockFileName;
+ rhs.mPidLockFileName = nullptr;
+ if (mPidLockFileName)
+ {
+ // rhs had a symlink lock, therefore it was on the list.
+ PR_REMOVE_LINK(&rhs);
+ PR_APPEND_LINK(this, &mPidLockList);
+ }
+#endif
+
+ return *this;
+}
+
+
+nsProfileLock::~nsProfileLock()
+{
+ Unlock();
+}
+
+
+#if defined (XP_UNIX)
+
+static int setupPidLockCleanup;
+
+PRCList nsProfileLock::mPidLockList =
+ PR_INIT_STATIC_CLIST(&nsProfileLock::mPidLockList);
+
+void nsProfileLock::RemovePidLockFiles(bool aFatalSignal)
+{
+ while (!PR_CLIST_IS_EMPTY(&mPidLockList))
+ {
+ nsProfileLock *lock = static_cast<nsProfileLock*>(mPidLockList.next);
+ lock->Unlock(aFatalSignal);
+ }
+}
+
+static struct sigaction SIGHUP_oldact;
+static struct sigaction SIGINT_oldact;
+static struct sigaction SIGQUIT_oldact;
+static struct sigaction SIGILL_oldact;
+static struct sigaction SIGABRT_oldact;
+static struct sigaction SIGSEGV_oldact;
+static struct sigaction SIGTERM_oldact;
+
+void nsProfileLock::FatalSignalHandler(int signo
+#ifdef SA_SIGINFO
+ , siginfo_t *info, void *context
+#endif
+ )
+{
+ // Remove any locks still held.
+ RemovePidLockFiles(true);
+
+ // Chain to the old handler, which may exit.
+ struct sigaction *oldact = nullptr;
+
+ switch (signo) {
+ case SIGHUP:
+ oldact = &SIGHUP_oldact;
+ break;
+ case SIGINT:
+ oldact = &SIGINT_oldact;
+ break;
+ case SIGQUIT:
+ oldact = &SIGQUIT_oldact;
+ break;
+ case SIGILL:
+ oldact = &SIGILL_oldact;
+ break;
+ case SIGABRT:
+ oldact = &SIGABRT_oldact;
+ break;
+ case SIGSEGV:
+ oldact = &SIGSEGV_oldact;
+ break;
+ case SIGTERM:
+ oldact = &SIGTERM_oldact;
+ break;
+ default:
+ NS_NOTREACHED("bad signo");
+ break;
+ }
+
+ if (oldact) {
+ if (oldact->sa_handler == SIG_DFL) {
+ // Make sure the default sig handler is executed
+ // We need it to get Mozilla to dump core.
+ sigaction(signo,oldact, nullptr);
+
+ // Now that we've restored the default handler, unmask the
+ // signal and invoke it.
+
+ sigset_t unblock_sigs;
+ sigemptyset(&unblock_sigs);
+ sigaddset(&unblock_sigs, signo);
+
+ sigprocmask(SIG_UNBLOCK, &unblock_sigs, nullptr);
+
+ raise(signo);
+ }
+#ifdef SA_SIGINFO
+ else if (oldact->sa_sigaction &&
+ (oldact->sa_flags & SA_SIGINFO) == SA_SIGINFO) {
+ oldact->sa_sigaction(signo, info, context);
+ }
+#endif
+ else if (oldact->sa_handler && oldact->sa_handler != SIG_IGN)
+ {
+ oldact->sa_handler(signo);
+ }
+ }
+
+ // Backstop exit call, just in case.
+ _exit(signo);
+}
+
+nsresult nsProfileLock::LockWithFcntl(nsIFile *aLockFile)
+{
+ nsresult rv = NS_OK;
+
+ nsAutoCString lockFilePath;
+#ifdef XP_WIN
+ rv = aLockFile->GetPersistentDescriptor(lockFilePath);
+#else
+ rv = aLockFile->GetNativePath(lockFilePath);
+#endif
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Could not get native path");
+ return rv;
+ }
+
+ aLockFile->GetLastModifiedTime(&mReplacedLockTime);
+
+ mLockFileDesc = open(lockFilePath.get(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (mLockFileDesc != -1)
+ {
+ struct flock lock;
+ lock.l_start = 0;
+ lock.l_len = 0; // len = 0 means entire file
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+
+ // If fcntl(F_GETLK) fails then the server does not support/allow fcntl(),
+ // return failure rather than access denied in this case so we fallback
+ // to using a symlink lock, bug 303633.
+ struct flock testlock = lock;
+ if (fcntl(mLockFileDesc, F_GETLK, &testlock) == -1)
+ {
+ close(mLockFileDesc);
+ mLockFileDesc = -1;
+ rv = NS_ERROR_FAILURE;
+ }
+ else if (fcntl(mLockFileDesc, F_SETLK, &lock) == -1)
+ {
+ close(mLockFileDesc);
+ mLockFileDesc = -1;
+
+ // With OS X, on NFS, errno == ENOTSUP
+ // XXX Check for that and return specific rv for it?
+#ifdef DEBUG
+ printf("fcntl(F_SETLK) failed. errno = %d\n", errno);
+#endif
+ if (errno == EAGAIN || errno == EACCES)
+ rv = NS_ERROR_FILE_ACCESS_DENIED;
+ else
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ else
+ {
+ NS_ERROR("Failed to open lock file.");
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+static bool IsSymlinkStaleLock(struct in_addr* aAddr, const char* aFileName,
+ bool aHaveFcntlLock)
+{
+ // the link exists; see if it's from this machine, and if
+ // so if the process is still active
+ char buf[1024];
+ int len = readlink(aFileName, buf, sizeof buf - 1);
+ if (len > 0)
+ {
+ buf[len] = '\0';
+ char *colon = strchr(buf, ':');
+ if (colon)
+ {
+ *colon++ = '\0';
+ unsigned long addr = inet_addr(buf);
+ if (addr != (unsigned long) -1)
+ {
+ if (colon[0] == '+' && aHaveFcntlLock) {
+ // This lock was placed by a Firefox build which would have
+ // taken the fnctl lock, and we've already taken the fcntl lock,
+ // so the process that created this obsolete lock must be gone
+ return true;
+ }
+
+ char *after = nullptr;
+ pid_t pid = strtol(colon, &after, 0);
+ if (pid != 0 && *after == '\0')
+ {
+ if (addr != aAddr->s_addr)
+ {
+ // Remote lock: give up even if stuck.
+ return false;
+ }
+
+ // kill(pid,0) is a neat trick to check if a
+ // process exists
+ if (kill(pid, 0) == 0 || errno != ESRCH)
+ {
+ // Local process appears to be alive, ass-u-me it
+ // is another Mozilla instance, or a compatible
+ // derivative, that's currently using the profile.
+ // XXX need an "are you Mozilla?" protocol
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+nsresult nsProfileLock::LockWithSymlink(nsIFile *aLockFile, bool aHaveFcntlLock)
+{
+ nsresult rv;
+ nsAutoCString lockFilePath;
+#ifdef XP_WIN
+ rv = aLockFile->GetPersistentDescriptor(lockFilePath);
+#else
+ rv = aLockFile->GetNativePath(lockFilePath);
+#endif
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Could not get native path");
+ return rv;
+ }
+
+ // don't replace an existing lock time if fcntl already got one
+ if (!mReplacedLockTime)
+ aLockFile->GetLastModifiedTimeOfLink(&mReplacedLockTime);
+
+ struct in_addr inaddr;
+ inaddr.s_addr = htonl(INADDR_LOOPBACK);
+
+ char hostname[256];
+ PRStatus status = PR_GetSystemInfo(PR_SI_HOSTNAME, hostname, sizeof hostname);
+ if (status == PR_SUCCESS)
+ {
+ char netdbbuf[PR_NETDB_BUF_SIZE];
+ PRHostEnt hostent;
+ status = PR_GetHostByName(hostname, netdbbuf, sizeof netdbbuf, &hostent);
+ if (status == PR_SUCCESS)
+ memcpy(&inaddr, hostent.h_addr, sizeof inaddr);
+ }
+
+ char *signature =
+ PR_smprintf("%s:%s%lu", inet_ntoa(inaddr), aHaveFcntlLock ? "+" : "",
+ (unsigned long)getpid());
+ const char *fileName = lockFilePath.get();
+ int symlink_rv, symlink_errno = 0, tries = 0;
+
+ // use ns4.x-compatible symlinks if the FS supports them
+ while ((symlink_rv = symlink(signature, fileName)) < 0)
+ {
+ symlink_errno = errno;
+ if (symlink_errno != EEXIST)
+ break;
+
+ if (!IsSymlinkStaleLock(&inaddr, fileName, aHaveFcntlLock))
+ break;
+
+ // Lock seems to be bogus: try to claim it. Give up after a large
+ // number of attempts (100 comes from the 4.x codebase).
+ (void) unlink(fileName);
+ if (++tries > 100)
+ break;
+ }
+
+ PR_smprintf_free(signature);
+ signature = nullptr;
+
+ if (symlink_rv == 0)
+ {
+ // We exclusively created the symlink: record its name for eventual
+ // unlock-via-unlink.
+ rv = NS_OK;
+ mPidLockFileName = strdup(fileName);
+ if (mPidLockFileName)
+ {
+ PR_APPEND_LINK(this, &mPidLockList);
+ if (!setupPidLockCleanup++)
+ {
+ // Clean up on normal termination.
+ // This instanciates a dummy class, and will trigger the class
+ // destructor when libxul is unloaded. This is equivalent to atexit(),
+ // but gracefully handles dlclose().
+ static RemovePidLockFilesExiting r;
+
+ // Clean up on abnormal termination, using POSIX sigaction.
+ // Don't arm a handler if the signal is being ignored, e.g.,
+ // because mozilla is run via nohup.
+ if (!sDisableSignalHandling) {
+ struct sigaction act, oldact;
+#ifdef SA_SIGINFO
+ act.sa_sigaction = FatalSignalHandler;
+ act.sa_flags = SA_SIGINFO;
+#else
+ act.sa_handler = FatalSignalHandler;
+#endif
+ sigfillset(&act.sa_mask);
+
+#define CATCH_SIGNAL(signame) \
+PR_BEGIN_MACRO \
+ if (sigaction(signame, nullptr, &oldact) == 0 && \
+ oldact.sa_handler != SIG_IGN) \
+ { \
+ sigaction(signame, &act, &signame##_oldact); \
+ } \
+ PR_END_MACRO
+
+ CATCH_SIGNAL(SIGHUP);
+ CATCH_SIGNAL(SIGINT);
+ CATCH_SIGNAL(SIGQUIT);
+ CATCH_SIGNAL(SIGILL);
+ CATCH_SIGNAL(SIGABRT);
+ CATCH_SIGNAL(SIGSEGV);
+ CATCH_SIGNAL(SIGTERM);
+
+#undef CATCH_SIGNAL
+ }
+ }
+ }
+ }
+ else if (symlink_errno == EEXIST)
+ rv = NS_ERROR_FILE_ACCESS_DENIED;
+ else
+ {
+#ifdef DEBUG
+ printf("symlink() failed. errno = %d\n", errno);
+#endif
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+#endif /* XP_UNIX */
+
+nsresult nsProfileLock::GetReplacedLockTime(PRTime *aResult) {
+ *aResult = mReplacedLockTime;
+ return NS_OK;
+}
+
+nsresult nsProfileLock::Lock(nsIFile* aProfileDir,
+ nsIProfileUnlocker* *aUnlocker)
+{
+#if defined (XP_UNIX)
+ NS_NAMED_LITERAL_STRING(OLD_LOCKFILE_NAME, "lock");
+ NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, ".parentlock");
+#else
+ NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, "parent.lock");
+#endif
+
+ nsresult rv;
+ if (aUnlocker)
+ *aUnlocker = nullptr;
+
+ NS_ENSURE_STATE(!mHaveLock);
+
+ bool isDir;
+ rv = aProfileDir->IsDirectory(&isDir);
+ if (NS_FAILED(rv))
+ return rv;
+ if (!isDir)
+ return NS_ERROR_FILE_NOT_DIRECTORY;
+
+ nsCOMPtr<nsIFile> lockFile;
+ rv = aProfileDir->Clone(getter_AddRefs(lockFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = lockFile->Append(LOCKFILE_NAME);
+ if (NS_FAILED(rv))
+ return rv;
+
+#if defined(XP_UNIX)
+ // Get the old lockfile name
+ nsCOMPtr<nsIFile> oldLockFile;
+ rv = aProfileDir->Clone(getter_AddRefs(oldLockFile));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = oldLockFile->Append(OLD_LOCKFILE_NAME);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // First, try locking using fcntl. It is more reliable on
+ // a local machine, but may not be supported by an NFS server.
+ rv = LockWithFcntl(lockFile);
+ if (NS_SUCCEEDED(rv)) {
+ // Check to see whether there is a symlink lock held by an older
+ // Firefox build, and also place our own symlink lock --- but
+ // mark it "obsolete" so that other newer builds can break the lock
+ // if they obtain the fcntl lock
+ rv = LockWithSymlink(oldLockFile, true);
+
+ // If the symlink failed for some reason other than it already
+ // exists, then something went wrong e.g. the file system
+ // doesn't support symlinks, or we don't have permission to
+ // create a symlink there. In such cases we should just
+ // continue because it's unlikely there is an old build
+ // running with a symlink there and we've already successfully
+ // placed a fcntl lock.
+ if (rv != NS_ERROR_FILE_ACCESS_DENIED)
+ rv = NS_OK;
+ }
+ else if (rv != NS_ERROR_FILE_ACCESS_DENIED)
+ {
+ // If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
+ // assume we tried an NFS that does not support it. Now, try with symlink
+ // using the old symlink path
+ rv = LockWithSymlink(oldLockFile, false);
+ }
+
+#elif defined(XP_WIN)
+ nsAutoString filePath;
+ rv = lockFile->GetPath(filePath);
+ if (NS_FAILED(rv))
+ return rv;
+
+ lockFile->GetLastModifiedTime(&mReplacedLockTime);
+
+ // always create the profile lock and never delete it so we can use its
+ // modification timestamp to detect startup crashes
+ mLockFileHandle = CreateFileW(filePath.get(),
+ GENERIC_READ | GENERIC_WRITE,
+ 0, // no sharing - of course
+ nullptr,
+ CREATE_ALWAYS,
+ 0,
+ nullptr);
+ if (mLockFileHandle == INVALID_HANDLE_VALUE) {
+ if (aUnlocker) {
+ RefPtr<mozilla::ProfileUnlockerWin> unlocker(
+ new mozilla::ProfileUnlockerWin(filePath));
+ if (NS_SUCCEEDED(unlocker->Init())) {
+ nsCOMPtr<nsIProfileUnlocker> unlockerInterface(
+ do_QueryObject(unlocker));
+ unlockerInterface.forget(aUnlocker);
+ }
+ }
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+#endif
+
+ if (NS_SUCCEEDED(rv))
+ mHaveLock = true;
+
+ return rv;
+}
+
+
+nsresult nsProfileLock::Unlock(bool aFatalSignal)
+{
+ nsresult rv = NS_OK;
+
+ if (mHaveLock)
+ {
+#if defined (XP_WIN)
+ if (mLockFileHandle != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(mLockFileHandle);
+ mLockFileHandle = INVALID_HANDLE_VALUE;
+ }
+#elif defined (XP_UNIX)
+ if (mPidLockFileName)
+ {
+ PR_REMOVE_LINK(this);
+ (void) unlink(mPidLockFileName);
+
+ // Only free mPidLockFileName if we're not in the fatal signal
+ // handler. The problem is that a call to free() might be the
+ // cause of this fatal signal. If so, calling free() might cause
+ // us to wait on the malloc implementation's lock. We're already
+ // holding this lock, so we'll deadlock. See bug 522332.
+ if (!aFatalSignal)
+ free(mPidLockFileName);
+ mPidLockFileName = nullptr;
+ }
+ if (mLockFileDesc != -1)
+ {
+ close(mLockFileDesc);
+ mLockFileDesc = -1;
+ // Don't remove it
+ }
+#endif
+
+ mHaveLock = false;
+ }
+
+ return rv;
+}
diff --git a/components/profile/src/nsProfileLock.h b/components/profile/src/nsProfileLock.h
new file mode 100644
index 000000000..e78a3577e
--- /dev/null
+++ b/components/profile/src/nsProfileLock.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef __nsProfileLock_h___
+#define __nsProfileLock_h___
+
+#include "nsIFile.h"
+
+class nsIProfileUnlocker;
+
+#if defined (XP_WIN)
+#include <windows.h>
+#endif
+
+#if defined (XP_UNIX)
+#include <signal.h>
+#include "prclist.h"
+#endif
+
+class nsProfileLock
+#if defined (XP_UNIX)
+ : public PRCList
+#endif
+{
+public:
+ nsProfileLock();
+ nsProfileLock(nsProfileLock& src);
+
+ ~nsProfileLock();
+
+ nsProfileLock& operator=(nsProfileLock& rhs);
+
+ /**
+ * Attempt to lock a profile directory.
+ *
+ * @param aProfileDir [in] The profile directory to lock.
+ * @param aUnlocker [out] Optional. This is only returned when locking
+ * fails with NS_ERROR_FILE_ACCESS_DENIED, and may not
+ * be returned at all.
+ * @throws NS_ERROR_FILE_ACCESS_DENIED if the profile is locked.
+ */
+ nsresult Lock(nsIFile* aProfileDir, nsIProfileUnlocker* *aUnlocker);
+
+ /**
+ * Unlock a profile directory. If you're unlocking the directory because
+ * the application is in the process of shutting down because of a fatal
+ * signal, set aFatalSignal to true.
+ */
+ nsresult Unlock(bool aFatalSignal = false);
+
+ /**
+ * Get the modification time of a replaced profile lock, otherwise 0.
+ */
+ nsresult GetReplacedLockTime(PRTime* aResult);
+
+private:
+ bool mHaveLock;
+ PRTime mReplacedLockTime;
+
+#if defined (XP_WIN)
+ HANDLE mLockFileHandle;
+#elif defined (XP_UNIX)
+
+ struct RemovePidLockFilesExiting {
+ RemovePidLockFilesExiting() {}
+ ~RemovePidLockFilesExiting() {
+ RemovePidLockFiles(false);
+ }
+ };
+
+ static void RemovePidLockFiles(bool aFatalSignal);
+ static void FatalSignalHandler(int signo
+#ifdef SA_SIGINFO
+ , siginfo_t *info, void *context
+#endif
+ );
+ static PRCList mPidLockList;
+
+ nsresult LockWithFcntl(nsIFile *aLockFile);
+
+ /**
+ * @param aHaveFcntlLock if true, we've already acquired an fcntl lock so this
+ * lock is merely an "obsolete" lock to keep out old Firefoxes
+ */
+ nsresult LockWithSymlink(nsIFile *aLockFile, bool aHaveFcntlLock);
+
+ char* mPidLockFileName;
+ int mLockFileDesc;
+#endif
+
+};
+
+#endif /* __nsProfileLock_h___ */
diff --git a/components/profile/src/nsProfileStringTypes.h b/components/profile/src/nsProfileStringTypes.h
new file mode 100644
index 000000000..fddea519b
--- /dev/null
+++ b/components/profile/src/nsProfileStringTypes.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/**
+ * We support two builds of the directory service provider.
+ * One, linked into the profile component, uses the internal
+ * string API. The other can be used by standalone embedding
+ * clients, and uses embed strings.
+ * To keep the code clean, we are using typedefs to equate
+ * embed/internal string types. We are also defining some
+ * internal macros in terms of the embedding strings API.
+ *
+ * When modifying the profile directory service provider, be
+ * sure to use methods supported by both the internal and
+ * embed strings APIs.
+ */
+
+#ifndef MOZILLA_INTERNAL_API
+
+#include "nsEmbedString.h"
+
+typedef nsCString nsPromiseFlatCString;
+typedef nsCString nsAutoCString;
+
+#define PromiseFlatCString nsCString
+
+#else
+#include "nsString.h"
+#include "nsPromiseFlatString.h"
+#endif
diff --git a/components/profile/src/nsToolkitProfileService.cpp b/components/profile/src/nsToolkitProfileService.cpp
new file mode 100644
index 000000000..3380246da
--- /dev/null
+++ b/components/profile/src/nsToolkitProfileService.cpp
@@ -0,0 +1,1043 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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>
+#include "nsProfileLock.h"
+
+#ifdef XP_WIN
+#include <windows.h>
+#include <shlobj.h>
+#endif
+#ifdef XP_UNIX
+#include <unistd.h>
+#endif
+
+#include "nsIToolkitProfileService.h"
+#include "nsIToolkitProfile.h"
+#include "nsIFactory.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsXULAppAPI.h"
+
+#include "nsINIParser.h"
+#include "nsXREDirProvider.h"
+#include "nsAppRunner.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsNativeCharsetUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Sprintf.h"
+
+using namespace mozilla;
+
+class nsToolkitProfile final : public nsIToolkitProfile
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITOOLKITPROFILE
+
+ friend class nsToolkitProfileService;
+ RefPtr<nsToolkitProfile> mNext;
+ nsToolkitProfile *mPrev;
+
+private:
+ ~nsToolkitProfile() { }
+
+ nsToolkitProfile(const nsACString& aName,
+ nsIFile* aRootDir,
+ nsIFile* aLocalDir,
+ nsToolkitProfile* aPrev,
+ bool aForExternalApp);
+
+ friend class nsToolkitProfileLock;
+
+ nsCString mName;
+ nsCOMPtr<nsIFile> mRootDir;
+ nsCOMPtr<nsIFile> mLocalDir;
+ nsIProfileLock* mLock;
+ bool mForExternalApp;
+};
+
+class nsToolkitProfileLock final : public nsIProfileLock
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROFILELOCK
+
+ nsresult Init(nsToolkitProfile* aProfile, nsIProfileUnlocker* *aUnlocker);
+ nsresult Init(nsIFile* aDirectory, nsIFile* aLocalDirectory,
+ nsIProfileUnlocker* *aUnlocker);
+
+ nsToolkitProfileLock() { }
+
+private:
+ ~nsToolkitProfileLock();
+
+ RefPtr<nsToolkitProfile> mProfile;
+ nsCOMPtr<nsIFile> mDirectory;
+ nsCOMPtr<nsIFile> mLocalDirectory;
+
+ nsProfileLock mLock;
+};
+
+class nsToolkitProfileFactory final : public nsIFactory
+{
+ ~nsToolkitProfileFactory() {}
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFACTORY
+};
+
+class nsToolkitProfileService final : public nsIToolkitProfileService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITOOLKITPROFILESERVICE
+
+private:
+ friend class nsToolkitProfile;
+ friend class nsToolkitProfileFactory;
+ friend nsresult NS_NewToolkitProfileService(nsIToolkitProfileService**);
+
+ nsToolkitProfileService() :
+ mDirty(false),
+ mStartWithLast(true),
+ mStartOffline(false)
+ {
+ gService = this;
+ }
+ ~nsToolkitProfileService()
+ {
+ gService = nullptr;
+ }
+
+ nsresult Init();
+
+ nsresult CreateTimesInternal(nsIFile *profileDir);
+
+ nsresult CreateProfileInternal(nsIFile* aRootDir,
+ const nsACString& aName,
+ const nsACString* aProfileName,
+ const nsACString* aAppName,
+ const nsACString* aVendorName,
+ bool aForExternalApp,
+ nsIToolkitProfile** aResult);
+
+ RefPtr<nsToolkitProfile> mFirst;
+ nsCOMPtr<nsIToolkitProfile> mChosen;
+ nsCOMPtr<nsIToolkitProfile> mDefault;
+ nsCOMPtr<nsIFile> mAppData;
+ nsCOMPtr<nsIFile> mTempData;
+ nsCOMPtr<nsIFile> mListFile;
+ bool mDirty;
+ bool mStartWithLast;
+ bool mStartOffline;
+
+ static nsToolkitProfileService *gService;
+
+ class ProfileEnumerator final : public nsISimpleEnumerator
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ explicit ProfileEnumerator(nsToolkitProfile *first)
+ { mCurrent = first; }
+ private:
+ ~ProfileEnumerator() { }
+ RefPtr<nsToolkitProfile> mCurrent;
+ };
+};
+
+nsToolkitProfile::nsToolkitProfile(const nsACString& aName,
+ nsIFile* aRootDir,
+ nsIFile* aLocalDir,
+ nsToolkitProfile* aPrev,
+ bool aForExternalApp) :
+ mPrev(aPrev),
+ mName(aName),
+ mRootDir(aRootDir),
+ mLocalDir(aLocalDir),
+ mLock(nullptr),
+ mForExternalApp(aForExternalApp)
+{
+ NS_ASSERTION(aRootDir, "No file!");
+
+ if (!aForExternalApp) {
+ if (aPrev) {
+ aPrev->mNext = this;
+ } else {
+ nsToolkitProfileService::gService->mFirst = this;
+ }
+ }
+}
+
+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?");
+ NS_ENSURE_TRUE(!mForExternalApp, NS_ERROR_NOT_IMPLEMENTED);
+
+ mName = aName;
+ nsToolkitProfileService::gService->mDirty = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::Remove(bool removeFiles)
+{
+ NS_ASSERTION(nsToolkitProfileService::gService,
+ "Whoa, my service is gone.");
+
+ NS_ENSURE_TRUE(!mForExternalApp, NS_ERROR_NOT_IMPLEMENTED);
+
+ if (mLock)
+ return NS_ERROR_FILE_IS_LOCKED;
+
+ if (!mPrev && !mNext && nsToolkitProfileService::gService->mFirst != this)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (removeFiles) {
+ bool equals;
+ nsresult rv = mRootDir->Equals(mLocalDir, &equals);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // The root dir might contain the temp dir, so remove
+ // the temp dir first.
+ if (!equals)
+ mLocalDir->Remove(true);
+
+ mRootDir->Remove(true);
+ }
+
+ if (mPrev)
+ mPrev->mNext = mNext;
+ else
+ nsToolkitProfileService::gService->mFirst = mNext;
+
+ if (mNext)
+ mNext->mPrev = mPrev;
+
+ mPrev = nullptr;
+ mNext = nullptr;
+
+ if (nsToolkitProfileService::gService->mChosen == this)
+ nsToolkitProfileService::gService->mChosen = nullptr;
+
+ nsToolkitProfileService::gService->mDirty = true;
+
+ return NS_OK;
+}
+
+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)
+
+nsresult
+nsToolkitProfileService::Init()
+{
+ NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!");
+ nsresult rv;
+
+ rv = gDirServiceProvider->GetUserAppDataDirectory(getter_AddRefs(mAppData));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = gDirServiceProvider->GetUserLocalDataDirectory(getter_AddRefs(mTempData));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mAppData->Clone(getter_AddRefs(mListFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mListFile->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = mListFile->IsFile(&exists);
+ if (NS_FAILED(rv) || !exists) {
+ return NS_OK;
+ }
+
+ int64_t size;
+ rv = mListFile->GetFileSize(&size);
+ if (NS_FAILED(rv) || !size) {
+ return NS_OK;
+ }
+
+ nsINIParser parser;
+ rv = parser.Init(mListFile);
+ // Init does not fail on parsing errors, only on OOM/really unexpected
+ // conditions.
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString buffer;
+ rv = parser.GetString("General", "StartWithLastProfile", buffer);
+ if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("0"))
+ mStartWithLast = false;
+
+ nsToolkitProfile* currentProfile = nullptr;
+
+ unsigned int c = 0;
+ bool foundAuroraDefault = false;
+ for (c = 0; true; ++c) {
+ nsAutoCString profileID("Profile");
+ profileID.AppendInt(c);
+
+ rv = parser.GetString(profileID.get(), "IsRelative", buffer);
+ if (NS_FAILED(rv)) break;
+
+ bool isRelative = buffer.EqualsLiteral("1");
+
+ nsAutoCString filePath;
+
+ rv = parser.GetString(profileID.get(), "Path", filePath);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Malformed profiles.ini: Path= not found");
+ continue;
+ }
+
+ nsAutoCString name;
+
+ rv = parser.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,
+ currentProfile, false);
+ NS_ENSURE_TRUE(currentProfile, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = parser.GetString(profileID.get(), "Default", buffer);
+ if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1") && !foundAuroraDefault) {
+ mChosen = currentProfile;
+ this->SetDefaultProfile(currentProfile);
+ }
+ }
+
+ if (!mChosen && mFirst && !mFirst->mNext) // only one profile
+ mChosen = mFirst;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::SetStartWithLastProfile(bool aValue)
+{
+ if (mStartWithLast != aValue) {
+ mStartWithLast = aValue;
+ mDirty = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetStartWithLastProfile(bool *aResult)
+{
+ *aResult = mStartWithLast;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetStartOffline(bool *aResult)
+{
+ *aResult = mStartOffline;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::SetStartOffline(bool aValue)
+{
+ mStartOffline = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetProfiles(nsISimpleEnumerator* *aResult)
+{
+ *aResult = new ProfileEnumerator(this->mFirst);
+ if (!*aResult)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsToolkitProfileService::ProfileEnumerator,
+ nsISimpleEnumerator)
+
+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->mNext;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetSelectedProfile(nsIToolkitProfile* *aResult)
+{
+ if (!mChosen && mFirst && !mFirst->mNext) // only one profile
+ mChosen = mFirst;
+
+ if (!mChosen) return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*aResult = mChosen);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::SetSelectedProfile(nsIToolkitProfile* aProfile)
+{
+ if (mChosen != aProfile) {
+ mChosen = aProfile;
+ mDirty = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetDefaultProfile(nsIToolkitProfile* *aResult)
+{
+ if (!mDefault) return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*aResult = mDefault);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile)
+{
+ if (mDefault != aProfile) {
+ mDefault = aProfile;
+ mDirty = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetProfileByName(const nsACString& aName,
+ nsIToolkitProfile* *aResult)
+{
+ nsToolkitProfile* curP = mFirst;
+ while (curP) {
+ if (curP->mName.Equals(aName)) {
+ NS_ADDREF(*aResult = curP);
+ return NS_OK;
+ }
+ curP = curP->mNext;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::LockProfilePath(nsIFile* aDirectory,
+ nsIFile* aLocalDirectory,
+ nsIProfileLock* *aResult)
+{
+ return NS_LockProfilePath(aDirectory, aLocalDirectory, nullptr, aResult);
+}
+
+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 const char kTable[] =
+ { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' };
+
+static void SaltProfileName(nsACString& aName)
+{
+ double fpTime = double(PR_Now());
+
+ // use 1e-6, granularity of PR_Now() on the mac is seconds
+ srand((unsigned int)(fpTime * 1e-6 + 0.5));
+
+ char salt[9];
+
+ int i;
+ for (i = 0; i < 8; ++i)
+ salt[i] = kTable[rand() % ArrayLength(kTable)];
+
+ salt[8] = '.';
+
+ aName.Insert(salt, 0, 9);
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::CreateDefaultProfileForApp(const nsACString& aProfileName,
+ const nsACString& aAppName,
+ const nsACString& aVendorName,
+ nsIToolkitProfile** aResult)
+{
+ NS_ENSURE_STATE(!aProfileName.IsEmpty() || !aAppName.IsEmpty());
+ nsCOMPtr<nsIFile> appData;
+ nsresult rv =
+ gDirServiceProvider->GetUserDataDirectory(getter_AddRefs(appData),
+ false,
+ &aProfileName,
+ &aAppName,
+ &aVendorName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> profilesini;
+ appData->Clone(getter_AddRefs(profilesini));
+ rv = profilesini->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ profilesini->Exists(&exists);
+ NS_ENSURE_FALSE(exists, NS_ERROR_ALREADY_INITIALIZED);
+
+ rv = CreateProfileInternal(nullptr,
+ NS_LITERAL_CSTRING("default"),
+ &aProfileName, &aAppName, &aVendorName,
+ true, aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(*aResult);
+
+ nsCOMPtr<nsIFile> rootDir;
+ (*aResult)->GetRootDir(getter_AddRefs(rootDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString profileDir;
+ rv = rootDir->GetRelativeDescriptor(appData, profileDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString ini;
+ ini.SetCapacity(512);
+ ini.AppendLiteral("[General]\n");
+ ini.AppendLiteral("StartWithLastProfile=1\n\n");
+
+ ini.AppendLiteral("[Profile0]\n");
+ ini.AppendLiteral("Name=default\n");
+ ini.AppendLiteral("IsRelative=1\n");
+ ini.AppendLiteral("Path=");
+ ini.Append(profileDir);
+ ini.Append('\n');
+ ini.AppendLiteral("Default=1\n\n");
+
+ FILE* writeFile;
+ rv = profilesini->OpenANSIFileDesc("w", &writeFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fwrite(ini.get(), sizeof(char), ini.Length(), writeFile) !=
+ ini.Length()) {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ fclose(writeFile);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
+ const nsACString& aName,
+ nsIToolkitProfile** aResult)
+{
+ return CreateProfileInternal(aRootDir, aName,
+ nullptr, nullptr, nullptr, false, aResult);
+}
+
+nsresult
+nsToolkitProfileService::CreateProfileInternal(nsIFile* aRootDir,
+ const nsACString& aName,
+ const nsACString* aProfileName,
+ const nsACString* aAppName,
+ const nsACString* aVendorName,
+ bool aForExternalApp,
+ nsIToolkitProfile** aResult)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (!aForExternalApp) {
+ rv = GetProfileByName(aName, aResult);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIFile> rootDir (aRootDir);
+
+ nsAutoCString dirName;
+ if (!rootDir) {
+ rv = gDirServiceProvider->GetUserProfilesRootDir(getter_AddRefs(rootDir),
+ aProfileName, aAppName,
+ aVendorName);
+ 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);
+
+ // If the profile is on the sdcard, this will fail but its non-fatal
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ }
+
+ 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);
+
+ nsToolkitProfile* last = aForExternalApp ? nullptr : mFirst.get();
+ if (last) {
+ while (last->mNext)
+ last = last->mNext;
+ }
+
+ nsCOMPtr<nsIToolkitProfile> profile =
+ new nsToolkitProfile(aName, rootDir, localDir, last, aForExternalApp);
+ if (!profile) return NS_ERROR_OUT_OF_MEMORY;
+
+ profile.forget(aResult);
+ return NS_OK;
+}
+
+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}\n", msec);
+ PR_Close(writeFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetProfileCount(uint32_t *aResult)
+{
+ if (!mFirst)
+ *aResult = 0;
+ else if (! mFirst->mNext)
+ *aResult = 1;
+ else
+ *aResult = 2;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::Flush()
+{
+ // Errors during writing might cause unhappy semi-written files.
+ // To avoid this, write the entire thing to a buffer, then write
+ // that buffer to disk.
+
+ nsresult rv;
+ uint32_t pCount = 0;
+ nsToolkitProfile *cur;
+
+ for (cur = mFirst; cur != nullptr; cur = cur->mNext)
+ ++pCount;
+
+ uint32_t length;
+ const int bufsize = 100+MAXPATHLEN*pCount;
+ auto buffer = MakeUnique<char[]>(bufsize);
+
+ char *pos = buffer.get();
+ char *end = pos + bufsize;
+
+ pos += snprintf(pos, end - pos,
+ "[General]\n"
+ "StartWithLastProfile=%s\n\n",
+ mStartWithLast ? "1" : "0");
+
+ nsAutoCString path;
+ cur = mFirst;
+ pCount = 0;
+
+ while (cur) {
+ // if the profile dir is relative to appdir...
+ bool isRelative;
+ rv = mAppData->Contains(cur->mRootDir, &isRelative);
+ if (NS_SUCCEEDED(rv) && isRelative) {
+ // we use a relative descriptor
+ rv = cur->mRootDir->GetRelativeDescriptor(mAppData, path);
+ } else {
+ // otherwise, a persistent descriptor
+ rv = cur->mRootDir->GetPersistentDescriptor(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ pos += snprintf(pos, end - pos,
+ "[Profile%u]\n"
+ "Name=%s\n"
+ "IsRelative=%s\n"
+ "Path=%s\n",
+ pCount, cur->mName.get(),
+ isRelative ? "1" : "0", path.get());
+
+ nsCOMPtr<nsIToolkitProfile> profile;
+ rv = this->GetDefaultProfile(getter_AddRefs(profile));
+ if (NS_SUCCEEDED(rv) && profile == cur) {
+ pos += snprintf(pos, end - pos, "Default=1\n");
+ }
+
+ pos += snprintf(pos, end - pos, "\n");
+
+ cur = cur->mNext;
+ ++pCount;
+ }
+
+ FILE* writeFile;
+ rv = mListFile->OpenANSIFileDesc("w", &writeFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ length = pos - buffer.get();
+
+ if (fwrite(buffer.get(), sizeof(char), length, writeFile) != length) {
+ fclose(writeFile);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ fclose(writeFile);
+ 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;
+
+ nsCOMPtr<nsIToolkitProfileService> 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(nsIToolkitProfileService* *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_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
+}