1 #include "pathlocks.hh"
15 int openLockFile(const Path
& path
, bool create
)
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
);
29 void deleteLockFile(const Path
& path
, int fd
)
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
37 /* Note that the result of unlink() is ignored; removing the lock
38 file is an optimisation, not a necessity. */
42 bool lockFile(int fd
, LockType lockType
, bool wait
)
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
;
49 lock
.l_whence
= SEEK_SET
;
51 lock
.l_len
= 0; /* entire file */
54 while (fcntl(fd
, F_SETLKW
, &lock
) != 0) {
57 throw SysError(format("acquiring/releasing lock"));
60 while (fcntl(fd
, F_SETLK
, &lock
) != 0) {
62 if (errno
== EACCES
|| errno
== EAGAIN
) return false;
64 throw SysError(format("acquiring/releasing lock"));
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 */
80 PathLocks::PathLocks()
86 PathLocks::PathLocks(const PathSet
& paths
, const string
& waitMsg
)
89 lockPaths(paths
, waitMsg
);
93 bool PathLocks::lockPaths(const PathSet
& _paths
,
94 const string
& waitMsg
, bool wait
)
98 /* Note that `fds' is built incrementally so that the destructor
99 will only release those locks that we have already acquired. */
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());
106 /* Acquire the lock for each path. */
107 foreach (Paths::iterator
, i
, paths
) {
110 Path lockPath
= path
+ ".lock";
112 debug(format("locking path `%1%'") % path
);
114 if (lockedPaths
.find(lockPath
) != lockedPaths
.end())
115 throw Error("deadlock: trying to re-acquire self-held lock");
121 /* Open/create the lock file. */
122 fd
= openLockFile(lockPath
, true);
124 /* Acquire an exclusive lock. */
125 if (!lockFile(fd
, ltWrite
, false)) {
127 if (waitMsg
!= "") printMsg(lvlError
, waitMsg
);
128 lockFile(fd
, ltWrite
, true);
130 /* Failed to lock this path; release all other
137 debug(format("lock acquired on `%1%'") % lockPath
);
139 /* Check that the lock file hasn't become stale (i.e.,
140 hasn't been unlinked). */
142 if (fstat(fd
, &st
) == -1)
143 throw SysError(format("statting lock file `%1%'") % lockPath
);
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
);
154 /* Use borrow so that the descriptor isn't closed. */
155 fds
.push_back(FDPair(fd
.borrow(), lockPath
));
156 lockedPaths
.insert(lockPath
);
163 PathLocks::~PathLocks()
173 void PathLocks::unlock()
175 foreach (list
<FDPair
>::iterator
, i
, fds
) {
176 if (deletePaths
) deleteLockFile(i
->second
, i
->first
);
178 lockedPaths
.erase(i
->second
);
179 if (close(i
->first
) == -1)
181 format("error (ignored): cannot close lock file on `%1%'") % i
->second
);
183 debug(format("lock released on `%1%'") % i
->second
);
190 void PathLocks::setDeletion(bool deletePaths
)
192 this->deletePaths
= deletePaths
;
196 bool pathIsLockedByMe(const Path
& path
)
198 Path lockPath
= path
+ ".lock";
199 return lockedPaths
.find(lockPath
) != lockedPaths
.end();