--- /dev/null
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <time.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include "pathnames.h"
+#include "nls.h"
+
+#include "mountP.h"
+
+/*
+ * lock handler
+ */
+struct _mnt_lock {
+ pid_t id; /* getpid() or so (see linkfile)... */
+ char *lockfile; /* path to lock file (e.g. /etc/mtab~) */
+ char *linkfile; /* path to link file (e.g. /etc/mtab~.<id>) */
+ int lockfile_fd; /* lock file descriptor */
+ int locked; /* do we own the lock? */
+};
+
+
+/**
+ * mnt_new_lock:
+ * @lockfile: path to lockfile or NULL (default is _PATH_MOUNTED_LOCK)
+ * @id: unique linkfile identifier or 0 (default is getpid())
+ *
+ * Returns newly allocated lock handler or NULL on case of error.
+ */
+mnt_lock *mnt_new_lock(const char *lockfile, pid_t id)
+{
+ mnt_lock *ml = calloc(1, sizeof(struct _mnt_lock));
+
+ if (!ml)
+ return NULL;
+
+ ml->lockfile_fd = -1;
+ ml->id = id;
+ if (lockfile) {
+ ml->lockfile = strdup(lockfile);
+ if (!ml->lockfile) {
+ free(ml);
+ return NULL;
+ }
+ }
+ return ml;
+}
+
+/**
+ * mnt_free_lock:
+ * @ml: mnt_lock handler
+ *
+ * Deallocates mnt_lock.
+ */
+void mnt_free_lock(mnt_lock *ml)
+{
+ if (!ml)
+ return;
+ free(ml->lockfile);
+ free(ml->linkfile);
+ free(ml);
+}
+
+/**
+ * mnt_lock_get_lockfile:
+ * @ml: mnt_lock handler
+ *
+ * Returns path to lockfile.
+ */
+const char *mnt_lock_get_lockfile(mnt_lock *ml)
+{
+ if (!ml)
+ return NULL;
+ if (ml->lockfile)
+ return ml->lockfile;
+ return _PATH_MOUNTED_LOCK;
+}
+
+/**
+ * mnt_lock_get_linkfile:
+ * @ml: mnt_lock handler
+ *
+ * Returns unique (per process) path to linkfile.
+ */
+const char *mnt_lock_get_linkfile(mnt_lock *ml)
+{
+ if (!ml)
+ return NULL;
+
+ if (!ml->linkfile) {
+ const char *lf = mnt_lock_get_lockfile(ml);
+ size_t sz;
+
+ if (!lf)
+ return NULL;
+ sz = strlen(lf) + 32;
+
+ ml->linkfile = malloc(sz);
+ if (ml->linkfile)
+ snprintf(ml->linkfile, sz, "%s.%d",
+ lf, ml->id ? ml->id : getpid());
+ }
+ return ml->linkfile;
+}
+
+static void mnt_lockalrm_handler(int sig)
+{
+ /* do nothing, say nothing, be nothing */
+}
+
+/*
+ * Waits for F_SETLKW, unfortunately we have to use SIGALRM here to interrupt
+ * fcntl() to avoid never ending waiting.
+ *
+ * Returns 0 on success, 1 on timeout, -errno on error.
+ */
+static int mnt_wait_lock(mnt_lock *ml, struct flock *fl, time_t maxtime)
+{
+ struct timeval now;
+ struct sigaction sa, osa;
+ int ret = 0;
+
+ gettimeofday(&now, NULL);
+
+ if (now.tv_sec >= maxtime)
+ return 1; /* timeout */
+
+ /* setup ALARM handler -- we don't want to wait forever */
+ sa.sa_flags = 0;
+ sa.sa_handler = mnt_lockalrm_handler;
+ sigfillset (&sa.sa_mask);
+
+ sigaction(SIGALRM, &sa, &osa);
+
+ DBG(DEBUG_LOCKS, fprintf(stderr,
+ "LOCK: (%d) waiting for F_SETLKW.\n", getpid()));
+
+ alarm(maxtime - now.tv_sec);
+ if (fcntl(ml->lockfile_fd, F_SETLKW, fl) == -1)
+ ret = errno == EINTR ? 1 : -errno;
+ alarm(0);
+
+ /* restore old sigaction */
+ sigaction(SIGALRM, &osa, NULL);
+
+ DBG(DEBUG_LOCKS, fprintf(stderr,
+ "LOCK: (%d) leaving mnt_wait_setlkw(), rc=%d.\n", getpid(), ret));
+ return ret;
+}
+
+/*
+ * Create the lock file.
+ *
+ * The old code here used flock on a lock file /etc/mtab~ and deleted
+ * this lock file afterwards. However, as rgooch remarks, that has a
+ * race: a second mount may be waiting on the lock and proceed as
+ * soon as the lock file is deleted by the first mount, and immediately
+ * afterwards a third mount comes, creates a new /etc/mtab~, applies
+ * flock to that, and also proceeds, so that the second and third mount
+ * now both are scribbling in /etc/mtab.
+ *
+ * The new code uses a link() instead of a creat(), where we proceed
+ * only if it was us that created the lock, and hence we always have
+ * to delete the lock afterwards. Now the use of flock() is in principle
+ * superfluous, but avoids an arbitrary sleep().
+ *
+ * Where does the link point to? Obvious choices are mtab and mtab~~.
+ * HJLu points out that the latter leads to races. Right now we use
+ * mtab~.<pid> instead.
+ *
+ *
+ * The original mount locking code has used sleep(1) between attempts and
+ * maximal number of attempts has been 5.
+ *
+ * There was very small number of attempts and extremely long waiting (1s)
+ * that is useless on machines with large number of mount processes.
+ *
+ * Now we wait few thousand microseconds between attempts and we have global
+ * time limit (30s) rather than limit for number of attempts. The advantage
+ * is that this method also counts time which we spend in fcntl(F_SETLKW) and
+ * number of attempts is not restricted.
+ * -- kzak@redhat.com [Mar-2007]
+ *
+ *
+ * This mtab locking code has been refactored and moved to libmount. The mtab
+ * locking is really not perfect (e.g. SIGALRM), but it's stable, reliable and
+ * backwardly compatible code. Don't forget that this code has to be compatible
+ * with 3rd party mounts (/sbin/mount.<foo>) and has to work with NFS.
+ * -- kzak@redhat.com [May-2009]
+ */
+
+/* maximum seconds between first and last attempt */
+#define MOUNTLOCK_MAXTIME 30
+
+/* sleep time (in microseconds, max=999999) between attempts */
+#define MOUNTLOCK_WAITTIME 5000
+
+/* Remove lock file. */
+void mnt_unlock_file(mnt_lock *ml)
+{
+ if (!ml)
+ return;
+
+ DBG(DEBUG_LOCKS, fprintf(stderr, "LOCK: (%d) unlocking/cleaning.\n", getpid()));
+
+ if (ml->locked == 0 && ml->lockfile && ml->linkfile)
+ {
+ /* We have (probably) all files, but we don't own the lock,
+ * Really? Check it! Maybe ml->locked wasn't set properly
+ * because code was interrupted by signal. Paranoia? Yes.
+ *
+ * We own the lock when linkfile == lockfile.
+ */
+ struct stat lo, li;
+
+ if (!stat(ml->lockfile, &lo) && !stat(ml->linkfile, &li) &&
+ lo.st_dev == li.st_dev && lo.st_ino == li.st_ino)
+ ml->locked = 1;
+ }
+ if (ml->linkfile)
+ unlink(ml->linkfile);
+ if (ml->lockfile_fd >= 0)
+ close(ml->lockfile_fd);
+ if (ml->locked == 1 && ml->lockfile)
+ unlink(ml->lockfile);
+
+ ml->locked = 0;
+ ml->lockfile_fd = -1;
+}
+
+/**
+ * mnt_lock:
+ * @ml: pointer to mnt_lock instance
+ *
+ * Creates lock file (e.g. /etc/mtab~). Note that this function uses
+ * alarm().
+ *
+ * Your application has to always call mnt_unlock_file() before exit.
+ *
+ * Locking scheme:
+ *
+ * 1. create linkfile (e.g. /etc/mtab~.<pid>)
+ * 2. link linkfile --> lockfile (e.g. /etc/mtab~.<pid> --> /etc/mtab~)
+ *
+ * 3. a) link() successful: setups F_SETLK lock (see fcnlt(2))
+ * b) link() failed: wait (max 30s) on F_SETLKW lock, goto 2.
+ *
+ * Example:
+ *
+ * mnt_lock *ml;
+ *
+ * void unlock_fallback(void)
+ * {
+ * if (!ml)
+ * return;
+ * mnt_unlock_file(ml);
+ * mnt_free_lock(ml);
+ * }
+ *
+ * int update_mtab()
+ * {
+ * int sig = 0;
+ *
+ * atexit(unlock_fallback);
+ *
+ * ml = mnt_new_lock(NULL, 0);
+ *
+ * if (mnt_lock_file(ml) != 0) {
+ * printf(stderr, "cannot create %s lockfile\n",
+ * mnt_lock_get_lockfile(ml));
+ * return -1;
+ * }
+ *
+ * ... modify mtab ...
+ *
+ * mnt_unlock_file(ml);
+ * mnt_free_lock(ml);
+ * ml = NULL;
+ * return 0;
+ * }
+ *
+ * Returns 0 on success or -1 in case of error.
+ */
+int mnt_lock_file(mnt_lock *ml)
+{
+ int i;
+ struct timespec waittime;
+ struct timeval maxtime;
+ const char *lockfile, *linkfile;
+
+ if (!ml)
+ return -1;
+ if (ml->locked)
+ return 0;
+
+ lockfile = mnt_lock_get_lockfile(ml);
+ if (!lockfile)
+ return -1;
+ linkfile = mnt_lock_get_linkfile(ml);
+ if (!linkfile)
+ return -1;
+
+ i = open(linkfile, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
+ if (i < 0)
+ /* linkfile does not exist (as a file)
+ and we cannot create it. Read-only filesystem?
+ Too many files open in the system?
+ Filesystem full? */
+ goto failed;
+
+ close(i);
+
+ gettimeofday(&maxtime, NULL);
+ maxtime.tv_sec += MOUNTLOCK_MAXTIME;
+
+ waittime.tv_sec = 0;
+ waittime.tv_nsec = (1000 * MOUNTLOCK_WAITTIME);
+
+ /* Repeat until it was us who made the link */
+ while (ml->locked == 0) {
+ struct timeval now;
+ struct flock flock;
+ int j;
+
+ j = link(linkfile, lockfile);
+ if (j == 0)
+ ml->locked = 1;
+
+ if (j < 0 && errno != EEXIST)
+ goto failed;
+
+ ml->lockfile_fd = open(lockfile, O_WRONLY);
+
+ if (ml->lockfile_fd < 0) {
+ /* Strange... Maybe the file was just deleted? */
+ int errsv = errno;
+ gettimeofday(&now, NULL);
+ if (errsv == ENOENT && now.tv_sec < maxtime.tv_sec) {
+ ml->locked = 0;
+ continue;
+ }
+ goto failed;
+ }
+
+ flock.l_type = F_WRLCK;
+ flock.l_whence = SEEK_SET;
+ flock.l_start = 0;
+ flock.l_len = 0;
+
+ if (ml->locked) {
+ /* We made the link. Now claim the lock. */
+ if (fcntl (ml->lockfile_fd, F_SETLK, &flock) == -1) {
+ DBG(DEBUG_LOCKS, fprintf(stderr,
+ "%s: can't F_SETLK lockfile, errno=%d\n",
+ lockfile, errno));
+ /* proceed, since it was us who created the lockfile anyway */
+ }
+ break;
+ } else {
+ /* Someone else made the link. Wait. */
+ int err = mnt_wait_lock(ml, &flock, maxtime.tv_sec);
+
+ if (err == 1) {
+ DBG(DEBUG_LOCKS, fprintf(stderr,
+ "%s: can't create link: time out (perhaps "
+ "there is a stale lock file?)", lockfile));
+ goto failed;
+
+ } else if (err < 0)
+ goto failed;
+
+ nanosleep(&waittime, NULL);
+ close(ml->lockfile_fd);
+ ml->lockfile_fd = -1;
+ }
+ }
+ DBG(DEBUG_LOCKS, fprintf(stderr,
+ "LOCK: %s: (%d) successfully locked\n",
+ ml->lockfile, getpid()));
+ unlink(linkfile);
+ return 0;
+
+failed:
+ mnt_unlock_file(ml);
+ return -1;
+}
+
+#ifdef TEST_PROGRAM
+#include <err.h>
+
+mnt_lock *lock;
+
+/*
+ * read number from @filename, increment the number and
+ * write the number back to the file
+ */
+void increment_data(const char *filename, int verbose, int loopno)
+{
+ long num;
+ FILE *f;
+ char buf[256];
+
+ if (!(f = fopen(filename, "r")))
+ err(EXIT_FAILURE, "%d: failed to open: %s", getpid(), filename);
+
+ if (!fgets(buf, sizeof(buf), f))
+ err(EXIT_FAILURE, "%d failed read: %s", getpid(), filename);
+
+ fclose(f);
+ num = atol(buf) + 1;
+
+ if (!(f = fopen(filename, "w")))
+ err(EXIT_FAILURE, "%d: failed to open: %s", getpid(), filename);
+
+ fprintf(f, "%ld", num);
+ fclose(f);
+
+ if (verbose)
+ fprintf(stderr, "%d: %s: %ld --> %ld (loop=%d)\n", getpid(),
+ filename, num - 1, num, loopno);
+}
+
+void clean_lock(void)
+{
+ fprintf(stderr, "%d: cleaning\n", getpid());
+ if (!lock)
+ return;
+ mnt_unlock_file(lock);
+ mnt_free_lock(lock);
+}
+
+void sig_handler(int sig)
+{
+ errx(EXIT_FAILURE, "\n%d: catch signal: %s\n", getpid(), strsignal(sig));
+}
+
+int test_lock(struct mtest *ts, int argc, char *argv[])
+{
+ const char *lockfile, *datafile;
+ int verbose = 0, loops, l;
+
+ if (argc < 4)
+ return -1;
+
+ lockfile = argv[1];
+ datafile = argv[2];
+ loops = atoi(argv[3]);
+
+ if (argc == 5 && strcmp(argv[4], "--verbose") == 0)
+ verbose = 1;
+
+ atexit(clean_lock);
+
+ /* be paranoid and call exit() (=clean_lock()) for all signals */
+ {
+ int sig = 0;
+ struct sigaction sa;
+
+ sa.sa_handler = sig_handler;
+ sa.sa_flags = 0;
+ sigfillset(&sa.sa_mask);
+
+ while (sigismember(&sa.sa_mask, ++sig) != -1 && sig != SIGCHLD)
+ sigaction (sig, &sa, (struct sigaction *) 0);
+ }
+
+ for (l = 0; l < loops; l++) {
+ lock = mnt_new_lock(lockfile, 0);
+
+ if (mnt_lock_file(lock) == -1) {
+ fprintf(stderr, "%d: failed to create lock file: %s\n",
+ getpid(), lockfile);
+ return -1;
+ }
+
+ increment_data(datafile, verbose, l);
+
+ mnt_unlock_file(lock);
+ mnt_free_lock(lock);
+ lock = NULL;
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct mtest tss[] = {
+ { "--lock", test_lock, " <lockfile> <datafile> <loops> [--verbose] increment number in datafile" },
+ { NULL }
+ };
+
+ return mnt_run_test(tss, argc, argv);
+}
+
+#endif /* TEST_PROGRAM */