1 : #include <cerrno>
2 :
3 : #include <sys/types.h>
4 : #include <sys/stat.h>
5 : #include <fcntl.h>
6 :
7 : #include "pathlocks.hh"
8 :
9 :
10 : bool lockFile(int fd, LockType lockType, bool wait)
11 195 : {
12 195 : struct flock lock;
13 195 : if (lockType == ltRead) lock.l_type = F_RDLCK;
14 156 : else if (lockType == ltWrite) lock.l_type = F_WRLCK;
15 5 : else if (lockType == ltNone) lock.l_type = F_UNLCK;
16 0 : else abort();
17 195 : lock.l_whence = SEEK_SET;
18 195 : lock.l_start = 0;
19 195 : lock.l_len = 0; /* entire file */
20 :
21 195 : if (wait) {
22 156 : while (fcntl(fd, F_SETLKW, &lock) != 0) {
23 0 : checkInterrupt();
24 0 : if (errno != EINTR)
25 0 : throw SysError(format("acquiring/releasing lock"));
26 : }
27 : } else {
28 39 : while (fcntl(fd, F_SETLK, &lock) != 0) {
29 5 : checkInterrupt();
30 5 : if (errno == EACCES || errno == EAGAIN) return false;
31 0 : if (errno != EINTR)
32 0 : throw SysError(format("acquiring/releasing lock"));
33 : }
34 : }
35 :
36 190 : return true;
37 : }
38 :
39 :
40 : /* This enables us to check whether are not already holding a lock on
41 : a file ourselves. POSIX locks (fcntl) suck in this respect: if we
42 : close a descriptor, the previous lock will be closed as well. And
43 : there is no way to query whether we already have a lock (F_GETLK
44 : only works on locks held by other processes). */
45 51 : static StringSet lockedPaths; /* !!! not thread-safe */
46 :
47 :
48 : PathLocks::PathLocks()
49 68 : : deletePaths(false)
50 320 : {
51 : }
52 :
53 :
54 : PathLocks::PathLocks(const PathSet & paths)
55 58 : : deletePaths(false)
56 58 : {
57 58 : lockPaths(paths);
58 : }
59 :
60 :
61 : void PathLocks::lockPaths(const PathSet & _paths)
62 107 : {
63 : /* May be called only once! */
64 107 : assert(this->paths.empty());
65 :
66 : /* Note that `fds' is built incrementally so that the destructor
67 : will only release those locks that we have already acquired. */
68 :
69 : /* Sort the paths. This assures that locks are always acquired in
70 : the same order, thus preventing deadlocks. */
71 107 : Paths paths(_paths.begin(), _paths.end());
72 107 : paths.sort();
73 :
74 : /* Acquire the lock for each path. */
75 214 : for (Paths::iterator i = paths.begin(); i != paths.end(); i++) {
76 107 : checkInterrupt();
77 107 : Path path = *i;
78 107 : Path lockPath = path + ".lock";
79 :
80 107 : debug(format("locking path `%1%'") % path);
81 :
82 107 : if (lockedPaths.find(lockPath) != lockedPaths.end()) {
83 0 : debug(format("already holding lock on `%1%'") % lockPath);
84 0 : continue;
85 : }
86 :
87 : /* Open/create the lock file. */
88 107 : int fd = open(lockPath.c_str(), O_WRONLY | O_CREAT, 0666);
89 107 : if (fd == -1)
90 0 : throw SysError(format("opening lock file `%1%'") % lockPath);
91 :
92 107 : fds.push_back(fd);
93 107 : this->paths.push_back(lockPath);
94 :
95 : /* Acquire an exclusive lock. */
96 107 : lockFile(fd, ltWrite, true);
97 :
98 107 : debug(format("lock acquired on `%1%'") % lockPath);
99 :
100 107 : lockedPaths.insert(lockPath);
101 : }
102 : }
103 :
104 :
105 : PathLocks::~PathLocks()
106 252 : {
107 233 : for (list<int>::iterator i = fds.begin(); i != fds.end(); i++)
108 107 : if (close(*i) != 0) throw SysError("closing fd");
109 :
110 233 : for (Paths::iterator i = paths.begin(); i != paths.end(); i++) {
111 107 : checkInterrupt();
112 107 : if (deletePaths) {
113 : /* This is not safe in general! */
114 106 : unlink(i->c_str());
115 : /* Note that the result of unlink() is ignored; removing
116 : the lock file is an optimisation, not a necessity. */
117 : }
118 107 : lockedPaths.erase(*i);
119 107 : debug(format("lock released on `%1%'") % *i);
120 : }
121 : }
122 :
123 :
124 : void PathLocks::setDeletion(bool deletePaths)
125 106 : {
126 106 : this->deletePaths = deletePaths;
127 51 : }
|