]> err.no Git - util-linux/commitdiff
libmount: add mtab locking code
authorKarel Zak <kzak@redhat.com>
Mon, 11 Jan 2010 14:12:02 +0000 (15:12 +0100)
committerKarel Zak <kzak@redhat.com>
Thu, 3 Jun 2010 13:20:10 +0000 (15:20 +0200)
Signed-off-by: Karel Zak <kzak@redhat.com>
shlibs/mount/src/Makefile.am
shlibs/mount/src/lock.c [new file with mode: 0644]
shlibs/mount/src/mount.h.in
shlibs/mount/src/mountP.h

index b525bf731ad61d2f0768f98045ea020f4acdcfc8..5cba0da9d46b695d95cfa613baa41f5e68b5734e 100644 (file)
@@ -12,7 +12,7 @@ usrlib_exec_LTLIBRARIES = libmount.la
 libmount_la_SOURCES = $(mountinc_HEADERS)
 
 nodist_libmount_la_SOURCES = mount.h version.c utils.c test.c init.c cache.c \
-                       optstr.c optmap.c optent.c optls.c iter.c list.h \
+                       optstr.c optmap.c optent.c optls.c iter.c list.h lock.c \
                        $(top_srcdir)/lib/canonicalize.c
 
 libmount_la_LIBADD = $(ul_libblkid_la)
@@ -41,7 +41,7 @@ uninstall-hook:
        rm -f $(DESTDIR)$(libdir)/libmount.so*
 
 # tests
-noinst_PROGRAMS = test_version test_cache test_optstr test_optls
+noinst_PROGRAMS = test_version test_cache test_optstr test_optls test_lock
 tests_cppflags = $(AM_CPPFLAGS) -DTEST_PROGRAM
 tests_ldadd = .libs/libmount.a $(ul_libblkid_la)
 
@@ -60,3 +60,7 @@ test_optstr_LDADD = $(tests_ldadd)
 test_optls_SOURCES = optls.c
 test_optls_CPPFLAGS = $(tests_cppflags)
 test_optls_LDADD = $(tests_ldadd)
+
+test_lock_SOURCES = lock.c
+test_lock_CPPFLAGS = $(tests_cppflags)
+test_lock_LDADD = $(tests_ldadd)
diff --git a/shlibs/mount/src/lock.c b/shlibs/mount/src/lock.c
new file mode 100644 (file)
index 0000000..2d2b3c0
--- /dev/null
@@ -0,0 +1,510 @@
+/*
+ * 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 */
index ab644a23ef97fa4c134d923a3d9d14ae0047f69c..05912e1123e3018ba473dbedef7caf2321345a42 100644 (file)
@@ -37,6 +37,13 @@ extern "C" {
  */
 typedef struct _mnt_cache mnt_cache;
 
+/**
+ * mnt_lock:
+ *
+ * Stores information about locked file (e.g. /etc/mtab)
+ */
+typedef struct _mnt_lock mnt_lock;
+
 /**
  * mnt_iter:
  *
@@ -176,6 +183,14 @@ extern char *mnt_optls_create_mtab_optstr(mnt_optls *ls);
 extern char *mnt_optls_create_userspace_optstr(mnt_optls *ls);
 extern int mnt_optls_print_debug(mnt_optls *ls, FILE *file);
 
+/* lock.c */
+extern mnt_lock *mnt_new_lock(const char *lockfile, pid_t id);
+extern void mnt_free_lock(mnt_lock *ml);
+extern const char *mnt_lock_get_lockfile(mnt_lock *ml);
+extern const char *mnt_lock_get_linkfile(mnt_lock *ml);
+extern void mnt_unlock_file(mnt_lock *ml);
+extern int mnt_lock_file(mnt_lock *ml);
+
 
 /*
  * mount(8) userspace options masks (MNT_MAP_USERSPACE map)
index 62a5eca9efb8f77daee7a95f13ba7ff02432d663..82c557b4e9655a9889097b2d48b9c4a6f1155d86 100644 (file)
@@ -34,6 +34,7 @@
 #define DEBUG_INIT     (1 << 1)
 #define DEBUG_CACHE    (1 << 2)
 #define DEBUG_OPTIONS  (1 << 3)
+#define DEBUG_LOCKS    (1 << 4)
 #define DEBUG_ALL      0xFFFF
 
 #ifdef CONFIG_LIBMOUNT_DEBUG