Blame mtab.c

Packit 5f9837
/*
Packit 5f9837
 * mtab locking routines for use with mount.cifs and umount.cifs
Packit 5f9837
 * Copyright (C) 2008 Jeff Layton (jlayton@samba.org)
Packit 5f9837
 *
Packit 5f9837
 * This program is free software; you can redistribute it and/or modify
Packit 5f9837
 * it under the terms of the GNU General Public License as published by
Packit 5f9837
 * the Free Software Foundation; either version 3 of the License, or
Packit 5f9837
 * (at your option) any later version.
Packit 5f9837
 *
Packit 5f9837
 * This program is distributed in the hope that it will be useful,
Packit 5f9837
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 5f9837
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 5f9837
 * GNU General Public License for more details.
Packit 5f9837
 *
Packit 5f9837
 * You should have received a copy of the GNU General Public License
Packit 5f9837
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit 5f9837
*/
Packit 5f9837
Packit 5f9837
/*
Packit 5f9837
 * This code was copied from the util-linux-ng sources and modified:
Packit 5f9837
 *
Packit 5f9837
 * git://git.kernel.org/pub/scm/utils/util-linux-ng/util-linux-ng.git
Packit 5f9837
 *
Packit 5f9837
 * ...specifically from mount/fstab.c. That file has no explicit license. The
Packit 5f9837
 * "default" license for anything in that tree is apparently GPLv2+, so I
Packit 5f9837
 * believe we're OK to copy it here.
Packit 5f9837
 *
Packit 5f9837
 * Jeff Layton <jlayton@samba.org> 
Packit 5f9837
 */
Packit 5f9837
Packit 5f9837
#include <unistd.h>
Packit 5f9837
#include <errno.h>
Packit 5f9837
#include <stdio.h>
Packit 5f9837
#include <sys/time.h>
Packit 5f9837
#include <sys/stat.h>
Packit 5f9837
#include <time.h>
Packit 5f9837
#include <fcntl.h>
Packit 5f9837
#include <mntent.h>
Packit 5f9837
#include <stdlib.h>
Packit 5f9837
#include <signal.h>
Packit 5f9837
#include <paths.h>
Packit 5f9837
#include "mount.h"
Packit 5f9837
#include "config.h"
Packit 5f9837
Packit 5f9837
Packit 5f9837
/* Updating mtab ----------------------------------------------*/
Packit 5f9837
Packit 5f9837
/* Flag for already existing lock file. */
Packit 5f9837
static int we_created_lockfile = 0;
Packit 5f9837
static int lockfile_fd = -1;
Packit 5f9837
Packit 5f9837
/* Flag to indicate that signals have been set up. */
Packit 5f9837
static int signals_have_been_setup = 0;
Packit 5f9837
Packit 5f9837
static void
Packit 5f9837
handler (int sig __attribute__((unused))) {
Packit 5f9837
     exit(EX_USER);
Packit 5f9837
}
Packit 5f9837
Packit 5f9837
static void
Packit 5f9837
setlkw_timeout (int sig __attribute__((unused))) {
Packit 5f9837
     /* nothing, fcntl will fail anyway */
Packit 5f9837
}
Packit 5f9837
Packit 5f9837
/* use monotonic time for timeouts */
Packit 5f9837
static struct timeval
Packit 5f9837
mono_time(void) {
Packit 5f9837
	struct timeval ret;
Packit 5f9837
#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
Packit 5f9837
	struct timespec ts;
Packit 5f9837
	if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
Packit 5f9837
		ret.tv_sec = ts.tv_sec;
Packit 5f9837
		ret.tv_usec = ts.tv_nsec/1000;
Packit 5f9837
		return ret;
Packit 5f9837
	}
Packit 5f9837
#endif
Packit 5f9837
	gettimeofday(&ret,NULL);
Packit 5f9837
	return ret;
Packit 5f9837
}
Packit 5f9837
Packit 5f9837
/*
Packit 5f9837
 * See if mtab is present and whether it's a symlink. Returns errno from stat()
Packit 5f9837
 * call or EMLINK if it's a symlink.
Packit 5f9837
 */
Packit 5f9837
int
Packit 5f9837
mtab_unusable(void)
Packit 5f9837
{
Packit 5f9837
	struct stat mstat;
Packit 5f9837
Packit 5f9837
	if(lstat(_PATH_MOUNTED, &mstat))
Packit 5f9837
		return errno;
Packit 5f9837
	else if (S_ISLNK(mstat.st_mode))
Packit 5f9837
		return EMLINK;
Packit 5f9837
	return 0;
Packit 5f9837
}
Packit 5f9837
Packit 5f9837
/* Remove lock file.  */
Packit 5f9837
void
Packit 5f9837
unlock_mtab (void) {
Packit 5f9837
	if (we_created_lockfile) {
Packit 5f9837
		close(lockfile_fd);
Packit 5f9837
		lockfile_fd = -1;
Packit 5f9837
		unlink (_PATH_MOUNTED_LOCK);
Packit 5f9837
		we_created_lockfile = 0;
Packit 5f9837
	}
Packit 5f9837
}
Packit 5f9837
Packit 5f9837
/* Create the lock file.
Packit 5f9837
   The lock file will be removed if we catch a signal or when we exit. */
Packit 5f9837
/* The old code here used flock on a lock file /etc/mtab~ and deleted
Packit 5f9837
   this lock file afterwards. However, as rgooch remarks, that has a
Packit 5f9837
   race: a second mount may be waiting on the lock and proceed as
Packit 5f9837
   soon as the lock file is deleted by the first mount, and immediately
Packit 5f9837
   afterwards a third mount comes, creates a new /etc/mtab~, applies
Packit 5f9837
   flock to that, and also proceeds, so that the second and third mount
Packit 5f9837
   now both are scribbling in /etc/mtab.
Packit 5f9837
   The new code uses a link() instead of a creat(), where we proceed
Packit 5f9837
   only if it was us that created the lock, and hence we always have
Packit 5f9837
   to delete the lock afterwards. Now the use of flock() is in principle
Packit 5f9837
   superfluous, but avoids an arbitrary sleep(). */
Packit 5f9837
Packit 5f9837
/* Where does the link point to? Obvious choices are mtab and mtab~~.
Packit 5f9837
   HJLu points out that the latter leads to races. Right now we use
Packit 5f9837
   mtab~.<pid> instead. Use 20 as upper bound for the length of %d. */
Packit 5f9837
#define MOUNTLOCK_LINKTARGET		_PATH_MOUNTED_LOCK "%d"
Packit 5f9837
#define MOUNTLOCK_LINKTARGET_LTH	(sizeof(_PATH_MOUNTED_LOCK)+20)
Packit 5f9837
Packit 5f9837
/*
Packit 5f9837
 * The original mount locking code has used sleep(1) between attempts and
Packit 5f9837
 * maximal number of attemps has been 5.
Packit 5f9837
 *
Packit 5f9837
 * There was very small number of attempts and extremely long waiting (1s)
Packit 5f9837
 * that is useless on machines with large number of concurret mount processes.
Packit 5f9837
 *
Packit 5f9837
 * Now we wait few thousand microseconds between attempts and we have global
Packit 5f9837
 * time limit (30s) rather than limit for number of attempts. The advantage
Packit 5f9837
 * is that this method also counts time which we spend in fcntl(F_SETLKW) and
Packit 5f9837
 * number of attempts is not so much restricted.
Packit 5f9837
 *
Packit 5f9837
 * -- kzak@redhat.com [2007-Mar-2007]
Packit 5f9837
 */
Packit 5f9837
Packit 5f9837
/* maximum seconds between first and last attempt */
Packit 5f9837
#define MOUNTLOCK_MAXTIME		30
Packit 5f9837
Packit 5f9837
/* sleep time (in microseconds, max=999999) between attempts */
Packit 5f9837
#define MOUNTLOCK_WAITTIME		5000
Packit 5f9837
Packit 5f9837
int
Packit 5f9837
lock_mtab (void) {
Packit 5f9837
	int i;
Packit 5f9837
	struct timespec waittime;
Packit 5f9837
	struct timeval maxtime;
Packit 5f9837
	char linktargetfile[MOUNTLOCK_LINKTARGET_LTH];
Packit 5f9837
Packit 5f9837
	if (!signals_have_been_setup) {
Packit 5f9837
		int sig = 0;
Packit 5f9837
		struct sigaction sa;
Packit 5f9837
Packit 5f9837
		sa.sa_handler = handler;
Packit 5f9837
		sa.sa_flags = 0;
Packit 5f9837
		sigfillset (&sa.sa_mask);
Packit 5f9837
Packit 5f9837
		while (sigismember (&sa.sa_mask, ++sig) != -1
Packit 5f9837
		       && sig != SIGCHLD) {
Packit 5f9837
			if (sig == SIGALRM)
Packit 5f9837
				sa.sa_handler = setlkw_timeout;
Packit 5f9837
			else
Packit 5f9837
				sa.sa_handler = handler;
Packit 5f9837
			sigaction (sig, &sa, (struct sigaction *) 0);
Packit 5f9837
		}
Packit 5f9837
		signals_have_been_setup = 1;
Packit 5f9837
	}
Packit 5f9837
Packit 5f9837
	sprintf(linktargetfile, MOUNTLOCK_LINKTARGET, getpid ());
Packit 5f9837
Packit 5f9837
	i = open (linktargetfile, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
Packit 5f9837
	if (i < 0) {
Packit 5f9837
		/* linktargetfile does not exist (as a file)
Packit 5f9837
		   and we cannot create it. Read-only filesystem?
Packit 5f9837
		   Too many files open in the system?
Packit 5f9837
		   Filesystem full? */
Packit 5f9837
		return EX_FILEIO;
Packit 5f9837
	}
Packit 5f9837
	close(i);
Packit 5f9837
Packit 5f9837
	maxtime = mono_time();
Packit 5f9837
	maxtime.tv_sec += MOUNTLOCK_MAXTIME;
Packit 5f9837
Packit 5f9837
	waittime.tv_sec = 0;
Packit 5f9837
	waittime.tv_nsec = (1000 * MOUNTLOCK_WAITTIME);
Packit 5f9837
Packit 5f9837
	/* Repeat until it was us who made the link */
Packit 5f9837
	while (!we_created_lockfile) {
Packit 5f9837
		struct timeval now;
Packit 5f9837
		struct flock flock;
Packit 5f9837
		int errsv, j;
Packit 5f9837
Packit 5f9837
		j = link(linktargetfile, _PATH_MOUNTED_LOCK);
Packit 5f9837
		errsv = errno;
Packit 5f9837
Packit 5f9837
		if (j == 0)
Packit 5f9837
			we_created_lockfile = 1;
Packit 5f9837
Packit 5f9837
		if (j < 0 && errsv != EEXIST) {
Packit 5f9837
			(void) unlink(linktargetfile);
Packit 5f9837
			return EX_FILEIO;
Packit 5f9837
		}
Packit 5f9837
Packit 5f9837
		lockfile_fd = open (_PATH_MOUNTED_LOCK, O_WRONLY);
Packit 5f9837
Packit 5f9837
		if (lockfile_fd < 0) {
Packit 5f9837
			/* Strange... Maybe the file was just deleted? */
Packit 5f9837
			now = mono_time();
Packit 5f9837
			if (errno == ENOENT && now.tv_sec < maxtime.tv_sec) {
Packit 5f9837
				we_created_lockfile = 0;
Packit 5f9837
				continue;
Packit 5f9837
			}
Packit 5f9837
			(void) unlink(linktargetfile);
Packit 5f9837
			return EX_FILEIO;
Packit 5f9837
		}
Packit 5f9837
Packit 5f9837
		flock.l_type = F_WRLCK;
Packit 5f9837
		flock.l_whence = SEEK_SET;
Packit 5f9837
		flock.l_start = 0;
Packit 5f9837
		flock.l_len = 0;
Packit 5f9837
Packit 5f9837
		if (j == 0) {
Packit 5f9837
			/* We made the link. Now claim the lock. If we can't
Packit 5f9837
			 * get it, continue anyway
Packit 5f9837
			 */
Packit 5f9837
			fcntl (lockfile_fd, F_SETLK, &flock);
Packit 5f9837
			(void) unlink(linktargetfile);
Packit 5f9837
		} else {
Packit 5f9837
			/* Someone else made the link. Wait. */
Packit 5f9837
			now = mono_time();
Packit 5f9837
			if (now.tv_sec < maxtime.tv_sec) {
Packit 5f9837
				alarm(maxtime.tv_sec - now.tv_sec);
Packit 5f9837
				if (fcntl (lockfile_fd, F_SETLKW, &flock) == -1) {
Packit 5f9837
					(void) unlink(linktargetfile);
Packit 5f9837
					return EX_FILEIO;
Packit 5f9837
				}
Packit 5f9837
				alarm(0);
Packit 5f9837
				nanosleep(&waittime, NULL);
Packit 5f9837
			} else {
Packit 5f9837
				(void) unlink(linktargetfile);
Packit 5f9837
				return EX_FILEIO;
Packit 5f9837
			}
Packit 5f9837
			close(lockfile_fd);
Packit 5f9837
		}
Packit 5f9837
	}
Packit 5f9837
	return 0;
Packit 5f9837
}
Packit 5f9837
Packit 5f9837
/*
Packit 5f9837
 * Call fflush and fsync on the mtab, and then endmntent. If either fflush
Packit 5f9837
 * or fsync fails, then truncate the file back to "size". endmntent is called
Packit 5f9837
 * unconditionally, and the errno (if any) from fflush and fsync are returned.
Packit 5f9837
 */
Packit 5f9837
int
Packit 5f9837
my_endmntent(FILE *stream, off_t size)
Packit 5f9837
{
Packit 5f9837
	int rc, fd;
Packit 5f9837
Packit 5f9837
	fd = fileno(stream);
Packit 5f9837
	if (fd < 0)
Packit 5f9837
		return -EBADF;
Packit 5f9837
Packit 5f9837
	rc = fflush(stream);
Packit 5f9837
	if (!rc)
Packit 5f9837
		rc = fsync(fd);
Packit 5f9837
Packit 5f9837
	/* truncate file back to "size" -- best effort here */
Packit 5f9837
	if (rc) {
Packit 5f9837
		int ignore __attribute__((unused));
Packit 5f9837
Packit 5f9837
		rc = errno;
Packit 5f9837
		ignore = ftruncate(fd, size);
Packit 5f9837
	}
Packit 5f9837
Packit 5f9837
	endmntent(stream);
Packit 5f9837
	return rc;
Packit 5f9837
}