Commit | Line | Data |
---|---|---|
36457566 LC |
1 | #include "pathlocks.hh" |
2 | #include "util.hh" | |
3 | ||
4 | #include <cerrno> | |
5 | #include <cstdlib> | |
6 | ||
7 | #include <sys/types.h> | |
8 | #include <sys/stat.h> | |
9 | #include <fcntl.h> | |
10 | ||
11 | ||
12 | namespace nix { | |
13 | ||
14 | ||
15 | int openLockFile(const Path & path, bool create) | |
16 | { | |
17 | AutoCloseFD fd; | |
18 | ||
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); | |
22 | ||
23 | closeOnExec(fd); | |
24 | ||
25 | return fd.borrow(); | |
26 | } | |
27 | ||
28 | ||
29 | void deleteLockFile(const Path & path, int fd) | |
30 | { | |
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()); | |
54c260e6 | 36 | writeFull(fd, "d"); |
36457566 LC |
37 | /* Note that the result of unlink() is ignored; removing the lock |
38 | file is an optimisation, not a necessity. */ | |
39 | } | |
40 | ||
41 | ||
42 | bool lockFile(int fd, LockType lockType, bool wait) | |
43 | { | |
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 */ | |
52 | ||
53 | if (wait) { | |
54 | while (fcntl(fd, F_SETLKW, &lock) != 0) { | |
55 | checkInterrupt(); | |
56 | if (errno != EINTR) | |
57 | throw SysError(format("acquiring/releasing lock")); | |
58 | } | |
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")); | |
65 | } | |
66 | } | |
67 | ||
68 | return true; | |
69 | } | |
70 | ||
71 | ||
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 */ | |
78 | ||
79 | ||
80 | PathLocks::PathLocks() | |
81 | : deletePaths(false) | |
82 | { | |
83 | } | |
84 | ||
85 | ||
86 | PathLocks::PathLocks(const PathSet & paths, const string & waitMsg) | |
87 | : deletePaths(false) | |
88 | { | |
89 | lockPaths(paths, waitMsg); | |
90 | } | |
91 | ||
92 | ||
93 | bool PathLocks::lockPaths(const PathSet & _paths, | |
94 | const string & waitMsg, bool wait) | |
95 | { | |
96 | assert(fds.empty()); | |
97 | ||
98 | /* Note that `fds' is built incrementally so that the destructor | |
99 | will only release those locks that we have already acquired. */ | |
100 | ||
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(); | |
105 | ||
106 | /* Acquire the lock for each path. */ | |
107 | foreach (Paths::iterator, i, paths) { | |
108 | checkInterrupt(); | |
109 | Path path = *i; | |
110 | Path lockPath = path + ".lock"; | |
111 | ||
112 | debug(format("locking path `%1%'") % path); | |
113 | ||
114 | if (lockedPaths.find(lockPath) != lockedPaths.end()) | |
115 | throw Error("deadlock: trying to re-acquire self-held lock"); | |
116 | ||
117 | AutoCloseFD fd; | |
118 | ||
119 | while (1) { | |
120 | ||
121 | /* Open/create the lock file. */ | |
122 | fd = openLockFile(lockPath, true); | |
123 | ||
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; | |
134 | } | |
135 | } | |
136 | ||
137 | debug(format("lock acquired on `%1%'") % lockPath); | |
138 | ||
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; | |
152 | } | |
153 | ||
154 | /* Use borrow so that the descriptor isn't closed. */ | |
155 | fds.push_back(FDPair(fd.borrow(), lockPath)); | |
156 | lockedPaths.insert(lockPath); | |
157 | } | |
158 | ||
159 | return true; | |
160 | } | |
161 | ||
162 | ||
163 | PathLocks::~PathLocks() | |
164 | { | |
63a5be07 ED |
165 | try { |
166 | unlock(); | |
167 | } catch (...) { | |
168 | ignoreException(); | |
169 | } | |
36457566 LC |
170 | } |
171 | ||
172 | ||
173 | void PathLocks::unlock() | |
174 | { | |
175 | foreach (list<FDPair>::iterator, i, fds) { | |
176 | if (deletePaths) deleteLockFile(i->second, i->first); | |
177 | ||
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); | |
182 | ||
183 | debug(format("lock released on `%1%'") % i->second); | |
184 | } | |
185 | ||
186 | fds.clear(); | |
187 | } | |
188 | ||
189 | ||
190 | void PathLocks::setDeletion(bool deletePaths) | |
191 | { | |
192 | this->deletePaths = deletePaths; | |
193 | } | |
194 | ||
195 | ||
196 | bool pathIsLockedByMe(const Path & path) | |
197 | { | |
198 | Path lockPath = path + ".lock"; | |
199 | return lockedPaths.find(lockPath) != lockedPaths.end(); | |
200 | } | |
201 | ||
202 | ||
203 | } |