Illumos #3098 zfs userspace/groupspace fail
[zfs.git] / lib / libzfs / libzfs_dataset.c
index 44fdadd..554b0ea 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) 2012 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 2012 Nexenta Systems, Inc. All rights reserved.
  */
 
 #include <ctype.h>
@@ -413,6 +415,9 @@ make_dataset_handle_common(zfs_handle_t *zhp, zfs_cmd_t *zc)
                zhp->zfs_head_type = ZFS_TYPE_VOLUME;
        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 */
        else
                abort();
 
@@ -460,7 +465,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);
@@ -477,6 +482,70 @@ 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);
+}
+
 /*
  * Opens the given snapshot, filesystem, or volume.   The 'types'
  * argument is a mask of acceptable types.  The function will print an
@@ -840,6 +909,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) {
@@ -1856,8 +1931,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;
@@ -1872,6 +1945,120 @@ 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_MAXNAMELEN];
+};
+
+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) {
+               if (nvlist_add_boolean(gca->value, zfs_get_name(zhp)) != 0) {
+                       zfs_close(zhp);
+                       return (no_memory(zhp->zfs_hdl));
+               }
+               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_MAXNAMELEN];
+                       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);
+
+                       (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
@@ -2000,6 +2187,11 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
                        return (-1);
                break;
 
+       case ZFS_PROP_CLONES:
+               if (get_clones_string(zhp, propbuf, proplen) != 0)
+                       return (-1);
+               break;
+
        case ZFS_PROP_QUOTA:
        case ZFS_PROP_REFQUOTA:
        case ZFS_PROP_RESERVATION:
@@ -2122,6 +2314,17 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
                }
                break;
 
+       case ZFS_PROP_GUID:
+               /*
+                * 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;
+
        default:
                switch (zfs_prop_get_type(prop)) {
                case PROP_TYPE_NUMBER:
@@ -2329,29 +2532,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);
@@ -2364,7 +2567,7 @@ zfs_prop_get_userquota_common(zfs_handle_t *zhp, const char *propname,
        int err;
        zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 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),
@@ -2417,144 +2620,96 @@ zfs_prop_get_userquota(zfs_handle_t *zhp, const char *propname,
        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", "\0", "\0", "\0", 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", (long long unsigned int)propvalue);
+       } else {
+               zfs_nicenum(propvalue, propbuf, proplen);
        }
-       zcmd_free_nvlists(&zc);
-       return ((ret < 0) ? ret : 0);
+
+       return (0);
 }
 
-/*
- * Iterate over all snapshots
- */
 int
-zfs_iter_snapshots(zfs_handle_t *zhp, zfs_iter_f func, void *data)
+zfs_get_snapused_int(zfs_handle_t *firstsnap, zfs_handle_t *lastsnap,
+    uint64_t *usedp)
 {
+       int err;
        zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
-       zfs_handle_t *nzhp;
-       int ret;
 
-       if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT)
-               return (0);
+       (void) strlcpy(zc.zc_name, lastsnap->zfs_name, sizeof (zc.zc_name));
+       (void) strlcpy(zc.zc_value, firstsnap->zfs_name, sizeof (zc.zc_value));
 
-       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) {
+       err = ioctl(lastsnap->zfs_hdl->libzfs_fd, ZFS_IOC_SPACE_SNAPS, &zc);
+       if (err)
+               return (err);
 
-               if ((nzhp = make_dataset_handle_zc(zhp->zfs_hdl,
-                   &zc)) == NULL) {
-                       continue;
-               }
+       *usedp = zc.zc_cookie;
 
-               if ((ret = func(nzhp, data)) != 0) {
-                       zcmd_free_nvlists(&zc);
-                       return (ret);
-               }
-       }
-       zcmd_free_nvlists(&zc);
-       return ((ret < 0) ? ret : 0);
+       return (0);
 }
 
 /*
- * Iterate over all children, snapshots and filesystems
+ * Returns the name of the given zfs handle.
  */
-int
-zfs_iter_children(zfs_handle_t *zhp, zfs_iter_f func, void *data)
+const char *
+zfs_get_name(const zfs_handle_t *zhp)
 {
-       int ret;
-
-       if ((ret = zfs_iter_filesystems(zhp, func, data)) != 0)
-               return (ret);
+       return (zhp->zfs_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);
 }
 
 /*
@@ -2580,18 +2735,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);
 }
@@ -2974,7 +3130,8 @@ 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)
@@ -2993,7 +3150,8 @@ zfs_destroy(zfs_handle_t *zhp, boolean_t defer)
        }
 
        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));
@@ -3005,9 +3163,8 @@ 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
@@ -3016,16 +3173,14 @@ 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;
        int rv = 0;
 
-       (void) strlcpy(name, zhp->zfs_name, sizeof (name));
-       (void) strlcat(name, "@", sizeof (name));
-       (void) strlcat(name, dd->snapname, sizeof (name));
+       (void) snprintf(name, sizeof (name),
+           "%s@%s", zhp->zfs_name, dd->snapname);
 
        szhp = make_dataset_handle(zhp->zfs_hdl, name);
        if (szhp) {
-               dd->gotone = B_TRUE;
+               verify(nvlist_add_boolean(dd->nvl, name) == 0);
                zfs_close(szhp);
        }
 
@@ -3038,10 +3193,8 @@ zfs_check_snap_cb(zfs_handle_t *zhp, void *arg)
                 */
        }
 
-       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);
 }
 
@@ -3051,29 +3204,45 @@ 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_next_nvpair(dd.nvl, NULL) == NULL) {
+               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, dd.nvl, defer);
        }
+       nvlist_free(dd.nvl);
+       return (ret);
+}
+
+/*
+ * Destroys all the snapshots named in the nvlist.  They must be underneath
+ * the zhp (either snapshots of it, or snapshots of its descendants).
+ */
+int
+zfs_destroy_snaps_nvl(zfs_handle_t *zhp, nvlist_t *snaps, boolean_t defer)
+{
+       int ret;
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
 
        (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
-       (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
+       if (zcmd_write_src_nvlist(zhp->zfs_hdl, &zc, snaps) != 0)
+               return (-1);
        zc.zc_defer_destroy = defer;
 
-       ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DESTROY_SNAPS, &zc);
+       ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DESTROY_SNAPS_NVL, &zc);
        if (ret != 0) {
                char errbuf[1024];
 
                (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
-                   "cannot destroy '%s@%s'"), zc.zc_name, snapname);
+                   "cannot destroy snapshots in %s"), zc.zc_name);
 
                switch (errno) {
                case EEXIST:
@@ -3109,7 +3278,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));
 
@@ -3284,7 +3453,7 @@ zfs_promote(zfs_handle_t *zhp)
                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);
+       ret = zfs_iter_snapshots(pzhp, B_FALSE, promote_snap_cb, &pd);
        if (ret != 0) {
                zfs_close(pzhp);
                return (-1);
@@ -3299,7 +3468,8 @@ zfs_promote(zfs_handle_t *zhp)
        if (ret != 0) {
                int save_errno = errno;
 
-               (void) zfs_iter_snapshots(pzhp, promote_snap_done_cb, &pd);
+               (void) zfs_iter_snapshots(pzhp, B_FALSE, promote_snap_done_cb,
+                   &pd);
                zfs_close(pzhp);
 
                switch (save_errno) {
@@ -3318,7 +3488,8 @@ zfs_promote(zfs_handle_t *zhp)
                        return (zfs_standard_error(hdl, save_errno, errbuf));
                }
        } else {
-               (void) zfs_iter_snapshots(zhp, promote_snap_done_cb, &pd);
+               (void) zfs_iter_snapshots(zhp, B_FALSE, promote_snap_done_cb,
+                   &pd);
        }
 
        zfs_close(pzhp);
@@ -3529,7 +3700,7 @@ 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;
@@ -3600,46 +3771,11 @@ zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
 }
 
 /*
- * 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 };
@@ -3752,16 +3888,17 @@ zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive)
                }
 
                dd.snapname = delim + 1;
-               dd.gotone = B_FALSE;
-               dd.closezhp = B_TRUE;
 
                /* We remove any zvol links prior to renaming them */
+               verify(nvlist_alloc(&dd.nvl, NV_UNIQUE_NAME, 0) == 0);
                ret = zfs_iter_filesystems(zhrp, zfs_check_snap_cb, &dd);
+               nvlist_free(dd.nvl);
                if (ret) {
                        goto error;
                }
        } else {
-               if ((cl = changelist_gather(zhp, ZFS_PROP_NAME, 0, 0)) == NULL)
+               if ((cl = changelist_gather(zhp, ZFS_PROP_NAME, 0,
+                   force_unmount ? MS_FORCE : 0)) == NULL)
                        return (-1);
 
                if (changelist_haszonedchild(cl)) {
@@ -3879,6 +4016,14 @@ zvol_create_link_common(libzfs_handle_t *hdl, const char *dataset, int ifexists)
                         */
                        return (0);
 
+               case ENODEV:
+                       /*
+                        * snapdev set to hidden :
+                        *  device creation was not permitted (see zvol.c)
+                        *  ignore error quietly
+                        */
+                       return (0);
+
                case ENOENT:
                        /*
                         * Dataset does not exist in the kernel.  If we
@@ -3917,10 +4062,29 @@ int
 zvol_remove_link(libzfs_handle_t *hdl, const char *dataset)
 {
        zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       int timeout = 3000; /* in milliseconds */
+       int error = 0;
+       int i;
 
        (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
 
-       if (ioctl(hdl->libzfs_fd, ZFS_IOC_REMOVE_MINOR, &zc) != 0) {
+       /*
+        * Due to concurrent updates by udev the device may be reported as
+        * busy.  In this case don't immediately fail.  Instead briefly delay
+        * and retry the ioctl() which is now likely to succeed.  If unable
+        * remove the link after timeout milliseconds return the failure.
+        */
+       for (i = 0; i < timeout; i++) {
+               error = ioctl(hdl->libzfs_fd, ZFS_IOC_REMOVE_MINOR, &zc);
+               if (error && errno == EBUSY) {
+                       usleep(1000);
+                       continue;
+               } else {
+                       break;
+               }
+       }
+
+       if (error) {
                switch (errno) {
                case ENXIO:
                        /*
@@ -3933,7 +4097,7 @@ zvol_remove_link(libzfs_handle_t *hdl, const char *dataset)
                default:
                        return (zfs_standard_error_fmt(hdl, errno,
                            dgettext(TEXT_DOMAIN, "cannot remove device "
-                           "links for '%s'"), dataset));
+                           "links for '%s': %s"), dataset, strerror(errno)));
                }
        }
 
@@ -4173,35 +4337,40 @@ 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_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[ZFS_MAXNAMELEN + 32];
+
+                       (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);
 }
 
 int
@@ -4330,7 +4499,7 @@ tryagain:
 
        (void) strlcpy(zc.zc_name, zhp->zfs_name, ZFS_MAXNAMELEN);
 
-       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);