summaryrefslogtreecommitdiffstats
path: root/solenv/lockfile/lockfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'solenv/lockfile/lockfile.c')
-rw-r--r--solenv/lockfile/lockfile.c611
1 files changed, 611 insertions, 0 deletions
diff --git a/solenv/lockfile/lockfile.c b/solenv/lockfile/lockfile.c
new file mode 100644
index 000000000..52be40b0f
--- /dev/null
+++ b/solenv/lockfile/lockfile.c
@@ -0,0 +1,611 @@
+/*
+ * lockfile.c Safely creates a lockfile, also over NFS.
+ * This file also holds the implementation for
+ * the Svr4 maillock functions.
+ *
+ * Copyright (C) Miquel van Smoorenburg and contributors 1997-2021.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ */
+
+#include "autoconf.h"
+
+#include <sys/types.h>
+#if HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include "lockfile.h"
+#include "maillock.h"
+
+#ifdef HAVE_UTIME
+#include <utime.h>
+#endif
+
+#ifdef LIB
+static char *mlockfile;
+static int islocked = 0;
+#endif
+
+#ifdef MAILGROUP
+/*
+ * Get the id of the mailgroup, by statting the helper program.
+ * If it is setgroup-id, then the group is the mailgroup.
+ */
+static int mailgid()
+{
+ struct stat st;
+
+ if (stat(LOCKPROG, &st) != 0)
+ return (gid_t)-1;
+ if ((st.st_mode & 02000) == 0)
+ return (gid_t)-1;
+ return st.st_gid;
+}
+
+/*
+ * Is this a lock for a mailbox? Check:
+ * - is the file in /path/to/USERNAME.lock format
+ * - is /path/to/USERNAME present and owned by us
+ * - is /path/to writable by group mail
+ *
+ * To be safe in a setgid program, chdir() into the lockfile
+ * directory first, then pass in the basename of the lockfile.
+ */
+#ifdef LIB
+static
+#endif
+int is_maillock(const char *lockfile)
+{
+ struct stat st;
+ gid_t gid;
+ char tmp[1024];
+ char *p;
+
+ /* remove .lock suffix */
+ strncpy(tmp, lockfile, sizeof(tmp) - 1);
+ tmp[sizeof(tmp) - 1] = 0;
+ if ((p = strrchr(tmp, '.')) == NULL || strcmp(p, ".lock") != 0)
+ return 0;
+ *p = 0;
+
+ /* file to lock must exist, and must be owned by us */
+ if (lstat(tmp, &st) != 0 ||
+ (st.st_mode & S_IFMT) != S_IFREG || st.st_uid != getuid())
+ return 0;
+
+ /* Directory this file is in must be writable by group mail. */
+ if ((gid = mailgid()) == (gid_t)-1)
+ return 0;
+ if ((p = strrchr(tmp, '/')) != NULL)
+ *p = 0;
+ else
+ strncpy(tmp, ".", sizeof(tmp));
+ if (stat(tmp, &st) != 0 || st.st_gid != gid || (st.st_mode & 0020) == 0)
+ return 0;
+
+ return 1;
+}
+
+#ifdef LIB
+/*
+ * Call external program to do the actual locking.
+ */
+static int run_helper(char *opt, const char *lockfile, int retries, int flags)
+{
+ sigset_t set, oldset;
+ char buf[8];
+ pid_t pid, n;
+ int st;
+
+ /*
+ * Better safe than sorry.
+ */
+ if (geteuid() == 0)
+ return L_ERROR;
+
+ /*
+ * Block SIGCHLD. The main program might have installed
+ * handlers we don't want to call.
+ */
+ sigemptyset(&set);
+ sigaddset(&set, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &set, &oldset);
+
+ /*
+ * Fork, execute locking program and wait.
+ */
+ if ((pid = fork()) < 0)
+ return L_ERROR;
+ if (pid == 0) {
+ /* drop privs */
+ if (setuid(geteuid()) < 0) {
+ perror("setuid");
+ _exit(L_ERROR);
+ }
+ snprintf(buf, sizeof(buf), "%d", retries % 1000);
+ execl(LOCKPROG, LOCKPROG, opt, "-r", buf, "-q",
+ (flags & L_PID) ? "-p" : "-N", lockfile, NULL);
+ _exit(L_ERROR);
+ }
+
+ /*
+ * Wait for return status - do something appropriate
+ * if program died or returned L_ERROR.
+ */
+ while ((n = waitpid(pid, &st, 0)) != pid)
+ if (n < 0 && errno != EINTR)
+ break;
+ if (!sigismember(&oldset, SIGCHLD))
+ sigprocmask(SIG_UNBLOCK, &set, NULL);
+ if (n < 0)
+ return L_ERROR;
+ if (!WIFEXITED(st) || WEXITSTATUS(st) == L_ERROR) {
+ errno = EINTR;
+ return L_ERROR;
+ }
+
+ return WEXITSTATUS(st);
+}
+#endif /* LIB*/
+
+#endif /* MAILGROUP */
+
+#define TMPLOCKSTR ".lk"
+#define TMPLOCKSTRSZ strlen(TMPLOCKSTR)
+#define TMPLOCKPIDSZ 5
+#define TMPLOCKTIMESZ 1
+#define TMPLOCKSYSNAMESZ 23
+#define TMPLOCKFILENAMESZ (TMPLOCKSTRSZ + TMPLOCKPIDSZ + \
+ TMPLOCKTIMESZ + TMPLOCKSYSNAMESZ)
+
+static int lockfilename(const char *lockfile, char *tmplock, size_t tmplocksz)
+{
+ char sysname[256];
+ char *p;
+
+#ifdef MAXPATHLEN
+ /*
+ * Safety measure.
+ */
+ if (strlen(lockfile) + TMPLOCKFILENAMESZ > MAXPATHLEN) {
+ errno = ENAMETOOLONG;
+ return L_ERROR;
+ }
+#endif
+
+ if (strlen(lockfile) + TMPLOCKFILENAMESZ + 1 > tmplocksz) {
+ errno = EINVAL;
+ return L_ERROR;
+ }
+
+ /*
+ * Create a temp lockfile (hopefully unique) and write
+ * either our pid/ppid in it, or 0\0 for svr4 compatibility.
+ */
+ if (gethostname(sysname, sizeof(sysname)) < 0)
+ return L_ERROR;
+ if ((p = strchr(sysname, '.')) != NULL)
+ *p = 0;
+ /* strcpy is safe: length-check above, limited at snprintf below */
+ strcpy(tmplock, lockfile);
+ if ((p = strrchr(tmplock, '/')) == NULL)
+ p = tmplock;
+ else
+ p++;
+ if (snprintf(p, TMPLOCKFILENAMESZ, "%s%0*d%0*x%s", TMPLOCKSTR,
+ TMPLOCKPIDSZ, (int)getpid(),
+ TMPLOCKTIMESZ, (int)time(NULL) & 15,
+ sysname) < 0) {
+ // never happens but gets rid of gcc truncation warning.
+ errno = EOVERFLOW;
+ return L_ERROR;
+ }
+
+ return 0;
+}
+
+/*
+ * Create a lockfile.
+ */
+static int lockfile_create_save_tmplock(const char *lockfile,
+ char *tmplock, size_t tmplocksz,
+ volatile char **xtmplock,
+ int retries, int flags, const struct lockargs_s_ *args)
+{
+ struct stat st, st1;
+ char pidbuf[40];
+ pid_t pid = 0;
+ int sleeptime = 0;
+ int statfailed = 0;
+ int fd;
+ int i, e, pidlen;
+ int dontsleep = 1;
+ int tries = retries + 1;
+
+ /* process optional flags that have arguments */
+ if (flags & L_INTERVAL_D_) {
+ sleeptime = args->interval;
+ }
+
+ /* decide which PID to write to the lockfile */
+ if (flags & L_PID)
+ pid = getpid();
+ if (flags & L_PPID) {
+ pid = getppid();
+ if (pid == 1) {
+ /* orphaned */
+ return L_ORPHANED;
+ }
+ }
+ pidlen = snprintf(pidbuf, sizeof(pidbuf), "%d\n", pid);
+ if (pidlen < 0 || pidlen > (int) sizeof(pidbuf) - 1) {
+ errno = EOVERFLOW;
+ return L_ERROR;
+ }
+
+ /* create temporary lockfile */
+ if ((i = lockfilename(lockfile, tmplock, tmplocksz)) != 0)
+ return i;
+ if (xtmplock)
+ *xtmplock = tmplock;
+ fd = open(tmplock, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0644);
+ if (fd < 0) {
+ /* permission denied? perhaps try suid helper */
+#if defined(LIB) && defined(MAILGROUP)
+ if (errno == EACCES && is_maillock(lockfile))
+ return run_helper("-l", lockfile, retries, flags);
+#endif
+ return L_TMPLOCK;
+ }
+ i = write(fd, pidbuf, pidlen);
+ e = errno;
+
+ if (close(fd) != 0) {
+ e = errno;
+ i = -1;
+ }
+ if (i != pidlen) {
+ unlink(tmplock);
+ tmplock[0] = 0;
+ errno = i < 0 ? e : EAGAIN;
+ return L_TMPWRITE;
+ }
+
+ /*
+ * Now try to link the temporary lock to the lock.
+ */
+ for (i = 0; i < tries && tries > 0; i++) {
+ if (!dontsleep) {
+ if (!(flags & L_INTERVAL_D_))
+ sleeptime += 5;
+
+ if (sleeptime > 5) sleeptime = 5;
+#ifdef LIB
+ sleep(sleeptime);
+#else
+ if ((e = check_sleep(sleeptime, flags)) != 0) {
+ unlink(tmplock);
+ tmplock[0] = 0;
+ return e;
+ }
+#endif
+ }
+ dontsleep = 0;
+
+
+ /*
+ * Now lock by linking the tempfile to the lock.
+ *
+ * KLUDGE: some people say the return code of
+ * link() over NFS can't be trusted.
+ * EXTRA FIX: the value of the nlink field
+ * can't be trusted (may be cached).
+ */
+ (void)!link(tmplock, lockfile);
+
+ if (lstat(tmplock, &st1) < 0) {
+ tmplock[0] = 0;
+ return L_ERROR; /* Can't happen */
+ }
+
+ if (lstat(lockfile, &st) < 0) {
+ if (statfailed++ > 5) {
+ /*
+ * Normally, this can't happen; either
+ * another process holds the lockfile or
+ * we do. So if this error pops up
+ * repeatedly, just exit...
+ */
+ e = errno;
+ (void)unlink(tmplock);
+ tmplock[0] = 0;
+ errno = e;
+ return L_MAXTRYS;
+ }
+ continue;
+ }
+
+ /*
+ * See if we got the lock.
+ */
+ if (st.st_rdev == st1.st_rdev &&
+ st.st_ino == st1.st_ino) {
+ (void)unlink(tmplock);
+ tmplock[0] = 0;
+ return L_SUCCESS;
+ }
+ statfailed = 0;
+
+ /*
+ * If there is a lockfile and it is invalid,
+ * remove the lockfile.
+ */
+ if (lockfile_check(lockfile, flags) == -1) {
+ if (unlink(lockfile) < 0 && errno != ENOENT) {
+ /*
+ * we failed to unlink the stale
+ * lockfile, give up.
+ */
+ return L_RMSTALE;
+ }
+ dontsleep = 1;
+ /*
+ * If the lockfile was invalid, then the first
+ * try wasn't valid either - make sure we
+ * try at least once more.
+ */
+ if (tries == 1) tries++;
+ }
+
+ }
+ (void)unlink(tmplock);
+ tmplock[0] = 0;
+ errno = EAGAIN;
+ return L_MAXTRYS;
+}
+
+#ifdef LIB
+static
+#endif
+int lockfile_create_set_tmplock(const char *lockfile, volatile char **xtmplock, int retries, int flags, const struct lockargs_s_ *args)
+{
+ char *tmplock;
+ int r, e;
+ size_t l;
+
+ l = strlen(lockfile)+TMPLOCKFILENAMESZ+1;
+ if ((tmplock = (char *)malloc(l)) == NULL)
+ return L_ERROR;
+ tmplock[0] = 0;
+ r = lockfile_create_save_tmplock(lockfile,
+ tmplock, l, xtmplock, retries, flags, args);
+ if (xtmplock)
+ *xtmplock = NULL;
+ e = errno;
+ free(tmplock);
+ errno = e;
+ return r;
+}
+
+#ifdef LIB
+int lockfile_create(const char *lockfile, int retries, int flags)
+{
+ /* check against unknown flags */
+ if (flags & ~(L_PID|L_PPID)) {
+ errno = EINVAL;
+ return L_ERROR;
+ }
+ return lockfile_create_set_tmplock(lockfile, NULL, retries, flags, NULL);
+}
+
+#ifdef STATIC
+int lockfile_create2(const char *lockfile, int retries,
+ int flags, struct lockargs_s_ *args, int args_sz)
+{
+
+ #define FLAGS_WITH_ARGS (L_INTERVAL_D_)
+ #define KNOWN_FLAGS (L_PID|L_PPID|L_INTERVAL_D_)
+
+ /* check if size is the same (version check) */
+ if (args != NULL && sizeof(struct lockargs_s_) != args_sz) {
+ errno = EINVAL;
+ return L_ERROR;
+ }
+ /* some flags _must_ have a non-null args */
+ if (args == NULL && (flags & FLAGS_WITH_ARGS)) {
+ errno = EINVAL;
+ return L_ERROR;
+ }
+ /* check against unknown flags */
+ if (flags & ~KNOWN_FLAGS) {
+ errno = EINVAL;
+ return L_ERROR;
+ }
+ return lockfile_create_set_tmplock(lockfile, NULL, retries, flags, args);
+}
+#endif
+
+#endif
+
+/*
+ * See if a valid lockfile is present.
+ * Returns 0 if so, -1 if not.
+ */
+int lockfile_check(const char *lockfile, int flags)
+{
+ struct stat st, st2;
+ char buf[16];
+ time_t now;
+ pid_t pid;
+ int fd, len, r;
+
+ if (stat(lockfile, &st) < 0)
+ return -1;
+
+ /*
+ * Get the contents and mtime of the lockfile.
+ */
+ time(&now);
+ pid = 0;
+ if ((fd = open(lockfile, O_RDONLY)) >= 0) {
+ /*
+ * Try to use 'atime after read' as now, this is
+ * the time of the filesystem. Should not get
+ * confused by 'atime' or 'noatime' mount options.
+ */
+ len = 0;
+ if (fstat(fd, &st) == 0 &&
+ (len = read(fd, buf, sizeof(buf))) >= 0 &&
+ fstat(fd, &st2) == 0 &&
+ st.st_atime != st2.st_atime)
+ now = st.st_atime;
+ close(fd);
+ if (len > 0 && (flags & (L_PID|L_PPID))) {
+ buf[len] = 0;
+ pid = atoi(buf);
+ }
+ }
+
+ if (pid > 0) {
+ /*
+ * If we have a pid, see if the process
+ * owning the lockfile is still alive.
+ */
+ r = kill(pid, 0);
+ if (r == 0 || errno == EPERM)
+ return 0;
+ if (r < 0 && errno == ESRCH)
+ return -1;
+ /* EINVAL - FALLTHRU */
+ }
+
+ /*
+ * Without a pid in the lockfile, the lock
+ * is valid if it is newer than 5 mins.
+ */
+
+ if (now < st.st_mtime + 300)
+ return 0;
+
+ return -1;
+}
+
+/*
+ * Remove a lock.
+ */
+int lockfile_remove(const char *lockfile)
+{
+ if (unlink(lockfile) < 0) {
+#if defined(LIB) && defined(MAILGROUP)
+ if (errno == EACCES && is_maillock(lockfile))
+ return run_helper("-u", lockfile, 0, 0);
+#endif
+ return errno == ENOENT ? 0 : -1;
+ }
+ return 0;
+}
+
+/*
+ * Touch a lock.
+ */
+int lockfile_touch(const char *lockfile)
+{
+#ifdef HAVE_UTIME
+ return utime(lockfile, NULL);
+#else
+ return utimes(lockfile, NULL);
+#endif
+}
+
+#ifdef LIB
+/*
+ * Lock a mailfile. This looks a lot like the SVR4 function.
+ * Arguments: lusername, retries.
+ */
+int maillock(const char *name, int retries)
+{
+ char *p, *mail;
+ char *newlock;
+ int i, e;
+ int len, newlen;
+
+ if (islocked) return 0;
+
+#ifdef MAXPATHLEN
+ if (strlen(name) + sizeof(MAILDIR) + 6 > MAXPATHLEN) {
+ errno = ENAMETOOLONG;
+ return L_NAMELEN;
+ }
+#endif
+
+ /*
+ * If $MAIL is for the same username as "name"
+ * then use $MAIL instead.
+ */
+
+ len = strlen(name)+strlen(MAILDIR)+6;
+ mlockfile = (char *)malloc(len);
+ if (!mlockfile)
+ return L_ERROR;
+ sprintf(mlockfile, "%s%s.lock", MAILDIR, name);
+ if ((mail = getenv("MAIL")) != NULL) {
+ if ((p = strrchr(mail, '/')) != NULL)
+ p++;
+ else
+ p = mail;
+ if (strcmp(p, name) == 0) {
+ newlen = strlen(mail)+6;
+#ifdef MAXPATHLEN
+ if (newlen > MAXPATHLEN) {
+ errno = ENAMETOOLONG;
+ return L_NAMELEN;
+ }
+#endif
+ if (newlen > len) {
+ newlock = (char *)realloc (mlockfile, newlen);
+ if (newlock == NULL) {
+ e = errno;
+ free (mlockfile);
+ mlockfile = NULL;
+ errno = e;
+ return L_ERROR;
+ }
+ mlockfile = newlock;
+ }
+ sprintf(mlockfile, "%s.lock", mail);
+ }
+ }
+ i = lockfile_create(mlockfile, retries, 0);
+ if (i == 0) islocked = 1;
+
+ return i;
+}
+
+void mailunlock(void)
+{
+ if (!islocked) return;
+ lockfile_remove(mlockfile);
+ free (mlockfile);
+ islocked = 0;
+}
+
+void touchlock(void)
+{
+ lockfile_touch(mlockfile);
+}
+#endif
+