Commit 86b9cd5d 86b9cd5d3455bd36e9ba4b8fa3c99c9a5ccafc3c by Sam Roberts

New modes, including a retry mode (the default), removed fcntl

and stub read/write lock mode.
New function, to remove a lock even if not owned (useful for the
external dotlock utility).
New functions, to set the expire time for MU_LOCKER_TIME, the flags,
and the retries and retry sleep for MU_LOCKER_RETRY.
1 parent 471021d1
...@@ -28,23 +28,49 @@ extern "C" { ...@@ -28,23 +28,49 @@ extern "C" {
28 struct _locker; 28 struct _locker;
29 typedef struct _locker *locker_t; 29 typedef struct _locker *locker_t;
30 30
31 extern int locker_create __P ((locker_t *, const char *filename, 31 /* lock expiry time */
32 size_t len, int flags)); 32 #define MU_LOCKER_EXPIRE_TIME (10 * 60)
33 #define MU_LOCKER_RETRIES (10)
34 #define MU_LOCKER_RETRY_SLEEP (1)
35
36 /* locker_create() flags */
37
38 #define MU_LOCKER_RETRY 0x01
39 /* This requests that we loop retries times, sleeping retry_sleep
40 seconds in between trying to obtain the lock before failing with
41 MU_LOCK_CONFLICT. */
42 #define MU_LOCKER_TIME 0x02
43 /* This mode checks the last update time of the lock, then removes
44 it if older than MU_LOCKER_EXPIRE_TIME. If a client uses this,
45 then the servers better periodically update the lock on the
46 file... do they? */
47 #define MU_LOCKER_PID 0x04
48 /* PID locking is only useful for programs that aren't using
49 an external dotlocker, non-setgid programs will use a dotlocker,
50 which locks and exits imediately. This is a protection against
51 a server crashing, it's not generally useful. */
52
53 #define MU_LOCKER_DEFAULT (MU_LOCKER_RETRY)
54
55 extern int locker_create __P ((locker_t *, const char *filename, int flags));
33 extern void locker_destroy __P ((locker_t *)); 56 extern void locker_destroy __P ((locker_t *));
34 57
35 #define MU_LOCKER_RDLOCK 0 58 /* Time is measured in seconds. */
36 #define MU_LOCKER_WRLOCK 1
37 59
38 /* locking flags */ 60 extern int locker_set_flags __P ((locker_t, int));
39 #define MU_LOCKER_PID 1 61 extern int locker_set_expire_time __P ((locker_t, int));
40 #define MU_LOCKER_FCNTL 2 62 extern int locker_set_retries __P ((locker_t, int));
41 #define MU_LOCKER_TIME 4 63 extern int locker_set_retry_sleep __P ((locker_t, int));
42 64
43 #define MU_LOCKER_EXPIRE_TIME (5 * 60) 65 extern int locker_get_flags __P ((locker_t, int*));
66 extern int locker_get_expire_time __P ((locker_t, int*));
67 extern int locker_get_retries __P ((locker_t, int*));
68 extern int locker_get_retry_sleep __P ((locker_t, int*));
44 69
45 extern int locker_lock __P ((locker_t, int flag)); 70 extern int locker_lock __P ((locker_t));
46 extern int locker_touchlock __P ((locker_t)); 71 extern int locker_touchlock __P ((locker_t));
47 extern int locker_unlock __P ((locker_t)); 72 extern int locker_unlock __P ((locker_t));
73 extern int locker_remove_lock __P ((locker_t));
48 74
49 #ifdef __cplusplus 75 #ifdef __cplusplus
50 } 76 }
......
...@@ -19,61 +19,104 @@ ...@@ -19,61 +19,104 @@
19 # include <config.h> 19 # include <config.h>
20 #endif 20 #endif
21 21
22 #include <assert.h>
22 #include <errno.h> 23 #include <errno.h>
23 #include <sys/types.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <fcntl.h> 24 #include <fcntl.h>
28 #include <limits.h> 25 #include <limits.h>
29 #include <sys/stat.h> 26 #include <signal.h>
30 #include <unistd.h> 27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
31 #include <time.h> 30 #include <time.h>
31 #include <unistd.h>
32 #include <utime.h> 32 #include <utime.h>
33 #include <signal.h> 33
34 #include <sys/types.h>
35 #include <sys/stat.h>
34 36
35 #include <mailutils/locker.h> 37 #include <mailutils/locker.h>
38 #include <mailutils/errno.h>
36 39
37 #define LOCKFILE_ATTR 0444 40 #define LOCKFILE_ATTR 0444
38 41
39 /* First draft by Brian Edmond. */ 42 /* First draft by Brian Edmond. */
43 /* For subsequent modifications, see the GNU mailutils ChangeLog. */
40 44
41 struct _locker 45 struct _locker
42 { 46 {
43 int fd; 47 int fd;
44 int refcnt; 48 int refcnt;
45 char *fname; 49
50 char *file;
51
52 char *dotlock;
53
46 int flags; 54 int flags;
55
56 int expire_time;
57 int retries;
58 int retry_sleep;
47 }; 59 };
48 60
61 /* Assert that we're managing the refcnt and fd correctly, either
62 * we have a lock, and the fd is valid, or refcnt is 0 and fd is -1.
63 * And refcnt can never be less than 0.
64 */
65 #define INVARIANT(l) \
66 assert((l)->refcnt >= 0); \
67 assert( \
68 (((l)->refcnt > 0) && ((l)->fd != -1)) || \
69 (((l)->refcnt == 0) && ((l)->fd == -1)) \
70 );
71
49 int 72 int
50 locker_create (locker_t *plocker, const char *filename, size_t len, int flags) 73 locker_create (locker_t *plocker, const char *filename, int flags)
51 { 74 {
52 locker_t l; 75 locker_t l;
53 76
54 if (plocker == NULL || filename == NULL || len == 0) 77 if (plocker == NULL)
78 return MU_ERR_OUT_PTR_NULL;
79
80 if(filename == NULL)
55 return EINVAL; 81 return EINVAL;
56 82
57 l = malloc (sizeof (*l)); 83 l = calloc (1, sizeof (*l));
58 if (l == NULL) 84 if (l == NULL)
59 return ENOMEM; 85 return ENOMEM;
60 86
61 l->fname = calloc (len + 5 /*strlen(".lock")*/ + 1, sizeof (*(l->fname))); 87 /* Should make l->file be the resulting of following the symlinks. */
62 if (l->fname == NULL) 88 l->file = strdup(filename);
89 if (l->file == NULL)
63 { 90 {
64 free (l); 91 free (l);
65 return ENOMEM; 92 return ENOMEM;
66 } 93 }
67 memcpy (l->fname, filename, len); 94
68 strcat (l->fname, ".lock"); 95 l->dotlock = malloc(strlen(l->file) + 5 /*strlen(".lock")*/ + 1);
96
97 if(!l->dotlock)
98 {
99 free(l->file);
100 free(l);
101 return ENOMEM;
102 }
103
104 sprintf(l->dotlock, "%s.lock", l->file);
69 105
70 if (flags) 106 if (flags)
71 l->flags = flags; 107 l->flags = flags;
72 else 108 else
73 l->flags = MU_LOCKER_TIME; /* Default is time lock implementation. */ 109 l->flags = MU_LOCKER_DEFAULT;
110
74 l->fd = -1; 111 l->fd = -1;
75 l->refcnt = 0; 112 l->expire_time = MU_LOCKER_EXPIRE_TIME;
113 l->retries = MU_LOCKER_RETRIES;
114 l->retry_sleep = MU_LOCKER_RETRY_SLEEP;
115
116 INVARIANT(l)
117
76 *plocker = l; 118 *plocker = l;
119
77 return 0; 120 return 0;
78 } 121 }
79 122
...@@ -82,151 +125,387 @@ locker_destroy (locker_t *plocker) ...@@ -82,151 +125,387 @@ locker_destroy (locker_t *plocker)
82 { 125 {
83 if (plocker && *plocker) 126 if (plocker && *plocker)
84 { 127 {
85 free ((*plocker)->fname); 128 close((*plocker)->fd);
129 free ((*plocker)->file);
130 free ((*plocker)->dotlock);
86 free (*plocker); 131 free (*plocker);
87 *plocker = NULL; 132 *plocker = NULL;
88 } 133 }
89 } 134 }
90 135
136 int locker_set_flags (locker_t locker, int flags)
137 {
138 if (!locker)
139 return MU_ERR_LOCKER_NULL;
140
141 locker->flags = flags;
142
143 return 0;
144 }
145
91 int 146 int
92 locker_lock (locker_t lock, int flags) 147 locker_set_expire_time (locker_t locker, int time)
93 { 148 {
94 int fd = -1; 149 if (!locker)
95 char buf[16]; 150 return MU_ERR_LOCKER_NULL;
96 pid_t pid; 151
97 int removed = 0; 152 if(time <= 0)
153 return EINVAL;
154
155 locker->expire_time = time;
156
157 return 0;
158 }
159
160 int
161 locker_set_retries (locker_t locker, int retries)
162 {
163 if (!locker)
164 return MU_ERR_LOCKER_NULL;
165
166 if(retries <= 0)
167 return EINVAL;
168
169 locker->retries = retries;
170
171 return 0;
172 }
173
174 int
175 locker_set_retry_sleep (locker_t locker, int retry_sleep)
176 {
177 if (!locker)
178 return MU_ERR_LOCKER_NULL;
179
180 if(retry_sleep <= 0)
181 return EINVAL;
182
183 locker->retry_sleep = retry_sleep;
184
185 return 0;
186 }
187
188 int locker_get_flags (locker_t locker, int *flags)
189 {
190 if (!locker)
191 return MU_ERR_LOCKER_NULL;
192
193 if(!flags)
194 return EINVAL;
195
196 *flags = locker->flags;
197
198 return 0;
199 }
200
201 int
202 locker_get_expire_time (locker_t locker, int *time)
203 {
204 if (!locker)
205 return MU_ERR_LOCKER_NULL;
206
207 if(!time)
208 return EINVAL;
209
210 *time = locker->expire_time;
211
212 return 0;
213 }
214
215 int
216 locker_get_retries (locker_t locker, int *retries)
217 {
218 if (!locker)
219 return MU_ERR_LOCKER_NULL;
220
221 if(!retries)
222 return EINVAL;
223
224 *retries = locker->retries;
225
226 return 0;
227 }
228
229 int
230 locker_get_retry_sleep (locker_t locker, int *retry_sleep)
231 {
232 if (!locker)
233 return MU_ERR_LOCKER_NULL;
234
235 if(!retry_sleep)
236 return EINVAL;
237
238 *retry_sleep = locker->retry_sleep;
239
240 return 0;
241 }
242
243 /* expire a stale lock (if MU_LOCKER_PID or MU_LOCKER_TIME) */
244 static void
245 expire_stale_lock (locker_t lock)
246 {
247 int stale = 0;
248 int fd = open (lock->dotlock, O_RDONLY);
249 if (fd == -1)
250 return;
251
252 /* Check to see if this process is still running. */
253 if (lock->flags & MU_LOCKER_PID)
254 {
255 char buf[16];
256 pid_t pid;
257 int nread = read (fd, buf, sizeof (buf) - 1);
258 if (nread > 0)
259 {
260 buf[nread] = '\0';
261 pid = strtol (buf, NULL, 10);
262 if (pid > 0)
263 {
264 /* Process is gone so we try to remove the lock. */
265 if (kill (pid, 0) == -1)
266 stale = 1;
267 }
268 else
269 stale = 1; /* Corrupted file, remove the lock. */
270 }
271 }
272 /* Check to see if the lock expired. */
273 if (lock->flags & MU_LOCKER_TIME)
274 {
275 struct stat stbuf;
276
277 fstat (fd, &stbuf);
278 /* The lock has expired. */
279 if ((time (NULL) - stbuf.st_mtime) > lock->expire_time)
280 stale = 1;
281 }
282
283 close (fd);
284 if (stale)
285 unlink (lock->dotlock);
286 }
287
288 static int
289 stat_check (const char *file, int fd)
290 {
291 struct stat fn_stat;
292 struct stat fd_stat;
293 int err = 0;
294
295 /* We should always be able to stat a valid fd, so this
296 is an error condition. */
297 if (lstat (file, &fn_stat) || fstat (fd, &fd_stat))
298 err = errno;
299 else
300 {
301 /* If the link and stat don't report the same info, or the
302 file is a symlink, fail the locking. */
303 #define CHK(X) if(X) return EINVAL
304
305 CHK (!S_ISREG (fn_stat.st_mode));
306 CHK (!S_ISREG (fd_stat.st_mode));
307 CHK (fn_stat.st_nlink != 1);
308 CHK (fn_stat.st_dev != fd_stat.st_dev);
309 CHK (fn_stat.st_ino != fd_stat.st_ino);
310 CHK (fn_stat.st_mode != fd_stat.st_mode);
311 CHK (fn_stat.st_nlink != fd_stat.st_nlink);
312 CHK (fn_stat.st_uid != fd_stat.st_uid);
313 CHK (fn_stat.st_gid != fd_stat.st_gid);
314 CHK (fn_stat.st_rdev != fd_stat.st_rdev);
315
316 #undef CHK
317 }
318 return err;
319 }
320
321 int
322 locker_lock (locker_t lock)
323 {
324 int err = 0;
325 int fd = -1;
326 int retries = 1;
98 327
99 (void)flags; /* Ignore for now. */
100 if (lock == NULL) 328 if (lock == NULL)
101 return EINVAL; 329 return EINVAL;
102 330
103 /* Is the lock already applied? 331 INVARIANT (lock)
104 FIXME: should we check flags != lock->flags ?? */ 332 /* Is the lock already applied? */
105 if (lock->fd != -1) 333 if (lock->refcnt > 0)
106 { 334 {
335 assert (lock->fd != -1);
107 lock->refcnt++; 336 lock->refcnt++;
108 return 0; 337 return 0;
109 } 338 }
110 339
111 /* 340 assert (lock->fd == -1);
112 Check for lock existance:
113 if it exists but the process is gone the lock can be removed,
114 if the lock is expired, remove it. */
115 fd = open (lock->fname, O_RDONLY);
116 if (fd != -1)
117 {
118 /* Check to see if this process is still running. */
119 if (lock->flags & MU_LOCKER_PID)
120 {
121 int nread = read (fd, buf, sizeof (buf) - 1);
122 if (nread > 0)
123 {
124 buf[nread] = '\0';
125 pid = strtol (buf, NULL, 10);
126 if (pid > 0)
127 {
128 /* Process is gone so we try to remove the lock. */
129 if (kill (pid, 0) == -1)
130 removed = 1;
131 }
132 else
133 removed = 1; /* Corrupted file, remove the lock. */
134 }
135 }
136 /* Check to see if the lock expired. */
137 if (lock->flags & MU_LOCKER_TIME)
138 {
139 struct stat stbuf;
140
141 fstat (fd, &stbuf);
142 /* The lock has expired. */
143 if ((time (NULL) - stbuf.st_mtime) > MU_LOCKER_EXPIRE_TIME)
144 removed = 1;
145 }
146 341
342 /* Check we are trying to lock a regular file, with a link count
343 of 1, that we have permission to read, etc., or don't lock it. */
344 if ((fd = open (lock->file, O_RDONLY)) == -1)
345 {
346 return errno;
347 }
348 else
349 {
350 err = stat_check (lock->file, fd);
147 close (fd); 351 close (fd);
148 if (removed) 352 fd = -1;
149 unlink (lock->fname); 353 if (err)
354 {
355 if (err == EINVAL)
356 err = MU_ERR_LOCK_BAD_FILE;
357 return err;
358 }
150 } 359 }
151 360
152 /* Try to create the lockfile. */ 361 if (lock->flags & MU_LOCKER_RETRY)
153 fd = open (lock->fname, O_WRONLY | O_CREAT | O_EXCL, LOCKFILE_ATTR); 362 {
154 if (fd == -1) 363 retries = lock->retries;
155 return errno; 364 }
156 else 365
366 while (retries--)
157 { 367 {
158 struct stat fn_stat; 368 /* cleanup after last loop */
159 struct stat fd_stat; 369 close (fd);
160 370 fd = -1;
161 if (lstat (lock->fname, &fn_stat) 371
162 || fstat(fd, &fd_stat) 372 expire_stale_lock (lock);
163 || fn_stat.st_nlink != 1 373
164 || fn_stat.st_dev != fd_stat.st_dev 374 /* Try to create the lockfile. */
165 || fn_stat.st_ino != fd_stat.st_ino 375 fd = open (lock->dotlock, O_WRONLY | O_CREAT | O_EXCL, LOCKFILE_ATTR);
166 || fn_stat.st_uid != fd_stat.st_uid 376 if (fd == -1)
167 || fn_stat.st_gid != fd_stat.st_gid)
168 { 377 {
169 close (fd); 378 err = errno;
170 unlink (lock->fname); 379
171 return EPERM; 380 /* EEXIST means somebody else has the lock, anything else is an
381 error. */
382 if (err == EEXIST)
383 err = MU_ERR_LOCK_CONFLICT;
384 else
385 return err;
172 } 386 }
387 else
388 {
389 err = stat_check (lock->dotlock, fd);
390 if (err == 0)
391 {
392 /* We got the lock! */
393 break;
394 }
395 else
396 {
397 close (fd);
398 unlink (lock->dotlock);
399
400 /* If there was something invalid about the file/fd, return
401 a more descriptive code. */
402 if (err == EINVAL)
403 err = MU_ERR_LOCK_BAD_LOCK;
404
405 return err;
406 }
407 }
408 if(retries)
409 sleep(lock->retry_sleep);
173 } 410 }
174 411
175 /* Success. */ 412 if (err)
176 sprintf (buf, "%ld", (long)getpid ()); 413 return err;
177 write (fd, buf, strlen (buf)); 414
415 /* We have the lock. */
416 assert (lock->refcnt == 0);
178 417
179 /* Try to get a file lock. */ 418 if (lock->flags & MU_LOCKER_PID)
180 if (lock->flags & MU_LOCKER_FCNTL)
181 { 419 {
182 struct flock fl; 420 char buf[16];
183 421 sprintf (buf, "%ld", (long) getpid ());
184 memset (&fl, 0, sizeof (struct flock)); 422 write (fd, buf, strlen (buf));
185 fl.l_type = F_WRLCK;
186 if (fcntl (fd, F_SETLK, &fl) == -1)
187 {
188 int err = errno;
189 /* Could not get the file lock. */
190 close (fd);
191 unlink (lock->fname); /* Remove the file I created. */
192 return err;
193 }
194 } 423 }
195 424
425 lock->refcnt = 1;
196 lock->fd = fd; 426 lock->fd = fd;
197 lock->refcnt++; 427
428 INVARIANT (lock);
429
198 return 0; 430 return 0;
199 } 431 }
200 432
201 int 433 int
202 locker_touchlock (locker_t lock) 434 locker_touchlock (locker_t lock)
203 { 435 {
204 if (!lock || !lock->fname || lock->fd == -1) 436 if (!lock)
205 return EINVAL; 437 return MU_ERR_LOCKER_NULL;
206 return utime (lock->fname, NULL); 438
439 assert(lock->dotlock);
440
441 INVARIANT(lock);
442
443 if(lock->refcnt > 0)
444 return utime (lock->dotlock, NULL);
445
446 return MU_ERR_LOCK_NOT_HELD;
207 } 447 }
208 448
209 int 449 int
210 locker_unlock (locker_t lock) 450 locker_unlock (locker_t lock)
211 { 451 {
212 if (!lock || !lock->fname || lock->fd == -1 || lock->refcnt <= 0) 452 assert(lock->refcnt >= 0);
213 return EINVAL; 453
454 /* FIXME: distinguish between bad args and lock not held */
455 if (!lock)
456 return MU_ERR_LOCKER_NULL;
457
458 INVARIANT(lock);
459
460 if(lock->refcnt == 0)
461 return MU_ERR_LOCK_NOT_HELD;
462
463 assert(lock->fd != -1);
214 464
215 if (--lock->refcnt > 0) 465 if (--lock->refcnt > 0)
216 return 0; 466 return 0;
217 467
218 if (lock->flags & MU_LOCKER_FCNTL) 468 assert(lock->refcnt == 0);
219 {
220 struct flock fl;
221 469
222 memset (&fl, 0, sizeof (struct flock));
223 fl.l_type = F_UNLCK;
224 /* Unlock failed? */
225 if (fcntl (lock->fd, F_SETLK, &fl) == -1)
226 return errno;
227 }
228 close (lock->fd); 470 close (lock->fd);
229 lock->fd = -1; 471 lock->fd = -1;
230 unlink (lock->fname); 472 unlink (lock->dotlock);
473
474 INVARIANT(lock);
475
231 return 0; 476 return 0;
232 } 477 }
478 int
479 locker_remove_lock (locker_t lock)
480 {
481 int err;
482
483 if (!lock)
484 return MU_ERR_LOCKER_NULL;
485
486 INVARIANT(lock);
487
488 /* If we hold the lock, do an unlock... */
489 if(lock->refcnt > 0)
490 {
491 /* Force the reference count to 1 to unlock the file. */
492 lock->refcnt = 1;
493 return locker_unlock(lock);
494 }
495
496 /* ... if we don't, unlink the lockfile. */
497 err = unlink (lock->dotlock);
498
499 if(err == -1)
500 {
501 err = errno;
502
503 if(err == ENOENT)
504 err = MU_ERR_LOCK_NOT_HELD;
505 }
506
507 INVARIANT(lock);
508
509 return err;
510 }
511
......