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