]> git.proxmox.com Git - mirror_zfs-debian.git/blobdiff - cmd/zfs/zfs_main.c
Imported Upstream version 0.6.4.2
[mirror_zfs-debian.git] / cmd / zfs / zfs_main.c
index 1cb2ac9587e95cdeba56179aa95c86e161939af7..84073435e2d769fa1c20899a21eec32ec150ebcf 100644 (file)
 
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
- * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2013 by Delphix. All rights reserved.
+ * Copyright (c) 2012, Joyent, Inc. All rights reserved.
+ * Copyright (c) 2013 Steven Hartland.  All rights reserved.
+ * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
  */
 
 #include <assert.h>
@@ -54,6 +56,7 @@
 #include <time.h>
 
 #include <libzfs.h>
+#include <libzfs_core.h>
 #include <zfs_prop.h>
 #include <zfs_deleg.h>
 #include <libuutil.h>
 #include "zfs_iter.h"
 #include "zfs_util.h"
 #include "zfs_comutil.h"
+#include "libzfs_impl.h"
 
 libzfs_handle_t *g_zfs;
 
 static FILE *mnttab_file;
 static char history_str[HIS_MAX_RECORD_LEN];
+static boolean_t log_history = B_TRUE;
 
 static int zfs_do_clone(int argc, char **argv);
 static int zfs_do_create(int argc, char **argv);
@@ -96,6 +101,7 @@ static int zfs_do_hold(int argc, char **argv);
 static int zfs_do_holds(int argc, char **argv);
 static int zfs_do_release(int argc, char **argv);
 static int zfs_do_diff(int argc, char **argv);
+static int zfs_do_bookmark(int argc, char **argv);
 
 /*
  * Enable a reasonable set of defaults for libumem debugging on DEBUG builds.
@@ -142,6 +148,7 @@ typedef enum {
        HELP_HOLDS,
        HELP_RELEASE,
        HELP_DIFF,
+       HELP_BOOKMARK,
 } zfs_help_t;
 
 typedef struct zfs_command {
@@ -168,6 +175,7 @@ static zfs_command_t command_table[] = {
        { "clone",      zfs_do_clone,           HELP_CLONE              },
        { "promote",    zfs_do_promote,         HELP_PROMOTE            },
        { "rename",     zfs_do_rename,          HELP_RENAME             },
+       { "bookmark",   zfs_do_bookmark,        HELP_BOOKMARK           },
        { NULL },
        { "list",       zfs_do_list,            HELP_LIST               },
        { NULL },
@@ -215,11 +223,12 @@ get_usage(zfs_help_t idx)
        case HELP_DESTROY:
                return (gettext("\tdestroy [-fnpRrv] <filesystem|volume>\n"
                    "\tdestroy [-dnpRrv] "
-                   "<filesystem|volume>@<snap>[%<snap>][,...]\n"));
+                   "<filesystem|volume>@<snap>[%<snap>][,...]\n"
+                   "\tdestroy <filesystem|volume>#<bookmark>\n"));
        case HELP_GET:
                return (gettext("\tget [-rHp] [-d max] "
-                   "[-o \"all\" | field[,...]] [-t type[,...]] "
-                   "[-s source[,...]]\n"
+                   "[-o \"all\" | field[,...]]\n"
+                   "\t    [-t type[,...]] [-s source[,...]]\n"
                    "\t    <\"all\" | property[,...]> "
                    "[filesystem|volume|snapshot] ...\n"));
        case HELP_INHERIT:
@@ -229,10 +238,9 @@ get_usage(zfs_help_t idx)
                return (gettext("\tupgrade [-v]\n"
                    "\tupgrade [-r] [-V version] <-a | filesystem ...>\n"));
        case HELP_LIST:
-               return (gettext("\tlist [-rH][-d max] "
-                   "[-o property[,...]] [-t type[,...]] [-s property] ...\n"
-                   "\t    [-S property] ... "
-                   "[filesystem|volume|snapshot|snap] ...\n"));
+               return (gettext("\tlist [-Hp] [-r|-d max] [-o property[,...]] "
+                   "[-s property]...\n\t    [-S property]... [-t type[,...]] "
+                   "[filesystem|volume|snapshot] ...\n"));
        case HELP_MOUNT:
                return (gettext("\tmount\n"
                    "\tmount [-vO] [-o opts] <-a | filesystem>\n"));
@@ -246,12 +254,14 @@ get_usage(zfs_help_t idx)
                return (gettext("\trename [-f] <filesystem|volume|snapshot> "
                    "<filesystem|volume|snapshot>\n"
                    "\trename [-f] -p <filesystem|volume> <filesystem|volume>\n"
-                   "\trename -r <snapshot> <snapshot>"));
+                   "\trename -r <snapshot> <snapshot>\n"));
        case HELP_ROLLBACK:
                return (gettext("\trollback [-rRf] <snapshot>\n"));
        case HELP_SEND:
-               return (gettext("\tsend [-DnPpRrv] [-[iI] snapshot] "
-                   "<snapshot>\n"));
+               return (gettext("\tsend [-DnPpRrve] [-[iI] snapshot] "
+                   "<snapshot>\n"
+                   "\tsend [-e] [-i snapshot|bookmark] "
+                   "<filesystem|volume|snapshot>\n"));
        case HELP_SET:
                return (gettext("\tset <property=value> "
                    "<filesystem|volume|snapshot> ...\n"));
@@ -259,7 +269,7 @@ get_usage(zfs_help_t idx)
                return (gettext("\tshare <-a | filesystem>\n"));
        case HELP_SNAPSHOT:
                return (gettext("\tsnapshot|snap [-r] [-o property=value] ... "
-                   "<filesystem@snapname|volume@snapname>\n"));
+                   "<filesystem|volume>@<snap> ...\n"));
        case HELP_UNMOUNT:
                return (gettext("\tunmount [-f] "
                    "<-a | filesystem|mountpoint>\n"));
@@ -287,13 +297,15 @@ get_usage(zfs_help_t idx)
                    "\tunallow [-r] -s @setname [<perm|@setname>[,...]] "
                    "<filesystem|volume>\n"));
        case HELP_USERSPACE:
-               return (gettext("\tuserspace [-hniHp] [-o field[,...]] "
-                   "[-sS field] ... [-t type[,...]]\n"
-                   "\t    <filesystem|snapshot>\n"));
+               return (gettext("\tuserspace [-Hinp] [-o field[,...]] "
+                   "[-s field] ...\n"
+                   "\t    [-S field] ... [-t type[,...]] "
+                   "<filesystem|snapshot>\n"));
        case HELP_GROUPSPACE:
-               return (gettext("\tgroupspace [-hniHpU] [-o field[,...]] "
-                   "[-sS field] ... [-t type[,...]]\n"
-                   "\t    <filesystem|snapshot>\n"));
+               return (gettext("\tgroupspace [-Hinp] [-o field[,...]] "
+                   "[-s field] ...\n"
+                   "\t    [-S field] ... [-t type[,...]] "
+                   "<filesystem|snapshot>\n"));
        case HELP_HOLD:
                return (gettext("\thold [-r] <tag> <snapshot> ...\n"));
        case HELP_HOLDS:
@@ -303,6 +315,8 @@ get_usage(zfs_help_t idx)
        case HELP_DIFF:
                return (gettext("\tdiff [-FHt] <snapshot> "
                    "[snapshot|filesystem]\n"));
+       case HELP_BOOKMARK:
+               return (gettext("\tbookmark <snapshot> <bookmark>\n"));
        }
 
        abort();
@@ -644,10 +658,26 @@ zfs_do_clone(int argc, char **argv)
        /* create the mountpoint if necessary */
        if (ret == 0) {
                zfs_handle_t *clone;
+               int canmount = ZFS_CANMOUNT_OFF;
+
+               if (log_history) {
+                       (void) zpool_log_history(g_zfs, history_str);
+                       log_history = B_FALSE;
+               }
 
                clone = zfs_open(g_zfs, argv[1], ZFS_TYPE_DATASET);
                if (clone != NULL) {
-                       if (zfs_get_type(clone) != ZFS_TYPE_VOLUME)
+                       /*
+                        * if the user doesn't want the dataset automatically
+                        * mounted, then skip the mount/share step.
+                        */
+                       if (zfs_prop_valid_for_type(ZFS_PROP_CANMOUNT,
+                           zfs_get_type(clone), B_FALSE))
+                               canmount = zfs_prop_get_int(clone,
+                                   ZFS_PROP_CANMOUNT);
+
+                       if (zfs_get_type(clone) != ZFS_TYPE_VOLUME &&
+                           canmount == ZFS_CANMOUNT_ON)
                                if ((ret = zfs_mount(clone, NULL, 0)) == 0)
                                        ret = zfs_share(clone);
                        zfs_close(clone);
@@ -824,6 +854,11 @@ zfs_do_create(int argc, char **argv)
        if (zfs_create(g_zfs, argv[0], type, props) != 0)
                goto error;
 
+       if (log_history) {
+               (void) zpool_log_history(g_zfs, history_str);
+               log_history = B_FALSE;
+       }
+
        if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET)) == NULL)
                goto error;
 
@@ -832,7 +867,7 @@ zfs_do_create(int argc, char **argv)
         * if the user doesn't want the dataset automatically mounted,
         * then skip the mount/share step
         */
-       if (zfs_prop_valid_for_type(ZFS_PROP_CANMOUNT, type))
+       if (zfs_prop_valid_for_type(ZFS_PROP_CANMOUNT, type, B_FALSE))
                canmount = zfs_prop_get_int(zhp, ZFS_PROP_CANMOUNT);
 
        /*
@@ -888,13 +923,15 @@ typedef struct destroy_cbdata {
        boolean_t       cb_parsable;
        boolean_t       cb_dryrun;
        nvlist_t        *cb_nvl;
+       nvlist_t        *cb_batchedsnaps;
 
        /* first snap in contiguous run */
-       zfs_handle_t    *cb_firstsnap;
+       char            *cb_firstsnap;
        /* previous snap in contiguous run */
-       zfs_handle_t    *cb_prevsnap;
+       char            *cb_prevsnap;
        int64_t         cb_snapused;
        char            *cb_snapspec;
+       char            *cb_bookmark;
 } destroy_cbdata_t;
 
 /*
@@ -984,9 +1021,27 @@ destroy_callback(zfs_handle_t *zhp, void *data)
                zfs_close(zhp);
                return (0);
        }
+       if (cb->cb_dryrun) {
+               zfs_close(zhp);
+               return (0);
+       }
+
+       /*
+        * We batch up all contiguous snapshots (even of different
+        * filesystems) and destroy them with one ioctl.  We can't
+        * simply do all snap deletions and then all fs deletions,
+        * because we must delete a clone before its origin.
+        */
+       if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT) {
+               fnvlist_add_boolean(cb->cb_batchedsnaps, name);
+       } else {
+               int error = zfs_destroy_snaps_nvl(g_zfs,
+                   cb->cb_batchedsnaps, B_FALSE);
+               fnvlist_free(cb->cb_batchedsnaps);
+               cb->cb_batchedsnaps = fnvlist_alloc();
 
-       if (!cb->cb_dryrun) {
-               if (zfs_unmount(zhp, NULL, cb->cb_force ? MS_FORCE : 0) != 0 ||
+               if (error != 0 ||
+                   zfs_unmount(zhp, NULL, cb->cb_force ? MS_FORCE : 0) != 0 ||
                    zfs_destroy(zhp, cb->cb_defer_destroy) != 0) {
                        zfs_close(zhp);
                        return (-1);
@@ -1006,11 +1061,13 @@ destroy_print_cb(zfs_handle_t *zhp, void *arg)
 
        if (nvlist_exists(cb->cb_nvl, name)) {
                if (cb->cb_firstsnap == NULL)
-                       cb->cb_firstsnap = zfs_handle_dup(zhp);
+                       cb->cb_firstsnap = strdup(name);
                if (cb->cb_prevsnap != NULL)
-                       zfs_close(cb->cb_prevsnap);
+                       free(cb->cb_prevsnap);
                /* this snap continues the current range */
-               cb->cb_prevsnap = zfs_handle_dup(zhp);
+               cb->cb_prevsnap = strdup(name);
+               if (cb->cb_firstsnap == NULL || cb->cb_prevsnap == NULL)
+                       nomem();
                if (cb->cb_verbose) {
                        if (cb->cb_parsable) {
                                (void) printf("destroy\t%s\n", name);
@@ -1025,12 +1082,12 @@ destroy_print_cb(zfs_handle_t *zhp, void *arg)
        } else if (cb->cb_firstsnap != NULL) {
                /* end of this range */
                uint64_t used = 0;
-               err = zfs_get_snapused_int(cb->cb_firstsnap,
+               err = lzc_snaprange_space(cb->cb_firstsnap,
                    cb->cb_prevsnap, &used);
                cb->cb_snapused += used;
-               zfs_close(cb->cb_firstsnap);
+               free(cb->cb_firstsnap);
                cb->cb_firstsnap = NULL;
-               zfs_close(cb->cb_prevsnap);
+               free(cb->cb_prevsnap);
                cb->cb_prevsnap = NULL;
        }
        zfs_close(zhp);
@@ -1047,13 +1104,13 @@ destroy_print_snapshots(zfs_handle_t *fs_zhp, destroy_cbdata_t *cb)
        if (cb->cb_firstsnap != NULL) {
                uint64_t used = 0;
                if (err == 0) {
-                       err = zfs_get_snapused_int(cb->cb_firstsnap,
+                       err = lzc_snaprange_space(cb->cb_firstsnap,
                            cb->cb_prevsnap, &used);
                }
                cb->cb_snapused += used;
-               zfs_close(cb->cb_firstsnap);
+               free(cb->cb_firstsnap);
                cb->cb_firstsnap = NULL;
-               zfs_close(cb->cb_prevsnap);
+               free(cb->cb_prevsnap);
                cb->cb_prevsnap = NULL;
        }
        return (err);
@@ -1140,9 +1197,11 @@ static int
 zfs_do_destroy(int argc, char **argv)
 {
        destroy_cbdata_t cb = { 0 };
+       int rv = 0;
+       int err = 0;
        int c;
-       zfs_handle_t *zhp;
-       char *at;
+       zfs_handle_t *zhp = NULL;
+       char *at, *pound;
        zfs_type_t type = ZFS_TYPE_DATASET;
 
        /* check options */
@@ -1194,12 +1253,11 @@ zfs_do_destroy(int argc, char **argv)
        }
 
        at = strchr(argv[0], '@');
+       pound = 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();
+               cb.cb_nvl = fnvlist_alloc();
 
                *at = '\0';
                zhp = zfs_open(g_zfs, argv[0],
@@ -1210,17 +1268,15 @@ zfs_do_destroy(int argc, char **argv)
                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);
+                       rv = 1;
+                       goto out;
                }
 
                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);
+                       rv = 1;
+                       goto out;
                }
 
                if (cb.cb_verbose) {
@@ -1239,18 +1295,66 @@ zfs_do_destroy(int argc, char **argv)
                }
 
                if (!cb.cb_dryrun) {
-                       if (cb.cb_doclones)
+                       if (cb.cb_doclones) {
+                               cb.cb_batchedsnaps = fnvlist_alloc();
                                err = destroy_clones(&cb);
+                               if (err == 0) {
+                                       err = zfs_destroy_snaps_nvl(g_zfs,
+                                           cb.cb_batchedsnaps, B_FALSE);
+                               }
+                               if (err != 0) {
+                                       rv = 1;
+                                       goto out;
+                               }
+                       }
                        if (err == 0) {
-                               err = zfs_destroy_snaps_nvl(zhp, cb.cb_nvl,
+                               err = zfs_destroy_snaps_nvl(g_zfs, cb.cb_nvl,
                                    cb.cb_defer_destroy);
                        }
                }
 
-               zfs_close(zhp);
-               nvlist_free(cb.cb_nvl);
                if (err != 0)
+                       rv = 1;
+       } else if (pound != NULL) {
+               int err;
+               nvlist_t *nvl;
+
+               if (cb.cb_dryrun) {
+                       (void) fprintf(stderr,
+                           "dryrun is not supported with bookmark\n");
+                       return (-1);
+               }
+
+               if (cb.cb_defer_destroy) {
+                       (void) fprintf(stderr,
+                           "defer destroy is not supported with bookmark\n");
+                       return (-1);
+               }
+
+               if (cb.cb_recurse) {
+                       (void) fprintf(stderr,
+                           "recursive is not supported with bookmark\n");
+                       return (-1);
+               }
+
+               if (!zfs_bookmark_exists(argv[0])) {
+                       (void) fprintf(stderr, gettext("bookmark '%s' "
+                           "does not exist.\n"), argv[0]);
                        return (1);
+               }
+
+               nvl = fnvlist_alloc();
+               fnvlist_add_boolean(nvl, argv[0]);
+
+               err = lzc_destroy_bookmarks(nvl, NULL);
+               if (err != 0) {
+                       (void) zfs_standard_error(g_zfs, err,
+                           "cannot destroy bookmark");
+               }
+
+               nvlist_free(cb.cb_nvl);
+
+               return (err);
        } else {
                /* Open the given dataset */
                if ((zhp = zfs_open(g_zfs, argv[0], type)) == NULL)
@@ -1271,8 +1375,8 @@ zfs_do_destroy(int argc, char **argv)
                            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);
+                       rv = 1;
+                       goto out;
                }
 
                /*
@@ -1282,30 +1386,42 @@ zfs_do_destroy(int argc, char **argv)
                if (!cb.cb_doclones &&
                    zfs_iter_dependents(zhp, B_TRUE, destroy_check_dependent,
                    &cb) != 0) {
-                       zfs_close(zhp);
-                       return (1);
+                       rv = 1;
+                       goto out;
                }
 
                if (cb.cb_error) {
-                       zfs_close(zhp);
-                       return (1);
+                       rv = 1;
+                       goto out;
                }
 
+               cb.cb_batchedsnaps = fnvlist_alloc();
                if (zfs_iter_dependents(zhp, B_FALSE, destroy_callback,
                    &cb) != 0) {
-                       zfs_close(zhp);
-                       return (1);
+                       rv = 1;
+                       goto out;
                }
 
                /*
                 * 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);
+               err = destroy_callback(zhp, &cb);
+               zhp = NULL;
+               if (err == 0) {
+                       err = zfs_destroy_snaps_nvl(g_zfs,
+                           cb.cb_batchedsnaps, cb.cb_defer_destroy);
+               }
+               if (err != 0)
+                       rv = 1;
        }
 
-       return (0);
+out:
+       fnvlist_free(cb.cb_batchedsnaps);
+       fnvlist_free(cb.cb_nvl);
+       if (zhp != NULL)
+               zfs_close(zhp);
+       return (rv);
 }
 
 static boolean_t
@@ -1376,7 +1492,7 @@ get_callback(zfs_handle_t *zhp, void *data)
                                if (pl->pl_all)
                                        continue;
                                if (!zfs_prop_valid_for_type(pl->pl_prop,
-                                   ZFS_TYPE_DATASET)) {
+                                   ZFS_TYPE_DATASET, B_FALSE)) {
                                        (void) fprintf(stderr,
                                            gettext("No such property '%s'\n"),
                                            zfs_prop_to_name(pl->pl_prop));
@@ -1601,7 +1717,8 @@ zfs_do_get(int argc, char **argv)
                        flags &= ~ZFS_ITER_PROP_LISTSNAPS;
                        while (*optarg != '\0') {
                                static char *type_subopts[] = { "filesystem",
-                                   "volume", "snapshot", "all", NULL };
+                                   "volume", "snapshot", "bookmark",
+                                   "all", NULL };
 
                                switch (getsubopt(&optarg, type_subopts,
                                    &value)) {
@@ -1615,7 +1732,11 @@ zfs_do_get(int argc, char **argv)
                                        types |= ZFS_TYPE_SNAPSHOT;
                                        break;
                                case 3:
-                                       types = ZFS_TYPE_DATASET;
+                                       types |= ZFS_TYPE_BOOKMARK;
+                                       break;
+                               case 4:
+                                       types = ZFS_TYPE_DATASET |
+                                           ZFS_TYPE_BOOKMARK;
                                        break;
 
                                default:
@@ -1710,7 +1831,7 @@ inherit_recurse_cb(zfs_handle_t *zhp, void *data)
         * are not valid for this type of dataset.
         */
        if (prop != ZPROP_INVAL &&
-           !zfs_prop_valid_for_type(prop, zfs_get_type(zhp)))
+           !zfs_prop_valid_for_type(prop, zfs_get_type(zhp), B_FALSE))
                return (0);
 
        return (zfs_prop_inherit(zhp, cb->cb_propname, cb->cb_received) != 0);
@@ -1906,9 +2027,11 @@ upgrade_set_callback(zfs_handle_t *zhp, void *data)
                        /*
                         * If they did "zfs upgrade -a", then we could
                         * be doing ioctls to different pools.  We need
-                        * to log this history once to each pool.
+                        * to log this history once to each pool, and bypass
+                        * the normal history logging that happens in main().
                         */
-                       verify(zpool_stage_history(g_zfs, history_str) == 0);
+                       (void) zpool_log_history(g_zfs, history_str);
+                       log_history = B_FALSE;
                }
                if (zfs_prop_set(zhp, "version", verstr) == 0)
                        cb->cb_numupgraded++;
@@ -2042,30 +2165,52 @@ zfs_do_upgrade(int argc, char **argv)
        return (ret);
 }
 
-#define        USTYPE_USR_BIT (0)
-#define        USTYPE_GRP_BIT (1)
-#define        USTYPE_PSX_BIT (2)
-#define        USTYPE_SMB_BIT (3)
-
-#define        USTYPE_USR (1 << USTYPE_USR_BIT)
-#define        USTYPE_GRP (1 << USTYPE_GRP_BIT)
-
-#define        USTYPE_PSX (1 << USTYPE_PSX_BIT)
-#define        USTYPE_SMB (1 << USTYPE_SMB_BIT)
-
-#define        USTYPE_PSX_USR (USTYPE_PSX | USTYPE_USR)
-#define        USTYPE_SMB_USR (USTYPE_SMB | USTYPE_USR)
-#define        USTYPE_PSX_GRP (USTYPE_PSX | USTYPE_GRP)
-#define        USTYPE_SMB_GRP (USTYPE_SMB | USTYPE_GRP)
-#define        USTYPE_ALL (USTYPE_PSX_USR | USTYPE_SMB_USR \
-               | USTYPE_PSX_GRP | USTYPE_SMB_GRP)
+/*
+ * zfs userspace [-Hinp] [-o field[,...]] [-s field [-s field]...]
+ *               [-S field [-S field]...] [-t type[,...]] filesystem | snapshot
+ * zfs groupspace [-Hinp] [-o field[,...]] [-s field [-s field]...]
+ *                [-S field [-S field]...] [-t type[,...]] filesystem | snapshot
+ *
+ *     -H      Scripted mode; elide headers and separate columns by tabs.
+ *     -i      Translate SID to POSIX ID.
+ *     -n      Print numeric ID instead of user/group name.
+ *     -o      Control which fields to display.
+ *     -p      Use exact (parsable) numeric output.
+ *     -s      Specify sort columns, descending order.
+ *     -S      Specify sort columns, ascending order.
+ *     -t      Control which object types to display.
+ *
+ *     Displays space consumed by, and quotas on, each user in the specified
+ *     filesystem or snapshot.
+ */
 
+/* us_field_types, us_field_hdr and us_field_names should be kept in sync */
+enum us_field_types {
+       USFIELD_TYPE,
+       USFIELD_NAME,
+       USFIELD_USED,
+       USFIELD_QUOTA
+};
+static char *us_field_hdr[] = { "TYPE", "NAME", "USED", "QUOTA" };
+static char *us_field_names[] = { "type", "name", "used", "quota" };
+#define        USFIELD_LAST    (sizeof (us_field_names) / sizeof (char *))
 
-#define        USPROP_USED_BIT (0)
-#define        USPROP_QUOTA_BIT (1)
+#define        USTYPE_PSX_GRP  (1 << 0)
+#define        USTYPE_PSX_USR  (1 << 1)
+#define        USTYPE_SMB_GRP  (1 << 2)
+#define        USTYPE_SMB_USR  (1 << 3)
+#define        USTYPE_ALL      \
+       (USTYPE_PSX_GRP | USTYPE_PSX_USR | USTYPE_SMB_GRP | USTYPE_SMB_USR)
 
-#define        USPROP_USED (1 << USPROP_USED_BIT)
-#define        USPROP_QUOTA (1 << USPROP_QUOTA_BIT)
+static int us_type_bits[] = {
+       USTYPE_PSX_GRP,
+       USTYPE_PSX_USR,
+       USTYPE_SMB_GRP,
+       USTYPE_SMB_USR,
+       USTYPE_ALL
+};
+static char *us_type_names[] = { "posixgroup", "posixuser", "smbgroup",
+       "smbuser", "all" };
 
 typedef struct us_node {
        nvlist_t        *usn_nvl;
@@ -2074,37 +2219,49 @@ typedef struct us_node {
 } us_node_t;
 
 typedef struct us_cbdata {
-       nvlist_t                **cb_nvlp;
-       uu_avl_pool_t           *cb_avl_pool;
-       uu_avl_t                *cb_avl;
-       boolean_t               cb_numname;
-       boolean_t               cb_nicenum;
-       boolean_t               cb_sid2posix;
-       zfs_userquota_prop_t    cb_prop;
-       zfs_sort_column_t       *cb_sortcol;
-       size_t                  cb_max_typelen;
-       size_t                  cb_max_namelen;
-       size_t                  cb_max_usedlen;
-       size_t                  cb_max_quotalen;
+       nvlist_t        **cb_nvlp;
+       uu_avl_pool_t   *cb_avl_pool;
+       uu_avl_t        *cb_avl;
+       boolean_t       cb_numname;
+       boolean_t       cb_nicenum;
+       boolean_t       cb_sid2posix;
+       zfs_userquota_prop_t cb_prop;
+       zfs_sort_column_t *cb_sortcol;
+       size_t          cb_width[USFIELD_LAST];
 } us_cbdata_t;
 
+static boolean_t us_populated = B_FALSE;
+
 typedef struct {
        zfs_sort_column_t *si_sortcol;
-       boolean_t si_num_name;
-       boolean_t si_parsable;
+       boolean_t       si_numname;
 } us_sort_info_t;
 
+static int
+us_field_index(char *field)
+{
+       int i;
+
+       for (i = 0; i < USFIELD_LAST; i++) {
+               if (strcmp(field, us_field_names[i]) == 0)
+                       return (i);
+       }
+
+       return (-1);
+}
+
 static int
 us_compare(const void *larg, const void *rarg, void *unused)
 {
        const us_node_t *l = larg;
        const us_node_t *r = rarg;
-       int rc = 0;
        us_sort_info_t *si = (us_sort_info_t *)unused;
        zfs_sort_column_t *sortcol = si->si_sortcol;
-       boolean_t num_name = si->si_num_name;
+       boolean_t numname = si->si_numname;
        nvlist_t *lnvl = l->usn_nvl;
        nvlist_t *rnvl = r->usn_nvl;
+       int rc = 0;
+       boolean_t lvb, rvb;
 
        for (; sortcol != NULL; sortcol = sortcol->sc_next) {
                char *lvstr = "";
@@ -2123,17 +2280,17 @@ us_compare(const void *larg, const void *rarg, void *unused)
                        (void) nvlist_lookup_uint32(lnvl, propname, &lv32);
                        (void) nvlist_lookup_uint32(rnvl, propname, &rv32);
                        if (rv32 != lv32)
-                               rc = (rv32 > lv32) ? 1 : -1;
+                               rc = (rv32 < lv32) ? 1 : -1;
                        break;
                case ZFS_PROP_NAME:
                        propname = "name";
-                       if (num_name) {
-                               (void) nvlist_lookup_uint32(lnvl, propname,
-                                   &lv32);
-                               (void) nvlist_lookup_uint32(rnvl, propname,
-                                   &rv32);
-                               if (rv32 != lv32)
-                                       rc = (rv32 > lv32) ? 1 : -1;
+                       if (numname) {
+                               (void) nvlist_lookup_uint64(lnvl, propname,
+                                   &lv64);
+                               (void) nvlist_lookup_uint64(rnvl, propname,
+                                   &rv64);
+                               if (rv64 != lv64)
+                                       rc = (rv64 < lv64) ? 1 : -1;
                        } else {
                                (void) nvlist_lookup_string(lnvl, propname,
                                    &lvstr);
@@ -2142,22 +2299,24 @@ us_compare(const void *larg, const void *rarg, void *unused)
                                rc = strcmp(lvstr, rvstr);
                        }
                        break;
-
                case ZFS_PROP_USED:
                case ZFS_PROP_QUOTA:
-                       if (ZFS_PROP_USED == prop)
+                       if (!us_populated)
+                               break;
+                       if (prop == ZFS_PROP_USED)
                                propname = "used";
                        else
                                propname = "quota";
                        (void) nvlist_lookup_uint64(lnvl, propname, &lv64);
                        (void) nvlist_lookup_uint64(rnvl, propname, &rv64);
                        if (rv64 != lv64)
-                               rc = (rv64 > lv64) ? 1 : -1;
+                               rc = (rv64 < lv64) ? 1 : -1;
+                       break;
                default:
                        break;
                }
 
-               if (rc) {
+               if (rc != 0) {
                        if (rc < 0)
                                return (reverse ? 1 : -1);
                        else
@@ -2165,7 +2324,17 @@ us_compare(const void *larg, const void *rarg, void *unused)
                }
        }
 
-       return (rc);
+       /*
+        * If entries still seem to be the same, check if they are of the same
+        * type (smbentity is added only if we are doing SID to POSIX ID
+        * translation where we can have duplicate type/name combinations).
+        */
+       if (nvlist_lookup_boolean_value(lnvl, "smbentity", &lvb) == 0 &&
+           nvlist_lookup_boolean_value(rnvl, "smbentity", &rvb) == 0 &&
+           lvb != rvb)
+               return (lvb < rvb ? -1 : 1);
+
+       return (0);
 }
 
 static inline const char *
@@ -2185,9 +2354,6 @@ us_type2str(unsigned field_type)
        }
 }
 
-/*
- * zfs userspace
- */
 static int
 userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space)
 {
@@ -2195,7 +2361,6 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space)
        zfs_userquota_prop_t prop = cb->cb_prop;
        char *name = NULL;
        char *propname;
-       char namebuf[32];
        char sizebuf[32];
        us_node_t *node;
        uu_avl_pool_t *avl_pool = cb->cb_avl_pool;
@@ -2204,36 +2369,34 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space)
        nvlist_t *props;
        us_node_t *n;
        zfs_sort_column_t *sortcol = cb->cb_sortcol;
-       unsigned type;
+       unsigned type = 0;
        const char *typestr;
        size_t namelen;
        size_t typelen;
        size_t sizelen;
+       int typeidx, nameidx, sizeidx;
        us_sort_info_t sortinfo = { sortcol, cb->cb_numname };
+       boolean_t smbentity = B_FALSE;
 
-       if (domain == NULL || domain[0] == '\0') {
-               /* POSIX */
-               if (prop == ZFS_PROP_GROUPUSED || prop == ZFS_PROP_GROUPQUOTA) {
-                       type = USTYPE_PSX_GRP;
-                       struct group *g = getgrgid(rid);
-                       if (g)
-                               name = g->gr_name;
-               } else {
-                       type = USTYPE_PSX_USR;
-                       struct passwd *p = getpwuid(rid);
-                       if (p)
-                               name = p->pw_name;
-               }
-       } else {
+       if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0)
+               nomem();
+       node = safe_malloc(sizeof (us_node_t));
+       uu_avl_node_init(node, &node->usn_avlnode, avl_pool);
+       node->usn_nvl = props;
+
+       if (domain != NULL && domain[0] != '\0') {
 #ifdef HAVE_IDMAP
-               char sid[ZFS_MAXNAMELEN+32];
+               /* SMB */
+               char sid[ZFS_MAXNAMELEN + 32];
                uid_t id;
                uint64_t classes;
-               int err = 0;
+               int err;
                directory_error_t e;
 
+               smbentity = B_TRUE;
+
                (void) snprintf(sid, sizeof (sid), "%s-%u", domain, rid);
-               /* SMB */
+
                if (prop == ZFS_PROP_GROUPUSED || prop == ZFS_PROP_GROUPQUOTA) {
                        type = USTYPE_SMB_GRP;
                        err = sid_to_id(sid, B_FALSE, &id);
@@ -2244,219 +2407,145 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space)
 
                if (err == 0) {
                        rid = id;
-
-                       e = directory_name_from_sid(NULL, sid, &name, &classes);
-                       if (e != NULL) {
-                               directory_error_free(e);
-                               return (NULL);
+                       if (!cb->cb_sid2posix) {
+                               e = directory_name_from_sid(NULL, sid, &name,
+                                   &classes);
+                               if (e != NULL)
+                                       directory_error_free(e);
+                               if (name == NULL)
+                                       name = sid;
                        }
-
-                       if (name == NULL)
-                               name = sid;
                }
 #else
+               nvlist_free(props);
+               free(node);
+
                return (-1);
 #endif /* HAVE_IDMAP */
        }
 
-/*
- *     if (prop == ZFS_PROP_GROUPUSED || prop == ZFS_PROP_GROUPQUOTA)
- *             ug = "group";
- *     else
- *             ug = "user";
- */
-
-       if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED)
-               propname = "used";
-       else
-               propname = "quota";
-
-       (void) snprintf(namebuf, sizeof (namebuf), "%u", rid);
-       if (name == NULL)
-               name = namebuf;
-
-       if (cb->cb_nicenum)
-               zfs_nicenum(space, sizebuf, sizeof (sizebuf));
-       else
-               (void) sprintf(sizebuf, "%llu", (u_longlong_t)space);
+       if (cb->cb_sid2posix || domain == NULL || domain[0] == '\0') {
+               /* POSIX or -i */
+               if (prop == ZFS_PROP_GROUPUSED || prop == ZFS_PROP_GROUPQUOTA) {
+                       type = USTYPE_PSX_GRP;
+                       if (!cb->cb_numname) {
+                               struct group *g;
 
-       node = safe_malloc(sizeof (us_node_t));
-       uu_avl_node_init(node, &node->usn_avlnode, avl_pool);
+                               if ((g = getgrgid(rid)) != NULL)
+                                       name = g->gr_name;
+                       }
+               } else {
+                       type = USTYPE_PSX_USR;
+                       if (!cb->cb_numname) {
+                               struct passwd *p;
 
-       if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) {
-               free(node);
-               return (-1);
+                               if ((p = getpwuid(rid)) != NULL)
+                                       name = p->pw_name;
+                       }
+               }
        }
 
+       /*
+        * Make sure that the type/name combination is unique when doing
+        * SID to POSIX ID translation (hence changing the type from SMB to
+        * POSIX).
+        */
+       if (cb->cb_sid2posix &&
+           nvlist_add_boolean_value(props, "smbentity", smbentity) != 0)
+               nomem();
+
+       /* Calculate/update width of TYPE field */
+       typestr = us_type2str(type);
+       typelen = strlen(gettext(typestr));
+       typeidx = us_field_index("type");
+       if (typelen > cb->cb_width[typeidx])
+               cb->cb_width[typeidx] = typelen;
        if (nvlist_add_uint32(props, "type", type) != 0)
                nomem();
 
-       if (cb->cb_numname) {
-               if (nvlist_add_uint32(props, "name", rid) != 0)
+       /* Calculate/update width of NAME field */
+       if ((cb->cb_numname && cb->cb_sid2posix) || name == NULL) {
+               if (nvlist_add_uint64(props, "name", rid) != 0)
                        nomem();
-               namelen = strlen(namebuf);
+               namelen = snprintf(NULL, 0, "%u", rid);
        } else {
                if (nvlist_add_string(props, "name", name) != 0)
                        nomem();
                namelen = strlen(name);
        }
+       nameidx = us_field_index("name");
+       if (namelen > cb->cb_width[nameidx])
+               cb->cb_width[nameidx] = namelen;
 
-       typestr = us_type2str(type);
-       typelen = strlen(gettext(typestr));
-       if (typelen > cb->cb_max_typelen)
-               cb->cb_max_typelen  = typelen;
-
-       if (namelen > cb->cb_max_namelen)
-               cb->cb_max_namelen  = namelen;
-
-       sizelen = strlen(sizebuf);
-       if (0 == strcmp(propname, "used")) {
-               if (sizelen > cb->cb_max_usedlen)
-                       cb->cb_max_usedlen  = sizelen;
-       } else {
-               if (sizelen > cb->cb_max_quotalen)
-                       cb->cb_max_quotalen  = sizelen;
-       }
-
-       node->usn_nvl = props;
-
-       n = uu_avl_find(avl, node, &sortinfo, &idx);
-       if (n == NULL)
+       /*
+        * Check if this type/name combination is in the list and update it;
+        * otherwise add new node to the list.
+        */
+       if ((n = uu_avl_find(avl, node, &sortinfo, &idx)) == NULL) {
                uu_avl_insert(avl, node, idx);
-       else {
+       else {
                nvlist_free(props);
                free(node);
                node = n;
                props = node->usn_nvl;
        }
 
+       /* Calculate/update width of USED/QUOTA fields */
+       if (cb->cb_nicenum)
+               zfs_nicenum(space, sizebuf, sizeof (sizebuf));
+       else
+               (void) snprintf(sizebuf, sizeof (sizebuf), "%llu",
+                   (u_longlong_t)space);
+       sizelen = strlen(sizebuf);
+       if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED) {
+               propname = "used";
+               if (!nvlist_exists(props, "quota"))
+                       (void) nvlist_add_uint64(props, "quota", 0);
+       } else {
+               propname = "quota";
+               if (!nvlist_exists(props, "used"))
+                       (void) nvlist_add_uint64(props, "used", 0);
+       }
+       sizeidx = us_field_index(propname);
+       if (sizelen > cb->cb_width[sizeidx])
+               cb->cb_width[sizeidx] = sizelen;
+
        if (nvlist_add_uint64(props, propname, space) != 0)
                nomem();
 
        return (0);
 }
 
-static inline boolean_t
-usprop_check(zfs_userquota_prop_t p, unsigned types, unsigned props)
-{
-       unsigned type;
-       unsigned prop;
-
-       switch (p) {
-       case ZFS_PROP_USERUSED:
-               type = USTYPE_USR;
-               prop = USPROP_USED;
-               break;
-       case ZFS_PROP_USERQUOTA:
-               type = USTYPE_USR;
-               prop = USPROP_QUOTA;
-               break;
-       case ZFS_PROP_GROUPUSED:
-               type = USTYPE_GRP;
-               prop = USPROP_USED;
-               break;
-       case ZFS_PROP_GROUPQUOTA:
-               type = USTYPE_GRP;
-               prop = USPROP_QUOTA;
-               break;
-       default: /* ALL */
-               return (B_TRUE);
-       };
-
-       return (type & types && prop & props);
-}
-
-#define        USFIELD_TYPE (1 << 0)
-#define        USFIELD_NAME (1 << 1)
-#define        USFIELD_USED (1 << 2)
-#define        USFIELD_QUOTA (1 << 3)
-#define        USFIELD_ALL (USFIELD_TYPE | USFIELD_NAME | USFIELD_USED | USFIELD_QUOTA)
-
-static int
-parsefields(unsigned *fieldsp, char **names, unsigned *bits, size_t len)
-{
-       char *field = optarg;
-       char *delim;
-
-       do {
-               int i;
-               boolean_t found = B_FALSE;
-               delim = strchr(field, ',');
-               if (delim != NULL)
-                       *delim = '\0';
-
-               for (i = 0; i < len; i++)
-                       if (0 == strcmp(field, names[i])) {
-                               found = B_TRUE;
-                               *fieldsp |= bits[i];
-                               break;
-                       }
-
-               if (!found) {
-                       (void) fprintf(stderr, gettext("invalid type '%s'"
-                           "for -t option\n"), field);
-                       return (-1);
-               }
-
-               field = delim + 1;
-       } while (delim);
-
-       return (0);
-}
-
-
-static char *type_names[] = { "posixuser", "smbuser", "posixgroup", "smbgroup",
-       "all" };
-static unsigned type_bits[] = {
-       USTYPE_PSX_USR,
-       USTYPE_SMB_USR,
-       USTYPE_PSX_GRP,
-       USTYPE_SMB_GRP,
-       USTYPE_ALL
-};
-
-static char *us_field_names[] = { "type", "name", "used", "quota" };
-static unsigned us_field_bits[] = {
-       USFIELD_TYPE,
-       USFIELD_NAME,
-       USFIELD_USED,
-       USFIELD_QUOTA
-};
-
 static void
-print_us_node(boolean_t scripted, boolean_t parseable, unsigned fields,
-               size_t type_width, size_t name_width, size_t used_width,
-               size_t quota_width, us_node_t *node)
+print_us_node(boolean_t scripted, boolean_t parsable, int *fields, int types,
+    size_t *width, us_node_t *node)
 {
        nvlist_t *nvl = node->usn_nvl;
-       nvpair_t *nvp = NULL;
        char valstr[ZFS_MAXNAMELEN];
        boolean_t first = B_TRUE;
-       boolean_t quota_found = B_FALSE;
+       int cfield = 0;
+       int field;
+       uint32_t ustype;
 
-       if (fields & USFIELD_QUOTA && !nvlist_exists(nvl, "quota"))
-               if (nvlist_add_string(nvl, "quota", "none") != 0)
-                       nomem();
+       /* Check type */
+       (void) nvlist_lookup_uint32(nvl, "type", &ustype);
+       if (!(ustype & types))
+               return;
 
-       while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
-               char *pname = nvpair_name(nvp);
-               data_type_t type = nvpair_type(nvp);
-               uint32_t val32 = 0;
-               uint64_t val64 = 0;
+       while ((field = fields[cfield]) != USFIELD_LAST) {
+               nvpair_t *nvp = NULL;
+               data_type_t type;
+               uint32_t val32;
+               uint64_t val64;
                char *strval = NULL;
-               unsigned field = 0;
-               unsigned width = 0;
-               int i;
-               for (i = 0; i < 4; i++) {
-                       if (0 == strcmp(pname, us_field_names[i])) {
-                               field = us_field_bits[i];
+
+               while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
+                       if (strcmp(nvpair_name(nvp),
+                           us_field_names[field]) == 0)
                                break;
-                       }
                }
 
-               if (!(field & fields))
-                       continue;
-
+               type = nvpair_type(nvp);
                switch (type) {
                case DATA_TYPE_UINT32:
                        (void) nvpair_value_uint32(nvp, &val32);
@@ -2468,20 +2557,12 @@ print_us_node(boolean_t scripted, boolean_t parseable, unsigned fields,
                        (void) nvpair_value_string(nvp, &strval);
                        break;
                default:
-                       (void) fprintf(stderr, "Invalid data type\n");
-               }
-
-               if (!first) {
-                       if (scripted)
-                               (void) printf("\t");
-                       else
-                               (void) printf("  ");
+                       (void) fprintf(stderr, "invalid data type\n");
                }
 
                switch (field) {
                case USFIELD_TYPE:
                        strval = (char *)us_type2str(val32);
-                       width = type_width;
                        break;
                case USFIELD_NAME:
                        if (type == DATA_TYPE_UINT64) {
@@ -2489,81 +2570,75 @@ print_us_node(boolean_t scripted, boolean_t parseable, unsigned fields,
                                    (u_longlong_t) val64);
                                strval = valstr;
                        }
-                       width = name_width;
                        break;
                case USFIELD_USED:
                case USFIELD_QUOTA:
                        if (type == DATA_TYPE_UINT64) {
-                               (void) nvpair_value_uint64(nvp, &val64);
-                               if (parseable)
+                               if (parsable) {
                                        (void) sprintf(valstr, "%llu",
                                            (u_longlong_t) val64);
-                               else
+                               } else {
                                        zfs_nicenum(val64, valstr,
                                            sizeof (valstr));
-                               strval = valstr;
-                       }
-
-                       if (field == USFIELD_USED)
-                               width = used_width;
-                       else {
-                               quota_found = B_FALSE;
-                               width = quota_width;
+                               }
+                               if (field == USFIELD_QUOTA &&
+                                   strcmp(valstr, "0") == 0)
+                                       strval = "none";
+                               else
+                                       strval = valstr;
                        }
-
                        break;
                }
 
-               if (field == USFIELD_QUOTA && !quota_found)
-                       (void) printf("%*s", width, strval);
-               else {
-                       if (type == DATA_TYPE_STRING)
-                               (void) printf("%-*s", width, strval);
+               if (!first) {
+                       if (scripted)
+                               (void) printf("\t");
                        else
-                               (void) printf("%*s", width, strval);
+                               (void) printf("  ");
                }
+               if (scripted)
+                       (void) printf("%s", strval);
+               else if (field == USFIELD_TYPE || field == USFIELD_NAME)
+                       (void) printf("%-*s", (int) width[field], strval);
+               else
+                       (void) printf("%*s", (int) width[field], strval);
 
                first = B_FALSE;
-
+               cfield++;
        }
 
        (void) printf("\n");
 }
 
 static void
-print_us(boolean_t scripted, boolean_t parsable, unsigned fields,
-               unsigned type_width, unsigned name_width, unsigned used_width,
-               unsigned quota_width, boolean_t rmnode, uu_avl_t *avl)
+print_us(boolean_t scripted, boolean_t parsable, int *fields, int types,
+    size_t *width, boolean_t rmnode, uu_avl_t *avl)
 {
-       static char *us_field_hdr[] = { "TYPE", "NAME", "USED", "QUOTA" };
        us_node_t *node;
        const char *col;
-       int i;
-       int width[4] = { type_width, name_width, used_width, quota_width };
+       int cfield = 0;
+       int field;
 
        if (!scripted) {
                boolean_t first = B_TRUE;
-               for (i = 0; i < 4; i++) {
-                       unsigned field = us_field_bits[i];
-                       if (!(field & fields))
-                               continue;
 
-                       col = gettext(us_field_hdr[i]);
-                       if (field == USFIELD_TYPE || field == USFIELD_NAME)
-                               (void) printf(first?"%-*s":"  %-*s", width[i],
-                                   col);
-                       else
-                               (void) printf(first?"%*s":"  %*s", width[i],
-                                   col);
+               while ((field = fields[cfield]) != USFIELD_LAST) {
+                       col = gettext(us_field_hdr[field]);
+                       if (field == USFIELD_TYPE || field == USFIELD_NAME) {
+                               (void) printf(first ? "%-*s" : "  %-*s",
+                                   (int) width[field], col);
+                       } else {
+                               (void) printf(first ? "%*s" : "  %*s",
+                                   (int) width[field], col);
+                       }
                        first = B_FALSE;
+                       cfield++;
                }
                (void) printf("\n");
        }
 
-       for (node = uu_avl_first(avl); node != NULL;
-           node = uu_avl_next(avl, node)) {
-               print_us_node(scripted, parsable, fields, type_width,
-                   name_width, used_width, used_width, node);
+       for (node = uu_avl_first(avl); node; node = uu_avl_next(avl, node)) {
+               print_us_node(scripted, parsable, fields, types, width, node);
                if (rmnode)
                        nvlist_free(node->usn_nvl);
        }
@@ -2577,32 +2652,36 @@ zfs_do_userspace(int argc, char **argv)
        uu_avl_pool_t *avl_pool;
        uu_avl_t *avl_tree;
        uu_avl_walk_t *walk;
-
-       char *cmd;
+       char *delim;
+       char deffields[] = "type,name,used,quota";
+       char *ofield = NULL;
+       char *tfield = NULL;
+       int cfield = 0;
+       int fields[256];
+       int i;
        boolean_t scripted = B_FALSE;
        boolean_t prtnum = B_FALSE;
-       boolean_t parseable = B_FALSE;
+       boolean_t parsable = B_FALSE;
        boolean_t sid2posix = B_FALSE;
-       int error = 0;
+       int ret = 0;
        int c;
-       zfs_sort_column_t *default_sortcol = NULL;
        zfs_sort_column_t *sortcol = NULL;
-       unsigned types = USTYPE_PSX_USR | USTYPE_SMB_USR;
-       unsigned fields = 0;
-       unsigned props = USPROP_USED | USPROP_QUOTA;
+       int types = USTYPE_PSX_USR | USTYPE_SMB_USR;
        us_cbdata_t cb;
        us_node_t *node;
-       boolean_t resort_avl = B_FALSE;
+       us_node_t *rmnode;
+       uu_list_pool_t *listpool;
+       uu_list_t *list;
+       uu_avl_index_t idx = 0;
+       uu_list_index_t idx2 = 0;
 
        if (argc < 2)
                usage(B_FALSE);
 
-       cmd = argv[0];
-       if (0 == strcmp(cmd, "groupspace"))
-               /* toggle default group types */
+       if (strcmp(argv[0], "groupspace") == 0)
+               /* Toggle default group types */
                types = USTYPE_PSX_GRP | USTYPE_SMB_GRP;
 
-       /* check options */
        while ((c = getopt(argc, argv, "nHpo:s:S:t:i")) != -1) {
                switch (c) {
                case 'n':
@@ -2612,32 +2691,22 @@ zfs_do_userspace(int argc, char **argv)
                        scripted = B_TRUE;
                        break;
                case 'p':
-                       parseable = B_TRUE;
+                       parsable = B_TRUE;
                        break;
                case 'o':
-                       if (parsefields(&fields, us_field_names, us_field_bits,
-                           4) != 0)
-                               return (1);
+                       ofield = optarg;
                        break;
                case 's':
-                       if (zfs_add_sort_column(&sortcol, optarg,
-                           B_FALSE) != 0) {
-                               (void) fprintf(stderr,
-                                   gettext("invalid property '%s'\n"), optarg);
-                               usage(B_FALSE);
-                       }
-                       break;
                case 'S':
                        if (zfs_add_sort_column(&sortcol, optarg,
-                           B_TRUE) != 0) {
+                           c == 's' ? B_FALSE : B_TRUE) != 0) {
                                (void) fprintf(stderr,
-                                   gettext("invalid property '%s'\n"), optarg);
+                                   gettext("invalid field '%s'\n"), optarg);
                                usage(B_FALSE);
                        }
                        break;
                case 't':
-                       if (parsefields(&types, type_names, type_bits, 5))
-                               return (1);
+                       tfield = optarg;
                        break;
                case 'i':
                        sid2posix = B_TRUE;
@@ -2657,104 +2726,129 @@ zfs_do_userspace(int argc, char **argv)
        argc -= optind;
        argv += optind;
 
-       /* ok, now we have sorted by default colums (type,name) avl tree */
-       if (sortcol) {
-               zfs_sort_column_t *sc;
-               for (sc = sortcol; sc; sc = sc->sc_next) {
-                       if (sc->sc_prop == ZFS_PROP_QUOTA) {
-                               resort_avl = B_TRUE;
-                               break;
-                       }
-               }
+       if (argc < 1) {
+               (void) fprintf(stderr, gettext("missing dataset name\n"));
+               usage(B_FALSE);
+       }
+       if (argc > 1) {
+               (void) fprintf(stderr, gettext("too many arguments\n"));
+               usage(B_FALSE);
        }
 
-       if (!fields)
-               fields = USFIELD_ALL;
+       /* Use default output fields if not specified using -o */
+       if (ofield == NULL)
+               ofield = deffields;
+       do {
+               if ((delim = strchr(ofield, ',')) != NULL)
+                       *delim = '\0';
+               if ((fields[cfield++] = us_field_index(ofield)) == -1) {
+                       (void) fprintf(stderr, gettext("invalid type '%s' "
+                           "for -o option\n"), ofield);
+                       return (-1);
+               }
+               if (delim != NULL)
+                       ofield = delim + 1;
+       } while (delim != NULL);
+       fields[cfield] = USFIELD_LAST;
+
+       /* Override output types (-t option) */
+       if (tfield != NULL) {
+               types = 0;
+
+               do {
+                       boolean_t found = B_FALSE;
 
-       if ((zhp = zfs_open(g_zfs, argv[argc-1], ZFS_TYPE_DATASET)) == NULL)
+                       if ((delim = strchr(tfield, ',')) != NULL)
+                               *delim = '\0';
+                       for (i = 0; i < sizeof (us_type_bits) / sizeof (int);
+                           i++) {
+                               if (strcmp(tfield, us_type_names[i]) == 0) {
+                                       found = B_TRUE;
+                                       types |= us_type_bits[i];
+                                       break;
+                               }
+                       }
+                       if (!found) {
+                               (void) fprintf(stderr, gettext("invalid type "
+                                   "'%s' for -t option\n"), tfield);
+                               return (-1);
+                       }
+                       if (delim != NULL)
+                               tfield = delim + 1;
+               } while (delim != NULL);
+       }
+
+       if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET)) == NULL)
                return (1);
 
        if ((avl_pool = uu_avl_pool_create("us_avl_pool", sizeof (us_node_t),
-           offsetof(us_node_t, usn_avlnode),
-           us_compare, UU_DEFAULT)) == NULL)
+           offsetof(us_node_t, usn_avlnode), us_compare, UU_DEFAULT)) == NULL)
                nomem();
        if ((avl_tree = uu_avl_create(avl_pool, NULL, UU_DEFAULT)) == NULL)
                nomem();
 
-       if (sortcol && !resort_avl)
-               cb.cb_sortcol = sortcol;
-       else {
-               (void) zfs_add_sort_column(&default_sortcol, "type", B_FALSE);
-               (void) zfs_add_sort_column(&default_sortcol, "name", B_FALSE);
-               cb.cb_sortcol = default_sortcol;
-       }
+       /* Always add default sorting columns */
+       (void) zfs_add_sort_column(&sortcol, "type", B_FALSE);
+       (void) zfs_add_sort_column(&sortcol, "name", B_FALSE);
+
+       cb.cb_sortcol = sortcol;
        cb.cb_numname = prtnum;
-       cb.cb_nicenum = !parseable;
+       cb.cb_nicenum = !parsable;
        cb.cb_avl_pool = avl_pool;
        cb.cb_avl = avl_tree;
        cb.cb_sid2posix = sid2posix;
-       cb.cb_max_typelen = strlen(gettext("TYPE"));
-       cb.cb_max_namelen = strlen(gettext("NAME"));
-       cb.cb_max_usedlen = strlen(gettext("USED"));
-       cb.cb_max_quotalen = strlen(gettext("QUOTA"));
+
+       for (i = 0; i < USFIELD_LAST; i++)
+               cb.cb_width[i] = strlen(gettext(us_field_hdr[i]));
 
        for (p = 0; p < ZFS_NUM_USERQUOTA_PROPS; p++) {
-               if (!usprop_check(p, types, props))
+               if (((p == ZFS_PROP_USERUSED || p == ZFS_PROP_USERQUOTA) &&
+                   !(types & (USTYPE_PSX_USR | USTYPE_SMB_USR))) ||
+                   ((p == ZFS_PROP_GROUPUSED || p == ZFS_PROP_GROUPQUOTA) &&
+                   !(types & (USTYPE_PSX_GRP | USTYPE_SMB_GRP))))
                        continue;
-
                cb.cb_prop = p;
-               error = zfs_userspace(zhp, p, userspace_cb, &cb);
-               if (error)
-                       break;
+               if ((ret = zfs_userspace(zhp, p, userspace_cb, &cb)) != 0)
+                       return (ret);
        }
 
+       /* Sort the list */
+       if ((node = uu_avl_first(avl_tree)) == NULL)
+               return (0);
 
-       if (resort_avl) {
-               us_node_t *node;
-               us_node_t *rmnode;
-               uu_list_pool_t *listpool;
-               uu_list_t *list;
-               uu_avl_index_t idx = 0;
-               uu_list_index_t idx2 = 0;
-               listpool = uu_list_pool_create("tmplist", sizeof (us_node_t),
-                   offsetof(us_node_t, usn_listnode), NULL,
-                   UU_DEFAULT);
-               list = uu_list_create(listpool, NULL, UU_DEFAULT);
-
-               node = uu_avl_first(avl_tree);
-               uu_list_node_init(node, &node->usn_listnode, listpool);
-               while (node != NULL) {
-                       rmnode = node;
-                       node = uu_avl_next(avl_tree, node);
-                       uu_avl_remove(avl_tree, rmnode);
-                       if (uu_list_find(list, rmnode, NULL, &idx2) == NULL) {
-                               uu_list_insert(list, rmnode, idx2);
-                       }
-               }
+       us_populated = B_TRUE;
 
-               for (node = uu_list_first(list); node != NULL;
-                   node = uu_list_next(list, node)) {
-                       us_sort_info_t sortinfo = { sortcol, cb.cb_numname };
-                       if (uu_avl_find(avl_tree, node, &sortinfo, &idx) ==
-                           NULL)
-                       uu_avl_insert(avl_tree, node, idx);
-               }
+       listpool = uu_list_pool_create("tmplist", sizeof (us_node_t),
+           offsetof(us_node_t, usn_listnode), NULL, UU_DEFAULT);
+       list = uu_list_create(listpool, NULL, UU_DEFAULT);
+       uu_list_node_init(node, &node->usn_listnode, listpool);
 
-               uu_list_destroy(list);
+       while (node != NULL) {
+               rmnode = node;
+               node = uu_avl_next(avl_tree, node);
+               uu_avl_remove(avl_tree, rmnode);
+               if (uu_list_find(list, rmnode, NULL, &idx2) == NULL)
+                       uu_list_insert(list, rmnode, idx2);
        }
 
-       /* print & free node`s nvlist memory */
-       print_us(scripted, parseable, fields, cb.cb_max_typelen,
-           cb.cb_max_namelen, cb.cb_max_usedlen,
-           cb.cb_max_quotalen, B_TRUE, cb.cb_avl);
+       for (node = uu_list_first(list); node != NULL;
+           node = uu_list_next(list, node)) {
+               us_sort_info_t sortinfo = { sortcol, cb.cb_numname };
 
-       if (sortcol)
-               zfs_free_sort_columns(sortcol);
-       zfs_free_sort_columns(default_sortcol);
+               if (uu_avl_find(avl_tree, node, &sortinfo, &idx) == NULL)
+                       uu_avl_insert(avl_tree, node, idx);
+       }
 
-       /*
-        * Finally, clean up the AVL tree.
-        */
+       uu_list_destroy(list);
+       uu_list_pool_destroy(listpool);
+
+       /* Print and free node nvlist memory */
+       print_us(scripted, parsable, fields, types, cb.cb_width, B_TRUE,
+           cb.cb_avl);
+
+       zfs_free_sort_columns(sortcol);
+
+       /* Clean up the AVL tree */
        if ((walk = uu_avl_walk_start(cb.cb_avl, UU_WALK_ROBUST)) == NULL)
                nomem();
 
@@ -2767,28 +2861,29 @@ zfs_do_userspace(int argc, char **argv)
        uu_avl_destroy(avl_tree);
        uu_avl_pool_destroy(avl_pool);
 
-       return (error);
+       return (ret);
 }
 
 /*
- * list [-r][-d max] [-H] [-o property[,property]...] [-t type[,type]...]
- *      [-s property [-s property]...] [-S property [-S property]...]
- *      <dataset> ...
+ * list [-Hp][-r|-d max] [-o property[,...]] [-s property] ... [-S property]
+ *      [-t type[,...]] [filesystem|volume|snapshot] ...
  *
+ *     -H      Scripted mode; elide headers and separate columns by tabs
+ *     -p      Display values in parsable (literal) format.
  *     -r      Recurse over all children
  *     -d      Limit recursion by depth.
- *     -H      Scripted mode; elide headers and separate columns by tabs
  *     -o      Control which fields to display.
- *     -t      Control which object types to display.
  *     -s      Specify sort columns, descending order.
  *     -S      Specify sort columns, ascending order.
+ *     -t      Control which object types to display.
  *
- * When given no arguments, lists all filesystems in the system.
+ * When given no arguments, list all filesystems in the system.
  * Otherwise, list the specified datasets, optionally recursing down them if
  * '-r' is specified.
  */
 typedef struct list_cbdata {
        boolean_t       cb_first;
+       boolean_t       cb_literal;
        boolean_t       cb_scripted;
        zprop_list_t    *cb_proplist;
 } list_cbdata_t;
@@ -2797,8 +2892,9 @@ typedef struct list_cbdata {
  * Given a list of columns to display, output appropriate headers for each one.
  */
 static void
-print_header(zprop_list_t *pl)
+print_header(list_cbdata_t *cb)
 {
+       zprop_list_t *pl = cb->cb_proplist;
        char headerbuf[ZFS_MAXPROPLEN];
        const char *header;
        int i;
@@ -2839,19 +2935,19 @@ print_header(zprop_list_t *pl)
  * to the described layout.
  */
 static void
-print_dataset(zfs_handle_t *zhp, zprop_list_t *pl, boolean_t scripted)
+print_dataset(zfs_handle_t *zhp, list_cbdata_t *cb)
 {
+       zprop_list_t *pl = cb->cb_proplist;
        boolean_t first = B_TRUE;
        char property[ZFS_MAXPROPLEN];
        nvlist_t *userprops = zfs_get_user_props(zhp);
        nvlist_t *propval;
        char *propstr;
        boolean_t right_justify;
-       int width;
 
        for (; pl != NULL; pl = pl->pl_next) {
                if (!first) {
-                       if (scripted)
+                       if (cb->cb_scripted)
                                (void) printf("\t");
                        else
                                (void) printf("  ");
@@ -2861,27 +2957,27 @@ print_dataset(zfs_handle_t *zhp, zprop_list_t *pl, boolean_t scripted)
 
                if (pl->pl_prop == ZFS_PROP_NAME) {
                        (void) strlcpy(property, zfs_get_name(zhp),
-                           sizeof(property));
+                           sizeof (property));
                        propstr = property;
                        right_justify = zfs_prop_align_right(pl->pl_prop);
                } else if (pl->pl_prop != ZPROP_INVAL) {
                        if (zfs_prop_get(zhp, pl->pl_prop, property,
-                           sizeof (property), NULL, NULL, 0, B_FALSE) != 0)
+                           sizeof (property), NULL, NULL, 0,
+                           cb->cb_literal) != 0)
                                propstr = "-";
                        else
                                propstr = property;
-
                        right_justify = zfs_prop_align_right(pl->pl_prop);
                } else if (zfs_prop_userquota(pl->pl_user_prop)) {
                        if (zfs_prop_get_userquota(zhp, pl->pl_user_prop,
-                           property, sizeof (property), B_FALSE) != 0)
+                           property, sizeof (property), cb->cb_literal) != 0)
                                propstr = "-";
                        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)
+                           property, sizeof (property), cb->cb_literal) != 0)
                                propstr = "-";
                        else
                                propstr = property;
@@ -2896,19 +2992,17 @@ print_dataset(zfs_handle_t *zhp, zprop_list_t *pl, boolean_t scripted)
                        right_justify = B_FALSE;
                }
 
-               width = pl->pl_width;
-
                /*
                 * If this is being called in scripted mode, or if this is the
                 * last column and it is left-justified, don't include a width
                 * format specifier.
                 */
-               if (scripted || (pl->pl_next == NULL && !right_justify))
+               if (cb->cb_scripted || (pl->pl_next == NULL && !right_justify))
                        (void) printf("%s", propstr);
                else if (right_justify)
-                       (void) printf("%*s", width, propstr);
+                       (void) printf("%*s", (int)pl->pl_width, propstr);
                else
-                       (void) printf("%-*s", width, propstr);
+                       (void) printf("%-*s", (int)pl->pl_width, propstr);
        }
 
        (void) printf("\n");
@@ -2924,11 +3018,11 @@ list_callback(zfs_handle_t *zhp, void *data)
 
        if (cbp->cb_first) {
                if (!cbp->cb_scripted)
-                       print_header(cbp->cb_proplist);
+                       print_header(cbp);
                cbp->cb_first = B_FALSE;
        }
 
-       print_dataset(zhp, cbp->cb_proplist, cbp->cb_scripted);
+       print_dataset(zhp, cbp);
 
        return (0);
 }
@@ -2937,7 +3031,6 @@ static int
 zfs_do_list(int argc, char **argv)
 {
        int c;
-       boolean_t scripted = B_FALSE;
        static char default_fields[] =
            "name,used,available,referenced,mountpoint";
        int types = ZFS_TYPE_DATASET;
@@ -2951,11 +3044,15 @@ zfs_do_list(int argc, char **argv)
        int flags = ZFS_ITER_PROP_LISTSNAPS | ZFS_ITER_ARGS_CAN_BE_PATHS;
 
        /* check options */
-       while ((c = getopt(argc, argv, ":d:o:rt:Hs:S:")) != -1) {
+       while ((c = getopt(argc, argv, "HS:d:o:prs:t:")) != -1) {
                switch (c) {
                case 'o':
                        fields = optarg;
                        break;
+               case 'p':
+                       cb.cb_literal = B_TRUE;
+                       flags |= ZFS_ITER_LITERAL_PROPS;
+                       break;
                case 'd':
                        limit = parse_depth(optarg, &flags);
                        break;
@@ -2963,7 +3060,7 @@ zfs_do_list(int argc, char **argv)
                        flags |= ZFS_ITER_RECURSE;
                        break;
                case 'H':
-                       scripted = B_TRUE;
+                       cb.cb_scripted = B_TRUE;
                        break;
                case 's':
                        if (zfs_add_sort_column(&sortcol, optarg,
@@ -2987,7 +3084,8 @@ zfs_do_list(int argc, char **argv)
                        flags &= ~ZFS_ITER_PROP_LISTSNAPS;
                        while (*optarg != '\0') {
                                static char *type_subopts[] = { "filesystem",
-                                   "volume", "snapshot", "snap", "all", NULL };
+                                   "volume", "snapshot", "snap", "bookmark",
+                                   "all", NULL };
 
                                switch (getsubopt(&optarg, type_subopts,
                                    &value)) {
@@ -3002,9 +3100,12 @@ zfs_do_list(int argc, char **argv)
                                        types |= ZFS_TYPE_SNAPSHOT;
                                        break;
                                case 4:
-                                       types = ZFS_TYPE_DATASET;
+                                       types |= ZFS_TYPE_BOOKMARK;
+                                       break;
+                               case 5:
+                                       types = ZFS_TYPE_DATASET |
+                                           ZFS_TYPE_BOOKMARK;
                                        break;
-
                                default:
                                        (void) fprintf(stderr,
                                            gettext("invalid type '%s'\n"),
@@ -3053,7 +3154,6 @@ zfs_do_list(int argc, char **argv)
            != 0)
                usage(B_FALSE);
 
-       cb.cb_scripted = scripted;
        cb.cb_first = B_TRUE;
 
        ret = zfs_for_each(argc, argv, flags, types, sortcol, &cb.cb_proplist,
@@ -3215,9 +3315,31 @@ typedef struct rollback_cbdata {
        char            *cb_target;
        int             cb_error;
        boolean_t       cb_recurse;
-       boolean_t       cb_dependent;
 } rollback_cbdata_t;
 
+static int
+rollback_check_dependent(zfs_handle_t *zhp, void *data)
+{
+       rollback_cbdata_t *cbp = data;
+
+       if (cbp->cb_first && cbp->cb_recurse) {
+               (void) fprintf(stderr, gettext("cannot rollback to "
+                   "'%s': clones of previous snapshots exist\n"),
+                   cbp->cb_target);
+               (void) fprintf(stderr, gettext("use '-R' to "
+                   "force deletion of the following clones and "
+                   "dependents:\n"));
+               cbp->cb_first = 0;
+               cbp->cb_error = 1;
+       }
+
+       (void) fprintf(stderr, "%s\n", zfs_get_name(zhp));
+
+       zfs_close(zhp);
+       return (0);
+}
+
+
 /*
  * Report any snapshots more recent than the one specified.  Used when '-r' is
  * not specified.  We reuse this same callback for the snapshot dependents - if
@@ -3234,52 +3356,30 @@ rollback_check(zfs_handle_t *zhp, void *data)
                return (0);
        }
 
-       if (!cbp->cb_dependent) {
-               if (strcmp(zfs_get_name(zhp), cbp->cb_target) != 0 &&
-                   zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT &&
-                   zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) >
-                   cbp->cb_create) {
-
-                       if (cbp->cb_first && !cbp->cb_recurse) {
-                               (void) fprintf(stderr, gettext("cannot "
-                                   "rollback to '%s': more recent snapshots "
-                                   "exist\n"),
-                                   cbp->cb_target);
-                               (void) fprintf(stderr, gettext("use '-r' to "
-                                   "force deletion of the following "
-                                   "snapshots:\n"));
-                               cbp->cb_first = 0;
-                               cbp->cb_error = 1;
-                       }
-
-                       if (cbp->cb_recurse) {
-                               cbp->cb_dependent = B_TRUE;
-                               if (zfs_iter_dependents(zhp, B_TRUE,
-                                   rollback_check, cbp) != 0) {
-                                       zfs_close(zhp);
-                                       return (-1);
-                               }
-                               cbp->cb_dependent = B_FALSE;
-                       } else {
-                               (void) fprintf(stderr, "%s\n",
-                                   zfs_get_name(zhp));
-                       }
-               }
-       } else {
-               if (cbp->cb_first && cbp->cb_recurse) {
-                       (void) fprintf(stderr, gettext("cannot rollback to "
-                           "'%s': clones of previous snapshots exist\n"),
+       if (zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) > cbp->cb_create) {
+               if (cbp->cb_first && !cbp->cb_recurse) {
+                       (void) fprintf(stderr, gettext("cannot "
+                           "rollback to '%s': more recent snapshots "
+                           "or bookmarks exist\n"),
                            cbp->cb_target);
-                       (void) fprintf(stderr, gettext("use '-R' to "
-                           "force deletion of the following clones and "
-                           "dependents:\n"));
+                       (void) fprintf(stderr, gettext("use '-r' to "
+                           "force deletion of the following "
+                           "snapshots and bookmarks:\n"));
                        cbp->cb_first = 0;
                        cbp->cb_error = 1;
                }
 
-               (void) fprintf(stderr, "%s\n", zfs_get_name(zhp));
+               if (cbp->cb_recurse) {
+                       if (zfs_iter_dependents(zhp, B_TRUE,
+                           rollback_check_dependent, cbp) != 0) {
+                               zfs_close(zhp);
+                               return (-1);
+                       }
+               } else {
+                       (void) fprintf(stderr, "%s\n",
+                           zfs_get_name(zhp));
+               }
        }
-
        zfs_close(zhp);
        return (0);
 }
@@ -3349,7 +3449,9 @@ zfs_do_rollback(int argc, char **argv)
        cb.cb_create = zfs_prop_get_int(snap, ZFS_PROP_CREATETXG);
        cb.cb_first = B_TRUE;
        cb.cb_error = 0;
-       if ((ret = zfs_iter_children(zhp, rollback_check, &cb)) != 0)
+       if ((ret = zfs_iter_snapshots(zhp, B_FALSE, rollback_check, &cb)) != 0)
+               goto out;
+       if ((ret = zfs_iter_bookmarks(zhp, rollback_check, &cb)) != 0)
                goto out;
 
        if ((ret = cb.cb_error) != 0)
@@ -3449,6 +3551,38 @@ zfs_do_set(int argc, char **argv)
        return (ret);
 }
 
+typedef struct snap_cbdata {
+       nvlist_t *sd_nvl;
+       boolean_t sd_recursive;
+       const char *sd_snapname;
+} snap_cbdata_t;
+
+static int
+zfs_snapshot_cb(zfs_handle_t *zhp, void *arg)
+{
+       snap_cbdata_t *sd = arg;
+       char *name;
+       int rv = 0;
+       int error;
+
+       if (sd->sd_recursive &&
+           zfs_prop_get_int(zhp, ZFS_PROP_INCONSISTENT) != 0) {
+               zfs_close(zhp);
+               return (0);
+       }
+
+       error = asprintf(&name, "%s@%s", zfs_get_name(zhp), sd->sd_snapname);
+       if (error == -1)
+               nomem();
+       fnvlist_add_boolean(sd->sd_nvl, name);
+       free(name);
+
+       if (sd->sd_recursive)
+               rv = zfs_iter_filesystems(zhp, zfs_snapshot_cb, sd);
+       zfs_close(zhp);
+       return (rv);
+}
+
 /*
  * zfs snapshot [-r] [-o prop=value] ... <fs@snap>
  *
@@ -3458,13 +3592,16 @@ zfs_do_set(int argc, char **argv)
 static int
 zfs_do_snapshot(int argc, char **argv)
 {
-       boolean_t recursive = B_FALSE;
        int ret = 0;
        signed char c;
        nvlist_t *props;
+       snap_cbdata_t sd = { 0 };
+       boolean_t multiple_snaps = B_FALSE;
 
        if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0)
                nomem();
+       if (nvlist_alloc(&sd.sd_nvl, NV_UNIQUE_NAME, 0) != 0)
+               nomem();
 
        /* check options */
        while ((c = getopt(argc, argv, "ro:")) != -1) {
@@ -3474,7 +3611,8 @@ zfs_do_snapshot(int argc, char **argv)
                                return (1);
                        break;
                case 'r':
-                       recursive = B_TRUE;
+                       sd.sd_recursive = B_TRUE;
+                       multiple_snaps = B_TRUE;
                        break;
                case '?':
                        (void) fprintf(stderr, gettext("invalid option '%c'\n"),
@@ -3491,18 +3629,35 @@ zfs_do_snapshot(int argc, char **argv)
                (void) fprintf(stderr, gettext("missing snapshot argument\n"));
                goto usage;
        }
-       if (argc > 1) {
-               (void) fprintf(stderr, gettext("too many arguments\n"));
-               goto usage;
+
+       if (argc > 1)
+               multiple_snaps = B_TRUE;
+       for (; argc > 0; argc--, argv++) {
+               char *atp;
+               zfs_handle_t *zhp;
+
+               atp = strchr(argv[0], '@');
+               if (atp == NULL)
+                       goto usage;
+               *atp = '\0';
+               sd.sd_snapname = atp + 1;
+               zhp = zfs_open(g_zfs, argv[0],
+                   ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
+               if (zhp == NULL)
+                       goto usage;
+               if (zfs_snapshot_cb(zhp, &sd) != 0)
+                       goto usage;
        }
 
-       ret = zfs_snapshot(g_zfs, argv[0], recursive, props);
+       ret = zfs_snapshot_nvl(g_zfs, sd.sd_nvl, props);
+       nvlist_free(sd.sd_nvl);
        nvlist_free(props);
-       if (ret && recursive)
+       if (ret != 0 && multiple_snaps)
                (void) fprintf(stderr, gettext("no snapshots were created\n"));
        return (ret != 0);
 
 usage:
+       nvlist_free(sd.sd_nvl);
        nvlist_free(props);
        usage(B_FALSE);
        return (-1);
@@ -3524,7 +3679,7 @@ zfs_do_send(int argc, char **argv)
        boolean_t extraverbose = B_FALSE;
 
        /* check options */
-       while ((c = getopt(argc, argv, ":i:I:RDpvnP")) != -1) {
+       while ((c = getopt(argc, argv, ":i:I:RDpvnPe")) != -1) {
                switch (c) {
                case 'i':
                        if (fromname)
@@ -3551,6 +3706,7 @@ zfs_do_send(int argc, char **argv)
                        if (flags.verbose)
                                extraverbose = B_TRUE;
                        flags.verbose = B_TRUE;
+                       flags.progress = B_TRUE;
                        break;
                case 'D':
                        flags.dedup = B_TRUE;
@@ -3558,6 +3714,9 @@ zfs_do_send(int argc, char **argv)
                case 'n':
                        flags.dryrun = B_TRUE;
                        break;
+               case 'e':
+                       flags.embed_data = B_TRUE;
+                       break;
                case ':':
                        (void) fprintf(stderr, gettext("missing argument for "
                            "'%c' option\n"), optopt);
@@ -3590,12 +3749,49 @@ zfs_do_send(int argc, char **argv)
                return (1);
        }
 
-       cp = strchr(argv[0], '@');
-       if (cp == NULL) {
-               (void) fprintf(stderr,
-                   gettext("argument must be a snapshot\n"));
-               usage(B_FALSE);
+       /*
+        * Special case sending a filesystem, or from a bookmark.
+        */
+       if (strchr(argv[0], '@') == NULL ||
+           (fromname && strchr(fromname, '#') != NULL)) {
+               char frombuf[ZFS_MAXNAMELEN];
+               enum lzc_send_flags lzc_flags = 0;
+
+               if (flags.replicate || flags.doall || flags.props ||
+                   flags.dedup || flags.dryrun || flags.verbose ||
+                   flags.progress) {
+                       (void) fprintf(stderr,
+                           gettext("Error: "
+                           "Unsupported flag with filesystem or bookmark.\n"));
+                       return (1);
+               }
+
+               zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET);
+               if (zhp == NULL)
+                       return (1);
+
+               if (flags.embed_data)
+                       lzc_flags |= LZC_SEND_FLAG_EMBED_DATA;
+
+               if (fromname != NULL &&
+                   (fromname[0] == '#' || fromname[0] == '@')) {
+                       /*
+                        * Incremental source name begins with # or @.
+                        * Default to same fs as target.
+                        */
+                       (void) strncpy(frombuf, argv[0], sizeof (frombuf));
+                       cp = strchr(frombuf, '@');
+                       if (cp != NULL)
+                               *cp = '\0';
+                       (void) strlcat(frombuf, fromname, sizeof (frombuf));
+                       fromname = frombuf;
+               }
+               err = zfs_send_one(zhp, fromname, STDOUT_FILENO, lzc_flags);
+               zfs_close(zhp);
+               return (err != 0);
        }
+
+       cp = strchr(argv[0], '@');
        *cp = '\0';
        toname = cp + 1;
        zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
@@ -3751,6 +3947,7 @@ zfs_do_receive(int argc, char **argv)
 #define        ZFS_DELEG_PERM_HOLD             "hold"
 #define        ZFS_DELEG_PERM_RELEASE          "release"
 #define        ZFS_DELEG_PERM_DIFF             "diff"
+#define        ZFS_DELEG_PERM_BOOKMARK         "bookmark"
 
 #define        ZFS_NUM_DELEG_NOTES ZFS_DELEG_NOTE_NONE
 
@@ -3770,6 +3967,7 @@ static zfs_deleg_perm_tab_t zfs_deleg_perm_tbl[] = {
        { ZFS_DELEG_PERM_SEND, ZFS_DELEG_NOTE_SEND },
        { ZFS_DELEG_PERM_SHARE, ZFS_DELEG_NOTE_SHARE },
        { ZFS_DELEG_PERM_SNAPSHOT, ZFS_DELEG_NOTE_SNAPSHOT },
+       { ZFS_DELEG_PERM_BOOKMARK, ZFS_DELEG_NOTE_BOOKMARK },
 
        { ZFS_DELEG_PERM_GROUPQUOTA, ZFS_DELEG_NOTE_GROUPQUOTA },
        { ZFS_DELEG_PERM_GROUPUSED, ZFS_DELEG_NOTE_GROUPUSED },
@@ -5054,28 +5252,12 @@ cleanup2:
        return (error);
 }
 
-/*
- * zfs allow [-r] [-t] <tag> <snap> ...
- *
- *     -r      Recursively hold
- *     -t      Temporary hold (hidden option)
- *
- * Apply a user-hold with the given tag to the list of snapshots.
- */
 static int
 zfs_do_allow(int argc, char **argv)
 {
        return (zfs_do_allow_unallow_impl(argc, argv, B_FALSE));
 }
 
-/*
- * zfs unallow [-r] [-t] <tag> <snap> ...
- *
- *     -r      Recursively hold
- *     -t      Temporary hold (hidden option)
- *
- * Apply a user-hold with the given tag to the list of snapshots.
- */
 static int
 zfs_do_unallow(int argc, char **argv)
 {
@@ -5089,7 +5271,6 @@ zfs_do_hold_rele_impl(int argc, char **argv, boolean_t holding)
        int i;
        const char *tag;
        boolean_t recursive = B_FALSE;
-       boolean_t temphold = B_FALSE;
        const char *opts = holding ? "rt" : "r";
        int c;
 
@@ -5099,9 +5280,6 @@ zfs_do_hold_rele_impl(int argc, char **argv, boolean_t holding)
                case 'r':
                        recursive = B_TRUE;
                        break;
-               case 't':
-                       temphold = B_TRUE;
-                       break;
                case '?':
                        (void) fprintf(stderr, gettext("invalid option '%c'\n"),
                            optopt);
@@ -5149,8 +5327,7 @@ zfs_do_hold_rele_impl(int argc, char **argv, boolean_t holding)
                        continue;
                }
                if (holding) {
-                       if (zfs_hold(zhp, delim+1, tag, recursive,
-                           temphold, B_FALSE, -1, 0, 0) != 0)
+                       if (zfs_hold(zhp, delim+1, tag, recursive, -1) != 0)
                                ++errors;
                } else {
                        if (zfs_release(zhp, delim+1, tag, recursive) != 0)
@@ -5166,7 +5343,6 @@ zfs_do_hold_rele_impl(int argc, char **argv, boolean_t holding)
  * zfs hold [-r] [-t] <tag> <snap> ...
  *
  *     -r      Recursively hold
- *     -t      Temporary hold (hidden option)
  *
  * Apply a user-hold with the given tag to the list of snapshots.
  */
@@ -5409,7 +5585,7 @@ get_one_dataset(zfs_handle_t *zhp, void *data)
        }
 
        /*
-        * Interate over any nested datasets.
+        * Iterate over any nested datasets.
         */
        if (zfs_iter_filesystems(zhp, get_one_dataset, data) != 0) {
                zfs_close(zhp);
@@ -5802,7 +5978,11 @@ share_mount(int op, int argc, char **argv)
                 * display any active ZFS mounts.  We hide any snapshots, since
                 * they are controlled automatically.
                 */
-               rewind(mnttab_file);
+
+               /* Reopen MNTTAB to prevent reading stale data from open file */
+               if (freopen(MNTTAB, "r", mnttab_file) == NULL)
+                       return (ENOENT);
+
                while (getmntent(mnttab_file, &entry) == 0) {
                        if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0 ||
                            strchr(entry.mnt_special, '@') != NULL)
@@ -5905,7 +6085,11 @@ unshare_unmount_path(int op, char *path, int flags, boolean_t is_manual)
        /*
         * Search for the given (major,minor) pair in the mount table.
         */
-       rewind(mnttab_file);
+
+       /* Reopen MNTTAB to prevent reading stale data from open file */
+       if (freopen(MNTTAB, "r", mnttab_file) == NULL)
+               return (ENOENT);
+
        while ((ret = getextmntent(mnttab_file, &entry, 0)) == 0) {
                if (entry.mnt_major == major(statbuf.st_dev) &&
                    entry.mnt_minor == minor(statbuf.st_dev))
@@ -6059,7 +6243,10 @@ unshare_unmount(int op, int argc, char **argv)
                    ((tree = uu_avl_create(pool, NULL, UU_DEFAULT)) == NULL))
                        nomem();
 
-               rewind(mnttab_file);
+               /* Reopen MNTTAB to prevent reading stale data from open file */
+               if (freopen(MNTTAB, "r", mnttab_file) == NULL)
+                       return (ENOENT);
+
                while (getmntent(mnttab_file, &entry) == 0) {
 
                        /* ignore non-ZFS entries */
@@ -6358,6 +6545,108 @@ zfs_do_diff(int argc, char **argv)
        return (err != 0);
 }
 
+/*
+ * zfs bookmark <fs@snap> <fs#bmark>
+ *
+ * Creates a bookmark with the given name from the given snapshot.
+ */
+static int
+zfs_do_bookmark(int argc, char **argv)
+{
+       char snapname[ZFS_MAXNAMELEN];
+       zfs_handle_t *zhp;
+       nvlist_t *nvl;
+       int ret = 0;
+       int c;
+
+       /* check options */
+       while ((c = getopt(argc, argv, "")) != -1) {
+               switch (c) {
+               case '?':
+                       (void) fprintf(stderr,
+                           gettext("invalid option '%c'\n"), optopt);
+                       goto usage;
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       /* check number of arguments */
+       if (argc < 1) {
+               (void) fprintf(stderr, gettext("missing snapshot argument\n"));
+               goto usage;
+       }
+       if (argc < 2) {
+               (void) fprintf(stderr, gettext("missing bookmark argument\n"));
+               goto usage;
+       }
+
+       if (strchr(argv[1], '#') == NULL) {
+               (void) fprintf(stderr,
+                   gettext("invalid bookmark name '%s' -- "
+                   "must contain a '#'\n"), argv[1]);
+               goto usage;
+       }
+
+       if (argv[0][0] == '@') {
+               /*
+                * Snapshot name begins with @.
+                * Default to same fs as bookmark.
+                */
+               (void) strncpy(snapname, argv[1], sizeof (snapname));
+               *strchr(snapname, '#') = '\0';
+               (void) strlcat(snapname, argv[0], sizeof (snapname));
+       } else {
+               (void) strncpy(snapname, argv[0], sizeof (snapname));
+       }
+       zhp = zfs_open(g_zfs, snapname, ZFS_TYPE_SNAPSHOT);
+       if (zhp == NULL)
+               goto usage;
+       zfs_close(zhp);
+
+
+       nvl = fnvlist_alloc();
+       fnvlist_add_string(nvl, argv[1], snapname);
+       ret = lzc_bookmark(nvl, NULL);
+       fnvlist_free(nvl);
+
+       if (ret != 0) {
+               const char *err_msg;
+               char errbuf[1024];
+
+               (void) snprintf(errbuf, sizeof (errbuf),
+                   dgettext(TEXT_DOMAIN,
+                   "cannot create bookmark '%s'"), argv[1]);
+
+               switch (ret) {
+               case EXDEV:
+                       err_msg = "bookmark is in a different pool";
+                       break;
+               case EEXIST:
+                       err_msg = "bookmark exists";
+                       break;
+               case EINVAL:
+                       err_msg = "invalid argument";
+                       break;
+               case ENOTSUP:
+                       err_msg = "bookmark feature not enabled";
+                       break;
+               default:
+                       err_msg = "unknown error";
+                       break;
+               }
+               (void) fprintf(stderr, "%s: %s\n", errbuf,
+                   dgettext(TEXT_DOMAIN, err_msg));
+       }
+
+       return (ret);
+
+usage:
+       usage(B_FALSE);
+       return (-1);
+}
+
 int
 main(int argc, char **argv)
 {
@@ -6370,12 +6659,6 @@ main(int argc, char **argv)
 
        opterr = 0;
 
-       if ((mnttab_file = fopen(MNTTAB, "r")) == NULL) {
-               (void) fprintf(stderr, gettext("internal error: unable to "
-                   "open %s\n"), MNTTAB);
-               return (1);
-       }
-
        /*
         * Make sure the user has specified some command.
         */
@@ -6414,15 +6697,16 @@ main(int argc, char **argv)
        if ((g_zfs = libzfs_init()) == NULL)
                return (1);
 
-       zpool_set_history_str("zfs", argc, argv, history_str);
-       verify(zpool_stage_history(g_zfs, history_str) == 0);
+       mnttab_file = g_zfs->libzfs_mnttab;
+
+       zfs_save_arguments(argc, argv, history_str, sizeof (history_str));
 
        libzfs_print_on_error(g_zfs, B_TRUE);
 
        /*
         * Run the appropriate command.
         */
-       libzfs_mnttab_cache(g_zfs, B_FALSE);
+       libzfs_mnttab_cache(g_zfs, B_TRUE);
        if (find_command_idx(cmdname, &i) == 0) {
                current_command = &command_table[i];
                ret = command_table[i].func(argc - 1, argv + 1);
@@ -6436,9 +6720,11 @@ main(int argc, char **argv)
                usage(B_FALSE);
                ret = 1;
        }
-       libzfs_fini(g_zfs);
 
-       (void) fclose(mnttab_file);
+       if (ret == 0 && log_history)
+               (void) zpool_log_history(g_zfs, history_str);
+
+       libzfs_fini(g_zfs);
 
        /*
         * The 'ZFS_ABORT' environment variable causes us to dump core on exit