busybox/util-linux/mount.c
Rob Landley eaa34ca5b4 More fixes. Type "auto" should mean unspecified from fstab as well as the
command line, initialize singlemount's rc to an error value so it doesn't
think it succeeded when it didn't, use absolute path when associating a
loop device (and the previous FEATURE_CLEAN_UP logic related to that was
freeing the wrong thing), move reading of /proc/filesystems to where we can
re-read it (when it's empty) for every entry on a "mount -a" so that when
/proc is mounted as the first entry, the later filesystems can autodetect
filesystem type.
2006-03-18 02:58:11 +00:00

555 lines
13 KiB
C

/* vi: set sw=4 ts=4: */
/*
* Mini mount implementation for busybox
*
* Copyright (C) 1995, 1996 by Bruce Perens <bruce@pixar.com>.
* Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
* Copyright (C) 2005-2006 by Rob Landley <rob@landley.net>
*
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
*/
/* todo:
* bb_getopt_ulflags();
*/
/* Design notes: There is no spec for this. Remind me to write one.
mount_main() calls singlemount() which calls mount_it_now().
mount_main() can loop through /etc/fstab for mount -a
singlemount() can loop through /etc/filesystems for fstype detection.
mount_it_now() does the actual mount.
*/
#include <limits.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <mntent.h>
#include <ctype.h>
#include <sys/mount.h>
#include <fcntl.h> // for CONFIG_FEATURE_MOUNT_LOOP
#include <sys/ioctl.h> // for CONFIG_FEATURE_MOUNT_LOOP
#include "busybox.h"
// These two aren't always defined in old headers
#ifndef MS_BIND
#define MS_BIND 4096
#endif
#ifndef MS_MOVE
#define MS_MOVE 8192
#endif
#ifndef MS_SILENT
#define MS_SILENT 32768
#endif
// Not real flags, but we want to be able to check for this.
#define MOUNT_NOAUTO (1<<29)
#define MOUNT_SWAP (1<<30)
/* Standard mount options (from -o options or --options), with corresponding
* flags */
struct {
const char *name;
long flags;
} static const mount_options[] = {
// NOP flags.
{"loop", 0},
{"defaults", 0},
{"quiet", 0},
// vfs flags
{"ro", MS_RDONLY},
{"rw", ~MS_RDONLY},
{"nosuid", MS_NOSUID},
{"suid", ~MS_NOSUID},
{"dev", ~MS_NODEV},
{"nodev", MS_NODEV},
{"exec", ~MS_NOEXEC},
{"noexec", MS_NOEXEC},
{"sync", MS_SYNCHRONOUS},
{"async", ~MS_SYNCHRONOUS},
{"atime", ~MS_NOATIME},
{"noatime", MS_NOATIME},
{"diratime", ~MS_NODIRATIME},
{"nodiratime", MS_NODIRATIME},
{"loud", ~MS_SILENT},
// action flags
{"remount", MS_REMOUNT},
{"bind", MS_BIND},
{"move", MS_MOVE},
{"noauto",MOUNT_NOAUTO},
{"swap",MOUNT_SWAP}
};
/* Append mount options to string */
static void append_mount_options(char **oldopts, char *newopts)
{
if(*oldopts && **oldopts) {
char *temp=bb_xasprintf("%s,%s",*oldopts,newopts);
free(*oldopts);
*oldopts=temp;
} else {
if (ENABLE_FEATURE_CLEAN_UP) free(*oldopts);
*oldopts = bb_xstrdup(newopts);
}
}
/* Use the mount_options list to parse options into flags.
* Return list of unrecognized options in *strflags if strflags!=NULL */
static int parse_mount_options(char *options, char **unrecognized)
{
int flags = MS_SILENT;
// Loop through options
for (;;) {
int i;
char *comma = strchr(options, ',');
if (comma) *comma = 0;
// Find this option in mount_options
for (i = 0; i < (sizeof(mount_options) / sizeof(*mount_options)); i++) {
if (!strcasecmp(mount_options[i].name, options)) {
long fl = mount_options[i].flags;
if(fl < 0) flags &= fl;
else flags |= fl;
break;
}
}
// If unrecognized not NULL, append unrecognized mount options */
if (unrecognized
&& i == (sizeof(mount_options) / sizeof(*mount_options)))
{
// Add it to strflags, to pass on to kernel
i = *unrecognized ? strlen(*unrecognized) : 0;
*unrecognized = xrealloc(*unrecognized, i+strlen(options)+2);
// Comma separated if it's not the first one
if (i) (*unrecognized)[i++] = ',';
strcpy((*unrecognized)+i, options);
}
// Advance to next option, or finish
if(comma) {
*comma = ',';
options = ++comma;
} else break;
}
return flags;
}
// Return a list of all block device backed filesystems
static llist_t *get_block_backed_filesystems(void)
{
char *fs, *buf,
*filesystems[] = {"/etc/filesystems", "/proc/filesystems", 0};
llist_t *list = 0;
int i;
FILE *f;
for(i = 0; filesystems[i]; i++) {
if(!(f = fopen(filesystems[i], "r"))) continue;
for(fs = buf = 0; (fs = buf = bb_get_chomped_line_from_file(f));
free(buf))
{
if(!strncmp(buf,"nodev",5) && isspace(buf[5])) continue;
while(isspace(*fs)) fs++;
if(*fs=='#' || *fs=='*') continue;
if(!*fs) continue;
list=llist_add_to_end(list,bb_xstrdup(fs));
}
if (ENABLE_FEATURE_CLEAN_UP) fclose(f);
}
return list;
}
llist_t *fslist = 0;
void delete_block_backed_filesystems(void);
#if ENABLE_FEATURE_CLEAN_UP
static void delete_block_backed_filesystems(void)
{
llist_free(fslist);
}
#endif
#if ENABLE_FEATURE_MTAB_SUPPORT
static int useMtab;
#else
#define useMtab 0
#endif
// Perform actual mount of specific filesystem at specific location.
static int mount_it_now(struct mntent *mp, int vfsflags)
{
int rc;
char *filteropts = 0;
parse_mount_options(mp->mnt_opts, &filteropts);
// Mount, with fallback to read-only if necessary.
for(;;) {
rc = mount(mp->mnt_fsname, mp->mnt_dir, mp->mnt_type,
vfsflags, filteropts);
if(!rc || (vfsflags&MS_RDONLY) || (errno!=EACCES && errno!=EROFS))
break;
bb_error_msg("%s is write-protected, mounting read-only",
mp->mnt_fsname);
vfsflags |= MS_RDONLY;
}
free(filteropts);
// Abort entirely if permission denied.
if (rc && errno == EPERM)
bb_error_msg_and_die(bb_msg_perm_denied_are_you_root);
/* If the mount was successful, and we're maintaining an old-style
* mtab file by hand, add the new entry to it now. */
if(ENABLE_FEATURE_MTAB_SUPPORT && useMtab && !rc) {
FILE *mountTable = setmntent(bb_path_mtab_file, "a+");
int i;
if(!mountTable)
bb_error_msg("No %s\n",bb_path_mtab_file);
// Add vfs string flags
for(i=0; mount_options[i].flags != MS_REMOUNT; i++)
if (mount_options[i].flags > 0)
append_mount_options(&(mp->mnt_opts),
// Shut up about the darn const. It's not important. I don't care.
(char *)mount_options[i].name);
// Remove trailing / (if any) from directory we mounted on
i = strlen(mp->mnt_dir);
if(i>1 && mp->mnt_dir[i-1] == '/') mp->mnt_dir[i-1] = 0;
// Write and close.
if(!mp->mnt_type || !*mp->mnt_type) mp->mnt_type="--bind";
addmntent(mountTable, mp);
endmntent(mountTable);
if (ENABLE_FEATURE_CLEAN_UP)
if(strcmp(mp->mnt_type,"--bind")) mp->mnt_type = 0;
}
return rc;
}
// Mount one directory. Handles NFS, loopback, autobind, and filesystem type
// detection. Returns 0 for success, nonzero for failure.
static int singlemount(struct mntent *mp)
{
int rc = 1, vfsflags;
char *loopFile = 0;
llist_t *fl = 0;
struct stat st;
vfsflags = parse_mount_options(mp->mnt_opts, 0);
// Treat fstype "auto" as unspecified.
if (mp->mnt_type && !strcmp(mp->mnt_type,"auto")) mp->mnt_type = 0;
// Might this be an NFS filesystem?
if (ENABLE_FEATURE_MOUNT_NFS &&
(!mp->mnt_type || !strcmp(mp->mnt_type,"nfs")) &&
strchr(mp->mnt_fsname, ':') != NULL)
{
char *options=0;
parse_mount_options(mp->mnt_opts, &options);
if (nfsmount(mp->mnt_fsname, mp->mnt_dir, &vfsflags, &options, 1)) {
bb_perror_msg("nfsmount failed");
return 1;
}
// Strangely enough, nfsmount() doesn't actually mount() anything.
else return mount_it_now(mp, vfsflags);
}
// Look at the file. (Not found isn't a failure for remount.)
if (lstat(mp->mnt_fsname, &st));
if (!(vfsflags & (MS_REMOUNT | MS_BIND | MS_MOVE))) {
// Do we need to allocate a loopback device for it?
if (ENABLE_FEATURE_MOUNT_LOOP && S_ISREG(st.st_mode)) {
loopFile = bb_simplify_path(mp->mnt_fsname);
mp->mnt_fsname = 0;
switch(set_loop(&(mp->mnt_fsname), loopFile, 0)) {
case 0:
case 1:
break;
default:
bb_error_msg( errno == EPERM || errno == EACCES
? bb_msg_perm_denied_are_you_root
: "Couldn't setup loop device");
return errno;
}
// Autodetect bind mounts
} else if (S_ISDIR(st.st_mode) && !mp->mnt_type) vfsflags |= MS_BIND;
}
/* If we know the fstype (or don't need to), jump straight
* to the actual mount. */
if (mp->mnt_type || (vfsflags & (MS_REMOUNT | MS_BIND | MS_MOVE)))
rc = mount_it_now(mp, vfsflags);
// Loop through filesystem types until mount succeeds or we run out
else {
/* Initialize list of block backed filesystems. This has to be
* done here so that during "mount -a", mounts after /proc shows up
* can autodetect. */
if (!fslist) {
fslist = get_block_backed_filesystems();
if (ENABLE_FEATURE_CLEAN_UP && fslist)
atexit(delete_block_backed_filesystems);
}
for (fl = fslist; fl; fl = fl->link) {
mp->mnt_type = fl->data;
if (!(rc = mount_it_now(mp,vfsflags))) break;
mp->mnt_type = 0;
}
}
// Mount failed. Clean up
if (rc && loopFile) {
del_loop(mp->mnt_fsname);
if(ENABLE_FEATURE_CLEAN_UP) {
free(loopFile);
free(mp->mnt_fsname);
}
}
return rc;
}
// Parse options, if necessary parse fstab/mtab, and call singlemount for
// each directory to be mounted.
int mount_main(int argc, char **argv)
{
char *cmdopts = bb_xstrdup(""), *fstabname, *fstype=0, *storage_path=0;
FILE *fstab;
int i, opt, all = FALSE, rc = 1;
struct mntent mtpair[2], *mtcur = mtpair;
/* parse long options, like --bind and --move. Note that -o option
* and --option are synonymous. Yes, this means --remount,rw works. */
for (i = opt = 0; i < argc; i++) {
if (argv[i][0] == '-' && argv[i][1] == '-') {
append_mount_options(&cmdopts,argv[i]+2);
} else argv[opt++] = argv[i];
}
argc = opt;
// Parse remaining options
while ((opt = getopt(argc, argv, "o:t:rwavnf")) > 0) {
switch (opt) {
case 'o':
append_mount_options(&cmdopts, optarg);
break;
case 't':
fstype = optarg;
break;
case 'r':
append_mount_options(&cmdopts, "ro");
break;
case 'w':
append_mount_options(&cmdopts, "rw");
break;
case 'a':
all = TRUE;
break;
case 'n':
USE_FEATURE_MTAB_SUPPORT(useMtab = FALSE;)
break;
case 'f':
USE_FEATURE_MTAB_SUPPORT(fakeIt = FALSE;)
break;
case 'v':
break; // ignore -v
default:
bb_show_usage();
}
}
// Three or more non-option arguments? Die with a usage message.
if (optind-argc>2) bb_show_usage();
// If we have no arguments, show currently mounted filesystems
if (optind == argc) {
if (!all) {
FILE *mountTable = setmntent(bb_path_mtab_file, "r");
if(!mountTable) bb_error_msg_and_die("No %s",bb_path_mtab_file);
while (getmntent_r(mountTable,mtpair,bb_common_bufsiz1,
sizeof(bb_common_bufsiz1)))
{
// Don't show rootfs.
if (!strcmp(mtpair->mnt_fsname, "rootfs")) continue;
if (!fstype || !strcmp(mtpair->mnt_type, fstype))
printf("%s on %s type %s (%s)\n", mtpair->mnt_fsname,
mtpair->mnt_dir, mtpair->mnt_type,
mtpair->mnt_opts);
}
if (ENABLE_FEATURE_CLEAN_UP) endmntent(mountTable);
return EXIT_SUCCESS;
}
}
// When we have two arguments, the second is the directory and we can
// skip looking at fstab entirely. We can always abspath() the directory
// argument when we get it.
if (optind+2 == argc) {
mtpair->mnt_fsname = argv[optind];
mtpair->mnt_dir = argv[optind+1];
mtpair->mnt_type = fstype;
mtpair->mnt_opts = cmdopts;
rc = singlemount(mtpair);
goto clean_up;
}
// If we have at least one argument, it's the storage location
if (optind < argc) storage_path = bb_simplify_path(argv[optind]);
// Open either fstab or mtab
if (parse_mount_options(cmdopts,0) & MS_REMOUNT)
fstabname = (char *)bb_path_mtab_file; // Again with the evil const.
else fstabname="/etc/fstab";
if (!(fstab=setmntent(fstabname,"r")))
bb_perror_msg_and_die("Cannot read %s",fstabname);
// Loop through entries until we find what we're looking for.
memset(mtpair,0,sizeof(mtpair));
for (;;) {
struct mntent *mtnext = mtpair + (mtcur==mtpair ? 1 : 0);
// Get next fstab entry
if (!getmntent_r(fstab, mtcur, bb_common_bufsiz1,
sizeof(bb_common_bufsiz1)))
{
// Were we looking for something specific?
if (optind != argc) {
// If we didn't find anything, complain.
if (!mtnext->mnt_fsname)
bb_error_msg_and_die("Can't find %s in %s",
argv[optind], fstabname);
// Mount the last thing we found.
mtcur = mtnext;
mtcur->mnt_opts=bb_xstrdup(mtcur->mnt_opts);
append_mount_options(&(mtcur->mnt_opts),cmdopts);
rc = singlemount(mtcur);
free(mtcur->mnt_opts);
}
break;
}
/* If we're trying to mount something specific and this isn't it,
* skip it. Note we must match both the exact text in fstab (ala
* "proc") or a full path from root */
if (optind != argc) {
// Is this what we're looking for?
if(strcmp(argv[optind],mtcur->mnt_fsname) &&
strcmp(storage_path,mtcur->mnt_fsname) &&
strcmp(argv[optind],mtcur->mnt_dir) &&
strcmp(storage_path,mtcur->mnt_dir)) continue;
// Remember this entry. Something later may have overmounted
// it, and we want the _last_ match.
mtcur = mtnext;
// If we're mounting all.
} else {
// Do we need to match a filesystem type?
if (fstype && strcmp(mtcur->mnt_type,fstype)) continue;
// Skip noauto and swap anyway.
if (parse_mount_options(mtcur->mnt_opts,0)
& (MOUNT_NOAUTO | MOUNT_SWAP)) continue;
// Mount this thing.
rc = singlemount(mtcur);
if (rc) {
// Don't whine about already mounted fs when mounting all.
if (errno == EBUSY) rc = 0;
else break;
}
}
}
if (ENABLE_FEATURE_CLEAN_UP) endmntent(fstab);
clean_up:
if (ENABLE_FEATURE_CLEAN_UP) {
free(storage_path);
free(cmdopts);
free(fstype);
}
if(rc)
bb_perror_msg("Mounting %s on %s failed",
mtcur->mnt_fsname, mtcur->mnt_dir);
return rc;
}