Imported upstream version 0.59.3
[hcoop/debian/courier-authlib.git] / 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
22static const char rcsid[]="$Id: mail.c,v 1.10 2006/05/28 15:29:52 mrsam Exp $";
23
24struct ll_mail *ll_mail_alloc(const char *filename)
25{
26 struct ll_mail *p=(struct ll_mail *)malloc(sizeof(struct ll_mail));
27
28 if (!p)
29 return NULL;
30
31 if ((p->file=strdup(filename)) == NULL)
32 {
33 free(p);
34 return NULL;
35 }
36
37 p->cclientfd= -1;
38 p->cclientfile=NULL;
39
40 p->dotlock=NULL;
41
42 return p;
43}
44
45#define IDBUFSIZE 512
46
47/*
48** For extra credit, we mark our territory.
49*/
50
51static void getid(char *idbuf)
52{
53 libmail_str_pid_t(getpid(), idbuf);
54
55 while (*idbuf)
56 idbuf++;
57
58 *idbuf++=':';
59
60 idbuf[IDBUFSIZE-NUMBUFSIZE-10]=0;
61
62 if (gethostname(idbuf, IDBUFSIZE-NUMBUFSIZE-10) < 0)
63 strcpy(idbuf, "localhost");
64}
65
66static int writeid(char *idbuf, int fd)
67{
68 int l=strlen(idbuf);
69
70 while (l)
71 {
72 int n=write(fd, idbuf, l);
73
74 if (n <= 0)
75 return (-1);
76
77 l -= n;
78 idbuf += n;
79 }
80 return 0;
81}
82
83static int readid(char *p, int fd)
84{
85 int l=IDBUFSIZE-1;
86
87 while (l)
88 {
89 int n=read(fd, p, l);
90
91 if (n < 0)
92 return (-1);
93
94 if (n == 0)
95 break;
96
97 p += n;
98 l -= n;
99 }
100 *p=0;
101 return 0;
102}
103
104static pid_t getpidid(char *idbuf, char *myidbuf)
105{
106 pid_t p=atol(idbuf);
107
108 if ((idbuf=strchr(idbuf, ':')) == NULL ||
109 (myidbuf=strchr(myidbuf, ':')) == NULL ||
110 strcmp(idbuf, myidbuf))
111 return 0;
112
113 return p;
114}
115
116int ll_mail_lock(struct ll_mail *p)
117{
118 struct stat stat_buf;
119 char idbuf[IDBUFSIZE];
120 char idbuf2[IDBUFSIZE];
121
122 char fn[NUMBUFSIZE*2 + 20];
123 char *f;
124 int fd;
125
126 getid(idbuf);
127
128 if (p->cclientfd >= 0)
129 return 0;
130
131 if (stat(p->file, &stat_buf) < 0)
132 return -1;
133
134 if (snprintf(fn, sizeof(fn), "/tmp/.%lx.%lx",
135 (unsigned long)stat_buf.st_dev,
136 (unsigned long)stat_buf.st_ino) < 0)
137 {
138 errno=ENOSPC;
139 return (-1);
140 }
141
142 if ((f=strdup(fn)) == NULL)
143 return (-1);
144
145 /* We do things a bit differently. First, try O_EXCL */
146
147 if ((fd=open(f, O_RDWR|O_CREAT|O_EXCL, 0644)) >= 0)
148 {
149 struct stat stat_buf2;
150
151 if (ll_lockfd(fd, ll_writelock, ll_whence_start, 0) < 0 ||
152 fcntl(fd, F_SETFD, FD_CLOEXEC) < 0 ||
153 writeid(idbuf, fd) < 0)
154 {
155 /* This shouldn't happen */
156
157 close(fd);
158 free(f);
159 return (-1);
160 }
161
162 /* Rare race condition: */
163
164 if (fstat(fd, &stat_buf) < 0 ||
165 lstat(f, &stat_buf2) < 0 ||
166 stat_buf.st_dev != stat_buf2.st_dev ||
167 stat_buf.st_ino != stat_buf2.st_ino)
168 {
169 errno=EAGAIN;
170 close(fd);
171 free(f);
172 return (-1);
173 }
174
175 p->cclientfd=fd;
176 p->cclientfile=f;
177 return 0;
178 }
179
180 /*
181 ** An existing lockfile. See if it's tagged with another
182 ** pid on this server, which no longer exists.
183 */
184
185 if ((fd=open(f, O_RDONLY)) >= 0)
186 {
187 pid_t p=-1;
188
189 if (readid(idbuf2, fd) == 0 &&
190 (p=getpidid(idbuf2, idbuf)) != 0 &&
191 kill(p, 0) < 0 && errno == ESRCH)
192 {
193 errno=EAGAIN;
194 close(fd);
195 unlink(f); /* Don't try again right away */
196 free(f);
197 return (-1);
198 }
199
200 /* If we can't lock, someone must have it open, game over. */
201
202 if (p == getpid() /* It's us! */
203
204 || ll_lockfd(fd, ll_readlock, ll_whence_start, 0) < 0)
205 {
206 errno=EEXIST;
207 close(fd);
208 free(f);
209 return (-1);
210 }
211
212 close(fd);
213 }
214
215 /* Stale 0-length lockfiles are blown away after 5 mins */
216
217 if (lstat(f, &stat_buf) == 0 && stat_buf.st_size == 0 &&
218 stat_buf.st_mtime + 300 < time(NULL))
219 {
220 errno=EAGAIN;
221 unlink(f);
222 free(f);
223 return (-1);
224 }
225
226 errno=EAGAIN;
227 free(f);
228 return (-1);
229}
230
231/* Try to create a dot-lock */
232
233static int try_dotlock(const char *tmpfile,
234 const char *dotlock,
235 char *idbuf);
236
237static int try_mail_dotlock(const char *dotlock, char *idbuf)
238{
239 char timebuf[NUMBUFSIZE];
240 char pidbuf[NUMBUFSIZE];
241 char *tmpname;
242 int rc;
243
244 libmail_str_time_t(time(NULL), timebuf);
245 libmail_str_pid_t(getpid(), pidbuf);
246
247 tmpname=malloc(strlen(dotlock) + strlen(timebuf) + strlen(pidbuf) +
248 strlen(idbuf) + 10);
249
250 if (!tmpname)
251 return -1;
252
253 strcpy(tmpname, dotlock);
254 strcat(tmpname, ".");
255 strcat(tmpname, timebuf);
256 strcat(tmpname, ".");
257 strcat(tmpname, pidbuf);
258 strcat(tmpname, ".");
259 strcat(tmpname, strchr(idbuf, ':')+1);
260
261 rc=try_dotlock(tmpname, dotlock, idbuf);
262 free(tmpname);
263 return (rc);
264}
265
266static int try_dotlock(const char *tmpname,
267 const char *dotlock,
268 char *idbuf)
269{
270 struct stat stat_buf;
271
272 int fd;
273
274 fd=open(tmpname, O_RDWR | O_CREAT, 0644);
275
276 if (fd < 0)
277 return (-1);
278
279 if (writeid(idbuf, fd))
280 {
281 close(fd);
282 unlink(tmpname);
283 return (-1);
284 }
285 close(fd);
286
287 if (link(tmpname, dotlock) < 0 || stat(tmpname, &stat_buf) ||
288 stat_buf.st_nlink != 2)
289 {
290 if (errno != EEXIST)
291 errno=EIO;
292
293 unlink(tmpname);
294 return (-1);
295 }
296 unlink(tmpname);
297 return (0);
298}
299
300static void dotlock_exists(const char *dotlock, char *myidbuf,
301 int timeout)
302{
303 char idbuf[IDBUFSIZE];
304 struct stat stat_buf;
305 int fd;
306
307 if ((fd=open(dotlock, O_RDONLY)) >= 0)
308 {
309 pid_t p;
310
311 /*
312 ** Where the locking process is on the same server,
313 ** the decision is easy: does the process still exist,
314 ** or not?
315 */
316
317 if (readid(idbuf, fd) == 0 && (p=getpidid(idbuf, myidbuf)))
318 {
319 if (kill(p, 0) < 0 && errno == ESRCH)
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