Illumos #1644, #1645, #1646, #1647, #1708
authorMatthew Ahrens <matt@delphix.com>
Thu, 17 Nov 2011 18:14:36 +0000 (10:14 -0800)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Tue, 31 Jul 2012 16:25:30 +0000 (09:25 -0700)
1644 add ZFS "clones" property
1645 add ZFS "written" and "written@..." properties
1646 "zfs send" should estimate size of stream
1647 "zfs destroy" should determine space reclaimed by
     destroying multiple snapshots
1708 adjust size of zpool history data

References:
  https://www.illumos.org/issues/1644
  https://www.illumos.org/issues/1645
  https://www.illumos.org/issues/1646
  https://www.illumos.org/issues/1647
  https://www.illumos.org/issues/1708

This commit modifies the user to kernel space ioctl ABI.  Extra
care should be taken when updating to ensure both the kernel
modules and utilities are updated.  This change has reordered
all of the new ioctl()s to the end of the list.  This should
help minimize this issue in the future.

Reviewed by: Richard Lowe <richlowe@richlowe.net>
Reviewed by: George Wilson <gwilson@zfsmail.com>
Reviewed by: Albert Lee <trisk@opensolaris.org>
Approved by: Garrett D'Amore <garret@nexenta.com>

Ported by: Martin Matuska <martin@matuska.org>
Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Closes #826
Closes #664

24 files changed:
cmd/zfs/zfs_main.c
include/libzfs.h
include/libzfs_impl.h
include/sys/dmu.h
include/sys/dsl_dataset.h
include/sys/dsl_deleg.h
include/sys/fs/zfs.h
lib/libzfs/Makefile.am
lib/libzfs/Makefile.in
lib/libzfs/libzfs_dataset.c
lib/libzfs/libzfs_iter.c [new file with mode: 0644]
lib/libzfs/libzfs_sendrecv.c
lib/libzfs/libzfs_util.c
man/man8/zfs.8
module/zcommon/zfs_prop.c
module/zfs/bpobj.c
module/zfs/dmu_send.c
module/zfs/dsl_dataset.c
module/zfs/dsl_deadlist.c
module/zfs/dsl_deleg.c
module/zfs/dsl_pool.c
module/zfs/spa_history.c
module/zfs/zap_micro.c
module/zfs/zfs_ioctl.c

index 0f00688..3bcf626 100644 (file)
@@ -22,6 +22,7 @@
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
  * Copyright 2011 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  */
 
 #include <assert.h>
@@ -140,7 +141,7 @@ typedef enum {
        HELP_HOLD,
        HELP_HOLDS,
        HELP_RELEASE,
-       HELP_DIFF
+       HELP_DIFF,
 } zfs_help_t;
 
 typedef struct zfs_command {
@@ -212,8 +213,9 @@ get_usage(zfs_help_t idx)
                    "\tcreate [-ps] [-b blocksize] [-o property=value] ... "
                    "-V <size> <volume>\n"));
        case HELP_DESTROY:
-               return (gettext("\tdestroy [-rRf] <filesystem|volume>\n"
-                   "\tdestroy [-rRd] <snapshot>\n"));
+               return (gettext("\tdestroy [-fnpRrv] <filesystem|volume>\n"
+                   "\tdestroy [-dnpRrv] "
+                   "<filesystem|volume>@<snap>[%<snap>][,...]\n"));
        case HELP_GET:
                return (gettext("\tget [-rHp] [-d max] "
                    "[-o \"all\" | field[,...]] [-s source[,...]]\n"
@@ -247,7 +249,8 @@ get_usage(zfs_help_t idx)
        case HELP_ROLLBACK:
                return (gettext("\trollback [-rRf] <snapshot>\n"));
        case HELP_SEND:
-               return (gettext("\tsend [-vRDp] [-[iI] snapshot] <snapshot>\n"));
+               return (gettext("\tsend [-DnPpRrv] [-[iI] snapshot] "
+                   "<snapshot>\n"));
        case HELP_SET:
                return (gettext("\tset <property=value> "
                    "<filesystem|volume|snapshot> ...\n"));
@@ -426,6 +429,8 @@ usage(boolean_t requested)
                (void) fprintf(fp, "YES       NO   <size> | none\n");
                (void) fprintf(fp, "\t%-15s ", "groupquota@...");
                (void) fprintf(fp, "YES       NO   <size> | none\n");
+               (void) fprintf(fp, "\t%-15s ", "written@<snap>");
+               (void) fprintf(fp, " NO       NO   <size>\n");
 
                (void) fprintf(fp, gettext("\nSizes are specified in bytes "
                    "with standard units such as K, M, G, etc.\n"));
@@ -872,15 +877,23 @@ badusage:
  */
 typedef struct destroy_cbdata {
        boolean_t       cb_first;
-       int             cb_force;
-       int             cb_recurse;
-       int             cb_error;
-       int             cb_needforce;
-       int             cb_doclones;
-       boolean_t       cb_closezhp;
+       boolean_t       cb_force;
+       boolean_t       cb_recurse;
+       boolean_t       cb_error;
+       boolean_t       cb_doclones;
        zfs_handle_t    *cb_target;
-       char            *cb_snapname;
        boolean_t       cb_defer_destroy;
+       boolean_t       cb_verbose;
+       boolean_t       cb_parsable;
+       boolean_t       cb_dryrun;
+       nvlist_t        *cb_nvl;
+
+       /* first snap in contiguous run */
+       zfs_handle_t    *cb_firstsnap;
+       /* previous snap in contiguous run */
+       zfs_handle_t    *cb_prevsnap;
+       int64_t         cb_snapused;
+       char            *cb_snapspec;
 } destroy_cbdata_t;
 
 /*
@@ -910,7 +923,7 @@ destroy_check_dependent(zfs_handle_t *zhp, void *data)
                        (void) fprintf(stderr, gettext("use '-r' to destroy "
                            "the following datasets:\n"));
                        cbp->cb_first = B_FALSE;
-                       cbp->cb_error = 1;
+                       cbp->cb_error = B_TRUE;
                }
 
                (void) fprintf(stderr, "%s\n", zfs_get_name(zhp));
@@ -931,7 +944,8 @@ destroy_check_dependent(zfs_handle_t *zhp, void *data)
                        (void) fprintf(stderr, gettext("use '-R' to destroy "
                            "the following datasets:\n"));
                        cbp->cb_first = B_FALSE;
-                       cbp->cb_error = 1;
+                       cbp->cb_error = B_TRUE;
+                       cbp->cb_dryrun = B_TRUE;
                }
 
                (void) fprintf(stderr, "%s\n", zfs_get_name(zhp));
@@ -945,7 +959,20 @@ out:
 static int
 destroy_callback(zfs_handle_t *zhp, void *data)
 {
-       destroy_cbdata_t *cbp = data;
+       destroy_cbdata_t *cb = data;
+       const char *name = zfs_get_name(zhp);
+
+       if (cb->cb_verbose) {
+               if (cb->cb_parsable) {
+                       (void) printf("destroy\t%s\n", name);
+               } else if (cb->cb_dryrun) {
+                       (void) printf(gettext("would destroy %s\n"),
+                           name);
+               } else {
+                       (void) printf(gettext("will destroy %s\n"),
+                           name);
+               }
+       }
 
        /*
         * Ignore pools (which we've already flagged as an error before getting
@@ -957,13 +984,12 @@ destroy_callback(zfs_handle_t *zhp, void *data)
                return (0);
        }
 
-       /*
-        * Bail out on the first error.
-        */
-       if (zfs_unmount(zhp, NULL, cbp->cb_force ? MS_FORCE : 0) != 0 ||
-           zfs_destroy(zhp, cbp->cb_defer_destroy) != 0) {
-               zfs_close(zhp);
-               return (-1);
+       if (!cb->cb_dryrun) {
+               if (zfs_unmount(zhp, NULL, cb->cb_force ? MS_FORCE : 0) != 0 ||
+                   zfs_destroy(zhp, cb->cb_defer_destroy) != 0) {
+                       zfs_close(zhp);
+                       return (-1);
+               }
        }
 
        zfs_close(zhp);
@@ -971,39 +997,142 @@ destroy_callback(zfs_handle_t *zhp, void *data)
 }
 
 static int
-destroy_snap_clones(zfs_handle_t *zhp, void *arg)
+destroy_print_cb(zfs_handle_t *zhp, void *arg)
 {
-       destroy_cbdata_t *cbp = arg;
-       char thissnap[MAXPATHLEN];
-       zfs_handle_t *szhp;
-       boolean_t closezhp = cbp->cb_closezhp;
-       int rv;
+       destroy_cbdata_t *cb = arg;
+       const char *name = zfs_get_name(zhp);
+       int err = 0;
 
-       (void) snprintf(thissnap, sizeof (thissnap),
-           "%s@%s", zfs_get_name(zhp), cbp->cb_snapname);
+       if (nvlist_exists(cb->cb_nvl, name)) {
+               if (cb->cb_firstsnap == NULL)
+                       cb->cb_firstsnap = zfs_handle_dup(zhp);
+               if (cb->cb_prevsnap != NULL)
+                       zfs_close(cb->cb_prevsnap);
+               /* this snap continues the current range */
+               cb->cb_prevsnap = zfs_handle_dup(zhp);
+               if (cb->cb_verbose) {
+                       if (cb->cb_parsable) {
+                               (void) printf("destroy\t%s\n", name);
+                       } else if (cb->cb_dryrun) {
+                               (void) printf(gettext("would destroy %s\n"),
+                                   name);
+                       } else {
+                               (void) printf(gettext("will destroy %s\n"),
+                                   name);
+                       }
+               }
+       } else if (cb->cb_firstsnap != NULL) {
+               /* end of this range */
+               uint64_t used = 0;
+               err = zfs_get_snapused_int(cb->cb_firstsnap,
+                   cb->cb_prevsnap, &used);
+               cb->cb_snapused += used;
+               zfs_close(cb->cb_firstsnap);
+               cb->cb_firstsnap = NULL;
+               zfs_close(cb->cb_prevsnap);
+               cb->cb_prevsnap = NULL;
+       }
+       zfs_close(zhp);
+       return (err);
+}
 
-       libzfs_print_on_error(g_zfs, B_FALSE);
-       szhp = zfs_open(g_zfs, thissnap, ZFS_TYPE_SNAPSHOT);
-       libzfs_print_on_error(g_zfs, B_TRUE);
-       if (szhp) {
-               /*
-                * Destroy any clones of this snapshot
-                */
-               if (zfs_iter_dependents(szhp, B_FALSE, destroy_callback,
-                   cbp) != 0) {
-                       zfs_close(szhp);
-                       if (closezhp)
-                               zfs_close(zhp);
-                       return (-1);
+static int
+destroy_print_snapshots(zfs_handle_t *fs_zhp, destroy_cbdata_t *cb)
+{
+       int err;
+       assert(cb->cb_firstsnap == NULL);
+       assert(cb->cb_prevsnap == NULL);
+       err = zfs_iter_snapshots_sorted(fs_zhp, destroy_print_cb, cb);
+       if (cb->cb_firstsnap != NULL) {
+               uint64_t used = 0;
+               if (err == 0) {
+                       err = zfs_get_snapused_int(cb->cb_firstsnap,
+                           cb->cb_prevsnap, &used);
                }
-               zfs_close(szhp);
+               cb->cb_snapused += used;
+               zfs_close(cb->cb_firstsnap);
+               cb->cb_firstsnap = NULL;
+               zfs_close(cb->cb_prevsnap);
+               cb->cb_prevsnap = NULL;
        }
+       return (err);
+}
 
-       cbp->cb_closezhp = B_TRUE;
-       rv = zfs_iter_filesystems(zhp, destroy_snap_clones, arg);
-       if (closezhp)
-               zfs_close(zhp);
-       return (rv);
+static int
+snapshot_to_nvl_cb(zfs_handle_t *zhp, void *arg)
+{
+       destroy_cbdata_t *cb = arg;
+       int err = 0;
+
+       /* Check for clones. */
+       if (!cb->cb_doclones) {
+               cb->cb_target = zhp;
+               cb->cb_first = B_TRUE;
+               err = zfs_iter_dependents(zhp, B_TRUE,
+                   destroy_check_dependent, cb);
+       }
+
+       if (err == 0) {
+               if (nvlist_add_boolean(cb->cb_nvl, zfs_get_name(zhp)))
+                       nomem();
+       }
+       zfs_close(zhp);
+       return (err);
+}
+
+static int
+gather_snapshots(zfs_handle_t *zhp, void *arg)
+{
+       destroy_cbdata_t *cb = arg;
+       int err = 0;
+
+       err = zfs_iter_snapspec(zhp, cb->cb_snapspec, snapshot_to_nvl_cb, cb);
+       if (err == ENOENT)
+               err = 0;
+       if (err != 0)
+               goto out;
+
+       if (cb->cb_verbose) {
+               err = destroy_print_snapshots(zhp, cb);
+               if (err != 0)
+                       goto out;
+       }
+
+       if (cb->cb_recurse)
+               err = zfs_iter_filesystems(zhp, gather_snapshots, cb);
+
+out:
+       zfs_close(zhp);
+       return (err);
+}
+
+static int
+destroy_clones(destroy_cbdata_t *cb)
+{
+       nvpair_t *pair;
+       for (pair = nvlist_next_nvpair(cb->cb_nvl, NULL);
+           pair != NULL;
+           pair = nvlist_next_nvpair(cb->cb_nvl, pair)) {
+               zfs_handle_t *zhp = zfs_open(g_zfs, nvpair_name(pair),
+                   ZFS_TYPE_SNAPSHOT);
+               if (zhp != NULL) {
+                       boolean_t defer = cb->cb_defer_destroy;
+                       int err;
+
+                       /*
+                        * We can't defer destroy non-snapshots, so set it to
+                        * false while destroying the clones.
+                        */
+                       cb->cb_defer_destroy = B_FALSE;
+                       err = zfs_iter_dependents(zhp, B_FALSE,
+                           destroy_callback, cb);
+                       cb->cb_defer_destroy = defer;
+                       zfs_close(zhp);
+                       if (err != 0)
+                               return (err);
+               }
+       }
+       return (0);
 }
 
 static int
@@ -1012,25 +1141,35 @@ zfs_do_destroy(int argc, char **argv)
        destroy_cbdata_t cb = { 0 };
        int c;
        zfs_handle_t *zhp;
-       char *cp;
+       char *at;
        zfs_type_t type = ZFS_TYPE_DATASET;
 
        /* check options */
-       while ((c = getopt(argc, argv, "dfrR")) != -1) {
+       while ((c = getopt(argc, argv, "vpndfrR")) != -1) {
                switch (c) {
+               case 'v':
+                       cb.cb_verbose = B_TRUE;
+                       break;
+               case 'p':
+                       cb.cb_verbose = B_TRUE;
+                       cb.cb_parsable = B_TRUE;
+                       break;
+               case 'n':
+                       cb.cb_dryrun = B_TRUE;
+                       break;
                case 'd':
                        cb.cb_defer_destroy = B_TRUE;
                        type = ZFS_TYPE_SNAPSHOT;
                        break;
                case 'f':
-                       cb.cb_force = 1;
+                       cb.cb_force = B_TRUE;
                        break;
                case 'r':
-                       cb.cb_recurse = 1;
+                       cb.cb_recurse = B_TRUE;
                        break;
                case 'R':
-                       cb.cb_recurse = 1;
-                       cb.cb_doclones = 1;
+                       cb.cb_recurse = B_TRUE;
+                       cb.cb_doclones = B_TRUE;
                        break;
                case '?':
                default:
@@ -1045,7 +1184,7 @@ zfs_do_destroy(int argc, char **argv)
 
        /* check number of arguments */
        if (argc == 0) {
-               (void) fprintf(stderr, gettext("missing path argument\n"));
+               (void) fprintf(stderr, gettext("missing dataset argument\n"));
                usage(B_FALSE);
        }
        if (argc > 1) {
@@ -1053,91 +1192,117 @@ zfs_do_destroy(int argc, char **argv)
                usage(B_FALSE);
        }
 
-       /*
-        * If we are doing recursive destroy of a snapshot, then the
-        * named snapshot may not exist.  Go straight to libzfs.
-        */
-       if (cb.cb_recurse && (cp = strchr(argv[0], '@'))) {
-               int ret = 0;
+       at = strchr(argv[0], '@');
+       if (at != NULL) {
+               int err = 0;
+
+               /* Build the list of snaps to destroy in cb_nvl. */
+               if (nvlist_alloc(&cb.cb_nvl, NV_UNIQUE_NAME, 0) != 0)
+                       nomem();
 
-               *cp = '\0';
-               if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET)) == NULL)
+               *at = '\0';
+               zhp = zfs_open(g_zfs, argv[0],
+                   ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
+               if (zhp == NULL)
                        return (1);
-               *cp = '@';
-               cp++;
 
-               if (cb.cb_doclones) {
-                       boolean_t defer = cb.cb_defer_destroy;
+               cb.cb_snapspec = at + 1;
+               if (gather_snapshots(zfs_handle_dup(zhp), &cb) != 0 ||
+                   cb.cb_error) {
+                       zfs_close(zhp);
+                       nvlist_free(cb.cb_nvl);
+                       return (1);
+               }
 
-                       /*
-                        * Temporarily ignore the defer_destroy setting since
-                        * it's not supported for clones.
-                        */
-                       cb.cb_defer_destroy = B_FALSE;
-                       cb.cb_snapname = cp;
-                       if (destroy_snap_clones(zhp, &cb) != 0) {
-                               zfs_close(zhp);
-                               return (1);
+               if (nvlist_empty(cb.cb_nvl)) {
+                       (void) fprintf(stderr, gettext("could not find any "
+                           "snapshots to destroy; check snapshot names.\n"));
+                       zfs_close(zhp);
+                       nvlist_free(cb.cb_nvl);
+                       return (1);
+               }
+
+               if (cb.cb_verbose) {
+                       char buf[16];
+                       zfs_nicenum(cb.cb_snapused, buf, sizeof (buf));
+                       if (cb.cb_parsable) {
+                               (void) printf("reclaim\t%llu\n",
+                                   (u_longlong_t)cb.cb_snapused);
+                       } else if (cb.cb_dryrun) {
+                               (void) printf(gettext("would reclaim %s\n"),
+                                   buf);
+                       } else {
+                               (void) printf(gettext("will reclaim %s\n"),
+                                   buf);
                        }
-                       cb.cb_defer_destroy = defer;
                }
 
-               ret = zfs_destroy_snaps(zhp, cp, cb.cb_defer_destroy);
-               zfs_close(zhp);
-               if (ret) {
-                       (void) fprintf(stderr,
-                           gettext("no snapshots destroyed\n"));
+               if (!cb.cb_dryrun) {
+                       if (cb.cb_doclones)
+                               err = destroy_clones(&cb);
+                       if (err == 0) {
+                               err = zfs_destroy_snaps_nvl(zhp, cb.cb_nvl,
+                                   cb.cb_defer_destroy);
+                       }
                }
-               return (ret != 0);
-       }
 
-       /* Open the given dataset */
-       if ((zhp = zfs_open(g_zfs, argv[0], type)) == NULL)
-               return (1);
+               zfs_close(zhp);
+               nvlist_free(cb.cb_nvl);
+               if (err != 0)
+                       return (1);
+       } else {
+               /* Open the given dataset */
+               if ((zhp = zfs_open(g_zfs, argv[0], type)) == NULL)
+                       return (1);
 
-       cb.cb_target = zhp;
+               cb.cb_target = zhp;
 
-       /*
-        * Perform an explicit check for pools before going any further.
-        */
-       if (!cb.cb_recurse && strchr(zfs_get_name(zhp), '/') == NULL &&
-           zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) {
-               (void) fprintf(stderr, gettext("cannot destroy '%s': "
-                   "operation does not apply to pools\n"),
-                   zfs_get_name(zhp));
-               (void) fprintf(stderr, gettext("use 'zfs destroy -r "
-                   "%s' to destroy all datasets in the pool\n"),
-                   zfs_get_name(zhp));
-               (void) fprintf(stderr, gettext("use 'zpool destroy %s' "
-                   "to destroy the pool itself\n"), zfs_get_name(zhp));
-               zfs_close(zhp);
-               return (1);
-       }
+               /*
+                * Perform an explicit check for pools before going any further.
+                */
+               if (!cb.cb_recurse && strchr(zfs_get_name(zhp), '/') == NULL &&
+                   zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) {
+                       (void) fprintf(stderr, gettext("cannot destroy '%s': "
+                           "operation does not apply to pools\n"),
+                           zfs_get_name(zhp));
+                       (void) fprintf(stderr, gettext("use 'zfs destroy -r "
+                           "%s' to destroy all datasets in the pool\n"),
+                           zfs_get_name(zhp));
+                       (void) fprintf(stderr, gettext("use 'zpool destroy %s' "
+                           "to destroy the pool itself\n"), zfs_get_name(zhp));
+                       zfs_close(zhp);
+                       return (1);
+               }
 
-       /*
-        * Check for any dependents and/or clones.
-        */
-       cb.cb_first = B_TRUE;
-       if (!cb.cb_doclones && !cb.cb_defer_destroy &&
-           zfs_iter_dependents(zhp, B_TRUE, destroy_check_dependent,
-           &cb) != 0) {
-               zfs_close(zhp);
-               return (1);
-       }
+               /*
+                * Check for any dependents and/or clones.
+                */
+               cb.cb_first = B_TRUE;
+               if (!cb.cb_doclones &&
+                   zfs_iter_dependents(zhp, B_TRUE, destroy_check_dependent,
+                   &cb) != 0) {
+                       zfs_close(zhp);
+                       return (1);
+               }
 
-       if (cb.cb_error || (!cb.cb_defer_destroy &&
-           (zfs_iter_dependents(zhp, B_FALSE, destroy_callback, &cb) != 0))) {
-               zfs_close(zhp);
-               return (1);
-       }
+               if (cb.cb_error) {
+                       zfs_close(zhp);
+                       return (1);
+               }
 
-       /*
-        * Do the real thing.  The callback will close the handle regardless of
-        * whether it succeeds or not.
-        */
+               if (zfs_iter_dependents(zhp, B_FALSE, destroy_callback,
+                   &cb) != 0) {
+                       zfs_close(zhp);
+                       return (1);
+               }
 
-       if (destroy_callback(zhp, &cb) != 0)
-               return (1);
+               /*
+                * Do the real thing.  The callback will close the
+                * handle regardless of whether it succeeds or not.
+                */
+               if (destroy_callback(zhp, &cb) != 0)
+                       return (1);
+       }
 
        return (0);
 }
@@ -1239,6 +1404,17 @@ get_callback(zfs_handle_t *zhp, void *data)
 
                        zprop_print_one_property(zfs_get_name(zhp), cbp,
                            pl->pl_user_prop, buf, sourcetype, source, NULL);
+               } else if (zfs_prop_written(pl->pl_user_prop)) {
+                       sourcetype = ZPROP_SRC_LOCAL;
+
+                       if (zfs_prop_get_written(zhp, pl->pl_user_prop,
+                           buf, sizeof (buf), cbp->cb_literal) != 0) {
+                               sourcetype = ZPROP_SRC_NONE;
+                               (void) strlcpy(buf, "-", sizeof (buf));
+                       }
+
+                       zprop_print_one_property(zfs_get_name(zhp), cbp,
+                           pl->pl_user_prop, buf, sourcetype, source, NULL);
                } else {
                        if (nvlist_lookup_nvlist(user_props,
                            pl->pl_user_prop, &propval) != 0) {
@@ -1783,8 +1959,8 @@ zfs_do_upgrade(int argc, char **argv)
                    "---------------\n");
                (void) printf(gettext(" 1   Initial ZFS filesystem version\n"));
                (void) printf(gettext(" 2   Enhanced directory entries\n"));
-               (void) printf(gettext(" 3   Case insensitive and File system "
-                   "unique identifier (FUID)\n"));
+               (void) printf(gettext(" 3   Case insensitive and filesystem "
+                   "user identifier (FUID)\n"));
                (void) printf(gettext(" 4   userquota, groupquota "
                    "properties\n"));
                (void) printf(gettext(" 5   System attributes\n"));
@@ -2670,6 +2846,13 @@ print_dataset(zfs_handle_t *zhp, zprop_list_t *pl, boolean_t scripted)
                        else
                                propstr = property;
                        right_justify = B_TRUE;
+               } else if (zfs_prop_written(pl->pl_user_prop)) {
+                       if (zfs_prop_get_written(zhp, pl->pl_user_prop,
+                           property, sizeof (property), B_FALSE) != 0)
+                               propstr = "-";
+                       else
+                               propstr = property;
+                       right_justify = B_TRUE;
                } else {
                        if (nvlist_lookup_nvlist(userprops,
                            pl->pl_user_prop, &propval) != 0)
@@ -3289,9 +3472,6 @@ usage:
 }
 
 /*
- * zfs send [-vDp] -R [-i|-I <@snap>] <fs@snap>
- * zfs send [-vDp] [-i|-I <@snap>] <fs@snap>
- *
  * Send a backup stream to stdout.
  */
 static int
@@ -3303,11 +3483,11 @@ zfs_do_send(int argc, char **argv)
        zfs_handle_t *zhp;
        sendflags_t flags = { 0 };
        int c, err;
-       nvlist_t *dbgnv;
+       nvlist_t *dbgnv = NULL;
        boolean_t extraverbose = B_FALSE;
 
        /* check options */
-       while ((c = getopt(argc, argv, ":i:I:RDpv")) != -1) {
+       while ((c = getopt(argc, argv, ":i:I:RDpvnP")) != -1) {
                switch (c) {
                case 'i':
                        if (fromname)
@@ -3326,6 +3506,10 @@ zfs_do_send(int argc, char **argv)
                case 'p':
                        flags.props = B_TRUE;
                        break;
+               case 'P':
+                       flags.parsable = B_TRUE;
+                       flags.verbose = B_TRUE;
+                       break;
                case 'v':
                        if (flags.verbose)
                                extraverbose = B_TRUE;
@@ -3334,6 +3518,9 @@ zfs_do_send(int argc, char **argv)
                case 'D':
                        flags.dedup = B_TRUE;
                        break;
+               case 'n':
+                       flags.dryrun = B_TRUE;
+                       break;
                case ':':
                        (void) fprintf(stderr, gettext("missing argument for "
                            "'%c' option\n"), optopt);
@@ -3359,7 +3546,7 @@ zfs_do_send(int argc, char **argv)
                usage(B_FALSE);
        }
 
-       if (isatty(STDOUT_FILENO)) {
+       if (!flags.dryrun && isatty(STDOUT_FILENO)) {
                (void) fprintf(stderr,
                    gettext("Error: Stream can not be written to a terminal.\n"
                    "You must redirect standard output.\n"));
@@ -3413,10 +3600,10 @@ zfs_do_send(int argc, char **argv)
        if (flags.replicate && fromname == NULL)
                flags.doall = B_TRUE;
 
-       err = zfs_send(zhp, fromname, toname, flags, STDOUT_FILENO, NULL, 0,
+       err = zfs_send(zhp, fromname, toname, &flags, STDOUT_FILENO, NULL, 0,
            extraverbose ? &dbgnv : NULL);
 
-       if (extraverbose) {
+       if (extraverbose && dbgnv != NULL) {
                /*
                 * dump_nvlist prints to stdout, but that's been
                 * redirected to a file.  Make it print to stderr
@@ -3497,7 +3684,7 @@ zfs_do_receive(int argc, char **argv)
                return (1);
        }
 
-       err = zfs_receive(g_zfs, argv[0], flags, STDIN_FILENO, NULL);
+       err = zfs_receive(g_zfs, argv[0], &flags, STDIN_FILENO, NULL);
 
        return (err != 0);
 }
index 096910b..83e1bee 100644 (file)
@@ -406,6 +406,7 @@ extern void zpool_explain_recover(libzfs_handle_t *, const char *, int,
  * underlying datasets, only the references to them.
  */
 extern zfs_handle_t *zfs_open(libzfs_handle_t *, const char *, int);
+extern zfs_handle_t *zfs_handle_dup(zfs_handle_t *);
 extern void zfs_close(zfs_handle_t *);
 extern zfs_type_t zfs_get_type(const zfs_handle_t *);
 extern const char *zfs_get_name(const zfs_handle_t *);
@@ -439,6 +440,12 @@ extern int zfs_prop_get_userquota_int(zfs_handle_t *zhp, const char *propname,
     uint64_t *propvalue);
 extern int zfs_prop_get_userquota(zfs_handle_t *zhp, const char *propname,
     char *propbuf, int proplen, boolean_t literal);
+extern int zfs_prop_get_written_int(zfs_handle_t *zhp, const char *propname,
+    uint64_t *propvalue);
+extern int zfs_prop_get_written(zfs_handle_t *zhp, const char *propname,
+    char *propbuf, int proplen, boolean_t literal);
+extern int zfs_get_snapused_int(zfs_handle_t *firstsnap, zfs_handle_t *lastsnap,
+    uint64_t *usedp);
 extern uint64_t getprop_uint64(zfs_handle_t *, zfs_prop_t, char **);
 extern uint64_t zfs_prop_get_int(zfs_handle_t *, zfs_prop_t);
 extern int zfs_prop_inherit(zfs_handle_t *, const char *, boolean_t);
@@ -446,6 +453,7 @@ extern const char *zfs_prop_values(zfs_prop_t);
 extern int zfs_prop_is_string(zfs_prop_t prop);
 extern nvlist_t *zfs_get_user_props(zfs_handle_t *);
 extern nvlist_t *zfs_get_recvd_props(zfs_handle_t *);
+extern nvlist_t *zfs_get_clones_nvl(zfs_handle_t *);
 
 typedef struct zprop_list {
        int             pl_prop;
@@ -520,6 +528,7 @@ extern int zfs_iter_dependents(zfs_handle_t *, boolean_t, zfs_iter_f, void *);
 extern int zfs_iter_filesystems(zfs_handle_t *, zfs_iter_f, void *);
 extern int zfs_iter_snapshots(zfs_handle_t *, boolean_t, zfs_iter_f, void *);
 extern int zfs_iter_snapshots_sorted(zfs_handle_t *, zfs_iter_f, void *);
+extern int zfs_iter_snapspec(zfs_handle_t *, const char *, zfs_iter_f, void *);
 
 typedef struct get_all_cb {
        zfs_handle_t    **cb_handles;
@@ -540,6 +549,7 @@ extern int zfs_create(libzfs_handle_t *, const char *, zfs_type_t,
 extern int zfs_create_ancestors(libzfs_handle_t *, const char *);
 extern int zfs_destroy(zfs_handle_t *, boolean_t);
 extern int zfs_destroy_snaps(zfs_handle_t *, char *, boolean_t);
+extern int zfs_destroy_snaps_nvl(zfs_handle_t *, nvlist_t *, boolean_t);
 extern int zfs_clone(zfs_handle_t *, const char *, nvlist_t *);
 extern int zfs_snapshot(libzfs_handle_t *, const char *, boolean_t, nvlist_t *);
 extern int zfs_rollback(zfs_handle_t *, zfs_handle_t *, boolean_t);
@@ -547,29 +557,34 @@ extern int zfs_rename(zfs_handle_t *, const char *, boolean_t);
 
 typedef struct sendflags {
        /* print informational messages (ie, -v was specified) */
-       int verbose : 1;
+       boolean_t verbose;
 
        /* recursive send  (ie, -R) */
-       int replicate : 1;
+       boolean_t replicate;
 
        /* for incrementals, do all intermediate snapshots */
-       int doall : 1; /* (ie, -I) */
+       boolean_t doall;
 
        /* if dataset is a clone, do incremental from its origin */
-       int fromorigin : 1;
+       boolean_t fromorigin;
 
        /* do deduplication */
-       int dedup : 1;
+       boolean_t dedup;
 
        /* send properties (ie, -p) */
-       int props : 1;
+       boolean_t props;
+
+       /* do not send (no-op, ie. -n) */
+       boolean_t dryrun;
+
+       /* parsable verbose output (ie. -P) */
+       boolean_t parsable;
 } sendflags_t;
 
 typedef boolean_t (snapfilter_cb_t)(zfs_handle_t *, void *);
 
-extern int zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
-    sendflags_t flags, int outfd, snapfilter_cb_t filter_func,
-    void *cb_arg, nvlist_t **debugnvp);
+extern int zfs_send(zfs_handle_t *, const char *, const char *,
+    sendflags_t *, int, snapfilter_cb_t, void *, nvlist_t **);
 
 extern int zfs_promote(zfs_handle_t *);
 extern int zfs_hold(zfs_handle_t *, const char *, const char *, boolean_t,
@@ -589,34 +604,34 @@ extern int zfs_set_fsacl(zfs_handle_t *, boolean_t, nvlist_t *);
 
 typedef struct recvflags {
        /* print informational messages (ie, -v was specified) */
-       int verbose : 1;
+       boolean_t verbose;
 
        /* the destination is a prefix, not the exact fs (ie, -d) */
-       int isprefix : 1;
+       boolean_t isprefix;
 
        /*
         * Only the tail of the sent snapshot path is appended to the
         * destination to determine the received snapshot name (ie, -e).
         */
-       int istail : 1;
+       boolean_t istail;
 
        /* do not actually do the recv, just check if it would work (ie, -n) */
-       int dryrun : 1;
+       boolean_t dryrun;
 
        /* rollback/destroy filesystems as necessary (eg, -F) */
-       int force : 1;
+       boolean_t force;
 
        /* set "canmount=off" on all modified filesystems */
-       int canmountoff : 1;
+       boolean_t canmountoff;
 
        /* byteswap flag is used internally; callers need not specify */
-       int byteswap : 1;
+       boolean_t byteswap;
 
        /* do not mount file systems as they are extracted (private) */
-       int nomount : 1;
+       boolean_t nomount;
 } recvflags_t;
 
-extern int zfs_receive(libzfs_handle_t *, const char *, recvflags_t,
+extern int zfs_receive(libzfs_handle_t *, const char *, recvflags_t *,
     int, avl_tree_t *);
 
 typedef enum diff_flags {
index 2389b78..fabcb11 100644 (file)
@@ -21,6 +21,7 @@
 
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  */
 
 #ifndef        _LIBFS_IMPL_H
@@ -119,7 +120,7 @@ struct zpool_handle {
        diskaddr_t zpool_start_block;
 };
 
-typedef  enum {
+typedef enum {
        PROTO_NFS = 0,
        PROTO_SMB = 1,
        PROTO_END = 2
@@ -151,7 +152,8 @@ int zpool_standard_error_fmt(libzfs_handle_t *, int, const char *, ...);
 
 int get_dependents(libzfs_handle_t *, boolean_t, const char *, char ***,
     size_t *);
-
+zfs_handle_t *make_dataset_handle_zc(libzfs_handle_t *, zfs_cmd_t *);
+zfs_handle_t *make_dataset_simple_handle_zc(zfs_handle_t *, zfs_cmd_t *);
 
 int zprop_parse_value(libzfs_handle_t *, nvpair_t *, int, zfs_type_t,
     nvlist_t *, char **, uint64_t *, const char *);
index 3f9c711..5b2e25b 100644 (file)
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  */
 
 /* Portions Copyright 2010 Robert Milkowski */
@@ -190,7 +191,7 @@ int dmu_objset_create(const char *name, dmu_objset_type_t type, uint64_t flags,
 int dmu_objset_clone(const char *name, struct dsl_dataset *clone_origin,
     uint64_t flags);
 int dmu_objset_destroy(const char *name, boolean_t defer);
-int dmu_snapshots_destroy(char *fsname, char *snapname, boolean_t defer);
+int dmu_snapshots_destroy_nvl(struct nvlist *snaps, boolean_t defer, char *);
 int dmu_objset_snapshot(char *fsname, char *snapname, char *tag,
     struct nvlist *props, boolean_t recursive, boolean_t temporary, int fd);
 int dmu_objset_rename(const char *name, const char *newname,
@@ -706,6 +707,8 @@ void dmu_traverse_objset(objset_t *os, uint64_t txg_start,
 
 int dmu_sendbackup(objset_t *tosnap, objset_t *fromsnap, boolean_t fromorigin,
     struct vnode *vp, offset_t *off);
+int dmu_send_estimate(objset_t *tosnap, objset_t *fromsnap, boolean_t fromorign,
+    uint64_t *sizep);
 
 typedef struct dmu_recv_cookie {
        /*
index 22733d0..c4530a8 100644 (file)
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  */
 
 #ifndef        _SYS_DSL_DATASET_H
@@ -249,6 +250,10 @@ void dsl_dataset_space(dsl_dataset_t *ds,
     uint64_t *refdbytesp, uint64_t *availbytesp,
     uint64_t *usedobjsp, uint64_t *availobjsp);
 uint64_t dsl_dataset_fsid_guid(dsl_dataset_t *ds);
+int dsl_dataset_space_written(dsl_dataset_t *oldsnap, dsl_dataset_t *new,
+    uint64_t *usedp, uint64_t *compp, uint64_t *uncompp);
+int dsl_dataset_space_wouldfree(dsl_dataset_t *firstsnap, dsl_dataset_t *last,
+    uint64_t *usedp, uint64_t *compp, uint64_t *uncompp);
 
 int dsl_dsobj_to_dsname(char *pname, uint64_t obj, char *buf);
 
index 73c43bd..9db6d07 100644 (file)
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  */
 
 #ifndef        _SYS_DSL_DELEG_H
@@ -64,7 +65,8 @@ extern "C" {
 int dsl_deleg_get(const char *ddname, nvlist_t **nvp);
 int dsl_deleg_set(const char *ddname, nvlist_t *nvp, boolean_t unset);
 int dsl_deleg_access(const char *ddname, const char *perm, cred_t *cr);
-int dsl_deleg_access_impl(struct dsl_dataset *ds, const char *perm, cred_t *cr);
+int dsl_deleg_access_impl(struct dsl_dataset *ds, boolean_t descendent,
+    const char *perm, cred_t *cr);
 void dsl_deleg_set_create_perms(dsl_dir_t *dd, dmu_tx_t *tx, cred_t *cr);
 int dsl_deleg_can_allow(char *ddname, nvlist_t *nvp, cred_t *cr);
 int dsl_deleg_can_unallow(char *ddname, nvlist_t *nvp, cred_t *cr);
index 032a952..86d7ad5 100644 (file)
@@ -125,6 +125,8 @@ typedef enum {
        ZFS_PROP_MLSLABEL,
        ZFS_PROP_SYNC,
        ZFS_PROP_REFRATIO,
+       ZFS_PROP_WRITTEN,
+       ZFS_PROP_CLONES,
        ZFS_NUM_PROPS
 } zfs_prop_t;
 
@@ -222,6 +224,7 @@ const char *zfs_prop_to_name(zfs_prop_t);
 zfs_prop_t zfs_name_to_prop(const char *);
 boolean_t zfs_prop_user(const char *);
 boolean_t zfs_prop_userquota(const char *);
+boolean_t zfs_prop_written(const char *);
 int zfs_prop_index_to_string(zfs_prop_t, uint64_t, const char **);
 int zfs_prop_string_to_index(zfs_prop_t, const char *, uint64_t *);
 uint64_t zfs_prop_random_value(zfs_prop_t, uint64_t seed);
@@ -764,7 +767,7 @@ typedef enum zfs_ioc {
        ZFS_IOC_ERROR_LOG,
        ZFS_IOC_CLEAR,
        ZFS_IOC_PROMOTE,
-       ZFS_IOC_DESTROY_SNAPS,
+       ZFS_IOC_DESTROY_SNAPS_NVL,
        ZFS_IOC_SNAPSHOT,
        ZFS_IOC_DSOBJ_TO_DSNAME,
        ZFS_IOC_OBJ_TO_PATH,
@@ -787,9 +790,11 @@ typedef enum zfs_ioc {
        ZFS_IOC_DIFF,
        ZFS_IOC_TMP_SNAPSHOT,
        ZFS_IOC_OBJ_TO_STATS,
-       ZFS_IOC_POOL_REGUID,
        ZFS_IOC_EVENTS_NEXT,
        ZFS_IOC_EVENTS_CLEAR,
+       ZFS_IOC_POOL_REGUID,
+       ZFS_IOC_SPACE_WRITTEN,
+       ZFS_IOC_SPACE_SNAPS,
 } zfs_ioc_t;
 
 /*
index 4e3a485..524efaa 100644 (file)
@@ -14,6 +14,7 @@ libzfs_la_SOURCES = \
        $(top_srcdir)/lib/libzfs/libzfs_fru.c \
        $(top_srcdir)/lib/libzfs/libzfs_graph.c \
        $(top_srcdir)/lib/libzfs/libzfs_import.c \
+       $(top_srcdir)/lib/libzfs/libzfs_iter.c \
        $(top_srcdir)/lib/libzfs/libzfs_mount.c \
        $(top_srcdir)/lib/libzfs/libzfs_pool.c \
        $(top_srcdir)/lib/libzfs/libzfs_sendrecv.c \
index e05a8a4..c2c1d2e 100644 (file)
@@ -137,7 +137,7 @@ libzfs_la_DEPENDENCIES = $(top_builddir)/lib/libshare/libshare.la \
        $(top_builddir)/lib/libzpool/libzpool.la
 am_libzfs_la_OBJECTS = libzfs_changelist.lo libzfs_config.lo \
        libzfs_dataset.lo libzfs_diff.lo libzfs_fru.lo libzfs_graph.lo \
-       libzfs_import.lo libzfs_mount.lo libzfs_pool.lo \
+       libzfs_import.lo libzfs_iter.lo libzfs_mount.lo libzfs_pool.lo \
        libzfs_sendrecv.lo libzfs_status.lo libzfs_util.lo
 libzfs_la_OBJECTS = $(am_libzfs_la_OBJECTS)
 AM_V_lt = $(am__v_lt_$(V))
@@ -371,6 +371,7 @@ libzfs_la_SOURCES = \
        $(top_srcdir)/lib/libzfs/libzfs_fru.c \
        $(top_srcdir)/lib/libzfs/libzfs_graph.c \
        $(top_srcdir)/lib/libzfs/libzfs_import.c \
+       $(top_srcdir)/lib/libzfs/libzfs_iter.c \
        $(top_srcdir)/lib/libzfs/libzfs_mount.c \
        $(top_srcdir)/lib/libzfs/libzfs_pool.c \
        $(top_srcdir)/lib/libzfs/libzfs_sendrecv.c \
@@ -464,6 +465,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libzfs_fru.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libzfs_graph.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libzfs_import.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libzfs_iter.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libzfs_mount.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libzfs_pool.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libzfs_sendrecv.Plo@am__quote@
@@ -550,6 +552,14 @@ libzfs_import.lo: $(top_srcdir)/lib/libzfs/libzfs_import.c
 @AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
 @am__fastdepCC_FALSE@  $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libzfs_import.lo `test -f '$(top_srcdir)/lib/libzfs/libzfs_import.c' || echo '$(srcdir)/'`$(top_srcdir)/lib/libzfs/libzfs_import.c
 
+libzfs_iter.lo: $(top_srcdir)/lib/libzfs/libzfs_iter.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libzfs_iter.lo -MD -MP -MF $(DEPDIR)/libzfs_iter.Tpo -c -o libzfs_iter.lo `test -f '$(top_srcdir)/lib/libzfs/libzfs_iter.c' || echo '$(srcdir)/'`$(top_srcdir)/lib/libzfs/libzfs_iter.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libzfs_iter.Tpo $(DEPDIR)/libzfs_iter.Plo
+@am__fastdepCC_FALSE@  $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      source='$(top_srcdir)/lib/libzfs/libzfs_iter.c' object='libzfs_iter.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libzfs_iter.lo `test -f '$(top_srcdir)/lib/libzfs/libzfs_iter.c' || echo '$(srcdir)/'`$(top_srcdir)/lib/libzfs/libzfs_iter.c
+
 libzfs_mount.lo: $(top_srcdir)/lib/libzfs/libzfs_mount.c
 @am__fastdepCC_TRUE@   $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libzfs_mount.lo -MD -MP -MF $(DEPDIR)/libzfs_mount.Tpo -c -o libzfs_mount.lo `test -f '$(top_srcdir)/lib/libzfs/libzfs_mount.c' || echo '$(srcdir)/'`$(top_srcdir)/lib/libzfs/libzfs_mount.c
 @am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libzfs_mount.Tpo $(DEPDIR)/libzfs_mount.Plo
index c51b99d..476598c 100644 (file)
@@ -464,7 +464,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);
@@ -481,7 +481,7 @@ make_dataset_handle_zc(libzfs_handle_t *hdl, zfs_cmd_t *zc)
        return (zhp);
 }
 
-static zfs_handle_t *
+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);
@@ -498,6 +498,53 @@ make_dataset_simple_handle_zc(zfs_handle_t *pzhp, zfs_cmd_t *zc)
        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
@@ -861,6 +908,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) {
@@ -1877,8 +1930,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;
@@ -1893,6 +1944,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
@@ -2021,6 +2186,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:
@@ -2385,7 +2555,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),
@@ -2438,149 +2608,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, boolean_t simple, 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);
-
-       zc.zc_simple = simple;
+       (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 (simple)
-                       nzhp = make_dataset_simple_handle_zc(zhp, &zc);
-               else
-                       nzhp = make_dataset_handle_zc(zhp->zfs_hdl, &zc);
-               if (nzhp == 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, B_FALSE, 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);
 }
 
 /*
@@ -2606,18 +2723,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);
 }
@@ -3031,9 +3149,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
@@ -3042,16 +3159,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);
        }
 
@@ -3064,10 +3179,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);
 }
 
@@ -3077,29 +3190,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:
@@ -3135,7 +3264,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));
 
@@ -3628,42 +3757,6 @@ 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
@@ -3780,8 +3873,6 @@ 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 */
                ret = zfs_iter_filesystems(zhrp, zfs_check_snap_cb, &dd);
@@ -4223,7 +4314,7 @@ zfs_userspace(zfs_handle_t *zhp, zfs_userquota_prop_t type,
        int error;
        zfs_useracct_t buf[100];
 
-       (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;
diff --git a/lib/libzfs/libzfs_iter.c b/lib/libzfs/libzfs_iter.c
new file mode 100644 (file)
index 0000000..8215d3c
--- /dev/null
@@ -0,0 +1,468 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2010 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <libintl.h>
+#include <libzfs.h>
+
+#include "libzfs_impl.h"
+
+int
+zfs_iter_clones(zfs_handle_t *zhp, zfs_iter_f func, void *data)
+{
+       nvlist_t *nvl = zfs_get_clones_nvl(zhp);
+       nvpair_t *pair;
+
+       if (nvl == NULL)
+               return (0);
+
+       for (pair = nvlist_next_nvpair(nvl, NULL); pair != NULL;
+           pair = nvlist_next_nvpair(nvl, pair)) {
+               zfs_handle_t *clone = zfs_open(zhp->zfs_hdl, nvpair_name(pair),
+                   ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
+               if (clone != NULL) {
+                       int err = func(clone, data);
+                       if (err != 0)
+                               return (err);
+               }
+       }
+       return (0);
+}
+
+static int
+zfs_do_list_ioctl(zfs_handle_t *zhp, int arg, zfs_cmd_t *zc)
+{
+       int rc;
+       uint64_t        orig_cookie;
+
+       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);
+
+       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);
+}
+
+/*
+ * Iterate over all child filesystems
+ */
+int
+zfs_iter_filesystems(zfs_handle_t *zhp, zfs_iter_f func, void *data)
+{
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       zfs_handle_t *nzhp;
+       int ret;
+
+       if (zhp->zfs_type != ZFS_TYPE_FILESYSTEM)
+               return (0);
+
+       if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0)
+               return (-1);
+
+       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 ((ret = func(nzhp, data)) != 0) {
+                       zcmd_free_nvlists(&zc);
+                       return (ret);
+               }
+       }
+       zcmd_free_nvlists(&zc);
+       return ((ret < 0) ? ret : 0);
+}
+
+/*
+ * Iterate over all snapshots
+ */
+int
+zfs_iter_snapshots(zfs_handle_t *zhp, boolean_t simple, zfs_iter_f func,
+    void *data)
+{
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       zfs_handle_t *nzhp;
+       int ret;
+
+       if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT)
+               return (0);
+
+       zc.zc_simple = simple;
+
+       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 (simple)
+                       nzhp = make_dataset_simple_handle_zc(zhp, &zc);
+               else
+                       nzhp = make_dataset_handle_zc(zhp->zfs_hdl, &zc);
+               if (nzhp == NULL)
+                       continue;
+
+               if ((ret = func(nzhp, data)) != 0) {
+                       zcmd_free_nvlists(&zc);
+                       return (ret);
+               }
+       }
+       zcmd_free_nvlists(&zc);
+       return ((ret < 0) ? ret : 0);
+}
+
+/*
+ * Routines for dealing with the sorted snapshot functionality
+ */
+typedef struct zfs_node {
+       zfs_handle_t    *zn_handle;
+       avl_node_t      zn_avlnode;
+} zfs_node_t;
+
+static int
+zfs_sort_snaps(zfs_handle_t *zhp, void *data)
+{
+       avl_tree_t *avl = data;
+       zfs_node_t *node;
+       zfs_node_t search;
+
+       search.zn_handle = zhp;
+       node = avl_find(avl, &search, NULL);
+       if (node) {
+               /*
+                * If this snapshot was renamed while we were creating the
+                * AVL tree, it's possible that we already inserted it under
+                * its old name. Remove the old handle before adding the new
+                * one.
+                */
+               zfs_close(node->zn_handle);
+               avl_remove(avl, node);
+               free(node);
+       }
+
+       node = zfs_alloc(zhp->zfs_hdl, sizeof (zfs_node_t));
+       node->zn_handle = zhp;
+       avl_add(avl, node);
+
+       return (0);
+}
+
+static int
+zfs_snapshot_compare(const void *larg, const void *rarg)
+{
+       zfs_handle_t *l = ((zfs_node_t *)larg)->zn_handle;
+       zfs_handle_t *r = ((zfs_node_t *)rarg)->zn_handle;
+       uint64_t lcreate, rcreate;
+
+       /*
+        * Sort them according to creation time.  We use the hidden
+        * CREATETXG property to get an absolute ordering of snapshots.
+        */
+       lcreate = zfs_prop_get_int(l, ZFS_PROP_CREATETXG);
+       rcreate = zfs_prop_get_int(r, ZFS_PROP_CREATETXG);
+
+       if (lcreate < rcreate)
+               return (-1);
+       else if (lcreate > rcreate)
+               return (+1);
+       else
+               return (0);
+}
+
+int
+zfs_iter_snapshots_sorted(zfs_handle_t *zhp, zfs_iter_f callback, void *data)
+{
+       int ret = 0;
+       zfs_node_t *node;
+       avl_tree_t avl;
+       void *cookie = NULL;
+
+       avl_create(&avl, zfs_snapshot_compare,
+           sizeof (zfs_node_t), offsetof(zfs_node_t, zn_avlnode));
+
+       ret = zfs_iter_snapshots(zhp, B_FALSE, zfs_sort_snaps, &avl);
+
+       for (node = avl_first(&avl); node != NULL; node = AVL_NEXT(&avl, node))
+               ret |= callback(node->zn_handle, data);
+
+       while ((node = avl_destroy_nodes(&avl, &cookie)) != NULL)
+               free(node);
+
+       avl_destroy(&avl);
+
+       return (ret);
+}
+
+typedef struct {
+       char *ssa_first;
+       char *ssa_last;
+       boolean_t ssa_seenfirst;
+       boolean_t ssa_seenlast;
+       zfs_iter_f ssa_func;
+       void *ssa_arg;
+} snapspec_arg_t;
+
+static int
+snapspec_cb(zfs_handle_t *zhp, void *arg) {
+       snapspec_arg_t *ssa = arg;
+       char *shortsnapname;
+       int err = 0;
+
+       if (ssa->ssa_seenlast)
+               return (0);
+       shortsnapname = zfs_strdup(zhp->zfs_hdl,
+           strchr(zfs_get_name(zhp), '@') + 1);
+
+       if (!ssa->ssa_seenfirst && strcmp(shortsnapname, ssa->ssa_first) == 0)
+               ssa->ssa_seenfirst = B_TRUE;
+
+       if (ssa->ssa_seenfirst) {
+               err = ssa->ssa_func(zhp, ssa->ssa_arg);
+       } else {
+               zfs_close(zhp);
+       }
+
+       if (strcmp(shortsnapname, ssa->ssa_last) == 0)
+               ssa->ssa_seenlast = B_TRUE;
+       free(shortsnapname);
+
+       return (err);
+}
+
+/*
+ * spec is a string like "A,B%C,D"
+ *
+ * <snaps>, where <snaps> can be:
+ *      <snap>          (single snapshot)
+ *      <snap>%<snap>   (range of snapshots, inclusive)
+ *      %<snap>         (range of snapshots, starting with earliest)
+ *      <snap>%         (range of snapshots, ending with last)
+ *      %               (all snapshots)
+ *      <snaps>[,...]   (comma separated list of the above)
+ *
+ * If a snapshot can not be opened, continue trying to open the others, but
+ * return ENOENT at the end.
+ */
+int
+zfs_iter_snapspec(zfs_handle_t *fs_zhp, const char *spec_orig,
+    zfs_iter_f func, void *arg)
+{
+       char buf[ZFS_MAXNAMELEN];
+       char *comma_separated, *cp;
+       int err = 0;
+       int ret = 0;
+
+       (void) strlcpy(buf, spec_orig, sizeof (buf));
+       cp = buf;
+
+       while ((comma_separated = strsep(&cp, ",")) != NULL) {
+               char *pct = strchr(comma_separated, '%');
+               if (pct != NULL) {
+                       snapspec_arg_t ssa = { 0 };
+                       ssa.ssa_func = func;
+                       ssa.ssa_arg = arg;
+
+                       if (pct == comma_separated)
+                               ssa.ssa_seenfirst = B_TRUE;
+                       else
+                               ssa.ssa_first = comma_separated;
+                       *pct = '\0';
+                       ssa.ssa_last = pct + 1;
+
+                       /*
+                        * If there is a lastname specified, make sure it
+                        * exists.
+                        */
+                       if (ssa.ssa_last[0] != '\0') {
+                               char snapname[ZFS_MAXNAMELEN];
+                               (void) snprintf(snapname, sizeof (snapname),
+                                   "%s@%s", zfs_get_name(fs_zhp),
+                                   ssa.ssa_last);
+                               if (!zfs_dataset_exists(fs_zhp->zfs_hdl,
+                                   snapname, ZFS_TYPE_SNAPSHOT)) {
+                                       ret = ENOENT;
+                                       continue;
+                               }
+                       }
+
+                       err = zfs_iter_snapshots_sorted(fs_zhp,
+                           snapspec_cb, &ssa);
+                       if (ret == 0)
+                               ret = err;
+                       if (ret == 0 && (!ssa.ssa_seenfirst ||
+                           (ssa.ssa_last[0] != '\0' && !ssa.ssa_seenlast))) {
+                               ret = ENOENT;
+                       }
+               } else {
+                       char snapname[ZFS_MAXNAMELEN];
+                       zfs_handle_t *snap_zhp;
+                       (void) snprintf(snapname, sizeof (snapname), "%s@%s",
+                           zfs_get_name(fs_zhp), comma_separated);
+                       snap_zhp = make_dataset_handle(fs_zhp->zfs_hdl,
+                           snapname);
+                       if (snap_zhp == NULL) {
+                               ret = ENOENT;
+                               continue;
+                       }
+                       err = func(snap_zhp, arg);
+                       if (ret == 0)
+                               ret = err;
+               }
+       }
+
+       return (ret);
+}
+
+/*
+ * Iterate over all children, snapshots and filesystems
+ */
+int
+zfs_iter_children(zfs_handle_t *zhp, zfs_iter_f func, void *data)
+{
+       int ret;
+
+       if ((ret = zfs_iter_filesystems(zhp, func, data)) != 0)
+               return (ret);
+
+       return (zfs_iter_snapshots(zhp, B_FALSE, func, data));
+}
+
+
+typedef struct iter_stack_frame {
+       struct iter_stack_frame *next;
+       zfs_handle_t *zhp;
+} iter_stack_frame_t;
+
+typedef struct iter_dependents_arg {
+       boolean_t first;
+       boolean_t allowrecursion;
+       iter_stack_frame_t *stack;
+       zfs_iter_f func;
+       void *data;
+} iter_dependents_arg_t;
+
+static int
+iter_dependents_cb(zfs_handle_t *zhp, void *arg)
+{
+       iter_dependents_arg_t *ida = arg;
+       int err;
+       boolean_t first = ida->first;
+       ida->first = B_FALSE;
+
+       if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) {
+               err = zfs_iter_clones(zhp, iter_dependents_cb, ida);
+       } else {
+               iter_stack_frame_t isf;
+               iter_stack_frame_t *f;
+
+               /*
+                * check if there is a cycle by seeing if this fs is already
+                * on the stack.
+                */
+               for (f = ida->stack; f != NULL; f = f->next) {
+                       if (f->zhp->zfs_dmustats.dds_guid ==
+                           zhp->zfs_dmustats.dds_guid) {
+                               if (ida->allowrecursion) {
+                                       zfs_close(zhp);
+                                       return (0);
+                               } else {
+                                       zfs_error_aux(zhp->zfs_hdl,
+                                           dgettext(TEXT_DOMAIN,
+                                           "recursive dependency at '%s'"),
+                                           zfs_get_name(zhp));
+                                       err = zfs_error(zhp->zfs_hdl,
+                                           EZFS_RECURSIVE,
+                                           dgettext(TEXT_DOMAIN,
+                                           "cannot determine dependent "
+                                           "datasets"));
+                                       zfs_close(zhp);
+                                       return (err);
+                               }
+                       }
+               }
+
+               isf.zhp = zhp;
+               isf.next = ida->stack;
+               ida->stack = &isf;
+               err = zfs_iter_filesystems(zhp, iter_dependents_cb, ida);
+               if (err == 0)
+                       err = zfs_iter_snapshots(zhp, B_FALSE,
+                           iter_dependents_cb, ida);
+               ida->stack = isf.next;
+       }
+       if (!first && err == 0)
+               err = ida->func(zhp, ida->data);
+       return (err);
+}
+
+int
+zfs_iter_dependents(zfs_handle_t *zhp, boolean_t allowrecursion,
+    zfs_iter_f func, void *data)
+{
+       iter_dependents_arg_t ida;
+       ida.allowrecursion = allowrecursion;
+       ida.stack = NULL;
+       ida.func = func;
+       ida.data = data;
+       ida.first = B_TRUE;
+       return (iter_dependents_cb(zfs_handle_dup(zhp), &ida));
+}
index bc62373..0702ba0 100644 (file)
@@ -21,6 +21,7 @@
 
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  * Copyright (c) 2012 Pawel Jakub Dawidek <pawel@dawidek.net>.
  * All rights reserved
  */
@@ -57,7 +58,7 @@
 /* in libzfs_dataset.c */
 extern void zfs_setprop_error(libzfs_handle_t *, zfs_prop_t, int, char *);
 
-static int zfs_receive_impl(libzfs_handle_t *, const char *, recvflags_t,
+static int zfs_receive_impl(libzfs_handle_t *, const char *, recvflags_t *,
     int, const char *, nvlist_t *, avl_tree_t *, char **, int, uint64_t *);
 
 static const zio_cksum_t zero_cksum = { { 0 } };
@@ -777,88 +778,6 @@ gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
 }
 
 /*
- * Routines for dealing with the sorted snapshot functionality
- */
-typedef struct zfs_node {
-       zfs_handle_t    *zn_handle;
-       avl_node_t      zn_avlnode;
-} zfs_node_t;
-
-static int
-zfs_sort_snaps(zfs_handle_t *zhp, void *data)
-{
-       avl_tree_t *avl = data;
-       zfs_node_t *node;
-       zfs_node_t search;
-
-       search.zn_handle = zhp;
-       node = avl_find(avl, &search, NULL);
-       if (node) {
-               /*
-                * If this snapshot was renamed while we were creating the
-                * AVL tree, it's possible that we already inserted it under
-                * its old name. Remove the old handle before adding the new
-                * one.
-                */
-               zfs_close(node->zn_handle);
-               avl_remove(avl, node);
-               free(node);
-       }
-
-       node = zfs_alloc(zhp->zfs_hdl, sizeof (zfs_node_t));
-       node->zn_handle = zhp;
-       avl_add(avl, node);
-
-       return (0);
-}
-
-static int
-zfs_snapshot_compare(const void *larg, const void *rarg)
-{
-       zfs_handle_t *l = ((zfs_node_t *)larg)->zn_handle;
-       zfs_handle_t *r = ((zfs_node_t *)rarg)->zn_handle;
-       uint64_t lcreate, rcreate;
-
-       /*
-        * Sort them according to creation time.  We use the hidden
-        * CREATETXG property to get an absolute ordering of snapshots.
-        */
-       lcreate = zfs_prop_get_int(l, ZFS_PROP_CREATETXG);
-       rcreate = zfs_prop_get_int(r, ZFS_PROP_CREATETXG);
-
-       if (lcreate < rcreate)
-               return (-1);
-       else if (lcreate > rcreate)
-               return (+1);
-       else
-               return (0);
-}
-
-int
-zfs_iter_snapshots_sorted(zfs_handle_t *zhp, zfs_iter_f callback, void *data)
-{
-       int ret = 0;
-       zfs_node_t *node;
-       avl_tree_t avl;
-       void *cookie = NULL;
-
-       avl_create(&avl, zfs_snapshot_compare,
-           sizeof (zfs_node_t), offsetof(zfs_node_t, zn_avlnode));
-
-       ret = zfs_iter_snapshots(zhp, B_FALSE, zfs_sort_snaps, &avl);
-
-       for (node = avl_first(&avl); node != NULL; node = AVL_NEXT(&avl, node))
-               ret |= callback(node->zn_handle, data);
-
-       while ((node = avl_destroy_nodes(&avl, &cookie)) != NULL)
-               free(node);
-
-       avl_destroy(&avl);
-
-       return (ret);
-}
-
-/*
  * Routines specific to "zfs send"
  */
 typedef struct send_dump_data {
@@ -868,7 +787,7 @@ typedef struct send_dump_data {
        char prevsnap[ZFS_MAXNAMELEN];
        uint64_t prevsnap_obj;
        boolean_t seenfrom, seento, replicate, doall, fromorigin;
-       boolean_t verbose;
+       boolean_t verbose, dryrun, parsable;
        int outfd;
        boolean_t err;
        nvlist_t *fss;
@@ -878,8 +797,69 @@ typedef struct send_dump_data {
        nvlist_t *debugnv;
        char holdtag[ZFS_MAXNAMELEN];
        int cleanup_fd;
+       uint64_t size;
 } send_dump_data_t;
 
+static int
+estimate_ioctl(zfs_handle_t *zhp, uint64_t fromsnap_obj,
+    boolean_t fromorigin, uint64_t *sizep)
+{
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       libzfs_handle_t *hdl = zhp->zfs_hdl;
+
+       assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT);
+       assert(fromsnap_obj == 0 || !fromorigin);
+
+       (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+       zc.zc_obj = fromorigin;
+       zc.zc_sendobj = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID);
+       zc.zc_fromobj = fromsnap_obj;
+       zc.zc_guid = 1;  /* estimate flag */
+
+       if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_SEND, &zc) != 0) {
+               char errbuf[1024];
+               (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+                   "warning: cannot estimate space for '%s'"), zhp->zfs_name);
+
+               switch (errno) {
+               case EXDEV:
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "not an earlier snapshot from the same fs"));
+                       return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf));
+
+               case ENOENT:
+                       if (zfs_dataset_exists(hdl, zc.zc_name,
+                           ZFS_TYPE_SNAPSHOT)) {
+                               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                                   "incremental source (@%s) does not exist"),
+                                   zc.zc_value);
+                       }
+                       return (zfs_error(hdl, EZFS_NOENT, errbuf));
+
+               case EDQUOT:
+               case EFBIG:
+               case EIO:
+               case ENOLINK:
+               case ENOSPC:
+               case ENOSTR:
+               case ENXIO:
+               case EPIPE:
+               case ERANGE:
+               case EFAULT:
+               case EROFS:
+                       zfs_error_aux(hdl, strerror(errno));
+                       return (zfs_error(hdl, EZFS_BADBACKUP, errbuf));
+
+               default:
+                       return (zfs_standard_error(hdl, errno, errbuf));
+               }
+       }
+
+       *sizep = zc.zc_objset_type;
+
+       return (0);
+}
+
 /*
  * Dumps a backup of the given snapshot (incremental from fromsnap if it's not
  * NULL) to the file descriptor specified by outfd.
@@ -907,7 +887,7 @@ dump_ioctl(zfs_handle_t *zhp, const char *fromsnap, uint64_t fromsnap_obj,
                    "fromsnap", fromsnap));
        }
 
-       if (ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_SEND, &zc) != 0) {
+       if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_SEND, &zc) != 0) {
                char errbuf[1024];
                (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
                    "warning: cannot send '%s'"), zhp->zfs_name);
@@ -920,7 +900,6 @@ dump_ioctl(zfs_handle_t *zhp, const char *fromsnap, uint64_t fromsnap_obj,
                nvlist_free(thisdbg);
 
                switch (errno) {
-
                case EXDEV:
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "not an earlier snapshot from the same fs"));
@@ -970,6 +949,9 @@ hold_for_send(zfs_handle_t *zhp, send_dump_data_t *sdd)
 
        assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT);
 
+       if (sdd->dryrun)
+               return (0);
+
        /*
         * zfs_send() only opens a cleanup_fd for sends that need it,
         * e.g. replication and doall.
@@ -1003,7 +985,7 @@ dump_snapshot(zfs_handle_t *zhp, void *arg)
        send_dump_data_t *sdd = arg;
        char *thissnap;
        int err;
-       boolean_t isfromsnap, istosnap;
+       boolean_t isfromsnap, istosnap, fromorigin;
        boolean_t exclude = B_FALSE;
 
        thissnap = strchr(zhp->zfs_name, '@') + 1;
@@ -1080,15 +1062,47 @@ dump_snapshot(zfs_handle_t *zhp, void *arg)
                return (err);
        }
 
-       /* send it */
+       fromorigin = sdd->prevsnap[0] == '\0' &&
+           (sdd->fromorigin || sdd->replicate);
+
        if (sdd->verbose) {
-               (void) fprintf(stderr, "sending from @%s to %s\n",
-                   sdd->prevsnap, zhp->zfs_name);
+               uint64_t size;
+               err = estimate_ioctl(zhp, sdd->prevsnap_obj,
+                   fromorigin, &size);
+
+               if (sdd->parsable) {
+                       if (sdd->prevsnap[0] != '\0') {
+                               (void) fprintf(stderr, "incremental\t%s\t%s",
+                                   sdd->prevsnap, zhp->zfs_name);
+                       } else {
+                               (void) fprintf(stderr, "full\t%s",
+                                   zhp->zfs_name);
+                       }
+               } else {
+                       (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+                           "send from @%s to %s"),
+                           sdd->prevsnap, zhp->zfs_name);
+               }
+               if (err == 0) {
+                       if (sdd->parsable) {
+                               (void) fprintf(stderr, "\t%llu\n",
+                                   (longlong_t)size);
+                       } else {
+                               char buf[16];
+                               zfs_nicenum(size, buf, sizeof (buf));
+                               (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+                                   " estimated size is %s\n"), buf);
+                       }
+                       sdd->size += size;
+               } else {
+                       (void) fprintf(stderr, "\n");
+               }
        }
 
-       err = dump_ioctl(zhp, sdd->prevsnap, sdd->prevsnap_obj,
-           sdd->prevsnap[0] == '\0' && (sdd->fromorigin || sdd->replicate),
-           sdd->outfd, sdd->debugnv);
+       if (!sdd->dryrun) {
+               err = dump_ioctl(zhp, sdd->prevsnap, sdd->prevsnap_obj,
+                   fromorigin, sdd->outfd, sdd->debugnv);
+       }
 
        (void) strcpy(sdd->prevsnap, thissnap);
        sdd->prevsnap_obj = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID);
@@ -1107,8 +1121,8 @@ dump_filesystem(zfs_handle_t *zhp, void *arg)
        (void) snprintf(zc.zc_name, sizeof (zc.zc_name), "%s@%s",
            zhp->zfs_name, sdd->tosnap);
        if (ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_OBJSET_STATS, &zc) != 0) {
-               (void) fprintf(stderr, "WARNING: "
-                   "could not send %s@%s: does not exist\n",
+               (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+                   "WARNING: could not send %s@%s: does not exist\n"),
                    zhp->zfs_name, sdd->tosnap);
                sdd->err = B_TRUE;
                return (0);
@@ -1137,23 +1151,24 @@ dump_filesystem(zfs_handle_t *zhp, void *arg)
 
        rv = zfs_iter_snapshots_sorted(zhp, dump_snapshot, arg);
        if (!sdd->seenfrom) {
-               (void) fprintf(stderr,
+               (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
                    "WARNING: could not send %s@%s:\n"
-                   "incremental source (%s@%s) does not exist\n",
+                   "incremental source (%s@%s) does not exist\n"),
                    zhp->zfs_name, sdd->tosnap,
                    zhp->zfs_name, sdd->fromsnap);
                sdd->err = B_TRUE;
        } else if (!sdd->seento) {
                if (sdd->fromsnap) {
-                       (void) fprintf(stderr,
+                       (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
                            "WARNING: could not send %s@%s:\n"
                            "incremental source (%s@%s) "
-                           "is not earlier than it\n",
+                           "is not earlier than it\n"),
                            zhp->zfs_name, sdd->tosnap,
                            zhp->zfs_name, sdd->fromsnap);
                } else {
-                       (void) fprintf(stderr, "WARNING: "
-                           "could not send %s@%s: does not exist\n",
+                       (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+                           "WARNING: "
+                           "could not send %s@%s: does not exist\n"),
                            zhp->zfs_name, sdd->tosnap);
                }
                sdd->err = B_TRUE;
@@ -1199,11 +1214,12 @@ again:
        needagain = progress = B_FALSE;
        for (fspair = nvlist_next_nvpair(sdd->fss, NULL); fspair;
            fspair = nvlist_next_nvpair(sdd->fss, fspair)) {
-               nvlist_t *fslist;
+               nvlist_t *fslist, *parent_nv;
                char *fsname;
                zfs_handle_t *zhp;
                int err;
                uint64_t origin_guid = 0;
+               uint64_t parent_guid = 0;
 
                VERIFY(nvpair_value_nvlist(fspair, &fslist) == 0);
                if (nvlist_lookup_boolean(fslist, "sent") == 0)
@@ -1211,13 +1227,23 @@ again:
 
                VERIFY(nvlist_lookup_string(fslist, "name", &fsname) == 0);
                (void) nvlist_lookup_uint64(fslist, "origin", &origin_guid);
+               (void) nvlist_lookup_uint64(fslist, "parentfromsnap",
+                   &parent_guid);
+
+               if (parent_guid != 0) {
+                       parent_nv = fsavl_find(sdd->fsavl, parent_guid, NULL);
+                       if (!nvlist_exists(parent_nv, "sent")) {
+                               /* parent has not been sent; skip this one */
+                               needagain = B_TRUE;
+                               continue;
+                       }
+               }
 
                if (origin_guid != 0) {
                        nvlist_t *origin_nv = fsavl_find(sdd->fsavl,
                            origin_guid, NULL);
                        if (origin_nv != NULL &&
-                           nvlist_lookup_boolean(origin_nv,
-                           "sent") == ENOENT) {
+                           !nvlist_exists(origin_nv, "sent")) {
                                /*
                                 * origin has not been sent yet;
                                 * skip this clone.
@@ -1241,6 +1267,16 @@ again:
                assert(progress);
                goto again;
        }
+
+       /* clean out the sent flags in case we reuse this fss */
+       for (fspair = nvlist_next_nvpair(sdd->fss, NULL); fspair;
+           fspair = nvlist_next_nvpair(sdd->fss, fspair)) {
+               nvlist_t *fslist;
+
+               VERIFY(nvpair_value_nvlist(fspair, &fslist) == 0);
+               (void) nvlist_remove_all(fslist, "sent");
+       }
+
        return (0);
 }
 
@@ -1262,12 +1298,12 @@ again:
  */
 int
 zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
-    sendflags_t flags, int outfd, snapfilter_cb_t filter_func,
+    sendflags_t *flags, int outfd, snapfilter_cb_t filter_func,
     void *cb_arg, nvlist_t **debugnvp)
 {
        char errbuf[1024];
        send_dump_data_t sdd = { 0 };
-       int err;
+       int err = 0;
        nvlist_t *fss = NULL;
        avl_tree_t *fsavl = NULL;
        static uint64_t holdseq;
@@ -1295,12 +1331,12 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                }
        }
 
-       if (zfs_spa_version(zhp, &spa_version) == 0 &&
+       if (!flags->dryrun && zfs_spa_version(zhp, &spa_version) == 0 &&
            spa_version >= SPA_VERSION_USERREFS &&
-           (flags.doall || flags.replicate))
+           (flags->doall || flags->replicate))
                holdsnaps = B_TRUE;
 
-       if (flags.dedup) {
+       if (flags->dedup && !flags->dryrun) {
                featureflags |= (DMU_BACKUP_FEATURE_DEDUP |
                    DMU_BACKUP_FEATURE_DEDUPPROPS);
                if ((err = socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd))) {
@@ -1320,13 +1356,13 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                }
        }
 
-       if (flags.replicate || flags.doall || flags.props) {
+       if (flags->replicate || flags->doall || flags->props) {
                dmu_replay_record_t drr = { 0 };
                char *packbuf = NULL;
                size_t buflen = 0;
                zio_cksum_t zc = { { 0 } };
 
-               if (flags.replicate || flags.props) {
+               if (flags->replicate || flags->props) {
                        nvlist_t *hdrnv;
 
                        VERIFY(0 == nvlist_alloc(&hdrnv, NV_UNIQUE_NAME, 0));
@@ -1335,13 +1371,13 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                                    "fromsnap", fromsnap));
                        }
                        VERIFY(0 == nvlist_add_string(hdrnv, "tosnap", tosnap));
-                       if (!flags.replicate) {
+                       if (!flags->replicate) {
                                VERIFY(0 == nvlist_add_boolean(hdrnv,
                                    "not_recursive"));
                        }
 
                        err = gather_nvlist(zhp->zfs_hdl, zhp->zfs_name,
-                           fromsnap, tosnap, flags.replicate, &fss, &fsavl);
+                           fromsnap, tosnap, flags->replicate, &fss, &fsavl);
                        if (err)
                                goto err_out;
                        VERIFY(0 == nvlist_add_nvlist(hdrnv, "fss", fss));
@@ -1358,33 +1394,34 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                        }
                }
 
-               /* write first begin record */
-               drr.drr_type = DRR_BEGIN;
-               drr.drr_u.drr_begin.drr_magic = DMU_BACKUP_MAGIC;
-               DMU_SET_STREAM_HDRTYPE(drr.drr_u.drr_begin.drr_versioninfo,
-                   DMU_COMPOUNDSTREAM);
-               DMU_SET_FEATUREFLAGS(drr.drr_u.drr_begin.drr_versioninfo,
-                   featureflags);
-               (void) snprintf(drr.drr_u.drr_begin.drr_toname,
-                   sizeof (drr.drr_u.drr_begin.drr_toname),
-                   "%s@%s", zhp->zfs_name, tosnap);
-               drr.drr_payloadlen = buflen;
-               err = cksum_and_write(&drr, sizeof (drr), &zc, outfd);
-
-               /* write header nvlist */
-               if (err != -1 && packbuf != NULL) {
-                       err = cksum_and_write(packbuf, buflen, &zc, outfd);
-               }
-               free(packbuf);
-               if (err == -1) {
-                       fsavl_destroy(fsavl);
-                       nvlist_free(fss);
-                       err = errno;
-                       goto stderr_out;
-               }
+               if (!flags->dryrun) {
+                       /* write first begin record */
+                       drr.drr_type = DRR_BEGIN;
+                       drr.drr_u.drr_begin.drr_magic = DMU_BACKUP_MAGIC;
+                       DMU_SET_STREAM_HDRTYPE(drr.drr_u.drr_begin.
+                           drr_versioninfo, DMU_COMPOUNDSTREAM);
+                       DMU_SET_FEATUREFLAGS(drr.drr_u.drr_begin.
+                           drr_versioninfo, featureflags);
+                       (void) snprintf(drr.drr_u.drr_begin.drr_toname,
+                           sizeof (drr.drr_u.drr_begin.drr_toname),
+                           "%s@%s", zhp->zfs_name, tosnap);
+                       drr.drr_payloadlen = buflen;
+                       err = cksum_and_write(&drr, sizeof (drr), &zc, outfd);
+
+                       /* write header nvlist */
+                       if (err != -1 && packbuf != NULL) {
+                               err = cksum_and_write(packbuf, buflen, &zc,
+                                   outfd);
+                       }
+                       free(packbuf);
+                       if (err == -1) {
+                               fsavl_destroy(fsavl);
+                               nvlist_free(fss);
+                               err = errno;
+                               goto stderr_out;
+                       }
 
-               /* write end record */
-               if (err != -1) {
+                       /* write end record */
                        bzero(&drr, sizeof (drr));
                        drr.drr_type = DRR_END;
                        drr.drr_u.drr_end.drr_checksum = zc;
@@ -1395,22 +1432,26 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                                err = errno;
                                goto stderr_out;
                        }
+
+                       err = 0;
                }
        }
 
        /* dump each stream */
        sdd.fromsnap = fromsnap;
        sdd.tosnap = tosnap;
-       if (flags.dedup)
+       if (flags->dedup)
                sdd.outfd = pipefd[0];
        else
                sdd.outfd = outfd;
-       sdd.replicate = flags.replicate;
-       sdd.doall = flags.doall;
-       sdd.fromorigin = flags.fromorigin;
+       sdd.replicate = flags->replicate;
+       sdd.doall = flags->doall;
+       sdd.fromorigin = flags->fromorigin;
        sdd.fss = fss;
        sdd.fsavl = fsavl;
-       sdd.verbose = flags.verbose;
+       sdd.verbose = flags->verbose;
+       sdd.parsable = flags->parsable;
+       sdd.dryrun = flags->dryrun;
        sdd.filter_cb = filter_func;
        sdd.filter_cb_arg = cb_arg;
        if (debugnvp)
@@ -1427,11 +1468,31 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
        } else {
                sdd.cleanup_fd = -1;
        }
+       if (flags->verbose) {
+               /*
+                * Do a verbose no-op dry run to get all the verbose output
+                * before generating any data.  Then do a non-verbose real
+                * run to generate the streams.
+                */
+               sdd.dryrun = B_TRUE;
+               err = dump_filesystems(zhp, &sdd);
+               sdd.dryrun = flags->dryrun;
+               sdd.verbose = B_FALSE;
+               if (flags->parsable) {
+                       (void) fprintf(stderr, "size\t%llu\n",
+                           (longlong_t)sdd.size);
+               } else {
+                       char buf[16];
+                       zfs_nicenum(sdd.size, buf, sizeof (buf));
+                       (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+                           "total estimated size is %s\n"), buf);
+               }
+       }
        err = dump_filesystems(zhp, &sdd);
        fsavl_destroy(fsavl);
        nvlist_free(fss);
 
-       if (flags.dedup) {
+       if (flags->dedup) {
                (void) close(pipefd[0]);
                (void) pthread_join(tid, NULL);
        }
@@ -1441,7 +1502,8 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                sdd.cleanup_fd = -1;
        }
 
-       if (flags.replicate || flags.doall || flags.props) {
+       if (!flags->dryrun && (flags->replicate || flags->doall ||
+           flags->props)) {
                /*
                 * write final end record.  NB: want to do this even if
                 * there was some error, because it might not be totally
@@ -1462,7 +1524,7 @@ stderr_out:
 err_out:
        if (sdd.cleanup_fd != -1)
                VERIFY(0 == close(sdd.cleanup_fd));
-       if (flags.dedup) {
+       if (flags->dedup) {
                (void) pthread_cancel(tid);
                (void) pthread_join(tid, NULL);
                (void) close(pipefd[0]);
@@ -1533,7 +1595,7 @@ recv_read_nvlist(libzfs_handle_t *hdl, int fd, int len, nvlist_t **nvp,
 
 static int
 recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
-    int baselen, char *newname, recvflags_t flags)
+    int baselen, char *newname, recvflags_t *flags)
 {
        static int seq;
        zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
@@ -1545,7 +1607,7 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
        if (zhp == NULL)
                return (-1);
        clp = changelist_gather(zhp, ZFS_PROP_NAME, 0,
-           flags.force ? MS_FORCE : 0);
+           flags->force ? MS_FORCE : 0);
        zfs_close(zhp);
        if (clp == NULL)
                return (-1);
@@ -1561,7 +1623,7 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
 
                (void) strlcpy(zc.zc_value, tryname, sizeof (zc.zc_value));
 
-               if (flags.verbose) {
+               if (flags->verbose) {
                        (void) printf("attempting rename %s to %s\n",
                            zc.zc_name, zc.zc_value);
                }
@@ -1580,19 +1642,19 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
                    "recv-%ld-%u", (long) getpid(), seq);
                (void) strlcpy(zc.zc_value, newname, sizeof (zc.zc_value));
 
-               if (flags.verbose) {
+               if (flags->verbose) {
                        (void) printf("failed - trying rename %s to %s\n",
                            zc.zc_name, zc.zc_value);
                }
                err = ioctl(hdl->libzfs_fd, ZFS_IOC_RENAME, &zc);
                if (err == 0)
                        changelist_rename(clp, name, newname);
-               if (err && flags.verbose) {
+               if (err && flags->verbose) {
                        (void) printf("failed (%u) - "
                            "will try again on next pass\n", errno);
                }
                err = EAGAIN;
-       } else if (flags.verbose) {
+       } else if (flags->verbose) {
                if (err == 0)
                        (void) printf("success\n");
                else
@@ -1607,7 +1669,7 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
 
 static int
 recv_destroy(libzfs_handle_t *hdl, const char *name, int baselen,
-    char *newname, recvflags_t flags)
+    char *newname, recvflags_t *flags)
 {
        zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        int err = 0;
@@ -1620,7 +1682,7 @@ recv_destroy(libzfs_handle_t *hdl, const char *name, int baselen,
        if (zhp == NULL)
                return (-1);
        clp = changelist_gather(zhp, ZFS_PROP_NAME, 0,
-           flags.force ? MS_FORCE : 0);
+           flags->force ? MS_FORCE : 0);
        if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT &&
            zfs_spa_version(zhp, &spa_version) == 0 &&
            spa_version >= SPA_VERSION_USERREFS)
@@ -1636,11 +1698,11 @@ recv_destroy(libzfs_handle_t *hdl, const char *name, int baselen,
        zc.zc_defer_destroy = defer;
        (void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name));
 
-       if (flags.verbose)
+       if (flags->verbose)
                (void) printf("attempting destroy %s\n", zc.zc_name);
        err = ioctl(hdl->libzfs_fd, ZFS_IOC_DESTROY, &zc);
        if (err == 0) {
-               if (flags.verbose)
+               if (flags->verbose)
                        (void) printf("success\n");
                changelist_remove(clp, zc.zc_name);
        }
@@ -1663,6 +1725,7 @@ recv_destroy(libzfs_handle_t *hdl, const char *name, int baselen,
 typedef struct guid_to_name_data {
        uint64_t guid;
        char *name;
+       char *skip;
 } guid_to_name_data_t;
 
 static int
@@ -1671,21 +1734,35 @@ guid_to_name_cb(zfs_handle_t *zhp, void *arg)
        guid_to_name_data_t *gtnd = arg;
        int err;
 
+       if (gtnd->skip != NULL &&
+           strcmp(zhp->zfs_name, gtnd->skip) == 0) {
+               return (0);
+       }
+
        if (zhp->zfs_dmustats.dds_guid == gtnd->guid) {
                (void) strcpy(gtnd->name, zhp->zfs_name);
                zfs_close(zhp);
                return (EEXIST);
        }
+
        err = zfs_iter_children(zhp, guid_to_name_cb, gtnd);
        zfs_close(zhp);
        return (err);
 }
 
+/*
+ * Attempt to find the local dataset associated with this guid.  In the case of
+ * multiple matches, we attempt to find the "best" match by searching
+ * progressively larger portions of the hierarchy.  This allows one to send a
+ * tree of datasets individually and guarantee that we will find the source
+ * guid within that hierarchy, even if there are multiple matches elsewhere.
+ */
 static int
 guid_to_name(libzfs_handle_t *hdl, const char *parent, uint64_t guid,
     char *name)
 {
        /* exhaustive search all local snapshots */
+       char pname[ZFS_MAXNAMELEN];
        guid_to_name_data_t gtnd;
        int err = 0;
        zfs_handle_t *zhp;
@@ -1693,35 +1770,42 @@ guid_to_name(libzfs_handle_t *hdl, const char *parent, uint64_t guid,
 
        gtnd.guid = guid;
        gtnd.name = name;
+       gtnd.skip = NULL;
 
-       if (strchr(parent, '@') == NULL) {
-               zhp = make_dataset_handle(hdl, parent);
-               if (zhp != NULL) {
-                       err = zfs_iter_children(zhp, guid_to_name_cb, &gtnd);
-                       zfs_close(zhp);
-                       if (err == EEXIST)
-                               return (0);
-               }
-       }
+       (void) strlcpy(pname, parent, sizeof (pname));
 
-       cp = strchr(parent, '/');
-       if (cp)
+       /*
+        * Search progressively larger portions of the hierarchy.  This will
+        * select the "most local" version of the origin snapshot in the case
+        * that there are multiple matching snapshots in the system.
+        */
+       while ((cp = strrchr(pname, '/')) != NULL) {
+
+               /* Chop off the last component and open the parent */
                *cp = '\0';
-       zhp = make_dataset_handle(hdl, parent);
-       if (cp)
-               *cp = '/';
+               zhp = make_dataset_handle(hdl, pname);
+
+               if (zhp == NULL)
+                       continue;
 
-       if (zhp) {
                err = zfs_iter_children(zhp, guid_to_name_cb, &gtnd);
                zfs_close(zhp);
-       }
+               if (err == EEXIST)
+                       return (0);
 
-       return (err == EEXIST ? 0 : ENOENT);
+               /*
+                * Remember the dataset that we already searched, so we
+                * skip it next time through.
+                */
+               gtnd.skip = pname;
+       }
 
+       return (ENOENT);
 }
 
 /*
- * Return true if dataset guid1 is created before guid2.
+ * Return +1 if guid1 is before guid2, 0 if they are the same, and -1 if
+ * guid1 is after guid2.
  */
 static int
 created_before(libzfs_handle_t *hdl, avl_tree_t *avl,
@@ -1731,7 +1815,8 @@ created_before(libzfs_handle_t *hdl, avl_tree_t *avl,
        char *fsname, *snapname;
        char buf[ZFS_MAXNAMELEN];
        int rv;
-       zfs_node_t zn1, zn2;
+       zfs_handle_t *guid1hdl, *guid2hdl;
+       uint64_t create1, create2;
 
        if (guid2 == 0)
                return (0);
@@ -1741,30 +1826,38 @@ created_before(libzfs_handle_t *hdl, avl_tree_t *avl,
        nvfs = fsavl_find(avl, guid1, &snapname);
        VERIFY(0 == nvlist_lookup_string(nvfs, "name", &fsname));
        (void) snprintf(buf, sizeof (buf), "%s@%s", fsname, snapname);
-       zn1.zn_handle = zfs_open(hdl, buf, ZFS_TYPE_SNAPSHOT);
-       if (zn1.zn_handle == NULL)
+       guid1hdl = zfs_open(hdl, buf, ZFS_TYPE_SNAPSHOT);
+       if (guid1hdl == NULL)
                return (-1);
 
        nvfs = fsavl_find(avl, guid2, &snapname);
        VERIFY(0 == nvlist_lookup_string(nvfs, "name", &fsname));
        (void) snprintf(buf, sizeof (buf), "%s@%s", fsname, snapname);
-       zn2.zn_handle = zfs_open(hdl, buf, ZFS_TYPE_SNAPSHOT);
-       if (zn2.zn_handle == NULL) {
-               zfs_close(zn2.zn_handle);
+       guid2hdl = zfs_open(hdl, buf, ZFS_TYPE_SNAPSHOT);
+       if (guid2hdl == NULL) {
+               zfs_close(guid1hdl);
                return (-1);
        }
 
-       rv = (zfs_snapshot_compare(&zn1, &zn2) == -1);
+       create1 = zfs_prop_get_int(guid1hdl, ZFS_PROP_CREATETXG);
+       create2 = zfs_prop_get_int(guid2hdl, ZFS_PROP_CREATETXG);
 
-       zfs_close(zn1.zn_handle);
-       zfs_close(zn2.zn_handle);
+       if (create1 < create2)
+               rv = -1;
+       else if (create1 > create2)
+               rv = +1;
+       else
+               rv = 0;
+
+       zfs_close(guid1hdl);
+       zfs_close(guid2hdl);
 
        return (rv);
 }
 
 static int
 recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs,
-    recvflags_t flags, nvlist_t *stream_nv, avl_tree_t *stream_avl,
+    recvflags_t *flags, nvlist_t *stream_nv, avl_tree_t *stream_avl,
     nvlist_t *renamed)
 {
        nvlist_t *local_nv;
@@ -1781,7 +1874,7 @@ recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs,
        recursive = (nvlist_lookup_boolean(stream_nv, "not_recursive") ==
            ENOENT);
 
-       if (flags.dryrun)
+       if (flags->dryrun)
                return (0);
 
 again:
@@ -1841,7 +1934,7 @@ again:
                                nvlist_t *origin_nvfs;
                                char *origin_fsname;
 
-                               if (flags.verbose)
+                               if (flags->verbose)
                                        (void) printf("promoting %s\n", fsname);
 
                                origin_nvfs = fsavl_find(local_avl, originguid,
@@ -1889,7 +1982,7 @@ again:
                        if (found == NULL) {
                                char name[ZFS_MAXNAMELEN];
 
-                               if (!flags.force)
+                               if (!flags->force)
                                        continue;
 
                                (void) snprintf(name, sizeof (name), "%s@%s",
@@ -1947,7 +2040,7 @@ again:
 
                /* check for delete */
                if (stream_nvfs == NULL) {
-                       if (!flags.force)
+                       if (!flags->force)
                                continue;
 
                        error = recv_destroy(hdl, fsname, strlen(tofs)+1,
@@ -1960,7 +2053,7 @@ again:
                }
 
                if (fromguid == 0) {
-                       if (flags.verbose) {
+                       if (flags->verbose) {
                                (void) printf("local fs %s does not have "
                                    "fromsnap (%s in stream); must have "
                                    "been deleted locally; ignoring\n",
@@ -1985,7 +2078,7 @@ again:
                if ((stream_parent_fromsnap_guid != 0 &&
                    parent_fromsnap_guid != 0 &&
                    stream_parent_fromsnap_guid != parent_fromsnap_guid) ||
-                   ((flags.isprefix || strcmp(tofs, fsname) != 0) &&
+                   ((flags->isprefix || strcmp(tofs, fsname) != 0) &&
                    (s1 != NULL) && (s2 != NULL) && strcmp(s1, s2) != 0)) {
                        nvlist_t *parent;
                        char tryname[ZFS_MAXNAMELEN];
@@ -2008,7 +2101,7 @@ again:
                                    "%s%s", pname, strrchr(stream_fsname, '/'));
                        } else {
                                tryname[0] = '\0';
-                               if (flags.verbose) {
+                               if (flags->verbose) {
                                        (void) printf("local fs %s new parent "
                                            "not found\n", fsname);
                                }
@@ -2036,7 +2129,7 @@ again:
 
        if (needagain && progress) {
                /* do another pass to fix up temporary names */
-               if (flags.verbose)
+               if (flags->verbose)
                        (void) printf("another pass:\n");
                goto again;
        }
@@ -2046,7 +2139,7 @@ again:
 
 static int
 zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
-    recvflags_t flags, dmu_replay_record_t *drr, zio_cksum_t *zc,
+    recvflags_t *flags, dmu_replay_record_t *drr, zio_cksum_t *zc,
     char **top_zfs, int cleanup_fd, uint64_t *action_handlep)
 {
        nvlist_t *stream_nv = NULL;
@@ -2075,7 +2168,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
         */
        if (drr->drr_payloadlen != 0) {
                error = recv_read_nvlist(hdl, fd, drr->drr_payloadlen,
-                   &stream_nv, flags.byteswap, zc);
+                   &stream_nv, flags->byteswap, zc);
                if (error) {
                        error = zfs_error(hdl, EZFS_BADSTREAM, errbuf);
                        goto out;
@@ -2096,9 +2189,9 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
         * Read in the end record and verify checksum.
         */
        if (0 != (error = recv_read(hdl, fd, &drre, sizeof (drre),
-           flags.byteswap, NULL)))
+           flags->byteswap, NULL)))
                goto out;
-       if (flags.byteswap) {
+       if (flags->byteswap) {
                drre.drr_type = BSWAP_32(drre.drr_type);
                drre.drr_u.drr_end.drr_checksum.zc_word[0] =
                    BSWAP_64(drre.drr_u.drr_end.drr_checksum.zc_word[0]);
@@ -2139,11 +2232,11 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
                        nvpair_t *pair = NULL;
 
                        (void) strlcpy(tofs, destname, ZFS_MAXNAMELEN);
-                       if (flags.isprefix) {
+                       if (flags->isprefix) {
                                struct drr_begin *drrb = &drr->drr_u.drr_begin;
                                int i;
 
-                               if (flags.istail) {
+                               if (flags->istail) {
                                        cp = strrchr(drrb->drr_toname, '/');
                                        if (cp == NULL) {
                                                (void) strlcat(tofs, "/",
@@ -2161,7 +2254,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
                                *strchr(tofs, '@') = '\0';
                        }
 
-                       if (recursive && !flags.dryrun && !flags.nomount) {
+                       if (recursive && !flags->dryrun && !flags->nomount) {
                                VERIFY(0 == nvlist_alloc(&renamed,
                                    NV_UNIQUE_NAME, 0));
                        }
@@ -2335,7 +2428,7 @@ recv_skip(libzfs_handle_t *hdl, int fd, boolean_t byteswap)
  */
 static int
 zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
-    recvflags_t flags, dmu_replay_record_t *drr,
+    recvflags_t *flags, dmu_replay_record_t *drr,
     dmu_replay_record_t *drr_noswap, const char *sendfs,
     nvlist_t *stream_nv, avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd,
     uint64_t *action_handlep)
@@ -2377,7 +2470,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                if (err)
                        VERIFY(0 == nvlist_alloc(&props, NV_UNIQUE_NAME, 0));
 
-               if (flags.canmountoff) {
+               if (flags->canmountoff) {
                        VERIFY(0 == nvlist_add_uint64(props,
                            zfs_prop_to_name(ZFS_PROP_CANMOUNT), 0));
                }
@@ -2404,7 +2497,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
         * If they specified a snapshot, chop the entire name stored in
         * the stream.
         */
-       if (flags.istail) {
+       if (flags->istail) {
                /*
                 * A filesystem was specified with -e. We want to tack on only
                 * the tail of the sent snapshot path.
@@ -2430,7 +2523,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                } else {
                        chopprefix = drrb->drr_toname + (chopprefix - sendfs);
                }
-       } else if (flags.isprefix) {
+       } else if (flags->isprefix) {
                /*
                 * A filesystem was specified with -d. We want to tack on
                 * everything but the first element of the sent snapshot path
@@ -2484,7 +2577,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
         * Determine the name of the origin snapshot, store in zc_string.
         */
        if (drrb->drr_flags & DRR_FLAG_CLONE) {
-               if (guid_to_name(hdl, tosnap,
+               if (guid_to_name(hdl, zc.zc_value,
                    drrb->drr_fromguid, zc.zc_string) != 0) {
                        zcmd_free_nvlists(&zc);
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
@@ -2492,7 +2585,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                            zc.zc_value);
                        return (zfs_error(hdl, EZFS_NOENT, errbuf));
                }
-               if (flags.verbose)
+               if (flags->verbose)
                        (void) printf("found clone origin %s\n", zc.zc_string);
        }
 
@@ -2515,7 +2608,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                    !zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_DATASET)) {
                        char suffix[ZFS_MAXNAMELEN];
                        (void) strcpy(suffix, strrchr(zc.zc_value, '/'));
-                       if (guid_to_name(hdl, tosnap, parent_snapguid,
+                       if (guid_to_name(hdl, zc.zc_name, parent_snapguid,
                            zc.zc_value) == 0) {
                                *strchr(zc.zc_value, '@') = '\0';
                                (void) strcat(zc.zc_value, suffix);
@@ -2537,12 +2630,12 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                 * topmost path in the stream, then if the fs does not exist we
                 * should look no further.
                 */
-               if ((flags.isprefix || (*(chopprefix = drrb->drr_toname +
+               if ((flags->isprefix || (*(chopprefix = drrb->drr_toname +
                    strlen(sendfs)) != '\0' && *chopprefix != '@')) &&
                    !zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_DATASET)) {
                        char snap[ZFS_MAXNAMELEN];
                        (void) strcpy(snap, strchr(zc.zc_value, '@'));
-                       if (guid_to_name(hdl, tosnap, drrb->drr_fromguid,
+                       if (guid_to_name(hdl, zc.zc_name, drrb->drr_fromguid,
                            zc.zc_value) == 0) {
                                *strchr(zc.zc_value, '@') = '\0';
                                (void) strcat(zc.zc_value, snap);
@@ -2564,7 +2657,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                 * snapshots).
                 */
                if (stream_wantsnewfs) {
-                       if (!flags.force) {
+                       if (!flags->force) {
                                zcmd_free_nvlists(&zc);
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                                    "destination '%s' exists\n"
@@ -2600,7 +2693,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                        return (zfs_error(hdl, EZFS_EXISTS, errbuf));
                }
 
-               if (!flags.dryrun && zhp->zfs_type == ZFS_TYPE_FILESYSTEM &&
+               if (!flags->dryrun && zhp->zfs_type == ZFS_TYPE_FILESYSTEM &&
                    stream_wantsnewfs) {
                        /* We can't do online recv in this case */
                        clp = changelist_gather(zhp, ZFS_PROP_NAME, 0, 0);
@@ -2616,7 +2709,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                                return (-1);
                        }
                }
-               if (!flags.dryrun && zhp->zfs_type == ZFS_TYPE_VOLUME &&
+               if (!flags->dryrun && zhp->zfs_type == ZFS_TYPE_VOLUME &&
                    zvol_remove_link(hdl, zhp->zfs_name) != 0) {
                        zfs_close(zhp);
                        zcmd_free_nvlists(&zc);
@@ -2645,7 +2738,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                 */
                *cp = '\0';
 
-               if (flags.isprefix && !flags.istail && !flags.dryrun &&
+               if (flags->isprefix && !flags->istail && !flags->dryrun &&
                    create_parents(hdl, zc.zc_value, strlen(tosnap)) != 0) {
                        zcmd_free_nvlists(&zc);
                        return (zfs_error(hdl, EZFS_BADRESTORE, errbuf));
@@ -2656,18 +2749,18 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
 
        zc.zc_begin_record = drr_noswap->drr_u.drr_begin;
        zc.zc_cookie = infd;
-       zc.zc_guid = flags.force;
-       if (flags.verbose) {
+       zc.zc_guid = flags->force;
+       if (flags->verbose) {
                (void) printf("%s %s stream of %s into %s\n",
-                   flags.dryrun ? "would receive" : "receiving",
+                   flags->dryrun ? "would receive" : "receiving",
                    drrb->drr_fromguid ? "incremental" : "full",
                    drrb->drr_toname, zc.zc_value);
                (void) fflush(stdout);
        }
 
-       if (flags.dryrun) {
+       if (flags->dryrun) {
                zcmd_free_nvlists(&zc);
-               return (recv_skip(hdl, infd, flags.byteswap));
+               return (recv_skip(hdl, infd, flags->byteswap));
        }
 
        zc.zc_nvlist_dst = (uint64_t)(uintptr_t)prop_errbuf;
@@ -2748,12 +2841,12 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                        nvlist_free(local_nv);
 
                        if (fs != NULL) {
-                               if (flags.verbose) {
+                               if (flags->verbose) {
                                        (void) printf("snap %s already exists; "
                                            "ignoring\n", zc.zc_value);
                                }
                                err = ioctl_err = recv_skip(hdl, infd,
-                                   flags.byteswap);
+                                   flags->byteswap);
                        }
                }
                *cp = '@';
@@ -2805,7 +2898,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                case EDQUOT:
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "destination %s space quota exceeded"), zc.zc_name);
-                       (void) zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+                       (void) zfs_error(hdl, EZFS_NOSPC, errbuf);
                        break;
                default:
                        (void) zfs_standard_error(hdl, ioctl_errno, errbuf);
@@ -2867,7 +2960,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
 
        *action_handlep = zc.zc_action_handle;
 
-       if (flags.verbose) {
+       if (flags->verbose) {
                char buf1[64];
                char buf2[64];
                uint64_t bytes = zc.zc_cookie;
@@ -2885,7 +2978,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
 }
 
 static int
-zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, recvflags_t flags,
+zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, recvflags_t *flags,
     int infd, const char *sendfs, nvlist_t *stream_nv, avl_tree_t *stream_avl,
     char **top_zfs, int cleanup_fd, uint64_t *action_handlep)
 {
@@ -2900,7 +2993,7 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, recvflags_t flags,
        (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
            "cannot receive"));
 
-       if (flags.isprefix &&
+       if (flags->isprefix &&
            !zfs_dataset_exists(hdl, tosnap, ZFS_TYPE_DATASET)) {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "specified fs "
                    "(%s) does not exist"), tosnap);
@@ -2920,7 +3013,7 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, recvflags_t flags,
        /* the kernel needs the non-byteswapped begin record */
        drr_noswap = drr;
 
-       flags.byteswap = B_FALSE;
+       flags->byteswap = B_FALSE;
        if (drrb->drr_magic == BSWAP_64(DMU_BACKUP_MAGIC)) {
                /*
                 * We computed the checksum in the wrong byteorder in
@@ -2928,7 +3021,7 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, recvflags_t flags,
                 */
                bzero(&zcksum, sizeof (zio_cksum_t));
                fletcher_4_incremental_byteswap(&drr, sizeof (drr), &zcksum);
-               flags.byteswap = B_TRUE;
+               flags->byteswap = B_TRUE;
 
                drr.drr_type = BSWAP_32(drr.drr_type);
                drr.drr_payloadlen = BSWAP_32(drr.drr_payloadlen);
@@ -2996,7 +3089,7 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, recvflags_t flags,
  * (-1 will override -2).
  */
 int
-zfs_receive(libzfs_handle_t *hdl, const char *tosnap, recvflags_t flags,
+zfs_receive(libzfs_handle_t *hdl, const char *tosnap, recvflags_t *flags,
     int infd, avl_tree_t *stream_avl)
 {
        char *top_zfs = NULL;
@@ -3012,7 +3105,7 @@ zfs_receive(libzfs_handle_t *hdl, const char *tosnap, recvflags_t flags,
 
        VERIFY(0 == close(cleanup_fd));
 
-       if (err == 0 && !flags.nomount && top_zfs) {
+       if (err == 0 && !flags->nomount && top_zfs) {
                zfs_handle_t *zhp;
                prop_changelist_t *clp;
 
index 5618a5c..de4bb72 100644 (file)
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  */
 
 /*
@@ -346,6 +347,7 @@ zfs_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...)
        switch (error) {
        case ENXIO:
        case ENODEV:
+       case EPIPE:
                zfs_verror(hdl, EZFS_IO, fmt, ap);
                break;
 
@@ -1420,7 +1422,8 @@ addlist(libzfs_handle_t *hdl, char *propname, zprop_list_t **listp,
         * dataset property,
         */
        if (prop == ZPROP_INVAL && (type == ZFS_TYPE_POOL ||
-           (!zfs_prop_user(propname) && !zfs_prop_userquota(propname)))) {
+           (!zfs_prop_user(propname) && !zfs_prop_userquota(propname) &&
+           !zfs_prop_written(propname)))) {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "invalid property '%s'"), propname);
                return (zfs_error(hdl, EZFS_BADPROP,
index 3fd91a9..62ca9da 100644 (file)
@@ -1,12 +1,10 @@
 '\" te
 .\" Copyright (c) 2009 Sun Microsystems, Inc. All Rights Reserved.
+.\" Copyright (c) 2011 by Delphix. All rights reserved.
 .\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License").  You may not use this file except in compliance with the License. You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.
 .\"  See the License for the specific language governing permissions and limitations under the License. When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE.  If applicable, add the following below this CDDL HEADER, with
 .\" the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License").  You may not use this file except in compliance with the License. You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.
-.\"  See the License for the specific language governing permissions and limitations under the License. When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE.  If applicable, add the following below this CDDL HEADER, with
-.\" the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH zfs 8 "8 Apr 2011" "ZFS pool 28, filesystem 5" "System Administration Commands"
+.TH zfs 8 "10 Jul 2012" "ZFS pool 28, filesystem 5" "System Administration Commands"
 .SH NAME
 zfs \- configures ZFS file systems
 .SH SYNOPSIS
@@ -27,12 +25,12 @@ zfs \- configures ZFS file systems
 
 .LP
 .nf
-\fBzfs\fR \fBdestroy\fR [\fB-rRf\fR] \fIfilesystem\fR|\fIvolume\fR
+\fBzfs\fR \fBdestroy\fR [\fB-fnpRrv\fR] \fIfilesystem\fR|\fIvolume\fR
 .fi
 
 .LP
 .nf
-\fBzfs\fR \fBdestroy\fR [\fB-rRd\fR] \fIsnapshot\fR
+\fBzfs\fR \fBdestroy\fR [\fB-dnpRrv\fR] \fIfilesystem\fR|\fIvolume\fR@\fIsnap\fR[%\fIsnap\fR][,...]
 .fi
 
 .LP
@@ -107,13 +105,13 @@ zfs \- configures ZFS file systems
 .LP
 .nf
 \fBzfs\fR \fBuserspace\fR [\fB-niHp\fR] [\fB-o\fR \fIfield\fR[,...]] [\fB-sS\fR \fIfield\fR] ...
-     [\fB-t\fR \fItype\fR [,...]] \fIfilesystem\fR|\fIsnapshot\fR
+     [\fB-t\fR \fItype\fR[,...]] \fIfilesystem\fR|\fIsnapshot\fR
 .fi
 
 .LP
 .nf
 \fBzfs\fR \fBgroupspace\fR [\fB-niHp\fR] [\fB-o\fR \fIfield\fR[,...]] [\fB-sS\fR \fIfield\fR] ...
-     [\fB-t\fR \fItype\fR [,...]] \fIfilesystem\fR|\fIsnapshot\fR
+     [\fB-t\fR \fItype\fR[,...]] \fIfilesystem\fR|\fIsnapshot\fR
 .fi
 
 .LP
@@ -143,7 +141,7 @@ zfs \- configures ZFS file systems
 
 .LP
 .nf
-\fBzfs\fR \fBsend\fR [\fB-vRDp\fR] [\fB-\fR[\fBiI\fR] \fIsnapshot\fR] \fIsnapshot\fR
+\fBzfs\fR \fBsend\fR [\fB-DnPpRrv\fR] [\fB-\fR[\fBiI\fR] \fIsnapshot\fR] \fIsnapshot\fR
 .fi
 
 .LP
@@ -378,6 +376,19 @@ The time this dataset was created.
 .ne 2
 .mk
 .na
+\fB\fBclones\fR\fR
+.ad
+.sp .6
+.RS 4n
+For snapshots, this property is a comma-separated list of filesystems or
+volumes which are clones of this snapshot.  The clones' \fBorigin\fR property
+is this snapshot.  If the \fBclones\fR property is not empty, then this
+snapshot can not be destroyed (even with the \fB-r\fR or \fB-f\fR options).
+.RE
+
+.sp
+.ne 2
+.na
 \fB\fBdefer_destroy\fR\fR
 .ad
 .sp .6
@@ -404,7 +415,7 @@ For file systems, indicates whether the file system is currently mounted. This p
 .ad
 .sp .6
 .RS 4n
-For cloned file systems or volumes, the snapshot from which the clone was created. The origin cannot be destroyed (even with the \fB-r\fR or \fB-f\fR options) so long as a clone exists.
+For cloned file systems or volumes, the snapshot from which the clone was created. See also the \fBclones\fR property.
 .RE
 
 .sp
@@ -591,6 +602,36 @@ This property can also be referred to by its shortened column name, \fBvolblock\
 .RE
 
 .sp
+.ne 2
+.na
+\fB\fBwritten\fR\fR
+.ad
+.sp .6
+.RS 4n
+The amount of \fBreferenced\fR space written to this dataset since the
+previous snapshot.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fBwritten@\fR\fIsnapshot\fR\fR
+.ad
+.sp .6
+.RS 4n
+The amount of \fBreferenced\fR space written to this dataset since the
+specified snapshot.  This is the space that is referenced by this dataset
+but was not referenced by the specified snapshot.
+.sp
+The \fIsnapshot\fR may be specified as a short snapshot name (just the part
+after the \fB@\fR), in which case it will be interpreted as a snapshot in
+the same filesystem as this dataset.
+The \fIsnapshot\fR be a full snapshot name (\fIfilesystem\fR@\fIsnapshot\fR),
+which for clones may be a snapshot in the origin's filesystem (or the origin
+of the origin's filesystem, etc).
+.RE
+
+.sp
 .LP
 The following native properties can be used to change the behavior of a \fBZFS\fR dataset.
 .sp
@@ -972,6 +1013,26 @@ Controls whether the \fB\&.zfs\fR directory is hidden or visible in the root of
 .ne 2
 .mk
 .na
+\fB\fBsync\fR=\fBdefault\fR | \fBalways\fR | \fBdisabled\fR\fR
+.ad
+.sp .6
+.RS 4n
+Controls the behavior of synchronous requests (e.g. fsync, O_DSYNC).
+\fBdefault\fR is the POSIX specified behavior of ensuring all synchronous
+requests are written to stable storage and all devices are flushed to ensure
+data is not cached by device controllers (this is the default). \fBalways\fR
+causes every file system transaction to be written and flushed before its
+system call returns. This has a large performance penalty. \fBdisabled\fR
+disables synchronous requests. File system transactions are only committed to
+stable storage periodically. This option will give the highest performance.
+However, it is very dangerous as ZFS would be ignoring the synchronous
+transaction demands of applications such as databases or NFS.  Administrators
+should only use this option when the risks are understood.
+.RE
+
+.sp
+.ne 2
+.na
 \fB\fBversion\fR=\fB1\fR | \fB2\fR | \fBcurrent\fR\fR
 .ad
 .sp .6
@@ -1214,7 +1275,7 @@ Equivalent to \fB-o\fR \fBvolblocksize\fR=\fIblocksize\fR. If this option is spe
 .ne 2
 .mk
 .na
-\fB\fBzfs destroy\fR [\fB-rRf\fR] \fIfilesystem\fR|\fIvolume\fR\fR
+\fBzfs destroy\fR [\fB-fnpRrv\fR] \fIfilesystem\fR|\fIvolume\fR
 .ad
 .sp .6
 .RS 4n
@@ -1252,6 +1313,39 @@ Recursively destroy all dependents, including cloned file systems outside the ta
 Force an unmount of any file systems using the \fBunmount -f\fR command. This option has no effect on non-file systems or unmounted file systems.
 .RE
 
+.sp
+.ne 2
+.na
+\fB\fB-n\fR\fR
+.ad
+.sp .6
+.RS 4n
+Do a dry-run ("No-op") deletion.  No data will be deleted.  This is
+useful in conjunction with the \fB-v\fR or \fB-p\fR flags to determine what
+data would be deleted.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fB-p\fR\fR
+.ad
+.sp .6
+.RS 4n
+Print machine-parsable verbose information about the deleted data.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fB-v\fR\fR
+.ad
+.sp .6
+.RS 4n
+Print verbose information about the deleted data.
+.RE
+.sp
+
 Extreme care should be taken when applying either the \fB-r\fR or the \fB-R\fR options, as they can destroy large portions of a pool and cause unexpected behavior for mounted file systems in use.
 .RE
 
@@ -1259,13 +1353,25 @@ Extreme care should be taken when applying either the \fB-r\fR or the \fB-R\fR o
 .ne 2
 .mk
 .na
-\fB\fBzfs destroy\fR [\fB-rRd\fR] \fIsnapshot\fR\fR
+\fBzfs destroy\fR [\fB-dnpRrv\fR] \fIfilesystem\fR|\fIvolume\fR@\fIsnap\fR[%\fIsnap\fR][,...]
 .ad
 .sp .6
 .RS 4n
-The given snapshot is destroyed immediately if and only if the \fBzfs destroy\fR command without the \fB-d\fR option would have destroyed it. Such immediate destruction would occur, for example, if the snapshot had no clones and the user-initiated reference count were zero.
+The given snapshots are destroyed immediately if and only if the \fBzfs destroy\fR command without the \fB-d\fR option would have destroyed it. Such immediate destruction would occur, for example, if the snapshot had no clones and the user-initiated reference count were zero.
+.sp
+If a snapshot does not qualify for immediate destruction, it is marked for deferred destruction. In this state, it exists as a usable, visible snapshot until both of the preconditions listed above are met, at which point it is destroyed.
+.sp
+An inclusive range of snapshots may be specified by separating the
+first and last snapshots with a percent sign.
+The first and/or last snapshots may be left blank, in which case the
+filesystem's oldest or newest snapshot will be implied.
 .sp
-If the snapshot does not qualify for immediate destruction, it is marked for deferred destruction. In this state, it exists as a usable, visible snapshot until both of the preconditions listed above are met, at which point it is destroyed.
+Multiple snapshots
+(or ranges of snapshots) of the same filesystem or volume may be specified
+in a comma-separated list of snapshots.
+Only the snapshot's short name (the
+part after the \fB@\fR) should be specified when using a range or
+comma-separated list to identify multiple snapshots.
 .sp
 .ne 2
 .mk
@@ -1299,6 +1405,44 @@ Destroy (or mark for deferred destruction) all snapshots with this name in desce
 Recursively destroy all dependents.
 .RE
 
+.sp
+.ne 2
+.na
+\fB\fB-n\fR\fR
+.ad
+.sp .6
+.RS 4n
+Do a dry-run ("No-op") deletion.  No data will be deleted.  This is
+useful in conjunction with the \fB-v\fR or \fB-p\fR flags to determine what
+data would be deleted.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fB-p\fR\fR
+.ad
+.sp .6
+.RS 4n
+Print machine-parsable verbose information about the deleted data.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fB-v\fR\fR
+.ad
+.sp .6
+.RS 4n
+Print verbose information about the deleted data.
+.RE
+
+.sp
+Extreme care should be taken when applying either the \fB-r\fR or the \fB-f\fR
+options, as they can destroy large portions of a pool and cause unexpected
+behavior for mounted file systems in use.
+.RE
+
 .RE
 
 .sp
@@ -2113,7 +2257,7 @@ Unshare the specified filesystem. The command can also be given a path to a \fBZ
 .ne 2
 .mk
 .na
-\fB\fBzfs send\fR [\fB-vRDp\fR] [\fB-\fR[\fBiI\fR] \fIsnapshot\fR] \fIsnapshot\fR\fR
+\fBzfs send\fR [\fB-DnPpRrv\fR] [\fB-\fR[\fBiI\fR] \fIsnapshot\fR] \fIsnapshot\fR
 .ad
 .sp .6
 .RS 4n
@@ -2240,6 +2384,66 @@ File system that is associated with the received stream is not mounted.
 .ne 2
 .mk
 .na
+\fB\fB-D\fR\fR
+.ad
+.sp .6
+.RS 4n
+Generate a deduplicated stream.  Blocks which would have been sent multiple
+times in the send stream will only be sent once.  The receiving system must
+also support this feature to recieve a deduplicated stream.  This flag can
+be used regardless of the dataset's \fBdedup\fR property, but performance
+will be much better if the filesystem uses a dedup-capable checksum (eg.
+\fBsha256\fR).
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fB-r\fR\fR
+.ad
+.sp .6
+.RS 4n
+Recursively send all descendant snapshots.  This is similar to the \fB-R\fR
+flag, but information about deleted and renamed datasets is not included, and
+property information is only included if the \fB-p\fR flag is specified.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fB-p\fR\fR
+.ad
+.sp .6
+.RS 4n
+Include the dataset's properties in the stream.  This flag is implicit when
+\fB-R\fR is specified.  The receiving system must also support this feature.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fB-n\fR\fR
+.ad
+.sp .6
+.RS 4n
+Do a dry-run ("No-op") send.  Do not generate any actual send data.  This is
+useful in conjunction with the \fB-v\fR or \fB-P\fR flags to determine what
+data will be sent.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fB-P\fR\fR
+.ad
+.sp .6
+.RS 4n
+Print machine-parsable verbose information about the stream package generated.
+.RE
+
+.sp
+.ne 2
+.na
 \fB\fB-v\fR\fR
 .ad
 .sp .6
index 9afe3d9..0a0c25b 100644 (file)
@@ -264,7 +264,7 @@ zfs_prop_init(void)
        /* default index properties */
        zprop_register_index(ZFS_PROP_VERSION, "version", 0, PROP_DEFAULT,
            ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT,
-           "1 | 2 | 3 | 4 | current", "VERSION", version_table);
+           "1 | 2 | 3 | 4 | 5 | current", "VERSION", version_table);
        zprop_register_index(ZFS_PROP_CANMOUNT, "canmount", ZFS_CANMOUNT_ON,
            PROP_DEFAULT, ZFS_TYPE_FILESYSTEM, "on | off | noauto",
            "CANMOUNT", canmount_table);
@@ -294,6 +294,8 @@ zfs_prop_init(void)
        /* string properties */
        zprop_register_string(ZFS_PROP_ORIGIN, "origin", NULL, PROP_READONLY,
            ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, "<snapshot>", "ORIGIN");
+       zprop_register_string(ZFS_PROP_CLONES, "clones", NULL, PROP_READONLY,
+           ZFS_TYPE_SNAPSHOT, "<dataset>[,...]", "CLONES");
        zprop_register_string(ZFS_PROP_MOUNTPOINT, "mountpoint", "/",
            PROP_INHERIT, ZFS_TYPE_FILESYSTEM, "<path> | legacy | none",
            "MOUNTPOINT");
@@ -339,6 +341,8 @@ zfs_prop_init(void)
            ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, "<size>", "USEDREFRESERV");
        zprop_register_number(ZFS_PROP_USERREFS, "userrefs", 0, PROP_READONLY,
            ZFS_TYPE_SNAPSHOT, "<count>", "USERREFS");
+       zprop_register_number(ZFS_PROP_WRITTEN, "written", 0, PROP_READONLY,
+           ZFS_TYPE_DATASET, "<size>", "WRITTEN");
 
        /* default number properties */
        zprop_register_number(ZFS_PROP_QUOTA, "quota", 0, PROP_DEFAULT,
@@ -472,6 +476,18 @@ zfs_prop_userquota(const char *name)
 }
 
 /*
+ * Returns true if this is a valid written@ property.
+ * Note that after the @, any character is valid (eg, another @, for
+ * written@pool/fs@origin).
+ */
+boolean_t
+zfs_prop_written(const char *name)
+{
+       static const char *prefix = "written@";
+       return (strncmp(name, prefix, strlen(prefix)) == 0);
+}
+
+/*
  * Tables of index types, plus functions to convert between the user view
  * (strings) and internal representation (uint64_t).
  */
index 72be312..022921c 100644 (file)
  */
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  */
 
 #include <sys/bpobj.h>
 #include <sys/zfs_context.h>
 #include <sys/refcount.h>
+#include <sys/dsl_pool.h>
 
 uint64_t
 bpobj_alloc(objset_t *os, int blocksize, dmu_tx_t *tx)
@@ -440,7 +442,10 @@ space_range_cb(void *arg, const blkptr_t *bp, dmu_tx_t *tx)
        struct space_range_arg *sra = arg;
 
        if (bp->blk_birth > sra->mintxg && bp->blk_birth <= sra->maxtxg) {
-               sra->used += bp_get_dsize_sync(sra->spa, bp);
+               if (dsl_pool_sync_context(spa_get_dsl(sra->spa)))
+                       sra->used += bp_get_dsize_sync(sra->spa, bp);
+               else
+                       sra->used += bp_get_dsize(sra->spa, bp);
                sra->comp += BP_GET_PSIZE(bp);
                sra->uncomp += BP_GET_UCSIZE(bp);
        }
index fad770e..2f06132 100644 (file)
@@ -20,9 +20,9 @@
  */
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
- */
-/*
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  * Copyright 2011 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  */
 
 #include <sys/dmu.h>
@@ -47,6 +47,9 @@
 #include <sys/ddt.h>
 #include <sys/zfs_onexit.h>
 
+/* Set this tunable to TRUE to replace corrupt data with 0x2f5baddb10c */
+int zfs_send_corrupt_data = B_FALSE;
+
 static char *dmu_recv_tag = "dmu_recv_tag";
 
 /*
@@ -368,8 +371,20 @@ backup_cb(spa_t *spa, zilog_t *zilog, const blkptr_t *bp, arc_buf_t *pbuf,
 
                if (dsl_read(NULL, spa, bp, pbuf,
                    arc_getbuf_func, &abuf, ZIO_PRIORITY_ASYNC_READ,
-                   ZIO_FLAG_CANFAIL, &aflags, zb) != 0)
-                       return (EIO);
+                   ZIO_FLAG_CANFAIL, &aflags, zb) != 0) {
+                       if (zfs_send_corrupt_data) {
+                               uint64_t *ptr;
+                               /* Send a block filled with 0x"zfs badd bloc" */
+                               abuf = arc_buf_alloc(spa, blksz, &abuf,
+                                   ARC_BUFC_DATA);
+                               for (ptr = abuf->b_data;
+                                   (char *)ptr < (char *)abuf->b_data + blksz;
+                                   ptr++)
+                                       *ptr = 0x2f5baddb10c;
+                       } else {
+                               return (EIO);
+                       }
+               }
 
                err = dump_data(ba, type, zb->zb_object, zb->zb_blkid * blksz,
                    blksz, bp, abuf->b_data);
@@ -498,6 +513,85 @@ dmu_sendbackup(objset_t *tosnap, objset_t *fromsnap, boolean_t fromorigin,
        return (0);
 }
 
+int
+dmu_send_estimate(objset_t *tosnap, objset_t *fromsnap, boolean_t fromorigin,
+    uint64_t *sizep)
+{
+       dsl_dataset_t *ds = tosnap->os_dsl_dataset;
+       dsl_dataset_t *fromds = fromsnap ? fromsnap->os_dsl_dataset : NULL;
+       dsl_pool_t *dp = ds->ds_dir->dd_pool;
+       int err;
+       uint64_t size, recordsize;
+
+       /* tosnap must be a snapshot */
+       if (ds->ds_phys->ds_next_snap_obj == 0)
+               return (EINVAL);
+
+       /* fromsnap must be an earlier snapshot from the same fs as tosnap */
+       if (fromds && (ds->ds_dir != fromds->ds_dir ||
+           fromds->ds_phys->ds_creation_txg >= ds->ds_phys->ds_creation_txg))
+               return (EXDEV);
+
+       if (fromorigin) {
+               if (fromsnap)
+                       return (EINVAL);
+
+               if (dsl_dir_is_clone(ds->ds_dir)) {
+                       rw_enter(&dp->dp_config_rwlock, RW_READER);
+                       err = dsl_dataset_hold_obj(dp,
+                           ds->ds_dir->dd_phys->dd_origin_obj, FTAG, &fromds);
+                       rw_exit(&dp->dp_config_rwlock);
+                       if (err)
+                               return (err);
+               } else {
+                       fromorigin = B_FALSE;
+               }
+       }
+
+       /* Get uncompressed size estimate of changed data. */
+       if (fromds == NULL) {
+               size = ds->ds_phys->ds_uncompressed_bytes;
+       } else {
+               uint64_t used, comp;
+               err = dsl_dataset_space_written(fromds, ds,
+                   &used, &comp, &size);
+               if (fromorigin)
+                       dsl_dataset_rele(fromds, FTAG);
+               if (err)
+                       return (err);
+       }
+
+       /*
+        * Assume that space (both on-disk and in-stream) is dominated by
+        * data.  We will adjust for indirect blocks and the copies property,
+        * but ignore per-object space used (eg, dnodes and DRR_OBJECT records).
+        */
+
+       /*
+        * Subtract out approximate space used by indirect blocks.
+        * Assume most space is used by data blocks (non-indirect, non-dnode).
+        * Assume all blocks are recordsize.  Assume ditto blocks and
+        * internal fragmentation counter out compression.
+        *
+        * Therefore, space used by indirect blocks is sizeof(blkptr_t) per
+        * block, which we observe in practice.
+        */
+       rw_enter(&dp->dp_config_rwlock, RW_READER);
+       err = dsl_prop_get_ds(ds, "recordsize",
+           sizeof (recordsize), 1, &recordsize, NULL);
+       rw_exit(&dp->dp_config_rwlock);
+       if (err)
+               return (err);
+       size -= size / recordsize * sizeof (blkptr_t);
+
+       /* Add in the space for the record associated with each block. */
+       size += size / recordsize * sizeof (dmu_replay_record_t);
+
+       *sizep = size;
+
+       return (0);
+}
+
 struct recvbeginsyncarg {
        const char *tofs;
        const char *tosnap;
@@ -1500,7 +1594,7 @@ dmu_recv_existing_end(dmu_recv_cookie_t *drc)
 {
        struct recvendsyncarg resa;
        dsl_dataset_t *ds = drc->drc_logical_ds;
-       int err;
+       int err, myerr;
 
        /*
         * XXX hack; seems the ds is still dirty and dsl_pool_zil_clean()
@@ -1538,7 +1632,8 @@ out:
        if (err == 0 && drc->drc_guid_to_ds_map != NULL)
                (void) add_ds_to_guidmap(drc->drc_guid_to_ds_map, ds);
        dsl_dataset_disown(ds, dmu_recv_tag);
-       (void) dsl_dataset_destroy(drc->drc_real_ds, dmu_recv_tag, B_FALSE);
+       myerr = dsl_dataset_destroy(drc->drc_real_ds, dmu_recv_tag, B_FALSE);
+       ASSERT3U(myerr, ==, 0);
        return (err);
 }
 
index 2deec8c..25c8ac6 100644 (file)
@@ -907,69 +907,56 @@ dsl_dataset_create_sync(dsl_dir_t *pdd, const char *lastname,
        return (dsobj);
 }
 
-struct destroyarg {
-       dsl_sync_task_group_t *dstg;
-       char *snapname;
-       char *failed;
-       boolean_t defer;
-};
-
-static int
-dsl_snapshot_destroy_one(const char *name, void *arg)
-{
-       struct destroyarg *da = arg;
-       dsl_dataset_t *ds;
-       int err;
-       char *dsname;
-
-       dsname = kmem_asprintf("%s@%s", name, da->snapname);
-       err = dsl_dataset_own(dsname, B_TRUE, da->dstg, &ds);
-       strfree(dsname);
-       if (err == 0) {
-               struct dsl_ds_destroyarg *dsda;
-
-               dsl_dataset_make_exclusive(ds, da->dstg);
-               dsda = kmem_zalloc(sizeof (struct dsl_ds_destroyarg), KM_SLEEP);
-               dsda->ds = ds;
-               dsda->defer = da->defer;
-               dsl_sync_task_create(da->dstg, dsl_dataset_destroy_check,
-                   dsl_dataset_destroy_sync, dsda, da->dstg, 0);
-       } else if (err == ENOENT) {
-               err = 0;
-       } else {
-               (void) strcpy(da->failed, name);
-       }
-       return (err);
-}
-
 /*
- * Destroy 'snapname' in all descendants of 'fsname'.
+ * The snapshots must all be in the same pool.
  */
-#pragma weak dmu_snapshots_destroy = dsl_snapshots_destroy
 int
-dsl_snapshots_destroy(char *fsname, char *snapname, boolean_t defer)
+dmu_snapshots_destroy_nvl(nvlist_t *snaps, boolean_t defer, char *failed)
 {
        int err;
-       struct destroyarg da;
        dsl_sync_task_t *dst;
        spa_t *spa;
+       nvpair_t *pair;
+       dsl_sync_task_group_t *dstg;
 
-       err = spa_open(fsname, &spa, FTAG);
+       pair = nvlist_next_nvpair(snaps, NULL);
+       if (pair == NULL)
+               return (0);
+
+       err = spa_open(nvpair_name(pair), &spa, FTAG);
        if (err)
                return (err);
-       da.dstg = dsl_sync_task_group_create(spa_get_dsl(spa));
-       da.snapname = snapname;
-       da.failed = fsname;
-       da.defer = defer;
+       dstg = dsl_sync_task_group_create(spa_get_dsl(spa));
 
-       err = dmu_objset_find(fsname,
-           dsl_snapshot_destroy_one, &da, DS_FIND_CHILDREN);
+       for (pair = nvlist_next_nvpair(snaps, NULL); pair != NULL;
+           pair = nvlist_next_nvpair(snaps, pair)) {
+               dsl_dataset_t *ds;
+               int err;
+
+               err = dsl_dataset_own(nvpair_name(pair), B_TRUE, dstg, &ds);
+               if (err == 0) {
+                       struct dsl_ds_destroyarg *dsda;
+
+                       dsl_dataset_make_exclusive(ds, dstg);
+                       dsda = kmem_zalloc(sizeof (struct dsl_ds_destroyarg),
+                           KM_SLEEP);
+                       dsda->ds = ds;
+                       dsda->defer = defer;
+                       dsl_sync_task_create(dstg, dsl_dataset_destroy_check,
+                           dsl_dataset_destroy_sync, dsda, dstg, 0);
+               } else if (err == ENOENT) {
+                       err = 0;
+               } else {
+                       (void) strcpy(failed, nvpair_name(pair));
+                       break;
+               }
+       }
 
        if (err == 0)
-               err = dsl_sync_task_group_wait(da.dstg);
+               err = dsl_sync_task_group_wait(dstg);
 
-       for (dst = list_head(&da.dstg->dstg_tasks); dst;
-           dst = list_next(&da.dstg->dstg_tasks, dst)) {
+       for (dst = list_head(&dstg->dstg_tasks); dst;
+           dst = list_next(&dstg->dstg_tasks, dst)) {
                struct dsl_ds_destroyarg *dsda = dst->dst_arg1;
                dsl_dataset_t *ds = dsda->ds;
 
@@ -977,17 +964,17 @@ dsl_snapshots_destroy(char *fsname, char *snapname, boolean_t defer)
                 * Return the file system name that triggered the error
                 */
                if (dst->dst_err) {
-                       dsl_dataset_name(ds, fsname);
-                       *strchr(fsname, '@') = '\0';
+                       dsl_dataset_name(ds, failed);
                }
                ASSERT3P(dsda->rm_origin, ==, NULL);
-               dsl_dataset_disown(ds, da.dstg);
+               dsl_dataset_disown(ds, dstg);
                kmem_free(dsda, sizeof (struct dsl_ds_destroyarg));
        }
 
-       dsl_sync_task_group_destroy(da.dstg);
+       dsl_sync_task_group_destroy(dstg);
        spa_close(spa, FTAG);
        return (err);
+
 }
 
 static boolean_t
@@ -2151,6 +2138,55 @@ dsl_dataset_sync(dsl_dataset_t *ds, zio_t *zio, dmu_tx_t *tx)
        dmu_objset_sync(ds->ds_objset, zio, tx);
 }
 
+static void
+get_clones_stat(dsl_dataset_t *ds, nvlist_t *nv)
+{
+       uint64_t count = 0;
+       objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
+       zap_cursor_t zc;
+       zap_attribute_t za;
+       nvlist_t *propval;
+       nvlist_t *val;
+
+       rw_enter(&ds->ds_dir->dd_pool->dp_config_rwlock, RW_READER);
+       VERIFY(nvlist_alloc(&propval, NV_UNIQUE_NAME, KM_SLEEP) == 0);
+       VERIFY(nvlist_alloc(&val, NV_UNIQUE_NAME, KM_SLEEP) == 0);
+
+       /*
+        * There may me missing entries in ds_next_clones_obj
+        * due to a bug in a previous version of the code.
+        * Only trust it if it has the right number of entries.
+        */
+       if (ds->ds_phys->ds_next_clones_obj != 0) {
+               ASSERT3U(0, ==, zap_count(mos, ds->ds_phys->ds_next_clones_obj,
+                   &count));
+       }
+       if (count != ds->ds_phys->ds_num_children - 1) {
+               goto fail;
+       }
+       for (zap_cursor_init(&zc, mos, ds->ds_phys->ds_next_clones_obj);
+           zap_cursor_retrieve(&zc, &za) == 0;
+           zap_cursor_advance(&zc)) {
+               dsl_dataset_t *clone;
+               char buf[ZFS_MAXNAMELEN];
+               if (dsl_dataset_hold_obj(ds->ds_dir->dd_pool,
+                   za.za_first_integer, FTAG, &clone) != 0) {
+                       goto fail;
+               }
+               dsl_dir_name(clone->ds_dir, buf);
+               VERIFY(nvlist_add_boolean(val, buf) == 0);
+               dsl_dataset_rele(clone, FTAG);
+       }
+       zap_cursor_fini(&zc);
+       VERIFY(nvlist_add_nvlist(propval, ZPROP_VALUE, val) == 0);
+       VERIFY(nvlist_add_nvlist(nv, zfs_prop_to_name(ZFS_PROP_CLONES),
+           propval) == 0);
+fail:
+       nvlist_free(val);
+       nvlist_free(propval);
+       rw_exit(&ds->ds_dir->dd_pool->dp_config_rwlock);
+}
+
 void
 dsl_dataset_stats(dsl_dataset_t *ds, nvlist_t *nv)
 {
@@ -2181,6 +2217,27 @@ dsl_dataset_stats(dsl_dataset_t *ds, nvlist_t *nv)
        dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_DEFER_DESTROY,
            DS_IS_DEFER_DESTROY(ds) ? 1 : 0);
 
+       if (ds->ds_phys->ds_prev_snap_obj != 0) {
+               uint64_t written, comp, uncomp;
+               dsl_pool_t *dp = ds->ds_dir->dd_pool;
+               dsl_dataset_t *prev;
+               int err;
+
+               rw_enter(&dp->dp_config_rwlock, RW_READER);
+               err = dsl_dataset_hold_obj(dp,
+                   ds->ds_phys->ds_prev_snap_obj, FTAG, &prev);
+               rw_exit(&dp->dp_config_rwlock);
+               if (err == 0) {
+                       err = dsl_dataset_space_written(prev, ds, &written,
+                           &comp, &uncomp);
+                       dsl_dataset_rele(prev, FTAG);
+                       if (err == 0) {
+                               dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_WRITTEN,
+                                   written);
+                       }
+               }
+       }
+
        ratio = ds->ds_phys->ds_compressed_bytes == 0 ? 100 :
            (ds->ds_phys->ds_uncompressed_bytes * 100 /
            ds->ds_phys->ds_compressed_bytes);
@@ -2194,6 +2251,8 @@ dsl_dataset_stats(dsl_dataset_t *ds, nvlist_t *nv)
                dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_USED,
                    ds->ds_phys->ds_unique_bytes);
                dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_COMPRESSRATIO, ratio);
+
+               get_clones_stat(ds, nv);
        }
 }
 
@@ -4022,7 +4081,7 @@ dsl_dataset_get_holds(const char *dsname, nvlist_t **nvp)
 }
 
 /*
- * Note, this fuction is used as the callback for dmu_objset_find().  We
+ * Note, this function is used as the callback for dmu_objset_find().  We
  * always return 0 so that we will continue to find and process
  * inconsistent datasets, even if we encounter an error trying to
  * process one of them.
@@ -4042,7 +4101,157 @@ dsl_destroy_inconsistent(const char *dsname, void *arg)
        return (0);
 }
 
+
+/*
+ * Return (in *usedp) the amount of space written in new that is not
+ * present in oldsnap.  New may be a snapshot or the head.  Old must be
+ * a snapshot before new, in new's filesystem (or its origin).  If not then
+ * fail and return EINVAL.
+ *
+ * The written space is calculated by considering two components:  First, we
+ * ignore any freed space, and calculate the written as new's used space
+ * minus old's used space.  Next, we add in the amount of space that was freed
+ * between the two snapshots, thus reducing new's used space relative to old's.
+ * Specifically, this is the space that was born before old->ds_creation_txg,
+ * and freed before new (ie. on new's deadlist or a previous deadlist).
+ *
+ * space freed                         [---------------------]
+ * snapshots                       ---O-------O--------O-------O------
+ *                                         oldsnap            new
+ */
+int
+dsl_dataset_space_written(dsl_dataset_t *oldsnap, dsl_dataset_t *new,
+    uint64_t *usedp, uint64_t *compp, uint64_t *uncompp)
+{
+       int err = 0;
+       uint64_t snapobj;
+       dsl_pool_t *dp = new->ds_dir->dd_pool;
+
+       *usedp = 0;
+       *usedp += new->ds_phys->ds_used_bytes;
+       *usedp -= oldsnap->ds_phys->ds_used_bytes;
+
+       *compp = 0;
+       *compp += new->ds_phys->ds_compressed_bytes;
+       *compp -= oldsnap->ds_phys->ds_compressed_bytes;
+
+       *uncompp = 0;
+       *uncompp += new->ds_phys->ds_uncompressed_bytes;
+       *uncompp -= oldsnap->ds_phys->ds_uncompressed_bytes;
+
+       rw_enter(&dp->dp_config_rwlock, RW_READER);
+       snapobj = new->ds_object;
+       while (snapobj != oldsnap->ds_object) {
+               dsl_dataset_t *snap;
+               uint64_t used, comp, uncomp;
+
+               err = dsl_dataset_hold_obj(dp, snapobj, FTAG, &snap);
+               if (err != 0)
+                       break;
+
+               if (snap->ds_phys->ds_prev_snap_txg ==
+                   oldsnap->ds_phys->ds_creation_txg) {
+                       /*
+                        * The blocks in the deadlist can not be born after
+                        * ds_prev_snap_txg, so get the whole deadlist space,
+                        * which is more efficient (especially for old-format
+                        * deadlists).  Unfortunately the deadlist code
+                        * doesn't have enough information to make this
+                        * optimization itself.
+                        */
+                       dsl_deadlist_space(&snap->ds_deadlist,
+                           &used, &comp, &uncomp);
+               } else {
+                       dsl_deadlist_space_range(&snap->ds_deadlist,
+                           0, oldsnap->ds_phys->ds_creation_txg,
+                           &used, &comp, &uncomp);
+               }
+               *usedp += used;
+               *compp += comp;
+               *uncompp += uncomp;
+
+               /*
+                * If we get to the beginning of the chain of snapshots
+                * (ds_prev_snap_obj == 0) before oldsnap, then oldsnap
+                * was not a snapshot of/before new.
+                */
+               snapobj = snap->ds_phys->ds_prev_snap_obj;
+               dsl_dataset_rele(snap, FTAG);
+               if (snapobj == 0) {
+                       err = EINVAL;
+                       break;
+               }
+
+       }
+       rw_exit(&dp->dp_config_rwlock);
+       return (err);
+}
+
+/*
+ * Return (in *usedp) the amount of space that will be reclaimed if firstsnap,
+ * lastsnap, and all snapshots in between are deleted.
+ *
+ * blocks that would be freed            [---------------------------]
+ * snapshots                       ---O-------O--------O-------O--------O
+ *                                        firstsnap        lastsnap
+ *
+ * This is the set of blocks that were born after the snap before firstsnap,
+ * (birth > firstsnap->prev_snap_txg) and died before the snap after the
+ * last snap (ie, is on lastsnap->ds_next->ds_deadlist or an earlier deadlist).
+ * We calculate this by iterating over the relevant deadlists (from the snap
+ * after lastsnap, backward to the snap after firstsnap), summing up the
+ * space on the deadlist that was born after the snap before firstsnap.
+ */
+int
+dsl_dataset_space_wouldfree(dsl_dataset_t *firstsnap,
+    dsl_dataset_t *lastsnap,
+    uint64_t *usedp, uint64_t *compp, uint64_t *uncompp)
+{
+       int err = 0;
+       uint64_t snapobj;
+       dsl_pool_t *dp = firstsnap->ds_dir->dd_pool;
+
+       ASSERT(dsl_dataset_is_snapshot(firstsnap));
+       ASSERT(dsl_dataset_is_snapshot(lastsnap));
+
+       /*
+        * Check that the snapshots are in the same dsl_dir, and firstsnap
+        * is before lastsnap.
+        */
+       if (firstsnap->ds_dir != lastsnap->ds_dir ||
+           firstsnap->ds_phys->ds_creation_txg >
+           lastsnap->ds_phys->ds_creation_txg)
+               return (EINVAL);
+
+       *usedp = *compp = *uncompp = 0;
+
+       rw_enter(&dp->dp_config_rwlock, RW_READER);
+       snapobj = lastsnap->ds_phys->ds_next_snap_obj;
+       while (snapobj != firstsnap->ds_object) {
+               dsl_dataset_t *ds;
+               uint64_t used, comp, uncomp;
+
+               err = dsl_dataset_hold_obj(dp, snapobj, FTAG, &ds);
+               if (err != 0)
+                       break;
+
+               dsl_deadlist_space_range(&ds->ds_deadlist,
+                   firstsnap->ds_phys->ds_prev_snap_txg, UINT64_MAX,
+                   &used, &comp, &uncomp);
+               *usedp += used;
+               *compp += comp;
+               *uncompp += uncomp;
+
+               snapobj = ds->ds_phys->ds_prev_snap_obj;
+               ASSERT3U(snapobj, !=, 0);
+               dsl_dataset_rele(ds, FTAG);
+       }
+       rw_exit(&dp->dp_config_rwlock);
+       return (err);
+}
+
 #if defined(_KERNEL) && defined(HAVE_SPL)
+EXPORT_SYMBOL(dmu_snapshots_destroy_nvl);
 EXPORT_SYMBOL(dsl_dataset_hold);
 EXPORT_SYMBOL(dsl_dataset_hold_obj);
 EXPORT_SYMBOL(dsl_dataset_own);
@@ -4056,7 +4265,6 @@ EXPORT_SYMBOL(dsl_dataset_make_exclusive);
 EXPORT_SYMBOL(dsl_dataset_create_sync);
 EXPORT_SYMBOL(dsl_dataset_create_sync_dd);
 EXPORT_SYMBOL(dsl_dataset_destroy);
-EXPORT_SYMBOL(dsl_snapshots_destroy);
 EXPORT_SYMBOL(dsl_dataset_destroy_check);
 EXPORT_SYMBOL(dsl_dataset_destroy_sync);
 EXPORT_SYMBOL(dsl_dataset_snapshot_check);
@@ -4072,6 +4280,8 @@ EXPORT_SYMBOL(dsl_dataset_get_blkptr);
 EXPORT_SYMBOL(dsl_dataset_set_blkptr);
 EXPORT_SYMBOL(dsl_dataset_get_spa);
 EXPORT_SYMBOL(dsl_dataset_modified_since_lastsnap);
+EXPORT_SYMBOL(dsl_dataset_space_written);
+EXPORT_SYMBOL(dsl_dataset_space_wouldfree);
 EXPORT_SYMBOL(dsl_dataset_sync);
 EXPORT_SYMBOL(dsl_dataset_block_born);
 EXPORT_SYMBOL(dsl_dataset_block_kill);
index 064f8ac..dd6db21 100644 (file)
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  */
 
 #include <sys/dsl_dataset.h>
 #include <sys/zfs_context.h>
 #include <sys/dsl_pool.h>
 
+/*
+ * Deadlist concurrency:
+ *
+ * Deadlists can only be modified from the syncing thread.
+ *
+ * Except for dsl_deadlist_insert(), it can only be modified with the
+ * dp_config_rwlock held with RW_WRITER.
+ *
+ * The accessors (dsl_deadlist_space() and dsl_deadlist_space_range()) can
+ * be called concurrently, from open context, with the dl_config_rwlock held
+ * with RW_READER.
+ *
+ * Therefore, we only need to provide locking between dsl_deadlist_insert() and
+ * the accessors, protecting:
+ *     dl_phys->dl_used,comp,uncomp
+ *     and protecting the dl_tree from being loaded.
+ * The locking is provided by dl_lock.  Note that locking on the bpobj_t
+ * provides its own locking, and dl_oldfmt is immutable.
+ */
+
 static int
 dsl_deadlist_compare(const void *arg1, const void *arg2)
 {
@@ -309,14 +330,14 @@ dsl_deadlist_space(dsl_deadlist_t *dl,
  * return space used in the range (mintxg, maxtxg].
  * Includes maxtxg, does not include mintxg.
  * mintxg and maxtxg must both be keys in the deadlist (unless maxtxg is
- * UINT64_MAX).
+ * larger than any bp in the deadlist (eg. UINT64_MAX)).
  */
 void
 dsl_deadlist_space_range(dsl_deadlist_t *dl, uint64_t mintxg, uint64_t maxtxg,
     uint64_t *usedp, uint64_t *compp, uint64_t *uncompp)
 {
-       dsl_deadlist_entry_t dle_tofind;
        dsl_deadlist_entry_t *dle;
+       dsl_deadlist_entry_t dle_tofind;
        avl_index_t where;
 
        if (dl->dl_oldfmt) {
@@ -325,9 +346,10 @@ dsl_deadlist_space_range(dsl_deadlist_t *dl, uint64_t mintxg, uint64_t maxtxg,
                return;
        }
 
-       dsl_deadlist_load_tree(dl);
        *usedp = *compp = *uncompp = 0;
 
+       mutex_enter(&dl->dl_lock);
+       dsl_deadlist_load_tree(dl);
        dle_tofind.dle_mintxg = mintxg;
        dle = avl_find(&dl->dl_tree, &dle_tofind, &where);
        /*
@@ -336,6 +358,7 @@ dsl_deadlist_space_range(dsl_deadlist_t *dl, uint64_t mintxg, uint64_t maxtxg,
         */
        ASSERT(dle != NULL ||
            avl_nearest(&dl->dl_tree, where, AVL_AFTER) == NULL);
+
        for (; dle && dle->dle_mintxg < maxtxg;
            dle = AVL_NEXT(&dl->dl_tree, dle)) {
                uint64_t used, comp, uncomp;
@@ -347,6 +370,7 @@ dsl_deadlist_space_range(dsl_deadlist_t *dl, uint64_t mintxg, uint64_t maxtxg,
                *compp += comp;
                *uncompp += uncomp;
        }
+       mutex_exit(&dl->dl_lock);
 }
 
 static void
index 6b5c842..a4d4e42 100644 (file)
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  */
 
 /*
@@ -534,10 +535,12 @@ dsl_load_user_sets(objset_t *mos, uint64_t zapobj, avl_tree_t *avl,
 }
 
 /*
- * Check if user has requested permission.
+ * Check if user has requested permission.  If descendent is set, must have
+ * descendent perms.
  */
 int
-dsl_deleg_access_impl(dsl_dataset_t *ds, const char *perm, cred_t *cr)
+dsl_deleg_access_impl(dsl_dataset_t *ds, boolean_t descendent, const char *perm,
+    cred_t *cr)
 {
        dsl_dir_t *dd;
        dsl_pool_t *dp;
@@ -558,7 +561,7 @@ dsl_deleg_access_impl(dsl_dataset_t *ds, const char *perm, cred_t *cr)
            SPA_VERSION_DELEGATED_PERMS)
                return (EPERM);
 
-       if (dsl_dataset_is_snapshot(ds)) {
+       if (dsl_dataset_is_snapshot(ds) || descendent) {
                /*
                 * Snapshots are treated as descendents only,
                 * local permissions do not apply.
@@ -651,7 +654,7 @@ dsl_deleg_access(const char *dsname, const char *perm, cred_t *cr)
        if (error)
                return (error);
 
-       error = dsl_deleg_access_impl(ds, perm, cr);
+       error = dsl_deleg_access_impl(ds, B_FALSE, perm, cr);
        dsl_dataset_rele(ds, FTAG);
 
        return (error);
index d428b7a..3b285df 100644 (file)
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  */
 
 #include <sys/dsl_pool.h>
@@ -291,7 +292,10 @@ static int
 deadlist_enqueue_cb(void *arg, const blkptr_t *bp, dmu_tx_t *tx)
 {
        dsl_deadlist_t *dl = arg;
+       dsl_pool_t *dp = dmu_objset_pool(dl->dl_os);
+       rw_enter(&dp->dp_config_rwlock, RW_READER);
        dsl_deadlist_insert(dl, bp, tx);
+       rw_exit(&dp->dp_config_rwlock);
        return (0);
 }
 
index 243f2b4..7a25378 100644 (file)
@@ -21,6 +21,7 @@
 
 /*
  * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  */
 
 #include <sys/spa.h>
@@ -101,11 +102,11 @@ spa_history_create_obj(spa_t *spa, dmu_tx_t *tx)
 
        /*
         * Figure out maximum size of history log.  We set it at
-        * 1% of pool size, with a max of 32MB and min of 128KB.
+        * 0.1% of pool size, with a max of 1G and min of 128KB.
         */
        shpp->sh_phys_max_off =
-           metaslab_class_get_dspace(spa_normal_class(spa)) / 100;
-       shpp->sh_phys_max_off = MIN(shpp->sh_phys_max_off, 32<<20);
+           metaslab_class_get_dspace(spa_normal_class(spa)) / 1000;
+       shpp->sh_phys_max_off = MIN(shpp->sh_phys_max_off, 1<<30);
        shpp->sh_phys_max_off = MAX(shpp->sh_phys_max_off, 128<<10);
 
        dmu_buf_rele(dbp, FTAG);
index 49aad2a..bd3d4a8 100644 (file)
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  */
 
 #include <sys/zio.h>
@@ -1404,7 +1405,7 @@ zap_count_write(objset_t *os, uint64_t zapobj, const char *name, int add,
        }
 
        /*
-        * We lock the zap with adding ==  FALSE. Because, if we pass
+        * We lock the zap with adding == FALSE. Because, if we pass
         * the actual value of add, it could trigger a mzap_upgrade().
         * At present we are just evaluating the possibility of this operation
         * and hence we donot want to trigger an upgrade.
index 65b0a19..94c91e8 100644 (file)
@@ -23,8 +23,6 @@
  * Portions Copyright 2011 Martin Matuska
  * Portions Copyright 2012 Pawel Jakub Dawidek <pawel@dawidek.net>
  * Copyright (c) 2012, Joyent, Inc. All rights reserved.
- */
-/*
  * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 2011 by Delphix. All rights reserved.
  */
@@ -319,17 +317,37 @@ zfs_dozonecheck_ds(const char *dataset, dsl_dataset_t *ds, cred_t *cr)
        return (zfs_dozonecheck_impl(dataset, zoned, cr));
 }
 
+/*
+ * If name ends in a '@', then require recursive permissions.
+ */
 int
 zfs_secpolicy_write_perms(const char *name, const char *perm, cred_t *cr)
 {
        int error;
+       boolean_t descendent = B_FALSE;
+       dsl_dataset_t *ds;
+       char *at;
+
+       at = strchr(name, '@');
+       if (at != NULL && at[1] == '\0') {
+               *at = '\0';
+               descendent = B_TRUE;
+       }
+
+       error = dsl_dataset_hold(name, FTAG, &ds);
+       if (at != NULL)
+               *at = '@';
+       if (error != 0)
+               return (error);
 
-       error = zfs_dozonecheck(name, cr);
+       error = zfs_dozonecheck_ds(name, ds, cr);
        if (error == 0) {
                error = secpolicy_zfs(cr);
                if (error)
-                       error = dsl_deleg_access(name, perm, cr);
+                       error = dsl_deleg_access_impl(ds, descendent, perm, cr);
        }
+
+       dsl_dataset_rele(ds, FTAG);
        return (error);
 }
 
@@ -343,7 +361,7 @@ zfs_secpolicy_write_perms_ds(const char *name, dsl_dataset_t *ds,
        if (error == 0) {
                error = secpolicy_zfs(cr);
                if (error)
-                       error = dsl_deleg_access_impl(ds, perm, cr);
+                       error = dsl_deleg_access_impl(ds, B_FALSE, perm, cr);
        }
        return (error);
 }
@@ -666,24 +684,14 @@ zfs_secpolicy_destroy(zfs_cmd_t *zc, cred_t *cr)
 /*
  * Destroying snapshots with delegated permissions requires
  * descendent mount and destroy permissions.
- * Reassemble the full filesystem@snap name so dsl_deleg_access()
- * can do the correct permission check.
- *
- * Since this routine is used when doing a recursive destroy of snapshots
- * and destroying snapshots requires descendent permissions, a successfull
- * check of the top level snapshot applies to snapshots of all descendent
- * datasets as well.
- *
- * The target snapshot may not exist when doing a recursive destroy.
- * In this case fallback to permissions of the parent dataset.
  */
 static int
-zfs_secpolicy_destroy_snaps(zfs_cmd_t *zc, cred_t *cr)
+zfs_secpolicy_destroy_recursive(zfs_cmd_t *zc, cred_t *cr)
 {
        int error;
        char *dsname;
 
-       dsname = kmem_asprintf("%s@%s", zc->zc_name, zc->zc_value);
+       dsname = kmem_asprintf("%s@", zc->zc_name);
 
        error = zfs_secpolicy_destroy_perms(dsname, cr);
        if (error == ENOENT)
@@ -1742,9 +1750,12 @@ zfs_ioc_objset_stats_impl(zfs_cmd_t *zc, objset_t *os)
                 * inconsistent.  So this is a bit of a workaround...
                 * XXX reading with out owning
                 */
-               if (!zc->zc_objset_stats.dds_inconsistent) {
-                       if (dmu_objset_type(os) == DMU_OST_ZVOL)
-                               error = zvol_get_stats(os, nv);
+               if (!zc->zc_objset_stats.dds_inconsistent &&
+                   dmu_objset_type(os) == DMU_OST_ZVOL) {
+                       error = zvol_get_stats(os, nv);
+                       if (error == EIO)
+                               return (error);
+                       VERIFY3S(error, ==, 0);
                }
                if (error == 0)
                        error = put_nvlist(zc, nv);
@@ -1954,8 +1965,7 @@ top:
                    NULL, &zc->zc_cookie);
                if (error == ENOENT)
                        error = ESRCH;
-       } while (error == 0 && dataset_name_hidden(zc->zc_name) &&
-           !(zc->zc_iflags & FKIOCTL));
+       } while (error == 0 && dataset_name_hidden(zc->zc_name));
        dmu_objset_rele(os, FTAG);
 
        /*
@@ -2233,6 +2243,8 @@ retry:
                                if (nvpair_type(propval) !=
                                    DATA_TYPE_UINT64_ARRAY)
                                        err = EINVAL;
+                       } else {
+                               err = EINVAL;
                        }
                } else if (err == 0) {
                        if (nvpair_type(propval) == DATA_TYPE_STRING) {
@@ -3118,25 +3130,45 @@ zfs_unmount_snap(const char *name, void *arg)
 
 /*
  * inputs:
- * zc_name             name of filesystem
- * zc_value            short name of snapshot
+ * zc_name             name of filesystem, snaps must be under it
+ * zc_nvlist_src[_size]        full names of snapshots to destroy
  * zc_defer_destroy    mark for deferred destroy
  *
- * outputs:    none
+ * outputs:
+ * zc_name             on failure, name of failed snapshot
  */
 static int
-zfs_ioc_destroy_snaps(zfs_cmd_t *zc)
+zfs_ioc_destroy_snaps_nvl(zfs_cmd_t *zc)
 {
-       int err;
+       int err, len;
+       nvlist_t *nvl;
+       nvpair_t *pair;
 
-       if (snapshot_namecheck(zc->zc_value, NULL, NULL) != 0)
-               return (EINVAL);
-       err = dmu_objset_find(zc->zc_name,
-           zfs_unmount_snap, zc->zc_value, DS_FIND_CHILDREN);
-       if (err)
+       if ((err = get_nvlist(zc->zc_nvlist_src, zc->zc_nvlist_src_size,
+           zc->zc_iflags, &nvl)) != 0)
                return (err);
-       return (dmu_snapshots_destroy(zc->zc_name, zc->zc_value,
-           zc->zc_defer_destroy));
+
+       len = strlen(zc->zc_name);
+       for (pair = nvlist_next_nvpair(nvl, NULL); pair != NULL;
+           pair = nvlist_next_nvpair(nvl, pair)) {
+               const char *name = nvpair_name(pair);
+               /*
+                * The snap name must be underneath the zc_name.  This ensures
+                * that our permission checks were legitimate.
+                */
+               if (strncmp(zc->zc_name, name, len) != 0 ||
+                   (name[len] != '@' && name[len] != '/')) {
+                       nvlist_free(nvl);
+                       return (EINVAL);
+               }
+
+               (void) zfs_unmount_snap(name, NULL);
+       }
+
+       err = dmu_snapshots_destroy_nvl(nvl, zc->zc_defer_destroy,
+           zc->zc_name);
+       nvlist_free(nvl);
+       return (err);
 }
 
 /*
@@ -3787,6 +3819,8 @@ out:
  * zc_obj      fromorigin flag (mutually exclusive with zc_fromobj)
  * zc_sendobj  objsetid of snapshot to send
  * zc_fromobj  objsetid of incremental fromsnap (may be zero)
+ * zc_guid     if set, estimate size of stream only.  zc_cookie is ignored.
+ *             output size in zc_objset_type.
  *
  * outputs: none
  */
@@ -3795,13 +3829,13 @@ zfs_ioc_send(zfs_cmd_t *zc)
 {
        objset_t *fromsnap = NULL;
        objset_t *tosnap;
-       file_t *fp;
        int error;
        offset_t off;
        dsl_dataset_t *ds;
        dsl_dataset_t *dsfrom = NULL;
        spa_t *spa;
        dsl_pool_t *dp;
+       boolean_t estimate = (zc->zc_guid != 0);
 
        error = spa_open(zc->zc_name, &spa, FTAG);
        if (error)
@@ -3842,20 +3876,26 @@ zfs_ioc_send(zfs_cmd_t *zc)
                spa_close(spa, FTAG);
        }
 
-       fp = getf(zc->zc_cookie);
-       if (fp == NULL) {
-               dsl_dataset_rele(ds, FTAG);
-               if (dsfrom)
-                       dsl_dataset_rele(dsfrom, FTAG);
-               return (EBADF);
-       }
+       if (estimate) {
+               error = dmu_send_estimate(tosnap, fromsnap, zc->zc_obj,
+                   &zc->zc_objset_type);
+       } else {
+               file_t *fp = getf(zc->zc_cookie);
+               if (fp == NULL) {
+                       dsl_dataset_rele(ds, FTAG);
+                       if (dsfrom)
+                               dsl_dataset_rele(dsfrom, FTAG);
+                       return (EBADF);
+               }
 
-       off = fp->f_offset;
-       error = dmu_sendbackup(tosnap, fromsnap, zc->zc_obj, fp->f_vnode, &off);
+               off = fp->f_offset;
+               error = dmu_sendbackup(tosnap, fromsnap, zc->zc_obj,
+                   fp->f_vnode, &off);
 
-       if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0)
-               fp->f_offset = off;
-       releasef(zc->zc_cookie);
+               if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0)
+                       fp->f_offset = off;
+               releasef(zc->zc_cookie);
+       }
        if (dsfrom)
                dsl_dataset_rele(dsfrom, FTAG);
        dsl_dataset_rele(ds, FTAG);
@@ -4591,6 +4631,70 @@ zfs_ioc_events_clear(zfs_cmd_t *zc)
 }
 
 /*
+ * inputs:
+ * zc_name             name of new filesystem or snapshot
+ * zc_value            full name of old snapshot
+ *
+ * outputs:
+ * zc_cookie           space in bytes
+ * zc_objset_type      compressed space in bytes
+ * zc_perm_action      uncompressed space in bytes
+ */
+static int
+zfs_ioc_space_written(zfs_cmd_t *zc)
+{
+       int error;
+       dsl_dataset_t *new, *old;
+
+       error = dsl_dataset_hold(zc->zc_name, FTAG, &new);
+       if (error != 0)
+               return (error);
+       error = dsl_dataset_hold(zc->zc_value, FTAG, &old);
+       if (error != 0) {
+               dsl_dataset_rele(new, FTAG);
+               return (error);
+       }
+
+       error = dsl_dataset_space_written(old, new, &zc->zc_cookie,
+           &zc->zc_objset_type, &zc->zc_perm_action);
+       dsl_dataset_rele(old, FTAG);
+       dsl_dataset_rele(new, FTAG);
+       return (error);
+}
+
+/*
+ * inputs:
+ * zc_name             full name of last snapshot
+ * zc_value            full name of first snapshot
+ *
+ * outputs:
+ * zc_cookie           space in bytes
+ * zc_objset_type      compressed space in bytes
+ * zc_perm_action      uncompressed space in bytes
+ */
+static int
+zfs_ioc_space_snaps(zfs_cmd_t *zc)
+{
+       int error;
+       dsl_dataset_t *new, *old;
+
+       error = dsl_dataset_hold(zc->zc_name, FTAG, &new);
+       if (error != 0)
+               return (error);
+       error = dsl_dataset_hold(zc->zc_value, FTAG, &old);
+       if (error != 0) {
+               dsl_dataset_rele(new, FTAG);
+               return (error);
+       }
+
+       error = dsl_dataset_space_wouldfree(old, new, &zc->zc_cookie,
+           &zc->zc_objset_type, &zc->zc_perm_action);
+       dsl_dataset_rele(old, FTAG);
+       dsl_dataset_rele(new, FTAG);
+       return (error);
+}
+
+/*
  * pool create, destroy, and export don't log the history as part of
  * zfsdev_ioctl, but rather zfs_ioc_pool_create, and zfs_ioc_pool_export
  * do the logging of those commands.
@@ -4656,7 +4760,7 @@ static zfs_ioc_vec_t zfs_ioc_vec[] = {
            POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY },
        { zfs_ioc_recv, zfs_secpolicy_receive, DATASET_NAME, B_TRUE,
            POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY },
-       { zfs_ioc_send, zfs_secpolicy_send, DATASET_NAME, B_TRUE,
+       { zfs_ioc_send, zfs_secpolicy_send, DATASET_NAME, B_FALSE,
            POOL_CHECK_NONE },
        { zfs_ioc_inject_fault, zfs_secpolicy_inject, NO_NAME, B_FALSE,
            POOL_CHECK_NONE },
@@ -4670,8 +4774,8 @@ static zfs_ioc_vec_t zfs_ioc_vec[] = {
            POOL_CHECK_NONE },
        { zfs_ioc_promote, zfs_secpolicy_promote, DATASET_NAME, B_TRUE,
            POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY },
-       { zfs_ioc_destroy_snaps, zfs_secpolicy_destroy_snaps, DATASET_NAME,
-           B_TRUE, POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY },
+       { zfs_ioc_destroy_snaps_nvl, zfs_secpolicy_destroy_recursive,
+           DATASET_NAME, B_TRUE, POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY },
        { zfs_ioc_snapshot, zfs_secpolicy_snapshot, DATASET_NAME, B_TRUE,
            POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY },
        { zfs_ioc_dsobj_to_dsname, zfs_secpolicy_diff, POOL_NAME, B_FALSE,
@@ -4716,12 +4820,16 @@ static zfs_ioc_vec_t zfs_ioc_vec[] = {
            B_FALSE, POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY },
        { zfs_ioc_obj_to_stats, zfs_secpolicy_diff, DATASET_NAME, B_FALSE,
            POOL_CHECK_SUSPENDED },
-        { zfs_ioc_pool_reguid, zfs_secpolicy_config, POOL_NAME, B_TRUE,
-            POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY },
        { zfs_ioc_events_next, zfs_secpolicy_config, NO_NAME, B_FALSE,
            POOL_CHECK_NONE },
        { zfs_ioc_events_clear, zfs_secpolicy_config, NO_NAME, B_FALSE,
-           POOL_CHECK_NONE }
+           POOL_CHECK_NONE },
+       { zfs_ioc_pool_reguid, zfs_secpolicy_config, POOL_NAME, B_TRUE,
+           POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY },
+       { zfs_ioc_space_written, zfs_secpolicy_read, DATASET_NAME, B_FALSE,
+           POOL_CHECK_SUSPENDED },
+       { zfs_ioc_space_snaps, zfs_secpolicy_read, DATASET_NAME, B_FALSE,
+           POOL_CHECK_SUSPENDED },
 };
 
 int