2 * lockfile.c Safely creates a lockfile, also over NFS.
3 * This file also holds the implementation for
4 * the Svr4 maillock functions.
6 * Copyright (C) Miquel van Smoorenburg and contributors 1997-2021.
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
16 #include <sys/types.h>
18 #include <sys/param.h>
39 static char *mlockfile
;
40 static int islocked
= 0;
45 * Get the id of the mailgroup, by statting the helper program.
46 * If it is setgroup-id, then the group is the mailgroup.
52 if (stat(LOCKPROG
, &st
) != 0)
54 if ((st
.st_mode
& 02000) == 0)
60 * Is this a lock for a mailbox? Check:
61 * - is the file in /path/to/USERNAME.lock format
62 * - is /path/to/USERNAME present and owned by us
63 * - is /path/to writable by group mail
65 * To be safe in a setgid program, chdir() into the lockfile
66 * directory first, then pass in the basename of the lockfile.
71 int is_maillock(const char *lockfile
)
78 /* remove .lock suffix */
79 strncpy(tmp
, lockfile
, sizeof(tmp
) - 1);
80 tmp
[sizeof(tmp
) - 1] = 0;
81 if ((p
= strrchr(tmp
, '.')) == NULL
|| strcmp(p
, ".lock") != 0)
85 /* file to lock must exist, and must be owned by us */
86 if (lstat(tmp
, &st
) != 0 ||
87 (st
.st_mode
& S_IFMT
) != S_IFREG
|| st
.st_uid
!= getuid())
90 /* Directory this file is in must be writable by group mail. */
91 if ((gid
= mailgid()) == (gid_t
)-1)
93 if ((p
= strrchr(tmp
, '/')) != NULL
)
96 strncpy(tmp
, ".", sizeof(tmp
));
97 if (stat(tmp
, &st
) != 0 || st
.st_gid
!= gid
|| (st
.st_mode
& 0020) == 0)
105 * Call external program to do the actual locking.
107 static int run_helper(char *opt
, const char *lockfile
, int retries
, int flags
)
109 sigset_t set
, oldset
;
115 * Better safe than sorry.
121 * Block SIGCHLD. The main program might have installed
122 * handlers we don't want to call.
125 sigaddset(&set
, SIGCHLD
);
126 sigprocmask(SIG_BLOCK
, &set
, &oldset
);
129 * Fork, execute locking program and wait.
131 if ((pid
= fork()) < 0)
135 if (setuid(geteuid()) < 0) {
139 snprintf(buf
, sizeof(buf
), "%d", retries
% 1000);
140 execl(LOCKPROG
, LOCKPROG
, opt
, "-r", buf
, "-q",
141 (flags
& L_PID
) ? "-p" : "-N", lockfile
, NULL
);
146 * Wait for return status - do something appropriate
147 * if program died or returned L_ERROR.
149 while ((n
= waitpid(pid
, &st
, 0)) != pid
)
150 if (n
< 0 && errno
!= EINTR
)
152 if (!sigismember(&oldset
, SIGCHLD
))
153 sigprocmask(SIG_UNBLOCK
, &set
, NULL
);
156 if (!WIFEXITED(st
) || WEXITSTATUS(st
) == L_ERROR
) {
161 return WEXITSTATUS(st
);
165 #endif /* MAILGROUP */
167 #define TMPLOCKSTR ".lk"
168 #define TMPLOCKSTRSZ strlen(TMPLOCKSTR)
169 #define TMPLOCKPIDSZ 5
170 #define TMPLOCKTIMESZ 1
171 #define TMPLOCKSYSNAMESZ 23
172 #define TMPLOCKFILENAMESZ (TMPLOCKSTRSZ + TMPLOCKPIDSZ + \
173 TMPLOCKTIMESZ + TMPLOCKSYSNAMESZ)
175 static int lockfilename(const char *lockfile
, char *tmplock
, size_t tmplocksz
)
184 if (strlen(lockfile
) + TMPLOCKFILENAMESZ
> MAXPATHLEN
) {
185 errno
= ENAMETOOLONG
;
190 if (strlen(lockfile
) + TMPLOCKFILENAMESZ
+ 1 > tmplocksz
) {
196 * Create a temp lockfile (hopefully unique) and write
197 * either our pid/ppid in it, or 0\0 for svr4 compatibility.
199 if (gethostname(sysname
, sizeof(sysname
)) < 0)
201 if ((p
= strchr(sysname
, '.')) != NULL
)
203 /* strcpy is safe: length-check above, limited at snprintf below */
204 strcpy(tmplock
, lockfile
);
205 if ((p
= strrchr(tmplock
, '/')) == NULL
)
209 if (snprintf(p
, TMPLOCKFILENAMESZ
, "%s%0*d%0*x%s", TMPLOCKSTR
,
210 TMPLOCKPIDSZ
, (int)getpid(),
211 TMPLOCKTIMESZ
, (int)time(NULL
) & 15,
213 // never happens but gets rid of gcc truncation warning.
224 static int lockfile_create_save_tmplock(const char *lockfile
,
225 char *tmplock
, size_t tmplocksz
,
226 volatile char **xtmplock
,
227 int retries
, int flags
, const struct lockargs_s_
*args
)
237 int tries
= retries
+ 1;
239 /* process optional flags that have arguments */
240 if (flags
& L_INTERVAL_D_
) {
241 sleeptime
= args
->interval
;
244 /* decide which PID to write to the lockfile */
247 if (flags
& L_PPID
) {
254 pidlen
= snprintf(pidbuf
, sizeof(pidbuf
), "%d\n", pid
);
255 if (pidlen
< 0 || pidlen
> (int) sizeof(pidbuf
) - 1) {
260 /* create temporary lockfile */
261 if ((i
= lockfilename(lockfile
, tmplock
, tmplocksz
)) != 0)
265 fd
= open(tmplock
, O_WRONLY
|O_CREAT
|O_EXCL
|O_CLOEXEC
, 0644);
267 /* permission denied? perhaps try suid helper */
268 #if defined(LIB) && defined(MAILGROUP)
269 if (errno
== EACCES
&& is_maillock(lockfile
))
270 return run_helper("-l", lockfile
, retries
, flags
);
274 i
= write(fd
, pidbuf
, pidlen
);
277 if (close(fd
) != 0) {
284 errno
= i
< 0 ? e
: EAGAIN
;
289 * Now try to link the temporary lock to the lock.
291 for (i
= 0; i
< tries
&& tries
> 0; i
++) {
293 if (!(flags
& L_INTERVAL_D_
))
296 if (sleeptime
> 5) sleeptime
= 5;
300 if ((e
= check_sleep(sleeptime
, flags
)) != 0) {
311 * Now lock by linking the tempfile to the lock.
313 * KLUDGE: some people say the return code of
314 * link() over NFS can't be trusted.
315 * EXTRA FIX: the value of the nlink field
316 * can't be trusted (may be cached).
318 (void)!link(tmplock
, lockfile
);
320 if (lstat(tmplock
, &st1
) < 0) {
322 return L_ERROR
; /* Can't happen */
325 if (lstat(lockfile
, &st
) < 0) {
326 if (statfailed
++ > 5) {
328 * Normally, this can't happen; either
329 * another process holds the lockfile or
330 * we do. So if this error pops up
331 * repeatedly, just exit...
334 (void)unlink(tmplock
);
343 * See if we got the lock.
345 if (st
.st_rdev
== st1
.st_rdev
&&
346 st
.st_ino
== st1
.st_ino
) {
347 (void)unlink(tmplock
);
354 * If there is a lockfile and it is invalid,
355 * remove the lockfile.
357 if (lockfile_check(lockfile
, flags
) == -1) {
358 if (unlink(lockfile
) < 0 && errno
!= ENOENT
) {
360 * we failed to unlink the stale
367 * If the lockfile was invalid, then the first
368 * try wasn't valid either - make sure we
369 * try at least once more.
371 if (tries
== 1) tries
++;
375 (void)unlink(tmplock
);
384 int lockfile_create_set_tmplock(const char *lockfile
, volatile char **xtmplock
, int retries
, int flags
, const struct lockargs_s_
*args
)
390 l
= strlen(lockfile
)+TMPLOCKFILENAMESZ
+1;
391 if ((tmplock
= (char *)malloc(l
)) == NULL
)
394 r
= lockfile_create_save_tmplock(lockfile
,
395 tmplock
, l
, xtmplock
, retries
, flags
, args
);
405 int lockfile_create(const char *lockfile
, int retries
, int flags
)
407 /* check against unknown flags */
408 if (flags
& ~(L_PID
|L_PPID
)) {
412 return lockfile_create_set_tmplock(lockfile
, NULL
, retries
, flags
, NULL
);
416 int lockfile_create2(const char *lockfile
, int retries
,
417 int flags
, struct lockargs_s_
*args
, int args_sz
)
420 #define FLAGS_WITH_ARGS (L_INTERVAL_D_)
421 #define KNOWN_FLAGS (L_PID|L_PPID|L_INTERVAL_D_)
423 /* check if size is the same (version check) */
424 if (args
!= NULL
&& sizeof(struct lockargs_s_
) != args_sz
) {
428 /* some flags _must_ have a non-null args */
429 if (args
== NULL
&& (flags
& FLAGS_WITH_ARGS
)) {
433 /* check against unknown flags */
434 if (flags
& ~KNOWN_FLAGS
) {
438 return lockfile_create_set_tmplock(lockfile
, NULL
, retries
, flags
, args
);
445 * See if a valid lockfile is present.
446 * Returns 0 if so, -1 if not.
448 int lockfile_check(const char *lockfile
, int flags
)
456 if (stat(lockfile
, &st
) < 0)
460 * Get the contents and mtime of the lockfile.
464 if ((fd
= open(lockfile
, O_RDONLY
)) >= 0) {
466 * Try to use 'atime after read' as now, this is
467 * the time of the filesystem. Should not get
468 * confused by 'atime' or 'noatime' mount options.
471 if (fstat(fd
, &st
) == 0 &&
472 (len
= read(fd
, buf
, sizeof(buf
))) >= 0 &&
473 fstat(fd
, &st2
) == 0 &&
474 st
.st_atime
!= st2
.st_atime
)
477 if (len
> 0 && (flags
& (L_PID
|L_PPID
))) {
485 * If we have a pid, see if the process
486 * owning the lockfile is still alive.
489 if (r
== 0 || errno
== EPERM
)
491 if (r
< 0 && errno
== ESRCH
)
493 /* EINVAL - FALLTHRU */
497 * Without a pid in the lockfile, the lock
498 * is valid if it is newer than 5 mins.
501 if (now
< st
.st_mtime
+ 300)
510 int lockfile_remove(const char *lockfile
)
512 if (unlink(lockfile
) < 0) {
513 #if defined(LIB) && defined(MAILGROUP)
514 if (errno
== EACCES
&& is_maillock(lockfile
))
515 return run_helper("-u", lockfile
, 0, 0);
517 return errno
== ENOENT
? 0 : -1;
525 int lockfile_touch(const char *lockfile
)
528 return utime(lockfile
, NULL
);
530 return utimes(lockfile
, NULL
);
536 * Lock a mailfile. This looks a lot like the SVR4 function.
537 * Arguments: lusername, retries.
539 int maillock(const char *name
, int retries
)
546 if (islocked
) return 0;
549 if (strlen(name
) + sizeof(MAILDIR
) + 6 > MAXPATHLEN
) {
550 errno
= ENAMETOOLONG
;
556 * If $MAIL is for the same username as "name"
557 * then use $MAIL instead.
560 len
= strlen(name
)+strlen(MAILDIR
)+6;
561 mlockfile
= (char *)malloc(len
);
564 sprintf(mlockfile
, "%s%s.lock", MAILDIR
, name
);
565 if ((mail
= getenv("MAIL")) != NULL
) {
566 if ((p
= strrchr(mail
, '/')) != NULL
)
570 if (strcmp(p
, name
) == 0) {
571 newlen
= strlen(mail
)+6;
573 if (newlen
> MAXPATHLEN
) {
574 errno
= ENAMETOOLONG
;
579 newlock
= (char *)realloc (mlockfile
, newlen
);
580 if (newlock
== NULL
) {
589 sprintf(mlockfile
, "%s.lock", mail
);
592 i
= lockfile_create(mlockfile
, retries
, 0);
593 if (i
== 0) islocked
= 1;
598 void mailunlock(void)
600 if (!islocked
) return;
601 lockfile_remove(mlockfile
);
608 lockfile_touch(mlockfile
);