зеркало из https://github.com/mozilla/gecko-dev.git
199 строки
7.1 KiB
C++
199 строки
7.1 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
|
/* 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 "MultiInstanceLock.h"
|
|
|
|
#include "commonupdatedir.h" // for GetInstallHash
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsPromiseFlatString.h"
|
|
#include "updatedefines.h" // for NS_t* definitions
|
|
|
|
#ifndef XP_WIN
|
|
# include <fcntl.h>
|
|
# include <sys/stat.h>
|
|
# include <sys/types.h>
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
|
|
static bool GetLockFileName(const char* nameToken, const char16_t* installPath,
|
|
nsCString& filePath) {
|
|
mozilla::UniquePtr<NS_tchar[]> pathHash;
|
|
if (!GetInstallHash(installPath, MOZ_APP_VENDOR, pathHash)) {
|
|
return false;
|
|
}
|
|
|
|
#ifdef XP_WIN
|
|
// On Windows, the lock file is placed at the path
|
|
// ProgramData\[vendor]\[nameToken]-[pathHash], so first we need to get the
|
|
// ProgramData path and then append our directory and the file name.
|
|
PWSTR programDataPath;
|
|
HRESULT hr = SHGetKnownFolderPath(FOLDERID_ProgramData, KF_FLAG_CREATE,
|
|
nullptr, &programDataPath);
|
|
if (FAILED(hr)) {
|
|
return false;
|
|
}
|
|
mozilla::UniquePtr<wchar_t, CoTaskMemFreeDeleter> programDataPathUnique(
|
|
programDataPath);
|
|
|
|
filePath = nsPrintfCString(
|
|
"%s\\%s\\%s-%s", NS_ConvertUTF16toUTF8(programDataPath).get(),
|
|
MOZ_APP_VENDOR, nameToken, NS_ConvertUTF16toUTF8(pathHash.get()).get());
|
|
|
|
#else
|
|
// On POSIX platforms the base path is /tmp/[vendor][nameToken]-[pathHash].
|
|
filePath = nsPrintfCString("/tmp/%s%s-%s", MOZ_APP_VENDOR, nameToken,
|
|
pathHash.get());
|
|
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
MultiInstLockHandle OpenMultiInstanceLock(const char* nameToken,
|
|
const char16_t* installPath) {
|
|
nsCString filePath;
|
|
if (!GetLockFileName(nameToken, installPath, filePath)) {
|
|
return MULTI_INSTANCE_LOCK_HANDLE_ERROR;
|
|
}
|
|
|
|
// Open a file handle with full privileges and sharing, and then attempt to
|
|
// take a shared (nonexclusive, read-only) lock on it.
|
|
#ifdef XP_WIN
|
|
HANDLE h =
|
|
::CreateFileW(PromiseFlatString(NS_ConvertUTF8toUTF16(filePath)).get(),
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
nullptr, OPEN_ALWAYS, 0, nullptr);
|
|
if (h != INVALID_HANDLE_VALUE) {
|
|
// The LockFileEx functions always require an OVERLAPPED structure even
|
|
// though we did not open the lock file for overlapped I/O.
|
|
OVERLAPPED o = {0};
|
|
if (!::LockFileEx(h, LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &o)) {
|
|
CloseHandle(h);
|
|
h = INVALID_HANDLE_VALUE;
|
|
}
|
|
}
|
|
return h;
|
|
|
|
#else
|
|
int fd = ::open(PromiseFlatCString(filePath).get(),
|
|
O_CLOEXEC | O_CREAT | O_NOFOLLOW,
|
|
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
|
if (fd != -1) {
|
|
// We would like to ensure that the lock file is deleted when we are done
|
|
// with it. The normal way to do that would be to call unlink on it right
|
|
// now, but that would immediately delete the name from the file system, and
|
|
// we need other instances to be able to open that name and get the same
|
|
// inode, so we can't unlink the file before we're done with it. This means
|
|
// we accept some unreliability in getting the file deleted, but it's a zero
|
|
// byte file in the tmp directory, so having it stay around isn't the worst.
|
|
struct flock l = {0};
|
|
l.l_start = 0;
|
|
l.l_len = 0;
|
|
l.l_type = F_RDLCK;
|
|
if (::fcntl(fd, F_SETLK, &l)) {
|
|
::close(fd);
|
|
fd = -1;
|
|
}
|
|
}
|
|
return fd;
|
|
|
|
#endif
|
|
}
|
|
|
|
void ReleaseMultiInstanceLock(MultiInstLockHandle lock) {
|
|
if (lock != MULTI_INSTANCE_LOCK_HANDLE_ERROR) {
|
|
#ifdef XP_WIN
|
|
OVERLAPPED o = {0};
|
|
::UnlockFileEx(lock, 0, 1, 0, &o);
|
|
::CloseHandle(lock);
|
|
|
|
#else
|
|
// If we're the last instance, then unlink the lock file. There is a race
|
|
// condition here that may cause an instance to fail to open the same inode
|
|
// as another even though they use the same path, but there's no reasonable
|
|
// way to avoid that without skipping deleting the file at all, so we accept
|
|
// that risk.
|
|
bool otherInstance = true;
|
|
if (IsOtherInstanceRunning(lock, &otherInstance) && !otherInstance) {
|
|
// Recover the file's path so we can unlink it.
|
|
// There's no error checking in here because we're content to let the file
|
|
// hang around if any of this fails (which can happen if for example we're
|
|
// on a system where /proc/self/fd does not exist); this is a zero-byte
|
|
// file in the tmp directory after all.
|
|
UniquePtr<NS_tchar[]> linkPath = MakeUnique<NS_tchar[]>(MAXPATHLEN + 1);
|
|
NS_tsnprintf(linkPath.get(), MAXPATHLEN + 1, "/proc/self/fd/%d", lock);
|
|
UniquePtr<NS_tchar[]> lockFilePath =
|
|
MakeUnique<NS_tchar[]>(MAXPATHLEN + 1);
|
|
if (::readlink(linkPath.get(), lockFilePath.get(), MAXPATHLEN + 1) !=
|
|
-1) {
|
|
::unlink(lockFilePath.get());
|
|
}
|
|
}
|
|
// Now close the lock file, which will release the lock.
|
|
::close(lock);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
bool IsOtherInstanceRunning(MultiInstLockHandle lock, bool* aResult) {
|
|
// Every running instance has opened a readonly lock, and read locks prevent
|
|
// write locks from being opened, so to see if we are the only instance, we
|
|
// attempt to take a write lock, and if it succeeds then that must mean there
|
|
// are no other read locks open and therefore no other instances.
|
|
if (lock == MULTI_INSTANCE_LOCK_HANDLE_ERROR) {
|
|
return false;
|
|
}
|
|
|
|
#ifdef XP_WIN
|
|
// We need to release the lock we're holding before we would be allowed to
|
|
// take an exclusive lock, and if that succeeds we need to release it too
|
|
// in order to get our shared lock back. This procedure is not atomic, so we
|
|
// accept the risk of the scheduler deciding to ruin our day between these
|
|
// operations; we'd get a false negative in a different instance's check.
|
|
OVERLAPPED o = {0};
|
|
// Release our current shared lock.
|
|
if (!::UnlockFileEx(lock, 0, 1, 0, &o)) {
|
|
return false;
|
|
}
|
|
// Attempt to take an exclusive lock.
|
|
bool rv = false;
|
|
if (::LockFileEx(lock, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0,
|
|
1, 0, &o)) {
|
|
// We got the exclusive lock, so now release it.
|
|
::UnlockFileEx(lock, 0, 1, 0, &o);
|
|
*aResult = false;
|
|
rv = true;
|
|
} else if (::GetLastError() == ERROR_LOCK_VIOLATION) {
|
|
// We didn't get the exclusive lock because of outstanding shared locks.
|
|
*aResult = true;
|
|
rv = true;
|
|
}
|
|
// Attempt to reclaim the shared lock we released at the beginning.
|
|
if (!::LockFileEx(lock, LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &o)) {
|
|
rv = false;
|
|
}
|
|
return rv;
|
|
|
|
#else
|
|
// See if we would be allowed to set a write lock (no need to actually do so).
|
|
struct flock l = {0};
|
|
l.l_start = 0;
|
|
l.l_len = 0;
|
|
l.l_type = F_WRLCK;
|
|
if (::fcntl(lock, F_GETLK, &l)) {
|
|
return false;
|
|
}
|
|
*aResult = l.l_type != F_UNLCK;
|
|
return true;
|
|
|
|
#endif
|
|
}
|
|
|
|
}; // namespace mozilla
|