2 * Copyright (C) 2010-2015 Joel Rosdahl
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the Free
6 * Software Foundation; either version 3 of the License, or (at your option)
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * You should have received a copy of the GNU General Public License along with
15 * this program; if not, write to the Free Software Foundation, Inc., 51
16 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 * This function acquires a lockfile for the given path. Returns true if the
23 * lock was acquired, otherwise false. If the lock has been considered stale
24 * for the number of microseconds specified by staleness_limit, the function
25 * will (if possible) break the lock and then try to acquire it again. The
26 * staleness limit should be reasonably larger than the longest time the lock
27 * can be expected to be held, and the updates of the locked path should
28 * probably be made with an atomic rename(2) to avoid corruption in the rare
29 * case that the lock is broken by another process.
32 lockfile_acquire(const char *path, unsigned staleness_limit)
35 char *lockfile = format("%s.lock", path);
36 char *my_content = NULL, *content = NULL, *initial_content = NULL;
37 const char *hostname = get_hostname();
38 bool acquired = false;
40 const size_t bufsize = 1024;
45 unsigned to_sleep = 1000, slept = 0; /* Microseconds. */
49 my_content = format("%s:%d:%d", hostname, (int)getpid(), (int)time(NULL));
52 fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL|O_BINARY, 0666);
55 cc_log("lockfile_acquire: open WRONLY %s: %s", lockfile, strerror(errno));
56 if (saved_errno == ENOENT) {
57 /* Directory doesn't exist? */
58 if (create_parent_dirs(lockfile) == 0) {
63 if (saved_errno != EEXIST) {
64 /* Directory doesn't exist or isn't writable? */
67 /* Someone else has the lock. */
68 fd = open(lockfile, O_RDONLY|O_BINARY);
70 if (errno == ENOENT) {
72 * The file was removed after the open() call above, so retry
77 cc_log("lockfile_acquire: open RDONLY %s: %s",
78 lockfile, strerror(errno));
83 content = x_malloc(bufsize);
84 if ((len = read(fd, content, bufsize - 1)) == -1) {
85 cc_log("lockfile_acquire: read %s: %s", lockfile, strerror(errno));
92 /* We got the lock. */
93 if (write(fd, my_content, strlen(my_content)) == -1) {
94 cc_log("lockfile_acquire: write %s: %s", lockfile, strerror(errno));
104 ret = symlink(my_content, lockfile);
106 /* We got the lock. */
111 cc_log("lockfile_acquire: symlink %s: %s", lockfile, strerror(saved_errno));
112 if (saved_errno == ENOENT) {
113 /* Directory doesn't exist? */
114 if (create_parent_dirs(lockfile) == 0) {
119 if (saved_errno == EPERM) {
121 * The file system does not support symbolic links. We have no choice but
122 * to grant the lock anyway.
127 if (saved_errno != EEXIST) {
128 /* Directory doesn't exist or isn't writable? */
132 content = x_readlink(lockfile);
134 if (errno == ENOENT) {
136 * The symlink was removed after the symlink() call above, so retry
141 cc_log("lockfile_acquire: readlink %s: %s", lockfile, strerror(errno));
147 if (str_eq(content, my_content)) {
148 /* Lost NFS reply? */
149 cc_log("lockfile_acquire: symlink %s failed but we got the lock anyway",
155 * A possible improvement here would be to check if the process holding the
156 * lock is still alive and break the lock early if it isn't.
158 cc_log("lockfile_acquire: lock info for %s: %s", lockfile, content);
159 if (!initial_content) {
160 initial_content = x_strdup(content);
162 if (slept > staleness_limit) {
163 if (str_eq(content, initial_content)) {
164 /* The lock seems to be stale -- break it. */
165 cc_log("lockfile_acquire: breaking %s", lockfile);
166 if (lockfile_acquire(lockfile, staleness_limit)) {
167 lockfile_release(path);
168 lockfile_release(lockfile);
174 cc_log("lockfile_acquire: gave up acquiring %s", lockfile);
177 cc_log("lockfile_acquire: failed to acquire %s; sleeping %u microseconds",
186 cc_log("Acquired lock %s", lockfile);
188 cc_log("Failed to acquire lock %s", lockfile);
192 free(initial_content);
198 * Release the lockfile for the given path. Assumes that we are the legitimate
202 lockfile_release(const char *path)
204 char *lockfile = format("%s.lock", path);
205 cc_log("Releasing lock %s", lockfile);
206 tmp_unlink(lockfile);
212 main(int argc, char **argv)
214 extern char *cache_logfile;
215 cache_logfile = "/dev/stdout";
217 unsigned staleness_limit = atoi(argv[1]);
218 if (str_eq(argv[2], "acquire")) {
219 return lockfile_acquire(argv[3], staleness_limit) == 0;
220 } else if (str_eq(argv[2], "release")) {
221 lockfile_release(argv[3]);
226 "Usage: testlockfile <staleness_limit> <acquire|release> <path>\n");