/* * mount(8) -- mount a filesystem * * Copyright (C) 2011 Red Hat, Inc. All rights reserved. * Written by Karel Zak <kzak@redhat.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it would be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <getopt.h> #include <unistd.h> #include <sys/types.h> #include <sys/mman.h> #include <sys/stat.h> #include <stdarg.h> #include <libmount.h> #include <ctype.h> #include "nls.h" #include "c.h" #include "env.h" #include "strutils.h" #include "closestream.h" #include "canonicalize.h" #define XALLOC_EXIT_CODE MNT_EX_SYSERR #include "xalloc.h" #define OPTUTILS_EXIT_CODE MNT_EX_USAGE #include "optutils.h" /*** TODO: DOCS: * * --options-mode={ignore,append,prepend,replace} MNT_OMODE_{IGNORE, ...} * --options-source={fstab,mtab,disable} MNT_OMODE_{FSTAB,MTAB,NOTAB} * --options-source-force MNT_OMODE_FORCE */ static int mk_exit_code(struct libmnt_context *cxt, int rc); static void __attribute__((__noreturn__)) exit_non_root(const char *option) { const uid_t ruid = getuid(); const uid_t euid = geteuid(); if (ruid == 0 && euid != 0) { /* user is root, but setuid to non-root */ if (option) errx(MNT_EX_USAGE, _("only root can use \"--%s\" option " "(effective UID is %u)"), option, euid); errx(MNT_EX_USAGE, _("only root can do that " "(effective UID is %u)"), euid); } if (option) errx(MNT_EX_USAGE, _("only root can use \"--%s\" option"), option); errx(MNT_EX_USAGE, _("only root can do that")); } static void __attribute__((__noreturn__)) print_version(void) { const char *ver = NULL; const char **features = NULL, **p; mnt_get_library_version(&ver); mnt_get_library_features(&features); printf(_("%s from %s (libmount %s"), program_invocation_short_name, PACKAGE_STRING, ver); p = features; while (p && *p) { fputs(p == features ? ": " : ", ", stdout); fputs(*p++, stdout); } fputs(")\n", stdout); exit(MNT_EX_SUCCESS); } static int table_parser_errcb(struct libmnt_table *tb __attribute__((__unused__)), const char *filename, int line) { if (filename) warnx(_("%s: parse error at line %d -- ignored"), filename, line); return 1; } /* * Replace control chars with '?' to be compatible with coreutils. For more * robust solution use findmnt(1) where we use \x?? hex encoding. */ static void safe_fputs(const char *data) { const char *p; for (p = data; p && *p; p++) { if (iscntrl((unsigned char) *p)) fputc('?', stdout); else fputc(*p, stdout); } } static void print_all(struct libmnt_context *cxt, char *pattern, int show_label) { struct libmnt_table *tb; struct libmnt_iter *itr = NULL; struct libmnt_fs *fs; struct libmnt_cache *cache = NULL; if (mnt_context_get_mtab(cxt, &tb)) err(MNT_EX_SYSERR, _("failed to read mtab")); itr = mnt_new_iter(MNT_ITER_FORWARD); if (!itr) err(MNT_EX_SYSERR, _("failed to initialize libmount iterator")); if (show_label) cache = mnt_new_cache(); while (mnt_table_next_fs(tb, itr, &fs) == 0) { const char *type = mnt_fs_get_fstype(fs); const char *src = mnt_fs_get_source(fs); const char *optstr = mnt_fs_get_options(fs); char *xsrc = NULL; if (type && pattern && !mnt_match_fstype(type, pattern)) continue; if (!mnt_fs_is_pseudofs(fs)) xsrc = mnt_pretty_path(src, cache); printf ("%s on ", xsrc ? xsrc : src); safe_fputs(mnt_fs_get_target(fs)); if (type) printf (" type %s", type); if (optstr) printf (" (%s)", optstr); if (show_label && src) { char *lb = mnt_cache_find_tag_value(cache, src, "LABEL"); if (lb) printf (" [%s]", lb); } fputc('\n', stdout); free(xsrc); } mnt_unref_cache(cache); mnt_free_iter(itr); } /* * mount -a [-F] */ static int mount_all(struct libmnt_context *cxt) { struct libmnt_iter *itr; struct libmnt_fs *fs; int mntrc, ignored, rc = MNT_EX_SUCCESS; int nsucc = 0, nerrs = 0; itr = mnt_new_iter(MNT_ITER_FORWARD); if (!itr) { warn(_("failed to initialize libmount iterator")); return MNT_EX_SYSERR; } while (mnt_context_next_mount(cxt, itr, &fs, &mntrc, &ignored) == 0) { const char *tgt = mnt_fs_get_target(fs); if (ignored) { if (mnt_context_is_verbose(cxt)) printf(ignored == 1 ? _("%-25s: ignored\n") : _("%-25s: already mounted\n"), tgt); } else if (mnt_context_is_fork(cxt)) { if (mnt_context_is_verbose(cxt)) printf("%-25s: mount successfully forked\n", tgt); } else { if (mk_exit_code(cxt, mntrc) == MNT_EX_SUCCESS) { nsucc++; /* Note that MNT_EX_SUCCESS return code does * not mean that FS has been really mounted * (e.g. nofail option) */ if (mnt_context_get_status(cxt) && mnt_context_is_verbose(cxt)) printf("%-25s: successfully mounted\n", tgt); } else nerrs++; } } if (mnt_context_is_parent(cxt)) { /* wait for mount --fork children */ int nchildren = 0; nerrs = 0, nsucc = 0; rc = mnt_context_wait_for_children(cxt, &nchildren, &nerrs); if (!rc && nchildren) nsucc = nchildren - nerrs; } if (nerrs == 0) rc = MNT_EX_SUCCESS; /* all success */ else if (nsucc == 0) rc = MNT_EX_FAIL; /* all failed */ else rc = MNT_EX_SOMEOK; /* some success, some failed */ mnt_free_iter(itr); return rc; } static void success_message(struct libmnt_context *cxt) { unsigned long mflags = 0; const char *tgt, *src, *pr = program_invocation_short_name; if (mnt_context_helper_executed(cxt) || mnt_context_get_status(cxt) != 1) return; mnt_context_get_mflags(cxt, &mflags); tgt = mnt_context_get_target(cxt); src = mnt_context_get_source(cxt); if (mflags & MS_MOVE) printf(_("%s: %s moved to %s.\n"), pr, src, tgt); else if (mflags & MS_BIND) printf(_("%s: %s bound on %s.\n"), pr, src, tgt); else if (mflags & MS_PROPAGATION) { if (src && strcmp(src, "none") != 0 && tgt) printf(_("%s: %s mounted on %s.\n"), pr, src, tgt); printf(_("%s: %s propagation flags changed.\n"), pr, tgt); } else printf(_("%s: %s mounted on %s.\n"), pr, src, tgt); } #if defined(HAVE_LIBSELINUX) && defined(HAVE_SECURITY_GET_INITIAL_CONTEXT) #include <selinux/selinux.h> #include <selinux/context.h> static void selinux_warning(struct libmnt_context *cxt, const char *tgt) { if (tgt && mnt_context_is_verbose(cxt) && is_selinux_enabled() > 0) { security_context_t raw = NULL, def = NULL; if (getfilecon(tgt, &raw) > 0 && security_get_initial_context("file", &def) == 0) { if (!selinux_file_context_cmp(raw, def)) printf(_( "mount: %s does not contain SELinux labels.\n" " You just mounted an file system that supports labels which does not\n" " contain labels, onto an SELinux box. It is likely that confined\n" " applications will generate AVC messages and not be allowed access to\n" " this file system. For more details see restorecon(8) and mount(8).\n"), tgt); } freecon(raw); freecon(def); } } #else # define selinux_warning(_x, _y) #endif /* * Returns exit status (MNT_EX_*) and/or prints error message. */ static int mk_exit_code(struct libmnt_context *cxt, int rc) { const char *tgt; char buf[BUFSIZ] = { 0 }; rc = mnt_context_get_excode(cxt, rc, buf, sizeof(buf)); tgt = mnt_context_get_target(cxt); if (*buf) { const char *spec = tgt; if (!spec) spec = mnt_context_get_source(cxt); if (!spec) spec = "???"; warnx(_("%s: %s."), spec, buf); } if (rc == MNT_EX_SUCCESS && mnt_context_get_status(cxt) == 1) { selinux_warning(cxt, tgt); } return rc; } static struct libmnt_table *append_fstab(struct libmnt_context *cxt, struct libmnt_table *fstab, const char *path) { if (!fstab) { fstab = mnt_new_table(); if (!fstab) err(MNT_EX_SYSERR, _("failed to initialize libmount table")); mnt_table_set_parser_errcb(fstab, table_parser_errcb); mnt_context_set_fstab(cxt, fstab); mnt_unref_table(fstab); /* reference is handled by @cxt now */ } if (mnt_table_parse_fstab(fstab, path)) errx(MNT_EX_USAGE,_("%s: failed to parse"), path); return fstab; } /* * Check source and target paths -- non-root user should not be able to * resolve paths which are unreadable for him. */ static void sanitize_paths(struct libmnt_context *cxt) { const char *p; struct libmnt_fs *fs = mnt_context_get_fs(cxt); if (!fs) return; p = mnt_fs_get_target(fs); if (p) { char *np = canonicalize_path_restricted(p); if (!np) err(MNT_EX_USAGE, "%s", p); mnt_fs_set_target(fs, np); free(np); } p = mnt_fs_get_srcpath(fs); if (p) { char *np = canonicalize_path_restricted(p); if (!np) err(MNT_EX_USAGE, "%s", p); mnt_fs_set_source(fs, np); free(np); } } static void append_option(struct libmnt_context *cxt, const char *opt) { if (opt && (*opt == '=' || *opt == '\'' || *opt == '\"' || isblank(*opt))) errx(MNT_EX_USAGE, _("unsupported option format: %s"), opt); if (mnt_context_append_options(cxt, opt)) err(MNT_EX_SYSERR, _("failed to append option '%s'"), opt); } static int has_remount_flag(struct libmnt_context *cxt) { unsigned long mflags = 0; if (mnt_context_get_mflags(cxt, &mflags)) return 0; return mflags & MS_REMOUNT; } static void __attribute__((__noreturn__)) usage(void) { FILE *out = stdout; fputs(USAGE_HEADER, out); fprintf(out, _( " %1$s [-lhV]\n" " %1$s -a [options]\n" " %1$s [options] [--source] <source> | [--target] <directory>\n" " %1$s [options] <source> <directory>\n" " %1$s <operation> <mountpoint> [<target>]\n"), program_invocation_short_name); fputs(USAGE_SEPARATOR, out); fputs(_("Mount a filesystem.\n"), out); fputs(USAGE_OPTIONS, out); fprintf(out, _( " -a, --all mount all filesystems mentioned in fstab\n" " -c, --no-canonicalize don't canonicalize paths\n" " -f, --fake dry run; skip the mount(2) syscall\n" " -F, --fork fork off for each device (use with -a)\n" " -T, --fstab <path> alternative file to /etc/fstab\n")); fprintf(out, _( " -i, --internal-only don't call the mount.<type> helpers\n")); fprintf(out, _( " -l, --show-labels show also filesystem labels\n")); fprintf(out, _( " -n, --no-mtab don't write to /etc/mtab\n")); fprintf(out, _( " -o, --options <list> comma-separated list of mount options\n" " -O, --test-opts <list> limit the set of filesystems (use with -a)\n" " -r, --read-only mount the filesystem read-only (same as -o ro)\n" " -t, --types <list> limit the set of filesystem types\n")); fprintf(out, _( " --source <src> explicitly specifies source (path, label, uuid)\n" " --target <target> explicitly specifies mountpoint\n")); fprintf(out, _( " -v, --verbose say what is being done\n")); fprintf(out, _( " -w, --rw, --read-write mount the filesystem read-write (default)\n")); fputs(USAGE_SEPARATOR, out); printf(USAGE_HELP_OPTIONS(25)); fprintf(out, _( "\nSource:\n" " -L, --label <label> synonym for LABEL=<label>\n" " -U, --uuid <uuid> synonym for UUID=<uuid>\n" " LABEL=<label> specifies device by filesystem label\n" " UUID=<uuid> specifies device by filesystem UUID\n" " PARTLABEL=<label> specifies device by partition label\n" " PARTUUID=<uuid> specifies device by partition UUID\n")); fprintf(out, _( " <device> specifies device by path\n" " <directory> mountpoint for bind mounts (see --bind/rbind)\n" " <file> regular file for loopdev setup\n")); fprintf(out, _( "\nOperations:\n" " -B, --bind mount a subtree somewhere else (same as -o bind)\n" " -M, --move move a subtree to some other place\n" " -R, --rbind mount a subtree and all submounts somewhere else\n")); fprintf(out, _( " --make-shared mark a subtree as shared\n" " --make-slave mark a subtree as slave\n" " --make-private mark a subtree as private\n" " --make-unbindable mark a subtree as unbindable\n")); fprintf(out, _( " --make-rshared recursively mark a whole subtree as shared\n" " --make-rslave recursively mark a whole subtree as slave\n" " --make-rprivate recursively mark a whole subtree as private\n" " --make-runbindable recursively mark a whole subtree as unbindable\n")); printf(USAGE_MAN_TAIL("mount(8)")); exit(MNT_EX_SUCCESS); } int main(int argc, char **argv) { int c, rc = MNT_EX_SUCCESS, all = 0, show_labels = 0; struct libmnt_context *cxt; struct libmnt_table *fstab = NULL; char *srcbuf = NULL; char *types = NULL; unsigned long oper = 0; int propa = 0; enum { MOUNT_OPT_SHARED = CHAR_MAX + 1, MOUNT_OPT_SLAVE, MOUNT_OPT_PRIVATE, MOUNT_OPT_UNBINDABLE, MOUNT_OPT_RSHARED, MOUNT_OPT_RSLAVE, MOUNT_OPT_RPRIVATE, MOUNT_OPT_RUNBINDABLE, MOUNT_OPT_TARGET, MOUNT_OPT_SOURCE }; static const struct option longopts[] = { { "all", no_argument, NULL, 'a' }, { "fake", no_argument, NULL, 'f' }, { "fstab", required_argument, NULL, 'T' }, { "fork", no_argument, NULL, 'F' }, { "help", no_argument, NULL, 'h' }, { "no-mtab", no_argument, NULL, 'n' }, { "read-only", no_argument, NULL, 'r' }, { "ro", no_argument, NULL, 'r' }, { "verbose", no_argument, NULL, 'v' }, { "version", no_argument, NULL, 'V' }, { "read-write", no_argument, NULL, 'w' }, { "rw", no_argument, NULL, 'w' }, { "options", required_argument, NULL, 'o' }, { "test-opts", required_argument, NULL, 'O' }, { "types", required_argument, NULL, 't' }, { "uuid", required_argument, NULL, 'U' }, { "label", required_argument, NULL, 'L' }, { "bind", no_argument, NULL, 'B' }, { "move", no_argument, NULL, 'M' }, { "rbind", no_argument, NULL, 'R' }, { "make-shared", no_argument, NULL, MOUNT_OPT_SHARED }, { "make-slave", no_argument, NULL, MOUNT_OPT_SLAVE }, { "make-private", no_argument, NULL, MOUNT_OPT_PRIVATE }, { "make-unbindable", no_argument, NULL, MOUNT_OPT_UNBINDABLE }, { "make-rshared", no_argument, NULL, MOUNT_OPT_RSHARED }, { "make-rslave", no_argument, NULL, MOUNT_OPT_RSLAVE }, { "make-rprivate", no_argument, NULL, MOUNT_OPT_RPRIVATE }, { "make-runbindable", no_argument, NULL, MOUNT_OPT_RUNBINDABLE }, { "no-canonicalize", no_argument, NULL, 'c' }, { "internal-only", no_argument, NULL, 'i' }, { "show-labels", no_argument, NULL, 'l' }, { "target", required_argument, NULL, MOUNT_OPT_TARGET }, { "source", required_argument, NULL, MOUNT_OPT_SOURCE }, { NULL, 0, NULL, 0 } }; static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ { 'B','M','R' }, /* bind,move,rbind */ { 'L','U', MOUNT_OPT_SOURCE }, /* label,uuid,source */ { 0 } }; int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; sanitize_env(); setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); atexit(close_stdout); strutils_set_exitcode(MNT_EX_USAGE); mnt_init_debug(0); cxt = mnt_new_context(); if (!cxt) err(MNT_EX_SYSERR, _("libmount context allocation failed")); mnt_context_set_tables_errcb(cxt, table_parser_errcb); while ((c = getopt_long(argc, argv, "aBcfFhilL:Mno:O:rRsU:vVwt:T:", longopts, NULL)) != -1) { /* only few options are allowed for non-root users */ if (mnt_context_is_restricted(cxt) && !strchr("hlLUVvrist", c) && c != MOUNT_OPT_TARGET && c != MOUNT_OPT_SOURCE) exit_non_root(option_to_longopt(c, longopts)); err_exclusive_options(c, longopts, excl, excl_st); switch(c) { case 'a': all = 1; break; case 'c': mnt_context_disable_canonicalize(cxt, TRUE); break; case 'f': mnt_context_enable_fake(cxt, TRUE); break; case 'F': mnt_context_enable_fork(cxt, TRUE); break; case 'h': usage(); break; case 'i': mnt_context_disable_helpers(cxt, TRUE); break; case 'n': mnt_context_disable_mtab(cxt, TRUE); break; case 'r': append_option(cxt, "ro"); mnt_context_enable_rwonly_mount(cxt, FALSE); break; case 'v': mnt_context_enable_verbose(cxt, TRUE); break; case 'V': print_version(); break; case 'w': append_option(cxt, "rw"); mnt_context_enable_rwonly_mount(cxt, TRUE); break; case 'o': append_option(cxt, optarg); break; case 'O': if (mnt_context_set_options_pattern(cxt, optarg)) err(MNT_EX_SYSERR, _("failed to set options pattern")); break; case 'L': xasprintf(&srcbuf, "LABEL=\"%s\"", optarg); mnt_context_disable_swapmatch(cxt, 1); mnt_context_set_source(cxt, srcbuf); free(srcbuf); break; case 'U': xasprintf(&srcbuf, "UUID=\"%s\"", optarg); mnt_context_disable_swapmatch(cxt, 1); mnt_context_set_source(cxt, srcbuf); free(srcbuf); break; case 'l': show_labels = 1; break; case 't': types = optarg; break; case 'T': fstab = append_fstab(cxt, fstab, optarg); break; case 's': mnt_context_enable_sloppy(cxt, TRUE); break; case 'B': oper |= MS_BIND; break; case 'M': oper |= MS_MOVE; break; case 'R': oper |= (MS_BIND | MS_REC); break; case MOUNT_OPT_SHARED: append_option(cxt, "shared"); propa = 1; break; case MOUNT_OPT_SLAVE: append_option(cxt, "slave"); propa = 1; break; case MOUNT_OPT_PRIVATE: append_option(cxt, "private"); propa = 1; break; case MOUNT_OPT_UNBINDABLE: append_option(cxt, "unbindable"); propa = 1; break; case MOUNT_OPT_RSHARED: append_option(cxt, "rshared"); propa = 1; break; case MOUNT_OPT_RSLAVE: append_option(cxt, "rslave"); propa = 1; break; case MOUNT_OPT_RPRIVATE: append_option(cxt, "rprivate"); propa = 1; break; case MOUNT_OPT_RUNBINDABLE: append_option(cxt, "runbindable"); propa = 1; break; case MOUNT_OPT_TARGET: mnt_context_disable_swapmatch(cxt, 1); mnt_context_set_target(cxt, optarg); break; case MOUNT_OPT_SOURCE: mnt_context_disable_swapmatch(cxt, 1); mnt_context_set_source(cxt, optarg); break; default: errtryhelp(MNT_EX_USAGE); } } argc -= optind; argv += optind; if (fstab && !mnt_context_is_nocanonicalize(cxt)) { /* * We have external (context independent) fstab instance, let's * make a connection between the fstab and the canonicalization * cache. */ mnt_table_set_cache(fstab, mnt_context_get_cache(cxt)); } if (!mnt_context_get_source(cxt) && !mnt_context_get_target(cxt) && !argc && !all) { if (oper || mnt_context_get_options(cxt)) { warnx(_("bad usage")); errtryhelp(MNT_EX_USAGE); } print_all(cxt, types, show_labels); goto done; } /* Non-root users are allowed to use -t to print_all(), but not to mount */ if (mnt_context_is_restricted(cxt) && types) exit_non_root("types"); if (oper && (types || all || mnt_context_get_source(cxt))) { warnx(_("bad usage")); errtryhelp(MNT_EX_USAGE); } if (types && (all || strchr(types, ',') || strncmp(types, "no", 2) == 0)) mnt_context_set_fstype_pattern(cxt, types); else if (types) mnt_context_set_fstype(cxt, types); if (all) { /* * A) Mount all */ rc = mount_all(cxt); goto done; } else if (argc == 0 && (mnt_context_get_source(cxt) || mnt_context_get_target(cxt))) { /* * B) mount -L|-U|--source|--target * * non-root may specify source *or* target, but not both */ if (mnt_context_is_restricted(cxt) && mnt_context_get_source(cxt) && mnt_context_get_target(cxt)) exit_non_root(NULL); } else if (argc == 1 && (!mnt_context_get_source(cxt) || !mnt_context_get_target(cxt))) { /* * C) mount [-L|-U|--source] <target> * mount [--target <dir>] <source> * mount <source|target> * * non-root may specify source *or* target, but not both * * It does not matter for libmount if we set source or target * here (the library is able to swap it), but it matters for * sanitize_paths(). */ int istag = mnt_tag_is_valid(argv[0]); if (istag && mnt_context_get_source(cxt)) /* -L, -U or --source together with LABEL= or UUID= */ errx(MNT_EX_USAGE, _("source specified more than once")); else if (istag || mnt_context_get_target(cxt)) mnt_context_set_source(cxt, argv[0]); else mnt_context_set_target(cxt, argv[0]); if (mnt_context_is_restricted(cxt) && mnt_context_get_source(cxt) && mnt_context_get_target(cxt)) exit_non_root(NULL); } else if (argc == 2 && !mnt_context_get_source(cxt) && !mnt_context_get_target(cxt)) { /* * D) mount <source> <target> */ if (mnt_context_is_restricted(cxt)) exit_non_root(NULL); mnt_context_set_source(cxt, argv[0]); mnt_context_set_target(cxt, argv[1]); } else { warnx(_("bad usage")); errtryhelp(MNT_EX_USAGE); } if (mnt_context_is_restricted(cxt)) sanitize_paths(cxt); if (oper) /* BIND/MOVE operations, let's set the mount flags */ mnt_context_set_mflags(cxt, oper); if ((oper && !has_remount_flag(cxt)) || propa) /* For --make-* or --bind is fstab/mtab unnecessary */ mnt_context_set_optsmode(cxt, MNT_OMODE_NOTAB); rc = mnt_context_mount(cxt); rc = mk_exit_code(cxt, rc); if (rc == MNT_EX_SUCCESS && mnt_context_is_verbose(cxt)) success_message(cxt); done: mnt_free_context(cxt); return rc; }