summaryrefslogtreecommitdiffstats
path: root/toolkit/profile/nsProfileLock.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/profile/nsProfileLock.cpp')
-rw-r--r--toolkit/profile/nsProfileLock.cpp661
1 files changed, 661 insertions, 0 deletions
diff --git a/toolkit/profile/nsProfileLock.cpp b/toolkit/profile/nsProfileLock.cpp
new file mode 100644
index 000000000..08d109224
--- /dev/null
+++ b/toolkit/profile/nsProfileLock.cpp
@@ -0,0 +1,661 @@
+/* -*- 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
+
+#if defined(XP_MACOSX)
+#include <Carbon/Carbon.h>
+#include <CoreFoundation/CoreFoundation.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
+
+#if defined(MOZ_WIDGET_GONK) && !defined(MOZ_CRASHREPORTER)
+#include <sys/syscall.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);
+ }
+ }
+
+#ifdef MOZ_WIDGET_GONK
+ switch (signo) {
+ case SIGQUIT:
+ case SIGILL:
+ case SIGABRT:
+ case SIGSEGV:
+#ifndef MOZ_CRASHREPORTER
+ // Retrigger the signal for those that can generate a core dump
+ signal(signo, SIG_DFL);
+ if (info->si_code <= 0) {
+ if (syscall(__NR_tgkill, getpid(), syscall(__NR_gettid), signo) < 0) {
+ break;
+ }
+ }
+#endif
+ return;
+ default:
+ break;
+ }
+#endif
+
+ // Backstop exit call, just in case.
+ _exit(signo);
+}
+
+nsresult nsProfileLock::LockWithFcntl(nsIFile *aLockFile)
+{
+ nsresult rv = NS_OK;
+
+ nsAutoCString lockFilePath;
+ rv = aLockFile->GetNativePath(lockFilePath);
+ 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;
+ rv = aLockFile->GetNativePath(lockFilePath);
+ 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_MACOSX)
+ NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, ".parentlock");
+ NS_NAMED_LITERAL_STRING(OLD_LOCKFILE_NAME, "parent.lock");
+#elif 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_MACOSX)
+ // 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_FAILED(rv) && (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.
+ rv = LockWithSymlink(lockFile, false);
+ }
+
+ if (NS_SUCCEEDED(rv))
+ {
+ // Check for the old-style lock used by pre-mozilla 1.3 builds.
+ // Those builds used an earlier check to prevent the application
+ // from launching if another instance was already running. Because
+ // of that, we don't need to create an old-style lock as well.
+ struct LockProcessInfo
+ {
+ ProcessSerialNumber psn;
+ unsigned long launchDate;
+ };
+
+ PRFileDesc *fd = nullptr;
+ int32_t ioBytes;
+ ProcessInfoRec processInfo;
+ LockProcessInfo lockProcessInfo;
+
+ rv = lockFile->SetLeafName(OLD_LOCKFILE_NAME);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = lockFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
+ if (NS_SUCCEEDED(rv))
+ {
+ ioBytes = PR_Read(fd, &lockProcessInfo, sizeof(LockProcessInfo));
+ PR_Close(fd);
+
+ if (ioBytes == sizeof(LockProcessInfo))
+ {
+#ifdef __LP64__
+ processInfo.processAppRef = nullptr;
+#else
+ processInfo.processAppSpec = nullptr;
+#endif
+ processInfo.processName = nullptr;
+ processInfo.processInfoLength = sizeof(ProcessInfoRec);
+ if (::GetProcessInformation(&lockProcessInfo.psn, &processInfo) == noErr &&
+ processInfo.processLaunchDate == lockProcessInfo.launchDate)
+ {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+ }
+ else
+ {
+ NS_WARNING("Could not read lock file - ignoring lock");
+ }
+ }
+ rv = NS_OK; // Don't propagate error from OpenNSPRFileDesc.
+ }
+#elif 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;
+}