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 
22 
ll_mail_alloc(const char * filename)23 struct 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 
getid(char * idbuf)50 static 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 
writeid(char * idbuf,int fd)65 static 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 
readid(char * p,int fd)82 static 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 
getpidid(char * idbuf,char * myidbuf)103 static 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 
ll_mail_lock(struct ll_mail * p)115 int 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 
232 static int try_dotlock(const char *tmpfile,
233 		       const char *dotlock,
234 		       char *idbuf);
235 
try_mail_dotlock(const char * dotlock,char * idbuf)236 static 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 
try_dotlock(const char * tmpname,const char * dotlock,char * idbuf)265 static 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 
dotlock_exists(const char * dotlock,char * myidbuf,int timeout)299 static 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 		{
318 			if (p == getpid() /* Possibly recycled PID */
319 			    || (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 
ll_mail_open_do(struct ll_mail * p,int ro)347 static 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 
ll_mail_open_ro(struct ll_mail * p)432 int ll_mail_open_ro(struct ll_mail *p)
433 {
434 	return ll_mail_open_do(p, 1);
435 }
436 
ll_mail_open(struct ll_mail * p)437 int ll_mail_open(struct ll_mail *p)
438 {
439 	return ll_mail_open_do(p, 0);
440 }
441 
ll_mail_free(struct ll_mail * p)442 void 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 
ll_dotlock(const char * dotlock,const char * tmpfile,int timeout)487 int 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 
504