summaryrefslogtreecommitdiff
path: root/usr/src/cmd/zfs/zfs_main.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/cmd/zfs/zfs_main.c')
-rw-r--r--usr/src/cmd/zfs/zfs_main.c2787
1 files changed, 2787 insertions, 0 deletions
diff --git a/usr/src/cmd/zfs/zfs_main.c b/usr/src/cmd/zfs/zfs_main.c
new file mode 100644
index 0000000000..78e3fecf99
--- /dev/null
+++ b/usr/src/cmd/zfs/zfs_main.c
@@ -0,0 +1,2787 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <assert.h>
+#include <errno.h>
+#include <libgen.h>
+#include <libintl.h>
+#include <libuutil.h>
+#include <locale.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <zone.h>
+#include <sys/mkdev.h>
+#include <sys/mntent.h>
+#include <sys/mnttab.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+
+#include <libzfs.h>
+
+#include "zfs_iter.h"
+
+static FILE *mnttab_file;
+
+static int zfs_do_clone(int argc, char **argv);
+static int zfs_do_create(int argc, char **argv);
+static int zfs_do_destroy(int argc, char **argv);
+static int zfs_do_get(int argc, char **argv);
+static int zfs_do_inherit(int argc, char **argv);
+static int zfs_do_list(int argc, char **argv);
+static int zfs_do_mount(int argc, char **argv);
+static int zfs_do_rename(int argc, char **argv);
+static int zfs_do_rollback(int argc, char **argv);
+static int zfs_do_set(int argc, char **argv);
+static int zfs_do_snapshot(int argc, char **argv);
+static int zfs_do_unmount(int argc, char **argv);
+static int zfs_do_share(int argc, char **argv);
+static int zfs_do_unshare(int argc, char **argv);
+static int zfs_do_backup(int argc, char **argv);
+static int zfs_do_restore(int argc, char **argv);
+
+/*
+ * These libumem hooks provide a reasonable set of defaults for the allocator's
+ * debugging facilities.
+ */
+const char *
+_umem_debug_init()
+{
+ return ("default,verbose"); /* $UMEM_DEBUG setting */
+}
+
+const char *
+_umem_logging_init(void)
+{
+ return ("fail,contents"); /* $UMEM_LOGGING setting */
+}
+
+typedef struct zfs_command {
+ const char *name;
+ int (*func)(int argc, char **argv);
+ const char *usage;
+} zfs_command_t;
+
+/*
+ * Master command table. Each ZFS command has a name, associated function, and
+ * usage message. These commands are organized according to how they are
+ * displayed in the usage message. An empty command (one with a NULL name)
+ * indicates an empty line in the generic usage message. A command with a NULL
+ * usage message indicates an alias for an existing command, and is not
+ * displayed in the general usage message.
+ */
+static zfs_command_t command_table[] = {
+ { "create", zfs_do_create,
+ "\tcreate <filesystem>\n"
+ "\tcreate [-s] [-b blocksize] -V <size> <volume>\n" },
+ { "destroy", zfs_do_destroy,
+ "\tdestroy [-rRf] <filesystem|volume|snapshot>\n" },
+ { NULL },
+ { "snapshot", zfs_do_snapshot,
+ "\tsnapshot <filesystem@name|volume@name>\n" },
+ { "rollback", zfs_do_rollback,
+ "\trollback [-rRf] <snapshot>\n" },
+ { "clone", zfs_do_clone,
+ "\tclone <snapshot> <filesystem|volume>\n" },
+ { "rename", zfs_do_rename,
+ "\trename <filesystems|volume|snapshot> "
+ "<filesystem|volume|snapshot>\n" },
+ { NULL },
+ { "list", zfs_do_list,
+ "\tlist [-rH] [-o property[,property]...] [-t type[,type]...]\n"
+ "\t [filesystem|volume|snapshot] ...\n" },
+ { NULL },
+ { "set", zfs_do_set,
+ "\tset <property=value> <filesystem|volume> ...\n" },
+ { "get", zfs_do_get,
+ "\tget [-rHp] [-o field[,field]...] [-s source[,source]...]\n"
+ "\t <all | property[,property]...> "
+ "<filesystem|volume|snapshot> ...\n" },
+ { "inherit", zfs_do_inherit,
+ "\tinherit [-r] <property> <filesystem|volume> ...\n" },
+ { NULL },
+ { "mount", zfs_do_mount,
+ "\tmount\n"
+ "\tmount [-o opts] [-O] -a\n"
+ "\tmount [-o opts] [-O] <filesystem>\n" },
+ { NULL },
+ { "unmount", zfs_do_unmount,
+ "\tunmount [-f] -a\n"
+ "\tunmount [-f] <filesystem|mountpoint>\n" },
+ { NULL },
+ { "share", zfs_do_share,
+ "\tshare -a\n"
+ "\tshare <filesystem>\n" },
+ { NULL },
+ { "unshare", zfs_do_unshare,
+ "\tunshare [-f] -a\n"
+ "\tunshare [-f] <filesystem|mountpoint>\n" },
+ { NULL },
+ { "backup", zfs_do_backup,
+ "\tbackup [-i <snapshot>] <snapshot>\n" },
+ { "restore", zfs_do_restore,
+ "\trestore [-vn] <filesystem|volume|snapshot>\n"
+ "\trestore [-vn] -d <filesystem>\n" },
+};
+
+#define NCOMMAND (sizeof (command_table) / sizeof (command_table[0]))
+
+zfs_command_t *current_command;
+
+/*
+ * Utility function to guarantee malloc() success.
+ */
+void *
+safe_malloc(size_t size)
+{
+ void *data;
+
+ if ((data = calloc(1, size)) == NULL) {
+ (void) fprintf(stderr, "internal error: out of memory\n");
+ exit(1);
+ }
+
+ return (data);
+}
+
+/*
+ * Display usage message. If we're inside a command, display only the usage for
+ * that command. Otherwise, iterate over the entire command table and display
+ * a complete usage message.
+ */
+static void
+usage(int requested)
+{
+ int i;
+ int show_properties = FALSE;
+ FILE *fp = requested ? stdout : stderr;
+
+ if (current_command == NULL) {
+
+ (void) fprintf(fp, gettext("usage: zfs command args ...\n"));
+ (void) fprintf(fp,
+ gettext("where 'command' is one of the following:\n\n"));
+
+ for (i = 0; i < NCOMMAND; i++) {
+ if (command_table[i].name == NULL)
+ (void) fprintf(fp, "\n");
+ else
+ (void) fprintf(fp, "%s",
+ command_table[i].usage);
+ }
+
+ (void) fprintf(fp, gettext("\nEach dataset is of the form: "
+ "pool/[dataset/]*dataset[@name]\n"));
+ } else {
+ (void) fprintf(fp, gettext("usage:\n"));
+ (void) fprintf(fp, current_command->usage);
+ }
+
+ if (current_command == NULL ||
+ strcmp(current_command->name, "set") == 0 ||
+ strcmp(current_command->name, "get") == 0 ||
+ strcmp(current_command->name, "inherit") == 0 ||
+ strcmp(current_command->name, "list") == 0)
+ show_properties = TRUE;
+
+ if (show_properties) {
+
+ (void) fprintf(fp,
+ gettext("\nThe following properties are supported:\n"));
+
+ (void) fprintf(fp, "\n\t%-13s %s %s %s\n\n",
+ "PROPERTY", "EDIT", "INHERIT", "VALUES");
+
+ for (i = 0; i < ZFS_NPROP_VISIBLE; i++) {
+ (void) fprintf(fp, "\t%-13s ", zfs_prop_to_name(i));
+
+ if (zfs_prop_readonly(i))
+ (void) fprintf(fp, " NO ");
+ else
+ (void) fprintf(fp, " YES ");
+
+ if (zfs_prop_inheritable(i))
+ (void) fprintf(fp, " YES ");
+ else
+ (void) fprintf(fp, " NO ");
+
+ if (zfs_prop_values(i) == NULL)
+ (void) fprintf(fp, "-\n");
+ else
+ (void) fprintf(fp, "%s\n", zfs_prop_values(i));
+ }
+ (void) fprintf(fp, gettext("\nSizes are specified in bytes "
+ "with standard units such as K, M, G, etc.\n"));
+ }
+
+ exit(requested ? 0 : 2);
+}
+
+/*
+ * zfs clone <fs, snap, vol> fs
+ *
+ * Given an existing dataset, create a writable copy whose initial contents
+ * are the same as the source. The newly created dataset maintains a
+ * dependency on the original; the original cannot be destroyed so long as
+ * the clone exists.
+ */
+static int
+zfs_do_clone(int argc, char **argv)
+{
+ zfs_handle_t *zhp;
+ int ret;
+
+ /* check options */
+ if (argc > 1 && argv[1][0] == '-') {
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ argv[1][1]);
+ usage(FALSE);
+ }
+
+ /* check number of arguments */
+ if (argc < 2) {
+ (void) fprintf(stderr, gettext("missing source dataset "
+ "argument\n"));
+ usage(FALSE);
+ }
+ if (argc < 3) {
+ (void) fprintf(stderr, gettext("missing target dataset "
+ "argument\n"));
+ usage(FALSE);
+ }
+ if (argc > 3) {
+ (void) fprintf(stderr, gettext("too many arguments\n"));
+ usage(FALSE);
+ }
+
+ /* open the source dataset */
+ if ((zhp = zfs_open(argv[1], ZFS_TYPE_SNAPSHOT)) == NULL)
+ return (1);
+
+ /* pass to libzfs */
+ ret = zfs_clone(zhp, argv[2]);
+
+ /* create the mountpoint if necessary */
+ if (ret == 0) {
+ zfs_handle_t *clone = zfs_open(argv[2], ZFS_TYPE_ANY);
+ if (clone != NULL) {
+ if ((ret = zfs_mount(clone, NULL, 0)) == 0)
+ ret = zfs_share(clone);
+ zfs_close(clone);
+ }
+ }
+
+ zfs_close(zhp);
+
+ return (ret == 0 ? 0 : 1);
+}
+
+/*
+ * zfs create fs
+ * zfs create [-s] -V vol size
+ *
+ * Create a new dataset. This command can be used to create filesystems
+ * and volumes. Snapshot creation is handled by 'zfs snapshot'.
+ * For volumes, the user must specify a size to be used.
+ *
+ * The '-s' flag applies only to volumes, and indicates that we should not try
+ * to set the reservation for this volume. By default we set a reservation
+ * equal to the size for any volume.
+ */
+static int
+zfs_do_create(int argc, char **argv)
+{
+ zfs_type_t type = ZFS_TYPE_FILESYSTEM;
+ zfs_handle_t *zhp;
+ char *size = NULL;
+ char *blocksize = NULL;
+ int c;
+ int noreserve = FALSE;
+ int ret;
+
+ /* check options */
+ while ((c = getopt(argc, argv, ":V:b:s")) != -1) {
+ switch (c) {
+ case 'V':
+ type = ZFS_TYPE_VOLUME;
+ size = optarg;
+ break;
+ case 'b':
+ blocksize = optarg;
+ break;
+ case 's':
+ noreserve = TRUE;
+ break;
+ case ':':
+ (void) fprintf(stderr, gettext("missing size "
+ "argument\n"));
+ usage(FALSE);
+ break;
+ case '?':
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ optopt);
+ usage(FALSE);
+ }
+ }
+
+ if (noreserve && type != ZFS_TYPE_VOLUME) {
+ (void) fprintf(stderr, gettext("'-s' can only be used when "
+ "creating a volume\n"));
+ usage(FALSE);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* check number of arguments */
+ if (argc == 0) {
+ (void) fprintf(stderr, gettext("missing %s argument\n"),
+ zfs_type_to_name(type));
+ usage(FALSE);
+ }
+ if (argc > 1) {
+ (void) fprintf(stderr, gettext("too many arguments\n"));
+ usage(FALSE);
+ }
+
+ /* pass to libzfs */
+ if (zfs_create(argv[0], type, size, blocksize) != 0)
+ return (1);
+
+ if ((zhp = zfs_open(argv[0], ZFS_TYPE_ANY)) == NULL)
+ return (1);
+
+ /*
+ * Volume handling. By default, we try to create a reservation of equal
+ * size for the volume. If we can't do this, then destroy the dataset
+ * and report an error.
+ */
+ if (type == ZFS_TYPE_VOLUME && !noreserve) {
+ if (zfs_prop_set(zhp, ZFS_PROP_RESERVATION, size) != 0) {
+ (void) fprintf(stderr, gettext("use '-s' to create a "
+ "volume without a matching reservation\n"));
+ (void) zfs_destroy(zhp);
+ return (1);
+ }
+ }
+
+ /*
+ * Mount and/or share the new filesystem as appropriate. We provide a
+ * verbose error message to let the user know that their filesystem was
+ * in fact created, even if we failed to mount or share it.
+ */
+ if (zfs_mount(zhp, NULL, 0) != 0) {
+ (void) fprintf(stderr, gettext("filesystem successfully "
+ "created, but not mounted\n"));
+ ret = 1;
+ } else if (zfs_share(zhp) != 0) {
+ (void) fprintf(stderr, gettext("filesystem successfully "
+ "created, but not shared\n"));
+ ret = 1;
+ } else {
+ ret = 0;
+ }
+
+ zfs_close(zhp);
+ return (ret);
+}
+
+/*
+ * zfs destroy [-rf] <fs, snap, vol>
+ *
+ * -r Recursively destroy all children
+ * -R Recursively destroy all dependents, including clones
+ * -f Force unmounting of any dependents
+ *
+ * Destroys the given dataset. By default, it will unmount any filesystems,
+ * and refuse to destroy a dataset that has any dependents. A dependent can
+ * either be a child, or a clone of a child.
+ */
+typedef struct destroy_cbdata {
+ int cb_first;
+ int cb_force;
+ int cb_recurse;
+ int cb_error;
+ int cb_needforce;
+ int cb_doclones;
+ zfs_handle_t *cb_target;
+} destroy_cbdata_t;
+
+/*
+ * Check for any dependents based on the '-r' or '-R' flags.
+ */
+static int
+destroy_check_dependent(zfs_handle_t *zhp, void *data)
+{
+ destroy_cbdata_t *cbp = data;
+ const char *tname = zfs_get_name(cbp->cb_target);
+ const char *name = zfs_get_name(zhp);
+
+ if (strncmp(tname, name, strlen(tname)) == 0 &&
+ (name[strlen(tname)] == '/' || name[strlen(tname)] == '@')) {
+ /*
+ * This is a direct descendant, not a clone somewhere else in
+ * the hierarchy.
+ */
+ if (cbp->cb_recurse)
+ goto out;
+
+ if (cbp->cb_first) {
+ (void) fprintf(stderr, gettext("cannot destroy '%s': "
+ "%s has children\n"),
+ zfs_get_name(cbp->cb_target),
+ zfs_type_to_name(zfs_get_type(cbp->cb_target)));
+ (void) fprintf(stderr, gettext("use '-r' to destroy "
+ "the following datasets:\n"));
+ cbp->cb_first = 0;
+ cbp->cb_error = 1;
+ }
+
+ (void) fprintf(stderr, "%s\n", zfs_get_name(zhp));
+ } else {
+ /*
+ * This is a clone. We only want to report this if the '-r'
+ * wasn't specified, or the target is a snapshot.
+ */
+ if (!cbp->cb_recurse &&
+ zfs_get_type(cbp->cb_target) != ZFS_TYPE_SNAPSHOT)
+ goto out;
+
+ if (cbp->cb_first) {
+ (void) fprintf(stderr, gettext("cannot destroy '%s': "
+ "%s has dependent clones\n"),
+ zfs_get_name(cbp->cb_target),
+ zfs_type_to_name(zfs_get_type(cbp->cb_target)));
+ (void) fprintf(stderr, gettext("use '-R' to destroy "
+ "the following datasets:\n"));
+ cbp->cb_first = 0;
+ cbp->cb_error = 1;
+ }
+
+ (void) fprintf(stderr, "%s\n", zfs_get_name(zhp));
+ }
+
+out:
+ zfs_close(zhp);
+ return (0);
+}
+
+static int
+destroy_callback(zfs_handle_t *zhp, void *data)
+{
+ destroy_cbdata_t *cbp = data;
+
+ /*
+ * Ignore pools (which we've already flagged as an error before getting
+ * here.
+ */
+ if (strchr(zfs_get_name(zhp), '/') == NULL &&
+ zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) {
+ zfs_close(zhp);
+ return (0);
+ }
+
+ /*
+ * Bail out on the first error.
+ */
+ if (zfs_unmount(zhp, NULL, cbp->cb_force ? MS_FORCE : 0) != 0 ||
+ zfs_destroy(zhp) != 0) {
+ zfs_close(zhp);
+ return (-1);
+ }
+
+ zfs_close(zhp);
+ return (0);
+}
+
+
+static int
+zfs_do_destroy(int argc, char **argv)
+{
+ destroy_cbdata_t cb = { 0 };
+ int c;
+ zfs_handle_t *zhp;
+
+ /* check options */
+ while ((c = getopt(argc, argv, "frR")) != -1) {
+ switch (c) {
+ case 'f':
+ cb.cb_force = 1;
+ break;
+ case 'r':
+ cb.cb_recurse = 1;
+ break;
+ case 'R':
+ cb.cb_recurse = 1;
+ cb.cb_doclones = 1;
+ break;
+ case '?':
+ default:
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ optopt);
+ usage(FALSE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* check number of arguments */
+ if (argc == 0) {
+ (void) fprintf(stderr, gettext("missing path argument\n"));
+ usage(FALSE);
+ }
+ if (argc > 1) {
+ (void) fprintf(stderr, gettext("too many arguments\n"));
+ usage(FALSE);
+ }
+
+ /* Open the given dataset */
+ if ((zhp = zfs_open(argv[0], ZFS_TYPE_ANY)) == NULL)
+ return (1);
+
+ cb.cb_target = zhp;
+
+ /*
+ * Perform an explicit check for pools before going any further.
+ */
+ if (!cb.cb_recurse && strchr(zfs_get_name(zhp), '/') == NULL &&
+ zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) {
+ (void) fprintf(stderr, gettext("cannot destroy '%s': "
+ "operation does not apply to pools\n"),
+ zfs_get_name(zhp));
+ (void) fprintf(stderr, gettext("use 'zfs destroy -r "
+ "%s' to destroy all datasets in the pool\n"),
+ zfs_get_name(zhp));
+ (void) fprintf(stderr, gettext("use 'zpool destroy %s' "
+ "to destroy the pool itself\n"), zfs_get_name(zhp));
+ zfs_close(zhp);
+ return (1);
+ }
+
+
+ /*
+ * Check for any dependents and/or clones.
+ */
+ cb.cb_first = 1;
+ if (!cb.cb_doclones)
+ (void) zfs_iter_dependents(zhp, destroy_check_dependent, &cb);
+
+ if (cb.cb_error) {
+ zfs_close(zhp);
+ return (1);
+ }
+
+ /*
+ * Do the real thing.
+ */
+ if (zfs_iter_dependents(zhp, destroy_callback, &cb) == 0 &&
+ destroy_callback(zhp, &cb) == 0)
+ return (0);
+
+ return (1);
+}
+
+/*
+ * zfs get [-rH] [-o field[,field]...] [-s source[,source]...]
+ * prop[,prop...] < fs | snap | vol > ...
+ *
+ * -r recurse over any child datasets
+ * -H scripted mode. Headers are stripped, and fields are separated
+ * by tabs instead of spaces.
+ * -o Set of fields to display. One of "name,property,value,source".
+ * Default is all four.
+ * -s Set of sources to allow. One of
+ * "local,default,inherited,temporary,none". Default is all
+ * five.
+ * -p Display values in parsable (literal) format.
+ *
+ * Prints properties for the given datasets. The user can control which
+ * columns to display as well as which property types to allow.
+ */
+typedef struct get_cbdata {
+ int cb_scripted;
+ int cb_sources;
+ int cb_literal;
+ int cb_columns[4];
+ zfs_prop_t cb_prop[ZFS_NPROP_ALL];
+ int cb_nprop;
+} get_cbdata_t;
+
+#define GET_COL_NAME 1
+#define GET_COL_PROPERTY 2
+#define GET_COL_VALUE 3
+#define GET_COL_SOURCE 4
+
+/*
+ * Display a single line of output, according to the settings in the callback
+ * structure.
+ */
+static void
+print_one_property(zfs_handle_t *zhp, get_cbdata_t *cbp, zfs_prop_t prop,
+ const char *value, zfs_source_t sourcetype, const char *source)
+{
+ int i;
+ int width;
+ const char *str;
+ char buf[128];
+
+ /*
+ * Ignore those source types that the user has chosen to ignore.
+ */
+ if ((sourcetype & cbp->cb_sources) == 0)
+ return;
+
+ for (i = 0; i < 4; i++) {
+ switch (cbp->cb_columns[i]) {
+ case GET_COL_NAME:
+ width = 15;
+ str = zfs_get_name(zhp);
+ break;
+
+ case GET_COL_PROPERTY:
+ width = 13;
+ str = zfs_prop_to_name(prop);
+ break;
+
+ case GET_COL_VALUE:
+ width = 25;
+ str = value;
+ break;
+
+ case GET_COL_SOURCE:
+ width = 15;
+ switch (sourcetype) {
+ case ZFS_SRC_NONE:
+ str = "-";
+ break;
+
+ case ZFS_SRC_DEFAULT:
+ str = "default";
+ break;
+
+ case ZFS_SRC_LOCAL:
+ str = "local";
+ break;
+
+ case ZFS_SRC_TEMPORARY:
+ str = "temporary";
+ break;
+
+ case ZFS_SRC_INHERITED:
+ (void) snprintf(buf, sizeof (buf),
+ "inherited from %s", source);
+ str = buf;
+ break;
+ }
+ break;
+
+ default:
+ continue;
+ }
+
+ if (cbp->cb_columns[i + 1] == 0)
+ (void) printf("%s", str);
+ else if (cbp->cb_scripted)
+ (void) printf("%s\t", str);
+ else
+ (void) printf("%-*s ", width, str);
+
+ }
+
+ (void) printf("\n");
+}
+
+/*
+ * Invoked to display the properties for a single dataset.
+ */
+static int
+get_callback(zfs_handle_t *zhp, void *data)
+{
+ char buf[ZFS_MAXPROPLEN];
+ zfs_source_t sourcetype;
+ char source[ZFS_MAXNAMELEN];
+ get_cbdata_t *cbp = data;
+ int i;
+
+ /*
+ * If we've been given a list of properties, always list properties
+ * in the order given. Otherwise, iterate over all properties and
+ * determine if we should display them.
+ */
+ if (cbp->cb_nprop != 0) {
+ for (i = 0; i < cbp->cb_nprop; i++) {
+ if (zfs_prop_get(zhp, cbp->cb_prop[i], buf,
+ sizeof (buf), &sourcetype, source, sizeof (source),
+ cbp->cb_literal) != 0) {
+ (void) strlcpy(buf, "-", sizeof (buf));
+ sourcetype = ZFS_SRC_NONE;
+ }
+
+ print_one_property(zhp, cbp, cbp->cb_prop[i],
+ buf, sourcetype, source);
+ }
+ } else {
+ for (i = 0; i < ZFS_NPROP_VISIBLE; i++) {
+ if (zfs_prop_get(zhp, i, buf,
+ sizeof (buf), &sourcetype, source, sizeof (source),
+ cbp->cb_literal) == 0) {
+ print_one_property(zhp, cbp, i,
+ buf, sourcetype, source);
+ }
+ }
+ }
+
+ return (0);
+}
+
+static int
+zfs_do_get(int argc, char **argv)
+{
+ get_cbdata_t cb = { 0 };
+ int recurse = 0;
+ int c;
+ char **subopts = zfs_prop_column_subopts();
+ char **shortsubopts = zfs_prop_column_short_subopts();
+ int prop;
+ char *value, *fields, *save_fields;
+ int i;
+
+ /*
+ * Set up default columns and sources.
+ */
+ cb.cb_sources = ZFS_SRC_ALL;
+ cb.cb_columns[0] = GET_COL_NAME;
+ cb.cb_columns[1] = GET_COL_PROPERTY;
+ cb.cb_columns[2] = GET_COL_VALUE;
+ cb.cb_columns[3] = GET_COL_SOURCE;
+
+ /* check options */
+ while ((c = getopt(argc, argv, ":o:s:rHp")) != -1) {
+ switch (c) {
+ case 'p':
+ cb.cb_literal = TRUE;
+ break;
+ case 'r':
+ recurse = TRUE;
+ break;
+ case 'H':
+ cb.cb_scripted = TRUE;
+ break;
+ case ':':
+ (void) fprintf(stderr, gettext("missing argument for "
+ "'%c' option\n"), optopt);
+ usage(FALSE);
+ break;
+ case 'o':
+ /*
+ * Process the set of columns to display. We zero out
+ * the structure to give us a blank slate.
+ */
+ bzero(&cb.cb_columns, sizeof (cb.cb_columns));
+ i = 0;
+ while (*optarg != '\0') {
+ static char *col_subopts[] =
+ { "name", "property", "value", "source",
+ NULL };
+
+ if (i == 4) {
+ (void) fprintf(stderr, gettext("too "
+ "many fields given to -o "
+ "option\n"));
+ usage(FALSE);
+ }
+
+ switch (getsubopt(&optarg, col_subopts,
+ &value)) {
+ case 0:
+ cb.cb_columns[i++] = GET_COL_NAME;
+ break;
+ case 1:
+ cb.cb_columns[i++] = GET_COL_PROPERTY;
+ break;
+ case 2:
+ cb.cb_columns[i++] = GET_COL_VALUE;
+ break;
+ case 3:
+ cb.cb_columns[i++] = GET_COL_SOURCE;
+ break;
+ default:
+ (void) fprintf(stderr,
+ gettext("invalid column name "
+ "'%s'\n"), value);
+ usage(FALSE);
+ }
+ }
+ break;
+
+ case 's':
+ cb.cb_sources = 0;
+ while (*optarg != '\0') {
+ static char *source_subopts[] = {
+ "local", "default", "inherited",
+ "temporary", "none", NULL };
+
+ switch (getsubopt(&optarg, source_subopts,
+ &value)) {
+ case 0:
+ cb.cb_sources |= ZFS_SRC_LOCAL;
+ break;
+ case 1:
+ cb.cb_sources |= ZFS_SRC_DEFAULT;
+ break;
+ case 2:
+ cb.cb_sources |= ZFS_SRC_INHERITED;
+ break;
+ case 3:
+ cb.cb_sources |= ZFS_SRC_TEMPORARY;
+ break;
+ case 4:
+ cb.cb_sources |= ZFS_SRC_NONE;
+ break;
+ default:
+ (void) fprintf(stderr,
+ gettext("invalid source "
+ "'%s'\n"), value);
+ usage(FALSE);
+ }
+ }
+ break;
+
+ case '?':
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ optopt);
+ usage(FALSE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ (void) fprintf(stderr, gettext("missing property "
+ "argument\n"));
+ usage(FALSE);
+ }
+
+ fields = argv[0];
+
+ /*
+ * Leaving 'cb_nprop' at 0 will cause the callback to iterate over all
+ * known properties.
+ */
+ if (strcmp(fields, "all") != 0) {
+ while (*fields != '\0') {
+ if (cb.cb_nprop == ZFS_NPROP_ALL) {
+ (void) fprintf(stderr, gettext("too many "
+ "properties given to -o option\n"));
+ usage(FALSE);
+ }
+
+ save_fields = fields;
+ if ((prop = getsubopt(&fields, subopts,
+ &value)) == -1) {
+ fields = save_fields;
+ prop = getsubopt(&fields, shortsubopts, &value);
+ }
+
+ if (prop == -1) {
+ (void) fprintf(stderr,
+ gettext("invalid property '%s'\n"), value);
+ usage(FALSE);
+ }
+
+ /*
+ * The 'name' property is a one-off special for 'zfs
+ * list', but is not a valid property for 'zfs get'.
+ */
+ if (zfs_prop_column_name(prop) == NULL ||
+ prop == ZFS_PROP_NAME) {
+ (void) fprintf(stderr, gettext("invalid "
+ "property '%s'\n"), zfs_prop_to_name(prop));
+ usage(FALSE);
+ }
+
+ cb.cb_prop[cb.cb_nprop++] = prop;
+ }
+ }
+
+ argc--;
+ argv++;
+
+ /* check for at least one dataset name */
+ if (argc < 1) {
+ (void) fprintf(stderr, gettext("missing dataset argument\n"));
+ usage(FALSE);
+ }
+
+ /*
+ * Print out any headers
+ */
+ if (!cb.cb_scripted) {
+ int i;
+ for (i = 0; i < 4; i++) {
+ switch (cb.cb_columns[i]) {
+ case GET_COL_NAME:
+ (void) printf("%-15s ", "NAME");
+ break;
+ case GET_COL_PROPERTY:
+ (void) printf("%-13s ", "PROPERTY");
+ break;
+ case GET_COL_VALUE:
+ (void) printf("%-25s ", "VALUE");
+ break;
+ case GET_COL_SOURCE:
+ (void) printf("%s", "SOURCE");
+ break;
+ }
+ }
+ (void) printf("\n");
+ }
+
+ free(subopts);
+ for (i = 0; i < ZFS_NPROP_ALL; i++)
+ if (shortsubopts[i][0])
+ free(shortsubopts[i]);
+ free(shortsubopts);
+
+ /* run for each object */
+ return (zfs_for_each(argc, argv, recurse, ZFS_TYPE_ANY,
+ get_callback, &cb));
+}
+
+/*
+ * inherit [-r] <property> <fs|vol> ...
+ *
+ * -r Recurse over all children
+ *
+ * For each dataset specified on the command line, inherit the given property
+ * from its parent. Inheriting a property at the pool level will cause it to
+ * use the default value. The '-r' flag will recurse over all children, and is
+ * useful for setting a property on a hierarchy-wide basis, regardless of any
+ * local modifications for each dataset.
+ */
+static int
+inherit_callback(zfs_handle_t *zhp, void *data)
+{
+ zfs_prop_t prop = (zfs_prop_t)data;
+
+ return (zfs_prop_inherit(zhp, prop) != 0);
+}
+
+static int
+zfs_do_inherit(int argc, char **argv)
+{
+ int recurse = 0;
+ int c;
+ zfs_prop_t prop;
+ char *propname;
+
+ /* check options */
+ while ((c = getopt(argc, argv, "r")) != -1) {
+ switch (c) {
+ case 'r':
+ recurse = TRUE;
+ break;
+ case '?':
+ default:
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ optopt);
+ usage(FALSE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* check number of arguments */
+ if (argc < 1) {
+ (void) fprintf(stderr, gettext("missing property argument\n"));
+ usage(FALSE);
+ }
+ if (argc < 2) {
+ (void) fprintf(stderr, gettext("missing dataset argument\n"));
+ usage(FALSE);
+ }
+
+ propname = argv[0];
+
+ /*
+ * Get and validate the property before iterating over the datasets. We
+ * do this now so as to avoid printing out an error message for each and
+ * every dataset.
+ */
+ if ((prop = zfs_name_to_prop(propname)) == ZFS_PROP_INVAL) {
+ (void) fprintf(stderr, gettext("invalid property '%s'\n"),
+ propname);
+ usage(FALSE);
+ }
+ if (zfs_prop_readonly(prop)) {
+ (void) fprintf(stderr, gettext("%s property is read-only\n"),
+ propname);
+ return (1);
+ }
+ if (!zfs_prop_inheritable(prop)) {
+ (void) fprintf(stderr, gettext("%s property cannot be "
+ "inherited\n"), propname);
+ (void) fprintf(stderr, gettext("use 'zfs set %s=none' to "
+ "clear\n"), propname);
+ return (1);
+ }
+
+ return (zfs_for_each(argc - 1, argv + 1, recurse,
+ ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME,
+ inherit_callback, (void *)prop));
+}
+
+/*
+ * list [-rH] [-a | -s] [-o prop[,prop]*] [fs | vol] ...
+ *
+ * -r Recurse over all children
+ * -H Scripted mode; elide headers and separate colums by tabs
+ * -a Display all datasets
+ * -s Display only snapshots
+ * -o Control which fields to display.
+ *
+ * When given no arguments, lists all filesystems in the system.
+ * Otherwise, list the specified datasets, optionally recursing down them if
+ * '-r' is specified.
+ *
+ * If '-a' is given, then all datasets (including snapshots) are displayed. If
+ * '-s' is given, then only snapshots are displayed. Use of these options
+ * change the default set of fields output, which can still be overridden with
+ * '-o'.
+ */
+typedef struct list_cbdata {
+ int cb_first;
+ int cb_scripted;
+ int cb_fields[ZFS_NPROP_ALL];
+ int cb_fieldcount;
+} list_cbdata_t;
+
+/*
+ * Given a list of columns to display, output appropriate headers for each one.
+ */
+static void
+print_header(int *fields, size_t count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ if (i != 0)
+ (void) printf(" ");
+ if (i == count - 1)
+ (void) printf("%s", zfs_prop_column_name(fields[i]));
+ else /* LINTED - format specifier */
+ (void) printf(zfs_prop_column_format(fields[i]),
+ zfs_prop_column_name(fields[i]));
+ }
+
+ (void) printf("\n");
+}
+
+/*
+ * Given a dataset and a list of fields, print out all the properties according
+ * to the described layout.
+ */
+static void
+print_dataset(zfs_handle_t *zhp, int *fields, size_t count, int scripted)
+{
+ int i;
+ char property[ZFS_MAXPROPLEN];
+
+ for (i = 0; i < count; i++) {
+ if (i != 0) {
+ if (scripted)
+ (void) printf("\t");
+ else
+ (void) printf(" ");
+ }
+
+ if (zfs_prop_get(zhp, fields[i], property,
+ sizeof (property), NULL, NULL, 0, FALSE) != 0)
+ (void) strlcpy(property, "-", sizeof (property));
+
+ if (scripted || i == count - 1)
+ (void) printf("%s", property);
+ else /* LINTED - format specifier */
+ (void) printf(zfs_prop_column_format(fields[i]),
+ property);
+ }
+
+ (void) printf("\n");
+}
+
+/*
+ * Generic callback function to list a dataset or snapshot.
+ */
+static int
+list_callback(zfs_handle_t *zhp, void *data)
+{
+ list_cbdata_t *cbp = data;
+
+ if (cbp->cb_first) {
+ if (!cbp->cb_scripted)
+ print_header(cbp->cb_fields, cbp->cb_fieldcount);
+ cbp->cb_first = FALSE;
+ }
+
+ print_dataset(zhp, cbp->cb_fields, cbp->cb_fieldcount,
+ cbp->cb_scripted);
+
+ return (0);
+}
+
+static int
+zfs_do_list(int argc, char **argv)
+{
+ int c;
+ int recurse = 0;
+ int scripted = FALSE;
+ static char default_fields[] =
+ "name,used,available,referenced,mountpoint";
+ int types = ZFS_TYPE_ANY;
+ char *fields = NULL;
+ char *basic_fields = default_fields;
+ list_cbdata_t cb = { 0 };
+ char *value;
+ int ret;
+ char **subopts = zfs_prop_column_subopts();
+ char **shortsubopts = zfs_prop_column_short_subopts();
+ int prop;
+ char *type_subopts[] = { "filesystem", "volume", "snapshot", NULL };
+ char *save_fields;
+ int i;
+
+ /* check options */
+ while ((c = getopt(argc, argv, ":o:rt:H")) != -1) {
+ switch (c) {
+ case 'o':
+ fields = optarg;
+ break;
+ case 'r':
+ recurse = TRUE;
+ break;
+ case 'H':
+ scripted = TRUE;
+ break;
+ case 't':
+ types = 0;
+ while (*optarg != '\0') {
+ switch (getsubopt(&optarg, type_subopts,
+ &value)) {
+ case 0:
+ types |= ZFS_TYPE_FILESYSTEM;
+ break;
+ case 1:
+ types |= ZFS_TYPE_VOLUME;
+ break;
+ case 2:
+ types |= ZFS_TYPE_SNAPSHOT;
+ break;
+ default:
+ (void) fprintf(stderr,
+ gettext("invalid type '%s'\n"),
+ value);
+ usage(FALSE);
+ }
+ }
+ break;
+ case ':':
+ (void) fprintf(stderr, gettext("missing argument for "
+ "'%c' option\n"), optopt);
+ usage(FALSE);
+ break;
+ case '?':
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ optopt);
+ usage(FALSE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (fields == NULL)
+ fields = basic_fields;
+
+ while (*fields != '\0') {
+ if (cb.cb_fieldcount == ZFS_NPROP_ALL) {
+ (void) fprintf(stderr, gettext("too many "
+ "properties given to -o option\n"));
+ usage(FALSE);
+ }
+
+ save_fields = fields;
+ if ((prop = getsubopt(&fields, subopts, &value)) == -1) {
+ fields = save_fields;
+ prop = getsubopt(&fields, shortsubopts, &value);
+ }
+
+ if (prop == -1) {
+ (void) fprintf(stderr, gettext("invalid property "
+ "'%s'\n"), value);
+ usage(FALSE);
+ }
+
+ if (zfs_prop_column_name(prop) == NULL) {
+ (void) fprintf(stderr, gettext("invalid property "
+ "'%s'\n"), zfs_prop_to_name(prop));
+ usage(FALSE);
+ }
+
+ cb.cb_fields[cb.cb_fieldcount++] = prop;
+ }
+
+ cb.cb_scripted = scripted;
+ cb.cb_first = TRUE;
+
+ ret = zfs_for_each(argc, argv, recurse, types, list_callback, &cb);
+
+ if (ret == 0 && cb.cb_first == TRUE)
+ (void) printf(gettext("no datasets available\n"));
+
+ free(subopts);
+ for (i = 0; i < ZFS_NPROP_ALL; i++)
+ if (shortsubopts[i][0])
+ free(shortsubopts[i]);
+ free(shortsubopts);
+
+ return (ret);
+}
+
+/*
+ * zfs rename <fs | snap | vol> <fs | snap | vol>
+ *
+ * Renames the given dataset to another of the same type.
+ */
+/* ARGSUSED */
+static int
+zfs_do_rename(int argc, char **argv)
+{
+ zfs_handle_t *zhp;
+ int ret = 1;
+
+ /* check options */
+ if (argc > 1 && argv[1][0] == '-') {
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ argv[1][1]);
+ usage(FALSE);
+ }
+
+ /* check number of arguments */
+ if (argc < 2) {
+ (void) fprintf(stderr, gettext("missing source dataset "
+ "argument\n"));
+ usage(FALSE);
+ }
+ if (argc < 3) {
+ (void) fprintf(stderr, gettext("missing target dataset "
+ "argument\n"));
+ usage(FALSE);
+ }
+ if (argc > 3) {
+ (void) fprintf(stderr, gettext("too many arguments\n"));
+ usage(FALSE);
+ }
+
+ if ((zhp = zfs_open(argv[1], ZFS_TYPE_ANY)) == NULL)
+ return (1);
+
+ if (zfs_rename(zhp, argv[2]) != 0)
+ goto error;
+
+ ret = 0;
+error:
+ zfs_close(zhp);
+ return (ret);
+}
+
+/*
+ * zfs rollback [-rfR] <snapshot>
+ *
+ * -r Delete any intervening snapshots before doing rollback
+ * -R Delete any snapshots and their clones
+ * -f Force unmount filesystems, even if they are in use.
+ *
+ * Given a filesystem, rollback to a specific snapshot, discarding any changes
+ * since then and making it the active dataset. If more recent snapshots exist,
+ * the command will complain unless the '-r' flag is given.
+ */
+typedef struct rollback_cbdata {
+ uint64_t cb_create;
+ int cb_first;
+ int cb_force;
+ int cb_doclones;
+ char *cb_target;
+ int cb_error;
+ int cb_recurse;
+ int cb_dependent;
+} rollback_cbdata_t;
+
+/*
+ * 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
+ * 'cb_dependent' is set, then this is a dependent and we should report it
+ * without checking the transaction group.
+ */
+static int
+rollback_check(zfs_handle_t *zhp, void *data)
+{
+ rollback_cbdata_t *cbp = data;
+
+ if (cbp->cb_doclones)
+ return (0);
+
+ if (!cbp->cb_dependent) {
+ if (strcmp(zfs_get_name(zhp), cbp->cb_target) != 0 &&
+ 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 = TRUE;
+ (void) zfs_iter_dependents(zhp, rollback_check,
+ cbp);
+ cbp->cb_dependent = 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"),
+ 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);
+}
+
+/*
+ * Unmount any filesystems or snapshots that will need to be destroyed as part
+ * of the rollback process.
+ */
+static int
+rollback_unmount(zfs_handle_t *zhp, void *data)
+{
+ rollback_cbdata_t *cbp = data;
+
+ if (!cbp->cb_dependent) {
+ if (strcmp(zfs_get_name(zhp), cbp->cb_target) != 0 &&
+ zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) >
+ cbp->cb_create) {
+
+ cbp->cb_dependent = TRUE;
+ (void) zfs_iter_dependents(zhp, rollback_unmount, cbp);
+ cbp->cb_dependent = FALSE;
+
+ if (zfs_unmount(zhp, NULL,
+ cbp->cb_force ? MS_FORCE: 0) != 0)
+ cbp->cb_error = 1;
+ }
+ } else if (zfs_unmount(zhp, NULL, cbp->cb_force ? MS_FORCE : 0) != 0) {
+ cbp->cb_error = 1;
+ }
+
+ zfs_close(zhp);
+ return (0);
+}
+
+/*
+ * Destroy any more recent snapshots. We invoke this callback on any dependents
+ * of the snapshot first. If the 'cb_dependent' member is non-zero, then this
+ * is a dependent and we should just destroy it without checking the transaction
+ * group.
+ */
+static int
+rollback_destroy(zfs_handle_t *zhp, void *data)
+{
+ rollback_cbdata_t *cbp = data;
+
+ if (!cbp->cb_dependent) {
+ if (strcmp(zfs_get_name(zhp), cbp->cb_target) != 0 &&
+ zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) >
+ cbp->cb_create) {
+
+ cbp->cb_dependent = TRUE;
+ (void) zfs_iter_dependents(zhp, rollback_destroy, cbp);
+ cbp->cb_dependent = FALSE;
+
+ if (zfs_destroy(zhp) != 0)
+ cbp->cb_error = 1;
+ }
+ } else if (zfs_destroy(zhp) != 0) {
+ cbp->cb_error = 1;
+ }
+
+ zfs_close(zhp);
+ return (0);
+}
+
+static int
+zfs_do_rollback(int argc, char **argv)
+{
+ int ret;
+ int c;
+ rollback_cbdata_t cb = { 0 };
+ int was_mounted;
+ zfs_handle_t *zhp, *snap;
+ char parentname[ZFS_MAXNAMELEN];
+ char *delim;
+
+ /* check options */
+ while ((c = getopt(argc, argv, "rfR")) != -1) {
+ switch (c) {
+ case 'f':
+ cb.cb_force = TRUE;
+ break;
+ case 'r':
+ cb.cb_recurse = 1;
+ break;
+ case 'R':
+ cb.cb_recurse = 1;
+ cb.cb_doclones = 1;
+ break;
+ case '?':
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ optopt);
+ usage(FALSE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* check number of arguments */
+ if (argc < 1) {
+ (void) fprintf(stderr, gettext("missing dataset argument\n"));
+ usage(FALSE);
+ }
+ if (argc > 1) {
+ (void) fprintf(stderr, gettext("too many arguments\n"));
+ usage(FALSE);
+ }
+
+ cb.cb_target = argv[0];
+
+ /* open the snapshot */
+ if ((snap = zfs_open(cb.cb_target, ZFS_TYPE_SNAPSHOT)) == NULL)
+ return (1);
+
+ (void) strlcpy(parentname, cb.cb_target, sizeof (parentname));
+ verify((delim = strrchr(parentname, '@')) != NULL);
+ *delim = '\0';
+ if ((zhp = zfs_open(parentname, ZFS_TYPE_ANY)) == NULL) {
+ zfs_close(snap);
+ return (1);
+ }
+
+ /* See if this dataset is mounted */
+ was_mounted = zfs_is_mounted(zhp, NULL);
+
+ cb.cb_create = zfs_prop_get_int(snap, ZFS_PROP_CREATETXG);
+
+ /*
+ * Check for more recent snapshots and/or clones based on the presence
+ * of '-r' and '-R'.
+ */
+ cb.cb_first = 1;
+ cb.cb_error = 0;
+ (void) zfs_iter_children(zhp, rollback_check, &cb);
+
+ if ((ret = cb.cb_error) != 0)
+ goto out;
+
+ cb.cb_error = 0;
+
+ /*
+ * Unmount any snapshots as well as the dataset itself.
+ */
+ if ((ret = zfs_iter_children(zhp, rollback_unmount,
+ &cb)) != 0 || (ret = zfs_unmount(zhp, NULL,
+ cb.cb_force ? MS_FORCE : 0)) != 0)
+ goto out;
+
+ (void) zfs_iter_children(zhp, rollback_destroy, &cb);
+
+ if ((ret = cb.cb_error) != 0)
+ goto out;
+
+ /*
+ * Now that we have verified that the snapshot is the latest, rollback
+ * to the given snapshot.
+ */
+ ret = zfs_rollback(zhp);
+
+ /*
+ * We only want to re-mount the filesystem if it was mounted in the
+ * first place.
+ */
+ if (was_mounted)
+ (void) zfs_mount(zhp, NULL, 0);
+
+out:
+ zfs_close(snap);
+ zfs_close(zhp);
+
+ if (ret == 0)
+ return (0);
+ else
+ return (1);
+}
+
+/*
+ * zfs set property=value { fs | snap | vol } ...
+ *
+ * Sets the given property for all datasets specified on the command line.
+ */
+typedef struct set_cbdata {
+ char *cb_propname;
+ char *cb_value;
+ zfs_prop_t cb_prop;
+} set_cbdata_t;
+
+static int
+set_callback(zfs_handle_t *zhp, void *data)
+{
+ set_cbdata_t *cbp = data;
+ int ret = 1;
+
+ /* don't allow setting of properties for snapshots */
+ if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT) {
+ (void) fprintf(stderr, gettext("cannot set %s property for "
+ "'%s': snapshot properties cannot be modified\n"),
+ cbp->cb_propname, zfs_get_name(zhp));
+ return (1);
+ }
+
+ /*
+ * If we're changing the volsize, and the volsize and reservation are
+ * the same, then change the reservation as well.
+ */
+ if (cbp->cb_prop == ZFS_PROP_VOLSIZE &&
+ zfs_get_type(zhp) == ZFS_TYPE_VOLUME &&
+ zfs_prop_get_int(zhp, ZFS_PROP_VOLSIZE) ==
+ zfs_prop_get_int(zhp, ZFS_PROP_RESERVATION)) {
+ uint64_t volsize = zfs_prop_get_int(zhp, ZFS_PROP_VOLSIZE);
+ uint64_t avail = zfs_prop_get_int(zhp, ZFS_PROP_AVAILABLE);
+ uint64_t value;
+
+ verify(zfs_nicestrtonum(cbp->cb_value, &value) == 0);
+
+ /*
+ * Warn about raising the volume size greater than the amount of
+ * available space.
+ */
+ if (value > volsize && (value - volsize) > avail) {
+ (void) fprintf(stderr, gettext("cannot set "
+ "%s property for '%s': volume size exceeds "
+ "amount of available space\n"),
+ cbp->cb_propname, zfs_get_name(zhp));
+ return (1);
+ }
+
+ if (zfs_prop_set(zhp, ZFS_PROP_RESERVATION,
+ cbp->cb_value) != 0) {
+ (void) fprintf(stderr, gettext("volsize and "
+ "reservation must remain equal\n"));
+ return (1);
+ }
+ }
+
+ /*
+ * Do not allow the reservation to be set above the volume size. We do
+ * this here instead of inside libzfs because libzfs violates this rule
+ * internally.
+ */
+ if (cbp->cb_prop == ZFS_PROP_RESERVATION &&
+ zfs_get_type(zhp) == ZFS_TYPE_VOLUME) {
+ uint64_t value;
+ uint64_t volsize;
+
+ volsize = zfs_prop_get_int(zhp, ZFS_PROP_VOLSIZE);
+ if (strcmp(cbp->cb_value, "none") == 0)
+ value = 0;
+ else
+ verify(zfs_nicestrtonum(cbp->cb_value, &value) == 0);
+
+ if (value > volsize) {
+ (void) fprintf(stderr, gettext("cannot set %s "
+ "for '%s': size is greater than current "
+ "volume size\n"), cbp->cb_propname,
+ zfs_get_name(zhp));
+ return (-1);
+ }
+ }
+
+ if (zfs_prop_set(zhp, cbp->cb_prop, cbp->cb_value) != 0)
+ return (1);
+
+ ret = 0;
+error:
+ return (ret);
+}
+
+static int
+zfs_do_set(int argc, char **argv)
+{
+ set_cbdata_t cb;
+
+ /* check for options */
+ if (argc > 1 && argv[1][0] == '-') {
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ argv[1][1]);
+ usage(FALSE);
+ }
+
+ /* check number of arguments */
+ if (argc < 2) {
+ (void) fprintf(stderr, gettext("missing property=value "
+ "argument\n"));
+ usage(FALSE);
+ }
+ if (argc < 3) {
+ (void) fprintf(stderr, gettext("missing dataset name\n"));
+ usage(FALSE);
+ }
+
+ /* validate property=value argument */
+ cb.cb_propname = argv[1];
+ if ((cb.cb_value = strchr(cb.cb_propname, '=')) == NULL) {
+ (void) fprintf(stderr, gettext("missing value in "
+ "property=value argument\n"));
+ usage(FALSE);
+ }
+
+ *cb.cb_value = '\0';
+ cb.cb_value++;
+
+ if (*cb.cb_propname == '\0') {
+ (void) fprintf(stderr,
+ gettext("missing property in property=value argument\n"));
+ usage(FALSE);
+ }
+ if (*cb.cb_value == '\0') {
+ (void) fprintf(stderr,
+ gettext("missing value in property=value argument\n"));
+ usage(FALSE);
+ }
+
+ /* get the property type */
+ if ((cb.cb_prop = zfs_name_to_prop(cb.cb_propname)) ==
+ ZFS_PROP_INVAL) {
+ (void) fprintf(stderr,
+ gettext("invalid property '%s'\n"), cb.cb_propname);
+ usage(FALSE);
+ }
+
+ /*
+ * Validate that the value is appropriate for this property. We do this
+ * once now so we don't generate multiple errors each time we try to
+ * apply it to a dataset.
+ */
+ if (zfs_prop_validate(cb.cb_prop, cb.cb_value, NULL) != 0)
+ return (1);
+
+ return (zfs_for_each(argc - 2, argv + 2, FALSE,
+ ZFS_TYPE_ANY, set_callback, &cb));
+}
+
+/*
+ * zfs snapshot <fs@snap>
+ *
+ * Creates a snapshot with the given name. While functionally equivalent to
+ * 'zfs create', it is a separate command to diffferentiate intent.
+ */
+static int
+zfs_do_snapshot(int argc, char **argv)
+{
+ /* check options */
+ if (argc > 1 && argv[1][0] == '-') {
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ argv[1][1]);
+ usage(FALSE);
+ }
+
+ /* check number of arguments */
+ if (argc < 2) {
+ (void) fprintf(stderr, gettext("missing snapshot argument\n"));
+ usage(FALSE);
+ }
+ if (argc > 2) {
+ (void) fprintf(stderr, gettext("too many arguments\n"));
+ usage(FALSE);
+ }
+
+ return (zfs_snapshot(argv[1]) != 0);
+}
+
+/*
+ * zfs backup [-i <fs@snap>] <fs@snap>
+ *
+ * Send a backup stream to stdout.
+ */
+static int
+zfs_do_backup(int argc, char **argv)
+{
+ char *fromname = NULL;
+ zfs_handle_t *zhp_from = NULL, *zhp_to;
+ int c, err;
+
+ /* check options */
+ while ((c = getopt(argc, argv, ":i:")) != -1) {
+ switch (c) {
+ case 'i':
+ fromname = optarg;
+ break;
+ case ':':
+ (void) fprintf(stderr, gettext("missing argument for "
+ "'%c' option\n"), optopt);
+ usage(FALSE);
+ break;
+ case '?':
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ optopt);
+ usage(FALSE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* check number of arguments */
+ if (argc < 1) {
+ (void) fprintf(stderr, gettext("missing snapshot argument\n"));
+ usage(FALSE);
+ }
+ if (argc > 1) {
+ (void) fprintf(stderr, gettext("too many arguments\n"));
+ usage(FALSE);
+ }
+
+ if (isatty(STDOUT_FILENO)) {
+ (void) fprintf(stderr,
+ gettext("Error: Backup stream can not be written "
+ "to a terminal.\n"
+ "You must redirect standard output.\n"));
+ return (1);
+ }
+
+ if (fromname) {
+ if ((zhp_from = zfs_open(fromname, ZFS_TYPE_SNAPSHOT)) == NULL)
+ return (1);
+ }
+ if ((zhp_to = zfs_open(argv[0], ZFS_TYPE_SNAPSHOT)) == NULL)
+ return (1);
+
+ err = zfs_backup(zhp_to, zhp_from);
+
+ if (zhp_from)
+ zfs_close(zhp_from);
+ zfs_close(zhp_to);
+
+ return (err != 0);
+}
+
+/*
+ * zfs restore <fs@snap>
+ *
+ * Restore a backup stream from stdin.
+ */
+static int
+zfs_do_restore(int argc, char **argv)
+{
+ int c, err;
+ int isprefix = FALSE;
+ int dryrun = FALSE;
+ int verbose = FALSE;
+
+ /* check options */
+ while ((c = getopt(argc, argv, ":dnv")) != -1) {
+ switch (c) {
+ case 'd':
+ isprefix = TRUE;
+ break;
+ case 'n':
+ dryrun = TRUE;
+ break;
+ case 'v':
+ verbose = TRUE;
+ break;
+ case ':':
+ (void) fprintf(stderr, gettext("missing argument for "
+ "'%c' option\n"), optopt);
+ usage(FALSE);
+ break;
+ case '?':
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ optopt);
+ usage(FALSE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* check number of arguments */
+ if (argc < 1) {
+ (void) fprintf(stderr, gettext("missing snapshot argument\n"));
+ usage(FALSE);
+ }
+ if (argc > 1) {
+ (void) fprintf(stderr, gettext("too many arguments\n"));
+ usage(FALSE);
+ }
+
+ if (isatty(STDIN_FILENO)) {
+ (void) fprintf(stderr,
+ gettext("Error: Backup stream can not be read "
+ "from a terminal.\n"
+ "You must redirect standard input.\n"));
+ return (1);
+ }
+
+ err = zfs_restore(argv[0], isprefix, verbose, dryrun);
+ return (err != 0);
+}
+
+
+/*
+ * Generic callback for sharing or mounting filesystems. Because the code is so
+ * similar, we have a common function with an extra parameter to determine which
+ * mode we are using.
+ */
+#define OP_SHARE 0x1
+#define OP_MOUNT 0x2
+
+typedef struct share_mount_cbdata {
+ int cb_type;
+ int cb_explicit;
+ int cb_flags;
+ const char *cb_options;
+} share_mount_cbdata_t;
+
+/*
+ * Share or mount the filesystem.
+ */
+static int
+share_mount_callback(zfs_handle_t *zhp, void *data)
+{
+ char mountpoint[ZFS_MAXPROPLEN];
+ char shareopts[ZFS_MAXPROPLEN];
+ share_mount_cbdata_t *cbp = data;
+ const char *cmdname = cbp->cb_type == OP_SHARE ? "share" : "mount";
+ struct mnttab mnt;
+ uint64_t zoned;
+
+ if (cbp->cb_options == NULL)
+ mnt.mnt_mntopts = "";
+ else
+ mnt.mnt_mntopts = (char *)cbp->cb_options;
+
+ /*
+ * Check to make sure we can mount/share this dataset. If we are in the
+ * global zone and the filesystem is exported to a local zone, or if we
+ * are in a local zone and the filesystem is not exported, then it is an
+ * error.
+ */
+ zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);
+
+ if (zoned && getzoneid() == GLOBAL_ZONEID) {
+ if (!cbp->cb_explicit)
+ return (0);
+
+ (void) fprintf(stderr, gettext("cannot %s '%s': dataset is "
+ "exported to a local zone\n"), cmdname, zfs_get_name(zhp));
+ return (1);
+
+ } else if (!zoned && getzoneid() != GLOBAL_ZONEID) {
+ if (!cbp->cb_explicit)
+ return (0);
+
+ (void) fprintf(stderr, gettext("cannot %s '%s': permission "
+ "denied\n"), cmdname, zfs_get_name(zhp));
+ return (1);
+ }
+
+ /*
+ * Inore any filesystems which don't apply to us. This includes those
+ * with a legacy mountpoint, or those with legacy share options.
+ */
+ verify(zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint,
+ sizeof (mountpoint), NULL, NULL, 0, FALSE) == 0);
+ verify(zfs_prop_get(zhp, ZFS_PROP_SHARENFS, shareopts,
+ sizeof (shareopts), NULL, NULL, 0, FALSE) == 0);
+
+ if (cbp->cb_type == OP_SHARE) {
+ if (strcmp(shareopts, "off") == 0) {
+ if (!cbp->cb_explicit)
+ return (0);
+
+ (void) fprintf(stderr, gettext("cannot share '%s': "
+ "legacy share\n"), zfs_get_name(zhp));
+ (void) fprintf(stderr, gettext("use share(1M) to "
+ "share this filesystem\n"));
+ return (1);
+ }
+ }
+
+ /*
+ * We cannot share or mount legacy filesystems. If the shareopts is
+ * non-legacy but the mountpoint is legacy, we treat it as a legacy
+ * share.
+ */
+ if (strcmp(mountpoint, "legacy") == 0) {
+ if (!cbp->cb_explicit)
+ return (0);
+
+ (void) fprintf(stderr, gettext("cannot %s '%s': "
+ "legacy mountpoint\n"), cmdname, zfs_get_name(zhp));
+ (void) fprintf(stderr, gettext("use %s to "
+ "%s this filesystem\n"), cbp->cb_type == OP_SHARE ?
+ "share(1M)" : "mount(1M)", cmdname);
+ return (1);
+ }
+
+ if (strcmp(mountpoint, "none") == 0) {
+ if (!cbp->cb_explicit)
+ return (0);
+
+ (void) fprintf(stderr, gettext("cannot %s '%s': no "
+ "mountpoint set\n"), cmdname, zfs_get_name(zhp));
+ return (1);
+ }
+
+ /*
+ * At this point, we have verified that the mountpoint and/or shareopts
+ * are appropriate for auto management. Determine if the filesystem is
+ * currently mounted or shared, and abort if this is an explicit
+ * request.
+ */
+ switch (cbp->cb_type) {
+ case OP_SHARE:
+ if (zfs_is_shared(zhp, NULL)) {
+ if (cbp->cb_explicit) {
+ (void) fprintf(stderr, gettext("cannot share "
+ "'%s': filesystem already shared\n"),
+ zfs_get_name(zhp));
+ return (1);
+ } else {
+ return (0);
+ }
+ }
+ break;
+
+ case OP_MOUNT:
+ if (!hasmntopt(&mnt, MNTOPT_REMOUNT) &&
+ zfs_is_mounted(zhp, NULL)) {
+ if (cbp->cb_explicit) {
+ (void) fprintf(stderr, gettext("cannot mount "
+ "'%s': filesystem already mounted\n"),
+ zfs_get_name(zhp));
+ return (1);
+ } else {
+ return (0);
+ }
+ }
+ break;
+ }
+
+ /*
+ * Mount and optionally share the filesystem.
+ */
+ switch (cbp->cb_type) {
+ case OP_SHARE:
+ {
+ if (!zfs_is_mounted(zhp, NULL) &&
+ zfs_mount(zhp, NULL, 0) != 0)
+ return (1);
+
+ if (zfs_share(zhp) != 0)
+ return (1);
+ }
+ break;
+
+ case OP_MOUNT:
+ if (zfs_mount(zhp, cbp->cb_options, cbp->cb_flags) != 0)
+ return (1);
+ break;
+ }
+
+ return (0);
+}
+
+static int
+share_or_mount(int type, int argc, char **argv)
+{
+ int do_all = 0;
+ int c, ret;
+ share_mount_cbdata_t cb = { 0 };
+
+ cb.cb_type = type;
+
+ /* check options */
+ while ((c = getopt(argc, argv, type == OP_MOUNT ? ":ao:O" : "a"))
+ != -1) {
+ switch (c) {
+ case 'a':
+ do_all = 1;
+ break;
+ case 'o':
+ cb.cb_options = optarg;
+ break;
+ case 'O':
+ cb.cb_flags |= MS_OVERLAY;
+ break;
+ case ':':
+ (void) fprintf(stderr, gettext("missing argument for "
+ "'%c' option\n"), optopt);
+ usage(FALSE);
+ break;
+ case '?':
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ optopt);
+ usage(FALSE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* check number of arguments */
+ if (do_all) {
+ if (argc != 0) {
+ (void) fprintf(stderr, gettext("too many arguments\n"));
+ usage(FALSE);
+ }
+
+ ret = zfs_for_each(argc, argv, TRUE,
+ ZFS_TYPE_FILESYSTEM, share_mount_callback, &cb);
+ } else if (argc == 0) {
+ struct mnttab entry;
+
+ if (type == OP_SHARE) {
+ (void) fprintf(stderr, gettext("missing filesystem "
+ "argument\n"));
+ usage(FALSE);
+ }
+
+ /*
+ * When mount is given no arguments, go through /etc/mnttab and
+ * display any active ZFS mounts. We hide any snapshots, since
+ * they are controlled automatically.
+ */
+ rewind(mnttab_file);
+ while (getmntent(mnttab_file, &entry) == 0) {
+ if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0 ||
+ strchr(entry.mnt_special, '@') != NULL)
+ continue;
+
+ (void) printf("%-30s %s\n", entry.mnt_special,
+ entry.mnt_mountp);
+ }
+
+ ret = 0;
+ } else {
+ zfs_handle_t *zhp;
+
+ if (argc > 1) {
+ (void) fprintf(stderr,
+ gettext("too many arguments\n"));
+ usage(FALSE);
+ }
+
+ if ((zhp = zfs_open(argv[0], ZFS_TYPE_FILESYSTEM)) == NULL)
+ ret = 1;
+ else {
+ cb.cb_explicit = TRUE;
+ ret = share_mount_callback(zhp, &cb);
+ zfs_close(zhp);
+ }
+ }
+
+ return (ret);
+}
+
+/*
+ * zfs mount -a
+ * zfs mount filesystem
+ *
+ * Mount all filesystems, or mount the given filesystem.
+ */
+static int
+zfs_do_mount(int argc, char **argv)
+{
+ return (share_or_mount(OP_MOUNT, argc, argv));
+}
+
+/*
+ * zfs share -a
+ * zfs share filesystem
+ *
+ * Share all filesystems, or share the given filesystem.
+ */
+static int
+zfs_do_share(int argc, char **argv)
+{
+ return (share_or_mount(OP_SHARE, argc, argv));
+}
+
+typedef struct unshare_unmount_node {
+ zfs_handle_t *un_zhp;
+ char *un_mountp;
+ uu_avl_node_t un_avlnode;
+} unshare_unmount_node_t;
+
+/* ARGSUSED */
+static int
+unshare_unmount_compare(const void *larg, const void *rarg, void *unused)
+{
+ const unshare_unmount_node_t *l = larg;
+ const unshare_unmount_node_t *r = rarg;
+
+ return (strcmp(l->un_mountp, r->un_mountp));
+}
+
+/*
+ * Convenience routine used by zfs_do_umount() and manual_unmount(). Given an
+ * absolute path, find the entry /etc/mnttab, verify that its a ZFS filesystem,
+ * and unmount it appropriately.
+ */
+static int
+unshare_unmount_path(int type, char *path, int flags, int is_manual)
+{
+ zfs_handle_t *zhp;
+ int ret;
+ struct stat64 statbuf;
+ struct extmnttab entry;
+ const char *cmdname = (type == OP_SHARE) ? "unshare" : "unmount";
+ char property[ZFS_MAXPROPLEN];
+
+ /*
+ * Search for the path in /etc/mnttab. Rather than looking for the
+ * specific path, which can be fooled by non-standard paths (i.e. ".."
+ * or "//"), we stat() the path and search for the corresponding
+ * (major,minor) device pair.
+ */
+ if (stat64(path, &statbuf) != 0) {
+ (void) fprintf(stderr, gettext("cannot %s '%s': %s\n"),
+ cmdname, path, strerror(errno));
+ return (1);
+ }
+
+ /*
+ * Search for the given (major,minor) pair in the mount table.
+ */
+ rewind(mnttab_file);
+ while ((ret = getextmntent(mnttab_file, &entry, 0)) == 0) {
+ if (entry.mnt_major == major(statbuf.st_dev) &&
+ entry.mnt_minor == minor(statbuf.st_dev))
+ break;
+ }
+ if (ret != 0) {
+ (void) fprintf(stderr, gettext("cannot %s '%s': not "
+ "currently mounted\n"), cmdname, path);
+ return (1);
+ }
+
+ if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0) {
+ (void) fprintf(stderr, gettext("cannot %s '%s': not a ZFS "
+ "filesystem\n"), cmdname, path);
+ return (1);
+ }
+
+ if ((zhp = zfs_open(entry.mnt_special, ZFS_TYPE_FILESYSTEM)) == NULL)
+ return (1);
+
+ verify(zfs_prop_get(zhp, type == OP_SHARE ?
+ ZFS_PROP_SHARENFS : ZFS_PROP_MOUNTPOINT, property,
+ sizeof (property), NULL, NULL, 0, FALSE) == 0);
+
+ if (type == OP_SHARE) {
+ if (strcmp(property, "off") == 0) {
+ (void) fprintf(stderr, gettext("cannot unshare "
+ "'%s': legacy share\n"), path);
+ (void) fprintf(stderr, gettext("use "
+ "unshare(1M) to unshare this filesystem\n"));
+ ret = 1;
+ } else if (!zfs_is_shared(zhp, NULL)) {
+ (void) fprintf(stderr, gettext("cannot unshare '%s': "
+ "not currently shared\n"), path);
+ ret = 1;
+ } else {
+ ret = zfs_unshareall(zhp);
+ }
+ } else {
+ if (strcmp(property, "legacy") == 0 && !is_manual) {
+ (void) fprintf(stderr, gettext("cannot unmount "
+ "'%s': legacy mountpoint\n"),
+ zfs_get_name(zhp));
+ (void) fprintf(stderr, gettext("use umount(1M) "
+ "to unmount this filesystem\n"));
+ ret = 1;
+ } else {
+ ret = zfs_unmountall(zhp, flags);
+ }
+ }
+
+ zfs_close(zhp);
+
+ return (ret != 0);
+}
+
+/*
+ * Generic callback for unsharing or unmounting a filesystem.
+ */
+static int
+unshare_unmount(int type, int argc, char **argv)
+{
+ int do_all = 0;
+ int flags = 0;
+ int ret = 0;
+ int c;
+ zfs_handle_t *zhp;
+ char property[ZFS_MAXPROPLEN];
+
+ /* check options */
+ while ((c = getopt(argc, argv, type == OP_SHARE ? "a" : "af")) != -1) {
+ switch (c) {
+ case 'a':
+ do_all = 1;
+ break;
+ case 'f':
+ flags = MS_FORCE;
+ break;
+ case '?':
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ optopt);
+ usage(FALSE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* ensure correct number of arguments */
+ if (do_all) {
+ if (argc != 0) {
+ (void) fprintf(stderr, gettext("too many arguments\n"));
+ usage(FALSE);
+ }
+ } else if (argc != 1) {
+ if (argc == 0)
+ (void) fprintf(stderr,
+ gettext("missing filesystem argument\n"));
+ else
+ (void) fprintf(stderr,
+ gettext("too many arguments\n"));
+ usage(FALSE);
+ }
+
+ if (do_all) {
+ /*
+ * We could make use of zfs_for_each() to walk all datasets in
+ * the system, but this would be very inefficient, especially
+ * since we would have to linearly search /etc/mnttab for each
+ * one. Instead, do one pass through /etc/mnttab looking for
+ * zfs entries and call zfs_unmount() for each one.
+ *
+ * Things get a little tricky if the administrator has created
+ * mountpoints beneath other ZFS filesystems. In this case, we
+ * have to unmount the deepest filesystems first. To accomplish
+ * this, we place all the mountpoints in an AVL tree sorted by
+ * the special type (dataset name), and walk the result in
+ * reverse to make sure to get any snapshots first.
+ */
+ struct mnttab entry;
+ uu_avl_pool_t *pool;
+ uu_avl_t *tree;
+ unshare_unmount_node_t *node;
+ uu_avl_index_t idx;
+ uu_avl_walk_t *walk;
+
+ if ((pool = uu_avl_pool_create("unmount_pool",
+ sizeof (unshare_unmount_node_t),
+ offsetof(unshare_unmount_node_t, un_avlnode),
+ unshare_unmount_compare,
+ UU_DEFAULT)) == NULL) {
+ (void) fprintf(stderr, gettext("internal error: "
+ "out of memory\n"));
+ exit(1);
+ }
+
+ if ((tree = uu_avl_create(pool, NULL, UU_DEFAULT)) == NULL) {
+ (void) fprintf(stderr, gettext("internal error: "
+ "out of memory\n"));
+ exit(1);
+ }
+
+ rewind(mnttab_file);
+ while (getmntent(mnttab_file, &entry) == 0) {
+
+ /* ignore non-ZFS entries */
+ if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0)
+ continue;
+
+ /* ignore snapshots */
+ if (strchr(entry.mnt_special, '@') != NULL)
+ continue;
+
+ if ((zhp = zfs_open(entry.mnt_special,
+ ZFS_TYPE_FILESYSTEM)) == NULL) {
+ ret = 1;
+ continue;
+ }
+
+ verify(zfs_prop_get(zhp, type == OP_SHARE ?
+ ZFS_PROP_SHARENFS : ZFS_PROP_MOUNTPOINT,
+ property, sizeof (property), NULL, NULL,
+ 0, FALSE) == 0);
+
+ /* Ignore legacy mounts and shares */
+ if ((type == OP_SHARE &&
+ strcmp(property, "off") == 0) ||
+ (type == OP_MOUNT &&
+ strcmp(property, "legacy") == 0)) {
+ zfs_close(zhp);
+ continue;
+ }
+
+ node = safe_malloc(sizeof (unshare_unmount_node_t));
+ node->un_zhp = zhp;
+
+ if ((node->un_mountp = strdup(entry.mnt_mountp)) ==
+ NULL) {
+ (void) fprintf(stderr, gettext("internal error:"
+ " out of memory\n"));
+ exit(1);
+ }
+
+ uu_avl_node_init(node, &node->un_avlnode, pool);
+
+ if (uu_avl_find(tree, node, NULL, &idx) == NULL) {
+ uu_avl_insert(tree, node, idx);
+ } else {
+ zfs_close(node->un_zhp);
+ free(node->un_mountp);
+ free(node);
+ }
+ }
+
+ /*
+ * Walk the AVL tree in reverse, unmounting each filesystem and
+ * removing it from the AVL tree in the process.
+ */
+ if ((walk = uu_avl_walk_start(tree,
+ UU_WALK_REVERSE | UU_WALK_ROBUST)) == NULL) {
+ (void) fprintf(stderr,
+ gettext("internal error: out of memory"));
+ exit(1);
+ }
+
+ while ((node = uu_avl_walk_next(walk)) != NULL) {
+ uu_avl_remove(tree, node);
+
+ switch (type) {
+ case OP_SHARE:
+ if (zfs_unshare(node->un_zhp,
+ node->un_mountp) != 0)
+ ret = 1;
+ break;
+
+ case OP_MOUNT:
+ if (zfs_unmount(node->un_zhp,
+ node->un_mountp, flags) != 0)
+ ret = 1;
+ break;
+ }
+
+ zfs_close(node->un_zhp);
+ free(node->un_mountp);
+ free(node);
+ }
+
+ uu_avl_walk_end(walk);
+ uu_avl_destroy(tree);
+ uu_avl_pool_destroy(pool);
+ } else {
+ /*
+ * We have an argument, but it may be a full path or a ZFS
+ * filesystem. Pass full paths off to unmount_path() (shared by
+ * manual_unmount), otherwise open the filesystem and pass to
+ * zfs_unmount().
+ */
+ if (argv[0][0] == '/')
+ return (unshare_unmount_path(type, argv[0],
+ flags, FALSE));
+
+ if ((zhp = zfs_open(argv[0], ZFS_TYPE_FILESYSTEM)) == NULL)
+ return (1);
+
+ verify(zfs_prop_get(zhp, type == OP_SHARE ?
+ ZFS_PROP_SHARENFS : ZFS_PROP_MOUNTPOINT, property,
+ sizeof (property), NULL, NULL, 0, FALSE) == 0);
+
+ switch (type) {
+ case OP_SHARE:
+ if (strcmp(property, "off") == 0) {
+ (void) fprintf(stderr, gettext("cannot unshare "
+ "'%s': legacy share\n"), zfs_get_name(zhp));
+ (void) fprintf(stderr, gettext("use unshare(1M)"
+ " to unshare this filesystem\n"));
+ ret = 1;
+ } else if (!zfs_is_shared(zhp, NULL)) {
+ (void) fprintf(stderr, gettext("cannot unshare "
+ "'%s': not currently shared\n"),
+ zfs_get_name(zhp));
+ ret = 1;
+ } else if (zfs_unshareall(zhp) != 0) {
+ ret = 1;
+ }
+ break;
+
+ case OP_MOUNT:
+ if (strcmp(property, "legacy") == 0) {
+ (void) fprintf(stderr, gettext("cannot unmount "
+ "'%s': legacy mountpoint\n"),
+ zfs_get_name(zhp));
+ (void) fprintf(stderr, gettext("use umount(1M) "
+ "to unmount this filesystem\n"));
+ ret = 1;
+ } else if (!zfs_is_mounted(zhp, NULL)) {
+ (void) fprintf(stderr, gettext("cannot unmount "
+ "'%s': not currently mounted\n"),
+ zfs_get_name(zhp));
+ ret = 1;
+ } else if (zfs_unmountall(zhp, flags) != 0) {
+ ret = 1;
+ }
+ }
+
+ zfs_close(zhp);
+ }
+
+ return (ret);
+}
+
+/*
+ * zfs unmount -a
+ * zfs unmount filesystem
+ *
+ * Unmount all filesystems, or a specific ZFS filesystem.
+ */
+static int
+zfs_do_unmount(int argc, char **argv)
+{
+ return (unshare_unmount(OP_MOUNT, argc, argv));
+}
+
+/*
+ * zfs unshare -a
+ * zfs unshare filesystem
+ *
+ * Unshare all filesystems, or a specific ZFS filesystem.
+ */
+static int
+zfs_do_unshare(int argc, char **argv)
+{
+ return (unshare_unmount(OP_SHARE, argc, argv));
+}
+
+/*
+ * Called when invoked as /etc/fs/zfs/mount. Do the mount if the mountpoint is
+ * 'legacy'. Otherwise, complain that use should be using 'zfs mount'.
+ */
+static int
+manual_mount(int argc, char **argv)
+{
+ zfs_handle_t *zhp;
+ char mountpoint[ZFS_MAXPROPLEN];
+ char mntopts[MNT_LINE_MAX] = { '\0' };
+ int ret;
+ int c;
+ int flags = 0;
+ char *dataset, *path;
+
+ /* check options */
+ while ((c = getopt(argc, argv, ":o:O")) != -1) {
+ switch (c) {
+ case 'o':
+ (void) strlcpy(mntopts, optarg, sizeof (mntopts));
+ break;
+ case 'O':
+ flags |= MS_OVERLAY;
+ break;
+ case ':':
+ (void) fprintf(stderr, gettext("missing argument for "
+ "'%c' option\n"), optopt);
+ usage(FALSE);
+ break;
+ case '?':
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ optopt);
+ (void) fprintf(stderr, gettext("usage: mount [-o opts] "
+ "<path>\n"));
+ return (2);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* check that we only have two arguments */
+ if (argc != 2) {
+ if (argc == 0)
+ (void) fprintf(stderr, gettext("missing dataset "
+ "argument\n"));
+ else if (argc == 1)
+ (void) fprintf(stderr,
+ gettext("missing mountpoint argument\n"));
+ else
+ (void) fprintf(stderr, gettext("too many arguments\n"));
+ (void) fprintf(stderr, "usage: mount <dataset> <mountpoint>\n");
+ return (2);
+ }
+
+ dataset = argv[0];
+ path = argv[1];
+
+ /* try to open the dataset */
+ if ((zhp = zfs_open(dataset, ZFS_TYPE_FILESYSTEM)) == NULL)
+ return (1);
+
+ (void) zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint,
+ sizeof (mountpoint), NULL, NULL, 0, FALSE);
+
+ /* check for legacy mountpoint and complain appropriately */
+ ret = 0;
+ if (strcmp(mountpoint, ZFS_MOUNTPOINT_LEGACY) == 0) {
+ if (mount(dataset, path, MS_OPTIONSTR | flags, MNTTYPE_ZFS,
+ NULL, 0, mntopts, sizeof (mntopts)) != 0) {
+ (void) fprintf(stderr, gettext("mount failed: %s\n"),
+ strerror(errno));
+ ret = 1;
+ }
+ } else {
+ (void) fprintf(stderr, gettext("filesystem '%s' cannot be "
+ "mounted using 'mount -F zfs'\n"), dataset);
+ (void) fprintf(stderr, gettext("Use 'zfs set mountpoint=%s' "
+ "instead.\n"), path);
+ (void) fprintf(stderr, gettext("If you must use 'mount -F zfs' "
+ "or /etc/vfstab, use 'zfs set mountpoint=legacy'.\n"));
+ (void) fprintf(stderr, gettext("See zfs(1M) for more "
+ "information.\n"));
+ ret = 1;
+ }
+
+ return (ret);
+}
+
+/*
+ * Called when invoked as /etc/fs/zfs/umount. Unlike a manual mount, we allow
+ * unmounts of non-legacy filesystems, as this is the dominant administrative
+ * interface.
+ */
+static int
+manual_unmount(int argc, char **argv)
+{
+ int flags = 0;
+ int c;
+
+ /* check options */
+ while ((c = getopt(argc, argv, "f")) != -1) {
+ switch (c) {
+ case 'f':
+ flags = MS_FORCE;
+ break;
+ case '?':
+ (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+ optopt);
+ (void) fprintf(stderr, gettext("usage: unmount [-f] "
+ "<path>\n"));
+ return (2);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* check arguments */
+ if (argc != 1) {
+ if (argc == 0)
+ (void) fprintf(stderr, gettext("missing path "
+ "argument\n"));
+ else
+ (void) fprintf(stderr, gettext("too many arguments\n"));
+ (void) fprintf(stderr, gettext("usage: unmount [-f] <path>\n"));
+ return (2);
+ }
+
+ return (unshare_unmount_path(OP_MOUNT, argv[0], flags, TRUE));
+}
+
+static int
+volcheck(zpool_handle_t *zhp, void *data)
+{
+ int isinit = (int)data;
+
+ if (isinit)
+ return (zpool_create_zvol_links(zhp));
+ else
+ return (zpool_remove_zvol_links(zhp));
+}
+
+/*
+ * Iterate over all pools in the system and either create or destroy /dev/zvol
+ * links, depending on the value of 'isinit'.
+ */
+static int
+do_volcheck(int isinit)
+{
+ return (zpool_iter(volcheck, (void *)isinit) ? 1 : 0);
+}
+
+int
+main(int argc, char **argv)
+{
+ int ret;
+ int i;
+ char *progname;
+ char *cmdname;
+
+ (void) setlocale(LC_ALL, "");
+ (void) textdomain(TEXT_DOMAIN);
+
+ opterr = 0;
+
+ if ((mnttab_file = fopen(MNTTAB, "r")) == NULL) {
+ (void) fprintf(stderr, gettext("internal error: unable to "
+ "open %s\n"), MNTTAB);
+ return (1);
+ }
+
+ /*
+ * This command also doubles as the /etc/fs mount and unmount program.
+ * Determine if we should take this behavior based on argv[0].
+ */
+ progname = basename(argv[0]);
+ if (strcmp(progname, "mount") == 0) {
+ ret = manual_mount(argc, argv);
+ } else if (strcmp(progname, "umount") == 0) {
+ ret = manual_unmount(argc, argv);
+ } else {
+ /*
+ * Make sure the user has specified some command.
+ */
+ if (argc < 2) {
+ (void) fprintf(stderr, gettext("missing command\n"));
+ usage(FALSE);
+ }
+
+ cmdname = argv[1];
+
+ /*
+ * The 'umount' command is an alias for 'unmount'
+ */
+ if (strcmp(cmdname, "umount") == 0)
+ cmdname = "unmount";
+
+ /*
+ * Special case '-?'
+ */
+ if (strcmp(cmdname, "-?") == 0)
+ usage(TRUE);
+
+ /*
+ * 'volinit' and 'volfini' do not appear in the usage message,
+ * so we have to special case them here.
+ */
+ if (strcmp(cmdname, "volinit") == 0)
+ return (do_volcheck(TRUE));
+ else if (strcmp(cmdname, "volfini") == 0)
+ return (do_volcheck(FALSE));
+
+ /*
+ * Run the appropriate command.
+ */
+ for (i = 0; i < NCOMMAND; i++) {
+ if (command_table[i].name == NULL)
+ continue;
+
+ if (strcmp(cmdname, command_table[i].name) == 0) {
+ current_command = &command_table[i];
+ ret = command_table[i].func(argc - 1, argv + 1);
+ break;
+ }
+ }
+
+ if (i == NCOMMAND) {
+ (void) fprintf(stderr, gettext("unrecognized "
+ "command '%s'\n"), cmdname);
+ usage(FALSE);
+ }
+ }
+
+ (void) fclose(mnttab_file);
+
+ /*
+ * The 'ZFS_ABORT' environment variable causes us to dump core on exit
+ * for the purposes of running ::findleaks.
+ */
+ if (getenv("ZFS_ABORT") != NULL) {
+ (void) printf("dumping core by request\n");
+ abort();
+ }
+
+ return (ret);
+}