diff options
Diffstat (limited to '')
-rw-r--r-- | utils/mount/fstab.c | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/utils/mount/fstab.c b/utils/mount/fstab.c new file mode 100644 index 0000000..146d8f4 --- /dev/null +++ b/utils/mount/fstab.c @@ -0,0 +1,653 @@ +/* 1999-02-22 Arkadiusz Miskiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * Sun Mar 21 1999 - Arnaldo Carvalho de Melo <acme@conectiva.com.br> + * - fixed strerr(errno) in gettext calls + * + * 2006-06-08 Amit Gud <agud@redhat.com> + * - Moved code to nfs-utils/support/nfs from util-linux/mount. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <sys/stat.h> +#include <mntent.h> + +#include "fstab.h" +#include "xcommon.h" +#include "nfs_mntent.h" +#include "nfs_paths.h" +#include "nls.h" + +#define LOCK_TIMEOUT 10 +#define streq(s, t) (strcmp ((s), (t)) == 0) +#define PROC_MOUNTS "/proc/mounts" + +extern char *progname; +extern int verbose; + +/* Information about mtab. ------------------------------------*/ +static int have_mtab_info = 0; +static int var_mtab_does_not_exist = 0; +static int var_mtab_is_a_symlink = 0; + +static void +get_mtab_info(void) { + struct stat mtab_stat; + + if (!have_mtab_info) { + if (lstat(MOUNTED, &mtab_stat)) + var_mtab_does_not_exist = 1; + else if (S_ISLNK(mtab_stat.st_mode)) + var_mtab_is_a_symlink = 1; + have_mtab_info = 1; + } +} + +void +reset_mtab_info(void) { + have_mtab_info = 0; +} + +int +mtab_does_not_exist(void) { + get_mtab_info(); + return var_mtab_does_not_exist; +} + +int +mtab_is_a_symlink(void) { + get_mtab_info(); + return var_mtab_is_a_symlink; +} + +int +mtab_is_writable() { + int fd; + + /* Should we write to /etc/mtab upon an update? + Probably not if it is a symlink to /proc/mounts, since that + would create a file /proc/mounts in case the proc filesystem + is not mounted. */ + if (mtab_is_a_symlink()) + return 0; + + fd = open(MOUNTED, O_RDWR | O_CREAT, 0644); + if (fd >= 0) { + close(fd); + return 1; + } else + return 0; +} + +/* Contents of mtab and fstab ---------------------------------*/ + +struct mntentchn mounttable; +static int got_mtab = 0; +struct mntentchn procmounts; +static int got_procmounts = 0; +struct mntentchn fstab; +static int got_fstab = 0; + +static void read_mounttable(void); +static void read_procmounts(void); +static void read_fstab(void); + +static struct mntentchn * +mtab_head(void) +{ + if (!got_mtab) + read_mounttable(); + return &mounttable; +} + +static struct mntentchn * +procmounts_head(void) +{ + if (!got_procmounts) + read_procmounts(); + return &procmounts; +} + +static struct mntentchn * +fstab_head(void) +{ + if (!got_fstab) + read_fstab(); + return &fstab; +} + +#if 0 +static void +my_free(const void *s) { + if (s) + free((void *) s); +} + +static void +discard_mntentchn(struct mntentchn *mc0) { + struct mntentchn *mc, *mc1; + + for (mc = mc0->nxt; mc && mc != mc0; mc = mc1) { + mc1 = mc->nxt; + my_free(mc->m.mnt_fsname); + my_free(mc->m.mnt_dir); + my_free(mc->m.mnt_type); + my_free(mc->m.mnt_opts); + free(mc); + } +} +#endif + +static void +read_mntentchn(mntFILE *mfp, const char *fnam, struct mntentchn *mc0) { + struct mntentchn *mc = mc0; + struct mntent *mnt; + + while ((mnt = nfs_getmntent(mfp)) != NULL) { + if (!streq(mnt->mnt_type, MNTTYPE_IGNORE)) { + mc->nxt = (struct mntentchn *) xmalloc(sizeof(*mc)); + mc->nxt->prev = mc; + mc = mc->nxt; + mc->m = *mnt; + mc->nxt = mc0; + } + } + mc0->prev = mc; + if (ferror(mfp->mntent_fp)) { + int errsv = errno; + nfs_error(_("warning: error reading %s: %s"), + fnam, strerror (errsv)); + mc0->nxt = mc0->prev = NULL; + } + nfs_endmntent(mfp); +} + +/* + * Read /etc/mtab. If that fails, try /proc/mounts. + * This produces a linked list. The list head mounttable is a dummy. + * Return 0 on success. + */ +static void +read_mounttable() { + mntFILE *mfp; + const char *fnam; + struct mntentchn *mc = &mounttable; + + got_mtab = 1; + mc->nxt = mc->prev = NULL; + + fnam = MOUNTED; + mfp = nfs_setmntent (fnam, "r"); + if (mfp == NULL || mfp->mntent_fp == NULL) { + int errsv = errno; + fnam = PROC_MOUNTS; + mfp = nfs_setmntent (fnam, "r"); + if (mfp == NULL || mfp->mntent_fp == NULL) { + nfs_error(_("warning: can't open %s: %s"), + MOUNTED, strerror (errsv)); + return; + } + if (verbose) + printf(_("%s: could not open %s; using %s instead\n"), + progname, MOUNTED, PROC_MOUNTS); + } + read_mntentchn(mfp, fnam, mc); +} + +/* + * Read /proc/mounts. + * This produces a linked list. The list head procmounts is a dummy. + * Return 0 on success. + */ +static void +read_procmounts() { + mntFILE *mfp; + const char *fnam; + struct mntentchn *mc = &procmounts; + + got_procmounts = 1; + mc->nxt = mc->prev = NULL; + + fnam = PROC_MOUNTS; + mfp = nfs_setmntent(fnam, "r"); + if (mfp == NULL || mfp->mntent_fp == NULL) { + nfs_error(_("warning: can't open %s: %s"), + PROC_MOUNTS, strerror (errno)); + return; + } + read_mntentchn(mfp, fnam, mc); +} + +static void +read_fstab() +{ + mntFILE *mfp = NULL; + const char *fnam; + struct mntentchn *mc = &fstab; + + got_fstab = 1; + mc->nxt = mc->prev = NULL; + + fnam = _PATH_FSTAB; + mfp = nfs_setmntent (fnam, "r"); + if (mfp == NULL || mfp->mntent_fp == NULL) { + int errsv = errno; + nfs_error(_("warning: can't open %s: %s"), + _PATH_FSTAB, strerror (errsv)); + return; + } + read_mntentchn(mfp, fnam, mc); +} + +/* + * Given the directory name NAME, and the place MCPREV we found it last time, + * try to find more occurrences. + */ +struct mntentchn * +getmntdirbackward (const char *name, struct mntentchn *mcprev) { + struct mntentchn *mc, *mc0; + + mc0 = mtab_head(); + if (!mcprev) + mcprev = mc0; + for (mc = mcprev->prev; mc && mc != mc0; mc = mc->prev) + if (streq(mc->m.mnt_dir, name)) + return mc; + return NULL; +} + +/* + * Given the directory name NAME, and the place MCPREV we found it last time, + * try to find more occurrences. + */ +struct mntentchn * +getprocmntdirbackward (const char *name, struct mntentchn *mcprev) { + struct mntentchn *mc, *mc0; + + mc0 = procmounts_head(); + if (!mcprev) + mcprev = mc0; + for (mc = mcprev->prev; mc && mc != mc0; mc = mc->prev) + if (streq(mc->m.mnt_dir, name)) + return mc; + return NULL; +} + +/* + * Given the device name NAME, and the place MCPREV we found it last time, + * try to find more occurrences. + */ +struct mntentchn * +getmntdevbackward (const char *name, struct mntentchn *mcprev) { + struct mntentchn *mc, *mc0; + + mc0 = mtab_head(); + if (!mcprev) + mcprev = mc0; + for (mc = mcprev->prev; mc && mc != mc0; mc = mc->prev) + if (streq(mc->m.mnt_fsname, name)) + return mc; + return NULL; +} + +/* Find the dir FILE in fstab. */ +struct mntentchn * +getfsfile (const char *file) +{ + struct mntentchn *mc, *mc0; + + mc0 = fstab_head(); + for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt) + if (streq(mc->m.mnt_dir, file)) + return mc; + return NULL; +} + +/* Find the device SPEC in fstab. */ +struct mntentchn * +getfsspec (const char *spec) +{ + struct mntentchn *mc, *mc0; + + mc0 = fstab_head(); + for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt) + if (streq(mc->m.mnt_fsname, spec)) + return mc; + return NULL; +} + +/* Updating mtab ----------------------------------------------*/ + +/* Flag for already existing lock file. */ +static int we_created_lockfile = 0; +static int lockfile_fd = -1; + +/* Flag to indicate that signals have been set up. */ +static int signals_have_been_setup = 0; + +/* Ensure that the lock is released if we are interrupted. */ +extern char *strsignal(int sig); /* not always in <string.h> */ + +static void +handler (int sig) { + die(EX_USER, "%s", strsignal(sig)); +} + +static void +setlkw_timeout (__attribute__((unused)) int sig) { + /* nothing, fcntl will fail anyway */ +} + +/* Remove lock file. */ +void +unlock_mtab (void) { + if (we_created_lockfile) { + close(lockfile_fd); + lockfile_fd = -1; + unlink (MOUNTED_LOCK); + we_created_lockfile = 0; + } +} + +/* Create the lock file. + The lock file will be removed if we catch a signal or when we exit. */ +/* 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. Use 20 as upper bound for the length of %d. */ +#define MOUNTLOCK_LINKTARGET MOUNTED_LOCK "%d" +#define MOUNTLOCK_LINKTARGET_LTH (sizeof(MOUNTED_LOCK)+20) + +void +lock_mtab (void) { + int tries = 100000, i; + char linktargetfile[MOUNTLOCK_LINKTARGET_LTH]; + + at_die = unlock_mtab; + + if (!signals_have_been_setup) { + int sig = 0; + struct sigaction sa; + + sa.sa_flags = 0; + sigfillset (&sa.sa_mask); + + while (sigismember (&sa.sa_mask, ++sig) != -1) { + switch(sig) { + case SIGCHLD: + case SIGKILL: + case SIGCONT: + case SIGSTOP: + /* The cannot be caught, or should not, + * so don't even try. + */ + continue; + case SIGALRM: + sa.sa_handler = setlkw_timeout; + break; + case SIGHUP: + case SIGINT: + case SIGQUIT: + case SIGWINCH: + case SIGTSTP: + case SIGTTIN: + case SIGTTOU: + case SIGPIPE: + case SIGXFSZ: + case SIGXCPU: + /* non-priv user can cause these to be + * generated, so ignore them. + */ + sa.sa_handler = SIG_IGN; + break; + default: + /* The rest should not be possible, so just + * print a message and unlock mtab. + */ + sa.sa_handler = handler; + } + sigaction (sig, &sa, (struct sigaction *) 0); + } + signals_have_been_setup = 1; + } + + sprintf(linktargetfile, MOUNTLOCK_LINKTARGET, getpid ()); + + i = open (linktargetfile, O_WRONLY|O_CREAT, 0); + if (i < 0) { + int errsv = errno; + /* linktargetfile does not exist (as a file) + and we cannot create it. Read-only filesystem? + Too many files open in the system? + Filesystem full? */ + die (EX_FILEIO, _("can't create lock file %s: %s " + "(use -n flag to override)"), + linktargetfile, strerror (errsv)); + } + close(i); + + /* Repeat until it was us who made the link */ + while (!we_created_lockfile) { + struct flock flock; + int j; + + j = link(linktargetfile, MOUNTED_LOCK); + + { + int errsv = errno; + + if (j == 0) + we_created_lockfile = 1; + + if (j < 0 && errsv != EEXIST) { + (void) unlink(linktargetfile); + die (EX_FILEIO, _("can't link lock file %s: %s " + "(use -n flag to override)"), + MOUNTED_LOCK, strerror (errsv)); + } + } + + lockfile_fd = open (MOUNTED_LOCK, O_WRONLY); + + if (lockfile_fd < 0) { + int errsv = errno; + /* Strange... Maybe the file was just deleted? */ + if (errno == ENOENT && tries-- > 0) { + if (tries % 200 == 0) + usleep(30); + continue; + } + (void) unlink(linktargetfile); + die (EX_FILEIO, _("can't open lock file %s: %s " + "(use -n flag to override)"), + MOUNTED_LOCK, strerror (errsv)); + } + + flock.l_type = F_WRLCK; + flock.l_whence = SEEK_SET; + flock.l_start = 0; + flock.l_len = 0; + + if (j == 0) { + /* We made the link. Now claim the lock. */ + if (fcntl (lockfile_fd, F_SETLK, &flock) == -1) { + if (verbose) { + int errsv = errno; + nfs_error(_("%s: Can't lock lock file " + "%s: %s"), progname, + MOUNTED_LOCK, + strerror (errsv)); + } + /* proceed anyway */ + } + (void) unlink(linktargetfile); + } else { + static int retries = 0; + + /* Someone else made the link. Wait. */ + alarm(LOCK_TIMEOUT); + if (fcntl (lockfile_fd, F_SETLKW, &flock) == -1) { + int errsv = errno; + (void) unlink(linktargetfile); + die (EX_FILEIO, _("can't lock lock file %s: %s"), + MOUNTED_LOCK, (errno == EINTR) ? + _("timed out") : strerror (errsv)); + } + alarm(0); + /* Limit the number of iterations - maybe there + still is some old /etc/mtab~ */ + ++retries; + if (retries % 200 == 0) + usleep(30); + if (retries > 100000) { + (void) unlink(linktargetfile); + close(lockfile_fd); + die (EX_FILEIO, _("Cannot create link %s\n" + "Perhaps there is a stale lock file?\n"), + MOUNTED_LOCK); + } + close(lockfile_fd); + } + } +} + +/* + * Update the mtab. + * Used by umount with null INSTEAD: remove the last DIR entry. + * Used by mount upon a remount: update option part, + * and complain if a wrong device or type was given. + * [Note that often a remount will be a rw remount of / + * where there was no entry before, and we'll have to believe + * the values given in INSTEAD.] + */ + +void +update_mtab (const char *dir, struct mntent *instead) +{ + mntFILE *mfp, *mftmp; + const char *fnam = MOUNTED; + struct mntentchn mtabhead; /* dummy */ + struct mntentchn *mc, *mc0, *absent = NULL; + + if (mtab_does_not_exist() || !mtab_is_writable()) + return; + + lock_mtab(); + + /* having locked mtab, read it again */ + mc0 = mc = &mtabhead; + mc->nxt = mc->prev = NULL; + + mfp = nfs_setmntent(fnam, "r"); + if (mfp == NULL || mfp->mntent_fp == NULL) { + int errsv = errno; + nfs_error (_("cannot open %s (%s) - mtab not updated"), + fnam, strerror (errsv)); + goto leave; + } + + read_mntentchn(mfp, fnam, mc); + + /* find last occurrence of dir */ + for (mc = mc0->prev; mc && mc != mc0; mc = mc->prev) + if (streq(mc->m.mnt_dir, dir)) + break; + if (mc && mc != mc0) { + if (instead == NULL) { + /* An umount - remove entry */ + if (mc && mc != mc0) { + mc->prev->nxt = mc->nxt; + mc->nxt->prev = mc->prev; + free(mc); + } + } else { + /* A remount */ + mc->m.mnt_opts = instead->mnt_opts; + } + } else if (instead) { + /* not found, add a new entry */ + absent = xmalloc(sizeof(*absent)); + absent->m = *instead; + absent->nxt = mc0; + absent->prev = mc0->prev; + mc0->prev = absent; + if (mc0->nxt == NULL) + mc0->nxt = absent; + } + + /* write chain to mtemp */ + mftmp = nfs_setmntent (MOUNTED_TEMP, "w"); + if (mftmp == NULL || mftmp->mntent_fp == NULL) { + int errsv = errno; + nfs_error (_("cannot open %s (%s) - mtab not updated"), + MOUNTED_TEMP, strerror (errsv)); + goto leave; + } + + for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt) { + if (nfs_addmntent(mftmp, &(mc->m)) == 1) { + int errsv = errno; + die (EX_FILEIO, _("error writing %s: %s"), + MOUNTED_TEMP, strerror (errsv)); + } + } + +#if 0 + /* the chain might have strings copied from 'instead', + * so we cannot safely free it. + * And there is no need anyway because we are going to exit + * shortly. So just don't call discard_mntentchn.... + */ + discard_mntentchn(mc0); +#endif + if (fchmod (fileno (mftmp->mntent_fp), + S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0) { + int errsv = errno; + nfs_error(_("%s: error changing mode of %s: %s"), + progname, MOUNTED_TEMP, strerror (errsv)); + } + nfs_endmntent (mftmp); + + { /* + * If mount is setuid and some non-root user mounts sth, + * then mtab.tmp might get the group of this user. Copy uid/gid + * from the present mtab before renaming. + */ + struct stat sbuf; + if (stat (MOUNTED, &sbuf) == 0) { + if (chown (MOUNTED_TEMP, sbuf.st_uid, sbuf.st_gid) < 0) { + nfs_error(_("%s: error changing owner of %s: %s"), + progname, MOUNTED_TEMP, strerror (errno)); + } + } + } + + /* rename mtemp to mtab */ + if (rename (MOUNTED_TEMP, MOUNTED) < 0) { + int errsv = errno; + nfs_error(_("%s: can't rename %s to %s: %s\n"), + progname, MOUNTED_TEMP, MOUNTED, + strerror(errsv)); + } + + leave: + unlock_mtab(); +} |