#include <unistd.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <sys/stat.h> #include "mntent.h" #include "fstab.h" #include "sundries.h" /* for xmalloc() etc */ #define streq(s, t) (strcmp ((s), (t)) == 0) #define PROC_MOUNTS "/proc/mounts" /* 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; } } 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() { static int ret = -1; /* 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; if (ret == -1) { int fd = open(MOUNTED, O_RDWR | O_CREAT, 0644); if (fd >= 0) { close(fd); ret = 1; } else ret = 0; } return ret; } /* Contents of mtab and fstab ---------------------------------*/ struct mntentchn mounttable, fstab; static int got_mtab = 0; static int got_fstab = 0; static void read_mounttable(void), read_fstab(void); struct mntentchn * mtab_head() { if (!got_mtab) read_mounttable(); return &mounttable; } struct mntentchn * fstab_head() { if (!got_fstab) read_fstab(); return &fstab; } static void read_mntentchn(mntFILE *mfp, const char *fnam, struct mntentchn *mc0) { struct mntentchn *mc = mc0; struct mntent *mnt; while ((mnt = my_getmntent (mfp)) != NULL && !streq (mnt->mnt_type, MNTTYPE_IGNORE)) { mc->nxt = (struct mntentchn *) xmalloc(sizeof(*mc)); mc->nxt->prev = mc; mc = mc->nxt; mc->mnt_fsname = mnt->mnt_fsname; mc->mnt_dir = mnt->mnt_dir; mc->mnt_type = mnt->mnt_type; mc->mnt_opts = mnt->mnt_opts; mc->nxt = NULL; } mc0->prev = mc; if (ferror (mfp->mntent_fp)) { error("warning: error reading %s: %s", fnam, strerror (errno)); mc0->nxt = mc0->prev = NULL; } my_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 = my_setmntent (fnam, "r"); if (mfp == NULL || mfp->mntent_fp == NULL) { int errsv = errno; fnam = PROC_MOUNTS; mfp = my_setmntent (fnam, "r"); if (mfp == NULL || mfp->mntent_fp == NULL) { error("warning: can't open %s: %s", MOUNTED, strerror (errsv)); return; } if (verbose) printf ("mount: could not open %s - using %s instead\n", MOUNTED, PROC_MOUNTS); } 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 = my_setmntent (fnam, "r"); if (mfp == NULL || mfp->mntent_fp == NULL) { error("warning: can't open %s: %s", _PATH_FSTAB, strerror (errno)); return; } read_mntentchn(mfp, fnam, mc); } /* Given the name NAME, try to find it in mtab. */ struct mntentchn * getmntfile (const char *name) { struct mntentchn *mc; for (mc = mtab_head()->nxt; mc; mc = mc->nxt) if (streq (mc->mnt_dir, name) || (streq (mc->mnt_fsname, name))) break; return mc; } /* Given the name FILE, try to find the option "loop=FILE" in mtab. */ struct mntentchn * getmntoptfile (const char *file) { struct mntentchn *mc; char *opts, *s; int l; if (!file) return NULL; l = strlen(file); for (mc = mtab_head()->nxt; mc; mc = mc->nxt) if ((opts = mc->mnt_opts) != NULL && (s = strstr(opts, "loop=")) && !strncmp(s+5, file, l) && (s == opts || s[-1] == ',') && (s[l+5] == 0 || s[l+5] == ',')) return mc; return NULL; } /* Find the dir FILE in fstab. */ struct mntentchn * getfsfile (const char *file) { struct mntentchn *mc; for (mc = fstab_head()->nxt; mc; mc = mc->nxt) if (streq (mc->mnt_dir, file)) break; return mc; } /* Find the device SPEC in fstab. */ struct mntentchn * getfsspec (const char *spec) { struct mntentchn *mc; for (mc = fstab_head()->nxt; mc; mc = mc->nxt) if (streq (mc->mnt_fsname, spec)) break; return mc; } /* Updating mtab ----------------------------------------------*/ /* File descriptor for lock. Value tested in unlock_mtab() to remove race. */ static int lock = -1; /* Flag for already existing lock file. */ static int old_lockfile = 1; /* Ensure that the lock is released if we are interrupted. */ static void handler (int sig) { die (EX_USER, "%s", sys_siglist[sig]); } static void setlkw_timeout (int sig) { /* nothing, fcntl will fail anyway */ } /* Create the lock file. The lock file will be removed if we catch a signal or when we exit. The value of lock is tested to remove the race. */ void lock_mtab (void) { int sig = 0; struct sigaction sa; struct flock flock; /* If this is the first time, ensure that the lock will be removed. */ if (lock < 0) { struct stat st; sa.sa_handler = handler; sa.sa_flags = 0; sigfillset (&sa.sa_mask); while (sigismember (&sa.sa_mask, ++sig) != -1 && sig != SIGCHLD) { if (sig == SIGALRM) sa.sa_handler = setlkw_timeout; else sa.sa_handler = handler; sigaction (sig, &sa, (struct sigaction *) 0); } /* This stat is performed so we know when not to be overly eager when cleaning up after signals. The window between stat and open is not significant. */ if (lstat (MOUNTED_LOCK, &st) < 0 && errno == ENOENT) old_lockfile = 0; lock = open (MOUNTED_LOCK, O_WRONLY|O_CREAT, 0); if (lock < 0) { die (EX_FILEIO, "can't create lock file %s: %s " "(use -n flag to override)", MOUNTED_LOCK, strerror (errno)); } flock.l_type = F_WRLCK; flock.l_whence = SEEK_SET; flock.l_start = 0; flock.l_len = 0; alarm(LOCK_TIMEOUT); if (fcntl (lock, F_SETLKW, &flock) == -1) { int errnosv = errno; close (lock); lock = -1; /* The file should not be removed */ die (EX_FILEIO, "can't lock lock file %s: %s", MOUNTED_LOCK, errnosv == EINTR ? "timed out" : strerror (errno)); } /* We have now access to the lock, and it can always be removed */ old_lockfile = 0; } } /* Remove lock file. */ void unlock_mtab (void) { if (lock != -1) { close (lock); if (!old_lockfile) unlink (MOUNTED_LOCK); } } /* * Update the mtab. * Used by umount with null INSTEAD: remove any DIR entries. * 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) { struct mntent *mnt; struct mntent *next; struct mntent remnt; int added = 0; mntFILE *mfp, *mftmp; if (mtab_does_not_exist() || mtab_is_a_symlink()) return; lock_mtab(); mfp = my_setmntent(MOUNTED, "r"); if (mfp == NULL || mfp->mntent_fp == NULL) { error ("cannot open %s (%s) - mtab not updated", MOUNTED, strerror (errno)); goto leave; } mftmp = my_setmntent (MOUNTED_TEMP, "w"); if (mftmp == NULL || mfp->mntent_fp == NULL) { error ("can't open %s (%s) - mtab not updated", MOUNTED_TEMP, strerror (errno)); goto leave; } while ((mnt = my_getmntent (mfp))) { if (streq (mnt->mnt_dir, dir)) { added++; if (instead) { /* a remount */ remnt = *instead; next = &remnt; remnt.mnt_fsname = mnt->mnt_fsname; remnt.mnt_type = mnt->mnt_type; if (instead->mnt_fsname && !streq(mnt->mnt_fsname, instead->mnt_fsname)) printf("mount: warning: cannot change " "mounted device with a remount\n"); else if (instead->mnt_type && !streq(instead->mnt_type, "unknown") && !streq(mnt->mnt_type, instead->mnt_type)) printf("mount: warning: cannot change " "filesystem type with a remount\n"); } else next = NULL; } else next = mnt; if (next && my_addmntent(mftmp, next) == 1) die (EX_FILEIO, "error writing %s: %s", MOUNTED_TEMP, strerror (errno)); } if (instead && !added && my_addmntent(mftmp, instead) == 1) die (EX_FILEIO, "error writing %s: %s", MOUNTED_TEMP, strerror (errno)); my_endmntent (mfp); if (fchmod (fileno (mftmp->mntent_fp), S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0) fprintf(stderr, "error changing mode of %s: %s\n", MOUNTED_TEMP, strerror (errno)); my_endmntent (mftmp); if (rename (MOUNTED_TEMP, MOUNTED) < 0) fprintf(stderr, "can't rename %s to %s: %s\n", MOUNTED_TEMP, MOUNTED, strerror(errno)); leave: unlock_mtab(); }