Fix parse_dataset error handling
[zfs.git] / cmd / mount_zfs / mount_zfs.c
index fbb954a..4db33ed 100644 (file)
@@ -30,6 +30,7 @@
 #include <sys/mount.h>
 #include <sys/stat.h>
 #include <libzfs.h>
+#include <locale.h>
 #ifdef HAVE_LIBSELINUX
 #include <selinux/selinux.h>
 #endif /* HAVE_LIBSELINUX */
@@ -72,13 +73,11 @@ static const option_map_t option_map[] = {
 #ifdef MS_STRICTATIME
        { MNTOPT_DFRATIME,      MS_STRICTATIME, ZS_COMMENT      },
 #endif
-#ifdef HAVE_SELINUX
        { MNTOPT_CONTEXT,       MS_COMMENT,     ZS_NOCONTEXT    },
        { MNTOPT_NOCONTEXT,     MS_COMMENT,     ZS_NOCONTEXT    },
        { MNTOPT_FSCONTEXT,     MS_COMMENT,     ZS_NOCONTEXT    },
        { MNTOPT_DEFCONTEXT,    MS_COMMENT,     ZS_NOCONTEXT    },
        { MNTOPT_ROOTCONTEXT,   MS_COMMENT,     ZS_NOCONTEXT    },
-#endif
 #ifdef MS_I_VERSION
        { MNTOPT_IVERSION,      MS_I_VERSION,   ZS_COMMENT      },
 #endif
@@ -98,6 +97,7 @@ static const option_map_t option_map[] = {
        { MNTOPT_QUIET,         MS_SILENT,      ZS_COMMENT      },
 #endif
        /* Custom zfs options */
+       { MNTOPT_XATTR,         MS_COMMENT,     ZS_COMMENT      },
        { MNTOPT_NOXATTR,       MS_COMMENT,     ZS_COMMENT      },
        { MNTOPT_ZFSUTIL,       MS_COMMENT,     ZS_ZFSUTIL      },
        { NULL,                 0,              0               } };
@@ -122,6 +122,7 @@ parse_option(char *mntopt, unsigned long *mntflags,
                if (*ptr == '=') {
                        *ptr = '\0';
                        value = ptr+1;
+                       VERIFY3P(value, !=, NULL);
                        break;
                }
        }
@@ -130,15 +131,6 @@ parse_option(char *mntopt, unsigned long *mntflags,
                if (strncmp(name, opt->name, strlen(name)) == 0) {
                        *mntflags |= opt->mntmask;
                        *zfsflags |= opt->zfsmask;
-
-                       /* MS_USERS implies default user options */
-                       if (opt->mntmask & (MS_USERS))
-                               *mntflags |= (MS_NOEXEC|MS_NOSUID|MS_NODEV);
-
-                       /* MS_OWNER|MS_GROUP imply default owner options */
-                       if (opt->mntmask & (MS_OWNER | MS_GROUP))
-                               *mntflags |= (MS_NOSUID|MS_NODEV);
-
                        error = 0;
                        goto out;
                }
@@ -158,10 +150,10 @@ out:
  * otherwise they are considered fatal are copied in to badopt.
  */
 static int
-parse_options(char *mntopts, unsigned long *mntflags,
-    unsigned long *zfsflags, int sloppy, char *badopt)
+parse_options(char *mntopts, unsigned long *mntflags, unsigned long *zfsflags,
+    int sloppy, char *badopt, char *mtabopt)
 {
-       int error = 0, quote = 0, flag = 0;
+       int error = 0, quote = 0, flag = 0, count = 0;
        char *ptr, *opt, *opts;
 
        opts = strdup(mntopts);
@@ -197,6 +189,16 @@ parse_options(char *mntopts, unsigned long *mntflags,
                        if (error) {
                                strcpy(badopt, opt);
                                goto out;
+
+                       }
+
+                       if (!(*mntflags & MS_REMOUNT) &&
+                           !(*zfsflags & ZS_ZFSUTIL)) {
+                               if (count > 0)
+                                       strlcat(mtabopt, ",", MNT_LINE_MAX);
+
+                               strlcat(mtabopt, opt, MNT_LINE_MAX);
+                               count++;
                        }
 
                        opt = NULL;
@@ -209,18 +211,60 @@ out:
 }
 
 /*
- * If a file or directory in your current working directory is named
- * 'dataset' then mount(8) will prepend your current working directory
- * to dataset.  The is no way to prevent this behavior so we simply
- * check for it and strip the prepended patch when it is added.
+ * Return the pool/dataset to mount given the name passed to mount.  This
+ * is expected to be of the form pool/dataset, however may also refer to
+ * a block device if that device contains a valid zfs label.
  */
 static char *
 parse_dataset(char *dataset)
 {
        char cwd[PATH_MAX];
+       struct stat64 statbuf;
+       int error;
        int len;
 
-       (void) getcwd(cwd, PATH_MAX);
+       /*
+        * We expect a pool/dataset to be provided, however if we're
+        * given a device which is a member of a zpool we attempt to
+        * extract the pool name stored in the label.  Given the pool
+        * name we can mount the root dataset.
+        */
+       error = stat64(dataset, &statbuf);
+       if (error == 0) {
+               nvlist_t *config;
+               char *name;
+               int fd;
+
+               fd = open(dataset, O_RDONLY);
+               if (fd < 0)
+                       goto out;
+
+               error = zpool_read_label(fd, &config);
+               (void) close(fd);
+               if (error)
+                       goto out;
+
+               error = nvlist_lookup_string(config,
+                   ZPOOL_CONFIG_POOL_NAME, &name);
+               if (error) {
+                       nvlist_free(config);
+               } else {
+                       dataset = strdup(name);
+                       nvlist_free(config);
+                       return (dataset);
+               }
+       }
+out:
+       /*
+        * If a file or directory in your current working directory is
+        * named 'dataset' then mount(8) will prepend your current working
+        * directory to the dataset.  There is no way to prevent this
+        * behavior so we simply check for it and strip the prepended
+        * patch when it is added.
+        */
+       if (getcwd(cwd, PATH_MAX) == NULL)
+               return (dataset);
+
        len = strlen(cwd);
 
        /* Do not add one when cwd already ends in a trailing '/' */
@@ -241,7 +285,7 @@ mtab_is_writeable(void)
        struct stat st;
        int error, fd;
 
-       error = stat(MNTTAB, &st);
+       error = lstat(MNTTAB, &st);
        if (error || S_ISLNK(st.st_mode))
                return (0);
 
@@ -297,8 +341,10 @@ main(int argc, char **argv)
        char legacy[ZFS_MAXPROPLEN];
        char mntopts[MNT_LINE_MAX] = { '\0' };
        char badopt[MNT_LINE_MAX] = { '\0' };
-       char *dataset, *mntpoint;
-       unsigned long mntflags = 0, zfsflags = 0;
+       char mtabopt[MNT_LINE_MAX] = { '\0' };
+       char mntpoint[PATH_MAX];
+       char *dataset;
+       unsigned long mntflags = 0, zfsflags = 0, remount = 0;
        int sloppy = 0, fake = 0, verbose = 0, nomtab = 0, zfsutil = 0;
        int error, c;
 
@@ -353,27 +399,35 @@ main(int argc, char **argv)
        }
 
        dataset = parse_dataset(argv[0]);
-       mntpoint = argv[1];
+
+       /* canonicalize the mount point */
+       if (realpath(argv[1], mntpoint) == NULL) {
+               (void) fprintf(stderr, gettext("filesystem '%s' cannot be "
+                   "mounted at '%s' due to canonicalization error %d.\n"),
+                   dataset, argv[1], errno);
+               return (MOUNT_SYSERR);
+       }
 
        /* validate mount options and set mntflags */
-       error = parse_options(mntopts, &mntflags, &zfsflags, sloppy, badopt);
+       error = parse_options(mntopts, &mntflags, &zfsflags, sloppy,
+           badopt, mtabopt);
        if (error) {
                switch (error) {
                case ENOMEM:
                        (void) fprintf(stderr, gettext("filesystem '%s' "
                            "cannot be mounted due to a memory allocation "
-                           "failure\n"), dataset);
+                           "failure.\n"), dataset);
                        return (MOUNT_SYSERR);
-               case EINVAL:
+               case ENOENT:
                        (void) fprintf(stderr, gettext("filesystem '%s' "
-                           "cannot be mounted of due to the invalid option "
-                           "'%s'\n"), dataset, badopt);
+                           "cannot be mounted of due invalid option "
+                           "'%s'.\n"), dataset, badopt);
                        (void) fprintf(stderr, gettext("Use the '-s' option "
                            "to ignore the bad mount option.\n"));
                        return (MOUNT_USAGE);
                default:
                        (void) fprintf(stderr, gettext("filesystem '%s' "
-                           "cannot be mounted due to internal error %d\n"),
+                           "cannot be mounted due to internal error %d.\n"),
                            dataset, error);
                        return (MOUNT_SOFTWARE);
                }
@@ -386,9 +440,12 @@ main(int argc, char **argv)
         * done until zfs is added to the default selinux policy configuration
         * as a known filesystem type which supports xattrs.
         */
-        if (is_selinux_enabled() && !(zfsflags & ZS_NOCONTEXT))
+        if (is_selinux_enabled() && !(zfsflags & ZS_NOCONTEXT)) {
                 (void) strlcat(mntopts, ",context=\"system_u:"
                     "object_r:file_t:s0\"", sizeof (mntopts));
+                (void) strlcat(mtabopt, ",context=\"system_u:"
+                    "object_r:file_t:s0\"", sizeof (mtabopt));
+       }
 #endif /* HAVE_LIBSELINUX */
 
 
@@ -396,28 +453,35 @@ main(int argc, char **argv)
                (void) fprintf(stdout, gettext("mount.zfs:\n"
                    "  dataset:    \"%s\"\n  mountpoint: \"%s\"\n"
                    "  mountflags: 0x%lx\n  zfsflags:   0x%lx\n"
-                   "  mountopts:  \"%s\"\n\n"),
-                   dataset, mntpoint, mntflags, zfsflags, mntopts);
+                   "  mountopts:  \"%s\"\n  mtabopts:   \"%s\"\n"),
+                   dataset, mntpoint, mntflags, zfsflags, mntopts, mtabopt);
 
-       if (mntflags & MS_REMOUNT)
+       if (mntflags & MS_REMOUNT) {
                nomtab = 1;
+               remount = 1;
+       }
 
-       if (zfsflags * ZS_ZFSUTIL)
+       if (zfsflags & ZS_ZFSUTIL)
                zfsutil = 1;
 
        if ((g_zfs = libzfs_init()) == NULL)
                return (MOUNT_SYSERR);
 
        /* try to open the dataset to access the mount point */
-       if ((zhp = zfs_open(g_zfs, dataset, ZFS_TYPE_FILESYSTEM)) == NULL) {
+       if ((zhp = zfs_open(g_zfs, dataset,
+           ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT)) == NULL) {
                (void) fprintf(stderr, gettext("filesystem '%s' cannot be "
                    "mounted, unable to open the dataset\n"), dataset);
                libzfs_fini(g_zfs);
                return (MOUNT_USAGE);
        }
 
-       (void) zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, legacy,
-           sizeof (legacy), NULL, NULL, 0, B_FALSE);
+       /* treat all snapshots as legacy mount points */
+       if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT)
+               (void) strlcpy(legacy, ZFS_MOUNTPOINT_LEGACY, ZFS_MAXPROPLEN);
+       else
+               (void) zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, legacy,
+                   sizeof (legacy), NULL, NULL, 0, B_FALSE);
 
        zfs_close(zhp);
        libzfs_fini(g_zfs);
@@ -427,6 +491,11 @@ main(int argc, char **argv)
         * 'zfs mount'.  However, since 'zfs mount' actually invokes 'mount'
         * we differentiate the two cases using the 'zfsutil' mount option.
         * This mount option should only be supplied by the 'zfs mount' util.
+        *
+        * The only exception to the above rule is '-o remount' which is
+        * always allowed for non-legacy datasets.  This is done because when
+        * using zfs as your root file system both rc.sysinit/umountroot and
+        * systemd depend on 'mount -o remount <mountpoint>' to work.
         */
        if (zfsutil && !strcmp(legacy, ZFS_MOUNTPOINT_LEGACY)) {
                (void) fprintf(stderr, gettext(
@@ -437,7 +506,8 @@ main(int argc, char **argv)
                return (MOUNT_USAGE);
        }
 
-       if (!zfsutil && strcmp(legacy, ZFS_MOUNTPOINT_LEGACY)) {
+       if (!zfsutil && !(remount || fake) &&
+           strcmp(legacy, ZFS_MOUNTPOINT_LEGACY)) {
                (void) fprintf(stderr, gettext(
                    "filesystem '%s' cannot be mounted using 'mount'.\n"
                    "Use 'zfs set mountpoint=%s' or 'zfs mount %s'.\n"
@@ -451,6 +521,10 @@ main(int argc, char **argv)
                    mntflags, mntopts);
                if (error) {
                        switch (errno) {
+                       case ENOENT:
+                               (void) fprintf(stderr, gettext("mount point "
+                                   "'%s' does not exist\n"), mntpoint);
+                               return (MOUNT_SYSERR);
                        case EBUSY:
                                (void) fprintf(stderr, gettext("filesystem "
                                    "'%s' is already mounted\n"), dataset);
@@ -465,7 +539,7 @@ main(int argc, char **argv)
        }
 
        if (!nomtab && mtab_is_writeable()) {
-               error = mtab_update(dataset, mntpoint, MNTTYPE_ZFS, mntopts);
+               error = mtab_update(dataset, mntpoint, MNTTYPE_ZFS, mtabopt);
                if (error)
                        return (error);
        }