X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/0ccd1945560c715e4cc4063209bd0d68b75d5470..94187a80bd60baab4b9c4b949ad820d730578123:/sbr/lock_file.c diff --git a/sbr/lock_file.c b/sbr/lock_file.c index 9771543c..2c8b1c75 100644 --- a/sbr/lock_file.c +++ b/sbr/lock_file.c @@ -1,64 +1,68 @@ - -/* - * lock.c -- routines to lock/unlock files +/* lock_file.c -- routines to lock/unlock files * - * $Id$ + * This code is Copyright (c) 2002, by the authors of nmh. See the + * COPYRIGHT file in the root directory of the nmh distribution for + * complete copyright information. */ +/* Modified by Ruud de Rooij to support Miquel van Smoorenburg's liblockfile + * + * Since liblockfile locking shares most of its code with dot locking, it + * is enabled by defining both DOT_LOCKING and HAVE_LIBLOCKFILE. + * + * Ruud de Rooij Sun, 28 Mar 1999 15:34:03 +0200 + */ + #include #include +#include +#include +#include "lock_file.h" +#include "m_mktemp.h" -#ifdef HAVE_ERRNO_H -# include +#ifdef HAVE_SYS_TIME_H +# include #endif - -#ifdef MMDFONLY -# include -# include -#endif /* MMDFONLY */ - -#ifdef HAVE_FCNTL_H -# include -#else +#include +#include +#ifdef HAVE_FLOCK # include #endif -#if defined(LOCKF_LOCKING) || defined(FLOCK_LOCKING) -# include +#if defined(HAVE_LIBLOCKFILE) +# include #endif -#include - -extern int errno; - #ifdef LOCKDIR char *lockdir = LOCKDIR; #endif -/* Are we using any kernel locking? */ -#if defined (FLOCK_LOCKING) || defined(LOCKF_LOCKING) || defined(FCNTL_LOCKING) -# define KERNEL_LOCKING -#endif - -#ifdef DOT_LOCKING - /* struct for getting name of lock file to create */ struct lockinfo { char curlock[BUFSIZ]; +#if !defined(HAVE_LIBLOCKFILE) char tmplock[BUFSIZ]; +#endif }; +/* + * Number of tries to retry locking + */ +#define LOCK_RETRIES 60 + /* * Amount of time to wait before * updating ctime of lock file. */ #define NSECS 20 +#if !defined(HAVE_LIBLOCKFILE) /* * How old does a lock file need to be * before we remove it. */ #define RSECS 180 +#endif /* HAVE_LIBLOCKFILE */ /* struct for recording and updating locks */ struct lock { @@ -67,109 +71,122 @@ struct lock { struct lock *l_next; }; +enum locktype { FCNTL_LOCKING, FLOCK_LOCKING, LOCKF_LOCKING, DOT_LOCKING }; + +/* Our saved lock types. */ +static enum locktype datalocktype, spoollocktype; + /* top of list containing all open locks */ static struct lock *l_top = NULL; -#endif /* DOT_LOCKING */ -/* - * static prototypes - */ -#ifdef KERNEL_LOCKING -static int lkopen_kernel (char *, int, mode_t); -#endif +static int lkopen(const char *, int, mode_t, enum locktype, int *); +static int str2accbits(const char *); -#ifdef DOT_LOCKING -static int lkopen_dot (char *, int, mode_t); -static int lockit (struct lockinfo *); -static void lockname (char *, struct lockinfo *, int); +static int lkopen_fcntl (const char *, int, mode_t, int *); +#ifdef HAVE_LOCKF +static int lkopen_lockf (const char *, int, mode_t, int *); +#endif /* HAVE_LOCKF */ +#ifdef HAVE_FLOCK +static int lkopen_flock (const char *, int, mode_t, int *); +#endif /* HAVE_FLOCK */ + +static enum locktype init_locktype(const char *) PURE; + +static int lkopen_dot (const char *, int, mode_t, int *); +static void lkclose_dot (int, const char *); +static void lockname (const char *, struct lockinfo *, int); static void timerON (char *, int); static void timerOFF (int); -static RETSIGTYPE alrmser (int); -#endif +static void alrmser (int); +#if !defined(HAVE_LIBLOCKFILE) +static int lockit (struct lockinfo *); +#endif /* - * Base routine to open and lock a file, - * and return a file descriptor. + * Base functions: determine the data type used to lock files and + * call the underlying function. */ int -lkopen (char *file, int access, mode_t mode) +lkopendata(const char *file, int access, mode_t mode, int *failed_to_lock) { -#ifdef KERNEL_LOCKING - return lkopen_kernel(file, access, mode); -#endif + static bool deja_vu; -#ifdef DOT_LOCKING - return lkopen_dot(file, access, mode); -#endif + if (!deja_vu) { + char *dl; + + deja_vu = true; + if ((dl = context_find("datalocking"))) { + datalocktype = init_locktype(dl); + } else { + /* We default to fcntl locking for data files */ + datalocktype = FCNTL_LOCKING; + } + } + + return lkopen(file, access, mode, datalocktype, failed_to_lock); } /* - * Base routine to close and unlock a file, - * given a file descriptor. + * Locking using the spool locking algorithm */ -int -lkclose (int fd, char *file) +int lkopenspool(const char *file, int access, mode_t mode, int *failed_to_lock) { -#ifdef FCNTL_LOCKING - struct flock buf; -#endif + static bool deja_vu; -#ifdef DOT_LOCKING - struct lockinfo lkinfo; -#endif + if (!deja_vu) { + deja_vu = true; + spoollocktype = init_locktype(spoollocking); + } - if (fd == -1) - return 0; + return lkopen(file, access, mode, spoollocktype, failed_to_lock); +} -#ifdef FCNTL_LOCKING - buf.l_type = F_UNLCK; - buf.l_whence = SEEK_SET; - buf.l_start = 0; - buf.l_len = 0; - fcntl(fd, F_SETLK, &buf); -#endif -#ifdef FLOCK_LOCKING - flock (fd, LOCK_UN); -#endif +/* + * Versions of lkopen that return a FILE * + */ -#ifdef LOCKF_LOCKING - /* make sure we unlock the whole thing */ - lseek (fd, (off_t) 0, SEEK_SET); - lockf (fd, F_ULOCK, 0L); -#endif +FILE * +lkfopendata(const char *file, const char *mode, int *failed_to_lock) +{ + FILE *fp; + int oflags = str2accbits(mode); + int fd; -#ifdef DOT_LOCKING - lockname (file, &lkinfo, 0); /* get name of lock file */ - unlink (lkinfo.curlock); /* remove lock file */ - timerOFF (fd); /* turn off lock timer */ -#endif + if (oflags == -1) { + errno = EINVAL; + return NULL; + } - return (close (fd)); -} + if ((fd = lkopendata(file, oflags, 0666, failed_to_lock)) == -1) + return NULL; + if ((fp = fdopen (fd, mode)) == NULL) { + close (fd); + return NULL; + } -/* - * Base routine to open and lock a file, - * and return a FILE pointer - */ + return fp; +} FILE * -lkfopen (char *file, char *mode) +lkfopenspool(const char *file, const char *mode) { - int fd, access; FILE *fp; + int oflags = str2accbits(mode); + int failed_to_lock = 0; + int fd; - if (strcmp (mode, "r") == 0) - access = O_RDONLY; - else - access = O_RDWR; + if (oflags == -1) { + errno = EINVAL; + return NULL; + } - if ((fd = lkopen (file, access, 0)) == -1) + if ((fd = lkopenspool(file, oflags, 0666, &failed_to_lock)) == -1) return NULL; if ((fp = fdopen (fd, mode)) == NULL) { @@ -182,138 +199,283 @@ lkfopen (char *file, char *mode) /* - * Base routine to close and unlock a file, - * given a FILE pointer + * Corresponding close functions. + * + * A note here: All of the kernel locking functions terminate the lock + * when the descriptor is closed, so why write the code to explicitly + * unlock the file? We only need to do this in the dot-locking case. */ int -lkfclose (FILE *fp, char *file) +lkclosedata(int fd, const char *name) { -#ifdef FCNTL_LOCKING - struct flock buf; -#endif + int rc = close(fd); -#ifdef DOT_LOCKING - struct lockinfo lkinfo; -#endif + if (datalocktype == DOT_LOCKING) + lkclose_dot(fd, name); - if (fp == NULL) - return 0; + return rc; +} -#ifdef FCNTL_LOCKING - buf.l_type = F_UNLCK; - buf.l_whence = SEEK_SET; - buf.l_start = 0; - buf.l_len = 0; - fcntl(fileno(fp), F_SETLK, &buf); -#endif +int +lkfclosedata(FILE *f, const char *name) +{ + int fd, rc; -#ifdef FLOCK_LOCKING - flock (fileno(fp), LOCK_UN); -#endif + if (f == NULL) + return 0; + + fd = fileno(f); + rc = fclose(f); -#ifdef LOCKF_LOCKING - /* make sure we unlock the whole thing */ - fseek (fp, 0L, SEEK_SET); - lockf (fileno(fp), F_ULOCK, 0L); -#endif + if (datalocktype == DOT_LOCKING) + lkclose_dot(fd, name); -#ifdef DOT_LOCKING - lockname (file, &lkinfo, 0); /* get name of lock file */ - unlink (lkinfo.curlock); /* remove lock file */ - timerOFF (fileno(fp)); /* turn off lock timer */ -#endif + return rc; +} + +int +lkclosespool(int fd, const char *name) +{ + int rc = close(fd); + + if (spoollocktype == DOT_LOCKING) + lkclose_dot(fd, name); + + return rc; +} + +int +lkfclosespool(FILE *f, const char *name) +{ + int fd, rc; + + if (f == NULL) + return 0; + + fd = fileno(f); + rc = fclose(f); + + if (spoollocktype == DOT_LOCKING) + lkclose_dot(fd, name); - return (fclose (fp)); + return rc; } -#ifdef KERNEL_LOCKING +/* + * Convert fopen() mode argument to open() bits + */ + +static int +str2accbits(const char *mode) +{ + if (strcmp (mode, "r") == 0) + return O_RDONLY; + if (strcmp (mode, "r+") == 0) + return O_RDWR; + if (strcmp (mode, "w") == 0) + return O_WRONLY | O_CREAT | O_TRUNC; + if (strcmp (mode, "w+") == 0) + return O_RDWR | O_CREAT | O_TRUNC; + if (strcmp (mode, "a") == 0) + return O_WRONLY | O_CREAT | O_APPEND; + if (strcmp (mode, "a+") == 0) + return O_RDWR | O_CREAT | O_APPEND; + + errno = EINVAL; + return -1; +} /* - * open and lock a file, using kernel locking + * Internal routine to switch between different locking types. */ static int -lkopen_kernel (char *file, int access, mode_t mode) +lkopen (const char *file, int access, mode_t mode, enum locktype ltype, + int *failed_to_lock) { - int fd, i, j; + switch (ltype) { -# ifdef FCNTL_LOCKING - struct flock buf; -# endif /* FCNTL_LOCKING */ + case FCNTL_LOCKING: + return lkopen_fcntl(file, access, mode, failed_to_lock); - for (i = 0; i < 5; i++) { + case DOT_LOCKING: + return lkopen_dot(file, access, mode, failed_to_lock); -# if defined(LOCKF_LOCKING) || defined(FCNTL_LOCKING) - /* remember the original mode */ - j = access; +#ifdef HAVE_FLOCK + case FLOCK_LOCKING: + return lkopen_flock(file, access, mode, failed_to_lock); +#endif /* HAVE_FLOCK */ - /* make sure we open at the beginning */ - access &= ~O_APPEND; +#ifdef HAVE_LOCKF + case LOCKF_LOCKING: + return lkopen_lockf(file, access, mode, failed_to_lock); +#endif /* HAVE_FLOCK */ - /* - * We MUST have write permission or - * lockf/fcntl() won't work - */ - if ((access & 03) == O_RDONLY) { - access &= ~O_RDONLY; - access |= O_RDWR; - } -# endif /* LOCKF_LOCKING || FCNTL_LOCKING */ + default: + adios(NULL, "Internal locking error: unsupported lock type used!"); + } + + return -1; +} - if ((fd = open (file, access | O_NDELAY, mode)) == -1) + +/* + * Routine to clean up the dot locking file + */ + +static void +lkclose_dot (int fd, const char *file) +{ + struct lockinfo lkinfo; + + lockname (file, &lkinfo, 0); /* get name of lock file */ +#if !defined(HAVE_LIBLOCKFILE) + (void) m_unlink (lkinfo.curlock); /* remove lock file */ +#else + lockfile_remove(lkinfo.curlock); +#endif /* HAVE_LIBLOCKFILE */ + timerOFF (fd); /* turn off lock timer */ +} + + +/* + * Open and lock a file, using fcntl locking + */ + +static int +lkopen_fcntl(const char *file, int access, mode_t mode, int *failed_to_lock) +{ + int fd, i, saved_errno; + struct flock flk; + + /* + * The assumption here is that if you open the file for writing, you + * need an exclusive lock. + */ + + for (i = 0; i < LOCK_RETRIES; i++) { + if ((fd = open(file, access, mode)) == -1) return -1; -# ifdef FCNTL_LOCKING - buf.l_type = F_WRLCK; - buf.l_whence = SEEK_SET; - buf.l_start = 0; - buf.l_len = 0; - if (fcntl (fd, F_SETLK, &buf) != -1) - return fd; -# endif + flk.l_start = 0; + flk.l_len = 0; + flk.l_type = (access & O_ACCMODE) == O_RDONLY ? F_RDLCK : F_WRLCK; + flk.l_whence = SEEK_SET; -# ifdef FLOCK_LOCKING - if (flock (fd, LOCK_EX | LOCK_NB) != -1) + if (fcntl(fd, F_SETLK, &flk) != -1) return fd; -# endif -# ifdef LOCKF_LOCKING - if (lockf (fd, F_TLOCK, 0L) != -1) { - /* see if we should be at the end */ - if (j & O_APPEND) - lseek (fd, (off_t) 0, SEEK_END); + saved_errno = errno; + close(fd); + sleep(1); + } + + *failed_to_lock = 1; + errno = saved_errno; + return -1; +} + + +#ifdef HAVE_FLOCK +/* + * Open and lock a file, using flock locking + */ + +static int +lkopen_flock(const char *file, int access, mode_t mode, int *failed_to_lock) +{ + int fd, i, saved_errno, locktype; + + /* + * The assumption here is that if you open the file for writing, you + * need an exclusive lock. + */ + + locktype = (((access & O_ACCMODE) == O_RDONLY) ? LOCK_SH : LOCK_EX) | + LOCK_NB; + + for (i = 0; i < LOCK_RETRIES; i++) { + if ((fd = open(file, access, mode)) == -1) + return -1; + + if (flock(fd, locktype) != -1) return fd; - } -# endif - j = errno; - close (fd); - sleep (5); + saved_errno = errno; + close(fd); + sleep(1); } - close (fd); - errno = j; + *failed_to_lock = 1; + errno = saved_errno; return -1; } +#endif /* HAVE_FLOCK */ + +/* + * Open and lock a file, using lockf locking + */ + +static int +lkopen_lockf(const char *file, int access, mode_t mode, int *failed_to_lock) +{ + int fd, i, saved_errno, saved_access; + + /* + * Two notes: + * + * Because lockf locks start from the current offset, mask off O_APPEND + * and seek to the end of the file later if it was requested. + * + * lockf locks require write access to the file, so always add it + * even if it wasn't requested. + */ + + saved_access = access; -#endif /* KERNEL_LOCKING */ + access &= ~O_APPEND; + + if ((access & O_ACCMODE) == O_RDONLY) { + access &= ~O_RDONLY; + access |= O_RDWR; + } + for (i = 0; i < LOCK_RETRIES; i++) { + if ((fd = open(file, access, mode)) == -1) + return -1; + + if (lockf(fd, F_TLOCK, 0) != -1) { + /* + * Seek to end if requested + */ + if (saved_access & O_APPEND) { + lseek(fd, 0, SEEK_END); + } + return fd; + } + + saved_errno = errno; + close(fd); + sleep(1); + } + + *failed_to_lock = 1; + errno = saved_errno; + return -1; +} -#ifdef DOT_LOCKING /* * open and lock a file, using dot locking */ static int -lkopen_dot (char *file, int access, mode_t mode) +lkopen_dot (const char *file, int access, mode_t mode, int *failed_to_lock) { - int i, fd; - time_t curtime; + int fd; struct lockinfo lkinfo; - struct stat st; /* open the file */ if ((fd = open (file, access, mode)) == -1) @@ -325,35 +487,58 @@ lkopen_dot (char *file, int access, mode_t mode) */ lockname (file, &lkinfo, 1); - for (i = 0;;) { - /* attempt to create lock file */ - if (lockit (&lkinfo) == 0) { - /* if successful, turn on timer and return */ - timerON (lkinfo.curlock, fd); - return fd; - } else { - /* - * Abort locking, if we fail to lock after 5 attempts - * and are never able to stat the lock file. - */ - if (stat (lkinfo.curlock, &st) == -1) { - if (i++ > 5) - return -1; - sleep (5); - } else { - i = 0; - time (&curtime); - - /* check for stale lockfile, else sleep */ - if (curtime > st.st_ctime + RSECS) - unlink (lkinfo.curlock); - else - sleep (5); +#if !defined(HAVE_LIBLOCKFILE) + { + int i; + for (i = 0; i < LOCK_RETRIES; ++i) { + struct stat st; + + /* attempt to create lock file */ + if (lockit (&lkinfo) == 0) { + /* if successful, turn on timer and return */ + timerON (lkinfo.curlock, fd); + return fd; } + + /* + * Abort locking, if we fail to lock after 5 attempts + * and are never able to stat the lock file. Or, if + * we can stat the lockfile but exceed LOCK_RETRIES + * seconds waiting for it (by falling out of the loop). + */ + if (stat (lkinfo.curlock, &st) == -1) { + if (i++ > 5) break; + sleep (1); + } else { + time_t curtime; + time (&curtime); + + /* check for stale lockfile, else sleep */ + if (curtime > st.st_ctime + RSECS) + (void) m_unlink (lkinfo.curlock); + else + sleep (1); + } + lockname (file, &lkinfo, 1); } + + close(fd); + *failed_to_lock = 1; + return -1; } +#else + if (lockfile_create(lkinfo.curlock, 5, 0) == L_SUCCESS) { + timerON(lkinfo.curlock, fd); + return fd; + } + + close(fd); + *failed_to_lock = 1; + return -1; +#endif /* HAVE_LIBLOCKFILE */ } +#if !defined(HAVE_LIBLOCKFILE) /* * Routine that actually tries to create * the lock file. @@ -363,18 +548,18 @@ static int lockit (struct lockinfo *li) { int fd; - char *curlock, *tmplock; + char *curlock, *tmpfile; #if 0 char buffer[128]; #endif curlock = li->curlock; - tmplock = li->tmplock; - /* create the temporary lock file */ - if ((fd = creat(tmplock, 0600)) == -1) + if ((tmpfile = m_mktemp(li->tmplock, &fd, NULL)) == NULL) { + inform("unable to create temporary file in %s", li->tmplock); return -1; + } #if 0 /* write our process id into lock file */ @@ -388,21 +573,23 @@ lockit (struct lockinfo *li) * Now try to create the real lock file * by linking to the temporary file. */ - fd = link(tmplock, curlock); - unlink(tmplock); + fd = link(tmpfile, curlock); + (void) m_unlink(tmpfile); - return (fd == -1 ? -1 : 0); + return fd == -1 ? -1 : 0; } +#endif /* HAVE_LIBLOCKFILE */ /* * Get name of lock file, and temporary lock file */ static void -lockname (char *file, struct lockinfo *li, int isnewlock) +lockname (const char *file, struct lockinfo *li, int isnewlock) { int bplen, tmplen; - char *bp, *cp; + char *bp; + const char *cp; #if 0 struct stat st; @@ -420,7 +607,7 @@ lockname (char *file, struct lockinfo *li, int isnewlock) bplen += tmplen; #else if (cp != file) { - snprintf (bp, sizeof(li->curlock), "%.*s", cp - file, file); + snprintf (bp, sizeof(li->curlock), "%.*s", (int)(cp - file), file); tmplen = strlen (bp); bp += tmplen; bplen += tmplen; @@ -442,6 +629,9 @@ lockname (char *file, struct lockinfo *li, int isnewlock) snprintf (bp, sizeof(li->curlock) - bplen, "%s.lock", cp); +#if defined(HAVE_LIBLOCKFILE) + NMH_UNUSED(isnewlock); +#else /* * If this is for a new lock, create a name for * the temporary lock file for lockit() @@ -451,15 +641,9 @@ lockname (char *file, struct lockinfo *li, int isnewlock) strncpy (li->tmplock, ",LCK.XXXXXX", sizeof(li->tmplock)); else snprintf (li->tmplock, sizeof(li->tmplock), "%.*s,LCK.XXXXXX", - cp - li->curlock, li->curlock); -#ifdef HAVE_MKSTEMP - mkstemp (li->tmplock); -#else - mktemp (li->tmplock); -#endif - - unlink (li->tmplock); /* remove any stray */ + (int)(cp - li->curlock), li->curlock); } +#endif } @@ -472,18 +656,10 @@ static void timerON (char *curlock, int fd) { struct lock *lp; - size_t len; - if (!(lp = (struct lock *) malloc (sizeof(*lp)))) - return; - - len = strlen(curlock) + 1; + NEW(lp); + lp->l_lock = mh_xstrdup(curlock); lp->l_fd = fd; - if (!(lp->l_lock = malloc (len))) { - free ((char *) lp); - return; - } - memcpy (lp->l_lock, curlock, len); lp->l_next = l_top; if (!l_top) { @@ -491,7 +667,6 @@ timerON (char *curlock, int fd) SIGNAL (SIGALRM, alrmser); alarm (NSECS); } - l_top = lp; } @@ -535,26 +710,60 @@ timerOFF (int fd) * lockfiles, so another command doesn't remove them. */ -static RETSIGTYPE +static void alrmser (int sig) { - int j; char *lockfile; struct lock *lp; - -#ifndef RELIABLE_SIGNALS - SIGNAL (SIGALRM, alrmser); -#endif + NMH_UNUSED (sig); /* update the ctime of all the lock files */ for (lp = l_top; lp; lp = lp->l_next) { lockfile = lp->l_lock; - if (*lockfile && (j = creat (lockfile, 0600)) != -1) - close (j); +#if !defined(HAVE_LIBLOCKFILE) + { + int j; + if (*lockfile && (j = creat (lockfile, 0600)) != -1) + close (j); + } +#else + lockfile_touch(lockfile); +#endif } /* restart the alarm */ alarm (NSECS); } -#endif /* DOT_LOCKING */ + +/* + * Return a locking algorithm based on the string name + */ + +static enum locktype +init_locktype(const char *lockname) +{ + if (strcasecmp(lockname, "fcntl") == 0) { + return FCNTL_LOCKING; + } + if (strcasecmp(lockname, "lockf") == 0) { +#ifdef HAVE_LOCKF + return LOCKF_LOCKING; +#else /* ! HAVE_LOCKF */ + adios(NULL, "lockf not supported on this system"); +#endif /* HAVE_LOCKF */ + } + if (strcasecmp(lockname, "flock") == 0) { +#ifdef HAVE_FLOCK + return FLOCK_LOCKING; +#else /* ! HAVE_FLOCK */ + adios(NULL, "flock not supported on this system"); +#endif /* HAVE_FLOCK */ + } + if (strcasecmp(lockname, "dot") == 0) { + return DOT_LOCKING; + } + adios(NULL, "Unknown lock type: \"%s\"", lockname); + /* NOTREACHED */ + return 0; +}