gnu: signify: Update to 26.
[guix.git] / nix / libstore / pathlocks.cc
blob9797ddd7abf12e29886ee6032003f7bde68ece2c
1 #include "pathlocks.hh"
2 #include "util.hh"
4 #include <cerrno>
5 #include <cstdlib>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <fcntl.h>
12 namespace nix {
15 int openLockFile(const Path & path, bool create)
17 AutoCloseFD fd;
19 fd = open(path.c_str(), O_RDWR | (create ? O_CREAT : 0), 0600);
20 if (fd == -1 && (create || errno != ENOENT))
21 throw SysError(format("opening lock file `%1%'") % path);
23 closeOnExec(fd);
25 return fd.borrow();
29 void deleteLockFile(const Path & path, int fd)
31 /* Get rid of the lock file. Have to be careful not to introduce
32 races. Write a (meaningless) token to the file to indicate to
33 other processes waiting on this lock that the lock is stale
34 (deleted). */
35 unlink(path.c_str());
36 writeFull(fd, "d");
37 /* Note that the result of unlink() is ignored; removing the lock
38 file is an optimisation, not a necessity. */
42 bool lockFile(int fd, LockType lockType, bool wait)
44 struct flock lock;
45 if (lockType == ltRead) lock.l_type = F_RDLCK;
46 else if (lockType == ltWrite) lock.l_type = F_WRLCK;
47 else if (lockType == ltNone) lock.l_type = F_UNLCK;
48 else abort();
49 lock.l_whence = SEEK_SET;
50 lock.l_start = 0;
51 lock.l_len = 0; /* entire file */
53 if (wait) {
54 while (fcntl(fd, F_SETLKW, &lock) != 0) {
55 checkInterrupt();
56 if (errno != EINTR)
57 throw SysError(format("acquiring/releasing lock"));
59 } else {
60 while (fcntl(fd, F_SETLK, &lock) != 0) {
61 checkInterrupt();
62 if (errno == EACCES || errno == EAGAIN) return false;
63 if (errno != EINTR)
64 throw SysError(format("acquiring/releasing lock"));
68 return true;
72 /* This enables us to check whether are not already holding a lock on
73 a file ourselves. POSIX locks (fcntl) suck in this respect: if we
74 close a descriptor, the previous lock will be closed as well. And
75 there is no way to query whether we already have a lock (F_GETLK
76 only works on locks held by other processes). */
77 static StringSet lockedPaths; /* !!! not thread-safe */
80 PathLocks::PathLocks()
81 : deletePaths(false)
86 PathLocks::PathLocks(const PathSet & paths, const string & waitMsg)
87 : deletePaths(false)
89 lockPaths(paths, waitMsg);
93 bool PathLocks::lockPaths(const PathSet & _paths,
94 const string & waitMsg, bool wait)
96 assert(fds.empty());
98 /* Note that `fds' is built incrementally so that the destructor
99 will only release those locks that we have already acquired. */
101 /* Sort the paths. This assures that locks are always acquired in
102 the same order, thus preventing deadlocks. */
103 Paths paths(_paths.begin(), _paths.end());
104 paths.sort();
106 /* Acquire the lock for each path. */
107 foreach (Paths::iterator, i, paths) {
108 checkInterrupt();
109 Path path = *i;
110 Path lockPath = path + ".lock";
112 debug(format("locking path `%1%'") % path);
114 if (lockedPaths.find(lockPath) != lockedPaths.end())
115 throw Error("deadlock: trying to re-acquire self-held lock");
117 AutoCloseFD fd;
119 while (1) {
121 /* Open/create the lock file. */
122 fd = openLockFile(lockPath, true);
124 /* Acquire an exclusive lock. */
125 if (!lockFile(fd, ltWrite, false)) {
126 if (wait) {
127 if (waitMsg != "") printMsg(lvlError, waitMsg);
128 lockFile(fd, ltWrite, true);
129 } else {
130 /* Failed to lock this path; release all other
131 locks. */
132 unlock();
133 return false;
137 debug(format("lock acquired on `%1%'") % lockPath);
139 /* Check that the lock file hasn't become stale (i.e.,
140 hasn't been unlinked). */
141 struct stat st;
142 if (fstat(fd, &st) == -1)
143 throw SysError(format("statting lock file `%1%'") % lockPath);
144 if (st.st_size != 0)
145 /* This lock file has been unlinked, so we're holding
146 a lock on a deleted file. This means that other
147 processes may create and acquire a lock on
148 `lockPath', and proceed. So we must retry. */
149 debug(format("open lock file `%1%' has become stale") % lockPath);
150 else
151 break;
154 /* Use borrow so that the descriptor isn't closed. */
155 fds.push_back(FDPair(fd.borrow(), lockPath));
156 lockedPaths.insert(lockPath);
159 return true;
163 PathLocks::~PathLocks()
165 try {
166 unlock();
167 } catch (...) {
168 ignoreException();
173 void PathLocks::unlock()
175 foreach (list<FDPair>::iterator, i, fds) {
176 if (deletePaths) deleteLockFile(i->second, i->first);
178 lockedPaths.erase(i->second);
179 if (close(i->first) == -1)
180 printMsg(lvlError,
181 format("error (ignored): cannot close lock file on `%1%'") % i->second);
183 debug(format("lock released on `%1%'") % i->second);
186 fds.clear();
190 void PathLocks::setDeletion(bool deletePaths)
192 this->deletePaths = deletePaths;
196 bool pathIsLockedByMe(const Path & path)
198 Path lockPath = path + ".lock";
199 return lockedPaths.find(lockPath) != lockedPaths.end();