Pathlocks Implementation for Windows (#10586)

Based on Volth's original port.

Co-authored-by: volth <volth@volth.com>
This commit is contained in:
Dylan Green 2024-04-22 10:08:10 -05:00 committed by GitHub
parent e1fd0e0a8c
commit aa165301d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 178 additions and 62 deletions

View file

@ -22,7 +22,6 @@
#include "experimental-features.hh"
using namespace nix;
using namespace nix::unix;
using std::cin;
static void handleAlarm(int sig) {

View file

@ -5,10 +5,26 @@
namespace nix {
/**
* Open (possibly create) a lock file and return the file descriptor.
* -1 is returned if create is false and the lock could not be opened
* because it doesn't exist. Any other error throws an exception.
*/
AutoCloseFD openLockFile(const Path & path, bool create);
/**
* Delete an open lock file.
*/
void deleteLockFile(const Path & path, Descriptor desc);
enum LockType { ltRead, ltWrite, ltNone };
bool lockFile(Descriptor desc, LockType lockType, bool wait);
class PathLocks
{
private:
typedef std::pair<int, Path> FDPair;
typedef std::pair<Descriptor, Path> FDPair;
std::list<FDPair> fds;
bool deletePaths;
@ -24,6 +40,18 @@ public:
void setDeletion(bool deletePaths);
};
}
struct FdLock
{
Descriptor desc;
bool acquired = false;
#include "pathlocks-impl.hh"
FdLock(Descriptor desc, LockType lockType, bool wait, std::string_view waitMsg);
~FdLock()
{
if (acquired)
lockFile(desc, ltNone, false);
}
};
}

View file

@ -9,8 +9,6 @@
namespace nix {
using namespace nix::unix;
#if __linux__
static std::vector<gid_t> get_group_list(const char *username, gid_t group_id)

View file

@ -1,38 +0,0 @@
#pragma once
///@file
#include "file-descriptor.hh"
namespace nix::unix {
/**
* Open (possibly create) a lock file and return the file descriptor.
* -1 is returned if create is false and the lock could not be opened
* because it doesn't exist. Any other error throws an exception.
*/
AutoCloseFD openLockFile(const Path & path, bool create);
/**
* Delete an open lock file.
*/
void deleteLockFile(const Path & path, int fd);
enum LockType { ltRead, ltWrite, ltNone };
bool lockFile(int fd, LockType lockType, bool wait);
struct FdLock
{
int fd;
bool acquired = false;
FdLock(int fd, LockType lockType, bool wait, std::string_view waitMsg);
~FdLock()
{
if (acquired)
lockFile(fd, ltNone, false);
}
};
}

View file

@ -14,9 +14,7 @@
namespace nix {
using namespace nix::unix;
AutoCloseFD unix::openLockFile(const Path & path, bool create)
AutoCloseFD openLockFile(const Path & path, bool create)
{
AutoCloseFD fd;
@ -28,20 +26,20 @@ AutoCloseFD unix::openLockFile(const Path & path, bool create)
}
void unix::deleteLockFile(const Path & path, int fd)
void deleteLockFile(const Path & path, Descriptor desc)
{
/* Get rid of the lock file. Have to be careful not to introduce
races. Write a (meaningless) token to the file to indicate to
other processes waiting on this lock that the lock is stale
(deleted). */
unlink(path.c_str());
writeFull(fd, "d");
writeFull(desc, "d");
/* Note that the result of unlink() is ignored; removing the lock
file is an optimisation, not a necessity. */
}
bool unix::lockFile(int fd, LockType lockType, bool wait)
bool lockFile(Descriptor desc, LockType lockType, bool wait)
{
int type;
if (lockType == ltRead) type = LOCK_SH;
@ -50,7 +48,7 @@ bool unix::lockFile(int fd, LockType lockType, bool wait)
else abort();
if (wait) {
while (flock(fd, type) != 0) {
while (flock(desc, type) != 0) {
checkInterrupt();
if (errno != EINTR)
throw SysError("acquiring/releasing lock");
@ -58,7 +56,7 @@ bool unix::lockFile(int fd, LockType lockType, bool wait)
return false;
}
} else {
while (flock(fd, type | LOCK_NB) != 0) {
while (flock(desc, type | LOCK_NB) != 0) {
checkInterrupt();
if (errno == EWOULDBLOCK) return false;
if (errno != EINTR)
@ -149,16 +147,16 @@ void PathLocks::unlock()
}
FdLock::FdLock(int fd, LockType lockType, bool wait, std::string_view waitMsg)
: fd(fd)
FdLock::FdLock(Descriptor desc, LockType lockType, bool wait, std::string_view waitMsg)
: desc(desc)
{
if (wait) {
if (!lockFile(fd, lockType, false)) {
if (!lockFile(desc, lockType, false)) {
printInfo("%s", waitMsg);
acquired = lockFile(fd, lockType, true);
acquired = lockFile(desc, lockType, true);
}
} else
acquired = lockFile(fd, lockType, false);
acquired = lockFile(desc, lockType, false);
}

View file

@ -1,2 +0,0 @@
#pragma once
///@file Needed because Unix-specific counterpart

View file

@ -1,16 +1,149 @@
#include "logging.hh"
#include "pathlocks.hh"
#include "signals.hh"
#include "util.hh"
#include <errhandlingapi.h>
#include <fileapi.h>
#include <windows.h>
#include "windows-error.hh"
namespace nix {
bool PathLocks::lockPaths(const PathSet & _paths, const std::string & waitMsg, bool wait)
void deleteLockFile(const Path & path, Descriptor desc)
{
return true;
int exit = DeleteFileA(path.c_str());
if (exit == 0)
warn("%s: &s", path, std::to_string(GetLastError()));
}
void PathLocks::unlock()
{
warn("PathLocks::unlock: not yet implemented");
for (auto & i : fds) {
if (deletePaths)
deleteLockFile(i.second, i.first);
if (CloseHandle(i.first) == -1)
printError("error (ignored): cannot close lock file on '%1%'", i.second);
debug("lock released on '%1%'", i.second);
}
fds.clear();
}
AutoCloseFD openLockFile(const Path & path, bool create)
{
AutoCloseFD desc = CreateFileA(
path.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
create ? OPEN_ALWAYS : OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_POSIX_SEMANTICS, NULL);
if (desc.get() == INVALID_HANDLE_VALUE)
warn("%s: %s", path, std::to_string(GetLastError()));
return desc;
}
bool lockFile(Descriptor desc, LockType lockType, bool wait)
{
switch (lockType) {
case ltNone: {
OVERLAPPED ov = {0};
if (!UnlockFileEx(desc, 0, 2, 0, &ov)) {
WinError winError("Failed to unlock file desc %s", desc);
throw winError;
}
return true;
}
case ltRead: {
OVERLAPPED ov = {0};
if (!LockFileEx(desc, wait ? 0 : LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &ov)) {
WinError winError("Failed to lock file desc %s", desc);
if (winError.lastError == ERROR_LOCK_VIOLATION)
return false;
throw winError;
}
ov.Offset = 1;
if (!UnlockFileEx(desc, 0, 1, 0, &ov)) {
WinError winError("Failed to unlock file desc %s", desc);
if (winError.lastError != ERROR_NOT_LOCKED)
throw winError;
}
return true;
}
case ltWrite: {
OVERLAPPED ov = {0};
ov.Offset = 1;
if (!LockFileEx(desc, LOCKFILE_EXCLUSIVE_LOCK | (wait ? 0 : LOCKFILE_FAIL_IMMEDIATELY), 0, 1, 0, &ov)) {
WinError winError("Failed to lock file desc %s", desc);
if (winError.lastError == ERROR_LOCK_VIOLATION)
return false;
throw winError;
}
ov.Offset = 0;
if (!UnlockFileEx(desc, 0, 1, 0, &ov)) {
WinError winError("Failed to unlock file desc %s", desc);
if (winError.lastError != ERROR_NOT_LOCKED)
throw winError;
}
return true;
}
default:
assert(false);
}
}
bool PathLocks::lockPaths(const PathSet & paths, const std::string & waitMsg, bool wait)
{
assert(fds.empty());
for (auto & path : paths) {
checkInterrupt();
Path lockPath = path + ".lock";
debug("locking path '%1%'", path);
AutoCloseFD fd;
while (1) {
fd = openLockFile(lockPath, true);
if (!lockFile(fd.get(), ltWrite, false)) {
if (wait) {
if (waitMsg != "")
printError(waitMsg);
lockFile(fd.get(), ltWrite, true);
} else {
unlock();
return false;
}
}
debug("lock aquired on '%1%'", lockPath);
struct _stat st;
if (_fstat(fromDescriptorReadOnly(fd.get()), &st) == -1)
throw SysError("statting lock file '%1%'", lockPath);
if (st.st_size != 0)
debug("open lock file '%1%' has become stale", lockPath);
else
break;
}
fds.push_back(FDPair(fd.release(), lockPath));
}
return true;
}
FdLock::FdLock(Descriptor desc, LockType lockType, bool wait, std::string_view waitMsg)
: desc(desc)
{
if (wait) {
if (!lockFile(desc, lockType, false)) {
printInfo("%s", waitMsg);
acquired = lockFile(desc, lockType, true);
}
} else
acquired = lockFile(desc, lockType, false);
}
}