Blob Blame History Raw
/* ----------------------------------------------------------------------- *
 * 
 *  spawn.c - run programs synchronously with output redirected to syslog
 *   
 *   Copyright 1997 Transmeta Corporation - All Rights Reserved
 *   Copyright 2005 Ian Kent <raven@themaw.net>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, Inc., 675 Mass Ave, Cambridge MA 02139,
 *   USA; either version 2 of the License, or (at your option) any later
 *   version; incorporated herein by reference.
 *
 * ----------------------------------------------------------------------- */

#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <grp.h>
#include <time.h>
#include <poll.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/mount.h>

#include "automount.h"

static pthread_mutex_t spawn_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t open_mutex = PTHREAD_MUTEX_INITIALIZER;

#define SPAWN_OPT_NONE		0x0000
#define SPAWN_OPT_LOCK		0x0001
#define SPAWN_OPT_OPEN		0x0002

#define MTAB_LOCK_RETRIES	3

void dump_core(void)
{
	sigset_t segv;

	sigemptyset(&segv);
	sigaddset(&segv, SIGSEGV);
	pthread_sigmask(SIG_UNBLOCK, &segv, NULL);
	sigprocmask(SIG_UNBLOCK, &segv, NULL);

	raise(SIGSEGV);
}

void open_mutex_lock(void)
{
	int _o_lock = pthread_mutex_lock(&open_mutex);
	if (_o_lock)
		fatal(_o_lock);
}

void open_mutex_unlock(void)
{
	int _o_unlock = pthread_mutex_unlock(&open_mutex);
	if (_o_unlock)
		fatal(_o_unlock);
}

/*
 * Use CLOEXEC flag for open(), pipe(), fopen() (read-only case) and
 * socket() if possible.
 */
static int cloexec_works = 0;

static void check_cloexec(int fd)
{
	if (cloexec_works == 0) {
		int fl = fcntl(fd, F_GETFD);
		if (fl != -1)
			cloexec_works = (fl & FD_CLOEXEC) ? 1 : -1;
	}
	if (cloexec_works > 0)
		return;
	fcntl(fd, F_SETFD, FD_CLOEXEC);
	return;
}

int open_fd(const char *path, int flags)
{
	int fd;

	open_mutex_lock();
#if defined(O_CLOEXEC) && defined(SOCK_CLOEXEC)
	if (cloexec_works != -1)
		flags |= O_CLOEXEC;
#endif
	fd = open(path, flags);
	if (fd == -1) {
		open_mutex_unlock();
		return -1;
	}
	check_cloexec(fd);
	open_mutex_unlock();
	return fd;
}

int open_fd_mode(const char *path, int flags, int mode)
{
	int fd;

	open_mutex_lock();
#if defined(O_CLOEXEC) && defined(SOCK_CLOEXEC)
	if (cloexec_works != -1)
		flags |= O_CLOEXEC;
#endif
	fd = open(path, flags, mode);
	if (fd == -1) {
		open_mutex_unlock();
		return -1;
	}
	check_cloexec(fd);
	open_mutex_unlock();
	return fd;
}

int open_pipe(int pipefd[2])
{
	int ret;

	open_mutex_lock();
#if defined(O_CLOEXEC) && defined(SOCK_CLOEXEC) && defined(HAVE_PIPE2)
	if (cloexec_works != -1) {
		ret = pipe2(pipefd, O_CLOEXEC);
		if (ret != -1)
			goto done;
		if (errno != EINVAL)
			goto err;
	}
#endif
	ret = pipe(pipefd);
	if (ret == -1)
		goto err;
	check_cloexec(pipefd[0]);
	check_cloexec(pipefd[1]);
done:
	open_mutex_unlock();
	return 0;
err:
	open_mutex_unlock();
	return -1;
}

int open_sock(int domain, int type, int protocol)
{
	int fd;

	open_mutex_lock();
#ifdef SOCK_CLOEXEC
	if (cloexec_works != -1)
		type |= SOCK_CLOEXEC;
#endif
	fd = socket(domain, type, protocol);
	if (fd == -1) {
		open_mutex_unlock();
		return -1;
	}
	check_cloexec(fd);
	open_mutex_unlock();
	return fd;
}

FILE *open_fopen_r(const char *path)
{
	FILE *f;

	open_mutex_lock();
#if defined(O_CLOEXEC) && defined(SOCK_CLOEXEC)
	if (cloexec_works != -1) {
		f = fopen(path, "re");
		if (f != NULL) {
			check_cloexec(fileno(f));
			open_mutex_unlock();
			return f;
		}
	}
#endif
	f = fopen(path, "r");
	if (f == NULL) {
		open_mutex_unlock();
		return NULL;
	}
	check_cloexec(fileno(f));
	open_mutex_unlock();
	return f;
}

FILE *open_setmntent_r(const char *table)
{
	FILE *tab;

	open_mutex_lock();
#if defined(O_CLOEXEC) && defined(SOCK_CLOEXEC)
	if (cloexec_works != -1) {
		tab = setmntent(table, "re");
		if (tab != NULL) {
			check_cloexec(fileno(tab));
			open_mutex_unlock();
			return tab;
		}
	}
#endif
	tab = fopen(table, "r");
	if (tab == NULL) {
		open_mutex_unlock();
		return NULL;
	}
	check_cloexec(fileno(tab));
	open_mutex_unlock();
	return tab;
}

/*
 * Used by subprocesses which exec to avoid carrying over the main
 * daemon's signalling environment
 */
void reset_signals(void)
{
	struct sigaction sa;
	sigset_t allsignals;
	int i;

	sigfillset(&allsignals);
	sigprocmask(SIG_BLOCK, &allsignals, NULL);

	/* Discard all pending signals */
	sa.sa_handler = SIG_IGN;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;

	for (i = 1; i < NSIG; i++)
		if (i != SIGKILL && i != SIGSTOP)
			sigaction(i, &sa, NULL);

	sa.sa_handler = SIG_DFL;

	for (i = 1; i < NSIG; i++)
		if (i != SIGKILL && i != SIGSTOP)
			sigaction(i, &sa, NULL);

	/* Ignore the user signals that may be sent so that we
	 *  don't terminate execed program by mistake */
	sa.sa_handler = SIG_IGN;
	sa.sa_flags = SA_RESTART;
	sigaction(SIGUSR1, &sa, NULL);
	sigaction(SIGUSR2, &sa, NULL);

	sigprocmask(SIG_UNBLOCK, &allsignals, NULL);
}

#define ERRBUFSIZ 2047		/* Max length of error string excl \0 */

static int timed_read(int pipe, char *buf, size_t len, int time)
{
	struct pollfd pfd[1];
	int timeout = time;
	int ret;

	pfd[0].fd = pipe;
	pfd[0].events = POLLIN;

	if (time != -1) {
		if (time >= (INT_MAX - 1)/1000)
			timeout = INT_MAX - 1;
		else
			timeout = time * 1000;
	}

	ret = poll(pfd, 1, timeout);
	if (ret <= 0) {
		if (ret == 0)
			ret = -ETIMEDOUT;
		return ret;
	}

	if (pfd[0].fd == -1)
		return 0;

	if ((pfd[0].revents & (POLLIN|POLLHUP)) == POLLHUP)
		return 0;

	while ((ret = read(pipe, buf, len)) == -1 && errno == EINTR);

	return ret;
}

static int do_spawn(unsigned logopt, unsigned int wait,
		    unsigned int options, const char *prog,
		    const char *const *argv)
{
	pid_t f;
	int ret, status, pipefd[2];
	char errbuf[ERRBUFSIZ + 1], *p, *sp;
	int errp, errn;
	int cancel_state;
	unsigned int use_lock = options & SPAWN_OPT_LOCK;
	unsigned int use_open = options & SPAWN_OPT_OPEN;
	sigset_t allsigs, tmpsig, oldsig;
	struct thread_stdenv_vars *tsv;
	pid_t euid = 0;
	gid_t egid = 0;

	if (open_pipe(pipefd))
		return -1;

	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state);

	sigfillset(&allsigs);
	pthread_sigmask(SIG_BLOCK, &allsigs, &oldsig);

	if (use_lock) {
		status = pthread_mutex_lock(&spawn_mutex);
		if (status)
			fatal(status);
	}

	tsv = pthread_getspecific(key_thread_stdenv_vars);
	if (tsv) {
		euid = tsv->uid;
		egid = tsv->gid;
	}

	open_mutex_lock();
	f = fork();
	if (f == 0) {
		char **pargv = (char **) argv;
		int loc = 0;

		reset_signals();
		close(pipefd[0]);
		dup2(pipefd[1], STDOUT_FILENO);
		dup2(pipefd[1], STDERR_FILENO);
		close(pipefd[1]);
		open_mutex_unlock();

		/* what to mount must always be second last */
		while (*pargv++)
			loc++;
		if (loc <= 3)
			goto done;
		loc -= 2;

		/*
		 * If the mount location starts with a "/" then it is
		 * a local path. In this case it is a bind mount, a
		 * loopback mount or a file system that uses a local
		 * path so we need to check for dependent mounts.
		 *
		 * I hope host names are never allowed "/" as first char
		 */
		if (use_open && *(argv[loc]) == '/') {
			char **p;
			int is_bind, fd;

			pid_t pgrp = getpgrp();

			/*
			 * Pretend to be requesting user and set non-autofs
			 * program group to trigger mount
			 */
			if (euid) {
				if (!tsv->user)
					fprintf(stderr,
						"warning: can't init groups\n");
				else if (initgroups(tsv->user, egid) == -1)
					fprintf(stderr,
						"warning: initgroups: %s\n",
						strerror(errno));

				if (setegid(egid) == -1)
					fprintf(stderr,
						"warning: setegid: %s\n",
						strerror(errno));
				if (seteuid(euid) == -1)
					fprintf(stderr,
						"warning: seteuid: %s\n",
						strerror(errno));
			}
			setpgrp();

			/*
			 * Trigger the recursive mount.
			 *
			 * Ignore the open(2) return code as there may be
			 * multiple waiters for this mount and we need to
			 * let the VFS handle returns to each individual
			 * waiter.
			 */
			fd = open(argv[loc], O_DIRECTORY);
			if (fd != -1)
				close(fd);

			if (seteuid(0) == -1)
				fprintf(stderr,
					"warning: seteuid: %s\n",
					strerror(errno));
			if (setegid(0) == -1)
				fprintf(stderr,
					"warning: setegid: %s\n",
					strerror(errno));
			if (pgrp >= 0)
				setpgid(0, pgrp);

			/*
			 * The kernel leaves mount type autofs alone because
			 * they are supposed to be autofs sub-mounts and they
			 * look after their own expiration. So mounts bound
			 * to an autofs submount won't ever be expired.
			 */
			is_bind = 0;
			p = (char **) argv;
			while (*p) {
				if (strcmp(*p, "--bind")) {
					p++;
					continue;
				}
				is_bind = 1;
				break;
			}
			if (!is_bind)
				goto done;

			if (is_mounted(_PROC_MOUNTS, argv[loc], MNTS_AUTOFS)) {
				fprintf(stderr,
				     "error: can't bind to an autofs mount\n");
				close(STDOUT_FILENO);
				close(STDERR_FILENO);
				 _exit(EINVAL);
			}
		}
done:
		execv(prog, (char *const *) argv);
		_exit(255);	/* execv() failed */
	} else {
		tmpsig = oldsig;

		sigaddset(&tmpsig, SIGCHLD);
		pthread_sigmask(SIG_SETMASK, &tmpsig, NULL);
		open_mutex_unlock();

		close(pipefd[1]);

		if (f < 0) {
			close(pipefd[0]);
			if (use_lock) {
				status = pthread_mutex_unlock(&spawn_mutex);
				if (status)
					fatal(status);
			}
			pthread_sigmask(SIG_SETMASK, &oldsig, NULL);
			pthread_setcancelstate(cancel_state, NULL);
			return -1;
		}

		errp = 0;
		do {
			errn = timed_read(pipefd[0],
					  errbuf + errp, ERRBUFSIZ - errp, wait);
			if (errn > 0) {
				errp += errn;

				sp = errbuf;
				while (errp && (p = memchr(sp, '\n', errp))) {
					*p++ = '\0';
					if (sp[0])	/* Don't output empty lines */
						warn(logopt, ">> %s", sp);
					errp -= (p - sp);
					sp = p;
				}

				if (errp && sp != errbuf)
					memmove(errbuf, sp, errp);

				if (errp >= ERRBUFSIZ) {
					/* Line too long, split */
					errbuf[errp] = '\0';
					warn(logopt, ">> %s", errbuf);
					errp = 0;
				}
			}
		} while (errn > 0);

		if (errn == -ETIMEDOUT)
			kill(f, SIGTERM);

		close(pipefd[0]);

		if (errp > 0) {
			/* End of file without \n */
			errbuf[errp] = '\0';
			warn(logopt, ">> %s", errbuf);
		}

		if (waitpid(f, &ret, 0) != f)
			ret = -1;	/* waitpid() failed */

		if (use_lock) {
			status = pthread_mutex_unlock(&spawn_mutex);
			if (status)
				fatal(status);
		}
		pthread_sigmask(SIG_SETMASK, &oldsig, NULL);
		pthread_setcancelstate(cancel_state, NULL);

		return ret;
	}
}

int spawnv(unsigned logopt, const char *prog, const char *const *argv)
{
	return do_spawn(logopt, -1, SPAWN_OPT_NONE, prog, argv);
}

int spawnl(unsigned logopt, const char *prog, ...)
{
	va_list arg;
	int argc, ret;
	char **argv, **p;
	unsigned int argv_len;

	va_start(arg, prog);
	for (argc = 1; va_arg(arg, char *); argc++);
	va_end(arg);

	argv_len = sizeof(char *) * (argc + 1);
	argv = malloc(argv_len);
	if (!argv) {
		char buf[MAX_ERR_BUF];
		char *estr = strerror_r(errno, buf, sizeof(buf));
		crit(logopt, "malloc: %s", estr);
		return -1;
	}
	memset(argv, 0, argv_len);

	va_start(arg, prog);
	p = argv;
	while ((*p++ = va_arg(arg, char *)));
	va_end(arg);

	ret = do_spawn(logopt, -1, SPAWN_OPT_NONE, prog, (const char **) argv);
	free(argv);

	return ret;
}

int spawn_mount(unsigned logopt, ...)
{
	va_list arg;
	int argc;
	char **argv, **p;
	char prog[] = PATH_MOUNT;
	char arg0[] = PATH_MOUNT;
	char argn[] = "-n";
	char argvr[] = "-v";
	/* In case we need to use the fake option to mount */
	char arg_fake[] = "-f";
	unsigned int options;
	unsigned int retries = MTAB_LOCK_RETRIES;
	int update_mtab = 1, ret, printed = 0;
	unsigned int wait = defaults_get_mount_wait();
	int verbose = defaults_get_mount_verbose();
	char buf[PATH_MAX + 1];
	unsigned int argv_len;

	/* If we use mount locking we can't validate the location */
#ifdef ENABLE_MOUNT_LOCKING
	options = SPAWN_OPT_LOCK;
#else
	options = SPAWN_OPT_OPEN;
#endif

	va_start(arg, logopt);
	for (argc = 1; va_arg(arg, char *); argc++);
	va_end(arg);

	ret = readlink(_PATH_MOUNTED, buf, PATH_MAX);
	if (ret != -1) {
		buf[ret] = '\0';
		if (!strcmp(buf, _PROC_MOUNTS) ||
		    !strcmp(buf, _PROC_SELF_MOUNTS)) {
			debug(logopt,
			      "mtab link detected, passing -n to mount");
			argc++;
			update_mtab = 0;
		}
	}

	/* Alloc 2 extra slots in case we need to use the "-f" or "-v" options
	 * plus the NULL slot for end of args.
	 */
	argv_len = sizeof(char *) * (argc + 3);
	argv = malloc(argv_len);
	if (!argv) {
		char buf[MAX_ERR_BUF];
		char *estr = strerror_r(errno, buf, sizeof(buf));
		crit(logopt, "malloc: %s", estr);
		return -1;
	}
	memset(argv, 0, argv_len);

	argv[0] = arg0;

	va_start(arg, logopt);
	p = argv + 1;
	if (!update_mtab)
		*(p++) = argn;
	if (verbose)
		*(p++) = argvr;

	while ((*p = va_arg(arg, char *))) {
		if (options == SPAWN_OPT_OPEN && !strcmp(*p, "-t")) {
			*(++p) = va_arg(arg, char *);
			if (!*p)
				break;
			/*
			 * A cifs mount location begins with a "/" but
			 * is not a local path, so don't try to resolve
			 * it. Mmmm ... does anyone use smbfs these days?
			 */
			if (strstr(*p, "cifs"))
				options = SPAWN_OPT_NONE;
		}
		p++;
	}
	va_end(arg);

	while (retries--) {
		ret = do_spawn(logopt, wait, options, prog, (const char **) argv);
		if (ret == MTAB_NOTUPDATED) {
			struct timespec tm = {3, 0};

			/*
			 * If the mount succeeded but the mtab was not
			 * updated, then retry the mount with the -f (fake)
			 * option to just update the mtab.
			 */
			if (!printed) {
				debug(logopt, "mount failed with error code 16"
				      ", retrying with the -f option");
				printed = 1;
			}

			/*
			 * Move the last two args so do_spawn() can find the
			 * mount target.
			 */
			if (!argv[argc]) {
				argv[argc + 1] = NULL;
				argv[argc] = argv[argc - 1];
				argv[argc - 1] = argv[argc - 2];
				argv[argc - 2] = arg_fake;
			}

			nanosleep(&tm, NULL);

			continue;
		}
		break;
	}

	/* This is not a fatal error */
	if (ret == MTAB_NOTUPDATED) {
		/*
		 * Version 5 requires that /etc/mtab be in sync with
		 * /proc/mounts. If we're unable to update matb after
		 * retrying then we have no choice but umount the mount
		 * and return a fail.
		 */
		warn(logopt,
		     "Unable to update the mtab file, forcing mount fail!");
		umount(argv[argc]);
		ret = MNT_FORCE_FAIL;
	}
	free(argv);

	return ret;
}

/*
 * For bind mounts that depend on the target being mounted (possibly
 * itself an automount) we attempt to mount the target using an open(2)
 * call. For this to work the location must be the second last arg.
 *
 * NOTE: If mount locking is enabled this type of recursive mount cannot
 *	 work.
 */
int spawn_bind_mount(unsigned logopt, ...)
{
	va_list arg;
	int argc;
	char **argv, **p;
	char prog[] = PATH_MOUNT;
	char arg0[] = PATH_MOUNT;
	char bind[] = "--bind";
	char argn[] = "-n";
	/* In case we need to use the fake option to mount */
	char arg_fake[] = "-f";
	unsigned int options;
	unsigned int retries = MTAB_LOCK_RETRIES;
	int update_mtab = 1, ret, printed = 0;
	unsigned int wait = defaults_get_mount_wait();
	char buf[PATH_MAX + 1];
	unsigned int argv_len;

	/* If we use mount locking we can't validate the location */
#ifdef ENABLE_MOUNT_LOCKING
	options = SPAWN_OPT_LOCK;
#else
	options = SPAWN_OPT_OPEN;
#endif

	/*
	 * Alloc 2 extra slots, one for the bind option and one in case
	 * we need to use the "-f" option
	 */
	va_start(arg, logopt);
	for (argc = 2; va_arg(arg, char *); argc++);
	va_end(arg);

	ret = readlink(_PATH_MOUNTED, buf, PATH_MAX);
	if (ret != -1) {
		buf[ret] = '\0';
		if (!strcmp(buf, _PROC_MOUNTS) ||
		    !strcmp(buf, _PROC_SELF_MOUNTS)) {
			debug(logopt,
			      "mtab link detected, passing -n to mount");
			argc++;
			update_mtab = 0;
		}
	}

	argv_len = sizeof(char *) * (argc + 2);
	argv = malloc(argv_len);
	if (!argv) {
		char buf[MAX_ERR_BUF];
		char *estr = strerror_r(errno, buf, sizeof(buf));
		crit(logopt, "malloc: %s", estr);
		return -1;
	}
	memset(argv, 0, argv_len);

	argv[0] = arg0;
	argv[1] = bind;

	va_start(arg, logopt);
	if (update_mtab)
		p = argv + 2;
	else {
		argv[2] = argn;
		p = argv + 3;
	}
	while ((*p++ = va_arg(arg, char *)));
	va_end(arg);

	while (retries--) {
		ret = do_spawn(logopt, wait, options, prog, (const char **) argv);
		if (ret == MTAB_NOTUPDATED) {
			struct timespec tm = {3, 0};

			/*
			 * If the mount succeeded but the mtab was not
			 * updated, then retry the mount with the -f (fake)
			 * option to just update the mtab.
			 */
			if (!printed) {
				debug(logopt, "mount failed with error code 16"
				      ", retrying with the -f option");
				printed = 1;
			}

			/*
			 * Move the last two args so do_spawn() can find the
			 * mount target.
			 */
			if (!argv[argc]) {
				argv[argc + 1] = NULL;
				argv[argc] = argv[argc - 1];
				argv[argc - 1] = argv[argc - 2];
				argv[argc - 2] = arg_fake;
			}

			nanosleep(&tm, NULL);

			continue;
		}
		break;
	}

	/* This is not a fatal error */
	if (ret == MTAB_NOTUPDATED) {
		/*
		 * Version 5 requires that /etc/mtab be in sync with
		 * /proc/mounts. If we're unable to update matb after
		 * retrying then we have no choice but umount the mount
		 * and return a fail.
		 */
		warn(logopt,
		     "Unable to update the mtab file, forcing mount fail!");
		umount(argv[argc]);
		ret = MNT_FORCE_FAIL;
	}
	free(argv);

	return ret;
}

int spawn_umount(unsigned logopt, ...)
{
	va_list arg;
	int argc;
	char **argv, **p;
	char prog[] = PATH_UMOUNT;
	char arg0[] = PATH_UMOUNT;
#ifdef HAVE_NO_CANON_UMOUNT
	char * const arg_c = "-c";
#else
	char * const arg_c = NULL;
#endif
	char argn[] = "-n";
	unsigned int options;
	unsigned int retries = MTAB_LOCK_RETRIES;
	int update_mtab = 1, ret, printed = 0;
	unsigned int wait = defaults_get_umount_wait();
	char buf[PATH_MAX + 1];
	unsigned int argv_len;

#ifdef ENABLE_MOUNT_LOCKING
	options = SPAWN_OPT_LOCK;
#else
	options = SPAWN_OPT_NONE;
#endif

	va_start(arg, logopt);
	for (argc = 1; va_arg(arg, char *); argc++);
	va_end(arg);

	ret = readlink(_PATH_MOUNTED, buf, PATH_MAX);
	if (ret != -1) {
		buf[ret] = '\0';
		if (!strcmp(buf, _PROC_MOUNTS) ||
		    !strcmp(buf, _PROC_SELF_MOUNTS)) {
			debug(logopt,
			      "mtab link detected, passing -n to mount");
			argc++;
			update_mtab = 0;
		}
	}
	if (arg_c)
		argc++;;

	argv_len = sizeof(char *) * (argc + 1);
	argv = malloc(argv_len);
	if (!argv) {
		char buf[MAX_ERR_BUF];
		char *estr = strerror_r(errno, buf, sizeof(buf));
		crit(logopt, "malloc: %s", estr);
		return -1;
	}
	memset(argv, 0, argv_len);

	p = argv;
	*p++ = arg0;
	if (arg_c)
		*p++ = arg_c;

	if (!update_mtab)
		*p++ = argn;

	va_start(arg, logopt);
	while ((*p++ = va_arg(arg, char *)));
	va_end(arg);

	while (retries--) {
		ret = do_spawn(logopt, wait, options, prog, (const char **) argv);
		if (ret == MTAB_NOTUPDATED) {
			/*
			 * If the mount succeeded but the mtab was not
			 * updated, then retry the umount just to update
			 * the mtab.
			 */
			if (!printed) {
				debug(logopt, "umount failed with error code 16"
				      ", retrying with the -f option");
				printed = 1;
			}
		} else {
			/*
			 * umount does not support the "fake" option.  Thus,
			 * if we got a return value of MTAB_NOTUPDATED the
			 * first time, that means the umount actually
			 * succeeded.  Then, a following umount will fail
			 * due to the fact that nothing was mounted on the
			 * mount point. So, report this as success.
			 */
			if (retries < MTAB_LOCK_RETRIES - 1)
				ret = 0;
			break;
		}
	}

	/* This is not a fatal error */
	if (ret == MTAB_NOTUPDATED) {
		warn(logopt, "Unable to update the mtab file, /proc/mounts "
		     "and /etc/mtab will differ");
		ret = 0;
	}
	free(argv);

	return ret;
}