Introduce enum flock_type for fcntl_lock() (drop fcntl.h)
[s-mailx.git] / dotlock.c
bloba0129ba2b715eed005d5637bf53c0b39bc13aab1
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 - 2014 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.
36 #ifndef HAVE_AMALGAMATION
37 # include "nail.h"
38 #endif
40 #include <sys/utsname.h>
42 #include <fcntl.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)
62 #define GID_RESET() \
63 do if (realgid != effectivegid && setgid(realgid) == -1) {\
64 perror("setgid");\
65 exit(1);\
66 } while (0)
68 /* GID_*() helper: set the gid if the path is in the normal mail spool */
69 static bool_t _maybe_setgid(char const *name, gid_t gid);
71 /* Check if we can write a lock file at all */
72 static int maildir_access(char const *fname);
74 /* Create a unique file. O_EXCL does not really work over NFS so we follow
75 * the following trick (inspired by S.R. van den Berg):
76 * - make a mostly unique filename and try to create it
77 * - link the unique filename to our target
78 * - get the link count of the target
79 * - unlink the mostly unique filename
80 * - if the link count was 2, then we are ok; else we've failed */
81 static int create_exclusive(char const *fname);
83 static bool_t
84 _maybe_setgid(char const *name, gid_t gid)
86 char const safepath[] = MAILSPOOL;
87 bool_t rv;
88 NYD_ENTER;
90 if (strncmp(name, safepath, sizeof(safepath) - 1) ||
91 strchr(name + sizeof(safepath), '/') != NULL)
92 rv = TRU1;
93 else
94 rv = (setgid(gid) != -1);
95 NYD_LEAVE;
96 return rv;
99 static int
100 maildir_access(char const *fname)
102 char *path, *p;
103 int i;
104 NYD_ENTER;
106 i = (int)strlen(fname);
107 path = ac_alloc(i + 2);
108 memcpy(path, fname, i + 1);
109 p = strrchr(path, '/');
110 if (p != NULL)
111 *p = '\0';
112 if (p == NULL || *path == '\0') {
113 path[0] = '.';
114 path[1] = '\0';
116 i = access(path, R_OK | W_OK | X_OK);
117 ac_free(path);
118 NYD_LEAVE;
119 return i;
122 static int
123 create_exclusive(char const *fname) /* TODO revisit! */
125 char path[PATH_MAX], apid[APID_SZ], *hostname;
126 struct stat st;
127 struct utsname ut;
128 char const *ptr;
129 time_t t;
130 size_t ntries;
131 int pid, fd, serrno, cc;
132 NYD_ENTER;
134 time(&t);
135 uname(&ut);
136 hostname = ut.nodename;
137 pid = (int)getpid();
139 /* We generate a semi-unique filename, from hostname.(pid ^ usec) */
140 if ((ptr = strrchr(fname, '/')) == NULL)
141 ptr = fname;
142 else
143 ++ptr;
145 snprintf(path, sizeof path, "%.*s.%s.%x",
146 (int)PTR2SIZE(ptr - fname), fname, hostname, (pid ^ (int)t));
148 /* We try to create the unique filename */
149 for (ntries = 0; ntries < CREATE_RETRIES; ++ntries) {
150 GID_MAYBESET(path);
151 fd = open(path, O_BITS, 0);
152 serrno = errno;
153 GID_RESET();
154 if (fd != -1) {
155 snprintf(apid, APID_SZ, "%d", pid);
156 write(fd, apid, strlen(apid));
157 close(fd);
158 break;
159 } else if (serrno != EEXIST) {
160 serrno = -1;
161 goto jleave;
165 /* We link the path to the name */
166 GID_MAYBESET(fname);
167 cc = link(path, fname);
168 serrno = errno;
169 GID_RESET();
170 if (cc == -1)
171 goto jbad;
173 /* Note that we stat our own exclusively created name, not the
174 * destination, since the destination can be affected by others */
175 if (stat(path, &st) == -1) {
176 serrno = errno;
177 goto jbad;
180 GID_MAYBESET(fname);
181 unlink(path);
182 GID_RESET();
184 /* If the number of links was two (one for the unique file and one for
185 * the lock), we've won the race */
186 if (st.st_nlink != 2) {
187 errno = EEXIST;
188 serrno = -1;
189 goto jleave;
191 serrno = 0;
192 jleave:
193 NYD_LEAVE;
194 return serrno;
195 jbad:
196 unlink(path);
197 errno = serrno;
198 serrno = -1;
199 goto jleave;
202 FL int
203 fcntl_lock(int fd, enum flock_type ft) /* TODO check callees */
205 struct flock flp;
206 int rv;
207 NYD_ENTER;
209 switch (ft) {
210 case FLOCK_READ: rv = F_RDLCK; break;
211 case FLOCK_WRITE: rv = F_WRLCK; break;
212 default:
213 case FLOCK_UNLOCK: rv = F_UNLCK; break;
216 flp.l_type = rv;
217 flp.l_start = 0;
218 flp.l_whence = SEEK_SET;
219 flp.l_len = 0;
220 rv = fcntl(fd, F_SETLKW, &flp);
221 NYD_LEAVE;
222 return rv;
225 FL int
226 dot_lock(char const *fname, int fd, int pollival, FILE *fp, char const *msg)
228 char path[PATH_MAX];
229 sigset_t nset, oset;
230 int i, olderrno, rv;
231 NYD_ENTER;
233 rv = 0;
234 if (maildir_access(fname) != 0)
235 goto jleave;
236 rv = -1;
238 sigemptyset(&nset);
239 sigaddset(&nset, SIGHUP);
240 sigaddset(&nset, SIGINT);
241 sigaddset(&nset, SIGQUIT);
242 sigaddset(&nset, SIGTERM);
243 sigaddset(&nset, SIGTTIN);
244 sigaddset(&nset, SIGTTOU);
245 sigaddset(&nset, SIGTSTP);
246 sigaddset(&nset, SIGCHLD);
248 snprintf(path, sizeof(path), "%s.lock", fname);
250 for (i = 0; i < DOTLOCK_RETRIES; ++i) {
251 sigprocmask(SIG_BLOCK, &nset, &oset);
252 rv = create_exclusive(path);
253 olderrno = errno;
254 sigprocmask(SIG_SETMASK, &oset, NULL);
255 if (!rv)
256 goto jleave;
257 assert(rv == -1);
259 fcntl_lock(fd, FLOCK_UNLOCK);
260 if (olderrno != EEXIST)
261 goto jleave;
263 if (fp != NULL && msg != NULL)
264 fputs(msg, fp);
266 if (pollival) {
267 if (pollival == -1) {
268 errno = EEXIST;
269 goto jleave;
271 sleep(pollival);
273 fcntl_lock(fd, FLOCK_WRITE);
275 fprintf(stderr, tr(71,
276 "%s seems a stale lock? Need to be removed by hand?\n"), path);
277 jleave:
278 NYD_LEAVE;
279 return rv;
282 FL void
283 dot_unlock(char const *fname)
285 char path[PATH_MAX];
286 NYD_ENTER;
288 if (maildir_access(fname) != 0)
289 goto jleave;
291 snprintf(path, sizeof(path), "%s.lock", fname);
292 GID_MAYBESET(path);
293 unlink(path);
294 GID_RESET();
295 jleave:
296 NYD_LEAVE;
299 /* vim:set fenc=utf-8:s-it-mode */