From: Karel Zak Date: Mon, 11 Jan 2010 14:12:02 +0000 (+0100) Subject: libmount: add mtab locking code X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=27c6d41518e139529feaac442716509bd8e31f43;p=util-linux libmount: add mtab locking code Signed-off-by: Karel Zak --- diff --git a/shlibs/mount/src/Makefile.am b/shlibs/mount/src/Makefile.am index b525bf73..5cba0da9 100644 --- a/shlibs/mount/src/Makefile.am +++ b/shlibs/mount/src/Makefile.am @@ -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 index 00000000..2d2b3c07 --- /dev/null +++ b/shlibs/mount/src/lock.c @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2009 Karel Zak + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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~.) */ + 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~. 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.) 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~.) + * 2. link linkfile --> lockfile (e.g. /etc/mtab~. --> /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 + +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, " [--verbose] increment number in datafile" }, + { NULL } + }; + + return mnt_run_test(tss, argc, argv); +} + +#endif /* TEST_PROGRAM */ diff --git a/shlibs/mount/src/mount.h.in b/shlibs/mount/src/mount.h.in index ab644a23..05912e11 100644 --- a/shlibs/mount/src/mount.h.in +++ b/shlibs/mount/src/mount.h.in @@ -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) diff --git a/shlibs/mount/src/mountP.h b/shlibs/mount/src/mountP.h index 62a5eca9..82c557b4 100644 --- a/shlibs/mount/src/mountP.h +++ b/shlibs/mount/src/mountP.h @@ -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