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