1 // Copyright (C) 2010-2018 Joel Rosdahl
2 //
3 // This program is free software; you can redistribute it and/or modify it
4 // under the terms of the GNU General Public License as published by the Free
5 // Software Foundation; either version 3 of the License, or (at your option)
6 // any later version.
7 //
8 // This program is distributed in the hope that it will be useful, but WITHOUT
9 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 // more details.
12 //
13 // You should have received a copy of the GNU General Public License along with
14 // this program; if not, write to the Free Software Foundation, Inc., 51
15 // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17 #include "ccache.h"
18
19 // This function acquires a lockfile for the given path. Returns true if the
20 // lock was acquired, otherwise false. If the lock has been considered stale
21 // for the number of microseconds specified by staleness_limit, the function
22 // will (if possible) break the lock and then try to acquire it again. The
23 // staleness limit should be reasonably larger than the longest time the lock
24 // can be expected to be held, and the updates of the locked path should
25 // probably be made with an atomic rename(2) to avoid corruption in the rare
26 // case that the lock is broken by another process.
27 bool
lockfile_acquire(const char * path,unsigned staleness_limit)28 lockfile_acquire(const char *path, unsigned staleness_limit)
29 {
30 char *lockfile = format("%s.lock", path);
31 char *my_content = NULL;
32 char *content = NULL;
33 char *initial_content = NULL;
34 const char *hostname = get_hostname();
35 bool acquired = false;
36 unsigned to_sleep = 1000; // Microseconds.
37 unsigned slept = 0; // Microseconds.
38
39 while (true) {
40 free(my_content);
41 my_content = format("%s:%d:%d", hostname, (int)getpid(), (int)time(NULL));
42
43 #ifdef _WIN32
44 int fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL|O_BINARY, 0666);
45 if (fd == -1) {
46 int saved_errno = errno;
47 cc_log("lockfile_acquire: open WRONLY %s: %s", lockfile, strerror(errno));
48 if (saved_errno == ENOENT) {
49 // Directory doesn't exist?
50 if (create_parent_dirs(lockfile) == 0) {
51 // OK. Retry.
52 continue;
53 }
54 }
55 if (saved_errno != EEXIST) {
56 // Directory doesn't exist or isn't writable?
57 goto out;
58 }
59 // Someone else has the lock.
60 fd = open(lockfile, O_RDONLY|O_BINARY);
61 if (fd == -1) {
62 if (errno == ENOENT) {
63 // The file was removed after the open() call above, so retry
64 // acquiring it.
65 continue;
66 } else {
67 cc_log("lockfile_acquire: open RDONLY %s: %s",
68 lockfile, strerror(errno));
69 goto out;
70 }
71 }
72 free(content);
73 const size_t bufsize = 1024;
74 content = x_malloc(bufsize);
75 int len = read(fd, content, bufsize - 1);
76 if (len == -1) {
77 cc_log("lockfile_acquire: read %s: %s", lockfile, strerror(errno));
78 close(fd);
79 goto out;
80 }
81 close(fd);
82 content[len] = '\0';
83 } else {
84 // We got the lock.
85 if (write(fd, my_content, strlen(my_content)) == -1) {
86 cc_log("lockfile_acquire: write %s: %s", lockfile, strerror(errno));
87 close(fd);
88 x_unlink(lockfile);
89 goto out;
90 }
91 close(fd);
92 acquired = true;
93 goto out;
94 }
95 #else
96 if (symlink(my_content, lockfile) == 0) {
97 // We got the lock.
98 acquired = true;
99 goto out;
100 }
101 int saved_errno = errno;
102 cc_log("lockfile_acquire: symlink %s: %s", lockfile, strerror(saved_errno));
103 if (saved_errno == ENOENT) {
104 // Directory doesn't exist?
105 if (create_parent_dirs(lockfile) == 0) {
106 // OK. Retry.
107 continue;
108 }
109 }
110 if (saved_errno == EPERM) {
111 // The file system does not support symbolic links. We have no choice but
112 // to grant the lock anyway.
113 acquired = true;
114 goto out;
115 }
116 if (saved_errno != EEXIST) {
117 // Directory doesn't exist or isn't writable?
118 goto out;
119 }
120 free(content);
121 content = x_readlink(lockfile);
122 if (!content) {
123 if (errno == ENOENT) {
124 // The symlink was removed after the symlink() call above, so retry
125 // acquiring it.
126 continue;
127 } else {
128 cc_log("lockfile_acquire: readlink %s: %s", lockfile, strerror(errno));
129 goto out;
130 }
131 }
132 #endif
133
134 if (str_eq(content, my_content)) {
135 // Lost NFS reply?
136 cc_log("lockfile_acquire: symlink %s failed but we got the lock anyway",
137 lockfile);
138 acquired = true;
139 goto out;
140 }
141 // A possible improvement here would be to check if the process holding the
142 // lock is still alive and break the lock early if it isn't.
143 cc_log("lockfile_acquire: lock info for %s: %s", lockfile, content);
144 if (!initial_content) {
145 initial_content = x_strdup(content);
146 }
147 if (slept > staleness_limit) {
148 if (str_eq(content, initial_content)) {
149 // The lock seems to be stale -- break it.
150 cc_log("lockfile_acquire: breaking %s", lockfile);
151 // Try to acquire path.lock.lock:
152 if (lockfile_acquire(lockfile, staleness_limit)) {
153 lockfile_release(path); // Remove path.lock
154 lockfile_release(lockfile); // Remove path.lock.lock
155 to_sleep = 1000;
156 slept = 0;
157 continue;
158 }
159 }
160 cc_log("lockfile_acquire: gave up acquiring %s", lockfile);
161 goto out;
162 }
163 cc_log("lockfile_acquire: failed to acquire %s; sleeping %u microseconds",
164 lockfile, to_sleep);
165 usleep(to_sleep);
166 slept += to_sleep;
167 to_sleep *= 2;
168 }
169
170 out:
171 if (acquired) {
172 cc_log("Acquired lock %s", lockfile);
173 } else {
174 cc_log("Failed to acquire lock %s", lockfile);
175 }
176 free(lockfile);
177 free(my_content);
178 free(initial_content);
179 free(content);
180 return acquired;
181 }
182
183 // Release the lockfile for the given path. Assumes that we are the legitimate
184 // owner.
185 void
lockfile_release(const char * path)186 lockfile_release(const char *path)
187 {
188 char *lockfile = format("%s.lock", path);
189 cc_log("Releasing lock %s", lockfile);
190 tmp_unlink(lockfile);
191 free(lockfile);
192 }
193
194 #ifdef TEST_LOCKFILE
195 int
main(int argc,char ** argv)196 main(int argc, char **argv)
197 {
198 extern char *cache_logfile;
199 cache_logfile = "/dev/stdout";
200 if (argc == 4) {
201 unsigned staleness_limit = atoi(argv[1]);
202 if (str_eq(argv[2], "acquire")) {
203 return lockfile_acquire(argv[3], staleness_limit) == 0;
204 } else if (str_eq(argv[2], "release")) {
205 lockfile_release(argv[3]);
206 return 0;
207 }
208 }
209 fprintf(stderr,
210 "Usage: testlockfile <staleness_limit> <acquire|release> <path>\n");
211 return 1;
212 }
213 #endif
214