Merge branch 'debian'
[hcoop/debian/courier-authlib.git] / libs / liblock / mail.c
CommitLineData
d9898ee8 1/*
2** Copyright 2006 Double Precision, Inc. See COPYING for
3** distribution information.
4*/
5
6#include "config.h"
7#include "liblock.h"
8#include "mail.h"
9#include "../numlib/numlib.h"
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <unistd.h>
14#include <errno.h>
15#include <signal.h>
16#include <sys/types.h>
17#include <sys/stat.h>
18#if HAVE_FCNTL_H
19#include <fcntl.h>
20#endif
21
d9898ee8 22
23struct ll_mail *ll_mail_alloc(const char *filename)
24{
25 struct ll_mail *p=(struct ll_mail *)malloc(sizeof(struct ll_mail));
26
27 if (!p)
28 return NULL;
29
30 if ((p->file=strdup(filename)) == NULL)
31 {
32 free(p);
33 return NULL;
34 }
35
36 p->cclientfd= -1;
37 p->cclientfile=NULL;
38
39 p->dotlock=NULL;
40
41 return p;
42}
43
44#define IDBUFSIZE 512
45
46/*
47** For extra credit, we mark our territory.
48*/
49
50static void getid(char *idbuf)
51{
52 libmail_str_pid_t(getpid(), idbuf);
53
54 while (*idbuf)
55 idbuf++;
56
57 *idbuf++=':';
58
59 idbuf[IDBUFSIZE-NUMBUFSIZE-10]=0;
60
61 if (gethostname(idbuf, IDBUFSIZE-NUMBUFSIZE-10) < 0)
62 strcpy(idbuf, "localhost");
63}
64
65static int writeid(char *idbuf, int fd)
66{
67 int l=strlen(idbuf);
68
69 while (l)
70 {
71 int n=write(fd, idbuf, l);
72
73 if (n <= 0)
74 return (-1);
75
76 l -= n;
77 idbuf += n;
78 }
79 return 0;
80}
81
82static int readid(char *p, int fd)
83{
84 int l=IDBUFSIZE-1;
85
86 while (l)
87 {
88 int n=read(fd, p, l);
89
90 if (n < 0)
91 return (-1);
92
93 if (n == 0)
94 break;
95
96 p += n;
97 l -= n;
98 }
99 *p=0;
100 return 0;
101}
102
103static pid_t getpidid(char *idbuf, char *myidbuf)
104{
105 pid_t p=atol(idbuf);
106
107 if ((idbuf=strchr(idbuf, ':')) == NULL ||
108 (myidbuf=strchr(myidbuf, ':')) == NULL ||
109 strcmp(idbuf, myidbuf))
110 return 0;
111
112 return p;
113}
114
115int ll_mail_lock(struct ll_mail *p)
116{
117 struct stat stat_buf;
118 char idbuf[IDBUFSIZE];
119 char idbuf2[IDBUFSIZE];
120
121 char fn[NUMBUFSIZE*2 + 20];
122 char *f;
123 int fd;
124
125 getid(idbuf);
126
127 if (p->cclientfd >= 0)
128 return 0;
129
130 if (stat(p->file, &stat_buf) < 0)
131 return -1;
132
133 if (snprintf(fn, sizeof(fn), "/tmp/.%lx.%lx",
134 (unsigned long)stat_buf.st_dev,
135 (unsigned long)stat_buf.st_ino) < 0)
136 {
137 errno=ENOSPC;
138 return (-1);
139 }
140
141 if ((f=strdup(fn)) == NULL)
142 return (-1);
143
144 /* We do things a bit differently. First, try O_EXCL */
145
146 if ((fd=open(f, O_RDWR|O_CREAT|O_EXCL, 0644)) >= 0)
147 {
148 struct stat stat_buf2;
149
150 if (ll_lockfd(fd, ll_writelock, ll_whence_start, 0) < 0 ||
151 fcntl(fd, F_SETFD, FD_CLOEXEC) < 0 ||
152 writeid(idbuf, fd) < 0)
153 {
154 /* This shouldn't happen */
155
156 close(fd);
157 free(f);
158 return (-1);
159 }
160
161 /* Rare race condition: */
162
163 if (fstat(fd, &stat_buf) < 0 ||
164 lstat(f, &stat_buf2) < 0 ||
165 stat_buf.st_dev != stat_buf2.st_dev ||
166 stat_buf.st_ino != stat_buf2.st_ino)
167 {
168 errno=EAGAIN;
169 close(fd);
170 free(f);
171 return (-1);
172 }
173
174 p->cclientfd=fd;
175 p->cclientfile=f;
176 return 0;
177 }
178
179 /*
180 ** An existing lockfile. See if it's tagged with another
181 ** pid on this server, which no longer exists.
182 */
183
184 if ((fd=open(f, O_RDONLY)) >= 0)
185 {
186 pid_t p=-1;
187
188 if (readid(idbuf2, fd) == 0 &&
189 (p=getpidid(idbuf2, idbuf)) != 0 &&
190 kill(p, 0) < 0 && errno == ESRCH)
191 {
192 errno=EAGAIN;
193 close(fd);
194 unlink(f); /* Don't try again right away */
195 free(f);
196 return (-1);
197 }
198
199 /* If we can't lock, someone must have it open, game over. */
200
201 if (p == getpid() /* It's us! */
202
203 || ll_lockfd(fd, ll_readlock, ll_whence_start, 0) < 0)
204 {
205 errno=EEXIST;
206 close(fd);
207 free(f);
208 return (-1);
209 }
210
211 close(fd);
212 }
213
214 /* Stale 0-length lockfiles are blown away after 5 mins */
215
216 if (lstat(f, &stat_buf) == 0 && stat_buf.st_size == 0 &&
217 stat_buf.st_mtime + 300 < time(NULL))
218 {
219 errno=EAGAIN;
220 unlink(f);
221 free(f);
222 return (-1);
223 }
224
225 errno=EAGAIN;
226 free(f);
227 return (-1);
228}
229
230/* Try to create a dot-lock */
231
232static int try_dotlock(const char *tmpfile,
233 const char *dotlock,
234 char *idbuf);
235
236static int try_mail_dotlock(const char *dotlock, char *idbuf)
237{
238 char timebuf[NUMBUFSIZE];
239 char pidbuf[NUMBUFSIZE];
240 char *tmpname;
241 int rc;
242
243 libmail_str_time_t(time(NULL), timebuf);
244 libmail_str_pid_t(getpid(), pidbuf);
245
246 tmpname=malloc(strlen(dotlock) + strlen(timebuf) + strlen(pidbuf) +
247 strlen(idbuf) + 10);
248
249 if (!tmpname)
250 return -1;
251
252 strcpy(tmpname, dotlock);
253 strcat(tmpname, ".");
254 strcat(tmpname, timebuf);
255 strcat(tmpname, ".");
256 strcat(tmpname, pidbuf);
257 strcat(tmpname, ".");
258 strcat(tmpname, strchr(idbuf, ':')+1);
259
260 rc=try_dotlock(tmpname, dotlock, idbuf);
261 free(tmpname);
262 return (rc);
263}
264
265static int try_dotlock(const char *tmpname,
266 const char *dotlock,
267 char *idbuf)
268{
269 struct stat stat_buf;
270
271 int fd;
272
273 fd=open(tmpname, O_RDWR | O_CREAT, 0644);
274
275 if (fd < 0)
276 return (-1);
277
278 if (writeid(idbuf, fd))
279 {
280 close(fd);
281 unlink(tmpname);
282 return (-1);
283 }
284 close(fd);
285
286 if (link(tmpname, dotlock) < 0 || stat(tmpname, &stat_buf) ||
287 stat_buf.st_nlink != 2)
288 {
289 if (errno != EEXIST)
290 errno=EIO;
291
292 unlink(tmpname);
293 return (-1);
294 }
295 unlink(tmpname);
296 return (0);
297}
298
299static void dotlock_exists(const char *dotlock, char *myidbuf,
300 int timeout)
301{
302 char idbuf[IDBUFSIZE];
303 struct stat stat_buf;
304 int fd;
305
306 if ((fd=open(dotlock, O_RDONLY)) >= 0)
307 {
308 pid_t p;
309
310 /*
311 ** Where the locking process is on the same server,
312 ** the decision is easy: does the process still exist,
313 ** or not?
314 */
315
316 if (readid(idbuf, fd) == 0 && (p=getpidid(idbuf, myidbuf)))
317 {
b0322a85
CE
318 if (p == getpid() /* Possibly recycled PID */
319 || (kill(p, 0) < 0 && errno == ESRCH))
d9898ee8 320 {
321 close(fd);
322 if (unlink(dotlock) == 0)
323 errno=EAGAIN;
324 else
325 errno=EEXIST;
326 return;
327 }
328 }
329 else if (timeout > 0 && fstat(fd, &stat_buf) >= 0 &&
330 stat_buf.st_mtime < time(NULL) - timeout)
331 {
332 close(fd);
333
334 if (unlink(dotlock) == 0)
335 errno=EAGAIN;
336 else
337 errno=EEXIST;
338 return;
339 }
340
341 close(fd);
342 }
343
344 errno=EEXIST;
345}
346
347static int ll_mail_open_do(struct ll_mail *p, int ro)
348{
349 char *dotlock;
350 char myidbuf[IDBUFSIZE];
351 int save_errno;
352 int fd;
353
354 getid(myidbuf);
355
356 if (p->dotlock) /* Already locked */
357 {
358 fd=open(p->file, ro ? O_RDONLY:O_RDWR);
359
360 if (fd >= 0 &&
361 (ll_lockfd(fd, ro ? ll_readlock:ll_writelock, 0, 0) < 0 ||
362 fcntl(fd, F_SETFD, FD_CLOEXEC) < 0))
363 {
364 close(fd);
365 fd= -1;
366 }
367 return fd;
368 }
369
370 if ((dotlock=malloc(strlen(p->file)+sizeof(".lock"))) == NULL)
371 return -1;
372
373 strcat(strcpy(dotlock, p->file), ".lock");
374
375 if (try_mail_dotlock(dotlock, myidbuf) == 0)
376 {
377 fd=open(p->file, ro ? O_RDONLY:O_RDWR);
378
379 if (fd >= 0 &&
380 (ll_lockfd(fd, ro ? ll_readlock:ll_writelock, 0, 0) ||
381 fcntl(fd, F_SETFD, FD_CLOEXEC) < 0))
382 {
383 close(fd);
384 fd= -1;
385 }
386
387 p->dotlock=dotlock;
388 return fd;
389 }
390
391 save_errno=errno;
392
393 /*
394 ** Last fallback: for EEXIST, a read-only lock should suffice
395 ** In all other instances, we'll fallback to read/write or read-only
396 ** flock as last resort.
397 */
398
399 if ((errno == EEXIST && ro) || errno == EPERM || errno == EACCES)
400 {
401 fd=open(p->file, ro ? O_RDONLY:O_RDWR);
402
403 if (fd >= 0)
404 {
405 if (ll_lockfd(fd, ro ? ll_readlock:ll_writelock,
406 0, 0) == 0 &&
407 fcntl(fd, F_SETFD, FD_CLOEXEC) == 0)
408 {
409 free(dotlock);
410 return fd;
411 }
412 close(fd);
413 }
414 }
415
416 /*
417 ** If try_dotlock blew up for anything other than EEXIST, we don't
418 ** know what the deal is, so punt.
419 */
420
421 if (save_errno != EEXIST)
422 {
423 free(dotlock);
424 return (-1);
425 }
426
427 dotlock_exists(dotlock, myidbuf, 300);
428 free(dotlock);
429 return (-1);
430}
431
432int ll_mail_open_ro(struct ll_mail *p)
433{
434 return ll_mail_open_do(p, 1);
435}
436
437int ll_mail_open(struct ll_mail *p)
438{
439 return ll_mail_open_do(p, 0);
440}
441
442void ll_mail_free(struct ll_mail *p)
443{
444 char myid[IDBUFSIZE];
445 char idbuf[IDBUFSIZE];
446
447 getid(myid);
448
449 if (p->cclientfd >= 0)
450 {
451 if (lseek(p->cclientfd, 0L, SEEK_SET) == 0 &&
452 readid(idbuf, p->cclientfd) == 0 &&
453 strcmp(myid, idbuf) == 0)
454 {
455 if (ftruncate(p->cclientfd, 0) >= 0)
456 unlink(p->cclientfile);
457 }
458 close(p->cclientfd);
459 free(p->cclientfile);
460 }
461
462 if (p->dotlock)
463 {
464 int fd=open(p->dotlock, O_RDONLY);
465
466 if (fd >= 0)
467 {
468 if (readid(idbuf, fd) == 0 &&
469 strcmp(myid, idbuf) == 0)
470 {
471 close(fd);
472 unlink(p->dotlock);
473 free(p->dotlock);
474 free(p->file);
475 free(p);
476 return;
477 }
478 close(fd);
479 }
480
481 free(p->dotlock);
482 }
483 free(p->file);
484 free(p);
485}
486
487int ll_dotlock(const char *dotlock, const char *tmpfile,
488 int timeout)
489{
490 char myidbuf[IDBUFSIZE];
491
492 getid(myidbuf);
493
494 if (try_dotlock(tmpfile, dotlock, myidbuf))
495 {
496 if (errno == EEXIST)
497 dotlock_exists(dotlock, myidbuf, timeout);
498 return -1;
499 }
500 return 0;
501}
502
503