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