Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-lock.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Author: Michael Zucchi <notzed@ximian.com>
4  *
5  * Copyright (C) 1999 Ximian (www.ximian.com/).
6  *
7  * This program is free software; you can redistribute it and/or 
8  * modify it under the terms of version 2 of the GNU Lesser General Public 
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
19  * USA
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <time.h>
32 #include <sys/stat.h>
33
34 #ifdef HAVE_ALLOCA_H
35 #include <alloca.h>
36 #endif
37
38 #ifdef USE_DOT
39 #include <fcntl.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #endif
43
44 #ifdef USE_FCNTL
45 #include <fcntl.h>
46 #include <unistd.h>
47 #endif
48
49 #ifdef USE_FLOCK
50 #include <sys/file.h>
51 #endif
52
53 #include <glib.h>
54 #include <glib/gi18n-lib.h>
55 #include <glib/gstdio.h>
56
57 #ifdef G_OS_WIN32
58 #include <windows.h>
59 #endif
60
61 #include "camel-lock.h"
62
63 #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
64
65 /**
66  * camel_lock_dot:
67  * @path: 
68  * @ex: 
69  * 
70  * Create an exclusive lock using .lock semantics.
71  * All locks are equivalent to write locks (exclusive).
72  * 
73  * Return value: -1 on error, sets @ex appropriately.
74  **/
75 int
76 camel_lock_dot(const char *path, CamelException *ex)
77 {
78 #ifdef USE_DOT
79         char *locktmp, *lock;
80         int retry = 0;
81         int fdtmp;
82         struct stat st;
83
84         /* TODO: Is there a reliable way to refresh the lock, if we're still busy with it?
85            Does it matter?  We will normally also use fcntl too ... */
86
87         /* use alloca, save cleaning up afterwards */
88         lock = alloca(strlen(path) + strlen(".lock") + 1);
89         sprintf(lock, "%s.lock", path);
90         locktmp = alloca(strlen(path) + strlen("XXXXXX") + 1);
91
92         while (retry < CAMEL_LOCK_DOT_RETRY) {
93
94                 d(printf("trying to lock '%s', attempt %d\n", lock, retry));
95
96                 if (retry > 0)
97                         sleep(CAMEL_LOCK_DOT_DELAY);
98
99                 sprintf(locktmp, "%sXXXXXX", path);
100                 fdtmp = g_mkstemp(locktmp);
101                 if (fdtmp == -1) {
102                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
103                                               _("Could not create lock file for %s: %s"),
104                                               path, g_strerror (errno));
105                         return -1;
106                 }
107                 close(fdtmp);
108
109                 /* apparently return code from link can be unreliable for nfs (see link(2)), so we ignore it */
110                 link(locktmp, lock);
111
112                 /* but we check stat instead (again, see link(2)) */
113                 if (stat(locktmp, &st) == -1) {
114                         d(printf("Our lock file %s vanished!?\n", locktmp));
115
116                         /* well that was unexpected, try cleanup/retry */
117                         unlink(locktmp);
118                         unlink(lock);
119                 } else {
120                         d(printf("tmp lock created, link count is %d\n", st.st_nlink));
121
122                         unlink(locktmp);
123
124                         /* if we had 2 links, we have created the .lock, return ok, otherwise we need to keep trying */
125                         if (st.st_nlink == 2)
126                                 return 0;
127                 }
128
129                 /* check for stale lock, kill it */
130                 if (stat(lock, &st) == 0) {
131                         time_t now = time (NULL);
132                         (printf("There is an existing lock %ld seconds old\n", now-st.st_ctime));
133                         if (st.st_ctime < now - CAMEL_LOCK_DOT_STALE) {
134                                 d(printf("Removing it now\n"));
135                                 unlink(lock);
136                         }
137                 }
138
139                 retry++;
140         }
141
142         d(printf("failed to get lock after %d retries\n", retry));
143
144         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Timed out trying to get lock file on %s. Try again later."), path);
145         return -1;
146 #else /* ! USE_DOT */
147         return 0;
148 #endif
149 }
150
151 /**
152  * camel_unlock_dot:
153  * @path: 
154  * 
155  * Attempt to unlock a .lock lock.
156  **/
157 void
158 camel_unlock_dot(const char *path)
159 {
160 #ifdef USE_DOT
161         char *lock;
162
163         lock = alloca(strlen(path) + strlen(".lock") + 1);
164         sprintf(lock, "%s.lock", path);
165         d(printf("unlocking %s\n", lock));
166         (void)unlink(lock);
167 #endif
168 }
169
170 /**
171  * camel_lock_fcntl:
172  * @fd: 
173  * @type: 
174  * @ex: 
175  * 
176  * Create a lock using fcntl(2).
177  *
178  * @type is CAMEL_LOCK_WRITE or CAMEL_LOCK_READ,
179  * to create exclusive or shared read locks
180  * 
181  * Return value: -1 on error.
182  **/
183 int
184 camel_lock_fcntl(int fd, CamelLockType type, CamelException *ex)
185 {
186 #ifdef USE_FCNTL
187         struct flock lock;
188
189         d(printf("fcntl locking %d\n", fd));
190
191         memset(&lock, 0, sizeof(lock));
192         lock.l_type = type==CAMEL_LOCK_READ?F_RDLCK:F_WRLCK;
193         if (fcntl(fd, F_SETLK, &lock) == -1) {
194                 /* If we get a 'locking not vailable' type error,
195                    we assume the filesystem doesn't support fcntl() locking */
196                 /* this is somewhat system-dependent */
197                 if (errno != EINVAL && errno != ENOLCK) {
198                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
199                                               _("Failed to get lock using fcntl(2): %s"),
200                                               g_strerror (errno));
201                         return -1;
202                 } else {
203                         static int failed = 0;
204
205                         if (failed == 0)
206                                 fprintf(stderr, "fcntl(2) locking appears not to work on this filesystem");
207                         failed++;
208                 }
209         }
210 #endif
211         return 0;
212 }
213
214 /**
215  * camel_unlock_fcntl:
216  * @fd: 
217  * 
218  * Unlock an fcntl lock.
219  **/
220 void
221 camel_unlock_fcntl(int fd)
222 {
223 #ifdef USE_FCNTL
224         struct flock lock;
225
226         d(printf("fcntl unlocking %d\n", fd));
227
228         memset(&lock, 0, sizeof(lock));
229         lock.l_type = F_UNLCK;
230         fcntl(fd, F_SETLK, &lock);
231 #endif
232 }
233
234 /**
235  * camel_lock_flock:
236  * @fd: 
237  * @type: 
238  * @ex: 
239  * 
240  * Create a lock using flock(2).
241  * 
242  * @type is CAMEL_LOCK_WRITE or CAMEL_LOCK_READ,
243  * to create exclusive or shared read locks
244  *
245  * Return value: -1 on error.
246  **/
247 int
248 camel_lock_flock(int fd, CamelLockType type, CamelException *ex)
249 {
250 #ifdef USE_FLOCK
251         int op;
252
253         d(printf("flock locking %d\n", fd));
254
255         if (type == CAMEL_LOCK_READ)
256                 op = LOCK_SH|LOCK_NB;
257         else
258                 op = LOCK_EX|LOCK_NB;
259
260         if (flock(fd, op) == -1) {
261                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
262                                       _("Failed to get lock using flock(2): %s"),
263                                       g_strerror (errno));
264                 return -1;
265         }
266 #endif
267         return 0;
268 }
269
270 /**
271  * camel_unlock_flock:
272  * @fd: 
273  * 
274  * Unlock an flock lock.
275  **/
276 void
277 camel_unlock_flock(int fd)
278 {
279 #ifdef USE_FLOCK
280         d(printf("flock unlocking %d\n", fd));
281
282         (void)flock(fd, LOCK_UN);
283 #endif
284 }
285
286 /**
287  * camel_lock_folder:
288  * @path: Path to the file to lock (used for .locking only).
289  * @fd: Open file descriptor of the right type to lock.
290  * @type: Type of lock, CAMEL_LOCK_READ or CAMEL_LOCK_WRITE.
291  * @ex: 
292  * 
293  * Attempt to lock a folder, multiple attempts will be made using all
294  * locking strategies available.
295  * 
296  * Return value: -1 on error, @ex will describe the locking system that failed.
297  **/
298 int
299 camel_lock_folder(const char *path, int fd, CamelLockType type, CamelException *ex)
300 {
301         int retry = 0;
302
303         while (retry < CAMEL_LOCK_RETRY) {
304                 if (retry > 0)
305                         g_usleep(CAMEL_LOCK_DELAY*1000000);
306
307                 if (camel_lock_fcntl(fd, type, ex) == 0) {
308                         if (camel_lock_flock(fd, type, ex) == 0) {
309                                 if (camel_lock_dot(path, ex) == 0)
310                                         return 0;
311                                 camel_unlock_flock(fd);
312                         }
313                         camel_unlock_fcntl(fd);
314                 }
315                 retry++;
316         }
317
318         return -1;
319 }
320
321 /**
322  * camel_unlock_folder:
323  * @path: Filename of folder.
324  * @fd: Open descrptor on which locks were placed.
325  * 
326  * Free a lock on a folder.
327  **/
328 void
329 camel_unlock_folder(const char *path, int fd)
330 {
331         camel_unlock_dot(path);
332         camel_unlock_flock(fd);
333         camel_unlock_fcntl(fd);
334 }
335
336 #if 0
337 int main(int argc, char **argv)
338 {
339         CamelException *ex;
340         int fd1, fd2;
341
342         ex = camel_exception_new();
343
344 #if 0
345         if (camel_lock_dot("mylock", ex) == 0) {
346                 if (camel_lock_dot("mylock", ex) == 0) {
347                         printf("Got lock twice?\n");
348                 } else {
349                         printf("failed to get lock 2: %s\n", camel_exception_get_description(ex));
350                 }
351                 camel_unlock_dot("mylock");
352         } else {
353                 printf("failed to get lock 1: %s\n", camel_exception_get_description(ex));
354         }
355
356         camel_exception_clear(ex);
357 #endif
358
359         fd1 = open("mylock", O_RDWR);
360         if (fd1 == -1) {
361                 printf("Could not open lock file (mylock): %s", g_strerror (errno));
362                 return 1;
363         }
364         fd2 = open("mylock", O_RDWR);
365         if (fd2 == -1) {                
366                 printf("Could not open lock file (mylock): %s", g_strerror (errno));
367                 close (fd1);
368                 return 1;
369         }
370
371         if (camel_lock_fcntl(fd1, CAMEL_LOCK_WRITE, ex) == 0) {
372                 printf("got fcntl write lock once\n");
373                 g_usleep(5000000);
374                 if (camel_lock_fcntl(fd2, CAMEL_LOCK_WRITE, ex) == 0) {
375                         printf("got fcntl write lock twice!\n");
376                 } else {
377                         printf("failed to get write lock: %s\n", camel_exception_get_description(ex));
378                 }
379
380                 camel_exception_clear(ex);
381
382                 if (camel_lock_fcntl(fd2, CAMEL_LOCK_READ, ex) == 0) {
383                         printf("got fcntl read lock as well?\n");
384                         camel_unlock_fcntl(fd2);
385                 } else {
386                         printf("failed to get read lock: %s\n", camel_exception_get_description(ex));
387                 }
388
389                 camel_exception_clear(ex);
390                 camel_unlock_fcntl(fd1);
391         } else {
392                 printf("failed to get write lock at all: %s\n", camel_exception_get_description(ex));
393         }
394
395         if (camel_lock_fcntl(fd1, CAMEL_LOCK_READ, ex) == 0) {
396                 printf("got fcntl read lock once\n");
397                 g_usleep(5000000);
398                 if (camel_lock_fcntl(fd2, CAMEL_LOCK_WRITE, ex) == 0) {
399                         printf("got fcntl write lock too?!\n");
400                 } else {
401                         printf("failed to get write lock: %s\n", camel_exception_get_description(ex));
402                 }
403
404                 camel_exception_clear(ex);
405
406                 if (camel_lock_fcntl(fd2, CAMEL_LOCK_READ, ex) == 0) {
407                         printf("got fcntl read lock twice\n");
408                         camel_unlock_fcntl(fd2);
409                 } else {
410                         printf("failed to get read lock: %s\n", camel_exception_get_description(ex));
411                 }
412
413                 camel_exception_clear(ex);
414                 camel_unlock_fcntl(fd1);
415         }
416
417         close(fd1);
418         close(fd2);
419
420         return 0;
421 }
422 #endif