f4f2028b7d612ae442d69c40d7484900ecd79a92
[platform/upstream/ccache.git] / lockfile.c
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 lockfile_acquire(const char *path, unsigned staleness_limit)
33 {
34         int saved_errno = 0;
35         char *lockfile = format("%s.lock", path);
36         char *my_content = NULL, *content = NULL, *initial_content = NULL;
37         const char *hostname = get_hostname();
38         bool acquired = false;
39 #ifdef _WIN32
40         const size_t bufsize = 1024;
41         int fd, len;
42 #else
43         int ret;
44 #endif
45         unsigned to_sleep = 1000, slept = 0; /* Microseconds. */
46
47         while (true) {
48                 free(my_content);
49                 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                 ret = symlink(my_content, lockfile);
105                 if (ret == 0) {
106                         /* We got the lock. */
107                         acquired = true;
108                         goto out;
109                 }
110                 saved_errno = errno;
111                 cc_log("lockfile_acquire: symlink %s: %s", lockfile, strerror(saved_errno));
112                 if (saved_errno == ENOENT) {
113                         /* Directory doesn't exist? */
114                         if (create_parent_dirs(lockfile) == 0) {
115                                 /* OK. Retry. */
116                                 continue;
117                         }
118                 }
119                 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                         acquired = true;
125                         goto out;
126                 }
127                 if (saved_errno != EEXIST) {
128                         /* Directory doesn't exist or isn't writable? */
129                         goto out;
130                 }
131                 free(content);
132                 content = x_readlink(lockfile);
133                 if (!content) {
134                         if (errno == ENOENT) {
135                                 /*
136                                  * The symlink was removed after the symlink() call above, so retry
137                                  * acquiring it.
138                                  */
139                                 continue;
140                         } else {
141                                 cc_log("lockfile_acquire: readlink %s: %s", lockfile, strerror(errno));
142                                 goto out;
143                         }
144                 }
145 #endif
146
147                 if (str_eq(content, my_content)) {
148                         /* Lost NFS reply? */
149                         cc_log("lockfile_acquire: symlink %s failed but we got the lock anyway",
150                                lockfile);
151                         acquired = true;
152                         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                 cc_log("lockfile_acquire: lock info for %s: %s", lockfile, content);
159                 if (!initial_content) {
160                         initial_content = x_strdup(content);
161                 }
162                 if (slept > staleness_limit) {
163                         if (str_eq(content, initial_content)) {
164                                 /* The lock seems to be stale -- break it. */
165                                 cc_log("lockfile_acquire: breaking %s", lockfile);
166                                 if (lockfile_acquire(lockfile, staleness_limit)) {
167                                         lockfile_release(path);
168                                         lockfile_release(lockfile);
169                                         to_sleep = 1000;
170                                         slept = 0;
171                                         continue;
172                                 }
173                         }
174                         cc_log("lockfile_acquire: gave up acquiring %s", lockfile);
175                         goto out;
176                 }
177                 cc_log("lockfile_acquire: failed to acquire %s; sleeping %u microseconds",
178                        lockfile, to_sleep);
179                 usleep(to_sleep);
180                 slept += to_sleep;
181                 to_sleep *= 2;
182         }
183
184 out:
185         if (acquired) {
186                 cc_log("Acquired lock %s", lockfile);
187         } else {
188                 cc_log("Failed to acquire lock %s", lockfile);
189         }
190         free(lockfile);
191         free(my_content);
192         free(initial_content);
193         free(content);
194         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 lockfile_release(const char *path)
203 {
204         char *lockfile = format("%s.lock", path);
205         cc_log("Releasing lock %s", lockfile);
206         tmp_unlink(lockfile);
207         free(lockfile);
208 }
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