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