| /* 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 |
| */ |
| |
| #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 */ |
| #include "get_label_uuid.h" |
| #include "nls.h" |
| |
| #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) { |
| 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; |
| error(_("warning: error reading %s: %s"), |
| fnam, strerror (errsv)); |
| 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) { |
| int errsv = errno; |
| error(_("warning: can't open %s: %s"), |
| _PATH_FSTAB, strerror (errsv)); |
| 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, *mc0; |
| |
| mc0 = mtab_head(); |
| for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt) |
| if (streq (mc->m.mnt_dir, name) || |
| streq (mc->m.mnt_fsname, name)) |
| return mc; |
| return NULL; |
| } |
| |
| /* |
| * Given the name NAME, and the place MCPREV we found it last time, |
| * try to find more occurrences. |
| */ |
| struct mntentchn * |
| getmntfilesbackward (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) || |
| streq (mc->m.mnt_fsname, name)) |
| return mc; |
| return NULL; |
| } |
| |
| /* Given the name FILE, try to find the option "loop=FILE" in mtab. */ |
| struct mntentchn * |
| getmntoptfile (const char *file) { |
| struct mntentchn *mc, *mc0; |
| char *opts, *s; |
| int l; |
| |
| if (!file) |
| return NULL; |
| |
| l = strlen(file); |
| |
| mc0 = mtab_head(); |
| for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt) |
| if ((opts = mc->m.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; |
| } |
| |
| static int |
| has_label(const char *device, const char *label) { |
| char devuuid[16]; |
| char *devlabel; |
| |
| return !get_label_uuid(device, &devlabel, devuuid) && |
| !strcmp(label, devlabel); |
| } |
| |
| static int |
| has_uuid(const char *device, const char *uuid){ |
| char devuuid[16]; |
| char *devlabel; |
| |
| return !get_label_uuid(device, &devlabel, devuuid) && |
| !memcmp(uuid, devuuid, sizeof(devuuid)); |
| } |
| |
| /* Find the entry (SPEC,FILE) in fstab */ |
| struct mntentchn * |
| getfsspecfile (const char *spec, const char *file) { |
| struct mntentchn *mc, *mc0; |
| |
| mc0 = fstab_head(); |
| |
| /* first attempt: names occur precisely as given */ |
| for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt) |
| if (streq (mc->m.mnt_dir, file) && |
| streq (mc->m.mnt_fsname, spec)) |
| return mc; |
| |
| /* second attempt: names found after symlink resolution */ |
| for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt) |
| if ((streq (mc->m.mnt_dir, file) || |
| streq (canonicalize(mc->m.mnt_dir), file)) |
| && (streq (mc->m.mnt_fsname, spec) || |
| streq (canonicalize(mc->m.mnt_fsname), spec))) |
| return mc; |
| |
| /* third attempt: names found after LABEL= or UUID= resolution */ |
| for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt) { |
| if (!strncmp (mc->m.mnt_fsname, "LABEL=", 6) && |
| (streq (mc->m.mnt_dir, file) || |
| streq (canonicalize(mc->m.mnt_dir), file))) { |
| if (has_label(spec, mc->m.mnt_fsname+6)) |
| return mc; |
| } |
| if (!strncmp (mc->m.mnt_fsname, "UUID=", 5) && |
| (streq (mc->m.mnt_dir, file) || |
| streq (canonicalize(mc->m.mnt_dir), file))) { |
| if (has_uuid(spec, mc->m.mnt_fsname+5)) |
| 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; |
| } |
| |
| /* Find the uuid UUID in fstab. */ |
| struct mntentchn * |
| getfsuuidspec (const char *uuid) { |
| struct mntentchn *mc, *mc0; |
| |
| mc0 = fstab_head(); |
| for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt) |
| if (strncmp (mc->m.mnt_fsname, "UUID=", 5) == 0 |
| && streq(mc->m.mnt_fsname + 5, uuid)) |
| return mc; |
| return NULL; |
| } |
| |
| /* Find the label LABEL in fstab. */ |
| struct mntentchn * |
| getfsvolspec (const char *label) { |
| struct mntentchn *mc, *mc0; |
| |
| mc0 = fstab_head(); |
| for (mc = mc0->nxt; mc && mc != mc0; mc = mc->nxt) |
| if (strncmp (mc->m.mnt_fsname, "LABEL=", 6) == 0 |
| && streq(mc->m.mnt_fsname + 6, label)) |
| return mc; |
| return NULL; |
| } |
| |
| /* Updating mtab ----------------------------------------------*/ |
| |
| /* Flag for already existing lock file. */ |
| static int we_created_lockfile = 0; |
| |
| /* 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. */ |
| 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 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. */ |
| #define MOUNTLOCK_LINKTARGET MOUNTED_LOCK "%d" |
| |
| void |
| lock_mtab (void) { |
| int tries = 3; |
| char *linktargetfile; |
| |
| 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; |
| } |
| |
| /* somewhat clumsy, but some ancient systems do not have snprintf() */ |
| /* use 20 as upper bound for the length of %d output */ |
| linktargetfile = xmalloc(strlen(MOUNTLOCK_LINKTARGET) + 20); |
| sprintf(linktargetfile, MOUNTLOCK_LINKTARGET, getpid ()); |
| |
| /* Repeat until it was us who made the link */ |
| while (!we_created_lockfile) { |
| struct flock flock; |
| int errsv, fd, i, j; |
| |
| 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); |
| |
| j = link(linktargetfile, MOUNTED_LOCK); |
| errsv = errno; |
| |
| (void) unlink(linktargetfile); |
| |
| if (j < 0 && errsv != EEXIST) { |
| die (EX_FILEIO, _("can't link lock file %s: %s " |
| "(use -n flag to override)"), |
| MOUNTED_LOCK, strerror (errsv)); |
| } |
| |
| fd = open (MOUNTED_LOCK, O_WRONLY); |
| |
| if (fd < 0) { |
| int errsv = errno; |
| /* Strange... Maybe the file was just deleted? */ |
| if (errno == ENOENT && tries-- > 0) |
| continue; |
| 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 (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 */ |
| } |
| we_created_lockfile = 1; |
| } else { |
| static int tries = 0; |
| |
| /* Someone else made the link. Wait. */ |
| alarm(LOCK_TIMEOUT); |
| if (fcntl (fd, F_SETLKW, &flock) == -1) { |
| int errsv = errno; |
| 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~ */ |
| if (tries++ > 3) { |
| if (tries > 5) |
| die (EX_FILEIO, _("Cannot create link %s\n" |
| "Perhaps there is a stale lock file?\n"), |
| MOUNTED_LOCK); |
| sleep(1); |
| } |
| } |
| |
| close(fd); |
| } |
| } |
| |
| /* Remove lock file. */ |
| void |
| unlock_mtab (void) { |
| if (we_created_lockfile) { |
| unlink (MOUNTED_LOCK); |
| we_created_lockfile = 0; |
| } |
| } |
| |
| /* |
| * 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; |
| |
| if (mtab_does_not_exist() || mtab_is_a_symlink()) |
| return; |
| |
| lock_mtab(); |
| |
| /* having locked mtab, read it again */ |
| mc0 = mc = &mtabhead; |
| mc->nxt = mc->prev = NULL; |
| |
| mfp = my_setmntent(fnam, "r"); |
| if (mfp == NULL || mfp->mntent_fp == NULL) { |
| int errsv = errno; |
| 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; |
| } |
| } else { |
| /* A remount */ |
| mc->m.mnt_opts = instead->mnt_opts; |
| } |
| } else if (instead) { |
| /* not found, add a new entry */ |
| 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 = my_setmntent (MOUNTED_TEMP, "w"); |
| if (mftmp == NULL || mftmp->mntent_fp == NULL) { |
| int errsv = errno; |
| 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 (my_addmntent(mftmp, &(mc->m)) == 1) { |
| int errsv = errno; |
| die (EX_FILEIO, _("error writing %s: %s"), |
| MOUNTED_TEMP, strerror (errsv)); |
| } |
| } |
| |
| 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)); |
| } |
| my_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(); |
| } |