Create no-op Window pathlocks implementation

This keeps the call sites simple, eventually this should be filled in.
This commit is contained in:
John Ericson 2024-04-17 11:34:09 -04:00
parent 05b9dac754
commit 2248a3f545
11 changed files with 232 additions and 182 deletions

View file

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

View file

@ -29,6 +29,7 @@
namespace nix { namespace nix {
using namespace nix::unix;
static std::string gcSocketPath = "/gc-socket/socket"; static std::string gcSocketPath = "/gc-socket/socket";
static std::string gcRootsDir = "gcroots"; static std::string gcRootsDir = "gcroots";

View file

@ -52,6 +52,8 @@
namespace nix { namespace nix {
using namespace nix::unix;
std::string LocalStoreConfig::doc() std::string LocalStoreConfig::doc()
{ {
return return

View file

@ -36,6 +36,9 @@ INCLUDE_libstore := -I $(d) -I $(d)/build
ifdef HOST_UNIX ifdef HOST_UNIX
INCLUDE_libstore += -I $(d)/unix INCLUDE_libstore += -I $(d)/unix
endif endif
ifdef HOST_WINDOWS
INCLUDE_libstore += -I $(d)/windows
endif
libstore_CXXFLAGS += \ libstore_CXXFLAGS += \
$(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libstore) \ $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libstore) \

View file

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

View file

@ -6,69 +6,9 @@
#include <cerrno> #include <cerrno>
#include <cstdlib> #include <cstdlib>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
namespace nix { namespace nix {
AutoCloseFD openLockFile(const Path & path, bool create)
{
AutoCloseFD fd;
fd = open(path.c_str(), O_CLOEXEC | O_RDWR | (create ? O_CREAT : 0), 0600);
if (!fd && (create || errno != ENOENT))
throw SysError("opening lock file '%1%'", path);
return fd;
}
void deleteLockFile(const Path & path, int fd)
{
/* 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");
/* Note that the result of unlink() is ignored; removing the lock
file is an optimisation, not a necessity. */
}
bool lockFile(int fd, LockType lockType, bool wait)
{
int type;
if (lockType == ltRead) type = LOCK_SH;
else if (lockType == ltWrite) type = LOCK_EX;
else if (lockType == ltNone) type = LOCK_UN;
else abort();
if (wait) {
while (flock(fd, type) != 0) {
checkInterrupt();
if (errno != EINTR)
throw SysError("acquiring/releasing lock");
else
return false;
}
} else {
while (flock(fd, type | LOCK_NB) != 0) {
checkInterrupt();
if (errno == EWOULDBLOCK) return false;
if (errno != EINTR)
throw SysError("acquiring/releasing lock");
}
}
return true;
}
PathLocks::PathLocks() PathLocks::PathLocks()
: deletePaths(false) : deletePaths(false)
{ {
@ -82,68 +22,6 @@ PathLocks::PathLocks(const PathSet & paths, const std::string & waitMsg)
} }
bool PathLocks::lockPaths(const PathSet & paths,
const std::string & waitMsg, bool wait)
{
assert(fds.empty());
/* Note that `fds' is built incrementally so that the destructor
will only release those locks that we have already acquired. */
/* Acquire the lock for each path in sorted order. This ensures
that locks are always acquired in the same order, thus
preventing deadlocks. */
for (auto & path : paths) {
checkInterrupt();
Path lockPath = path + ".lock";
debug("locking path '%1%'", path);
AutoCloseFD fd;
while (1) {
/* Open/create the lock file. */
fd = openLockFile(lockPath, true);
/* Acquire an exclusive lock. */
if (!lockFile(fd.get(), ltWrite, false)) {
if (wait) {
if (waitMsg != "") printError(waitMsg);
lockFile(fd.get(), ltWrite, true);
} else {
/* Failed to lock this path; release all other
locks. */
unlock();
return false;
}
}
debug("lock acquired on '%1%'", lockPath);
/* Check that the lock file hasn't become stale (i.e.,
hasn't been unlinked). */
struct stat st;
if (fstat(fd.get(), &st) == -1)
throw SysError("statting lock file '%1%'", lockPath);
if (st.st_size != 0)
/* This lock file has been unlinked, so we're holding
a lock on a deleted file. This means that other
processes may create and acquire a lock on
`lockPath', and proceed. So we must retry. */
debug("open lock file '%1%' has become stale", lockPath);
else
break;
}
/* Use borrow so that the descriptor isn't closed. */
fds.push_back(FDPair(fd.release(), lockPath));
}
return true;
}
PathLocks::~PathLocks() PathLocks::~PathLocks()
{ {
try { try {
@ -154,40 +32,10 @@ PathLocks::~PathLocks()
} }
void PathLocks::unlock()
{
for (auto & i : fds) {
if (deletePaths) deleteLockFile(i.second, i.first);
if (close(i.first) == -1)
printError(
"error (ignored): cannot close lock file on '%1%'",
i.second);
debug("lock released on '%1%'", i.second);
}
fds.clear();
}
void PathLocks::setDeletion(bool deletePaths) void PathLocks::setDeletion(bool deletePaths)
{ {
this->deletePaths = deletePaths; this->deletePaths = deletePaths;
} }
FdLock::FdLock(int fd, LockType lockType, bool wait, std::string_view waitMsg)
: fd(fd)
{
if (wait) {
if (!lockFile(fd, lockType, false)) {
printInfo("%s", waitMsg);
acquired = lockFile(fd, lockType, true);
}
} else
acquired = lockFile(fd, lockType, false);
}
} }

View file

@ -5,22 +5,6 @@
namespace nix { 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, int fd);
enum LockType { ltRead, ltWrite, ltNone };
bool lockFile(int fd, LockType lockType, bool wait);
class PathLocks class PathLocks
{ {
private: private:
@ -40,18 +24,6 @@ public:
void setDeletion(bool deletePaths); void setDeletion(bool deletePaths);
}; };
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);
}
};
} }
#include "pathlocks-impl.hh"

View file

@ -0,0 +1,38 @@
#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

@ -0,0 +1,165 @@
#include "pathlocks.hh"
#include "util.hh"
#include "sync.hh"
#include "signals.hh"
#include <cerrno>
#include <cstdlib>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
namespace nix {
using namespace nix::unix;
AutoCloseFD unix::openLockFile(const Path & path, bool create)
{
AutoCloseFD fd;
fd = open(path.c_str(), O_CLOEXEC | O_RDWR | (create ? O_CREAT : 0), 0600);
if (!fd && (create || errno != ENOENT))
throw SysError("opening lock file '%1%'", path);
return fd;
}
void unix::deleteLockFile(const Path & path, int fd)
{
/* 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");
/* 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)
{
int type;
if (lockType == ltRead) type = LOCK_SH;
else if (lockType == ltWrite) type = LOCK_EX;
else if (lockType == ltNone) type = LOCK_UN;
else abort();
if (wait) {
while (flock(fd, type) != 0) {
checkInterrupt();
if (errno != EINTR)
throw SysError("acquiring/releasing lock");
else
return false;
}
} else {
while (flock(fd, type | LOCK_NB) != 0) {
checkInterrupt();
if (errno == EWOULDBLOCK) return false;
if (errno != EINTR)
throw SysError("acquiring/releasing lock");
}
}
return true;
}
bool PathLocks::lockPaths(const PathSet & paths,
const std::string & waitMsg, bool wait)
{
assert(fds.empty());
/* Note that `fds' is built incrementally so that the destructor
will only release those locks that we have already acquired. */
/* Acquire the lock for each path in sorted order. This ensures
that locks are always acquired in the same order, thus
preventing deadlocks. */
for (auto & path : paths) {
checkInterrupt();
Path lockPath = path + ".lock";
debug("locking path '%1%'", path);
AutoCloseFD fd;
while (1) {
/* Open/create the lock file. */
fd = openLockFile(lockPath, true);
/* Acquire an exclusive lock. */
if (!lockFile(fd.get(), ltWrite, false)) {
if (wait) {
if (waitMsg != "") printError(waitMsg);
lockFile(fd.get(), ltWrite, true);
} else {
/* Failed to lock this path; release all other
locks. */
unlock();
return false;
}
}
debug("lock acquired on '%1%'", lockPath);
/* Check that the lock file hasn't become stale (i.e.,
hasn't been unlinked). */
struct stat st;
if (fstat(fd.get(), &st) == -1)
throw SysError("statting lock file '%1%'", lockPath);
if (st.st_size != 0)
/* This lock file has been unlinked, so we're holding
a lock on a deleted file. This means that other
processes may create and acquire a lock on
`lockPath', and proceed. So we must retry. */
debug("open lock file '%1%' has become stale", lockPath);
else
break;
}
/* Use borrow so that the descriptor isn't closed. */
fds.push_back(FDPair(fd.release(), lockPath));
}
return true;
}
void PathLocks::unlock()
{
for (auto & i : fds) {
if (deletePaths) deleteLockFile(i.second, i.first);
if (close(i.first) == -1)
printError(
"error (ignored): cannot close lock file on '%1%'",
i.second);
debug("lock released on '%1%'", i.second);
}
fds.clear();
}
FdLock::FdLock(int fd, LockType lockType, bool wait, std::string_view waitMsg)
: fd(fd)
{
if (wait) {
if (!lockFile(fd, lockType, false)) {
printInfo("%s", waitMsg);
acquired = lockFile(fd, lockType, true);
}
} else
acquired = lockFile(fd, lockType, false);
}
}

View file

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

View file

@ -0,0 +1,16 @@
#include "logging.hh"
#include "pathlocks.hh"
namespace nix {
bool PathLocks::lockPaths(const PathSet & _paths, const std::string & waitMsg, bool wait)
{
return true;
}
void PathLocks::unlock()
{
warn("PathLocks::unlock: not yet implemented");
}
}