2 * Copyright (C) 2010-2011 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)
34 char *lockfile = format("%s.lock", path);
35 char *my_content = NULL, *content = NULL, *initial_content = NULL;
36 const char *hostname = get_hostname();
37 bool acquired = false;
39 const size_t bufsize = 1024;
44 unsigned to_sleep = 1000, slept = 0; /* Microseconds. */
48 my_content = format("%s:%d:%d", hostname, (int)getpid(), (int)time(NULL));
51 fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL|O_BINARY, 0666);
53 cc_log("lockfile_acquire: open WRONLY %s: %s", lockfile, strerror(errno));
54 if (errno != EEXIST) {
55 /* Directory doesn't exist or isn't writable? */
58 /* Someone else has the lock. */
59 fd = open(lockfile, O_RDONLY|O_BINARY);
61 if (errno == ENOENT) {
63 * The file was removed after the open() call above, so retry
68 cc_log("lockfile_acquire: open RDONLY %s: %s",
69 lockfile, strerror(errno));
74 content = x_malloc(bufsize);
75 if ((len = read(fd, content, bufsize - 1)) == -1) {
76 cc_log("lockfile_acquire: read %s: %s", lockfile, strerror(errno));
83 /* We got the lock. */
84 if (write(fd, my_content, strlen(my_content)) == -1) {
85 cc_log("lockfile_acquire: write %s: %s", lockfile, strerror(errno));
95 ret = symlink(my_content, lockfile);
97 /* We got the lock. */
101 cc_log("lockfile_acquire: symlink %s: %s", lockfile, strerror(errno));
102 if (errno == EPERM) {
104 * The file system does not support symbolic links. We have no choice but
105 * to grant the lock anyway.
110 if (errno != EEXIST) {
111 /* Directory doesn't exist or isn't writable? */
115 content = x_readlink(lockfile);
117 if (errno == ENOENT) {
119 * The symlink was removed after the symlink() call above, so retry
124 cc_log("lockfile_acquire: readlink %s: %s", lockfile, strerror(errno));
130 if (str_eq(content, my_content)) {
131 /* Lost NFS reply? */
132 cc_log("lockfile_acquire: symlink %s failed but we got the lock anyway",
138 * A possible improvement here would be to check if the process holding the
139 * lock is still alive and break the lock early if it isn't.
141 cc_log("lockfile_acquire: lock info for %s: %s", lockfile, content);
142 if (!initial_content) {
143 initial_content = x_strdup(content);
145 if (slept > staleness_limit) {
146 if (str_eq(content, initial_content)) {
147 /* The lock seems to be stale -- break it. */
148 cc_log("lockfile_acquire: breaking %s", lockfile);
149 if (lockfile_acquire(lockfile, staleness_limit)) {
150 lockfile_release(path);
151 lockfile_release(lockfile);
157 cc_log("lockfile_acquire: gave up acquiring %s", lockfile);
160 cc_log("lockfile_acquire: failed to acquire %s; sleeping %u microseconds",
169 cc_log("Acquired lock %s", lockfile);
171 cc_log("Failed to acquire lock %s", lockfile);
175 free(initial_content);
181 * Release the lockfile for the given path. Assumes that we are the legitimate
185 lockfile_release(const char *path)
187 char *lockfile = format("%s.lock", path);
188 cc_log("Releasing lock %s", lockfile);
189 tmp_unlink(lockfile);
195 main(int argc, char **argv)
197 extern char *cache_logfile;
198 cache_logfile = "/dev/stdout";
200 unsigned staleness_limit = atoi(argv[1]);
201 if (str_eq(argv[2], "acquire")) {
202 return lockfile_acquire(argv[3], staleness_limit) == 0;
203 } else if (str_eq(argv[2], "release")) {
204 lockfile_release(argv[3]);
209 "Usage: testlockfile <staleness_limit> <acquire|release> <path>\n");