mk-mk.in: fix bad substitution (old Bourne shells)
[s-mailx.git] / dotlock.c
blobdf81fd76cf8b426e761319a19f5e2d76284c39dd
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Mailbox file locking.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1996 Christos Zoulas. All rights reserved.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by Christos Zoulas.
21 * 4. The name of the author may not be used to endorse or promote products
22 * derived from this software without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
27 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
28 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
29 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
33 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE dotlock
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 #include <sys/utsname.h>
44 #define APID_SZ 40 /* sufficient for 128 bits pids XXX nail.h */
45 #define CREATE_RETRIES 5 /* XXX nail.h */
46 #define DOTLOCK_RETRIES 15 /* XXX nail.h */
48 #ifdef O_SYNC
49 # define O_BITS (O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_SYNC)
50 #else
51 # define O_BITS (O_WRONLY | O_CREAT | O_TRUNC | O_EXCL)
52 #endif
54 /* TODO Allow safe setgid, optional: check on startup wether in receive mode,
55 * TODO start helper process that is setgid and only does dotlocking.
56 * TODO Approach two, also optional: use a configurable setgid dotlock prog */
57 #define GID_MAYBESET(P) \
58 do if (realgid != effectivegid && !_maybe_setgid(P, effectivegid)) {\
59 perror("setgid");\
60 exit(1);\
61 } while (0)
63 #define GID_RESET() \
64 do if (realgid != effectivegid && setgid(realgid) == -1) {\
65 perror("setgid");\
66 exit(1);\
67 } while (0)
69 /* GID_*() helper: set the gid if the path is in the normal mail spool */
70 static bool_t _maybe_setgid(char const *name, gid_t gid);
72 /* Check if we can write a lock file at all */
73 static bool_t _dot_dir_access(char const *fname);
75 /* Create a unique file. O_EXCL does not really work over NFS so we follow
76 * the following trick (inspired by S.R. van den Berg):
77 * - make a mostly unique filename and try to create it
78 * - link the unique filename to our target
79 * - get the link count of the target
80 * - unlink the mostly unique filename
81 * - if the link count was 2, then we are ok; else we've failed */
82 static int create_exclusive(char const *fname);
84 /* fcntl(2) plus error handling */
85 static bool_t _dot_fcntl_lock(int fd, enum flock_type ft);
87 /* Print a message :) */
88 static void _dot_lock_msg(char const *fname);
90 static bool_t
91 _maybe_setgid(char const *name, gid_t gid)
93 char const safepath[] = MAILSPOOL;
94 bool_t rv;
95 NYD_ENTER;
97 if (strncmp(name, safepath, sizeof(safepath) -1) ||
98 strchr(name + sizeof(safepath), '/') != NULL)
99 rv = TRU1;
100 else
101 rv = (setgid(gid) != -1);
102 NYD_LEAVE;
103 return rv;
106 static bool_t
107 _dot_dir_access(char const *fname)
109 size_t i;
110 char *path, *p;
111 bool_t rv;
112 NYD_ENTER;
114 i = strlen(fname);
115 path = ac_alloc(i + 1 +1);
117 memcpy(path, fname, i +1);
118 p = strrchr(path, '/');
119 if (p != NULL)
120 *p = '\0';
121 if (p == NULL || *path == '\0') {
122 path[0] = '.';
123 path[1] = '\0';
126 if ((rv = is_dir(path))) {
127 for (;;)
128 if (!access(path, R_OK | W_OK | X_OK)) {
129 rv = TRU1;
130 break;
131 } else if (errno != EINTR)
132 break;
135 ac_free(path);
136 NYD_LEAVE;
137 return rv;
140 static int
141 create_exclusive(char const *fname) /* TODO revisit! */
143 char path[PATH_MAX], apid[APID_SZ], *hostname;
144 struct stat st;
145 struct utsname ut;
146 char const *ptr;
147 time_t t;
148 size_t ntries;
149 int pid, fd, serrno, cc;
150 NYD_ENTER;
152 time(&t);
153 uname(&ut);
154 hostname = ut.nodename;
155 pid = (int)getpid();
157 /* We generate a semi-unique filename, from hostname.(pid ^ usec) */
158 if ((ptr = strrchr(fname, '/')) == NULL)
159 ptr = fname;
160 else
161 ++ptr;
163 snprintf(path, sizeof path, "%.*s.%s.%x",
164 (int)PTR2SIZE(ptr - fname), fname, hostname, (pid ^ (int)t));
166 /* We try to create the unique filename */
167 for (ntries = 0; ntries < CREATE_RETRIES; ++ntries) {
168 GID_MAYBESET(path);
169 fd = open(path, O_BITS, 0);
170 serrno = errno;
171 GID_RESET();
172 if (fd != -1) {
173 snprintf(apid, APID_SZ, "%d", pid);
174 write(fd, apid, strlen(apid));
175 close(fd);
176 break;
177 } else if (serrno != EEXIST) {
178 serrno = -1;
179 goto jleave;
183 /* We link the path to the name */
184 GID_MAYBESET(fname);
185 cc = link(path, fname);
186 serrno = errno;
187 GID_RESET();
188 if (cc == -1)
189 goto jbad;
191 /* Note that we stat our own exclusively created name, not the
192 * destination, since the destination can be affected by others */
193 if (stat(path, &st) == -1) {
194 serrno = errno;
195 goto jbad;
198 GID_MAYBESET(fname);
199 unlink(path);
200 GID_RESET();
202 /* If the number of links was two (one for the unique file and one for
203 * the lock), we've won the race */
204 if (st.st_nlink != 2) {
205 errno = EEXIST;
206 serrno = -1;
207 goto jleave;
209 serrno = 0;
210 jleave:
211 NYD_LEAVE;
212 return serrno;
213 jbad:
214 unlink(path);
215 errno = serrno;
216 serrno = -1;
217 goto jleave;
220 static bool_t
221 _dot_fcntl_lock(int fd, enum flock_type ft)
223 struct flock flp;
224 bool_t rv;
225 NYD2_ENTER;
227 switch (ft) {
228 case FLOCK_READ: rv = F_RDLCK; break;
229 case FLOCK_WRITE: rv = F_WRLCK; break;
230 default:
231 case FLOCK_UNLOCK: rv = F_UNLCK; break;
234 /* (For now we restart, but in the future we may not */
235 flp.l_type = rv;
236 flp.l_start = 0;
237 flp.l_whence = SEEK_SET;
238 flp.l_len = 0;
239 while (!(rv = (fcntl(fd, F_SETLKW, &flp) != -1)) && errno == EINTR)
241 NYD2_LEAVE;
242 return rv;
245 static void
246 _dot_lock_msg(char const *fname)
248 NYD2_ENTER;
249 fprintf(stdout, _("Creating dot lock for \"%s\""), fname);
250 NYD2_LEAVE;
253 FL bool_t
254 fcntl_lock(int fd, enum flock_type ft, size_t pollmsecs)
256 size_t retries;
257 bool_t rv;
258 NYD_ENTER;
260 for (rv = FAL0, retries = 0; retries < DOTLOCK_RETRIES; ++retries)
261 if ((rv = _dot_fcntl_lock(fd, ft)) || pollmsecs == 0)
262 break;
263 else
264 sleep(1); /* TODO pollmsecs -> use finer grain */
265 NYD_LEAVE;
266 return rv;
269 FL bool_t
270 dot_lock(char const *fname, int fd, size_t pollmsecs)
272 char path[PATH_MAX];
273 sigset_t nset, oset;
274 int olderrno;
275 size_t retries = 0;
276 bool_t didmsg = FAL0, rv = FAL0;
277 NYD_ENTER;
279 if (options & OPT_D_VV) {
280 _dot_lock_msg(fname);
281 putchar('\n');
282 didmsg = TRU1;
285 while (!_dot_fcntl_lock(fd, FLOCK_WRITE))
286 switch (errno) {
287 case EACCES:
288 case EAGAIN:
289 case ENOLCK:
290 if (pollmsecs > 0 && ++retries < DOTLOCK_RETRIES) {
291 if (!didmsg)
292 _dot_lock_msg(fname);
293 putchar('.');
294 didmsg = -TRU1;
295 sleep(1); /* TODO pollmsecs -> use finer grain */
296 continue;
298 /* FALLTHRU */
299 default:
300 goto jleave;
303 /* If we can't deal with dot-lock files in there, go with the FLOCK lock and
304 * don't fail otherwise */
305 if (!_dot_dir_access(fname)) {
306 if (options & OPT_D_V) /* TODO Really? dotlock's are crucial! Always!?! */
307 fprintf(stderr,
308 _("Can't manage lock files in \"%s\", please check permissions\n"),
309 fname);
310 rv = TRU1;
311 goto jleave;
314 sigemptyset(&nset);
315 sigaddset(&nset, SIGHUP);
316 sigaddset(&nset, SIGINT);
317 sigaddset(&nset, SIGQUIT);
318 sigaddset(&nset, SIGTERM);
319 sigaddset(&nset, SIGTTIN);
320 sigaddset(&nset, SIGTTOU);
321 sigaddset(&nset, SIGTSTP);
322 sigaddset(&nset, SIGCHLD);
324 snprintf(path, sizeof(path), "%s.lock", fname);
326 while (retries++ < DOTLOCK_RETRIES) {
327 sigprocmask(SIG_BLOCK, &nset, &oset);
328 rv = (create_exclusive(path) == 0);
329 olderrno = errno;
330 sigprocmask(SIG_SETMASK, &oset, NULL);
331 if (!rv)
332 goto jleave;
334 while (!_dot_fcntl_lock(fd, FLOCK_UNLOCK))
335 if (pollmsecs == 0 || retries++ >= DOTLOCK_RETRIES)
336 goto jleave;
337 else {
338 if (!didmsg)
339 _dot_lock_msg(fname);
340 putchar('.');
341 didmsg = -TRU1;
342 sleep(1); /* TODO pollmsecs -> use finer grain */
345 if (olderrno != EEXIST)
346 goto jleave;
347 if (pollmsecs == 0) {
348 errno = EEXIST;
349 goto jleave;
352 while (!_dot_fcntl_lock(fd, FLOCK_WRITE))
353 if (pollmsecs == 0 || retries++ >= DOTLOCK_RETRIES)
354 goto jleave;
355 else {
356 if (!didmsg)
357 _dot_lock_msg(fname);
358 putchar('.');
359 didmsg = -TRU1;
360 sleep(1); /* TODO pollmsecs -> use finer grain */
364 fprintf(stderr, _("Is \"%s\" a stale lock? Please remove file manually.\n"),
365 path);
366 jleave:
367 if (didmsg < FAL0)
368 putchar('\n');
369 NYD_LEAVE;
370 return rv;
373 FL void
374 dot_unlock(char const *fname)
376 char path[PATH_MAX];
377 NYD_ENTER;
379 if (!_dot_dir_access(fname))
380 goto jleave;
382 snprintf(path, sizeof(path), "%s.lock", fname);
383 GID_MAYBESET(path);
384 unlink(path);
385 GID_RESET();
386 jleave:
387 NYD_LEAVE;
390 /* s-it-mode */