| /* 1999-02-22 Arkadiusz Mi¶kiewicz <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. |
| */ |
| |
| #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 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; |
| } |
| } |
| |
| int |
| mtab_does_not_exist(void) { |
| get_mtab_info(); |
| return var_mtab_does_not_exist; |
| } |
| |
| static 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 fstab; |
| static int got_fstab = 0; |
| |
| static void read_mounttable(void); |
| static void read_fstab(void); |
| |
| static struct mntentchn * |
| mtab_head(void) { |
| if (!got_mtab) |
| read_mounttable(); |
| return &mounttable; |
| } |
| |
| 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 (_("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 = 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 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 (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_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); |
| } |
| 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 errsv, j; |
| |
| j = link(linktargetfile, MOUNTED_LOCK); |
| 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; |
| printf(_("Can't lock lock file %s: %s\n"), |
| MOUNTED_LOCK, strerror (errsv)); |
| } |
| /* proceed anyway */ |
| } |
| (void) unlink(linktargetfile); |
| } else { |
| static int tries = 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~ */ |
| ++tries; |
| if (tries % 200 == 0) |
| usleep(30); |
| if (tries > 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; |
| fprintf(stderr, _("error changing mode of %s: %s\n"), |
| 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) |
| chown (MOUNTED_TEMP, sbuf.st_uid, sbuf.st_gid); |
| } |
| |
| /* rename mtemp to mtab */ |
| if (rename (MOUNTED_TEMP, MOUNTED) < 0) { |
| int errsv = errno; |
| fprintf(stderr, _("can't rename %s to %s: %s\n"), |
| MOUNTED_TEMP, MOUNTED, strerror(errsv)); |
| } |
| |
| leave: |
| unlock_mtab(); |
| } |