Linux compat 2.6.39: mount_nodev()
[zfs.git] / lib / libzfs / libzfs_dataset.c
index b7c1360..5f8847a 100644 (file)
 #include <grp.h>
 #include <stddef.h>
 #include <ucred.h>
+#ifdef HAVE_IDMAP
 #include <idmap.h>
 #include <aclutils.h>
 #include <directory.h>
+#endif /* HAVE_IDMAP */
 
 #include <sys/dnode.h>
 #include <sys/spa.h>
@@ -55,6 +57,7 @@
 #include "libzfs_impl.h"
 #include "zfs_deleg.h"
 
+static int zvol_create_link_common(libzfs_handle_t *, const char *, int);
 static int userquota_propname_decode(const char *propname, boolean_t zoned,
     zfs_userquota_prop_t *typep, char *domain, int domainlen, uint64_t *ridp);
 
@@ -72,55 +75,14 @@ zfs_type_to_name(zfs_type_t type)
                return (dgettext(TEXT_DOMAIN, "snapshot"));
        case ZFS_TYPE_VOLUME:
                return (dgettext(TEXT_DOMAIN, "volume"));
+       default:
+               break;
        }
 
        return (NULL);
 }
 
 /*
- * Given a path and mask of ZFS types, return a string describing this dataset.
- * This is used when we fail to open a dataset and we cannot get an exact type.
- * We guess what the type would have been based on the path and the mask of
- * acceptable types.
- */
-static const char *
-path_to_str(const char *path, int types)
-{
-       /*
-        * When given a single type, always report the exact type.
-        */
-       if (types == ZFS_TYPE_SNAPSHOT)
-               return (dgettext(TEXT_DOMAIN, "snapshot"));
-       if (types == ZFS_TYPE_FILESYSTEM)
-               return (dgettext(TEXT_DOMAIN, "filesystem"));
-       if (types == ZFS_TYPE_VOLUME)
-               return (dgettext(TEXT_DOMAIN, "volume"));
-
-       /*
-        * The user is requesting more than one type of dataset.  If this is the
-        * case, consult the path itself.  If we're looking for a snapshot, and
-        * a '@' is found, then report it as "snapshot".  Otherwise, remove the
-        * snapshot attribute and try again.
-        */
-       if (types & ZFS_TYPE_SNAPSHOT) {
-               if (strchr(path, '@') != NULL)
-                       return (dgettext(TEXT_DOMAIN, "snapshot"));
-               return (path_to_str(path, types & ~ZFS_TYPE_SNAPSHOT));
-       }
-
-       /*
-        * The user has requested either filesystems or volumes.
-        * We have no way of knowing a priori what type this would be, so always
-        * report it as "filesystem" or "volume", our two primitive types.
-        */
-       if (types & ZFS_TYPE_FILESYSTEM)
-               return (dgettext(TEXT_DOMAIN, "filesystem"));
-
-       assert(types & ZFS_TYPE_VOLUME);
-       return (dgettext(TEXT_DOMAIN, "volume"));
-}
-
-/*
  * Validate a ZFS path.  This is used even before trying to open the dataset, to
  * provide a more meaningful error message.  We call zfs_error_aux() to
  * explain exactly why the name was not valid.
@@ -180,6 +142,8 @@ zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type,
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                                    "reserved disk name"));
                                break;
+                       default:
+                               break;
                        }
                }
 
@@ -344,7 +308,7 @@ get_recvd_props_ioctl(zfs_handle_t *zhp)
 {
        libzfs_handle_t *hdl = zhp->zfs_hdl;
        nvlist_t *recvdprops;
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        int err;
 
        if (zcmd_alloc_dst_nvlist(hdl, &zc, 0) != 0)
@@ -407,7 +371,7 @@ static int
 get_stats(zfs_handle_t *zhp)
 {
        int rc = 0;
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
 
        if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0)
                return (-1);
@@ -467,7 +431,7 @@ make_dataset_handle_common(zfs_handle_t *zhp, zfs_cmd_t *zc)
 zfs_handle_t *
 make_dataset_handle(libzfs_handle_t *hdl, const char *path)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
 
        zfs_handle_t *zhp = calloc(sizeof (zfs_handle_t), 1);
 
@@ -619,7 +583,7 @@ libzfs_mnttab_fini(libzfs_handle_t *hdl)
        void *cookie = NULL;
        mnttab_node_t *mtn;
 
-       while (mtn = avl_destroy_nodes(&hdl->libzfs_mnttab_cache, &cookie)) {
+       while ((mtn = avl_destroy_nodes(&hdl->libzfs_mnttab_cache, &cookie))) {
                free(mtn->mtn_mt.mnt_special);
                free(mtn->mtn_mt.mnt_mountp);
                free(mtn->mtn_mt.mnt_fstype);
@@ -691,7 +655,7 @@ libzfs_mnttab_remove(libzfs_handle_t *hdl, const char *fsname)
        mnttab_node_t *ret;
 
        find.mtn_mt.mnt_special = (char *)fsname;
-       if (ret = avl_find(&hdl->libzfs_mnttab_cache, (void *)&find, NULL)) {
+       if ((ret = avl_find(&hdl->libzfs_mnttab_cache, (void *)&find, NULL))) {
                avl_remove(&hdl->libzfs_mnttab_cache, ret);
                free(ret->mtn_mt.mnt_special);
                free(ret->mtn_mt.mnt_mountp);
@@ -941,6 +905,7 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl,
 
                case ZFS_PROP_MLSLABEL:
                {
+#ifdef HAVE_MLSLABEL
                        /*
                         * Verify the mlslabel string and convert to
                         * internal hex label string.
@@ -990,7 +955,12 @@ badlabel:
                        (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
                        m_label_free(new_sl);   /* OK if null */
                        goto error;
-
+#else
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "mlslabels are unsupported"));
+                       (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+                       goto error;
+#endif /* HAVE_MLSLABEL */
                }
 
                case ZFS_PROP_MOUNTPOINT:
@@ -1015,6 +985,8 @@ badlabel:
                                            "component of '%s' is too long"),
                                            propname);
                                        break;
+                               default:
+                                       break;
                                }
                                (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
                                goto error;
@@ -1139,6 +1111,8 @@ badlabel:
                case ZFS_PROP_NORMALIZE:
                        chosen_normal = (int)intval;
                        break;
+               default:
+                       break;
                }
 
                /*
@@ -1187,6 +1161,8 @@ badlabel:
                                        goto error;
                                }
                                break;
+                       default:
+                               break;
                        }
                }
        }
@@ -1337,13 +1313,32 @@ zfs_setprop_error(libzfs_handle_t *hdl, zfs_prop_t prop, int err,
        }
 }
 
+static boolean_t
+zfs_is_namespace_prop(zfs_prop_t prop)
+{
+       switch (prop) {
+
+       case ZFS_PROP_ATIME:
+       case ZFS_PROP_DEVICES:
+       case ZFS_PROP_EXEC:
+       case ZFS_PROP_SETUID:
+       case ZFS_PROP_READONLY:
+       case ZFS_PROP_XATTR:
+       case ZFS_PROP_NBMAND:
+               return (B_TRUE);
+
+       default:
+               return (B_FALSE);
+       }
+}
+
 /*
  * Given a property name and value, set the property for the given dataset.
  */
 int
 zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        int ret = -1;
        prop_changelist_t *cl = NULL;
        char errbuf[1024];
@@ -1352,7 +1347,7 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval)
        zfs_prop_t prop;
        boolean_t do_prefix;
        uint64_t idx;
-       int added_resv;
+       int added_resv = 0;
 
        (void) snprintf(errbuf, sizeof (errbuf),
            dgettext(TEXT_DOMAIN, "cannot set property for '%s'"),
@@ -1432,12 +1427,22 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval)
                if (do_prefix)
                        ret = changelist_postfix(cl);
 
-               /*
-                * Refresh the statistics so the new property value
-                * is reflected.
-                */
-               if (ret == 0)
+               if (ret == 0) {
+                       /*
+                        * Refresh the statistics so the new property
+                        * value is reflected.
+                        */
                        (void) get_stats(zhp);
+
+                       /*
+                        * Remount the filesystem to propagate the change
+                        * if one of the options handled by the generic
+                        * Linux namespace layer has been modified.
+                        */
+                       if (zfs_is_namespace_prop(prop) &&
+                           zfs_is_mounted(zhp, NULL))
+                               ret = zfs_mount(zhp, MNTOPT_REMOUNT, 0);
+               }
        }
 
 error:
@@ -1455,7 +1460,7 @@ error:
 int
 zfs_prop_inherit(zfs_handle_t *zhp, const char *propname, boolean_t received)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        int ret;
        prop_changelist_t *cl;
        libzfs_handle_t *hdl = zhp->zfs_hdl;
@@ -1554,7 +1559,7 @@ error:
  * True DSL properties are stored in an nvlist.  The following two functions
  * extract them appropriately.
  */
-static uint64_t
+uint64_t
 getprop_uint64(zfs_handle_t *zhp, zfs_prop_t prop, char **source)
 {
        nvlist_t *nv;
@@ -1622,7 +1627,7 @@ zfs_unset_recvd_props_mode(zfs_handle_t *zhp, uint64_t *cookie)
  * zfs_prop_get_int() are built using this interface.
  *
  * Certain properties can be overridden using 'mount -o'.  In this case, scan
- * the contents of the /etc/mnttab entry, searching for the appropriate options.
+ * the contents of the /etc/mtab entry, searching for the appropriate options.
  * If they differ from the on-disk values, report the current values and mark
  * the source "temporary".
  */
@@ -1630,7 +1635,7 @@ static int
 get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src,
     char **source, uint64_t *val)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        nvlist_t *zplprops = NULL;
        struct mnttab mnt;
        char *mntopt_on = NULL;
@@ -1674,11 +1679,13 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src,
                mntopt_on = MNTOPT_NBMAND;
                mntopt_off = MNTOPT_NONBMAND;
                break;
+       default:
+               break;
        }
 
        /*
         * Because looking up the mount options is potentially expensive
-        * (iterating over all of /etc/mnttab), we defer its calculation until
+        * (iterating over all of /etc/mtab), we defer its calculation until
         * we're looking up a property which requires its presence.
         */
        if (!zhp->zfs_mntcheck &&
@@ -1906,7 +1913,7 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
                            localtime_r(&time, &t) == NULL ||
                            strftime(propbuf, proplen, "%a %b %e %k:%M %Y",
                            &t) == 0)
-                               (void) snprintf(propbuf, proplen, "%llu", val);
+                               (void) snprintf(propbuf, proplen, "%llu", (u_longlong_t) val);
                }
                break;
 
@@ -2070,6 +2077,7 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
 
        case ZFS_PROP_MLSLABEL:
                {
+#ifdef HAVE_MLSLABEL
                        m_label_t *new_sl = NULL;
                        char *ascii = NULL;     /* human readable label */
 
@@ -2103,6 +2111,10 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
 
                        (void) strlcpy(propbuf, ascii, proplen);
                        free(ascii);
+#else
+                       (void) strlcpy(propbuf,
+                           getprop_string(zhp, prop, &source), proplen);
+#endif /* HAVE_MLSLABEL */
                }
                break;
 
@@ -2197,6 +2209,7 @@ zfs_prop_get_numeric(zfs_handle_t *zhp, zfs_prop_t prop, uint64_t *value,
        return (0);
 }
 
+#ifdef HAVE_IDMAP
 static int
 idmap_id_to_numeric_domain_rid(uid_t id, boolean_t isuser,
     char **domainp, idmap_rid_t *ridp)
@@ -2226,6 +2239,7 @@ out:
                idmap_get_destroy(get_hdl);
        return (err);
 }
+#endif /* HAVE_IDMAP */
 
 /*
  * convert the propname into parameters needed by kernel
@@ -2259,6 +2273,7 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
        cp = strchr(propname, '@') + 1;
 
        if (strchr(cp, '@')) {
+#ifdef HAVE_IDMAP
                /*
                 * It's a SID name (eg "user@domain") that needs to be
                 * turned into S-1-domainID-RID.
@@ -2281,6 +2296,9 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
                        return (ENOENT);
                cp = numericsid;
                /* will be further decoded below */
+#else
+               return (ENOSYS);
+#endif /* HAVE_IDMAP */
        }
 
        if (strncmp(cp, "S-1-", 4) == 0) {
@@ -2319,6 +2337,7 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
                        *ridp = gr->gr_gid;
                }
        } else {
+#ifdef HAVE_IDMAP
                /* It's a user/group ID (eg "12345"). */
                uid_t id = strtoul(cp, &end, 10);
                idmap_rid_t rid;
@@ -2336,6 +2355,9 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
                } else {
                        *ridp = id;
                }
+#else
+               return (ENOSYS);
+#endif /* HAVE_IDMAP */
        }
 
        ASSERT3P(numericsid, ==, NULL);
@@ -2347,7 +2369,7 @@ zfs_prop_get_userquota_common(zfs_handle_t *zhp, const char *propname,
     uint64_t *propvalue, zfs_userquota_prop_t *typep)
 {
        int err;
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
 
        (void) strncpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
 
@@ -2391,7 +2413,8 @@ zfs_prop_get_userquota(zfs_handle_t *zhp, const char *propname,
                return (err);
 
        if (literal) {
-               (void) snprintf(propbuf, proplen, "%llu", propvalue);
+               (void) snprintf(propbuf, proplen, "%llu",
+                              (u_longlong_t)propvalue);
        } else if (propvalue == 0 &&
            (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_GROUPQUOTA)) {
                (void) strlcpy(propbuf, "none", proplen);
@@ -2465,7 +2488,7 @@ top:
 int
 zfs_iter_filesystems(zfs_handle_t *zhp, zfs_iter_f func, void *data)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        zfs_handle_t *nzhp;
        int ret;
 
@@ -2501,7 +2524,7 @@ zfs_iter_filesystems(zfs_handle_t *zhp, zfs_iter_f func, void *data)
 int
 zfs_iter_snapshots(zfs_handle_t *zhp, zfs_iter_f func, void *data)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        zfs_handle_t *nzhp;
        int ret;
 
@@ -2592,7 +2615,7 @@ static int
 check_parents(libzfs_handle_t *hdl, const char *path, uint64_t *zoned,
     boolean_t accept_ancestor, int *prefixlen)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        char parent[ZFS_MAXNAMELEN];
        char *slash;
        zfs_handle_t *zhp;
@@ -2721,7 +2744,7 @@ create_parents(libzfs_handle_t *hdl, char *target, int prefixlen)
         * up to the prefixlen-long one.
         */
        for (cp = target + prefixlen + 1;
-           cp = strchr(cp, '/'); *cp = '/', cp++) {
+           (cp = strchr(cp, '/')); *cp = '/', cp++) {
                char *logstr;
 
                *cp = '\0';
@@ -2778,7 +2801,7 @@ zfs_create_ancestors(libzfs_handle_t *hdl, const char *path)
 {
        int prefix;
        char *path_copy;
-       int rc;
+       int rc = 0;
 
        if (check_parents(hdl, path, NULL, B_TRUE, &prefix) != 0)
                return (-1);
@@ -2800,7 +2823,7 @@ int
 zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
     nvlist_t *props)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        int ret;
        uint64_t size = 0;
        uint64_t blocksize = zfs_prop_default_numeric(ZFS_PROP_VOLBLOCKSIZE);
@@ -2894,6 +2917,18 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
        /* create the dataset */
        ret = zfs_ioctl(hdl, ZFS_IOC_CREATE, &zc);
 
+       if (ret == 0 && type == ZFS_TYPE_VOLUME) {
+               ret = zvol_create_link(hdl, path);
+               if (ret) {
+                       (void) zfs_standard_error(hdl, errno,
+                           dgettext(TEXT_DOMAIN,
+                           "Volume successfully created, but device links "
+                           "were not created"));
+                       zcmd_free_nvlists(&zc);
+                       return (-1);
+               }
+       }
+
        zcmd_free_nvlists(&zc);
 
        /* check for failure */
@@ -2951,11 +2986,14 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
 int
 zfs_destroy(zfs_handle_t *zhp, boolean_t defer)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
 
        (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
 
        if (ZFS_IS_VOLUME(zhp)) {
+               if (zvol_remove_link(zhp->zfs_hdl, zhp->zfs_name) != 0)
+                       return (-1);
+
                zc.zc_objset_type = DMU_OST_ZVOL;
        } else {
                zc.zc_objset_type = DMU_OST_ZFS;
@@ -2998,9 +3036,17 @@ zfs_check_snap_cb(zfs_handle_t *zhp, void *arg)
                zfs_close(szhp);
        }
 
+       if (zhp->zfs_type == ZFS_TYPE_VOLUME) {
+               (void) zvol_remove_link(zhp->zfs_hdl, name);
+               /*
+                * NB: this is simply a best-effort.  We don't want to
+                * return an error, because then we wouldn't visit all
+                * the volumes.
+                */
+       }
+
        dd->closezhp = B_TRUE;
-       if (!dd->gotone)
-               rv = zfs_iter_filesystems(zhp, zfs_check_snap_cb, arg);
+       rv = zfs_iter_filesystems(zhp, zfs_check_snap_cb, arg);
        if (closezhp)
                zfs_close(zhp);
        return (rv);
@@ -3012,7 +3058,7 @@ zfs_check_snap_cb(zfs_handle_t *zhp, void *arg)
 int
 zfs_destroy_snaps(zfs_handle_t *zhp, char *snapname, boolean_t defer)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        int ret;
        struct destroydata dd = { 0 };
 
@@ -3057,7 +3103,7 @@ zfs_destroy_snaps(zfs_handle_t *zhp, char *snapname, boolean_t defer)
 int
 zfs_clone(zfs_handle_t *zhp, const char *target, nvlist_t *props)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        char parent[ZFS_MAXNAMELEN];
        int ret;
        char errbuf[1024];
@@ -3135,11 +3181,70 @@ zfs_clone(zfs_handle_t *zhp, const char *target, nvlist_t *props)
                        return (zfs_standard_error(zhp->zfs_hdl, errno,
                            errbuf));
                }
+       } else if (ZFS_IS_VOLUME(zhp)) {
+               ret = zvol_create_link(zhp->zfs_hdl, target);
        }
 
        return (ret);
 }
 
+typedef struct promote_data {
+       char cb_mountpoint[MAXPATHLEN];
+       const char *cb_target;
+       const char *cb_errbuf;
+       uint64_t cb_pivot_txg;
+} promote_data_t;
+
+static int
+promote_snap_cb(zfs_handle_t *zhp, void *data)
+{
+       promote_data_t *pd = data;
+       zfs_handle_t *szhp;
+       char snapname[MAXPATHLEN];
+       int rv = 0;
+
+       /* We don't care about snapshots after the pivot point */
+       if (zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) > pd->cb_pivot_txg) {
+               zfs_close(zhp);
+               return (0);
+       }
+
+       /* Remove the device link if it's a zvol. */
+       if (ZFS_IS_VOLUME(zhp))
+               (void) zvol_remove_link(zhp->zfs_hdl, zhp->zfs_name);
+
+       /* Check for conflicting names */
+       (void) strlcpy(snapname, pd->cb_target, sizeof (snapname));
+       (void) strlcat(snapname, strchr(zhp->zfs_name, '@'), sizeof (snapname));
+       szhp = make_dataset_handle(zhp->zfs_hdl, snapname);
+       if (szhp != NULL) {
+               zfs_close(szhp);
+               zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+                   "snapshot name '%s' from origin \n"
+                   "conflicts with '%s' from target"),
+                   zhp->zfs_name, snapname);
+               rv = zfs_error(zhp->zfs_hdl, EZFS_EXISTS, pd->cb_errbuf);
+       }
+       zfs_close(zhp);
+       return (rv);
+}
+
+static int
+promote_snap_done_cb(zfs_handle_t *zhp, void *data)
+{
+       promote_data_t *pd = data;
+
+       /* We don't care about snapshots after the pivot point */
+       if (zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) <= pd->cb_pivot_txg) {
+               /* Create the device link if it's a zvol. */
+               if (ZFS_IS_VOLUME(zhp))
+                       (void) zvol_create_link(zhp->zfs_hdl, zhp->zfs_name);
+       }
+
+       zfs_close(zhp);
+       return (0);
+}
+
 /*
  * Promotes the given clone fs to be the clone parent.
  */
@@ -3147,9 +3252,12 @@ int
 zfs_promote(zfs_handle_t *zhp)
 {
        libzfs_handle_t *hdl = zhp->zfs_hdl;
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        char parent[MAXPATHLEN];
+       char *cp;
        int ret;
+       zfs_handle_t *pzhp;
+       promote_data_t pd;
        char errbuf[1024];
 
        (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
@@ -3167,7 +3275,29 @@ zfs_promote(zfs_handle_t *zhp)
                    "not a cloned filesystem"));
                return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
        }
+       cp = strchr(parent, '@');
+       *cp = '\0';
+
+       /* Walk the snapshots we will be moving */
+       pzhp = zfs_open(hdl, zhp->zfs_dmustats.dds_origin, ZFS_TYPE_SNAPSHOT);
+       if (pzhp == NULL)
+               return (-1);
+       pd.cb_pivot_txg = zfs_prop_get_int(pzhp, ZFS_PROP_CREATETXG);
+       zfs_close(pzhp);
+       pd.cb_target = zhp->zfs_name;
+       pd.cb_errbuf = errbuf;
+       pzhp = zfs_open(hdl, parent, ZFS_TYPE_DATASET);
+       if (pzhp == NULL)
+               return (-1);
+       (void) zfs_prop_get(pzhp, ZFS_PROP_MOUNTPOINT, pd.cb_mountpoint,
+           sizeof (pd.cb_mountpoint), NULL, NULL, 0, FALSE);
+       ret = zfs_iter_snapshots(pzhp, promote_snap_cb, &pd);
+       if (ret != 0) {
+               zfs_close(pzhp);
+               return (-1);
+       }
 
+       /* issue the ioctl */
        (void) strlcpy(zc.zc_value, zhp->zfs_dmustats.dds_origin,
            sizeof (zc.zc_value));
        (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
@@ -3176,9 +3306,16 @@ zfs_promote(zfs_handle_t *zhp)
        if (ret != 0) {
                int save_errno = errno;
 
+               (void) zfs_iter_snapshots(pzhp, promote_snap_done_cb, &pd);
+               zfs_close(pzhp);
+
                switch (save_errno) {
                case EEXIST:
-                       /* There is a conflicting snapshot name. */
+                       /*
+                        * There is a conflicting snapshot name.  We
+                        * should have caught this above, but they could
+                        * have renamed something in the mean time.
+                        */
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "conflicting snapshot '%s' from parent '%s'"),
                            zc.zc_string, parent);
@@ -3187,7 +3324,44 @@ zfs_promote(zfs_handle_t *zhp)
                default:
                        return (zfs_standard_error(hdl, save_errno, errbuf));
                }
+       } else {
+               (void) zfs_iter_snapshots(zhp, promote_snap_done_cb, &pd);
+       }
+
+       zfs_close(pzhp);
+       return (ret);
+}
+
+struct createdata {
+       const char *cd_snapname;
+       int cd_ifexists;
+};
+
+static int
+zfs_create_link_cb(zfs_handle_t *zhp, void *arg)
+{
+       struct createdata *cd = arg;
+       int ret;
+
+       if (zhp->zfs_type == ZFS_TYPE_VOLUME) {
+               char name[MAXPATHLEN];
+
+               (void) strlcpy(name, zhp->zfs_name, sizeof (name));
+               (void) strlcat(name, "@", sizeof (name));
+               (void) strlcat(name, cd->cd_snapname, sizeof (name));
+               (void) zvol_create_link_common(zhp->zfs_hdl, name,
+                   cd->cd_ifexists);
+               /*
+                * NB: this is simply a best-effort.  We don't want to
+                * return an error, because then we wouldn't visit all
+                * the volumes.
+                */
        }
+
+       ret = zfs_iter_filesystems(zhp, zfs_create_link_cb, cd);
+
+       zfs_close(zhp);
+
        return (ret);
 }
 
@@ -3201,7 +3375,7 @@ zfs_snapshot(libzfs_handle_t *hdl, const char *path, boolean_t recursive,
        const char *delim;
        char parent[ZFS_MAXNAMELEN];
        zfs_handle_t *zhp;
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        int ret;
        char errbuf[1024];
 
@@ -3251,12 +3425,32 @@ zfs_snapshot(libzfs_handle_t *hdl, const char *path, boolean_t recursive,
         * if it was recursive, the one that actually failed will be in
         * zc.zc_name.
         */
-       if (ret != 0) {
+       if (ret != 0)
                (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
                    "cannot create snapshot '%s@%s'"), zc.zc_name, zc.zc_value);
-               (void) zfs_standard_error(hdl, errno, errbuf);
+
+       if (ret == 0 && recursive) {
+               struct createdata cd;
+
+               cd.cd_snapname = delim + 1;
+               cd.cd_ifexists = B_FALSE;
+               (void) zfs_iter_filesystems(zhp, zfs_create_link_cb, &cd);
+       }
+       if (ret == 0 && zhp->zfs_type == ZFS_TYPE_VOLUME) {
+               ret = zvol_create_link(zhp->zfs_hdl, path);
+               if (ret != 0) {
+                       (void) zfs_standard_error(hdl, errno,
+                           dgettext(TEXT_DOMAIN,
+                           "Volume successfully snapshotted, but device links "
+                           "were not created"));
+                       zfs_close(zhp);
+                       return (-1);
+               }
        }
 
+       if (ret != 0)
+               (void) zfs_standard_error(hdl, errno, errbuf);
+
        zfs_close(zhp);
 
        return (ret);
@@ -3333,10 +3527,10 @@ zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
 {
        rollback_data_t cb = { 0 };
        int err;
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        boolean_t restore_resv = 0;
-       uint64_t old_volsize, new_volsize;
-       zfs_prop_t resv_prop;
+       uint64_t old_volsize = 0, new_volsize;
+       zfs_prop_t resv_prop = { 0 };
 
        assert(zhp->zfs_type == ZFS_TYPE_FILESYSTEM ||
            zhp->zfs_type == ZFS_TYPE_VOLUME);
@@ -3358,6 +3552,8 @@ zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
         */
 
        if (zhp->zfs_type == ZFS_TYPE_VOLUME) {
+               if (zvol_remove_link(zhp->zfs_hdl, zhp->zfs_name) != 0)
+                       return (-1);
                if (zfs_which_resv_prop(zhp, &resv_prop) < 0)
                        return (-1);
                old_volsize = zfs_prop_get_int(zhp, ZFS_PROP_VOLSIZE);
@@ -3395,6 +3591,10 @@ zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
         */
        if ((zhp->zfs_type == ZFS_TYPE_VOLUME) &&
            (zhp = make_dataset_handle(zhp->zfs_hdl, zhp->zfs_name))) {
+               if ((err = zvol_create_link(zhp->zfs_hdl, zhp->zfs_name))) {
+                       zfs_close(zhp);
+                       return (err);
+               }
                if (restore_resv) {
                        new_volsize = zfs_prop_get_int(zhp, ZFS_PROP_VOLSIZE);
                        if (old_volsize != new_volsize)
@@ -3449,7 +3649,7 @@ int
 zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive)
 {
        int ret;
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        char *delim;
        prop_changelist_t *cl = NULL;
        zfs_handle_t *zhrp = NULL;
@@ -3543,6 +3743,7 @@ zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive)
        }
 
        if (recursive) {
+               struct destroydata dd;
 
                parentname = zfs_strdup(zhp->zfs_hdl, zhp->zfs_name);
                if (parentname == NULL) {
@@ -3557,6 +3758,15 @@ zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive)
                        goto error;
                }
 
+               dd.snapname = delim + 1;
+               dd.gotone = B_FALSE;
+               dd.closezhp = B_TRUE;
+
+               /* We remove any zvol links prior to renaming them */
+               ret = zfs_iter_filesystems(zhrp, zfs_check_snap_cb, &dd);
+               if (ret) {
+                       goto error;
+               }
        } else {
                if ((cl = changelist_gather(zhp, ZFS_PROP_NAME, 0, 0)) == NULL)
                        return (-1);
@@ -3566,6 +3776,7 @@ zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive)
                            "child dataset with inherited mountpoint is used "
                            "in a non-global zone"));
                        (void) zfs_error(hdl, EZFS_ZONED, errbuf);
+                       ret = -1;
                        goto error;
                }
 
@@ -3604,10 +3815,27 @@ zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive)
                 * On failure, we still want to remount any filesystems that
                 * were previously mounted, so we don't alter the system state.
                 */
-               if (!recursive)
+               if (recursive) {
+                       struct createdata cd;
+
+                       /* only create links for datasets that had existed */
+                       cd.cd_snapname = delim + 1;
+                       cd.cd_ifexists = B_TRUE;
+                       (void) zfs_iter_filesystems(zhrp, zfs_create_link_cb,
+                           &cd);
+               } else {
                        (void) changelist_postfix(cl);
+               }
        } else {
-               if (!recursive) {
+               if (recursive) {
+                       struct createdata cd;
+
+                       /* only create links for datasets that had existed */
+                       cd.cd_snapname = strchr(target, '@') + 1;
+                       cd.cd_ifexists = B_TRUE;
+                       ret = zfs_iter_filesystems(zhrp, zfs_create_link_cb,
+                           &cd);
+               } else {
                        changelist_rename(cl, zfs_get_name(zhp), target);
                        ret = changelist_postfix(cl);
                }
@@ -3626,19 +3854,103 @@ error:
        return (ret);
 }
 
-nvlist_t *
-zfs_get_user_props(zfs_handle_t *zhp)
+/*
+ * Given a zvol dataset, issue the ioctl to create the appropriate minor node,
+ * and wait briefly for udev to create the /dev link.
+ */
+int
+zvol_create_link(libzfs_handle_t *hdl, const char *dataset)
 {
-       return (zhp->zfs_user_props);
+       return (zvol_create_link_common(hdl, dataset, B_FALSE));
+}
+
+static int
+zvol_create_link_common(libzfs_handle_t *hdl, const char *dataset, int ifexists)
+{
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       char path[MAXPATHLEN];
+       int error;
+
+       (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
+
+       /*
+        * Issue the appropriate ioctl.
+        */
+       if (ioctl(hdl->libzfs_fd, ZFS_IOC_CREATE_MINOR, &zc) != 0) {
+               switch (errno) {
+               case EEXIST:
+                       /*
+                        * Silently ignore the case where the link already
+                        * exists.  This allows 'zfs volinit' to be run multiple
+                        * times without errors.
+                        */
+                       return (0);
+
+               case ENOENT:
+                       /*
+                        * Dataset does not exist in the kernel.  If we
+                        * don't care (see zfs_rename), then ignore the
+                        * error quietly.
+                        */
+                       if (ifexists) {
+                               return (0);
+                       }
+
+                       /* FALLTHROUGH */
+
+               default:
+                       return (zfs_standard_error_fmt(hdl, errno,
+                           dgettext(TEXT_DOMAIN, "cannot create device links "
+                           "for '%s'"), dataset));
+               }
+       }
+
+       /*
+        * Wait up to 10 seconds for udev to create the device.
+        */
+       (void) snprintf(path, sizeof (path), "%s/%s", ZVOL_DIR, dataset);
+       error = zpool_label_disk_wait(path, 10000);
+       if (error)
+               (void) printf(gettext("%s may not be immediately "
+                   "available\n"), path);
+
+       return (0);
+}
+
+/*
+ * Remove a minor node for the given zvol and the associated /dev links.
+ */
+int
+zvol_remove_link(libzfs_handle_t *hdl, const char *dataset)
+{
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+
+       (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
+
+       if (ioctl(hdl->libzfs_fd, ZFS_IOC_REMOVE_MINOR, &zc) != 0) {
+               switch (errno) {
+               case ENXIO:
+                       /*
+                        * Silently ignore the case where the link no longer
+                        * exists, so that 'zfs volfini' can be run multiple
+                        * times without errors.
+                        */
+                       return (0);
+
+               default:
+                       return (zfs_standard_error_fmt(hdl, errno,
+                           dgettext(TEXT_DOMAIN, "cannot remove device "
+                           "links for '%s'"), dataset));
+               }
+       }
+
+       return (0);
 }
 
 nvlist_t *
-zfs_get_recvd_props(zfs_handle_t *zhp)
+zfs_get_user_props(zfs_handle_t *zhp)
 {
-       if (zhp->zfs_recvd_props == NULL)
-               if (get_recvd_props_ioctl(zhp) != 0)
-                       return (NULL);
-       return (zhp->zfs_recvd_props);
+       return (zhp->zfs_user_props);
 }
 
 /*
@@ -3750,26 +4062,6 @@ zfs_expand_proplist(zfs_handle_t *zhp, zprop_list_t **plp, boolean_t received)
        return (0);
 }
 
-int
-zfs_deleg_share_nfs(libzfs_handle_t *hdl, char *dataset, char *path,
-    char *resource, void *export, void *sharetab,
-    int sharemax, zfs_share_op_t operation)
-{
-       zfs_cmd_t zc = { 0 };
-       int error;
-
-       (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
-       (void) strlcpy(zc.zc_value, path, sizeof (zc.zc_value));
-       if (resource)
-               (void) strlcpy(zc.zc_string, resource, sizeof (zc.zc_string));
-       zc.zc_share.z_sharedata = (uint64_t)(uintptr_t)sharetab;
-       zc.zc_share.z_exportdata = (uint64_t)(uintptr_t)export;
-       zc.zc_share.z_sharetype = operation;
-       zc.zc_share.z_sharemax = sharemax;
-       error = ioctl(hdl->libzfs_fd, ZFS_IOC_SHARE, &zc);
-       return (error);
-}
-
 void
 zfs_prune_proplist(zfs_handle_t *zhp, uint8_t *props)
 {
@@ -3805,7 +4097,7 @@ static int
 zfs_smb_acl_mgmt(libzfs_handle_t *hdl, char *dataset, char *path,
     zfs_smb_acl_op_t cmd, char *resource1, char *resource2)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        nvlist_t *nvlist = NULL;
        int error;
 
@@ -3816,7 +4108,7 @@ zfs_smb_acl_mgmt(libzfs_handle_t *hdl, char *dataset, char *path,
        if (cmd == ZFS_SMB_ACL_RENAME) {
                if (nvlist_alloc(&nvlist, NV_UNIQUE_NAME, 0) != 0) {
                        (void) no_memory(hdl);
-                       return (NULL);
+                       return (-1);
                }
        }
 
@@ -3887,7 +4179,7 @@ int
 zfs_userspace(zfs_handle_t *zhp, zfs_userquota_prop_t type,
     zfs_userspace_cb_t func, void *arg)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        int error;
        zfs_useracct_t buf[100];
 
@@ -3924,7 +4216,7 @@ zfs_hold(zfs_handle_t *zhp, const char *snapname, const char *tag,
     boolean_t recursive, boolean_t temphold, boolean_t enoent_ok,
     int cleanup_fd, uint64_t dsobj, uint64_t createtxg)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        libzfs_handle_t *hdl = zhp->zfs_hdl;
 
        ASSERT(!recursive || dsobj == 0);
@@ -3982,7 +4274,7 @@ int
 zfs_release(zfs_handle_t *zhp, const char *snapname, const char *tag,
     boolean_t recursive)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        libzfs_handle_t *hdl = zhp->zfs_hdl;
 
        (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));