1 #include "pathlocks.hh"
2 #include "util.hh"
3 #include "sync.hh"
4
5 #include <cerrno>
6 #include <cstdlib>
7
8 #include <fcntl.h>
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <sys/file.h>
12
13
14 namespace nix {
15
16
openLockFile(const Path & path,bool create)17 AutoCloseFD openLockFile(const Path & path, bool create)
18 {
19 AutoCloseFD fd;
20
21 fd = open(path.c_str(), O_CLOEXEC | O_RDWR | (create ? O_CREAT : 0), 0600);
22 if (!fd && (create || errno != ENOENT))
23 throw SysError(format("opening lock file '%1%'") % path);
24
25 return fd;
26 }
27
28
deleteLockFile(const Path & path,int fd)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
lockFile(int fd,LockType lockType,bool wait)42 bool lockFile(int fd, LockType lockType, bool wait)
43 {
44 int type;
45 if (lockType == ltRead) type = LOCK_SH;
46 else if (lockType == ltWrite) type = LOCK_EX;
47 else if (lockType == ltNone) type = LOCK_UN;
48 else abort();
49
50 if (wait) {
51 while (flock(fd, type) != 0) {
52 checkInterrupt();
53 if (errno != EINTR)
54 throw SysError(format("acquiring/releasing lock"));
55 else
56 return false;
57 }
58 } else {
59 while (flock(fd, type | LOCK_NB) != 0) {
60 checkInterrupt();
61 if (errno == EWOULDBLOCK) return false;
62 if (errno != EINTR)
63 throw SysError(format("acquiring/releasing lock"));
64 }
65 }
66
67 return true;
68 }
69
70
PathLocks()71 PathLocks::PathLocks()
72 : deletePaths(false)
73 {
74 }
75
76
PathLocks(const PathSet & paths,const string & waitMsg)77 PathLocks::PathLocks(const PathSet & paths, const string & waitMsg)
78 : deletePaths(false)
79 {
80 lockPaths(paths, waitMsg);
81 }
82
83
lockPaths(const PathSet & paths,const string & waitMsg,bool wait)84 bool PathLocks::lockPaths(const PathSet & paths,
85 const string & waitMsg, bool wait)
86 {
87 assert(fds.empty());
88
89 /* Note that `fds' is built incrementally so that the destructor
90 will only release those locks that we have already acquired. */
91
92 /* Acquire the lock for each path in sorted order. This ensures
93 that locks are always acquired in the same order, thus
94 preventing deadlocks. */
95 for (auto & path : paths) {
96 checkInterrupt();
97 Path lockPath = path + ".lock";
98
99 debug(format("locking path '%1%'") % path);
100
101 AutoCloseFD fd;
102
103 while (1) {
104
105 /* Open/create the lock file. */
106 fd = openLockFile(lockPath, true);
107
108 /* Acquire an exclusive lock. */
109 if (!lockFile(fd.get(), ltWrite, false)) {
110 if (wait) {
111 if (waitMsg != "") printError(waitMsg);
112 lockFile(fd.get(), ltWrite, true);
113 } else {
114 /* Failed to lock this path; release all other
115 locks. */
116 unlock();
117 return false;
118 }
119 }
120
121 debug(format("lock acquired on '%1%'") % lockPath);
122
123 /* Check that the lock file hasn't become stale (i.e.,
124 hasn't been unlinked). */
125 struct stat st;
126 if (fstat(fd.get(), &st) == -1)
127 throw SysError(format("statting lock file '%1%'") % lockPath);
128 if (st.st_size != 0)
129 /* This lock file has been unlinked, so we're holding
130 a lock on a deleted file. This means that other
131 processes may create and acquire a lock on
132 `lockPath', and proceed. So we must retry. */
133 debug(format("open lock file '%1%' has become stale") % lockPath);
134 else
135 break;
136 }
137
138 /* Use borrow so that the descriptor isn't closed. */
139 fds.push_back(FDPair(fd.release(), lockPath));
140 }
141
142 return true;
143 }
144
145
~PathLocks()146 PathLocks::~PathLocks()
147 {
148 try {
149 unlock();
150 } catch (...) {
151 ignoreException();
152 }
153 }
154
155
unlock()156 void PathLocks::unlock()
157 {
158 for (auto & i : fds) {
159 if (deletePaths) deleteLockFile(i.second, i.first);
160
161 if (close(i.first) == -1)
162 printError(
163 format("error (ignored): cannot close lock file on '%1%'") % i.second);
164
165 debug(format("lock released on '%1%'") % i.second);
166 }
167
168 fds.clear();
169 }
170
171
setDeletion(bool deletePaths)172 void PathLocks::setDeletion(bool deletePaths)
173 {
174 this->deletePaths = deletePaths;
175 }
176
177
178 }
179