Merge branch 'nix'.
[jackhill/guix/guix.git] / nix / libstore / pathlocks.cc
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());
36 writeFull(fd, "d");
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 {
165 unlock();
166 }
167
168
169 void PathLocks::unlock()
170 {
171 foreach (list<FDPair>::iterator, i, fds) {
172 if (deletePaths) deleteLockFile(i->second, i->first);
173
174 lockedPaths.erase(i->second);
175 if (close(i->first) == -1)
176 printMsg(lvlError,
177 format("error (ignored): cannot close lock file on `%1%'") % i->second);
178
179 debug(format("lock released on `%1%'") % i->second);
180 }
181
182 fds.clear();
183 }
184
185
186 void PathLocks::setDeletion(bool deletePaths)
187 {
188 this->deletePaths = deletePaths;
189 }
190
191
192 bool pathIsLockedByMe(const Path & path)
193 {
194 Path lockPath = path + ".lock";
195 return lockedPaths.find(lockPath) != lockedPaths.end();
196 }
197
198
199 }