gecko/toolkit/profile/nsProfileLock.cpp
2015-12-18 15:02:50 +02:00

664 lines
20 KiB
C++

/* -*- 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
mHaveLock = true;
}
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;
mHaveLock = true;
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
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;
}