/* * 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 */