/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2011, 2018 by Delphix. All rights reserved.
+ * Copyright (c) 2011, 2019 by Delphix. All rights reserved.
* Copyright 2012 Milan Jurik. All rights reserved.
* Copyright (c) 2012, Joyent, Inc. All rights reserved.
* Copyright (c) 2013 Steven Hartland. All rights reserved.
case HELP_PROMOTE:
return (gettext("\tpromote <clone-filesystem>\n"));
case HELP_RECEIVE:
- return (gettext("\treceive [-vnsFu] "
+ return (gettext("\treceive [-vnsFhu] "
"[-o <property>=<value>] ... [-x <property>] ...\n"
"\t <filesystem|volume|snapshot>\n"
- "\treceive [-vnsFu] [-o <property>=<value>] ... "
+ "\treceive [-vnsFhu] [-o <property>=<value>] ... "
"[-x <property>] ... \n"
"\t [-d | -e] <filesystem>\n"
"\treceive -A <filesystem|volume>\n"));
case HELP_ROLLBACK:
return (gettext("\trollback [-rRf] <snapshot>\n"));
case HELP_SEND:
- return (gettext("\tsend [-DnPpRvLecwb] [-[i|I] snapshot] "
+ return (gettext("\tsend [-DnPpRvLecwhb] [-[i|I] snapshot] "
"<snapshot>\n"
"\tsend [-nvPLecw] [-i snapshot|bookmark] "
"<filesystem|volume|snapshot>\n"
return (gettext("\tbookmark <snapshot> <bookmark>\n"));
case HELP_CHANNEL_PROGRAM:
return (gettext("\tprogram [-jn] [-t <instruction limit>] "
- "[-m <memory limit (b)>] <pool> <program file> "
- "[lua args...]\n"));
+ "[-m <memory limit (b)>]\n"
+ "\t <pool> <program file> [lua args...]\n"));
case HELP_LOAD_KEY:
return (gettext("\tload-key [-rn] [-L <keylocation>] "
"<-a | filesystem|volume>\n"));
} while (delim != NULL);
}
- if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET)) == NULL)
+ if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_FILESYSTEM |
+ ZFS_TYPE_SNAPSHOT)) == NULL)
return (1);
+ if (zhp->zfs_head_type != ZFS_TYPE_FILESYSTEM) {
+ (void) fprintf(stderr, gettext("operation is only applicable "
+ "to filesystems and their snapshots\n"));
+ zfs_close(zhp);
+ return (1);
+ }
if ((avl_pool = uu_avl_pool_create("us_avl_pool", sizeof (us_node_t),
offsetof(us_node_t, usn_avlnode), us_compare, UU_DEFAULT)) == NULL)
continue;
cb.cb_prop = p;
- if ((ret = zfs_userspace(zhp, p, userspace_cb, &cb)) != 0)
+ if ((ret = zfs_userspace(zhp, p, userspace_cb, &cb)) != 0) {
+ zfs_close(zhp);
return (ret);
+ }
}
+ zfs_close(zhp);
/* Sort the list */
if ((node = uu_avl_first(avl_tree)) == NULL)
{"compressed", no_argument, NULL, 'c'},
{"raw", no_argument, NULL, 'w'},
{"backup", no_argument, NULL, 'b'},
+ {"holds", no_argument, NULL, 'h'},
{0, 0, 0, 0}
};
/* check options */
- while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLet:cwb", long_options,
+ while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwb", long_options,
NULL)) != -1) {
switch (c) {
case 'i':
case 'b':
flags.backup = B_TRUE;
break;
+ case 'h':
+ flags.holds = B_TRUE;
+ break;
case 'P':
flags.parsable = B_TRUE;
flags.verbose = B_TRUE;
char frombuf[ZFS_MAX_DATASET_NAME_LEN];
if (flags.replicate || flags.doall || flags.props ||
- flags.backup || flags.dedup ||
+ flags.backup || flags.dedup || flags.holds ||
(strchr(argv[0], '@') == NULL &&
(flags.dryrun || flags.verbose || flags.progress))) {
(void) fprintf(stderr, gettext("Error: "
nomem();
/* check options */
- while ((c = getopt(argc, argv, ":o:x:denuvFsA")) != -1) {
+ while ((c = getopt(argc, argv, ":o:x:dehnuvFsA")) != -1) {
switch (c) {
case 'o':
if (!parseprop(props, optarg)) {
}
break;
case 'd':
+ if (flags.istail) {
+ (void) fprintf(stderr, gettext("invalid option "
+ "combination: -d and -e are mutually "
+ "exclusive\n"));
+ usage(B_FALSE);
+ }
flags.isprefix = B_TRUE;
break;
case 'e':
- flags.isprefix = B_TRUE;
+ if (flags.isprefix) {
+ (void) fprintf(stderr, gettext("invalid option "
+ "combination: -d and -e are mutually "
+ "exclusive\n"));
+ usage(B_FALSE);
+ }
flags.istail = B_TRUE;
break;
+ case 'h':
+ flags.skipholds = B_TRUE;
+ break;
case 'n':
flags.dryrun = B_TRUE;
break;
argc -= optind;
argv += optind;
+ /* zfs recv -e (use "tail" name) implies -d (remove dataset "head") */
+ if (flags.istail)
+ flags.isprefix = B_TRUE;
+
/* check number of arguments */
if (argc < 1) {
(void) fprintf(stderr, gettext("missing snapshot argument\n"));
#define CHECK_SPINNER 30
#define SPINNER_TIME 3 /* seconds */
-#define MOUNT_TIME 5 /* seconds */
+#define MOUNT_TIME 1 /* seconds */
+
+typedef struct get_all_state {
+ boolean_t ga_verbose;
+ get_all_cb_t *ga_cbp;
+} get_all_state_t;
static int
get_one_dataset(zfs_handle_t *zhp, void *data)
static int spinval = 0;
static int spincheck = 0;
static time_t last_spin_time = (time_t)0;
- get_all_cb_t *cbp = data;
+ get_all_state_t *state = data;
zfs_type_t type = zfs_get_type(zhp);
- if (cbp->cb_verbose) {
+ if (state->ga_verbose) {
if (--spincheck < 0) {
time_t now = time(NULL);
if (last_spin_time + SPINNER_TIME < now) {
zfs_close(zhp);
return (0);
}
- libzfs_add_handle(cbp, zhp);
- assert(cbp->cb_used <= cbp->cb_alloc);
+ libzfs_add_handle(state->ga_cbp, zhp);
+ assert(state->ga_cbp->cb_used <= state->ga_cbp->cb_alloc);
return (0);
}
static void
-get_all_datasets(zfs_handle_t ***dslist, size_t *count, boolean_t verbose)
+get_all_datasets(get_all_cb_t *cbp, boolean_t verbose)
{
- get_all_cb_t cb = { 0 };
- cb.cb_verbose = verbose;
- cb.cb_getone = get_one_dataset;
+ get_all_state_t state = {
+ .ga_verbose = verbose,
+ .ga_cbp = cbp
+ };
if (verbose)
set_progress_header(gettext("Reading ZFS config"));
- (void) zfs_iter_root(g_zfs, get_one_dataset, &cb);
-
- *dslist = cb.cb_handles;
- *count = cb.cb_used;
+ (void) zfs_iter_root(g_zfs, get_one_dataset, &state);
if (verbose)
finish_progress(gettext("done."));
* 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 enum { OP_SHARE, OP_MOUNT } share_mount_op_t;
+
+typedef struct share_mount_state {
+ share_mount_op_t sm_op;
+ boolean_t sm_verbose;
+ int sm_flags;
+ char *sm_options;
+ char *sm_proto; /* only valid for OP_SHARE */
+ pthread_mutex_t sm_lock; /* protects the remaining fields */
+ uint_t sm_total; /* number of filesystems to process */
+ uint_t sm_done; /* number of filesystems processed */
+ int sm_status; /* -1 if any of the share/mount operations failed */
+} share_mount_state_t;
/*
* Share or mount a dataset.
update_progress(info);
}
+/*
+ * zfs_foreach_mountpoint() callback that mounts or shares one filesystem and
+ * updates the progress meter.
+ */
+static int
+share_mount_one_cb(zfs_handle_t *zhp, void *arg)
+{
+ share_mount_state_t *sms = arg;
+ int ret;
+
+ ret = share_mount_one(zhp, sms->sm_op, sms->sm_flags, sms->sm_proto,
+ B_FALSE, sms->sm_options);
+
+ pthread_mutex_lock(&sms->sm_lock);
+ if (ret != 0)
+ sms->sm_status = ret;
+ sms->sm_done++;
+ if (sms->sm_verbose)
+ report_mount_progress(sms->sm_done, sms->sm_total);
+ pthread_mutex_unlock(&sms->sm_lock);
+ return (ret);
+}
+
static void
append_options(char *mntopts, char *newopts)
{
/* check number of arguments */
if (do_all) {
- zfs_handle_t **dslist = NULL;
- size_t i, count = 0;
char *protocol = NULL;
if (op == OP_SHARE && argc > 0) {
}
start_progress_timer();
- get_all_datasets(&dslist, &count, verbose);
+ get_all_cb_t cb = { 0 };
+ get_all_datasets(&cb, verbose);
- if (count == 0) {
+ if (cb.cb_used == 0) {
if (options != NULL)
free(options);
return (0);
}
- qsort(dslist, count, sizeof (void *), libzfs_dataset_cmp);
-
- for (i = 0; i < count; i++) {
- if (verbose)
- report_mount_progress(i, count);
+ share_mount_state_t share_mount_state = { 0 };
+ share_mount_state.sm_op = op;
+ share_mount_state.sm_verbose = verbose;
+ share_mount_state.sm_flags = flags;
+ share_mount_state.sm_options = options;
+ share_mount_state.sm_proto = protocol;
+ share_mount_state.sm_total = cb.cb_used;
+ pthread_mutex_init(&share_mount_state.sm_lock, NULL);
- if (share_mount_one(dslist[i], op, flags, protocol,
- B_FALSE, options) != 0)
- ret = 1;
- zfs_close(dslist[i]);
- }
+ /*
+ * libshare isn't mt-safe, so only do the operation in parallel
+ * if we're mounting.
+ */
+ zfs_foreach_mountpoint(g_zfs, cb.cb_handles, cb.cb_used,
+ share_mount_one_cb, &share_mount_state, op == OP_MOUNT);
+ ret = share_mount_state.sm_status;
- free(dslist);
+ for (int i = 0; i < cb.cb_used; i++)
+ zfs_close(cb.cb_handles[i]);
+ free(cb.cb_handles);
} else if (argc == 0) {
struct mnttab entry;
return (unshare_unmount(OP_SHARE, argc, argv));
}
+static int
+disable_command_idx(char *command)
+{
+ for (int i = 0; i < NCOMMAND; i++) {
+ if (command_table[i].name == NULL)
+ continue;
+
+ if (strcmp(command, command_table[i].name) == 0) {
+ command_table[i].name = NULL;
+ return (0);
+ }
+ }
+ return (1);
+}
+
static int
find_command_idx(char *command, int *idx)
{
/*
* zfs remap <filesystem | volume>
*
- * Remap the indirect blocks in the given fileystem or volume.
+ * N.B. The remap command has been disabled and may be removed in the future.
+ *
+ * Remap the indirect blocks in the given filesystem or volume so that they no
+ * longer reference blocks on previously removed vdevs and we can eventually
+ * shrink the size of the indirect mapping objects for the previously removed
+ * vdevs. Note that remapping all blocks might not be possible and that
+ * references from snapshots will still exist and cannot be remapped.
+ *
+ * This functionality is no longer particularly useful now that the removal
+ * code can map large chunks. Furthermore, explaining what this command
+ * does and why it may be useful requires a detailed understanding of the
+ * internals of device removal. These are details users should not be
+ * bothered with. If required, the remap command can be re-enabled by
+ * setting the ZFS_REMAP_ENABLED environment variable.
+ *
+ * > ZFS_REMAP_ENABLED=yes zfs remap <filesystem | volume>
*/
static int
zfs_do_remap(int argc, char **argv)
if (strcmp(cmdname, "snap") == 0)
cmdname = "snapshot";
+ /*
+ * The 'remap' command has been disabled and may be removed in the
+ * future. See the comment above zfs_do_remap() for details.
+ */
+ if (!libzfs_envvar_is_set("ZFS_REMAP_ENABLED"))
+ disable_command_idx("remap");
+
/*
* Special case '-?'
*/