]> git.proxmox.com Git - mirror_zfs-debian.git/blobdiff - cmd/zfs/zfs_main.c
Imported Upstream version 0.6.2+git20140204
[mirror_zfs-debian.git] / cmd / zfs / zfs_main.c
index 5753ccea0d8e4a7c61ba4f4fac696cf80b30a9e1..836979bdf741b7866de5941fabc57cae83349b58 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>
@@ -55,6 +56,7 @@
 #include <time.h>
 
 #include <libzfs.h>
+#include <libzfs_core.h>
 #include <zfs_prop.h>
 #include <zfs_deleg.h>
 #include <libuutil.h>
@@ -72,6 +74,7 @@ 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);
@@ -231,10 +234,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"));
@@ -261,7 +263,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@snapname|volume@snapname> ...\n"));
        case HELP_UNMOUNT:
                return (gettext("\tunmount [-f] "
                    "<-a | filesystem|mountpoint>\n"));
@@ -290,12 +292,12 @@ get_usage(zfs_help_t idx)
                    "<filesystem|volume>\n"));
        case HELP_USERSPACE:
                return (gettext("\tuserspace [-Hinp] [-o field[,...]] "
-                   "[-s field] ...\n\t[-S field] ... "
-                   "[-t type[,...]] <filesystem|snapshot>\n"));
+                   "[-s field]...\n\t    [-S field]... [-t type[,...]] "
+                   "<filesystem|snapshot>\n"));
        case HELP_GROUPSPACE:
                return (gettext("\tgroupspace [-Hinp] [-o field[,...]] "
-                   "[-s field] ...\n\t[-S field] ... "
-                   "[-t type[,...]] <filesystem|snapshot>\n"));
+                   "[-s field]...\n\t    [-S field]... [-t type[,...]] "
+                   "<filesystem|snapshot>\n"));
        case HELP_HOLD:
                return (gettext("\thold [-r] <tag> <snapshot> ...\n"));
        case HELP_HOLDS:
@@ -647,6 +649,11 @@ zfs_do_clone(int argc, char **argv)
        if (ret == 0) {
                zfs_handle_t *clone;
 
+               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)
@@ -826,6 +833,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;
 
@@ -890,11 +902,12 @@ 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;
 } destroy_cbdata_t;
@@ -986,9 +999,27 @@ destroy_callback(zfs_handle_t *zhp, void *data)
                zfs_close(zhp);
                return (0);
        }
+       if (cb->cb_dryrun) {
+               zfs_close(zhp);
+               return (0);
+       }
 
-       if (!cb->cb_dryrun) {
-               if (zfs_unmount(zhp, NULL, cb->cb_force ? MS_FORCE : 0) != 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 (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);
@@ -1008,11 +1039,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);
@@ -1027,12 +1060,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);
@@ -1049,13 +1082,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);
@@ -1142,8 +1175,10 @@ 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;
+       zfs_handle_t *zhp = NULL;
        char *at;
        zfs_type_t type = ZFS_TYPE_DATASET;
 
@@ -1197,11 +1232,9 @@ zfs_do_destroy(int argc, char **argv)
 
        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();
+               cb.cb_nvl = fnvlist_alloc();
 
                *at = '\0';
                zhp = zfs_open(g_zfs, argv[0],
@@ -1212,17 +1245,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) {
@@ -1241,18 +1272,26 @@ 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)
-                       return (1);
+                       rv = 1;
        } else {
                /* Open the given dataset */
                if ((zhp = zfs_open(g_zfs, argv[0], type)) == NULL)
@@ -1273,8 +1312,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;
                }
 
                /*
@@ -1284,30 +1323,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
@@ -1908,9 +1959,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++;
@@ -2054,7 +2107,7 @@ zfs_do_upgrade(int argc, char **argv)
  *     -i      Translate SID to POSIX ID.
  *     -n      Print numeric ID instead of user/group name.
  *     -o      Control which fields to display.
- *     -p      Use exact (parseable) numeric output.
+ *     -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.
@@ -2088,7 +2141,7 @@ static int us_type_bits[] = {
        USTYPE_SMB_USR,
        USTYPE_ALL
 };
-static char *us_type_names[] = { "posixgroup", "posxiuser", "smbgroup",
+static char *us_type_names[] = { "posixgroup", "posixuser", "smbgroup",
        "smbuser", "all" };
 
 typedef struct us_node {
@@ -2744,24 +2797,25 @@ zfs_do_userspace(int argc, char **argv)
 }
 
 /*
- * 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;
@@ -2770,8 +2824,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;
@@ -2812,19 +2867,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("  ");
@@ -2834,27 +2889,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;
@@ -2869,19 +2924,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");
@@ -2897,11 +2950,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);
 }
@@ -2910,7 +2963,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;
@@ -2924,11 +2976,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;
@@ -2936,7 +2992,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,
@@ -3026,7 +3082,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,
@@ -3422,6 +3477,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>
  *
@@ -3431,13 +3518,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) {
@@ -3447,7 +3537,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"),
@@ -3464,18 +3555,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);
@@ -5028,28 +5136,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)
 {
@@ -5063,7 +5155,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;
 
@@ -5073,9 +5164,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);
@@ -5123,8 +5211,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)
@@ -5140,7 +5227,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.
  */
@@ -6384,15 +6470,14 @@ main(int argc, char **argv)
 
        mnttab_file = g_zfs->libzfs_mnttab;
 
-       zpool_set_history_str("zfs", argc, argv, history_str);
-       verify(zpool_stage_history(g_zfs, history_str) == 0);
+       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);
@@ -6406,6 +6491,10 @@ main(int argc, char **argv)
                usage(B_FALSE);
                ret = 1;
        }
+
+       if (ret == 0 && log_history)
+               (void) zpool_log_history(g_zfs, history_str);
+
        libzfs_fini(g_zfs);
 
        /*