projects/guix·blob javascript-team · refs

205 lines · 5.3 KiB

1#include "pathlocks.hh"
2#include "util.hh"
3
4#include <cerrno>
5#include <cstdlib>
6#include <cassert>
7#include <format>
8
9#include <sys/types.h>
10#include <sys/stat.h>
11#include <fcntl.h>
12
13
14namespace nix {
15
16
17int openLockFile(const Path & path, bool create)
18{
19 AutoCloseFD fd;
20
21 fd = open(path.c_str(), O_RDWR | (create ? O_CREAT : 0), 0600);
22 if (fd == -1 && (create || errno != ENOENT))
23 throw SysError(std::format("opening lock file `{}'", path));
24
25 closeOnExec(fd);
26
27 return fd.borrow();
28}
29
30
31void deleteLockFile(const Path & path, int fd)
32{
33 /* Get rid of the lock file. Have to be careful not to introduce
34 races. Write a (meaningless) token to the file to indicate to
35 other processes waiting on this lock that the lock is stale
36 (deleted). */
37 unlink(path.c_str());
38 writeFull(fd, "d");
39 /* Note that the result of unlink() is ignored; removing the lock
40 file is an optimisation, not a necessity. */
41}
42
43
44bool lockFile(int fd, LockType lockType, bool wait)
45{
46 struct flock lock;
47 if (lockType == ltRead) lock.l_type = F_RDLCK;
48 else if (lockType == ltWrite) lock.l_type = F_WRLCK;
49 else if (lockType == ltNone) lock.l_type = F_UNLCK;
50 else abort();
51 lock.l_whence = SEEK_SET;
52 lock.l_start = 0;
53 lock.l_len = 0; /* entire file */
54
55 if (wait) {
56 while (fcntl(fd, F_SETLKW, &lock) != 0) {
57 checkInterrupt();
58 if (errno != EINTR)
59 throw SysError("acquiring/releasing lock");
60 }
61 } else {
62 while (fcntl(fd, F_SETLK, &lock) != 0) {
63 checkInterrupt();
64 if (errno == EACCES || errno == EAGAIN) return false;
65 if (errno != EINTR)
66 throw SysError("acquiring/releasing lock");
67 }
68 }
69
70 return true;
71}
72
73
74/* This enables us to check whether are not already holding a lock on
75 a file ourselves. POSIX locks (fcntl) suck in this respect: if we
76 close a descriptor, the previous lock will be closed as well. And
77 there is no way to query whether we already have a lock (F_GETLK
78 only works on locks held by other processes). */
79static StringSet lockedPaths; /* !!! not thread-safe */
80
81
82PathLocks::PathLocks()
83 : deletePaths(false)
84{
85}
86
87
88PathLocks::PathLocks(const PathSet & paths, const string & waitMsg)
89 : deletePaths(false)
90{
91 lockPaths(paths, waitMsg);
92}
93
94
95bool PathLocks::lockPaths(const PathSet & _paths,
96 const string & waitMsg, bool wait)
97{
98 assert(fds.empty());
99
100 /* Note that `fds' is built incrementally so that the destructor
101 will only release those locks that we have already acquired. */
102
103 /* Sort the paths. This assures that locks are always acquired in
104 the same order, thus preventing deadlocks. */
105 Paths paths(_paths.begin(), _paths.end());
106 paths.sort();
107
108 /* Acquire the lock for each path. */
109 for (auto& i : paths) {
110 checkInterrupt();
111 Path path = i;
112 Path lockPath = path + ".lock";
113
114 debug(std::format("locking path `{}'", path));
115
116 if (lockedPaths.find(lockPath) != lockedPaths.end())
117 throw Error("deadlock: trying to re-acquire self-held lock");
118
119 AutoCloseFD fd;
120
121 while (1) {
122
123 /* Open/create the lock file. */
124 fd = openLockFile(lockPath, true);
125
126 /* Acquire an exclusive lock. */
127 if (!lockFile(fd, ltWrite, false)) {
128 if (wait) {
129 if (waitMsg != "") printMsg(lvlError, waitMsg);
130 lockFile(fd, ltWrite, true);
131 } else {
132 /* Failed to lock this path; release all other
133 locks. */
134 unlock();
135 return false;
136 }
137 }
138
139 debug(std::format("lock acquired on `{}'", lockPath));
140
141 /* Check that the lock file hasn't become stale (i.e.,
142 hasn't been unlinked). */
143 struct stat st;
144 if (fstat(fd, &st) == -1)
145 throw SysError(std::format("statting lock file `{}'", lockPath));
146 if (st.st_size != 0)
147 /* This lock file has been unlinked, so we're holding
148 a lock on a deleted file. This means that other
149 processes may create and acquire a lock on
150 `lockPath', and proceed. So we must retry. */
151 debug(std::format("open lock file `{}' has become stale", lockPath));
152 else
153 break;
154 }
155
156 /* Use borrow so that the descriptor isn't closed. */
157 fds.push_back(FDPair(fd.borrow(), lockPath));
158 lockedPaths.insert(lockPath);
159 }
160
161 return true;
162}
163
164
165PathLocks::~PathLocks()
166{
167 try {
168 unlock();
169 } catch (...) {
170 ignoreException();
171 }
172}
173
174
175void PathLocks::unlock()
176{
177 for (auto& i : fds) {
178 if (deletePaths) deleteLockFile(i.second, i.first);
179
180 lockedPaths.erase(i.second);
181 if (close(i.first) == -1)
182 printMsg(lvlError,
183 std::format("error (ignored): cannot close lock file on `{}'", i.second));
184
185 debug(std::format("lock released on `{}'", i.second));
186 }
187
188 fds.clear();
189}
190
191
192void PathLocks::setDeletion(bool deletePaths)
193{
194 this->deletePaths = deletePaths;
195}
196
197
198bool pathIsLockedByMe(const Path & path)
199{
200 Path lockPath = path + ".lock";
201 return lockedPaths.find(lockPath) != lockedPaths.end();
202}
203
204
205}