]> git.proxmox.com Git - mirror_zfs-debian.git/blobdiff - lib/libzfs/libzfs_dataset.c
New upstream version 0.7.6
[mirror_zfs-debian.git] / lib / libzfs / libzfs_dataset.c
index e8c329ced9cb42773ecf6cb8a3f086647679e04a..b65dbc826854a766c912c52d184d4db6c7a4f205 100644 (file)
 
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2010 Nexenta Systems, Inc. All rights reserved.
- * Copyright (c) 2011 by Delphix. All rights reserved.
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ * Copyright (c) 2011, 2016 by Delphix. All rights reserved.
+ * Copyright (c) 2012 DEY Storage Systems, Inc.  All rights reserved.
+ * Copyright (c) 2012 Pawel Jakub Dawidek <pawel@dawidek.net>.
+ * Copyright (c) 2013 Martin Matuska. All rights reserved.
+ * Copyright (c) 2013 Steven Hartland. All rights reserved.
+ * Copyright 2016 Nexenta Systems, Inc.
+ * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
+ * Copyright 2017 RackTop Systems.
  */
 
 #include <ctype.h>
@@ -38,7 +45,6 @@
 #include <fcntl.h>
 #include <sys/mntent.h>
 #include <sys/mount.h>
-#include <priv.h>
 #include <pwd.h>
 #include <grp.h>
 #include <stddef.h>
@@ -59,7 +65,6 @@
 #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);
 
@@ -77,8 +82,12 @@ zfs_type_to_name(zfs_type_t type)
                return (dgettext(TEXT_DOMAIN, "snapshot"));
        case ZFS_TYPE_VOLUME:
                return (dgettext(TEXT_DOMAIN, "volume"));
+       case ZFS_TYPE_POOL:
+               return (dgettext(TEXT_DOMAIN, "pool"));
+       case ZFS_TYPE_BOOKMARK:
+               return (dgettext(TEXT_DOMAIN, "bookmark"));
        default:
-               break;
+               assert(!"unhandled zfs_type_t");
        }
 
        return (NULL);
@@ -96,8 +105,7 @@ zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type,
        namecheck_err_t why;
        char what;
 
-       (void) zfs_prop_get_table();
-       if (dataset_namecheck(path, &why, &what) != 0) {
+       if (entity_namecheck(path, &why, &what) != 0) {
                if (hdl != NULL) {
                        switch (why) {
                        case NAME_ERR_TOOLONG:
@@ -126,9 +134,10 @@ zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type,
                                    "'%c' in name"), what);
                                break;
 
-                       case NAME_ERR_MULTIPLE_AT:
+                       case NAME_ERR_MULTIPLE_DELIMITERS:
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-                                   "multiple '@' delimiters in name"));
+                                   "multiple '@' and/or '#' delimiters in "
+                                   "name"));
                                break;
 
                        case NAME_ERR_NOLETTER:
@@ -145,7 +154,10 @@ zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type,
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                                    "reserved disk name"));
                                break;
+
                        default:
+                               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                                   "(%d) not defined"), why);
                                break;
                        }
                }
@@ -156,7 +168,7 @@ zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type,
        if (!(type & ZFS_TYPE_SNAPSHOT) && strchr(path, '@') != NULL) {
                if (hdl != NULL)
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-                           "snapshot delimiter '@' in filesystem name"));
+                           "snapshot delimiter '@' is not expected here"));
                return (0);
        }
 
@@ -167,6 +179,20 @@ zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type,
                return (0);
        }
 
+       if (!(type & ZFS_TYPE_BOOKMARK) && strchr(path, '#') != NULL) {
+               if (hdl != NULL)
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "bookmark delimiter '#' is not expected here"));
+               return (0);
+       }
+
+       if (type == ZFS_TYPE_BOOKMARK && strchr(path, '#') == NULL) {
+               if (hdl != NULL)
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "missing '#' delimiter in bookmark name"));
+               return (0);
+       }
+
        if (modifying && strchr(path, '%') != NULL) {
                if (hdl != NULL)
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
@@ -256,7 +282,7 @@ zpool_handle(zfs_handle_t *zhp)
        int len;
        zpool_handle_t *zph;
 
-       len = strcspn(zhp->zfs_name, "/@") + 1;
+       len = strcspn(zhp->zfs_name, "/@#") + 1;
        pool_name = zfs_alloc(zhp->zfs_hdl, len);
        (void) strlcpy(pool_name, zhp->zfs_name, len);
 
@@ -311,7 +337,7 @@ get_recvd_props_ioctl(zfs_handle_t *zhp)
 {
        libzfs_handle_t *hdl = zhp->zfs_hdl;
        nvlist_t *recvdprops;
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       zfs_cmd_t zc = {"\0"};
        int err;
 
        if (zcmd_alloc_dst_nvlist(hdl, &zc, 0) != 0)
@@ -374,7 +400,7 @@ static int
 get_stats(zfs_handle_t *zhp)
 {
        int rc = 0;
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       zfs_cmd_t zc = {"\0"};
 
        if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0)
                return (-1);
@@ -414,8 +440,7 @@ make_dataset_handle_common(zfs_handle_t *zhp, zfs_cmd_t *zc)
        else if (zhp->zfs_dmustats.dds_type == DMU_OST_ZFS)
                zhp->zfs_head_type = ZFS_TYPE_FILESYSTEM;
        else if (zhp->zfs_dmustats.dds_type == DMU_OST_OTHER)
-               return (-1); /* zpios' and other testing datasets are
-                               of this type, ignore if encountered */
+               return (-1);
        else
                abort();
 
@@ -437,7 +462,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", "\0", "\0", "\0", 0 };
+       zfs_cmd_t zc = {"\0"};
 
        zfs_handle_t *zhp = calloc(sizeof (zfs_handle_t), 1);
 
@@ -463,7 +488,7 @@ make_dataset_handle(libzfs_handle_t *hdl, const char *path)
        return (zhp);
 }
 
-static zfs_handle_t *
+zfs_handle_t *
 make_dataset_handle_zc(libzfs_handle_t *hdl, zfs_cmd_t *zc)
 {
        zfs_handle_t *zhp = calloc(sizeof (zfs_handle_t), 1);
@@ -480,8 +505,164 @@ make_dataset_handle_zc(libzfs_handle_t *hdl, zfs_cmd_t *zc)
        return (zhp);
 }
 
+zfs_handle_t *
+make_dataset_simple_handle_zc(zfs_handle_t *pzhp, zfs_cmd_t *zc)
+{
+       zfs_handle_t *zhp = calloc(sizeof (zfs_handle_t), 1);
+
+       if (zhp == NULL)
+               return (NULL);
+
+       zhp->zfs_hdl = pzhp->zfs_hdl;
+       (void) strlcpy(zhp->zfs_name, zc->zc_name, sizeof (zhp->zfs_name));
+       zhp->zfs_head_type = pzhp->zfs_type;
+       zhp->zfs_type = ZFS_TYPE_SNAPSHOT;
+       zhp->zpool_hdl = zpool_handle(zhp);
+
+       return (zhp);
+}
+
+zfs_handle_t *
+zfs_handle_dup(zfs_handle_t *zhp_orig)
+{
+       zfs_handle_t *zhp = calloc(sizeof (zfs_handle_t), 1);
+
+       if (zhp == NULL)
+               return (NULL);
+
+       zhp->zfs_hdl = zhp_orig->zfs_hdl;
+       zhp->zpool_hdl = zhp_orig->zpool_hdl;
+       (void) strlcpy(zhp->zfs_name, zhp_orig->zfs_name,
+           sizeof (zhp->zfs_name));
+       zhp->zfs_type = zhp_orig->zfs_type;
+       zhp->zfs_head_type = zhp_orig->zfs_head_type;
+       zhp->zfs_dmustats = zhp_orig->zfs_dmustats;
+       if (zhp_orig->zfs_props != NULL) {
+               if (nvlist_dup(zhp_orig->zfs_props, &zhp->zfs_props, 0) != 0) {
+                       (void) no_memory(zhp->zfs_hdl);
+                       zfs_close(zhp);
+                       return (NULL);
+               }
+       }
+       if (zhp_orig->zfs_user_props != NULL) {
+               if (nvlist_dup(zhp_orig->zfs_user_props,
+                   &zhp->zfs_user_props, 0) != 0) {
+                       (void) no_memory(zhp->zfs_hdl);
+                       zfs_close(zhp);
+                       return (NULL);
+               }
+       }
+       if (zhp_orig->zfs_recvd_props != NULL) {
+               if (nvlist_dup(zhp_orig->zfs_recvd_props,
+                   &zhp->zfs_recvd_props, 0)) {
+                       (void) no_memory(zhp->zfs_hdl);
+                       zfs_close(zhp);
+                       return (NULL);
+               }
+       }
+       zhp->zfs_mntcheck = zhp_orig->zfs_mntcheck;
+       if (zhp_orig->zfs_mntopts != NULL) {
+               zhp->zfs_mntopts = zfs_strdup(zhp_orig->zfs_hdl,
+                   zhp_orig->zfs_mntopts);
+       }
+       zhp->zfs_props_table = zhp_orig->zfs_props_table;
+       return (zhp);
+}
+
+boolean_t
+zfs_bookmark_exists(const char *path)
+{
+       nvlist_t *bmarks;
+       nvlist_t *props;
+       char fsname[ZFS_MAX_DATASET_NAME_LEN];
+       char *bmark_name;
+       char *pound;
+       int err;
+       boolean_t rv;
+
+
+       (void) strlcpy(fsname, path, sizeof (fsname));
+       pound = strchr(fsname, '#');
+       if (pound == NULL)
+               return (B_FALSE);
+
+       *pound = '\0';
+       bmark_name = pound + 1;
+       props = fnvlist_alloc();
+       err = lzc_get_bookmarks(fsname, props, &bmarks);
+       nvlist_free(props);
+       if (err != 0) {
+               nvlist_free(bmarks);
+               return (B_FALSE);
+       }
+
+       rv = nvlist_exists(bmarks, bmark_name);
+       nvlist_free(bmarks);
+       return (rv);
+}
+
+zfs_handle_t *
+make_bookmark_handle(zfs_handle_t *parent, const char *path,
+    nvlist_t *bmark_props)
+{
+       zfs_handle_t *zhp = calloc(sizeof (zfs_handle_t), 1);
+
+       if (zhp == NULL)
+               return (NULL);
+
+       /* Fill in the name. */
+       zhp->zfs_hdl = parent->zfs_hdl;
+       (void) strlcpy(zhp->zfs_name, path, sizeof (zhp->zfs_name));
+
+       /* Set the property lists. */
+       if (nvlist_dup(bmark_props, &zhp->zfs_props, 0) != 0) {
+               free(zhp);
+               return (NULL);
+       }
+
+       /* Set the types. */
+       zhp->zfs_head_type = parent->zfs_head_type;
+       zhp->zfs_type = ZFS_TYPE_BOOKMARK;
+
+       if ((zhp->zpool_hdl = zpool_handle(zhp)) == NULL) {
+               nvlist_free(zhp->zfs_props);
+               free(zhp);
+               return (NULL);
+       }
+
+       return (zhp);
+}
+
+struct zfs_open_bookmarks_cb_data {
+       const char *path;
+       zfs_handle_t *zhp;
+};
+
+static int
+zfs_open_bookmarks_cb(zfs_handle_t *zhp, void *data)
+{
+       struct zfs_open_bookmarks_cb_data *dp = data;
+
+       /*
+        * Is it the one we are looking for?
+        */
+       if (strcmp(dp->path, zfs_get_name(zhp)) == 0) {
+               /*
+                * We found it.  Save it and let the caller know we are done.
+                */
+               dp->zhp = zhp;
+               return (EEXIST);
+       }
+
+       /*
+        * Not found.  Close the handle and ask for another one.
+        */
+       zfs_close(zhp);
+       return (0);
+}
+
 /*
- * Opens the given snapshot, filesystem, or volume.   The 'types'
+ * Opens the given snapshot, bookmark, filesystem, or volume.   The 'types'
  * argument is a mask of acceptable types.  The function will print an
  * appropriate error message and return NULL if it can't be opened.
  */
@@ -490,6 +671,7 @@ zfs_open(libzfs_handle_t *hdl, const char *path, int types)
 {
        zfs_handle_t *zhp;
        char errbuf[1024];
+       char *bookp;
 
        (void) snprintf(errbuf, sizeof (errbuf),
            dgettext(TEXT_DOMAIN, "cannot open '%s'"), path);
@@ -497,20 +679,68 @@ zfs_open(libzfs_handle_t *hdl, const char *path, int types)
        /*
         * Validate the name before we even try to open it.
         */
-       if (!zfs_validate_name(hdl, path, ZFS_TYPE_DATASET, B_FALSE)) {
-               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-                   "invalid dataset name"));
+       if (!zfs_validate_name(hdl, path, types, B_FALSE)) {
                (void) zfs_error(hdl, EZFS_INVALIDNAME, errbuf);
                return (NULL);
        }
 
        /*
-        * Try to get stats for the dataset, which will tell us if it exists.
+        * Bookmarks needs to be handled separately.
         */
-       errno = 0;
-       if ((zhp = make_dataset_handle(hdl, path)) == NULL) {
-               (void) zfs_standard_error(hdl, errno, errbuf);
-               return (NULL);
+       bookp = strchr(path, '#');
+       if (bookp == NULL) {
+               /*
+                * Try to get stats for the dataset, which will tell us if it
+                * exists.
+                */
+               errno = 0;
+               if ((zhp = make_dataset_handle(hdl, path)) == NULL) {
+                       (void) zfs_standard_error(hdl, errno, errbuf);
+                       return (NULL);
+               }
+       } else {
+               char dsname[ZFS_MAX_DATASET_NAME_LEN];
+               zfs_handle_t *pzhp;
+               struct zfs_open_bookmarks_cb_data cb_data = {path, NULL};
+
+               /*
+                * We need to cut out '#' and everything after '#'
+                * to get the parent dataset name only.
+                */
+               assert(bookp - path < sizeof (dsname));
+               (void) strncpy(dsname, path, bookp - path);
+               dsname[bookp - path] = '\0';
+
+               /*
+                * Create handle for the parent dataset.
+                */
+               errno = 0;
+               if ((pzhp = make_dataset_handle(hdl, dsname)) == NULL) {
+                       (void) zfs_standard_error(hdl, errno, errbuf);
+                       return (NULL);
+               }
+
+               /*
+                * Iterate bookmarks to find the right one.
+                */
+               errno = 0;
+               if ((zfs_iter_bookmarks(pzhp, zfs_open_bookmarks_cb,
+                   &cb_data) == 0) && (cb_data.zhp == NULL)) {
+                       (void) zfs_error(hdl, EZFS_NOENT, errbuf);
+                       zfs_close(pzhp);
+                       return (NULL);
+               }
+               if (cb_data.zhp == NULL) {
+                       (void) zfs_standard_error(hdl, errno, errbuf);
+                       zfs_close(pzhp);
+                       return (NULL);
+               }
+               zhp = cb_data.zhp;
+
+               /*
+                * Cleanup.
+                */
+               zfs_close(pzhp);
        }
 
        if (!(types & zhp->zfs_type)) {
@@ -544,15 +774,13 @@ typedef struct mnttab_node {
 static int
 libzfs_mnttab_cache_compare(const void *arg1, const void *arg2)
 {
-       const mnttab_node_t *mtn1 = arg1;
-       const mnttab_node_t *mtn2 = arg2;
+       const mnttab_node_t *mtn1 = (const mnttab_node_t *)arg1;
+       const mnttab_node_t *mtn2 = (const mnttab_node_t *)arg2;
        int rv;
 
        rv = strcmp(mtn1->mtn_mt.mnt_special, mtn2->mtn_mt.mnt_special);
 
-       if (rv == 0)
-               return (0);
-       return (rv > 0 ? 1 : -1);
+       return (AVL_ISIGN(rv));
 }
 
 void
@@ -563,24 +791,42 @@ libzfs_mnttab_init(libzfs_handle_t *hdl)
            sizeof (mnttab_node_t), offsetof(mnttab_node_t, mtn_node));
 }
 
-void
+int
 libzfs_mnttab_update(libzfs_handle_t *hdl)
 {
        struct mnttab entry;
 
-       rewind(hdl->libzfs_mnttab);
+       /* Reopen MNTTAB to prevent reading stale data from open file */
+       if (freopen(MNTTAB, "r", hdl->libzfs_mnttab) == NULL)
+               return (ENOENT);
+
        while (getmntent(hdl->libzfs_mnttab, &entry) == 0) {
                mnttab_node_t *mtn;
+               avl_index_t where;
 
                if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0)
                        continue;
+
                mtn = zfs_alloc(hdl, sizeof (mnttab_node_t));
                mtn->mtn_mt.mnt_special = zfs_strdup(hdl, entry.mnt_special);
                mtn->mtn_mt.mnt_mountp = zfs_strdup(hdl, entry.mnt_mountp);
                mtn->mtn_mt.mnt_fstype = zfs_strdup(hdl, entry.mnt_fstype);
                mtn->mtn_mt.mnt_mntopts = zfs_strdup(hdl, entry.mnt_mntopts);
+
+               /* Exclude duplicate mounts */
+               if (avl_find(&hdl->libzfs_mnttab_cache, mtn, &where) != NULL) {
+                       free(mtn->mtn_mt.mnt_special);
+                       free(mtn->mtn_mt.mnt_mountp);
+                       free(mtn->mtn_mt.mnt_fstype);
+                       free(mtn->mtn_mt.mnt_mntopts);
+                       free(mtn);
+                       continue;
+               }
+
                avl_add(&hdl->libzfs_mnttab_cache, mtn);
        }
+
+       return (0);
 }
 
 void
@@ -589,7 +835,8 @@ 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))
+           != NULL) {
                free(mtn->mtn_mt.mnt_special);
                free(mtn->mtn_mt.mnt_mountp);
                free(mtn->mtn_mt.mnt_fstype);
@@ -611,13 +858,18 @@ libzfs_mnttab_find(libzfs_handle_t *hdl, const char *fsname,
 {
        mnttab_node_t find;
        mnttab_node_t *mtn;
+       int error;
 
        if (!hdl->libzfs_mnttab_enable) {
                struct mnttab srch = { 0 };
 
                if (avl_numnodes(&hdl->libzfs_mnttab_cache))
                        libzfs_mnttab_fini(hdl);
-               rewind(hdl->libzfs_mnttab);
+
+               /* Reopen MNTTAB to prevent reading stale data from open file */
+               if (freopen(MNTTAB, "r", hdl->libzfs_mnttab) == NULL)
+                       return (ENOENT);
+
                srch.mnt_special = (char *)fsname;
                srch.mnt_fstype = MNTTYPE_ZFS;
                if (getmntany(hdl->libzfs_mnttab, entry, &srch) == 0)
@@ -627,7 +879,8 @@ libzfs_mnttab_find(libzfs_handle_t *hdl, const char *fsname,
        }
 
        if (avl_numnodes(&hdl->libzfs_mnttab_cache) == 0)
-               libzfs_mnttab_update(hdl);
+               if ((error = libzfs_mnttab_update(hdl)) != 0)
+                       return (error);
 
        find.mtn_mt.mnt_special = (char *)fsname;
        mtn = avl_find(&hdl->libzfs_mnttab_cache, &find, NULL);
@@ -661,7 +914,8 @@ 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))
+           != NULL) {
                avl_remove(&hdl->libzfs_mnttab_cache, ret);
                free(ret->mtn_mt.mnt_special);
                free(ret->mtn_mt.mnt_mountp);
@@ -710,7 +964,8 @@ zfs_which_resv_prop(zfs_handle_t *zhp, zfs_prop_t *resv_prop)
  */
 nvlist_t *
 zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl,
-    uint64_t zoned, zfs_handle_t *zhp, const char *errbuf)
+    uint64_t zoned, zfs_handle_t *zhp, zpool_handle_t *zpool_hdl,
+    const char *errbuf)
 {
        nvpair_t *elem;
        uint64_t intval;
@@ -791,7 +1046,9 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl,
                        }
 
                        if (uqtype != ZFS_PROP_USERQUOTA &&
-                           uqtype != ZFS_PROP_GROUPQUOTA) {
+                           uqtype != ZFS_PROP_GROUPQUOTA &&
+                           uqtype != ZFS_PROP_USEROBJQUOTA &&
+                           uqtype != ZFS_PROP_GROUPOBJQUOTA) {
                                zfs_error_aux(hdl,
                                    dgettext(TEXT_DOMAIN, "'%s' is readonly"),
                                    propname);
@@ -843,6 +1100,12 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl,
                                goto error;
                        }
                        continue;
+               } else if (prop == ZPROP_INVAL && zfs_prop_written(propname)) {
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "'%s' is readonly"),
+                           propname);
+                       (void) zfs_error(hdl, EZFS_PROPREADONLY, errbuf);
+                       goto error;
                }
 
                if (prop == ZPROP_INVAL) {
@@ -852,7 +1115,7 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl,
                        goto error;
                }
 
-               if (!zfs_prop_valid_for_type(prop, type)) {
+               if (!zfs_prop_valid_for_type(prop, type, B_FALSE)) {
                        zfs_error_aux(hdl,
                            dgettext(TEXT_DOMAIN, "'%s' does not "
                            "apply to datasets of this type"), propname);
@@ -894,21 +1157,31 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl,
                        break;
                }
 
-               case ZFS_PROP_RECORDSIZE:
                case ZFS_PROP_VOLBLOCKSIZE:
-                       /* must be power of two within SPA_{MIN,MAX}BLOCKSIZE */
+               case ZFS_PROP_RECORDSIZE:
+               {
+                       int maxbs = SPA_MAXBLOCKSIZE;
+                       char buf[64];
+
+                       if (zpool_hdl != NULL) {
+                               maxbs = zpool_get_prop_int(zpool_hdl,
+                                   ZPOOL_PROP_MAXBLOCKSIZE, NULL);
+                       }
+                       /*
+                        * The value must be a power of two between
+                        * SPA_MINBLOCKSIZE and maxbs.
+                        */
                        if (intval < SPA_MINBLOCKSIZE ||
-                           intval > SPA_MAXBLOCKSIZE || !ISP2(intval)) {
+                           intval > maxbs || !ISP2(intval)) {
+                               zfs_nicebytes(maxbs, buf, sizeof (buf));
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-                                   "'%s' must be power of 2 from %u "
-                                   "to %uk"), propname,
-                                   (uint_t)SPA_MINBLOCKSIZE,
-                                   (uint_t)SPA_MAXBLOCKSIZE >> 10);
+                                   "'%s' must be power of 2 from 512B "
+                                   "to %s"), propname, buf);
                                (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
                                goto error;
                        }
                        break;
-
+               }
                case ZFS_PROP_MLSLABEL:
                {
 #ifdef HAVE_MLSLABEL
@@ -991,7 +1264,12 @@ badlabel:
                                            "component of '%s' is too long"),
                                            propname);
                                        break;
+
                                default:
+                                       zfs_error_aux(hdl,
+                                           dgettext(TEXT_DOMAIN,
+                                           "(%d) not defined"),
+                                           why);
                                        break;
                                }
                                (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
@@ -1111,12 +1389,15 @@ badlabel:
                        }
 
                        break;
+
                case ZFS_PROP_UTF8ONLY:
                        chosen_utf = (int)intval;
                        break;
+
                case ZFS_PROP_NORMALIZE:
                        chosen_normal = (int)intval;
                        break;
+
                default:
                        break;
                }
@@ -1147,7 +1428,7 @@ badlabel:
 
                        case ZFS_PROP_VOLSIZE:
                                if (intval % blocksize != 0) {
-                                       zfs_nicenum(blocksize, buf,
+                                       zfs_nicebytes(blocksize, buf,
                                            sizeof (buf));
                                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                                            "'%s' must be a multiple of "
@@ -1167,6 +1448,7 @@ badlabel:
                                        goto error;
                                }
                                break;
+
                        default:
                                break;
                        }
@@ -1208,6 +1490,7 @@ zfs_add_synthetic_resv(zfs_handle_t *zhp, nvlist_t *nvl)
        uint64_t old_reservation;
        uint64_t new_reservation;
        zfs_prop_t resv_prop;
+       nvlist_t *props;
 
        /*
         * If this is an existing volume, and someone is setting the volsize,
@@ -1217,16 +1500,25 @@ zfs_add_synthetic_resv(zfs_handle_t *zhp, nvlist_t *nvl)
        if (zfs_which_resv_prop(zhp, &resv_prop) < 0)
                return (-1);
        old_reservation = zfs_prop_get_int(zhp, resv_prop);
-       if ((zvol_volsize_to_reservation(old_volsize, zhp->zfs_props) !=
-           old_reservation) || nvlist_lookup_uint64(nvl,
-           zfs_prop_to_name(resv_prop), &new_reservation) != ENOENT) {
+
+       props = fnvlist_alloc();
+       fnvlist_add_uint64(props, zfs_prop_to_name(ZFS_PROP_VOLBLOCKSIZE),
+           zfs_prop_get_int(zhp, ZFS_PROP_VOLBLOCKSIZE));
+
+       if ((zvol_volsize_to_reservation(old_volsize, props) !=
+           old_reservation) || nvlist_exists(nvl,
+           zfs_prop_to_name(resv_prop))) {
+               fnvlist_free(props);
                return (0);
        }
        if (nvlist_lookup_uint64(nvl, zfs_prop_to_name(ZFS_PROP_VOLSIZE),
-           &new_volsize) != 0)
+           &new_volsize) != 0) {
+               fnvlist_free(props);
                return (-1);
-       new_reservation = zvol_volsize_to_reservation(new_volsize,
-           zhp->zfs_props);
+       }
+       new_reservation = zvol_volsize_to_reservation(new_volsize, props);
+       fnvlist_free(props);
+
        if (nvlist_add_uint64(nvl, zfs_prop_to_name(resv_prop),
            new_reservation) != 0) {
                (void) no_memory(zhp->zfs_hdl);
@@ -1277,6 +1569,12 @@ zfs_setprop_error(libzfs_handle_t *hdl, zfs_prop_t prop, int err,
                (void) zfs_error(hdl, EZFS_DSREADONLY, errbuf);
                break;
 
+       case E2BIG:
+               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                   "property value too long"));
+               (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+               break;
+
        case ENOTSUP:
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "pool and or dataset must be upgraded to set this "
@@ -1285,11 +1583,19 @@ zfs_setprop_error(libzfs_handle_t *hdl, zfs_prop_t prop, int err,
                break;
 
        case ERANGE:
-               if (prop == ZFS_PROP_COMPRESSION) {
+               if (prop == ZFS_PROP_COMPRESSION ||
+                   prop == ZFS_PROP_DNODESIZE ||
+                   prop == ZFS_PROP_RECORDSIZE) {
                        (void) zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "property setting is not allowed on "
                            "bootable datasets"));
                        (void) zfs_error(hdl, EZFS_NOTSUP, errbuf);
+               } else if (prop == ZFS_PROP_CHECKSUM ||
+                   prop == ZFS_PROP_DEDUP) {
+                       (void) zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "property setting is not allowed on "
+                           "root pools"));
+                       (void) zfs_error(hdl, EZFS_NOTSUP, errbuf);
                } else {
                        (void) zfs_standard_error(hdl, err, errbuf);
                }
@@ -1325,6 +1631,7 @@ zfs_is_namespace_prop(zfs_prop_t prop)
        switch (prop) {
 
        case ZFS_PROP_ATIME:
+       case ZFS_PROP_RELATIME:
        case ZFS_PROP_DEVICES:
        case ZFS_PROP_EXEC:
        case ZFS_PROP_SETUID:
@@ -1344,16 +1651,10 @@ zfs_is_namespace_prop(zfs_prop_t prop)
 int
 zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval)
 {
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        int ret = -1;
-       prop_changelist_t *cl = NULL;
        char errbuf[1024];
        libzfs_handle_t *hdl = zhp->zfs_hdl;
-       nvlist_t *nvl = NULL, *realprops;
-       zfs_prop_t prop;
-       boolean_t do_prefix;
-       uint64_t idx;
-       int added_resv = 0;
+       nvlist_t *nvl = NULL;
 
        (void) snprintf(errbuf, sizeof (errbuf),
            dgettext(TEXT_DOMAIN, "cannot set property for '%s'"),
@@ -1365,60 +1666,136 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval)
                goto error;
        }
 
-       if ((realprops = zfs_valid_proplist(hdl, zhp->zfs_type, nvl,
-           zfs_prop_get_int(zhp, ZFS_PROP_ZONED), zhp, errbuf)) == NULL)
-               goto error;
+       ret = zfs_prop_set_list(zhp, nvl);
 
+error:
        nvlist_free(nvl);
-       nvl = realprops;
+       return (ret);
+}
 
-       prop = zfs_name_to_prop(propname);
 
-       if (prop == ZFS_PROP_VOLSIZE) {
-               if ((added_resv = zfs_add_synthetic_resv(zhp, nvl)) == -1)
-                       goto error;
-       }
 
-       if ((cl = changelist_gather(zhp, prop, 0, 0)) == NULL)
-               goto error;
+/*
+ * Given an nvlist of property names and values, set the properties for the
+ * given dataset.
+ */
+int
+zfs_prop_set_list(zfs_handle_t *zhp, nvlist_t *props)
+{
+       zfs_cmd_t zc = {"\0"};
+       int ret = -1;
+       prop_changelist_t **cls = NULL;
+       int cl_idx;
+       char errbuf[1024];
+       libzfs_handle_t *hdl = zhp->zfs_hdl;
+       nvlist_t *nvl;
+       int nvl_len = 0;
+       int added_resv = 0;
+       zfs_prop_t prop = 0;
+       nvpair_t *elem;
 
-       if (prop == ZFS_PROP_MOUNTPOINT && changelist_haszonedchild(cl)) {
-               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-                   "child dataset with inherited mountpoint is used "
-                   "in a non-global zone"));
-               ret = zfs_error(hdl, EZFS_ZONED, errbuf);
+       (void) snprintf(errbuf, sizeof (errbuf),
+           dgettext(TEXT_DOMAIN, "cannot set property for '%s'"),
+           zhp->zfs_name);
+
+       if ((nvl = zfs_valid_proplist(hdl, zhp->zfs_type, props,
+           zfs_prop_get_int(zhp, ZFS_PROP_ZONED), zhp, zhp->zpool_hdl,
+           errbuf)) == NULL)
                goto error;
-       }
 
        /*
-        * If the dataset's canmount property is being set to noauto,
-        * then we want to prevent unmounting & remounting it.
+        * We have to check for any extra properties which need to be added
+        * before computing the length of the nvlist.
         */
-       do_prefix = !((prop == ZFS_PROP_CANMOUNT) &&
-           (zprop_string_to_index(prop, propval, &idx,
-           ZFS_TYPE_DATASET) == 0) && (idx == ZFS_CANMOUNT_NOAUTO));
-
-       if (do_prefix && (ret = changelist_prefix(cl)) != 0)
+       for (elem = nvlist_next_nvpair(nvl, NULL);
+           elem != NULL;
+           elem = nvlist_next_nvpair(nvl, elem)) {
+               if (zfs_name_to_prop(nvpair_name(elem)) == ZFS_PROP_VOLSIZE &&
+                   (added_resv = zfs_add_synthetic_resv(zhp, nvl)) == -1) {
+                       goto error;
+               }
+       }
+       /*
+        * Check how many properties we're setting and allocate an array to
+        * store changelist pointers for postfix().
+        */
+       for (elem = nvlist_next_nvpair(nvl, NULL);
+           elem != NULL;
+           elem = nvlist_next_nvpair(nvl, elem))
+               nvl_len++;
+       if ((cls = calloc(nvl_len, sizeof (prop_changelist_t *))) == NULL)
                goto error;
 
+       cl_idx = 0;
+       for (elem = nvlist_next_nvpair(nvl, NULL);
+           elem != NULL;
+           elem = nvlist_next_nvpair(nvl, elem)) {
+
+               prop = zfs_name_to_prop(nvpair_name(elem));
+
+               assert(cl_idx < nvl_len);
+               /*
+                * We don't want to unmount & remount the dataset when changing
+                * its canmount property to 'on' or 'noauto'.  We only use
+                * the changelist logic to unmount when setting canmount=off.
+                */
+               if (prop != ZFS_PROP_CANMOUNT ||
+                   (fnvpair_value_uint64(elem) == ZFS_CANMOUNT_OFF &&
+                   zfs_is_mounted(zhp, NULL))) {
+                       cls[cl_idx] = changelist_gather(zhp, prop, 0, 0);
+                       if (cls[cl_idx] == NULL)
+                               goto error;
+               }
+
+               if (prop == ZFS_PROP_MOUNTPOINT &&
+                   changelist_haszonedchild(cls[cl_idx])) {
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "child dataset with inherited mountpoint is used "
+                           "in a non-global zone"));
+                       ret = zfs_error(hdl, EZFS_ZONED, errbuf);
+                       goto error;
+               }
+
+               if (cls[cl_idx] != NULL &&
+                   (ret = changelist_prefix(cls[cl_idx])) != 0)
+                       goto error;
+
+               cl_idx++;
+       }
+       assert(cl_idx == nvl_len);
+
        /*
-        * Execute the corresponding ioctl() to set this property.
+        * Execute the corresponding ioctl() to set this list of properties.
         */
        (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
 
-       if (zcmd_write_src_nvlist(hdl, &zc, nvl) != 0)
+       if ((ret = zcmd_write_src_nvlist(hdl, &zc, nvl)) != 0 ||
+           (ret = zcmd_alloc_dst_nvlist(hdl, &zc, 0)) != 0)
                goto error;
 
        ret = zfs_ioctl(hdl, ZFS_IOC_SET_PROP, &zc);
 
        if (ret != 0) {
-               zfs_setprop_error(hdl, prop, errno, errbuf);
+               /* Get the list of unset properties back and report them. */
+               nvlist_t *errorprops = NULL;
+               if (zcmd_read_dst_nvlist(hdl, &zc, &errorprops) != 0)
+                       goto error;
+               for (elem = nvlist_next_nvpair(nvl, NULL);
+                   elem != NULL;
+                   elem = nvlist_next_nvpair(nvl, elem)) {
+                       prop = zfs_name_to_prop(nvpair_name(elem));
+                       zfs_setprop_error(hdl, prop, errno, errbuf);
+               }
+               nvlist_free(errorprops);
+
                if (added_resv && errno == ENOSPC) {
                        /* clean up the volsize property we tried to set */
                        uint64_t old_volsize = zfs_prop_get_int(zhp,
                            ZFS_PROP_VOLSIZE);
                        nvlist_free(nvl);
+                       nvl = NULL;
                        zcmd_free_nvlists(&zc);
+
                        if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0)
                                goto error;
                        if (nvlist_add_uint64(nvl,
@@ -1430,8 +1807,13 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval)
                        (void) zfs_ioctl(hdl, ZFS_IOC_SET_PROP, &zc);
                }
        } else {
-               if (do_prefix)
-                       ret = changelist_postfix(cl);
+               for (cl_idx = 0; cl_idx < nvl_len; cl_idx++) {
+                       if (cls[cl_idx] != NULL) {
+                               int clp_err = changelist_postfix(cls[cl_idx]);
+                               if (clp_err != 0)
+                                       ret = clp_err;
+                       }
+               }
 
                if (ret == 0) {
                        /*
@@ -1454,8 +1836,13 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval)
 error:
        nvlist_free(nvl);
        zcmd_free_nvlists(&zc);
-       if (cl)
-               changelist_free(cl);
+       if (cls != NULL) {
+               for (cl_idx = 0; cl_idx < nvl_len; cl_idx++) {
+                       if (cls[cl_idx] != NULL)
+                               changelist_free(cls[cl_idx]);
+               }
+               free(cls);
+       }
        return (ret);
 }
 
@@ -1466,7 +1853,7 @@ error:
 int
 zfs_prop_inherit(zfs_handle_t *zhp, const char *propname, boolean_t received)
 {
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       zfs_cmd_t zc = {"\0"};
        int ret;
        prop_changelist_t *cl;
        libzfs_handle_t *hdl = zhp->zfs_hdl;
@@ -1509,7 +1896,7 @@ zfs_prop_inherit(zfs_handle_t *zhp, const char *propname, boolean_t received)
        /*
         * Check to see if the value applies to this type
         */
-       if (!zfs_prop_valid_for_type(prop, zhp->zfs_type))
+       if (!zfs_prop_valid_for_type(prop, zhp->zfs_type, B_FALSE))
                return (zfs_error(hdl, EZFS_PROPTYPE, errbuf));
 
        /*
@@ -1554,6 +1941,15 @@ zfs_prop_inherit(zfs_handle_t *zhp, const char *propname, boolean_t received)
                 * Refresh the statistics so the new property 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:
@@ -1586,22 +1982,21 @@ getprop_uint64(zfs_handle_t *zhp, zfs_prop_t prop, char **source)
        return (value);
 }
 
-static char *
+static const char *
 getprop_string(zfs_handle_t *zhp, zfs_prop_t prop, char **source)
 {
        nvlist_t *nv;
-       char *value;
+       const char *value;
 
        *source = NULL;
        if (nvlist_lookup_nvlist(zhp->zfs_props,
            zfs_prop_to_name(prop), &nv) == 0) {
-               verify(nvlist_lookup_string(nv, ZPROP_VALUE, &value) == 0);
+               value = fnvlist_lookup_string(nv, ZPROP_VALUE);
                (void) nvlist_lookup_string(nv, ZPROP_SOURCE, source);
        } else {
                verify(!zhp->zfs_props_table ||
                    zhp->zfs_props_table[prop] == B_TRUE);
-               if ((value = (char *)zfs_prop_default_string(prop)) == NULL)
-                       value = "";
+               value = zfs_prop_default_string(prop);
                *source = "";
        }
 
@@ -1633,15 +2028,15 @@ 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/mtab entry, searching for the appropriate options.
- * If they differ from the on-disk values, report the current values and mark
- * the source "temporary".
+ * the contents of the /proc/self/mounts entry, searching for the
+ * appropriate options. If they differ from the on-disk values, report the
+ * current values and mark the source "temporary".
  */
 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", "\0", "\0", "\0", 0 };
+       zfs_cmd_t zc = {"\0"};
        nvlist_t *zplprops = NULL;
        struct mnttab mnt;
        char *mntopt_on = NULL;
@@ -1650,12 +2045,27 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src,
 
        *source = NULL;
 
+       /*
+        * If the property is being fetched for a snapshot, check whether
+        * the property is valid for the snapshot's head dataset type.
+        */
+       if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT &&
+           !zfs_prop_valid_for_type(prop, zhp->zfs_head_type, B_TRUE)) {
+               *val = zfs_prop_default_numeric(prop);
+               return (-1);
+       }
+
        switch (prop) {
        case ZFS_PROP_ATIME:
                mntopt_on = MNTOPT_ATIME;
                mntopt_off = MNTOPT_NOATIME;
                break;
 
+       case ZFS_PROP_RELATIME:
+               mntopt_on = MNTOPT_RELATIME;
+               mntopt_off = MNTOPT_NORELATIME;
+               break;
+
        case ZFS_PROP_DEVICES:
                mntopt_on = MNTOPT_DEVICES;
                mntopt_off = MNTOPT_NODEVICES;
@@ -1685,14 +2095,16 @@ 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/mtab), we defer its calculation until
-        * we're looking up a property which requires its presence.
+        * (iterating over all of /proc/self/mounts), we defer its
+        * calculation until we're looking up a property which requires
+        * its presence.
         */
        if (!zhp->zfs_mntcheck &&
            (mntopt_on != NULL || prop == ZFS_PROP_MOUNTED)) {
@@ -1716,6 +2128,7 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src,
 
        switch (prop) {
        case ZFS_PROP_ATIME:
+       case ZFS_PROP_RELATIME:
        case ZFS_PROP_DEVICES:
        case ZFS_PROP_EXEC:
        case ZFS_PROP_READONLY:
@@ -1744,6 +2157,10 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src,
        case ZFS_PROP_REFQUOTA:
        case ZFS_PROP_RESERVATION:
        case ZFS_PROP_REFRESERVATION:
+       case ZFS_PROP_FILESYSTEM_LIMIT:
+       case ZFS_PROP_SNAPSHOT_LIMIT:
+       case ZFS_PROP_FILESYSTEM_COUNT:
+       case ZFS_PROP_SNAPSHOT_COUNT:
                *val = getprop_uint64(zhp, prop, source);
 
                if (*source == NULL) {
@@ -1764,12 +2181,14 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src,
        case ZFS_PROP_NORMALIZE:
        case ZFS_PROP_UTF8ONLY:
        case ZFS_PROP_CASE:
-               if (!zfs_prop_valid_for_type(prop, zhp->zfs_head_type) ||
-                   zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0)
+               if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0)
                        return (-1);
                (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
                if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_OBJSET_ZPLPROPS, &zc)) {
                        zcmd_free_nvlists(&zc);
+                       if (prop == ZFS_PROP_VERSION &&
+                           zhp->zfs_type == ZFS_TYPE_VOLUME)
+                               *val = zfs_prop_default_numeric(prop);
                        return (-1);
                }
                if (zcmd_read_dst_nvlist(zhp->zfs_hdl, &zc, &zplprops) != 0 ||
@@ -1778,11 +2197,14 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src,
                        zcmd_free_nvlists(&zc);
                        return (-1);
                }
-               if (zplprops)
-                       nvlist_free(zplprops);
+               nvlist_free(zplprops);
                zcmd_free_nvlists(&zc);
                break;
 
+       case ZFS_PROP_INCONSISTENT:
+               *val = zhp->zfs_dmustats.dds_inconsistent;
+               break;
+
        default:
                switch (zfs_prop_get_type(prop)) {
                case PROP_TYPE_NUMBER:
@@ -1791,11 +2213,15 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src,
                        /*
                         * If we tried to use a default value for a
                         * readonly property, it means that it was not
-                        * present.
+                        * present.  Note this only applies to "truly"
+                        * readonly properties, not set-once properties
+                        * like volblocksize.
                         */
                        if (zfs_prop_readonly(prop) &&
+                           !zfs_prop_setonce(prop) &&
                            *source != NULL && (*source)[0] == '\0') {
                                *source = NULL;
+                               return (-1);
                        }
                        break;
 
@@ -1818,8 +2244,10 @@ static void
 get_source(zfs_handle_t *zhp, zprop_source_t *srctype, char *source,
     char *statbuf, size_t statlen)
 {
-       if (statbuf == NULL || *srctype == ZPROP_SRC_TEMPORARY)
+       if (statbuf == NULL ||
+           srctype == NULL || *srctype == ZPROP_SRC_TEMPORARY) {
                return;
+       }
 
        if (source == NULL) {
                *srctype = ZPROP_SRC_NONE;
@@ -1859,8 +2287,6 @@ zfs_prop_get_recvd(zfs_handle_t *zhp, const char *propname, char *propbuf,
                err = zfs_prop_get(zhp, prop, propbuf, proplen,
                    NULL, NULL, 0, literal);
                zfs_unset_recvd_props_mode(zhp, &cookie);
-       } else if (zfs_prop_userquota(propname)) {
-               return (-1);
        } else {
                nvlist_t *propval;
                char *recvdval;
@@ -1875,6 +2301,122 @@ zfs_prop_get_recvd(zfs_handle_t *zhp, const char *propname, char *propbuf,
        return (err == 0 ? 0 : -1);
 }
 
+static int
+get_clones_string(zfs_handle_t *zhp, char *propbuf, size_t proplen)
+{
+       nvlist_t *value;
+       nvpair_t *pair;
+
+       value = zfs_get_clones_nvl(zhp);
+       if (value == NULL)
+               return (-1);
+
+       propbuf[0] = '\0';
+       for (pair = nvlist_next_nvpair(value, NULL); pair != NULL;
+           pair = nvlist_next_nvpair(value, pair)) {
+               if (propbuf[0] != '\0')
+                       (void) strlcat(propbuf, ",", proplen);
+               (void) strlcat(propbuf, nvpair_name(pair), proplen);
+       }
+
+       return (0);
+}
+
+struct get_clones_arg {
+       uint64_t numclones;
+       nvlist_t *value;
+       const char *origin;
+       char buf[ZFS_MAX_DATASET_NAME_LEN];
+};
+
+int
+get_clones_cb(zfs_handle_t *zhp, void *arg)
+{
+       struct get_clones_arg *gca = arg;
+
+       if (gca->numclones == 0) {
+               zfs_close(zhp);
+               return (0);
+       }
+
+       if (zfs_prop_get(zhp, ZFS_PROP_ORIGIN, gca->buf, sizeof (gca->buf),
+           NULL, NULL, 0, B_TRUE) != 0)
+               goto out;
+       if (strcmp(gca->buf, gca->origin) == 0) {
+               fnvlist_add_boolean(gca->value, zfs_get_name(zhp));
+               gca->numclones--;
+       }
+
+out:
+       (void) zfs_iter_children(zhp, get_clones_cb, gca);
+       zfs_close(zhp);
+       return (0);
+}
+
+nvlist_t *
+zfs_get_clones_nvl(zfs_handle_t *zhp)
+{
+       nvlist_t *nv, *value;
+
+       if (nvlist_lookup_nvlist(zhp->zfs_props,
+           zfs_prop_to_name(ZFS_PROP_CLONES), &nv) != 0) {
+               struct get_clones_arg gca;
+
+               /*
+                * if this is a snapshot, then the kernel wasn't able
+                * to get the clones.  Do it by slowly iterating.
+                */
+               if (zhp->zfs_type != ZFS_TYPE_SNAPSHOT)
+                       return (NULL);
+               if (nvlist_alloc(&nv, NV_UNIQUE_NAME, 0) != 0)
+                       return (NULL);
+               if (nvlist_alloc(&value, NV_UNIQUE_NAME, 0) != 0) {
+                       nvlist_free(nv);
+                       return (NULL);
+               }
+
+               gca.numclones = zfs_prop_get_int(zhp, ZFS_PROP_NUMCLONES);
+               gca.value = value;
+               gca.origin = zhp->zfs_name;
+
+               if (gca.numclones != 0) {
+                       zfs_handle_t *root;
+                       char pool[ZFS_MAX_DATASET_NAME_LEN];
+                       char *cp = pool;
+
+                       /* get the pool name */
+                       (void) strlcpy(pool, zhp->zfs_name, sizeof (pool));
+                       (void) strsep(&cp, "/@");
+                       root = zfs_open(zhp->zfs_hdl, pool,
+                           ZFS_TYPE_FILESYSTEM);
+                       if (root == NULL) {
+                               nvlist_free(nv);
+                               nvlist_free(value);
+                               return (NULL);
+                       }
+
+                       (void) get_clones_cb(root, &gca);
+               }
+
+               if (gca.numclones != 0 ||
+                   nvlist_add_nvlist(nv, ZPROP_VALUE, value) != 0 ||
+                   nvlist_add_nvlist(zhp->zfs_props,
+                   zfs_prop_to_name(ZFS_PROP_CLONES), nv) != 0) {
+                       nvlist_free(nv);
+                       nvlist_free(value);
+                       return (NULL);
+               }
+               nvlist_free(nv);
+               nvlist_free(value);
+               verify(0 == nvlist_lookup_nvlist(zhp->zfs_props,
+                   zfs_prop_to_name(ZFS_PROP_CLONES), &nv));
+       }
+
+       verify(nvlist_lookup_nvlist(nv, ZPROP_VALUE, &value) == 0);
+
+       return (value);
+}
+
 /*
  * Retrieve a property from the given object.  If 'literal' is specified, then
  * numbers are left as exact values.  Otherwise, numbers are converted to a
@@ -1888,14 +2430,14 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
 {
        char *source = NULL;
        uint64_t val;
-       char *str;
+       const char *str;
        const char *strval;
        boolean_t received = zfs_is_recvd_props_mode(zhp);
 
        /*
         * Check to see if this property applies to our object
         */
-       if (!zfs_prop_valid_for_type(prop, zhp->zfs_type))
+       if (!zfs_prop_valid_for_type(prop, zhp->zfs_type, B_FALSE))
                return (-1);
 
        if (received && zfs_prop_readonly(prop))
@@ -1919,7 +2461,8 @@ 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", (u_longlong_t) val);
+                               (void) snprintf(propbuf, proplen, "%llu",
+                                   (u_longlong_t)val);
                }
                break;
 
@@ -1958,8 +2501,8 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
                        }
 
                        if ((zpool_get_prop(zhp->zpool_hdl,
-                           ZPOOL_PROP_ALTROOT, buf, MAXPATHLEN, NULL)) ||
-                           (strcmp(root, "-") == 0))
+                           ZPOOL_PROP_ALTROOT, buf, MAXPATHLEN, NULL,
+                           B_FALSE)) || (strcmp(root, "-") == 0))
                                root[0] = '\0';
                        /*
                         * Special case an alternate root of '/'. This will
@@ -1993,13 +2536,14 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
                break;
 
        case ZFS_PROP_ORIGIN:
-               (void) strlcpy(propbuf, getprop_string(zhp, prop, &source),
-                   proplen);
-               /*
-                * If there is no parent at all, return failure to indicate that
-                * it doesn't apply to this dataset.
-                */
-               if (propbuf[0] == '\0')
+               str = getprop_string(zhp, prop, &source);
+               if (str == NULL)
+                       return (-1);
+               (void) strlcpy(propbuf, str, proplen);
+               break;
+
+       case ZFS_PROP_CLONES:
+               if (get_clones_string(zhp, propbuf, proplen) != 0)
                        return (-1);
                break;
 
@@ -2027,7 +2571,31 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
                                (void) snprintf(propbuf, proplen, "%llu",
                                    (u_longlong_t)val);
                        else
-                               zfs_nicenum(val, propbuf, proplen);
+                               zfs_nicebytes(val, propbuf, proplen);
+               }
+               break;
+
+       case ZFS_PROP_FILESYSTEM_LIMIT:
+       case ZFS_PROP_SNAPSHOT_LIMIT:
+       case ZFS_PROP_FILESYSTEM_COUNT:
+       case ZFS_PROP_SNAPSHOT_COUNT:
+
+               if (get_numeric_property(zhp, prop, src, &source, &val) != 0)
+                       return (-1);
+
+               /*
+                * If limit is UINT64_MAX, we translate this into 'none' (unless
+                * literal is set), and indicate that it's the default value.
+                * Otherwise, we print the number nicely and indicate that it's
+                * set locally.
+                */
+               if (literal) {
+                       (void) snprintf(propbuf, proplen, "%llu",
+                           (u_longlong_t)val);
+               } else if (val == UINT64_MAX) {
+                       (void) strlcpy(propbuf, "none", proplen);
+               } else {
+                       zfs_nicenum(val, propbuf, proplen);
                }
                break;
 
@@ -2051,6 +2619,9 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
                case ZFS_TYPE_SNAPSHOT:
                        str = "snapshot";
                        break;
+               case ZFS_TYPE_BOOKMARK:
+                       str = "bookmark";
+                       break;
                default:
                        abort();
                }
@@ -2125,6 +2696,34 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
                }
                break;
 
+       case ZFS_PROP_GUID:
+       case ZFS_PROP_CREATETXG:
+               /*
+                * GUIDs are stored as numbers, but they are identifiers.
+                * We don't want them to be pretty printed, because pretty
+                * printing mangles the ID into a truncated and useless value.
+                */
+               if (get_numeric_property(zhp, prop, src, &source, &val) != 0)
+                       return (-1);
+               (void) snprintf(propbuf, proplen, "%llu", (u_longlong_t)val);
+               break;
+
+       case ZFS_PROP_REFERENCED:
+       case ZFS_PROP_AVAILABLE:
+       case ZFS_PROP_USED:
+       case ZFS_PROP_USEDSNAP:
+       case ZFS_PROP_USEDDS:
+       case ZFS_PROP_USEDREFRESERV:
+       case ZFS_PROP_USEDCHILD:
+               if (get_numeric_property(zhp, prop, src, &source, &val) != 0)
+                       return (-1);
+               if (literal)
+                       (void) snprintf(propbuf, proplen, "%llu",
+                           (u_longlong_t)val);
+               else
+                       zfs_nicebytes(val, propbuf, proplen);
+               break;
+
        default:
                switch (zfs_prop_get_type(prop)) {
                case PROP_TYPE_NUMBER:
@@ -2139,8 +2738,10 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
                        break;
 
                case PROP_TYPE_STRING:
-                       (void) strlcpy(propbuf,
-                           getprop_string(zhp, prop, &source), proplen);
+                       str = getprop_string(zhp, prop, &source);
+                       if (str == NULL)
+                               return (-1);
+                       (void) strlcpy(propbuf, str, proplen);
                        break;
 
                case PROP_TYPE_INDEX:
@@ -2171,7 +2772,7 @@ uint64_t
 zfs_prop_get_int(zfs_handle_t *zhp, zfs_prop_t prop)
 {
        char *source;
-       uint64_t val;
+       uint64_t val = 0;
 
        (void) get_numeric_property(zhp, prop, NULL, &source, &val);
 
@@ -2199,7 +2800,7 @@ zfs_prop_get_numeric(zfs_handle_t *zhp, zfs_prop_t prop, uint64_t *value,
        /*
         * Check to see if this property applies to our object
         */
-       if (!zfs_prop_valid_for_type(prop, zhp->zfs_type)) {
+       if (!zfs_prop_valid_for_type(prop, zhp->zfs_type, B_FALSE)) {
                return (zfs_error_fmt(zhp->zfs_hdl, EZFS_PROPTYPE,
                    dgettext(TEXT_DOMAIN, "cannot get property '%s'"),
                    zfs_prop_to_name(prop)));
@@ -2278,8 +2879,12 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
                return (EINVAL);
        *typep = type;
 
-       isuser = (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_USERUSED);
-       isgroup = (type == ZFS_PROP_GROUPQUOTA || type == ZFS_PROP_GROUPUSED);
+       isuser = (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_USERUSED ||
+           type == ZFS_PROP_USEROBJQUOTA ||
+           type == ZFS_PROP_USEROBJUSED);
+       isgroup = (type == ZFS_PROP_GROUPQUOTA || type == ZFS_PROP_GROUPUSED ||
+           type == ZFS_PROP_GROUPOBJQUOTA ||
+           type == ZFS_PROP_GROUPOBJUSED);
 
        cp = strchr(propname, '@') + 1;
 
@@ -2332,29 +2937,29 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
                return (ENOSYS);
 #endif /* HAVE_IDMAP */
        } else {
-#ifdef HAVE_IDMAP
                /* It's a user/group ID (eg "12345"). */
                uid_t id;
-               idmap_rid_t rid;
-               char *mapdomain;
                char *end;
-
                id = strtoul(cp, &end, 10);
                if (*end != '\0')
                        return (EINVAL);
                if (id > MAXUID) {
+#ifdef HAVE_IDMAP
                        /* It's an ephemeral ID. */
+                       idmap_rid_t rid;
+                       char *mapdomain;
+
                        if (idmap_id_to_numeric_domain_rid(id, isuser,
                            &mapdomain, &rid) != 0)
                                return (ENOENT);
                        (void) strlcpy(domain, mapdomain, domainlen);
                        *ridp = rid;
+#else
+                       return (ENOSYS);
+#endif /* HAVE_IDMAP */
                } else {
                        *ridp = id;
                }
-#else
-               return (ENOSYS);
-#endif /* HAVE_IDMAP */
        }
 
        return (0);
@@ -2365,9 +2970,9 @@ 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", "\0", "\0", "\0", 0 };
+       zfs_cmd_t zc = {"\0"};
 
-       (void) strncpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+       (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
 
        err = userquota_propname_decode(propname,
            zfs_prop_get_int(zhp, ZFS_PROP_ZONED),
@@ -2410,154 +3015,101 @@ zfs_prop_get_userquota(zfs_handle_t *zhp, const char *propname,
 
        if (literal) {
                (void) snprintf(propbuf, proplen, "%llu",
-                              (u_longlong_t)propvalue);
+                   (u_longlong_t)propvalue);
        } else if (propvalue == 0 &&
-           (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_GROUPQUOTA)) {
+           (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_GROUPQUOTA ||
+           type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA)) {
                (void) strlcpy(propbuf, "none", proplen);
+       } else if (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_GROUPQUOTA ||
+           type == ZFS_PROP_USERUSED || type == ZFS_PROP_GROUPUSED) {
+               zfs_nicebytes(propvalue, propbuf, proplen);
        } else {
                zfs_nicenum(propvalue, propbuf, proplen);
        }
        return (0);
 }
 
-/*
- * Returns the name of the given zfs handle.
- */
-const char *
-zfs_get_name(const zfs_handle_t *zhp)
+int
+zfs_prop_get_written_int(zfs_handle_t *zhp, const char *propname,
+    uint64_t *propvalue)
 {
-       return (zhp->zfs_name);
-}
+       int err;
+       zfs_cmd_t zc = {"\0"};
+       const char *snapname;
 
-/*
- * Returns the type of the given zfs handle.
- */
-zfs_type_t
-zfs_get_type(const zfs_handle_t *zhp)
-{
-       return (zhp->zfs_type);
-}
+       (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
 
-static int
-zfs_do_list_ioctl(zfs_handle_t *zhp, int arg, zfs_cmd_t *zc)
-{
-       int rc;
-       uint64_t        orig_cookie;
+       snapname = strchr(propname, '@') + 1;
+       if (strchr(snapname, '@')) {
+               (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
+       } else {
+               /* snapname is the short name, append it to zhp's fsname */
+               char *cp;
+
+               (void) strlcpy(zc.zc_value, zhp->zfs_name,
+                   sizeof (zc.zc_value));
+               cp = strchr(zc.zc_value, '@');
+               if (cp != NULL)
+                       *cp = '\0';
+               (void) strlcat(zc.zc_value, "@", sizeof (zc.zc_value));
+               (void) strlcat(zc.zc_value, snapname, sizeof (zc.zc_value));
+       }
 
-       orig_cookie = zc->zc_cookie;
-top:
-       (void) strlcpy(zc->zc_name, zhp->zfs_name, sizeof (zc->zc_name));
-       rc = ioctl(zhp->zfs_hdl->libzfs_fd, arg, zc);
+       err = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_SPACE_WRITTEN, &zc);
+       if (err)
+               return (err);
 
-       if (rc == -1) {
-               switch (errno) {
-               case ENOMEM:
-                       /* expand nvlist memory and try again */
-                       if (zcmd_expand_dst_nvlist(zhp->zfs_hdl, zc) != 0) {
-                               zcmd_free_nvlists(zc);
-                               return (-1);
-                       }
-                       zc->zc_cookie = orig_cookie;
-                       goto top;
-               /*
-                * An errno value of ESRCH indicates normal completion.
-                * If ENOENT is returned, then the underlying dataset
-                * has been removed since we obtained the handle.
-                */
-               case ESRCH:
-               case ENOENT:
-                       rc = 1;
-                       break;
-               default:
-                       rc = zfs_standard_error(zhp->zfs_hdl, errno,
-                           dgettext(TEXT_DOMAIN,
-                           "cannot iterate filesystems"));
-                       break;
-               }
-       }
-       return (rc);
+       *propvalue = zc.zc_cookie;
+       return (0);
 }
 
-/*
- * Iterate over all child filesystems
- */
 int
-zfs_iter_filesystems(zfs_handle_t *zhp, zfs_iter_f func, void *data)
+zfs_prop_get_written(zfs_handle_t *zhp, const char *propname,
+    char *propbuf, int proplen, boolean_t literal)
 {
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
-       zfs_handle_t *nzhp;
-       int ret;
-
-       if (zhp->zfs_type != ZFS_TYPE_FILESYSTEM)
-               return (0);
+       int err;
+       uint64_t propvalue;
 
-       if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0)
-               return (-1);
+       err = zfs_prop_get_written_int(zhp, propname, &propvalue);
 
-       while ((ret = zfs_do_list_ioctl(zhp, ZFS_IOC_DATASET_LIST_NEXT,
-           &zc)) == 0) {
-               /*
-                * Silently ignore errors, as the only plausible explanation is
-                * that the pool has since been removed.
-                */
-               if ((nzhp = make_dataset_handle_zc(zhp->zfs_hdl,
-                   &zc)) == NULL) {
-                       continue;
-               }
+       if (err)
+               return (err);
 
-               if ((ret = func(nzhp, data)) != 0) {
-                       zcmd_free_nvlists(&zc);
-                       return (ret);
-               }
+       if (literal) {
+               (void) snprintf(propbuf, proplen, "%llu",
+                   (u_longlong_t)propvalue);
+       } else {
+               zfs_nicebytes(propvalue, propbuf, proplen);
        }
-       zcmd_free_nvlists(&zc);
-       return ((ret < 0) ? ret : 0);
+
+       return (0);
 }
 
 /*
- * Iterate over all snapshots
+ * Returns the name of the given zfs handle.
  */
-int
-zfs_iter_snapshots(zfs_handle_t *zhp, zfs_iter_f func, void *data)
+const char *
+zfs_get_name(const zfs_handle_t *zhp)
 {
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
-       zfs_handle_t *nzhp;
-       int ret;
-
-       if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT)
-               return (0);
-
-       if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0)
-               return (-1);
-       while ((ret = zfs_do_list_ioctl(zhp, ZFS_IOC_SNAPSHOT_LIST_NEXT,
-           &zc)) == 0) {
-
-               if ((nzhp = make_dataset_handle_zc(zhp->zfs_hdl,
-                   &zc)) == NULL) {
-                       continue;
-               }
-
-               if ((ret = func(nzhp, data)) != 0) {
-                       zcmd_free_nvlists(&zc);
-                       return (ret);
-               }
-       }
-       zcmd_free_nvlists(&zc);
-       return ((ret < 0) ? ret : 0);
+       return (zhp->zfs_name);
 }
 
 /*
- * Iterate over all children, snapshots and filesystems
+ * Returns the name of the parent pool for the given zfs handle.
  */
-int
-zfs_iter_children(zfs_handle_t *zhp, zfs_iter_f func, void *data)
+const char *
+zfs_get_pool_name(const zfs_handle_t *zhp)
 {
-       int ret;
-
-       if ((ret = zfs_iter_filesystems(zhp, func, data)) != 0)
-               return (ret);
+       return (zhp->zpool_hdl->zpool_name);
+}
 
-       return (zfs_iter_snapshots(zhp, func, data));
+/*
+ * Returns the type of the given zfs handle.
+ */
+zfs_type_t
+zfs_get_type(const zfs_handle_t *zhp)
+{
+       return (zhp->zfs_type);
 }
 
 /*
@@ -2583,18 +3135,19 @@ is_descendant(const char *ds1, const char *ds2)
 
 /*
  * Given a complete name, return just the portion that refers to the parent.
- * Can return NULL if this is a pool.
+ * Will return -1 if there is no parent (path is just the name of the
+ * pool).
  */
 static int
 parent_name(const char *path, char *buf, size_t buflen)
 {
-       char *loc;
+       char *slashp;
 
-       if ((loc = strrchr(path, '/')) == NULL)
-               return (-1);
+       (void) strlcpy(buf, path, buflen);
 
-       (void) strncpy(buf, path, MIN(buflen, loc - path));
-       buf[loc - path] = '\0';
+       if ((slashp = strrchr(buf, '/')) == NULL)
+               return (-1);
+       *slashp = '\0';
 
        return (0);
 }
@@ -2611,8 +3164,8 @@ 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", "\0", "\0", "\0", 0 };
-       char parent[ZFS_MAXNAMELEN];
+       zfs_cmd_t zc = {"\0"};
+       char parent[ZFS_MAX_DATASET_NAME_LEN];
        char *slash;
        zfs_handle_t *zhp;
        char errbuf[1024];
@@ -2740,8 +3293,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++) {
-               char *logstr;
+           (cp = strchr(cp, '/')) != NULL; *cp = '/', cp++) {
 
                *cp = '\0';
 
@@ -2752,16 +3304,12 @@ create_parents(libzfs_handle_t *hdl, char *target, int prefixlen)
                        continue;
                }
 
-               logstr = hdl->libzfs_log_str;
-               hdl->libzfs_log_str = NULL;
                if (zfs_create(hdl, target, ZFS_TYPE_FILESYSTEM,
                    NULL) != 0) {
-                       hdl->libzfs_log_str = logstr;
                        opname = dgettext(TEXT_DOMAIN, "create");
                        goto ancestorerr;
                }
 
-               hdl->libzfs_log_str = logstr;
                h = zfs_open(hdl, target, ZFS_TYPE_FILESYSTEM);
                if (h == NULL) {
                        opname = dgettext(TEXT_DOMAIN, "open");
@@ -2819,12 +3367,13 @@ int
 zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
     nvlist_t *props)
 {
-       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);
        char errbuf[1024];
        uint64_t zoned;
+       enum lzc_dataset_type ost;
+       zpool_handle_t *zpool_handle;
 
        (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
            "cannot create '%s'"), path);
@@ -2844,21 +3393,35 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
         * will return ENOENT, not EEXIST.  To prevent this from happening, we
         * first try to see if the dataset exists.
         */
-       (void) strlcpy(zc.zc_name, path, sizeof (zc.zc_name));
-       if (zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_DATASET)) {
+       if (zfs_dataset_exists(hdl, path, ZFS_TYPE_DATASET)) {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "dataset already exists"));
                return (zfs_error(hdl, EZFS_EXISTS, errbuf));
        }
 
        if (type == ZFS_TYPE_VOLUME)
-               zc.zc_objset_type = DMU_OST_ZVOL;
+               ost = LZC_DATSET_TYPE_ZVOL;
        else
-               zc.zc_objset_type = DMU_OST_ZFS;
+               ost = LZC_DATSET_TYPE_ZFS;
+
+       /* open zpool handle for prop validation */
+       char pool_path[ZFS_MAX_DATASET_NAME_LEN];
+       (void) strlcpy(pool_path, path, sizeof (pool_path));
+
+       /* truncate pool_path at first slash */
+       char *p = strchr(pool_path, '/');
+       if (p != NULL)
+               *p = '\0';
+
+       if ((zpool_handle = zpool_open(hdl, pool_path)) == NULL)
+               return (-1);
 
        if (props && (props = zfs_valid_proplist(hdl, type, props,
-           zoned, NULL, errbuf)) == 0)
+           zoned, NULL, zpool_handle, errbuf)) == 0) {
+               zpool_close(zpool_handle);
                return (-1);
+       }
+       zpool_close(zpool_handle);
 
        if (type == ZFS_TYPE_VOLUME) {
                /*
@@ -2906,30 +3469,13 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
                }
        }
 
-       if (props && zcmd_write_src_nvlist(hdl, &zc, props) != 0)
-               return (-1);
-       nvlist_free(props);
-
        /* 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);
+       ret = lzc_create(path, ost, props);
+       nvlist_free(props);
 
        /* check for failure */
        if (ret != 0) {
-               char parent[ZFS_MAXNAMELEN];
+               char parent[ZFS_MAX_DATASET_NAME_LEN];
                (void) parent_name(path, parent, sizeof (parent));
 
                switch (errno) {
@@ -2943,15 +3489,6 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
                            "parent '%s' is not a filesystem"), parent);
                        return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
 
-               case EDOM:
-                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-                           "volume block size must be power of 2 from "
-                           "%u to %uk"),
-                           (uint_t)SPA_MINBLOCKSIZE,
-                           (uint_t)SPA_MAXBLOCKSIZE >> 10);
-
-                       return (zfs_error(hdl, EZFS_BADPROP, errbuf));
-
                case ENOTSUP:
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "pool must be upgraded to set this "
@@ -2977,26 +3514,38 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
 
 /*
  * Destroys the given dataset.  The caller must make sure that the filesystem
- * isn't mounted, and that there are no active dependents.
+ * isn't mounted, and that there are no active dependents. If the file system
+ * does not exist this function does nothing.
  */
 int
 zfs_destroy(zfs_handle_t *zhp, boolean_t defer)
 {
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       zfs_cmd_t zc = {"\0"};
+
+       if (zhp->zfs_type == ZFS_TYPE_BOOKMARK) {
+               nvlist_t *nv = fnvlist_alloc();
+               fnvlist_add_boolean(nv, zhp->zfs_name);
+               int error = lzc_destroy_bookmarks(nv, NULL);
+               fnvlist_free(nv);
+               if (error != 0) {
+                       return (zfs_standard_error_fmt(zhp->zfs_hdl, errno,
+                           dgettext(TEXT_DOMAIN, "cannot destroy '%s'"),
+                           zhp->zfs_name));
+               }
+               return (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;
        }
 
        zc.zc_defer_destroy = defer;
-       if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DESTROY, &zc) != 0) {
+       if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DESTROY, &zc) != 0 &&
+           errno != ENOENT) {
                return (zfs_standard_error_fmt(zhp->zfs_hdl, errno,
                    dgettext(TEXT_DOMAIN, "cannot destroy '%s'"),
                    zhp->zfs_name));
@@ -3008,43 +3557,26 @@ zfs_destroy(zfs_handle_t *zhp, boolean_t defer)
 }
 
 struct destroydata {
-       char *snapname;
-       boolean_t gotone;
-       boolean_t closezhp;
+       nvlist_t *nvl;
+       const char *snapname;
 };
 
 static int
 zfs_check_snap_cb(zfs_handle_t *zhp, void *arg)
 {
        struct destroydata *dd = arg;
-       zfs_handle_t *szhp;
-       char name[ZFS_MAXNAMELEN];
-       boolean_t closezhp = dd->closezhp;
+       char name[ZFS_MAX_DATASET_NAME_LEN];
        int rv = 0;
 
-       (void) strlcpy(name, zhp->zfs_name, sizeof (name));
-       (void) strlcat(name, "@", sizeof (name));
-       (void) strlcat(name, dd->snapname, sizeof (name));
-
-       szhp = make_dataset_handle(zhp->zfs_hdl, name);
-       if (szhp) {
-               dd->gotone = B_TRUE;
-               zfs_close(szhp);
-       }
+       if (snprintf(name, sizeof (name), "%s@%s", zhp->zfs_name,
+           dd->snapname) >= sizeof (name))
+               return (EINVAL);
 
-       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.
-                */
-       }
+       if (lzc_exists(name))
+               verify(nvlist_add_boolean(dd->nvl, name) == 0);
 
-       dd->closezhp = B_TRUE;
-       rv = zfs_iter_filesystems(zhp, zfs_check_snap_cb, arg);
-       if (closezhp)
-               zfs_close(zhp);
+       rv = zfs_iter_filesystems(zhp, zfs_check_snap_cb, dd);
+       zfs_close(zhp);
        return (rv);
 }
 
@@ -3054,43 +3586,69 @@ 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", "\0", "\0", "\0", 0 };
        int ret;
        struct destroydata dd = { 0 };
 
        dd.snapname = snapname;
-       (void) zfs_check_snap_cb(zhp, &dd);
+       verify(nvlist_alloc(&dd.nvl, NV_UNIQUE_NAME, 0) == 0);
+       (void) zfs_check_snap_cb(zfs_handle_dup(zhp), &dd);
 
-       if (!dd.gotone) {
-               return (zfs_standard_error_fmt(zhp->zfs_hdl, ENOENT,
+       if (nvlist_empty(dd.nvl)) {
+               ret = zfs_standard_error_fmt(zhp->zfs_hdl, ENOENT,
                    dgettext(TEXT_DOMAIN, "cannot destroy '%s@%s'"),
-                   zhp->zfs_name, snapname));
+                   zhp->zfs_name, snapname);
+       } else {
+               ret = zfs_destroy_snaps_nvl(zhp->zfs_hdl, dd.nvl, defer);
        }
+       nvlist_free(dd.nvl);
+       return (ret);
+}
 
-       (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
-       (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
-       zc.zc_defer_destroy = defer;
-
-       ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DESTROY_SNAPS, &zc);
-       if (ret != 0) {
-               char errbuf[1024];
+/*
+ * Destroys all the snapshots named in the nvlist.
+ */
+int
+zfs_destroy_snaps_nvl(libzfs_handle_t *hdl, nvlist_t *snaps, boolean_t defer)
+{
+       int ret;
+       nvlist_t *errlist = NULL;
+       nvpair_t *pair;
 
-               (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
-                   "cannot destroy '%s@%s'"), zc.zc_name, snapname);
+       ret = lzc_destroy_snaps(snaps, defer, &errlist);
 
-               switch (errno) {
-               case EEXIST:
-                       zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
-                           "snapshot is cloned"));
-                       return (zfs_error(zhp->zfs_hdl, EZFS_EXISTS, errbuf));
+       if (ret == 0) {
+               nvlist_free(errlist);
+               return (0);
+       }
+
+       if (nvlist_empty(errlist)) {
+               char errbuf[1024];
+               (void) snprintf(errbuf, sizeof (errbuf),
+                   dgettext(TEXT_DOMAIN, "cannot destroy snapshots"));
 
+               ret = zfs_standard_error(hdl, ret, errbuf);
+       }
+       for (pair = nvlist_next_nvpair(errlist, NULL);
+           pair != NULL; pair = nvlist_next_nvpair(errlist, pair)) {
+               char errbuf[1024];
+               (void) snprintf(errbuf, sizeof (errbuf),
+                   dgettext(TEXT_DOMAIN, "cannot destroy snapshot %s"),
+                   nvpair_name(pair));
+
+               switch (fnvpair_value_int32(pair)) {
+               case EEXIST:
+                       zfs_error_aux(hdl,
+                           dgettext(TEXT_DOMAIN, "snapshot is cloned"));
+                       ret = zfs_error(hdl, EZFS_EXISTS, errbuf);
+                       break;
                default:
-                       return (zfs_standard_error(zhp->zfs_hdl, errno,
-                           errbuf));
+                       ret = zfs_standard_error(hdl, errno, errbuf);
+                       break;
                }
        }
 
-       return (0);
+       nvlist_free(errlist);
+       return (ret);
 }
 
 /*
@@ -3099,12 +3657,10 @@ 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", "\0", "\0", "\0", 0 };
-       char parent[ZFS_MAXNAMELEN];
+       char parent[ZFS_MAX_DATASET_NAME_LEN];
        int ret;
        char errbuf[1024];
        libzfs_handle_t *hdl = zhp->zfs_hdl;
-       zfs_type_t type;
        uint64_t zoned;
 
        assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT);
@@ -3112,7 +3668,7 @@ zfs_clone(zfs_handle_t *zhp, const char *target, nvlist_t *props)
        (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
            "cannot create '%s'"), target);
 
-       /* validate the target name */
+       /* validate the target/clone name */
        if (!zfs_validate_name(hdl, target, ZFS_TYPE_FILESYSTEM, B_TRUE))
                return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
 
@@ -3123,32 +3679,21 @@ zfs_clone(zfs_handle_t *zhp, const char *target, nvlist_t *props)
        (void) parent_name(target, parent, sizeof (parent));
 
        /* do the clone */
-       if (ZFS_IS_VOLUME(zhp)) {
-               zc.zc_objset_type = DMU_OST_ZVOL;
-               type = ZFS_TYPE_VOLUME;
-       } else {
-               zc.zc_objset_type = DMU_OST_ZFS;
-               type = ZFS_TYPE_FILESYSTEM;
-       }
 
        if (props) {
+               zfs_type_t type;
+               if (ZFS_IS_VOLUME(zhp)) {
+                       type = ZFS_TYPE_VOLUME;
+               } else {
+                       type = ZFS_TYPE_FILESYSTEM;
+               }
                if ((props = zfs_valid_proplist(hdl, type, props, zoned,
-                   zhp, errbuf)) == NULL)
+                   zhp, zhp->zpool_hdl, errbuf)) == NULL)
                        return (-1);
-
-               if (zcmd_write_src_nvlist(hdl, &zc, props) != 0) {
-                       nvlist_free(props);
-                       return (-1);
-               }
-
-               nvlist_free(props);
        }
 
-       (void) strlcpy(zc.zc_name, target, sizeof (zc.zc_name));
-       (void) strlcpy(zc.zc_value, zhp->zfs_name, sizeof (zc.zc_value));
-       ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_CREATE, &zc);
-
-       zcmd_free_nvlists(&zc);
+       ret = lzc_clone(target, zhp->zfs_name, props);
+       nvlist_free(props);
 
        if (ret != 0) {
                switch (errno) {
@@ -3177,70 +3722,11 @@ 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.
  */
@@ -3248,12 +3734,8 @@ int
 zfs_promote(zfs_handle_t *zhp)
 {
        libzfs_handle_t *hdl = zhp->zfs_hdl;
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
-       char parent[MAXPATHLEN];
-       char *cp;
+       char snapname[ZFS_MAX_DATASET_NAME_LEN];
        int ret;
-       zfs_handle_t *pzhp;
-       promote_data_t pd;
        char errbuf[1024];
 
        (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
@@ -3265,190 +3747,177 @@ zfs_promote(zfs_handle_t *zhp)
                return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
        }
 
-       (void) strlcpy(parent, zhp->zfs_dmustats.dds_origin, sizeof (parent));
-       if (parent[0] == '\0') {
+       if (zhp->zfs_dmustats.dds_origin[0] == '\0') {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "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));
-       ret = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc);
+       ret = lzc_promote(zhp->zfs_name, snapname, sizeof (snapname));
 
        if (ret != 0) {
-               int save_errno = errno;
-
-               (void) zfs_iter_snapshots(pzhp, promote_snap_done_cb, &pd);
-               zfs_close(pzhp);
-
-               switch (save_errno) {
+               switch (ret) {
                case EEXIST:
-                       /*
-                        * There is a conflicting snapshot name.  We
-                        * should have caught this above, but they could
-                        * have renamed something in the mean time.
-                        */
+                       /* There is a conflicting snapshot name. */
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "conflicting snapshot '%s' from parent '%s'"),
-                           zc.zc_string, parent);
+                           snapname, zhp->zfs_dmustats.dds_origin);
                        return (zfs_error(hdl, EZFS_EXISTS, errbuf));
 
                default:
-                       return (zfs_standard_error(hdl, save_errno, errbuf));
+                       return (zfs_standard_error(hdl, ret, 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;
-};
+typedef struct snapdata {
+       nvlist_t *sd_nvl;
+       const char *sd_snapname;
+} snapdata_t;
 
 static int
-zfs_create_link_cb(zfs_handle_t *zhp, void *arg)
+zfs_snapshot_cb(zfs_handle_t *zhp, void *arg)
 {
-       struct createdata *cd = arg;
-       int ret;
-
-       if (zhp->zfs_type == ZFS_TYPE_VOLUME) {
-               char name[MAXPATHLEN];
+       snapdata_t *sd = arg;
+       char name[ZFS_MAX_DATASET_NAME_LEN];
+       int rv = 0;
 
-               (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.
-                */
-       }
+       if (zfs_prop_get_int(zhp, ZFS_PROP_INCONSISTENT) == 0) {
+               if (snprintf(name, sizeof (name), "%s@%s", zfs_get_name(zhp),
+                   sd->sd_snapname) >= sizeof (name))
+                       return (EINVAL);
 
-       ret = zfs_iter_filesystems(zhp, zfs_create_link_cb, cd);
+               fnvlist_add_boolean(sd->sd_nvl, name);
 
+               rv = zfs_iter_filesystems(zhp, zfs_snapshot_cb, sd);
+       }
        zfs_close(zhp);
 
-       return (ret);
+       return (rv);
 }
 
 /*
- * Takes a snapshot of the given dataset.
+ * Creates snapshots.  The keys in the snaps nvlist are the snapshots to be
+ * created.
  */
 int
-zfs_snapshot(libzfs_handle_t *hdl, const char *path, boolean_t recursive,
-    nvlist_t *props)
+zfs_snapshot_nvl(libzfs_handle_t *hdl, nvlist_t *snaps, nvlist_t *props)
 {
-       const char *delim;
-       char parent[ZFS_MAXNAMELEN];
-       zfs_handle_t *zhp;
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        int ret;
        char errbuf[1024];
+       nvpair_t *elem;
+       nvlist_t *errors;
+       zpool_handle_t *zpool_hdl;
+       char pool[ZFS_MAX_DATASET_NAME_LEN];
 
        (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
-           "cannot snapshot '%s'"), path);
+           "cannot create snapshots "));
 
-       /* validate the target name */
-       if (!zfs_validate_name(hdl, path, ZFS_TYPE_SNAPSHOT, B_TRUE))
-               return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
-
-       if (props) {
-               if ((props = zfs_valid_proplist(hdl, ZFS_TYPE_SNAPSHOT,
-                   props, B_FALSE, NULL, errbuf)) == NULL)
-                       return (-1);
+       elem = NULL;
+       while ((elem = nvlist_next_nvpair(snaps, elem)) != NULL) {
+               const char *snapname = nvpair_name(elem);
 
-               if (zcmd_write_src_nvlist(hdl, &zc, props) != 0) {
-                       nvlist_free(props);
-                       return (-1);
+               /* validate the target name */
+               if (!zfs_validate_name(hdl, snapname, ZFS_TYPE_SNAPSHOT,
+                   B_TRUE)) {
+                       (void) snprintf(errbuf, sizeof (errbuf),
+                           dgettext(TEXT_DOMAIN,
+                           "cannot create snapshot '%s'"), snapname);
+                       return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
                }
-
-               nvlist_free(props);
        }
 
-       /* make sure the parent exists and is of the appropriate type */
-       delim = strchr(path, '@');
-       (void) strncpy(parent, path, delim - path);
-       parent[delim - path] = '\0';
+       /*
+        * get pool handle for prop validation. assumes all snaps are in the
+        * same pool, as does lzc_snapshot (below).
+        */
+       elem = nvlist_next_nvpair(snaps, NULL);
+       (void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
+       pool[strcspn(pool, "/@")] = '\0';
+       zpool_hdl = zpool_open(hdl, pool);
+       if (zpool_hdl == NULL)
+               return (-1);
 
-       if ((zhp = zfs_open(hdl, parent, ZFS_TYPE_FILESYSTEM |
-           ZFS_TYPE_VOLUME)) == NULL) {
-               zcmd_free_nvlists(&zc);
+       if (props != NULL &&
+           (props = zfs_valid_proplist(hdl, ZFS_TYPE_SNAPSHOT,
+           props, B_FALSE, NULL, zpool_hdl, errbuf)) == NULL) {
+               zpool_close(zpool_hdl);
                return (-1);
        }
+       zpool_close(zpool_hdl);
 
-       (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
-       (void) strlcpy(zc.zc_value, delim+1, sizeof (zc.zc_value));
-       if (ZFS_IS_VOLUME(zhp))
-               zc.zc_objset_type = DMU_OST_ZVOL;
-       else
-               zc.zc_objset_type = DMU_OST_ZFS;
-       zc.zc_cookie = recursive;
-       ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_SNAPSHOT, &zc);
+       ret = lzc_snapshot(snaps, props, &errors);
 
-       zcmd_free_nvlists(&zc);
+       if (ret != 0) {
+               boolean_t printed = B_FALSE;
+               for (elem = nvlist_next_nvpair(errors, NULL);
+                   elem != NULL;
+                   elem = nvlist_next_nvpair(errors, elem)) {
+                       (void) snprintf(errbuf, sizeof (errbuf),
+                           dgettext(TEXT_DOMAIN,
+                           "cannot create snapshot '%s'"), nvpair_name(elem));
+                       (void) zfs_standard_error(hdl,
+                           fnvpair_value_int32(elem), errbuf);
+                       printed = B_TRUE;
+               }
+               if (!printed) {
+                       switch (ret) {
+                       case EXDEV:
+                               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                                   "multiple snapshots of same "
+                                   "fs not allowed"));
+                               (void) zfs_error(hdl, EZFS_EXISTS, errbuf);
 
-       /*
-        * if it was recursive, the one that actually failed will be in
-        * zc.zc_name.
-        */
-       if (ret != 0)
-               (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
-                   "cannot create snapshot '%s@%s'"), zc.zc_name, zc.zc_value);
+                               break;
+                       default:
+                               (void) zfs_standard_error(hdl, ret, errbuf);
+                       }
+               }
+       }
 
-       if (ret == 0 && recursive) {
-               struct createdata cd;
+       nvlist_free(props);
+       nvlist_free(errors);
+       return (ret);
+}
 
-               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);
-               }
+int
+zfs_snapshot(libzfs_handle_t *hdl, const char *path, boolean_t recursive,
+    nvlist_t *props)
+{
+       int ret;
+       snapdata_t sd = { 0 };
+       char fsname[ZFS_MAX_DATASET_NAME_LEN];
+       char *cp;
+       zfs_handle_t *zhp;
+       char errbuf[1024];
+
+       (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+           "cannot snapshot %s"), path);
+
+       if (!zfs_validate_name(hdl, path, ZFS_TYPE_SNAPSHOT, B_TRUE))
+               return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+
+       (void) strlcpy(fsname, path, sizeof (fsname));
+       cp = strchr(fsname, '@');
+       *cp = '\0';
+       sd.sd_snapname = cp + 1;
+
+       if ((zhp = zfs_open(hdl, fsname, ZFS_TYPE_FILESYSTEM |
+           ZFS_TYPE_VOLUME)) == NULL) {
+               return (-1);
        }
 
-       if (ret != 0)
-               (void) zfs_standard_error(hdl, errno, errbuf);
+       verify(nvlist_alloc(&sd.sd_nvl, NV_UNIQUE_NAME, 0) == 0);
+       if (recursive) {
+               (void) zfs_snapshot_cb(zfs_handle_dup(zhp), &sd);
+       } else {
+               fnvlist_add_boolean(sd.sd_nvl, path);
+       }
 
+       ret = zfs_snapshot_nvl(hdl, sd.sd_nvl, props);
+       nvlist_free(sd.sd_nvl);
        zfs_close(zhp);
-
        return (ret);
 }
 
@@ -3462,49 +3931,44 @@ typedef struct rollback_data {
        const char      *cb_target;             /* the snapshot */
        uint64_t        cb_create;              /* creation time reference */
        boolean_t       cb_error;
-       boolean_t       cb_dependent;
        boolean_t       cb_force;
 } rollback_data_t;
 
+static int
+rollback_destroy_dependent(zfs_handle_t *zhp, void *data)
+{
+       rollback_data_t *cbp = data;
+       prop_changelist_t *clp;
+
+       /* We must destroy this clone; first unmount it */
+       clp = changelist_gather(zhp, ZFS_PROP_NAME, 0,
+           cbp->cb_force ? MS_FORCE: 0);
+       if (clp == NULL || changelist_prefix(clp) != 0) {
+               cbp->cb_error = B_TRUE;
+               zfs_close(zhp);
+               return (0);
+       }
+       if (zfs_destroy(zhp, B_FALSE) != 0)
+               cbp->cb_error = B_TRUE;
+       else
+               changelist_remove(clp, zhp->zfs_name);
+       (void) changelist_postfix(clp);
+       changelist_free(clp);
+
+       zfs_close(zhp);
+       return (0);
+}
+
 static int
 rollback_destroy(zfs_handle_t *zhp, void *data)
 {
        rollback_data_t *cbp = data;
 
-       if (!cbp->cb_dependent) {
-               if (strcmp(zhp->zfs_name, cbp->cb_target) != 0 &&
-                   zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT &&
-                   zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) >
-                   cbp->cb_create) {
-                       char *logstr;
-
-                       cbp->cb_dependent = B_TRUE;
-                       cbp->cb_error |= zfs_iter_dependents(zhp, B_FALSE,
-                           rollback_destroy, cbp);
-                       cbp->cb_dependent = B_FALSE;
-
-                       logstr = zhp->zfs_hdl->libzfs_log_str;
-                       zhp->zfs_hdl->libzfs_log_str = NULL;
-                       cbp->cb_error |= zfs_destroy(zhp, B_FALSE);
-                       zhp->zfs_hdl->libzfs_log_str = logstr;
-               }
-       } else {
-               /* We must destroy this clone; first unmount it */
-               prop_changelist_t *clp;
+       if (zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) > cbp->cb_create) {
+               cbp->cb_error |= zfs_iter_dependents(zhp, B_FALSE,
+                   rollback_destroy_dependent, cbp);
 
-               clp = changelist_gather(zhp, ZFS_PROP_NAME, 0,
-                   cbp->cb_force ? MS_FORCE: 0);
-               if (clp == NULL || changelist_prefix(clp) != 0) {
-                       cbp->cb_error = B_TRUE;
-                       zfs_close(zhp);
-                       return (0);
-               }
-               if (zfs_destroy(zhp, B_FALSE) != 0)
-                       cbp->cb_error = B_TRUE;
-               else
-                       changelist_remove(clp, zhp->zfs_name);
-               (void) changelist_postfix(clp);
-               changelist_free(clp);
+               cbp->cb_error |= zfs_destroy(zhp, B_FALSE);
        }
 
        zfs_close(zhp);
@@ -3515,15 +3979,14 @@ rollback_destroy(zfs_handle_t *zhp, void *data)
  * Given a dataset, rollback to a specific snapshot, discarding any
  * data changes since then and making it the active dataset.
  *
- * Any snapshots more recent than the target are destroyed, along with
- * their dependents.
+ * Any snapshots and bookmarks more recent than the target are
+ * destroyed, along with their dependents (i.e. clones).
  */
 int
 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", "\0", "\0", "\0", 0 };
        boolean_t restore_resv = 0;
        uint64_t old_volsize = 0, new_volsize;
        zfs_prop_t resv_prop = { 0 };
@@ -3532,12 +3995,13 @@ zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
            zhp->zfs_type == ZFS_TYPE_VOLUME);
 
        /*
-        * Destroy all recent snapshots and its dependends.
+        * Destroy all recent snapshots and their dependents.
         */
        cb.cb_force = force;
        cb.cb_target = snap->zfs_name;
        cb.cb_create = zfs_prop_get_int(snap, ZFS_PROP_CREATETXG);
-       (void) zfs_iter_children(zhp, rollback_destroy, &cb);
+       (void) zfs_iter_snapshots(zhp, B_FALSE, rollback_destroy, &cb);
+       (void) zfs_iter_bookmarks(zhp, rollback_destroy, &cb);
 
        if (cb.cb_error)
                return (-1);
@@ -3548,8 +4012,6 @@ 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);
@@ -3557,22 +4019,20 @@ zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
                    (old_volsize == zfs_prop_get_int(zhp, resv_prop));
        }
 
-       (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
-
-       if (ZFS_IS_VOLUME(zhp))
-               zc.zc_objset_type = DMU_OST_ZVOL;
-       else
-               zc.zc_objset_type = DMU_OST_ZFS;
-
        /*
-        * We rely on zfs_iter_children() to verify that there are no
-        * newer snapshots for the given dataset.  Therefore, we can
-        * simply pass the name on to the ioctl() call.  There is still
-        * an unlikely race condition where the user has taken a
-        * snapshot since we verified that this was the most recent.
-        *
+        * Pass both the filesystem and the wanted snapshot names,
+        * we would get an error back if the snapshot is destroyed or
+        * a new snapshot is created before this request is processed.
         */
-       if ((err = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_ROLLBACK, &zc)) != 0) {
+       err = lzc_rollback_to(zhp->zfs_name, snap->zfs_name);
+       if (err == EXDEV) {
+               zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+                   "'%s' is not the latest snapshot"), snap->zfs_name);
+               (void) zfs_error_fmt(zhp->zfs_hdl, EZFS_BUSY,
+                   dgettext(TEXT_DOMAIN, "cannot rollback '%s'"),
+                   zhp->zfs_name);
+               return (err);
+       } else if (err != 0) {
                (void) zfs_standard_error_fmt(zhp->zfs_hdl, errno,
                    dgettext(TEXT_DOMAIN, "cannot rollback '%s'"),
                    zhp->zfs_name);
@@ -3587,10 +4047,6 @@ 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)
@@ -3602,55 +4058,20 @@ zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
        return (err);
 }
 
-/*
- * Iterate over all dependents for a given dataset.  This includes both
- * hierarchical dependents (children) and data dependents (snapshots and
- * clones).  The bulk of the processing occurs in get_dependents() in
- * libzfs_graph.c.
- */
-int
-zfs_iter_dependents(zfs_handle_t *zhp, boolean_t allowrecursion,
-    zfs_iter_f func, void *data)
-{
-       char **dependents;
-       size_t count;
-       int i;
-       zfs_handle_t *child;
-       int ret = 0;
-
-       if (get_dependents(zhp->zfs_hdl, allowrecursion, zhp->zfs_name,
-           &dependents, &count) != 0)
-               return (-1);
-
-       for (i = 0; i < count; i++) {
-               if ((child = make_dataset_handle(zhp->zfs_hdl,
-                   dependents[i])) == NULL)
-                       continue;
-
-               if ((ret = func(child, data)) != 0)
-                       break;
-       }
-
-       for (i = 0; i < count; i++)
-               free(dependents[i]);
-       free(dependents);
-
-       return (ret);
-}
-
 /*
  * Renames the given dataset.
  */
 int
-zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive)
+zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive,
+    boolean_t force_unmount)
 {
-       int ret;
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       int ret = 0;
+       zfs_cmd_t zc = {"\0"};
        char *delim;
        prop_changelist_t *cl = NULL;
        zfs_handle_t *zhrp = NULL;
        char *parentname = NULL;
-       char parent[ZFS_MAXNAMELEN];
+       char parent[ZFS_MAX_DATASET_NAME_LEN];
        libzfs_handle_t *hdl = zhp->zfs_hdl;
        char errbuf[1024];
 
@@ -3739,8 +4160,6 @@ 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) {
                        ret = -1;
@@ -3753,18 +4172,9 @@ zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive)
                        ret = -1;
                        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)
+       } else if (zhp->zfs_type != ZFS_TYPE_SNAPSHOT) {
+               if ((cl = changelist_gather(zhp, ZFS_PROP_NAME, 0,
+                   force_unmount ? MS_FORCE : 0)) == NULL)
                        return (-1);
 
                if (changelist_haszonedchild(cl)) {
@@ -3811,138 +4221,28 @@ 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) {
-                       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 {
+               if (cl != NULL)
                        (void) changelist_postfix(cl);
-               }
        } else {
-               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 {
+               if (cl != NULL) {
                        changelist_rename(cl, zfs_get_name(zhp), target);
                        ret = changelist_postfix(cl);
                }
        }
 
 error:
-       if (parentname) {
+       if (parentname != NULL) {
                free(parentname);
        }
-       if (zhrp) {
+       if (zhrp != NULL) {
                zfs_close(zhrp);
        }
-       if (cl) {
+       if (cl != NULL) {
                changelist_free(cl);
        }
        return (ret);
 }
 
-/*
- * 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 (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_user_props(zfs_handle_t *zhp)
 {
@@ -3963,7 +4263,8 @@ zfs_get_user_props(zfs_handle_t *zhp)
  *        of the RECEIVED column.
  */
 int
-zfs_expand_proplist(zfs_handle_t *zhp, zprop_list_t **plp, boolean_t received)
+zfs_expand_proplist(zfs_handle_t *zhp, zprop_list_t **plp, boolean_t received,
+    boolean_t literal)
 {
        libzfs_handle_t *hdl = zhp->zfs_hdl;
        zprop_list_t *entry;
@@ -4025,18 +4326,18 @@ zfs_expand_proplist(zfs_handle_t *zhp, zprop_list_t **plp, boolean_t received)
         * Now go through and check the width of any non-fixed columns
         */
        for (entry = *plp; entry != NULL; entry = entry->pl_next) {
-               if (entry->pl_fixed)
+               if (entry->pl_fixed && !literal)
                        continue;
 
                if (entry->pl_prop != ZPROP_INVAL) {
                        if (zfs_prop_get(zhp, entry->pl_prop,
-                           buf, sizeof (buf), NULL, NULL, 0, B_FALSE) == 0) {
+                           buf, sizeof (buf), NULL, NULL, 0, literal) == 0) {
                                if (strlen(buf) > entry->pl_width)
                                        entry->pl_width = strlen(buf);
                        }
                        if (received && zfs_prop_get_recvd(zhp,
                            zfs_prop_to_name(entry->pl_prop),
-                           buf, sizeof (buf), B_FALSE) == 0)
+                           buf, sizeof (buf), literal) == 0)
                                if (strlen(buf) > entry->pl_recvd_width)
                                        entry->pl_recvd_width = strlen(buf);
                } else {
@@ -4049,7 +4350,7 @@ zfs_expand_proplist(zfs_handle_t *zhp, zprop_list_t **plp, boolean_t received)
                        }
                        if (received && zfs_prop_get_recvd(zhp,
                            entry->pl_user_prop,
-                           buf, sizeof (buf), B_FALSE) == 0)
+                           buf, sizeof (buf), literal) == 0)
                                if (strlen(buf) > entry->pl_recvd_width)
                                        entry->pl_recvd_width = strlen(buf);
                }
@@ -4062,6 +4363,7 @@ void
 zfs_prune_proplist(zfs_handle_t *zhp, uint8_t *props)
 {
        nvpair_t *curr;
+       nvpair_t *next;
 
        /*
         * Keep a reference to the props-table against which we prune the
@@ -4073,7 +4375,7 @@ zfs_prune_proplist(zfs_handle_t *zhp, uint8_t *props)
 
        while (curr) {
                zfs_prop_t zfs_prop = zfs_name_to_prop(nvpair_name(curr));
-               nvpair_t *next = nvlist_next_nvpair(zhp->zfs_props, curr);
+               next = nvlist_next_nvpair(zhp->zfs_props, curr);
 
                /*
                 * User properties will result in ZPROP_INVAL, and since we
@@ -4093,7 +4395,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", "\0", "\0", "\0", 0 };
+       zfs_cmd_t zc = {"\0"};
        nvlist_t *nvlist = NULL;
        int error;
 
@@ -4104,7 +4406,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 (-1);
+                       return (0);
                }
        }
 
@@ -4135,8 +4437,7 @@ zfs_smb_acl_mgmt(libzfs_handle_t *hdl, char *dataset, char *path,
                return (-1);
        }
        error = ioctl(hdl->libzfs_fd, ZFS_IOC_SMB_ACL, &zc);
-       if (nvlist)
-               nvlist_free(nvlist);
+       nvlist_free(nvlist);
        return (error);
 }
 
@@ -4175,69 +4476,153 @@ int
 zfs_userspace(zfs_handle_t *zhp, zfs_userquota_prop_t type,
     zfs_userspace_cb_t func, void *arg)
 {
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
-       int error;
+       zfs_cmd_t zc = {"\0"};
        zfs_useracct_t buf[100];
+       libzfs_handle_t *hdl = zhp->zfs_hdl;
+       int ret;
 
-       (void) strncpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+       (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
 
        zc.zc_objset_type = type;
        zc.zc_nvlist_dst = (uintptr_t)buf;
 
-       /* CONSTCOND */
-       while (1) {
+       for (;;) {
                zfs_useracct_t *zua = buf;
 
                zc.zc_nvlist_dst_size = sizeof (buf);
-               error = ioctl(zhp->zfs_hdl->libzfs_fd,
-                   ZFS_IOC_USERSPACE_MANY, &zc);
-               if (error || zc.zc_nvlist_dst_size == 0)
+               if (zfs_ioctl(hdl, ZFS_IOC_USERSPACE_MANY, &zc) != 0) {
+                       char errbuf[1024];
+
+                       if ((errno == ENOTSUP &&
+                           (type == ZFS_PROP_USEROBJUSED ||
+                           type == ZFS_PROP_GROUPOBJUSED ||
+                           type == ZFS_PROP_USEROBJQUOTA ||
+                           type == ZFS_PROP_GROUPOBJQUOTA)))
+                               break;
+
+                       (void) snprintf(errbuf, sizeof (errbuf),
+                           dgettext(TEXT_DOMAIN,
+                           "cannot get used/quota for %s"), zc.zc_name);
+                       return (zfs_standard_error_fmt(hdl, errno, errbuf));
+               }
+               if (zc.zc_nvlist_dst_size == 0)
                        break;
 
                while (zc.zc_nvlist_dst_size > 0) {
-                       error = func(arg, zua->zu_domain, zua->zu_rid,
-                           zua->zu_space);
-                       if (error != 0)
-                               return (error);
+                       if ((ret = func(arg, zua->zu_domain, zua->zu_rid,
+                           zua->zu_space)) != 0)
+                               return (ret);
                        zua++;
                        zc.zc_nvlist_dst_size -= sizeof (zfs_useracct_t);
                }
        }
 
-       return (error);
+       return (0);
+}
+
+struct holdarg {
+       nvlist_t *nvl;
+       const char *snapname;
+       const char *tag;
+       boolean_t recursive;
+       int error;
+};
+
+static int
+zfs_hold_one(zfs_handle_t *zhp, void *arg)
+{
+       struct holdarg *ha = arg;
+       char name[ZFS_MAX_DATASET_NAME_LEN];
+       int rv = 0;
+
+       if (snprintf(name, sizeof (name), "%s@%s", zhp->zfs_name,
+           ha->snapname) >= sizeof (name))
+               return (EINVAL);
+
+       if (lzc_exists(name))
+               fnvlist_add_string(ha->nvl, name, ha->tag);
+
+       if (ha->recursive)
+               rv = zfs_iter_filesystems(zhp, zfs_hold_one, ha);
+       zfs_close(zhp);
+       return (rv);
 }
 
 int
 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)
+    boolean_t recursive, int cleanup_fd)
+{
+       int ret;
+       struct holdarg ha;
+
+       ha.nvl = fnvlist_alloc();
+       ha.snapname = snapname;
+       ha.tag = tag;
+       ha.recursive = recursive;
+       (void) zfs_hold_one(zfs_handle_dup(zhp), &ha);
+
+       if (nvlist_empty(ha.nvl)) {
+               char errbuf[1024];
+
+               fnvlist_free(ha.nvl);
+               ret = ENOENT;
+               (void) snprintf(errbuf, sizeof (errbuf),
+                   dgettext(TEXT_DOMAIN,
+                   "cannot hold snapshot '%s@%s'"),
+                   zhp->zfs_name, snapname);
+               (void) zfs_standard_error(zhp->zfs_hdl, ret, errbuf);
+               return (ret);
+       }
+
+       ret = zfs_hold_nvl(zhp, cleanup_fd, ha.nvl);
+       fnvlist_free(ha.nvl);
+
+       return (ret);
+}
+
+int
+zfs_hold_nvl(zfs_handle_t *zhp, int cleanup_fd, nvlist_t *holds)
 {
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       int ret;
+       nvlist_t *errors;
        libzfs_handle_t *hdl = zhp->zfs_hdl;
+       char errbuf[1024];
+       nvpair_t *elem;
 
-       ASSERT(!recursive || dsobj == 0);
+       errors = NULL;
+       ret = lzc_hold(holds, cleanup_fd, &errors);
 
-       (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
-       (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
-       if (strlcpy(zc.zc_string, tag, sizeof (zc.zc_string))
-           >= sizeof (zc.zc_string))
-               return (zfs_error(hdl, EZFS_TAGTOOLONG, tag));
-       zc.zc_cookie = recursive;
-       zc.zc_temphold = temphold;
-       zc.zc_cleanup_fd = cleanup_fd;
-       zc.zc_sendobj = dsobj;
-       zc.zc_createtxg = createtxg;
+       if (ret == 0) {
+               /* There may be errors even in the success case. */
+               fnvlist_free(errors);
+               return (0);
+       }
 
-       if (zfs_ioctl(hdl, ZFS_IOC_HOLD, &zc) != 0) {
-               char errbuf[ZFS_MAXNAMELEN+32];
+       if (nvlist_empty(errors)) {
+               /* no hold-specific errors */
+               (void) snprintf(errbuf, sizeof (errbuf),
+                   dgettext(TEXT_DOMAIN, "cannot hold"));
+               switch (ret) {
+               case ENOTSUP:
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "pool must be upgraded"));
+                       (void) zfs_error(hdl, EZFS_BADVERSION, errbuf);
+                       break;
+               case EINVAL:
+                       (void) zfs_error(hdl, EZFS_BADTYPE, errbuf);
+                       break;
+               default:
+                       (void) zfs_standard_error(hdl, ret, errbuf);
+               }
+       }
 
-               /*
-                * if it was recursive, the one that actually failed will be in
-                * zc.zc_name.
-                */
-               (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
-                   "cannot hold '%s@%s'"), zc.zc_name, snapname);
-               switch (errno) {
+       for (elem = nvlist_next_nvpair(errors, NULL);
+           elem != NULL;
+           elem = nvlist_next_nvpair(errors, elem)) {
+               (void) snprintf(errbuf, sizeof (errbuf),
+                   dgettext(TEXT_DOMAIN,
+                   "cannot hold snapshot '%s'"), nvpair_name(elem));
+               switch (fnvpair_value_int32(elem)) {
                case E2BIG:
                        /*
                         * Temporary tags wind up having the ds object id
@@ -4245,77 +4630,145 @@ zfs_hold(zfs_handle_t *zhp, const char *snapname, const char *tag,
                         * above, it's still possible for the tag to wind
                         * up being slightly too long.
                         */
-                       return (zfs_error(hdl, EZFS_TAGTOOLONG, errbuf));
-               case ENOTSUP:
-                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-                           "pool must be upgraded"));
-                       return (zfs_error(hdl, EZFS_BADVERSION, errbuf));
+                       (void) zfs_error(hdl, EZFS_TAGTOOLONG, errbuf);
+                       break;
                case EINVAL:
-                       return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
+                       (void) zfs_error(hdl, EZFS_BADTYPE, errbuf);
+                       break;
                case EEXIST:
-                       return (zfs_error(hdl, EZFS_REFTAG_HOLD, errbuf));
-               case ENOENT:
-                       if (enoent_ok)
-                               return (ENOENT);
-                       /* FALLTHROUGH */
+                       (void) zfs_error(hdl, EZFS_REFTAG_HOLD, errbuf);
+                       break;
                default:
-                       return (zfs_standard_error_fmt(hdl, errno, errbuf));
+                       (void) zfs_standard_error(hdl,
+                           fnvpair_value_int32(elem), errbuf);
                }
        }
 
-       return (0);
+       fnvlist_free(errors);
+       return (ret);
+}
+
+static int
+zfs_release_one(zfs_handle_t *zhp, void *arg)
+{
+       struct holdarg *ha = arg;
+       char name[ZFS_MAX_DATASET_NAME_LEN];
+       int rv = 0;
+       nvlist_t *existing_holds;
+
+       if (snprintf(name, sizeof (name), "%s@%s", zhp->zfs_name,
+           ha->snapname) >= sizeof (name)) {
+               ha->error = EINVAL;
+               rv = EINVAL;
+       }
+
+       if (lzc_get_holds(name, &existing_holds) != 0) {
+               ha->error = ENOENT;
+       } else if (!nvlist_exists(existing_holds, ha->tag)) {
+               ha->error = ESRCH;
+       } else {
+               nvlist_t *torelease = fnvlist_alloc();
+               fnvlist_add_boolean(torelease, ha->tag);
+               fnvlist_add_nvlist(ha->nvl, name, torelease);
+               fnvlist_free(torelease);
+       }
+
+       if (ha->recursive)
+               rv = zfs_iter_filesystems(zhp, zfs_release_one, ha);
+       zfs_close(zhp);
+       return (rv);
 }
 
 int
 zfs_release(zfs_handle_t *zhp, const char *snapname, const char *tag,
     boolean_t recursive)
 {
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       int ret;
+       struct holdarg ha;
+       nvlist_t *errors = NULL;
+       nvpair_t *elem;
        libzfs_handle_t *hdl = zhp->zfs_hdl;
+       char errbuf[1024];
 
-       (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
-       (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
-       if (strlcpy(zc.zc_string, tag, sizeof (zc.zc_string))
-           >= sizeof (zc.zc_string))
-               return (zfs_error(hdl, EZFS_TAGTOOLONG, tag));
-       zc.zc_cookie = recursive;
+       ha.nvl = fnvlist_alloc();
+       ha.snapname = snapname;
+       ha.tag = tag;
+       ha.recursive = recursive;
+       ha.error = 0;
+       (void) zfs_release_one(zfs_handle_dup(zhp), &ha);
+
+       if (nvlist_empty(ha.nvl)) {
+               fnvlist_free(ha.nvl);
+               ret = ha.error;
+               (void) snprintf(errbuf, sizeof (errbuf),
+                   dgettext(TEXT_DOMAIN,
+                   "cannot release hold from snapshot '%s@%s'"),
+                   zhp->zfs_name, snapname);
+               if (ret == ESRCH) {
+                       (void) zfs_error(hdl, EZFS_REFTAG_RELE, errbuf);
+               } else {
+                       (void) zfs_standard_error(hdl, ret, errbuf);
+               }
+               return (ret);
+       }
 
-       if (zfs_ioctl(hdl, ZFS_IOC_RELEASE, &zc) != 0) {
-               char errbuf[ZFS_MAXNAMELEN+32];
+       ret = lzc_release(ha.nvl, &errors);
+       fnvlist_free(ha.nvl);
 
-               /*
-                * if it was recursive, the one that actually failed will be in
-                * zc.zc_name.
-                */
+       if (ret == 0) {
+               /* There may be errors even in the success case. */
+               fnvlist_free(errors);
+               return (0);
+       }
+
+       if (nvlist_empty(errors)) {
+               /* no hold-specific errors */
                (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
-                   "cannot release '%s' from '%s@%s'"), tag, zc.zc_name,
-                   snapname);
+                   "cannot release"));
                switch (errno) {
-               case ESRCH:
-                       return (zfs_error(hdl, EZFS_REFTAG_RELE, errbuf));
                case ENOTSUP:
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "pool must be upgraded"));
-                       return (zfs_error(hdl, EZFS_BADVERSION, errbuf));
+                       (void) zfs_error(hdl, EZFS_BADVERSION, errbuf);
+                       break;
+               default:
+                       (void) zfs_standard_error_fmt(hdl, errno, errbuf);
+               }
+       }
+
+       for (elem = nvlist_next_nvpair(errors, NULL);
+           elem != NULL;
+           elem = nvlist_next_nvpair(errors, elem)) {
+               (void) snprintf(errbuf, sizeof (errbuf),
+                   dgettext(TEXT_DOMAIN,
+                   "cannot release hold from snapshot '%s'"),
+                   nvpair_name(elem));
+               switch (fnvpair_value_int32(elem)) {
+               case ESRCH:
+                       (void) zfs_error(hdl, EZFS_REFTAG_RELE, errbuf);
+                       break;
                case EINVAL:
-                       return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
+                       (void) zfs_error(hdl, EZFS_BADTYPE, errbuf);
+                       break;
                default:
-                       return (zfs_standard_error_fmt(hdl, errno, errbuf));
+                       (void) zfs_standard_error_fmt(hdl,
+                           fnvpair_value_int32(elem), errbuf);
                }
        }
 
-       return (0);
+       fnvlist_free(errors);
+       return (ret);
 }
 
 int
 zfs_get_fsacl(zfs_handle_t *zhp, nvlist_t **nvl)
 {
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       zfs_cmd_t zc = {"\0"};
        libzfs_handle_t *hdl = zhp->zfs_hdl;
        int nvsz = 2048;
        void *nvbuf;
        int err = 0;
-       char errbuf[ZFS_MAXNAMELEN+32];
+       char errbuf[1024];
 
        assert(zhp->zfs_type == ZFS_TYPE_VOLUME ||
            zhp->zfs_type == ZFS_TYPE_FILESYSTEM);
@@ -4331,9 +4784,9 @@ tryagain:
        zc.zc_nvlist_dst_size = nvsz;
        zc.zc_nvlist_dst = (uintptr_t)nvbuf;
 
-       (void) strlcpy(zc.zc_name, zhp->zfs_name, ZFS_MAXNAMELEN);
+       (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
 
-       if (zfs_ioctl(hdl, ZFS_IOC_GET_FSACL, &zc) != 0) {
+       if (ioctl(hdl->libzfs_fd, ZFS_IOC_GET_FSACL, &zc) != 0) {
                (void) snprintf(errbuf, sizeof (errbuf),
                    dgettext(TEXT_DOMAIN, "cannot get permissions on '%s'"),
                    zc.zc_name);
@@ -4377,10 +4830,10 @@ out:
 int
 zfs_set_fsacl(zfs_handle_t *zhp, boolean_t un, nvlist_t *nvl)
 {
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       zfs_cmd_t zc = {"\0"};
        libzfs_handle_t *hdl = zhp->zfs_hdl;
        char *nvbuf;
-       char errbuf[ZFS_MAXNAMELEN+32];
+       char errbuf[1024];
        size_t nvsz;
        int err;
 
@@ -4431,38 +4884,18 @@ zfs_set_fsacl(zfs_handle_t *zhp, boolean_t un, nvlist_t *nvl)
 int
 zfs_get_holds(zfs_handle_t *zhp, nvlist_t **nvl)
 {
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
-       libzfs_handle_t *hdl = zhp->zfs_hdl;
-       int nvsz = 2048;
-       void *nvbuf;
-       int err = 0;
-       char errbuf[ZFS_MAXNAMELEN+32];
-
-       assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT);
-
-tryagain:
-
-       nvbuf = malloc(nvsz);
-       if (nvbuf == NULL) {
-               err = (zfs_error(hdl, EZFS_NOMEM, strerror(errno)));
-               goto out;
-       }
+       int err;
+       char errbuf[1024];
 
-       zc.zc_nvlist_dst_size = nvsz;
-       zc.zc_nvlist_dst = (uintptr_t)nvbuf;
+       err = lzc_get_holds(zhp->zfs_name, nvl);
 
-       (void) strlcpy(zc.zc_name, zhp->zfs_name, ZFS_MAXNAMELEN);
+       if (err != 0) {
+               libzfs_handle_t *hdl = zhp->zfs_hdl;
 
-       if (zfs_ioctl(hdl, ZFS_IOC_GET_HOLDS, &zc) != 0) {
                (void) snprintf(errbuf, sizeof (errbuf),
                    dgettext(TEXT_DOMAIN, "cannot get holds for '%s'"),
-                   zc.zc_name);
-               switch (errno) {
-               case ENOMEM:
-                       free(nvbuf);
-                       nvsz = zc.zc_nvlist_dst_size;
-                       goto tryagain;
-
+                   zhp->zfs_name);
+               switch (err) {
                case ENOTSUP:
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "pool must be upgraded"));
@@ -4478,22 +4911,16 @@ tryagain:
                        err = zfs_standard_error_fmt(hdl, errno, errbuf);
                        break;
                }
-       } else {
-               /* success */
-               int rc = nvlist_unpack(nvbuf, zc.zc_nvlist_dst_size, nvl, 0);
-               if (rc) {
-                       (void) snprintf(errbuf, sizeof (errbuf),
-                           dgettext(TEXT_DOMAIN, "cannot get holds for '%s'"),
-                           zc.zc_name);
-                       err = zfs_standard_error_fmt(hdl, rc, errbuf);
-               }
        }
 
-       free(nvbuf);
-out:
        return (err);
 }
 
+/*
+ * Convert the zvol's volume size to an appropriate reservation.
+ * Note: If this routine is updated, it is necessary to update the ZFS test
+ * suite's shell version in reservation.kshlib.
+ */
 uint64_t
 zvol_volsize_to_reservation(uint64_t volsize, nvlist_t *props)
 {