Branch data Line data Source code
1 : : /*
2 : : * Copyright (C) 2010-2015 Joel Rosdahl
3 : : *
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)
7 : : * any later version.
8 : : *
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
12 : : * more details.
13 : : *
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
17 : : */
18 : :
19 : : #include "ccache.h"
20 : :
21 : : /*
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.
30 : : */
31 : : bool
32 : 6 : lockfile_acquire(const char *path, unsigned staleness_limit)
33 : : {
34 : 6 : int saved_errno = 0;
35 : 6 : char *lockfile = format("%s.lock", path);
36 : 6 : char *my_content = NULL, *content = NULL, *initial_content = NULL;
37 : 6 : const char *hostname = get_hostname();
38 : 6 : bool acquired = false;
39 : : #ifdef _WIN32
40 : : const size_t bufsize = 1024;
41 : : int fd, len;
42 : : #else
43 : : int ret;
44 : : #endif
45 : 6 : unsigned to_sleep = 1000, slept = 0; /* Microseconds. */
46 : :
47 : : while (true) {
48 : 12 : free(my_content);
49 : 12 : my_content = format("%s:%d:%d", hostname, (int)getpid(), (int)time(NULL));
50 : :
51 : : #ifdef _WIN32
52 : : fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL|O_BINARY, 0666);
53 : : if (fd == -1) {
54 : : saved_errno = errno;
55 : : cc_log("lockfile_acquire: open WRONLY %s: %s", lockfile, strerror(errno));
56 : : if (saved_errno == ENOENT) {
57 : : /* Directory doesn't exist? */
58 : : if (create_parent_dirs(lockfile) == 0) {
59 : : /* OK. Retry. */
60 : : continue;
61 : : }
62 : : }
63 : : if (saved_errno != EEXIST) {
64 : : /* Directory doesn't exist or isn't writable? */
65 : : goto out;
66 : : }
67 : : /* Someone else has the lock. */
68 : : fd = open(lockfile, O_RDONLY|O_BINARY);
69 : : if (fd == -1) {
70 : : if (errno == ENOENT) {
71 : : /*
72 : : * The file was removed after the open() call above, so retry
73 : : * acquiring it.
74 : : */
75 : : continue;
76 : : } else {
77 : : cc_log("lockfile_acquire: open RDONLY %s: %s",
78 : : lockfile, strerror(errno));
79 : : goto out;
80 : : }
81 : : }
82 : : free(content);
83 : : content = x_malloc(bufsize);
84 : : if ((len = read(fd, content, bufsize - 1)) == -1) {
85 : : cc_log("lockfile_acquire: read %s: %s", lockfile, strerror(errno));
86 : : close(fd);
87 : : goto out;
88 : : }
89 : : close(fd);
90 : : content[len] = '\0';
91 : : } else {
92 : : /* We got the lock. */
93 : : if (write(fd, my_content, strlen(my_content)) == -1) {
94 : : cc_log("lockfile_acquire: write %s: %s", lockfile, strerror(errno));
95 : : close(fd);
96 : : x_unlink(lockfile);
97 : : goto out;
98 : : }
99 : : close(fd);
100 : : acquired = true;
101 : : goto out;
102 : : }
103 : : #else
104 : 12 : ret = symlink(my_content, lockfile);
105 [ + + ]: 12 : if (ret == 0) {
106 : : /* We got the lock. */
107 : 5 : acquired = true;
108 : 5 : goto out;
109 : : }
110 : 7 : saved_errno = errno;
111 : 7 : cc_log("lockfile_acquire: symlink %s: %s", lockfile, strerror(saved_errno));
112 [ - + ]: 7 : if (saved_errno == ENOENT) {
113 : : /* Directory doesn't exist? */
114 [ # # ]: 0 : if (create_parent_dirs(lockfile) == 0) {
115 : : /* OK. Retry. */
116 : 0 : continue;
117 : : }
118 : : }
119 [ - + ]: 7 : if (saved_errno == EPERM) {
120 : : /*
121 : : * The file system does not support symbolic links. We have no choice but
122 : : * to grant the lock anyway.
123 : : */
124 : 0 : acquired = true;
125 : 0 : goto out;
126 : : }
127 [ - + ]: 7 : if (saved_errno != EEXIST) {
128 : : /* Directory doesn't exist or isn't writable? */
129 : 0 : goto out;
130 : : }
131 : 7 : free(content);
132 : 7 : content = x_readlink(lockfile);
133 [ + + ]: 7 : if (!content) {
134 [ - + ]: 1 : if (errno == ENOENT) {
135 : : /*
136 : : * The symlink was removed after the symlink() call above, so retry
137 : : * acquiring it.
138 : : */
139 : 0 : continue;
140 : : } else {
141 : 1 : cc_log("lockfile_acquire: readlink %s: %s", lockfile, strerror(errno));
142 : 1 : goto out;
143 : : }
144 : : }
145 : : #endif
146 : :
147 [ - + ]: 6 : if (str_eq(content, my_content)) {
148 : : /* Lost NFS reply? */
149 : 0 : cc_log("lockfile_acquire: symlink %s failed but we got the lock anyway",
150 : : lockfile);
151 : 0 : acquired = true;
152 : 0 : goto out;
153 : : }
154 : : /*
155 : : * A possible improvement here would be to check if the process holding the
156 : : * lock is still alive and break the lock early if it isn't.
157 : : */
158 : 6 : cc_log("lockfile_acquire: lock info for %s: %s", lockfile, content);
159 [ + + ]: 6 : if (!initial_content) {
160 : 2 : initial_content = x_strdup(content);
161 : : }
162 [ + + ]: 6 : if (slept > staleness_limit) {
163 [ + - ]: 2 : if (str_eq(content, initial_content)) {
164 : : /* The lock seems to be stale -- break it. */
165 : 2 : cc_log("lockfile_acquire: breaking %s", lockfile);
166 [ + - ]: 2 : if (lockfile_acquire(lockfile, staleness_limit)) {
167 : 2 : lockfile_release(path);
168 : 2 : lockfile_release(lockfile);
169 : 2 : to_sleep = 1000;
170 : 2 : slept = 0;
171 : 2 : continue;
172 : : }
173 : : }
174 : 0 : cc_log("lockfile_acquire: gave up acquiring %s", lockfile);
175 : 0 : goto out;
176 : : }
177 : 4 : cc_log("lockfile_acquire: failed to acquire %s; sleeping %u microseconds",
178 : : lockfile, to_sleep);
179 : 4 : usleep(to_sleep);
180 : 4 : slept += to_sleep;
181 : 4 : to_sleep *= 2;
182 : 6 : }
183 : :
184 : : out:
185 [ + + ]: 6 : if (acquired) {
186 : 5 : cc_log("Acquired lock %s", lockfile);
187 : : } else {
188 : 1 : cc_log("Failed to acquire lock %s", lockfile);
189 : : }
190 : 6 : free(lockfile);
191 : 6 : free(my_content);
192 : 6 : free(initial_content);
193 : 6 : free(content);
194 : 6 : return acquired;
195 : : }
196 : :
197 : : /*
198 : : * Release the lockfile for the given path. Assumes that we are the legitimate
199 : : * owner.
200 : : */
201 : : void
202 : 6 : lockfile_release(const char *path)
203 : : {
204 : 6 : char *lockfile = format("%s.lock", path);
205 : 6 : cc_log("Releasing lock %s", lockfile);
206 : 6 : tmp_unlink(lockfile);
207 : 6 : free(lockfile);
208 : 6 : }
209 : :
210 : : #ifdef TEST_LOCKFILE
211 : : int
212 : : main(int argc, char **argv)
213 : : {
214 : : extern char *cache_logfile;
215 : : cache_logfile = "/dev/stdout";
216 : : if (argc == 4) {
217 : : unsigned staleness_limit = atoi(argv[1]);
218 : : if (str_eq(argv[2], "acquire")) {
219 : : return lockfile_acquire(argv[3], staleness_limit) == 0;
220 : : } else if (str_eq(argv[2], "release")) {
221 : : lockfile_release(argv[3]);
222 : : return 0;
223 : : }
224 : : }
225 : : fprintf(stderr,
226 : : "Usage: testlockfile <staleness_limit> <acquire|release> <path>\n");
227 : : return 1;
228 : : }
229 : : #endif
|