diff options
Diffstat (limited to 'components/profile/src')
-rw-r--r-- | components/profile/src/ProfileUnlockerWin.cpp | 277 | ||||
-rw-r--r-- | components/profile/src/ProfileUnlockerWin.h | 59 | ||||
-rw-r--r-- | components/profile/src/nsProfileLock.cpp | 576 | ||||
-rw-r--r-- | components/profile/src/nsProfileLock.h | 95 | ||||
-rw-r--r-- | components/profile/src/nsProfileStringTypes.h | 32 | ||||
-rw-r--r-- | components/profile/src/nsToolkitProfileService.cpp | 1043 |
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 +} |