/* * lock.c -- routines to lock/unlock files * * $Id: lock_file.c,v 1.1.1.1 1999-02-07 18:14:08 danw Exp $ */ #include #include #ifdef HAVE_ERRNO_H # include #endif #ifdef MMDFONLY # include # include #endif /* MMDFONLY */ #ifdef HAVE_FCNTL_H # include #else # include #endif #if defined(LOCKF_LOCKING) || defined(FLOCK_LOCKING) # 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]; char tmplock[BUFSIZ]; }; /* * Amount of time to wait before * updating ctime of lock file. */ #define NSECS 20 /* * How old does a lock file need to be * before we remove it. */ #define RSECS 180 /* struct for recording and updating locks */ struct lock { int l_fd; char *l_lock; struct lock *l_next; }; /* 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 #ifdef DOT_LOCKING static int lkopen_dot (char *, int, mode_t); static int lockit (struct lockinfo *); static void lockname (char *, struct lockinfo *, int); static void timerON (char *, int); static void timerOFF (int); static RETSIGTYPE alrmser (int); #endif /* * Base routine to open and lock a file, * and return a file descriptor. */ int lkopen (char *file, int access, mode_t mode) { #ifdef KERNEL_LOCKING return lkopen_kernel(file, access, mode); #endif #ifdef DOT_LOCKING return lkopen_dot(file, access, mode); #endif } /* * Base routine to close and unlock a file, * given a file descriptor. */ int lkclose (int fd, char *file) { #ifdef FCNTL_LOCKING struct flock buf; #endif #ifdef DOT_LOCKING struct lockinfo lkinfo; #endif if (fd == -1) return 0; #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 #ifdef LOCKF_LOCKING /* make sure we unlock the whole thing */ lseek (fd, (off_t) 0, SEEK_SET); lockf (fd, F_ULOCK, 0L); #endif #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 return (close (fd)); } /* * Base routine to open and lock a file, * and return a FILE pointer */ FILE * lkfopen (char *file, char *mode) { int fd, access; FILE *fp; if (strcmp (mode, "r") == 0) access = O_RDONLY; else access = O_RDWR; if ((fd = lkopen (file, access, 0)) == -1) return NULL; if ((fp = fdopen (fd, mode)) == NULL) { close (fd); return NULL; } return fp; } /* * Base routine to close and unlock a file, * given a FILE pointer */ int lkfclose (FILE *fp, char *file) { #ifdef FCNTL_LOCKING struct flock buf; #endif #ifdef DOT_LOCKING struct lockinfo lkinfo; #endif if (fp == NULL) return 0; #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 #ifdef FLOCK_LOCKING flock (fileno(fp), LOCK_UN); #endif #ifdef LOCKF_LOCKING /* make sure we unlock the whole thing */ fseek (fp, 0L, SEEK_SET); lockf (fileno(fp), F_ULOCK, 0L); #endif #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 (fclose (fp)); } #ifdef KERNEL_LOCKING /* * open and lock a file, using kernel locking */ static int lkopen_kernel (char *file, int access, mode_t mode) { int fd, i, j; # ifdef FCNTL_LOCKING struct flock buf; # endif /* FCNTL_LOCKING */ for (i = 0; i < 5; i++) { # if defined(LOCKF_LOCKING) || defined(FCNTL_LOCKING) /* remember the original mode */ j = access; /* make sure we open at the beginning */ access &= ~O_APPEND; /* * 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 */ if ((fd = open (file, access | O_NDELAY, 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 # ifdef FLOCK_LOCKING if (flock (fd, LOCK_EX | LOCK_NB) != -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); return fd; } # endif j = errno; close (fd); sleep (5); } close (fd); errno = j; return -1; } #endif /* KERNEL_LOCKING */ #ifdef DOT_LOCKING /* * open and lock a file, using dot locking */ static int lkopen_dot (char *file, int access, mode_t mode) { int i, fd; time_t curtime; struct lockinfo lkinfo; struct stat st; /* open the file */ if ((fd = open (file, access, mode)) == -1) return -1; /* * Get the name of the eventual lock file, as well * as a name for a temporary lock file. */ 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); } } } } /* * Routine that actually tries to create * the lock file. */ static int lockit (struct lockinfo *li) { int fd; char *curlock, *tmplock; #if 0 char buffer[128]; #endif curlock = li->curlock; tmplock = li->tmplock; /* create the temporary lock file */ if ((fd = creat(tmplock, 0600)) == -1) return -1; #if 0 /* write our process id into lock file */ snprintf (buffer, sizeof(buffer), "nmh lock: pid %d\n", (int) getpid()); write(fd, buffer, strlen(buffer) + 1); #endif close (fd); /* * Now try to create the real lock file * by linking to the temporary file. */ fd = link(tmplock, curlock); unlink(tmplock); return (fd == -1 ? -1 : 0); } /* * Get name of lock file, and temporary lock file */ static void lockname (char *file, struct lockinfo *li, int isnewlock) { int bplen, tmplen; char *bp, *cp; #if 0 struct stat st; #endif if ((cp = strrchr (file, '/')) == NULL || *++cp == 0) cp = file; bp = li->curlock; bplen = 0; #ifdef LOCKDIR snprintf (bp, sizeof(li->curlock), "%s/", lockdir); tmplen = strlen (bp); bp += tmplen; bplen += tmplen; #else if (cp != file) { snprintf (bp, sizeof(li->curlock), "%.*s", cp - file, file); tmplen = strlen (bp); bp += tmplen; bplen += tmplen; } #endif #if 0 /* * mmdf style dot locking. Currently not supported. * If we start supporting mmdf style dot locking, * we will need to change the return value of lockname */ if (stat (file, &st) == -1) return -1; snprintf (bp, sizeof(li->curlock) - bplen, "LCK%05d.%05d", st.st_dev, st.st_ino); #endif snprintf (bp, sizeof(li->curlock) - bplen, "%s.lock", cp); /* * If this is for a new lock, create a name for * the temporary lock file for lockit() */ if (isnewlock) { if ((cp = strrchr (li->curlock, '/')) == NULL || *++cp == 0) strncpy (li->tmplock, ",LCK.XXXXXX", sizeof(li->tmplock)); else snprintf (li->tmplock, sizeof(li->tmplock), "%.*s,LCK.XXXXXX", cp - li->curlock, li->curlock); mktemp (li->tmplock); unlink (li->tmplock); /* remove any stray */ } } /* * Add new lockfile to the list of open lockfiles * and start the lock file timer. */ 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; 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) { /* perhaps SIGT{STP,TIN,TOU} ? */ SIGNAL (SIGALRM, alrmser); alarm (NSECS); } l_top = lp; } /* * Search through the list of lockfiles for the * current lockfile, and remove it from the list. */ static void timerOFF (int fd) { struct lock *pp, *lp; alarm(0); if (l_top) { for (pp = lp = l_top; lp; pp = lp, lp = lp->l_next) { if (lp->l_fd == fd) break; } if (lp) { if (lp == l_top) l_top = lp->l_next; else pp->l_next = lp->l_next; free (lp->l_lock); free (lp); } } /* if there are locks left, restart timer */ if (l_top) alarm (NSECS); } /* * If timer goes off, we update the ctime of all open * lockfiles, so another command doesn't remove them. */ static RETSIGTYPE alrmser (int sig) { int j; char *lockfile; struct lock *lp; #ifndef RELIABLE_SIGNALS SIGNAL (SIGALRM, alrmser); #endif /* 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); } /* restart the alarm */ alarm (NSECS); } #endif /* DOT_LOCKING */