about summary refs log tree commit diff
path: root/third_party/nix/src/libstore/pathlocks.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/nix/src/libstore/pathlocks.cc')
-rw-r--r--third_party/nix/src/libstore/pathlocks.cc172
1 files changed, 172 insertions, 0 deletions
diff --git a/third_party/nix/src/libstore/pathlocks.cc b/third_party/nix/src/libstore/pathlocks.cc
new file mode 100644
index 000000000000..eeee5ee1e9c9
--- /dev/null
+++ b/third_party/nix/src/libstore/pathlocks.cc
@@ -0,0 +1,172 @@
+#include "pathlocks.hh"
+
+#include <cerrno>
+#include <cstdlib>
+
+#include <fcntl.h>
+#include <glog/logging.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "sync.hh"
+#include "util.hh"
+
+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(format("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(format("acquiring/releasing lock"));
+      }
+      return false;
+    }
+  } else {
+    while (flock(fd, type | LOCK_NB) != 0) {
+      checkInterrupt();
+      if (errno == EWOULDBLOCK) {
+        return false;
+      }
+      if (errno != EINTR) {
+        throw SysError(format("acquiring/releasing lock"));
+      }
+    }
+  }
+
+  return true;
+}
+
+PathLocks::PathLocks() : deletePaths(false) {}
+
+PathLocks::PathLocks(const PathSet& paths, const string& waitMsg)
+    : deletePaths(false) {
+  lockPaths(paths, waitMsg);
+}
+
+bool PathLocks::lockPaths(const PathSet& paths, const 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";
+
+    DLOG(INFO) << "locking path '" << path << "'";
+
+    AutoCloseFD fd;
+
+    while (true) {
+      /* Open/create the lock file. */
+      fd = openLockFile(lockPath, true);
+
+      /* Acquire an exclusive lock. */
+      if (!lockFile(fd.get(), ltWrite, false)) {
+        if (wait) {
+          if (!waitMsg.empty()) {
+            LOG(WARNING) << waitMsg;
+          }
+          lockFile(fd.get(), ltWrite, true);
+        } else {
+          /* Failed to lock this path; release all other
+             locks. */
+          unlock();
+          return false;
+        }
+      }
+
+      DLOG(INFO) << "lock acquired on '" << 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(format("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. */
+        DLOG(INFO) << "open lock file '" << lockPath << "' has become stale";
+      } else {
+        break;
+      }
+    }
+
+    /* Use borrow so that the descriptor isn't closed. */
+    fds.emplace_back(fd.release(), lockPath);
+  }
+
+  return true;
+}
+
+PathLocks::~PathLocks() {
+  try {
+    unlock();
+  } catch (...) {
+    ignoreException();
+  }
+}
+
+void PathLocks::unlock() {
+  for (auto& i : fds) {
+    if (deletePaths) {
+      deleteLockFile(i.second, i.first);
+    }
+
+    if (close(i.first) == -1) {
+      LOG(WARNING) << "cannot close lock file on '" << i.second << "'";
+    }
+
+    DLOG(INFO) << "lock released on '" << i.second << "'";
+  }
+
+  fds.clear();
+}
+
+void PathLocks::setDeletion(bool deletePaths) {
+  this->deletePaths = deletePaths;
+}
+
+}  // namespace nix