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 : #ifdef __CYGWIN__
12 : #include <windows.h>
13 : #include <sys/cygwin.h>
14 : #endif
15 :
16 :
17 : namespace nix {
18 :
19 :
20 14029 : int openLockFile(const Path & path, bool create)
21 : {
22 14029 : AutoCloseFD fd;
23 :
24 : #ifdef __CYGWIN__
25 : /* On Cygwin we have to open the lock file without "DELETE"
26 : sharing mode; otherwise Windows will allow open lock files to
27 : be deleted (which is almost but not quite what Unix does). */
28 : char win32Path[MAX_PATH + 1];
29 : cygwin_conv_to_full_win32_path(path.c_str(), win32Path);
30 :
31 : SECURITY_ATTRIBUTES sa; /* required, otherwise inexplicably bad shit happens */
32 : sa.nLength = sizeof sa;
33 : sa.lpSecurityDescriptor = 0;
34 : sa.bInheritHandle = TRUE;
35 : HANDLE h = CreateFile(win32Path, GENERIC_READ | GENERIC_WRITE,
36 : FILE_SHARE_READ | FILE_SHARE_WRITE, &sa,
37 : (create ? OPEN_ALWAYS : OPEN_EXISTING),
38 : FILE_ATTRIBUTE_NORMAL, 0);
39 : if (h == INVALID_HANDLE_VALUE) {
40 : if (create || GetLastError() != ERROR_FILE_NOT_FOUND)
41 : throw Error(format("opening lock file `%1%'") % path);
42 : fd = -1;
43 : }
44 : else
45 : fd = cygwin_attach_handle_to_fd((char *) path.c_str(), -1, h, 1, O_RDWR);
46 : #else
47 14029 : fd = open(path.c_str(), O_RDWR | (create ? O_CREAT : 0), 0666);
48 14029 : if (fd == -1 && (create || errno != ENOENT))
49 0 : throw SysError(format("opening lock file `%1%'") % path);
50 : #endif
51 :
52 14029 : return fd.borrow();
53 : }
54 :
55 :
56 13482 : void deleteLockFilePreClose(const Path & path, int fd)
57 : {
58 : #ifndef __CYGWIN__
59 : /* Get rid of the lock file. Have to be careful not to introduce
60 : races. */
61 : /* On Unix, write a (meaningless) token to the file to indicate to
62 : other processes waiting on this lock that the lock is stale
63 : (deleted). */
64 13482 : unlink(path.c_str());
65 13482 : writeFull(fd, (const unsigned char *) "d", 1);
66 : /* Note that the result of unlink() is ignored; removing the lock
67 : file is an optimisation, not a necessity. */
68 : #endif
69 13482 : }
70 :
71 :
72 13482 : void deleteLockFilePostClose(const Path & path)
73 : {
74 : #ifdef __CYGWIN__
75 : /* On Windows, just try to delete the lock file. This will fail
76 : if anybody still has the file open. We cannot use unlink()
77 : here, because Cygwin emulates Unix semantics of allowing an
78 : open file to be deleted (but fakes it - the file isn't actually
79 : deleted until later, so a file with the same name cannot be
80 : created in the meantime). */
81 : char win32Path[MAX_PATH + 1];
82 : cygwin_conv_to_full_win32_path(path.c_str(), win32Path);
83 : if (DeleteFile(win32Path))
84 : debug(format("delete of `%1%' succeeded") % path.c_str());
85 : else
86 : /* Not an error: probably means that the lock is still opened
87 : by someone else. */
88 : debug(format("delete of `%1%' failed: %2%") % path.c_str() % GetLastError());
89 : #endif
90 13482 : }
91 :
92 :
93 16405 : bool lockFile(int fd, LockType lockType, bool wait)
94 : {
95 : struct flock lock;
96 16405 : if (lockType == ltRead) lock.l_type = F_RDLCK;
97 14630 : else if (lockType == ltWrite) lock.l_type = F_WRLCK;
98 0 : else if (lockType == ltNone) lock.l_type = F_UNLCK;
99 0 : else abort();
100 16405 : lock.l_whence = SEEK_SET;
101 16405 : lock.l_start = 0;
102 16405 : lock.l_len = 0; /* entire file */
103 :
104 16405 : if (wait) {
105 4586 : while (fcntl(fd, F_SETLKW, &lock) != 0) {
106 0 : checkInterrupt();
107 0 : if (errno != EINTR)
108 0 : throw SysError(format("acquiring/releasing lock"));
109 : }
110 : } else {
111 28224 : while (fcntl(fd, F_SETLK, &lock) != 0) {
112 29 : checkInterrupt();
113 29 : if (errno == EACCES || errno == EAGAIN) return false;
114 0 : if (errno != EINTR)
115 0 : throw SysError(format("acquiring/releasing lock"));
116 : }
117 : }
118 :
119 16376 : return true;
120 : }
121 :
122 :
123 : /* This enables us to check whether are not already holding a lock on
124 : a file ourselves. POSIX locks (fcntl) suck in this respect: if we
125 : close a descriptor, the previous lock will be closed as well. And
126 : there is no way to query whether we already have a lock (F_GETLK
127 : only works on locks held by other processes). */
128 1151 : static StringSet lockedPaths; /* !!! not thread-safe */
129 :
130 :
131 5648 : PathLocks::PathLocks()
132 5648 : : deletePaths(false)
133 : {
134 5648 : }
135 :
136 :
137 8309 : PathLocks::PathLocks(const PathSet & paths, const string & waitMsg)
138 8309 : : deletePaths(false)
139 : {
140 8309 : lockPaths(paths, waitMsg);
141 8309 : }
142 :
143 :
144 8523 : void PathLocks::lockPaths(const PathSet & _paths, const string & waitMsg)
145 : {
146 : /* May be called only once! */
147 8523 : assert(fds.empty());
148 :
149 : /* Note that `fds' is built incrementally so that the destructor
150 : will only release those locks that we have already acquired. */
151 :
152 : /* Sort the paths. This assures that locks are always acquired in
153 : the same order, thus preventing deadlocks. */
154 8523 : Paths paths(_paths.begin(), _paths.end());
155 8523 : paths.sort();
156 :
157 : /* Acquire the lock for each path. */
158 22015 : for (Paths::iterator i = paths.begin(); i != paths.end(); i++) {
159 13492 : checkInterrupt();
160 13492 : Path path = *i;
161 13492 : Path lockPath = path + ".lock";
162 :
163 13492 : debug(format("locking path `%1%'") % path);
164 :
165 13492 : if (lockedPaths.find(lockPath) != lockedPaths.end())
166 0 : throw Error("deadlock: trying to re-acquire self-held lock");
167 :
168 13492 : AutoCloseFD fd;
169 :
170 27 : while (1) {
171 :
172 : /* Open/create the lock file. */
173 13519 : fd = openLockFile(lockPath, true);
174 :
175 : /* Acquire an exclusive lock. */
176 13519 : if (!lockFile(fd, ltWrite, false)) {
177 27 : if (waitMsg != "") printMsg(lvlError, waitMsg);
178 27 : lockFile(fd, ltWrite, true);
179 : }
180 :
181 13519 : debug(format("lock acquired on `%1%'") % lockPath);
182 :
183 : /* Check that the lock file hasn't become stale (i.e.,
184 : hasn't been unlinked). */
185 : struct stat st;
186 13519 : if (fstat(fd, &st) == -1)
187 0 : throw SysError(format("statting lock file `%1%'") % lockPath);
188 13519 : if (st.st_size != 0)
189 : /* This lock file has been unlinked, so we're holding
190 : a lock on a deleted file. This means that other
191 : processes may create and acquire a lock on
192 : `lockPath', and proceed. So we must retry. */
193 27 : debug(format("open lock file `%1%' has become stale") % lockPath);
194 : else
195 13492 : break;
196 : }
197 :
198 : /* Use borrow so that the descriptor isn't closed. */
199 13492 : fds.push_back(FDPair(fd.borrow(), lockPath));
200 13492 : lockedPaths.insert(lockPath);
201 8523 : }
202 8523 : }
203 :
204 :
205 13957 : PathLocks::~PathLocks()
206 : {
207 27449 : for (list<FDPair>::iterator i = fds.begin(); i != fds.end(); i++) {
208 13492 : if (deletePaths) deleteLockFilePreClose(i->second, i->first);
209 :
210 13492 : lockedPaths.erase(i->second);
211 13492 : if (close(i->first) == -1)
212 0 : printMsg(lvlError,
213 : format("error (ignored): cannot close lock file on `%1%'") % i->second);
214 :
215 13492 : if (deletePaths) deleteLockFilePostClose(i->second);
216 :
217 13492 : debug(format("lock released on `%1%'") % i->second);
218 : }
219 13957 : }
220 :
221 :
222 8513 : void PathLocks::setDeletion(bool deletePaths)
223 : {
224 8513 : this->deletePaths = deletePaths;
225 8513 : }
226 :
227 :
228 299 : bool pathIsLockedByMe(const Path & path)
229 : {
230 299 : Path lockPath = path + ".lock";
231 299 : return lockedPaths.find(lockPath) != lockedPaths.end();
232 : }
233 :
234 0 :
235 1106 : }
|