1 // Copyright (C) 2010-2020 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 #ifndef _WIN32
20
21 // This function acquires a lockfile for the given path. Returns true if the
22 // lock was acquired, otherwise false. If the lock has been considered stale
23 // for the number of microseconds specified by staleness_limit, the function
24 // will (if possible) break the lock and then try to acquire it again. The
25 // staleness limit should be reasonably larger than the longest time the lock
26 // can be expected to be held, and the updates of the locked path should
27 // probably be made with an atomic rename(2) to avoid corruption in the rare
28 // case that the lock is broken by another process.
29 bool
lockfile_acquire(const char * path,unsigned staleness_limit)30 lockfile_acquire(const char *path, unsigned staleness_limit)
31 {
32 char *lockfile = format("%s.lock", path);
33 char *my_content = NULL;
34 char *content = NULL;
35 char *initial_content = NULL;
36 const char *hostname = get_hostname();
37 bool acquired = false;
38 unsigned to_sleep = 1000; // Microseconds.
39 unsigned max_to_sleep = 10000; // Microseconds.
40 unsigned slept = 0; // Microseconds.
41
42 while (true) {
43 free(my_content);
44 my_content = format("%s:%d:%d", hostname, (int)getpid(), (int)time(NULL));
45
46 if (symlink(my_content, lockfile) == 0) {
47 // We got the lock.
48 acquired = true;
49 goto out;
50 }
51 int saved_errno = errno;
52 cc_log("lockfile_acquire: symlink %s: %s", lockfile, strerror(saved_errno));
53 if (saved_errno == ENOENT) {
54 // Directory doesn't exist?
55 if (create_parent_dirs(lockfile) == 0) {
56 // OK. Retry.
57 continue;
58 }
59 }
60 if (saved_errno == EPERM) {
61 // The file system does not support symbolic links. We have no choice but
62 // to grant the lock anyway.
63 acquired = true;
64 goto out;
65 }
66 if (saved_errno != EEXIST) {
67 // Directory doesn't exist or isn't writable?
68 goto out;
69 }
70 free(content);
71 content = x_readlink(lockfile);
72 if (!content) {
73 if (errno == ENOENT) {
74 // The symlink was removed after the symlink() call above, so retry
75 // acquiring it.
76 continue;
77 } else {
78 cc_log("lockfile_acquire: readlink %s: %s", lockfile, strerror(errno));
79 goto out;
80 }
81 }
82
83 if (str_eq(content, my_content)) {
84 // Lost NFS reply?
85 cc_log("lockfile_acquire: symlink %s failed but we got the lock anyway",
86 lockfile);
87 acquired = true;
88 goto out;
89 }
90 // A possible improvement here would be to check if the process holding the
91 // lock is still alive and break the lock early if it isn't.
92 cc_log("lockfile_acquire: lock info for %s: %s", lockfile, content);
93 if (!initial_content) {
94 initial_content = x_strdup(content);
95 }
96 if (slept > staleness_limit) {
97 if (str_eq(content, initial_content)) {
98 // The lock seems to be stale -- break it.
99 cc_log("lockfile_acquire: breaking %s", lockfile);
100 // Try to acquire path.lock.lock:
101 if (lockfile_acquire(lockfile, staleness_limit)) {
102 lockfile_release(path); // Remove path.lock
103 lockfile_release(lockfile); // Remove path.lock.lock
104 to_sleep = 1000;
105 slept = 0;
106 continue;
107 }
108 }
109 cc_log("lockfile_acquire: gave up acquiring %s", lockfile);
110 goto out;
111 }
112 cc_log("lockfile_acquire: failed to acquire %s; sleeping %u microseconds",
113 lockfile, to_sleep);
114 usleep(to_sleep);
115 slept += to_sleep;
116 to_sleep = MIN(max_to_sleep, 2 * to_sleep);
117 }
118
119 out:
120 if (acquired) {
121 cc_log("Acquired lock %s", lockfile);
122 } else {
123 cc_log("Failed to acquire lock %s", lockfile);
124 }
125 free(lockfile);
126 free(my_content);
127 free(initial_content);
128 free(content);
129 return acquired;
130 }
131
132 // Release the lockfile for the given path. Assumes that we are the legitimate
133 // owner.
134 void
lockfile_release(const char * path)135 lockfile_release(const char *path)
136 {
137 char *lockfile = format("%s.lock", path);
138 cc_log("Releasing lock %s", lockfile);
139 tmp_unlink(lockfile);
140 free(lockfile);
141 }
142
143 #else
144
145 HANDLE lockfile_handle = NULL;
146
147 // This function acquires a lockfile for the given path. Returns true if the
148 // lock was acquired, otherwise false. If the lock has been acquired within the
149 // limit (in microseconds) the function will give up and return false. The time
150 // limit should be reasonably larger than the longest time the lock can be
151 // expected to be held.
152 bool
lockfile_acquire(const char * path,unsigned time_limit)153 lockfile_acquire(const char *path, unsigned time_limit)
154 {
155 char *lockfile = format("%s.lock", path);
156 unsigned to_sleep = 1000; // Microseconds.
157 unsigned max_to_sleep = 10000; // Microseconds.
158 unsigned slept = 0; // Microseconds.
159 bool acquired = false;
160
161 while (true) {
162 DWORD flags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE;
163 lockfile_handle = CreateFile(
164 lockfile,
165 GENERIC_WRITE, // desired access
166 0, // shared mode (0 = not shared)
167 NULL, // security attributes
168 CREATE_ALWAYS, // creation disposition,
169 flags, // flags and attributes
170 NULL // template file
171 );
172 if (lockfile_handle != INVALID_HANDLE_VALUE) {
173 acquired = true;
174 break;
175 }
176
177 DWORD error = GetLastError();
178 cc_log("lockfile_acquire: CreateFile %s: error code %lu", lockfile, error);
179 if (error == ERROR_PATH_NOT_FOUND) {
180 // Directory doesn't exist?
181 if (create_parent_dirs(lockfile) == 0) {
182 // OK. Retry.
183 continue;
184 }
185 }
186
187 // ERROR_SHARING_VIOLATION: lock already held.
188 // ERROR_ACCESS_DENIED: maybe pending delete.
189 if (error != ERROR_SHARING_VIOLATION && error != ERROR_ACCESS_DENIED) {
190 // Fatal error, give up.
191 break;
192 }
193
194 if (slept > time_limit) {
195 cc_log("lockfile_acquire: gave up acquiring %s", lockfile);
196 break;
197 }
198
199 cc_log("lockfile_acquire: failed to acquire %s; sleeping %u microseconds",
200 lockfile, to_sleep);
201 usleep(to_sleep);
202 slept += to_sleep;
203 to_sleep = MIN(max_to_sleep, 2 * to_sleep);
204 }
205
206 if (acquired) {
207 cc_log("Acquired lock %s", lockfile);
208 } else {
209 cc_log("Failed to acquire lock %s", lockfile);
210 }
211 free(lockfile);
212 return acquired;
213 }
214
215 // Release the lockfile for the given path. Assumes that we are the legitimate
216 // owner.
217 void
lockfile_release(const char * path)218 lockfile_release(const char *path)
219 {
220 assert(lockfile_handle != INVALID_HANDLE_VALUE);
221 cc_log("Releasing lock %s.lock", path);
222 CloseHandle(lockfile_handle);
223 lockfile_handle = NULL;
224 }
225
226 #endif
227
228 #ifdef TEST_LOCKFILE
229
230 int
main(int argc,char ** argv)231 main(int argc, char **argv)
232 {
233 extern struct conf *conf;
234 conf = conf_create();
235 if (argc == 3) {
236 unsigned staleness_limit = atoi(argv[1]);
237 printf("Acquiring\n");
238 bool acquired = lockfile_acquire(argv[2], staleness_limit);
239 if (acquired) {
240 printf("Sleeping 2 seconds\n");
241 sleep(2);
242 lockfile_release(argv[2]);
243 printf("Released\n");
244 } else {
245 printf("Failed to acquire\n");
246 }
247 } else {
248 fprintf(stderr,
249 "Usage: testlockfile <staleness_limit> <path>\n");
250 }
251 return 1;
252 }
253 #endif
254