xref: /netbsd-src/usr.bin/mail/dotlock.c (revision b5677b36047b601b9addaaa494a58ceae82c2a6c)
1 /*	$NetBSD: dotlock.c,v 1.10 2009/04/10 13:08:24 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1996 Christos Zoulas.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *	This product includes software developed by Christos Zoulas.
17  * 4. The name of the author may not be used to endorse or promote products
18  *    derived from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 __RCSID("$NetBSD: dotlock.c,v 1.10 2009/04/10 13:08:24 christos Exp $");
35 #endif
36 
37 #include "rcv.h"
38 #include "extern.h"
39 #include "sig.h"
40 
41 #ifndef O_SYNC
42 #define O_SYNC	0
43 #endif
44 
45 static int create_exclusive(const char *);
46 /*
47  * Create a unique file. O_EXCL does not really work over NFS so we follow
48  * the following trick: [Inspired by  S.R. van den Berg]
49  *
50  * - make a mostly unique filename and try to create it.
51  * - link the unique filename to our target
52  * - get the link count of the target
53  * - unlink the mostly unique filename
54  * - if the link count was 2, then we are ok; else we've failed.
55  */
56 static int
57 create_exclusive(const char *fname)
58 {
59 	char path[MAXPATHLEN], hostname[MAXHOSTNAMELEN + 1];
60 	const char *ptr;
61 	struct timeval tv;
62 	pid_t pid;
63 	size_t ntries, cookie;
64 	int fd, serrno;
65 	struct stat st;
66 
67 	(void)gettimeofday(&tv, NULL);
68 	(void)gethostname(hostname, sizeof(hostname));
69 	hostname[sizeof(hostname) - 1] = '\0';
70 	pid = getpid();
71 
72 	cookie = pid ^ tv.tv_usec;
73 
74 	/*
75 	 * We generate a semi-unique filename, from hostname.(pid ^ usec)
76 	 */
77 	if ((ptr = strrchr(fname, '/')) == NULL)
78 		ptr = fname;
79 	else
80 		ptr++;
81 
82 	(void)snprintf(path, sizeof(path), "%.*s.%s.%lx",
83 	    (int)(ptr - fname), fname, hostname, (u_long)cookie);
84 
85 	/*
86 	 * We try to create the unique filename.
87 	 */
88 	for (ntries = 0; ntries < 5; ntries++) {
89 		fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_SYNC, 0);
90 		if (fd != -1) {
91 			(void)close(fd);
92 			break;
93 		}
94 		else if (errno == EEXIST)
95 			continue;
96 		else
97 			return -1;
98 	}
99 
100 	/*
101 	 * We link the path to the name
102 	 */
103 	if (link(path, fname) == -1)
104 		goto bad;
105 
106 	/*
107 	 * Note that we stat our own exclusively created name, not the
108 	 * destination, since the destination can be affected by others.
109 	 */
110 	if (stat(path, &st) == -1)
111 		goto bad;
112 
113 	(void)unlink(path);
114 
115 	/*
116 	 * If the number of links was two (one for the unique file and one
117 	 * for the lock), we've won the race
118 	 */
119 	if (st.st_nlink != 2) {
120 		errno = EEXIST;
121 		return -1;
122 	}
123 	return 0;
124 
125 bad:
126 	serrno = errno;
127 	(void)unlink(path);
128 	errno = serrno;
129 	return -1;
130 }
131 
132 /*
133  * fname -- Pathname to lock
134  * pollinterval -- Interval to check for lock, -1 return
135  * fp -- File to print message
136  * msg -- Message to print
137  */
138 PUBLIC int
139 dot_lock(const char *fname, int pollinterval, FILE *fp, const char *msg)
140 {
141 	char path[MAXPATHLEN];
142 	sigset_t nset, oset;
143 	int retval;
144 
145 	(void)sigemptyset(&nset);
146 	(void)sigaddset(&nset, SIGHUP);
147 	(void)sigaddset(&nset, SIGINT);
148 	(void)sigaddset(&nset, SIGQUIT);
149 	(void)sigaddset(&nset, SIGTERM);
150 	(void)sigaddset(&nset, SIGTTIN);
151 	(void)sigaddset(&nset, SIGTTOU);
152 	(void)sigaddset(&nset, SIGTSTP);
153 	(void)sigaddset(&nset, SIGCHLD);
154 
155 	(void)snprintf(path, sizeof(path), "%s.lock", fname);
156 
157 	retval = -1;
158 	for (;;) {
159 		sig_check();
160 		(void)sigprocmask(SIG_BLOCK, &nset, &oset);
161 		if (create_exclusive(path) != -1) {
162 			(void)sigprocmask(SIG_SETMASK, &oset, NULL);
163 			retval = 0;
164 			break;
165 		}
166 		else
167 			(void)sigprocmask(SIG_SETMASK, &oset, NULL);
168 
169 		if (errno != EEXIST)
170 			break;
171 
172 		if (fp && msg)
173 		    (void)fputs(msg, fp);
174 
175 		if (pollinterval) {
176 			if (pollinterval == -1) {
177 				errno = EEXIST;
178 				break;
179 			}
180 			(void)sleep((unsigned int)pollinterval);
181 		}
182 	}
183 	sig_check();
184 	return retval;
185 }
186 
187 PUBLIC void
188 dot_unlock(const char *fname)
189 {
190 	char path[MAXPATHLEN];
191 
192 	(void)snprintf(path, sizeof(path), "%s.lock", fname);
193 	(void)unlink(path);
194 }
195