X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=cmd%2Fzpool%2Fzpool_main.c;h=1dac4b167f7ce2e4575dfdc2ffc42cec63e493bd;hb=e2dddb7e58f7046dc1bfdbc49db52f298fb7019f;hp=f4873cf196a68d99304e608cf259c2056333b302;hpb=4e33ba4c389f59b74138bf7130e924a4230d64e9;p=mirror_zfs.git diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index f4873cf19..1dac4b167 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -22,10 +22,14 @@ /* * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2011 Nexenta Systems, Inc. All rights reserved. - * Copyright (c) 2011, 2015 by Delphix. All rights reserved. + * Copyright (c) 2011, 2018 by Delphix. All rights reserved. * Copyright (c) 2012 by Frederik Wessels. All rights reserved. * Copyright (c) 2012 by Cyril Plisko. All rights reserved. * Copyright (c) 2013 by Prasad Joshi (sTec). All rights reserved. + * Copyright 2016 Igor Kozhukhov . + * Copyright (c) 2017 Datto Inc. + * Copyright (c) 2017 Open-E, Inc. All Rights Reserved. + * Copyright (c) 2017, Intel Corporation. */ #include @@ -33,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -44,16 +49,22 @@ #include #include #include +#include #include #include #include +#include #include #include #include #include +#include +#include + #include #include +#include #include "zpool_util.h" #include "zfs_comutil.h" @@ -68,6 +79,8 @@ static int zpool_do_add(int, char **); static int zpool_do_remove(int, char **); static int zpool_do_labelclear(int, char **); +static int zpool_do_checkpoint(int, char **); + static int zpool_do_list(int, char **); static int zpool_do_iostat(int, char **); static int zpool_do_status(int, char **); @@ -84,7 +97,10 @@ static int zpool_do_detach(int, char **); static int zpool_do_replace(int, char **); static int zpool_do_split(int, char **); +static int zpool_do_initialize(int, char **); static int zpool_do_scrub(int, char **); +static int zpool_do_resilver(int, char **); +static int zpool_do_trim(int, char **); static int zpool_do_import(int, char **); static int zpool_do_export(int, char **); @@ -97,6 +113,10 @@ static int zpool_do_events(int, char **); static int zpool_do_get(int, char **); static int zpool_do_set(int, char **); +static int zpool_do_sync(int, char **); + +static int zpool_do_version(int, char **); + /* * These libumem hooks provide a reasonable set of defaults for the allocator's * debugging facilities. @@ -121,6 +141,7 @@ typedef enum { HELP_ATTACH, HELP_CLEAR, HELP_CREATE, + HELP_CHECKPOINT, HELP_DESTROY, HELP_DETACH, HELP_EXPORT, @@ -133,15 +154,20 @@ typedef enum { HELP_ONLINE, HELP_REPLACE, HELP_REMOVE, + HELP_INITIALIZE, HELP_SCRUB, + HELP_RESILVER, + HELP_TRIM, HELP_STATUS, HELP_UPGRADE, HELP_EVENTS, HELP_GET, HELP_SET, HELP_SPLIT, + HELP_SYNC, HELP_REGUID, - HELP_REOPEN + HELP_REOPEN, + HELP_VERSION } zpool_help_t; @@ -172,7 +198,7 @@ enum iostat_type { * of all the nvlists a flag requires. Also specifies the order in * which data gets printed in zpool iostat. */ -static const char *vsx_type_to_nvlist[IOS_COUNT][11] = { +static const char *vsx_type_to_nvlist[IOS_COUNT][13] = { [IOS_L_HISTO] = { ZPOOL_CONFIG_VDEV_TOT_R_LAT_HISTO, ZPOOL_CONFIG_VDEV_TOT_W_LAT_HISTO, @@ -183,12 +209,14 @@ static const char *vsx_type_to_nvlist[IOS_COUNT][11] = { ZPOOL_CONFIG_VDEV_ASYNC_R_LAT_HISTO, ZPOOL_CONFIG_VDEV_ASYNC_W_LAT_HISTO, ZPOOL_CONFIG_VDEV_SCRUB_LAT_HISTO, + ZPOOL_CONFIG_VDEV_TRIM_LAT_HISTO, NULL}, [IOS_LATENCY] = { ZPOOL_CONFIG_VDEV_TOT_R_LAT_HISTO, ZPOOL_CONFIG_VDEV_TOT_W_LAT_HISTO, ZPOOL_CONFIG_VDEV_DISK_R_LAT_HISTO, ZPOOL_CONFIG_VDEV_DISK_W_LAT_HISTO, + ZPOOL_CONFIG_VDEV_TRIM_LAT_HISTO, NULL}, [IOS_QUEUES] = { ZPOOL_CONFIG_VDEV_SYNC_R_ACTIVE_QUEUE, @@ -196,6 +224,7 @@ static const char *vsx_type_to_nvlist[IOS_COUNT][11] = { ZPOOL_CONFIG_VDEV_ASYNC_R_ACTIVE_QUEUE, ZPOOL_CONFIG_VDEV_ASYNC_W_ACTIVE_QUEUE, ZPOOL_CONFIG_VDEV_SCRUB_ACTIVE_QUEUE, + ZPOOL_CONFIG_VDEV_TRIM_ACTIVE_QUEUE, NULL}, [IOS_RQ_HISTO] = { ZPOOL_CONFIG_VDEV_SYNC_IND_R_HISTO, @@ -208,6 +237,8 @@ static const char *vsx_type_to_nvlist[IOS_COUNT][11] = { ZPOOL_CONFIG_VDEV_ASYNC_AGG_W_HISTO, ZPOOL_CONFIG_VDEV_IND_SCRUB_HISTO, ZPOOL_CONFIG_VDEV_AGG_SCRUB_HISTO, + ZPOOL_CONFIG_VDEV_IND_TRIM_HISTO, + ZPOOL_CONFIG_VDEV_AGG_TRIM_HISTO, NULL}, }; @@ -235,6 +266,8 @@ typedef struct zpool_command { * the generic usage message. */ static zpool_command_t command_table[] = { + { "version", zpool_do_version, HELP_VERSION }, + { NULL }, { "create", zpool_do_create, HELP_CREATE }, { "destroy", zpool_do_destroy, HELP_DESTROY }, { NULL }, @@ -243,6 +276,8 @@ static zpool_command_t command_table[] = { { NULL }, { "labelclear", zpool_do_labelclear, HELP_LABELCLEAR }, { NULL }, + { "checkpoint", zpool_do_checkpoint, HELP_CHECKPOINT }, + { NULL }, { "list", zpool_do_list, HELP_LIST }, { "iostat", zpool_do_iostat, HELP_IOSTAT }, { "status", zpool_do_status, HELP_STATUS }, @@ -257,7 +292,10 @@ static zpool_command_t command_table[] = { { "replace", zpool_do_replace, HELP_REPLACE }, { "split", zpool_do_split, HELP_SPLIT }, { NULL }, + { "initialize", zpool_do_initialize, HELP_INITIALIZE }, + { "resilver", zpool_do_resilver, HELP_RESILVER }, { "scrub", zpool_do_scrub, HELP_SCRUB }, + { "trim", zpool_do_trim, HELP_TRIM }, { NULL }, { "import", zpool_do_import, HELP_IMPORT }, { "export", zpool_do_export, HELP_EXPORT }, @@ -269,17 +307,21 @@ static zpool_command_t command_table[] = { { NULL }, { "get", zpool_do_get, HELP_GET }, { "set", zpool_do_set, HELP_SET }, + { "sync", zpool_do_sync, HELP_SYNC }, }; #define NCOMMAND (ARRAY_SIZE(command_table)) +#define VDEV_ALLOC_CLASS_LOGS "logs" + static zpool_command_t *current_command; static char history_str[HIS_MAX_RECORD_LEN]; static boolean_t log_history = B_TRUE; static uint_t timestamp_fmt = NODATE; static const char * -get_usage(zpool_help_t idx) { +get_usage(zpool_help_t idx) +{ switch (idx) { case HELP_ADD: return (gettext("\tadd [-fgLnP] [-o property=value] " @@ -293,6 +335,8 @@ get_usage(zpool_help_t idx) { return (gettext("\tcreate [-fnd] [-o property=value] ... \n" "\t [-O file-system-property=value] ... \n" "\t [-m mountpoint] [-R root] ...\n")); + case HELP_CHECKPOINT: + return (gettext("\tcheckpoint [--discard] ...\n")); case HELP_DESTROY: return (gettext("\tdestroy [-f] \n")); case HELP_DETACH: @@ -303,63 +347,101 @@ get_usage(zpool_help_t idx) { return (gettext("\thistory [-il] [] ...\n")); case HELP_IMPORT: return (gettext("\timport [-d dir] [-D]\n" - "\timport [-d dir | -c cachefile] [-F [-n]] \n" "\timport [-o mntopts] [-o property=value] ... \n" - "\t [-d dir | -c cachefile] [-D] [-f] [-m] [-N] " + "\t [-d dir | -c cachefile] [-D] [-l] [-f] [-m] [-N] " "[-R root] [-F [-n]] -a\n" "\timport [-o mntopts] [-o property=value] ... \n" - "\t [-d dir | -c cachefile] [-D] [-f] [-m] [-N] " + "\t [-d dir | -c cachefile] [-D] [-l] [-f] [-m] [-N] " "[-R root] [-F [-n]]\n" - "\t [newpool]\n")); + "\t [--rewind-to-checkpoint] [newpool]\n")); case HELP_IOSTAT: - return (gettext("\tiostat [-c CMD] [-T d | u] [-ghHLpPvy] " - "[[-lq]|[-r|-w]]\n" - "\t [[pool ...]|[pool vdev ...]|[vdev ...]] " - "[interval [count]]\n")); + return (gettext("\tiostat [[[-c [script1,script2,...]" + "[-lq]]|[-rw]] [-T d | u] [-ghHLpPvy]\n" + "\t [[pool ...]|[pool vdev ...]|[vdev ...]]" + " [[-n] interval [count]]\n")); case HELP_LABELCLEAR: return (gettext("\tlabelclear [-f] \n")); case HELP_LIST: return (gettext("\tlist [-gHLpPv] [-o property[,...]] " - "[-T d|u] [pool] ... [interval [count]]\n")); + "[-T d|u] [pool] ... \n" + "\t [interval [count]]\n")); case HELP_OFFLINE: - return (gettext("\toffline [-t] ...\n")); + return (gettext("\toffline [-f] [-t] ...\n")); case HELP_ONLINE: - return (gettext("\tonline ...\n")); + return (gettext("\tonline [-e] ...\n")); case HELP_REPLACE: return (gettext("\treplace [-f] [-o property=value] " " [new-device]\n")); case HELP_REMOVE: - return (gettext("\tremove ...\n")); + return (gettext("\tremove [-nps] ...\n")); case HELP_REOPEN: - return (gettext("\treopen \n")); + return (gettext("\treopen [-n] \n")); + case HELP_INITIALIZE: + return (gettext("\tinitialize [-c | -s] " + "[ ...]\n")); case HELP_SCRUB: - return (gettext("\tscrub [-s] ...\n")); + return (gettext("\tscrub [-s | -p] ...\n")); + case HELP_RESILVER: + return (gettext("\tresilver ...\n")); + case HELP_TRIM: + return (gettext("\ttrim [-dp] [-r ] [-c | -s] " + "[ ...]\n")); case HELP_STATUS: - return (gettext("\tstatus [-c CMD] [-gLPvxD] [-T d|u] [pool]" - " ... [interval [count]]\n")); + return (gettext("\tstatus [-c [script1,script2,...]] " + "[-igLpPsvxD] [-T d|u] [pool] ... \n" + "\t [interval [count]]\n")); case HELP_UPGRADE: return (gettext("\tupgrade\n" "\tupgrade -v\n" "\tupgrade [-V version] <-a | pool ...>\n")); case HELP_EVENTS: - return (gettext("\tevents [-vHfc]\n")); + return (gettext("\tevents [-vHf [pool] | -c]\n")); case HELP_GET: return (gettext("\tget [-Hp] [-o \"all\" | field[,...]] " "<\"all\" | property[,...]> ...\n")); case HELP_SET: return (gettext("\tset \n")); case HELP_SPLIT: - return (gettext("\tsplit [-gLnP] [-R altroot] [-o mntopts]\n" + return (gettext("\tsplit [-gLnPl] [-R altroot] [-o mntopts]\n" "\t [-o property=value] " "[ ...]\n")); case HELP_REGUID: return (gettext("\treguid \n")); + case HELP_SYNC: + return (gettext("\tsync [pool] ...\n")); + case HELP_VERSION: + return (gettext("\tversion\n")); } abort(); /* NOTREACHED */ } +static void +zpool_collect_leaves(zpool_handle_t *zhp, nvlist_t *nvroot, nvlist_t *res) +{ + uint_t children = 0; + nvlist_t **child; + uint_t i; + + (void) nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN, + &child, &children); + + if (children == 0) { + char *path = zpool_vdev_name(g_zfs, zhp, nvroot, + VDEV_NAME_PATH); + + if (strcmp(path, VDEV_TYPE_INDIRECT) != 0) + fnvlist_add_boolean(res, path); + + free(path); + return; + } + + for (i = 0; i < children; i++) { + zpool_collect_leaves(zhp, child[i], res); + } +} /* * Callback routine that will print out a pool property value. @@ -369,7 +451,7 @@ print_prop_cb(int prop, void *cb) { FILE *fp = cb; - (void) fprintf(fp, "\t%-15s ", zpool_prop_to_name(prop)); + (void) fprintf(fp, "\t%-19s ", zpool_prop_to_name(prop)); if (zpool_prop_readonly(prop)) (void) fprintf(fp, " NO "); @@ -421,14 +503,14 @@ usage(boolean_t requested) (void) fprintf(fp, gettext("\nthe following properties are supported:\n")); - (void) fprintf(fp, "\n\t%-15s %s %s\n\n", + (void) fprintf(fp, "\n\t%-19s %s %s\n\n", "PROPERTY", "EDIT", "VALUES"); /* Iterate over all properties */ (void) zprop_iter(print_prop_cb, fp, B_FALSE, B_TRUE, ZFS_TYPE_POOL); - (void) fprintf(fp, "\t%-15s ", "feature@..."); + (void) fprintf(fp, "\t%-19s ", "feature@..."); (void) fprintf(fp, "YES disabled | enabled | active\n"); (void) fprintf(fp, gettext("\nThe feature@ properties must be " @@ -446,32 +528,137 @@ usage(boolean_t requested) exit(requested ? 0 : 2); } -void +/* + * zpool initialize [-c | -s] [ ...] + * Initialize all unused blocks in the specified vdevs, or all vdevs in the pool + * if none specified. + * + * -c Cancel. Ends active initializing. + * -s Suspend. Initializing can then be restarted with no flags. + */ +int +zpool_do_initialize(int argc, char **argv) +{ + int c; + char *poolname; + zpool_handle_t *zhp; + nvlist_t *vdevs; + int err = 0; + + struct option long_options[] = { + {"cancel", no_argument, NULL, 'c'}, + {"suspend", no_argument, NULL, 's'}, + {0, 0, 0, 0} + }; + + pool_initialize_func_t cmd_type = POOL_INITIALIZE_START; + while ((c = getopt_long(argc, argv, "cs", long_options, NULL)) != -1) { + switch (c) { + case 'c': + if (cmd_type != POOL_INITIALIZE_START && + cmd_type != POOL_INITIALIZE_CANCEL) { + (void) fprintf(stderr, gettext("-c cannot be " + "combined with other options\n")); + usage(B_FALSE); + } + cmd_type = POOL_INITIALIZE_CANCEL; + break; + case 's': + if (cmd_type != POOL_INITIALIZE_START && + cmd_type != POOL_INITIALIZE_SUSPEND) { + (void) fprintf(stderr, gettext("-s cannot be " + "combined with other options\n")); + usage(B_FALSE); + } + cmd_type = POOL_INITIALIZE_SUSPEND; + break; + case '?': + if (optopt != 0) { + (void) fprintf(stderr, + gettext("invalid option '%c'\n"), optopt); + } else { + (void) fprintf(stderr, + gettext("invalid option '%s'\n"), + argv[optind - 1]); + } + usage(B_FALSE); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1) { + (void) fprintf(stderr, gettext("missing pool name argument\n")); + usage(B_FALSE); + return (-1); + } + + poolname = argv[0]; + zhp = zpool_open(g_zfs, poolname); + if (zhp == NULL) + return (-1); + + vdevs = fnvlist_alloc(); + if (argc == 1) { + /* no individual leaf vdevs specified, so add them all */ + nvlist_t *config = zpool_get_config(zhp, NULL); + nvlist_t *nvroot = fnvlist_lookup_nvlist(config, + ZPOOL_CONFIG_VDEV_TREE); + zpool_collect_leaves(zhp, nvroot, vdevs); + } else { + for (int i = 1; i < argc; i++) { + fnvlist_add_boolean(vdevs, argv[i]); + } + } + + err = zpool_initialize(zhp, cmd_type, vdevs); + + fnvlist_free(vdevs); + zpool_close(zhp); + + return (err); +} + +/* + * print a pool vdev config for dry runs + */ +static void print_vdev_tree(zpool_handle_t *zhp, const char *name, nvlist_t *nv, int indent, - boolean_t print_logs, int name_flags) + const char *match, int name_flags) { nvlist_t **child; uint_t c, children; char *vname; - - if (name != NULL) - (void) printf("\t%*s%s\n", indent, "", name); + boolean_t printed = B_FALSE; if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN, - &child, &children) != 0) + &child, &children) != 0) { + if (name != NULL) + (void) printf("\t%*s%s\n", indent, "", name); return; + } for (c = 0; c < children; c++) { uint64_t is_log = B_FALSE; + char *class = ""; (void) nvlist_lookup_uint64(child[c], ZPOOL_CONFIG_IS_LOG, &is_log); - if ((is_log && !print_logs) || (!is_log && print_logs)) + if (is_log) + class = VDEV_ALLOC_BIAS_LOG; + (void) nvlist_lookup_string(child[c], + ZPOOL_CONFIG_ALLOCATION_BIAS, &class); + if (strcmp(match, class) != 0) continue; + if (!printed && name != NULL) { + (void) printf("\t%*s%s\n", indent, "", name); + printed = B_TRUE; + } vname = zpool_vdev_name(g_zfs, zhp, child[c], name_flags); - print_vdev_tree(zhp, vname, child[c], indent + 2, - B_FALSE, name_flags); + print_vdev_tree(zhp, vname, child[c], indent + 2, "", + name_flags); free(vname); } } @@ -495,8 +682,7 @@ static int add_prop_list(const char *propname, char *propval, nvlist_t **props, boolean_t poolprop) { - zpool_prop_t prop = ZPROP_INVAL; - zfs_prop_t fprop; + zpool_prop_t prop = ZPOOL_PROP_INVAL; nvlist_t *proplist; const char *normnm; char *strval; @@ -513,7 +699,7 @@ add_prop_list(const char *propname, char *propval, nvlist_t **props, if (poolprop) { const char *vname = zpool_prop_to_name(ZPOOL_PROP_VERSION); - if ((prop = zpool_name_to_prop(propname)) == ZPROP_INVAL && + if ((prop = zpool_name_to_prop(propname)) == ZPOOL_PROP_INVAL && !zpool_prop_feature(propname)) { (void) fprintf(stderr, gettext("property '%s' is " "not a valid pool property\n"), propname); @@ -524,7 +710,7 @@ add_prop_list(const char *propname, char *propval, nvlist_t **props, * feature@ properties and version should not be specified * at the same time. */ - if ((prop == ZPROP_INVAL && zpool_prop_feature(propname) && + if ((prop == ZPOOL_PROP_INVAL && zpool_prop_feature(propname) && nvlist_exists(proplist, vname)) || (prop == ZPOOL_PROP_VERSION && prop_list_contains_feature(proplist))) { @@ -540,10 +726,18 @@ add_prop_list(const char *propname, char *propval, nvlist_t **props, else normnm = zpool_prop_to_name(prop); } else { - if ((fprop = zfs_name_to_prop(propname)) != ZPROP_INVAL) { - normnm = zfs_prop_to_name(fprop); - } else { + zfs_prop_t fsprop = zfs_name_to_prop(propname); + + if (zfs_prop_valid_for_type(fsprop, ZFS_TYPE_FILESYSTEM, + B_FALSE)) { + normnm = zfs_prop_to_name(fsprop); + } else if (zfs_prop_user(propname) || + zfs_prop_userquota(propname)) { normnm = propname; + } else { + (void) fprintf(stderr, gettext("property '%s' is " + "not a valid filesystem property\n"), propname); + return (2); } } @@ -674,6 +868,20 @@ zpool_do_add(int argc, char **argv) return (1); } + /* unless manually specified use "ashift" pool property (if set) */ + if (!nvlist_exists(props, ZPOOL_CONFIG_ASHIFT)) { + int intval; + zprop_source_t src; + char strval[ZPOOL_MAXPROPLEN]; + + intval = zpool_get_prop_int(zhp, ZPOOL_PROP_ASHIFT, &src); + if (src != ZPROP_SRC_DEFAULT) { + (void) sprintf(strval, "%" PRId32, intval); + verify(add_prop_list(ZPOOL_CONFIG_ASHIFT, strval, + &props, B_TRUE) == 0); + } + } + /* pass off to get_vdev_spec for processing */ nvroot = make_root_vdev(zhp, props, force, !force, B_FALSE, dryrun, argc, argv); @@ -696,20 +904,25 @@ zpool_do_add(int argc, char **argv) "configuration:\n"), zpool_get_name(zhp)); /* print original main pool and new tree */ - print_vdev_tree(zhp, poolname, poolnvroot, 0, B_FALSE, + print_vdev_tree(zhp, poolname, poolnvroot, 0, "", + name_flags | VDEV_NAME_TYPE_ID); + print_vdev_tree(zhp, NULL, nvroot, 0, "", name_flags); + + /* print other classes: 'dedup', 'special', and 'log' */ + print_vdev_tree(zhp, "dedup", poolnvroot, 0, + VDEV_ALLOC_BIAS_DEDUP, name_flags); + print_vdev_tree(zhp, NULL, nvroot, 0, VDEV_ALLOC_BIAS_DEDUP, name_flags); - print_vdev_tree(zhp, NULL, nvroot, 0, B_FALSE, name_flags); - /* Do the same for the logs */ - if (num_logs(poolnvroot) > 0) { - print_vdev_tree(zhp, "logs", poolnvroot, 0, B_TRUE, - name_flags); - print_vdev_tree(zhp, NULL, nvroot, 0, B_TRUE, - name_flags); - } else if (num_logs(nvroot) > 0) { - print_vdev_tree(zhp, "logs", nvroot, 0, B_TRUE, - name_flags); - } + print_vdev_tree(zhp, "special", poolnvroot, 0, + VDEV_ALLOC_BIAS_SPECIAL, name_flags); + print_vdev_tree(zhp, NULL, nvroot, 0, VDEV_ALLOC_BIAS_SPECIAL, + name_flags); + + print_vdev_tree(zhp, "logs", poolnvroot, 0, VDEV_ALLOC_BIAS_LOG, + name_flags); + print_vdev_tree(zhp, NULL, nvroot, 0, VDEV_ALLOC_BIAS_LOG, + name_flags); /* Do the same for the caches */ if (nvlist_lookup_nvlist_array(poolnvroot, ZPOOL_CONFIG_L2CACHE, @@ -750,8 +963,7 @@ zpool_do_add(int argc, char **argv) /* * zpool remove ... * - * Removes the given vdev from the pool. Currently, this supports removing - * spares, cache, and log devices from the pool. + * Removes the given vdev from the pool. */ int zpool_do_remove(int argc, char **argv) @@ -759,28 +971,87 @@ zpool_do_remove(int argc, char **argv) char *poolname; int i, ret = 0; zpool_handle_t *zhp = NULL; + boolean_t stop = B_FALSE; + char c; + boolean_t noop = B_FALSE; + boolean_t parsable = B_FALSE; - argc--; - argv++; + /* check options */ + while ((c = getopt(argc, argv, "nps")) != -1) { + switch (c) { + case 'n': + noop = B_TRUE; + break; + case 'p': + parsable = B_TRUE; + break; + case 's': + stop = B_TRUE; + break; + case '?': + (void) fprintf(stderr, gettext("invalid option '%c'\n"), + optopt); + usage(B_FALSE); + } + } + + argc -= optind; + argv += optind; /* get pool name and check number of arguments */ if (argc < 1) { (void) fprintf(stderr, gettext("missing pool name argument\n")); usage(B_FALSE); } - if (argc < 2) { - (void) fprintf(stderr, gettext("missing device\n")); - usage(B_FALSE); - } poolname = argv[0]; if ((zhp = zpool_open(g_zfs, poolname)) == NULL) return (1); - for (i = 1; i < argc; i++) { - if (zpool_vdev_remove(zhp, argv[i]) != 0) + if (stop && noop) { + (void) fprintf(stderr, gettext("stop request ignored\n")); + return (0); + } + + if (stop) { + if (argc > 1) { + (void) fprintf(stderr, gettext("too many arguments\n")); + usage(B_FALSE); + } + if (zpool_vdev_remove_cancel(zhp) != 0) ret = 1; + } else { + if (argc < 2) { + (void) fprintf(stderr, gettext("missing device\n")); + usage(B_FALSE); + } + + for (i = 1; i < argc; i++) { + if (noop) { + uint64_t size; + + if (zpool_vdev_indirect_size(zhp, argv[i], + &size) != 0) { + ret = 1; + break; + } + if (parsable) { + (void) printf("%s %llu\n", + argv[i], (unsigned long long)size); + } else { + char valstr[32]; + zfs_nicenum(size, valstr, + sizeof (valstr)); + (void) printf("Memory that will be " + "used after removing %s: %s\n", + argv[i], valstr); + } + } else { + if (zpool_vdev_remove(zhp, argv[i]) != 0) + ret = 1; + } + } } zpool_close(zhp); @@ -788,7 +1059,10 @@ zpool_do_remove(int argc, char **argv) } /* - * zpool labelclear + * zpool labelclear [-f] + * + * -f Force clearing the label for the vdevs which are members of + * the exported or foreign pools. * * Verifies that the vdev is not active and zeros out the label information * on the device. @@ -796,8 +1070,11 @@ zpool_do_remove(int argc, char **argv) int zpool_do_labelclear(int argc, char **argv) { - char *vdev, *name; + char vdev[MAXPATHLEN]; + char *name = NULL; + struct stat st; int c, fd = -1, ret = 0; + nvlist_t *config; pool_state_t state; boolean_t inuse = B_FALSE; boolean_t force = B_FALSE; @@ -820,90 +1097,118 @@ zpool_do_labelclear(int argc, char **argv) /* get vdev name */ if (argc < 1) { - (void) fprintf(stderr, gettext("missing vdev device name\n")); + (void) fprintf(stderr, gettext("missing vdev name\n")); usage(B_FALSE); } - - vdev = argv[0]; - if ((fd = open(vdev, O_RDWR)) < 0) { - (void) fprintf(stderr, gettext("Unable to open %s\n"), vdev); - return (B_FALSE); + if (argc > 1) { + (void) fprintf(stderr, gettext("too many arguments\n")); + usage(B_FALSE); } - name = NULL; - if (zpool_in_use(g_zfs, fd, &state, &name, &inuse) != 0) { - if (force) - goto wipe_label; + /* + * Check if we were given absolute path and use it as is. + * Otherwise if the provided vdev name doesn't point to a file, + * try prepending expected disk paths and partition numbers. + */ + (void) strlcpy(vdev, argv[0], sizeof (vdev)); + if (vdev[0] != '/' && stat(vdev, &st) != 0) { + int error; + + error = zfs_resolve_shortname(argv[0], vdev, MAXPATHLEN); + if (error == 0 && zfs_dev_is_whole_disk(vdev)) { + if (zfs_append_partition(vdev, MAXPATHLEN) == -1) + error = ENOENT; + } - (void) fprintf(stderr, - gettext("Unable to determine pool state for %s\n" - "Use -f to force the clearing any label data\n"), vdev); + if (error || (stat(vdev, &st) != 0)) { + (void) fprintf(stderr, gettext( + "failed to find device %s, try specifying absolute " + "path instead\n"), argv[0]); + return (1); + } + } + if ((fd = open(vdev, O_RDWR)) < 0) { + (void) fprintf(stderr, gettext("failed to open %s: %s\n"), + vdev, strerror(errno)); return (1); } - if (inuse) { - switch (state) { - default: - case POOL_STATE_ACTIVE: - case POOL_STATE_SPARE: - case POOL_STATE_L2CACHE: - (void) fprintf(stderr, - gettext("labelclear operation failed.\n" - "\tVdev %s is a member (%s), of pool \"%s\".\n" - "\tTo remove label information from this device, " - "export or destroy\n\tthe pool, or remove %s from " - "the configuration of this pool\n\tand retry the " - "labelclear operation.\n"), - vdev, zpool_pool_state_to_name(state), name, vdev); - ret = 1; - goto errout; + /* + * Flush all dirty pages for the block device. This should not be + * fatal when the device does not support BLKFLSBUF as would be the + * case for a file vdev. + */ + if ((ioctl(fd, BLKFLSBUF) != 0) && (errno != ENOTTY)) + (void) fprintf(stderr, gettext("failed to invalidate " + "cache for %s: %s\n"), vdev, strerror(errno)); - case POOL_STATE_EXPORTED: - if (force) - break; + if (zpool_read_label(fd, &config, NULL) != 0) { + (void) fprintf(stderr, + gettext("failed to read label from %s\n"), vdev); + ret = 1; + goto errout; + } + nvlist_free(config); - (void) fprintf(stderr, - gettext("labelclear operation failed.\n\tVdev " - "%s is a member of the exported pool \"%s\".\n" - "\tUse \"zpool labelclear -f %s\" to force the " - "removal of label\n\tinformation.\n"), - vdev, name, vdev); - ret = 1; - goto errout; + ret = zpool_in_use(g_zfs, fd, &state, &name, &inuse); + if (ret != 0) { + (void) fprintf(stderr, + gettext("failed to check state for %s\n"), vdev); + ret = 1; + goto errout; + } - case POOL_STATE_POTENTIALLY_ACTIVE: - if (force) - break; + if (!inuse) + goto wipe_label; - (void) fprintf(stderr, - gettext("labelclear operation failed.\n" - "\tVdev %s is a member of the pool \"%s\".\n" - "\tThis pool is unknown to this system, but may " - "be active on\n\tanother system. Use " - "\'zpool labelclear -f %s\' to force the\n" - "\tremoval of label information.\n"), - vdev, name, vdev); - ret = 1; - goto errout; + switch (state) { + default: + case POOL_STATE_ACTIVE: + case POOL_STATE_SPARE: + case POOL_STATE_L2CACHE: + (void) fprintf(stderr, gettext( + "%s is a member (%s) of pool \"%s\"\n"), + vdev, zpool_pool_state_to_name(state), name); + ret = 1; + goto errout; - case POOL_STATE_DESTROYED: - /* inuse should never be set for a destroyed pool... */ + case POOL_STATE_EXPORTED: + if (force) break; - } + (void) fprintf(stderr, gettext( + "use '-f' to override the following error:\n" + "%s is a member of exported pool \"%s\"\n"), + vdev, name); + ret = 1; + goto errout; + + case POOL_STATE_POTENTIALLY_ACTIVE: + if (force) + break; + (void) fprintf(stderr, gettext( + "use '-f' to override the following error:\n" + "%s is a member of potentially active pool \"%s\"\n"), + vdev, name); + ret = 1; + goto errout; + + case POOL_STATE_DESTROYED: + /* inuse should never be set for a destroyed pool */ + assert(0); + break; } wipe_label: - if (zpool_clear_label(fd) != 0) { + ret = zpool_clear_label(fd); + if (ret != 0) { (void) fprintf(stderr, - gettext("Label clear failed on vdev %s\n"), vdev); - ret = 1; + gettext("failed to clear label for %s\n"), vdev); } errout: - close(fd); - if (name != NULL) - free(name); + free(name); + (void) close(fd); return (ret); } @@ -1180,9 +1485,13 @@ zpool_do_create(int argc, char **argv) (void) printf(gettext("would create '%s' with the " "following layout:\n\n"), poolname); - print_vdev_tree(NULL, poolname, nvroot, 0, B_FALSE, 0); - if (num_logs(nvroot) > 0) - print_vdev_tree(NULL, "logs", nvroot, 0, B_TRUE, 0); + print_vdev_tree(NULL, poolname, nvroot, 0, "", 0); + print_vdev_tree(NULL, "dedup", nvroot, 0, + VDEV_ALLOC_BIAS_DEDUP, 0); + print_vdev_tree(NULL, "special", nvroot, 0, + VDEV_ALLOC_BIAS_SPECIAL, 0); + print_vdev_tree(NULL, "logs", nvroot, 0, + VDEV_ALLOC_BIAS_LOG, 0); ret = 0; } else { @@ -1506,24 +1815,182 @@ typedef struct status_cbdata { int cb_namewidth; boolean_t cb_allpools; boolean_t cb_verbose; + boolean_t cb_literal; boolean_t cb_explain; boolean_t cb_first; boolean_t cb_dedup_stats; boolean_t cb_print_status; + boolean_t cb_print_slow_ios; + boolean_t cb_print_vdev_init; + boolean_t cb_print_vdev_trim; vdev_cmd_data_list_t *vcdl; } status_cbdata_t; -/* Print output line for specific vdev in a specific pool */ +/* Return 1 if string is NULL, empty, or whitespace; return 0 otherwise. */ +static int +is_blank_str(char *str) +{ + while (str != NULL && *str != '\0') { + if (!isblank(*str)) + return (0); + str++; + } + return (1); +} + +/* Print command output lines for specific vdev in a specific pool */ static void zpool_print_cmd(vdev_cmd_data_list_t *vcdl, const char *pool, char *path) { - int i; + vdev_cmd_data_t *data; + int i, j; + char *val; + for (i = 0; i < vcdl->count; i++) { - if ((strcmp(vcdl->data[i].path, path) == 0) && - (strcmp(vcdl->data[i].pool, pool) == 0)) { - printf("%s", vcdl->data[i].line); - break; + if ((strcmp(vcdl->data[i].path, path) != 0) || + (strcmp(vcdl->data[i].pool, pool) != 0)) { + /* Not the vdev we're looking for */ + continue; } + + data = &vcdl->data[i]; + /* Print out all the output values for this vdev */ + for (j = 0; j < vcdl->uniq_cols_cnt; j++) { + val = NULL; + /* Does this vdev have values for this column? */ + for (int k = 0; k < data->cols_cnt; k++) { + if (strcmp(data->cols[k], + vcdl->uniq_cols[j]) == 0) { + /* yes it does, record the value */ + val = data->lines[k]; + break; + } + } + /* + * Mark empty values with dashes to make output + * awk-able. + */ + if (is_blank_str(val)) + val = "-"; + + printf("%*s", vcdl->uniq_cols_width[j], val); + if (j < vcdl->uniq_cols_cnt - 1) + printf(" "); + } + + /* Print out any values that aren't in a column at the end */ + for (j = data->cols_cnt; j < data->lines_cnt; j++) { + /* Did we have any columns? If so print a spacer. */ + if (vcdl->uniq_cols_cnt > 0) + printf(" "); + + val = data->lines[j]; + printf("%s", val ? val : ""); + } + break; + } +} + +/* + * Print vdev initialization status for leaves + */ +static void +print_status_initialize(vdev_stat_t *vs, boolean_t verbose) +{ + if (verbose) { + if ((vs->vs_initialize_state == VDEV_INITIALIZE_ACTIVE || + vs->vs_initialize_state == VDEV_INITIALIZE_SUSPENDED || + vs->vs_initialize_state == VDEV_INITIALIZE_COMPLETE) && + !vs->vs_scan_removing) { + char zbuf[1024]; + char tbuf[256]; + struct tm zaction_ts; + + time_t t = vs->vs_initialize_action_time; + int initialize_pct = 100; + if (vs->vs_initialize_state != + VDEV_INITIALIZE_COMPLETE) { + initialize_pct = (vs->vs_initialize_bytes_done * + 100 / (vs->vs_initialize_bytes_est + 1)); + } + + (void) localtime_r(&t, &zaction_ts); + (void) strftime(tbuf, sizeof (tbuf), "%c", &zaction_ts); + + switch (vs->vs_initialize_state) { + case VDEV_INITIALIZE_SUSPENDED: + (void) snprintf(zbuf, sizeof (zbuf), ", %s %s", + gettext("suspended, started at"), tbuf); + break; + case VDEV_INITIALIZE_ACTIVE: + (void) snprintf(zbuf, sizeof (zbuf), ", %s %s", + gettext("started at"), tbuf); + break; + case VDEV_INITIALIZE_COMPLETE: + (void) snprintf(zbuf, sizeof (zbuf), ", %s %s", + gettext("completed at"), tbuf); + break; + } + + (void) printf(gettext(" (%d%% initialized%s)"), + initialize_pct, zbuf); + } else { + (void) printf(gettext(" (uninitialized)")); + } + } else if (vs->vs_initialize_state == VDEV_INITIALIZE_ACTIVE) { + (void) printf(gettext(" (initializing)")); + } +} + +/* + * Print vdev TRIM status for leaves + */ +static void +print_status_trim(vdev_stat_t *vs, boolean_t verbose) +{ + if (verbose) { + if ((vs->vs_trim_state == VDEV_TRIM_ACTIVE || + vs->vs_trim_state == VDEV_TRIM_SUSPENDED || + vs->vs_trim_state == VDEV_TRIM_COMPLETE) && + !vs->vs_scan_removing) { + char zbuf[1024]; + char tbuf[256]; + struct tm zaction_ts; + + time_t t = vs->vs_trim_action_time; + int trim_pct = 100; + if (vs->vs_trim_state != VDEV_TRIM_COMPLETE) { + trim_pct = (vs->vs_trim_bytes_done * + 100 / (vs->vs_trim_bytes_est + 1)); + } + + (void) localtime_r(&t, &zaction_ts); + (void) strftime(tbuf, sizeof (tbuf), "%c", &zaction_ts); + + switch (vs->vs_trim_state) { + case VDEV_TRIM_SUSPENDED: + (void) snprintf(zbuf, sizeof (zbuf), ", %s %s", + gettext("suspended, started at"), tbuf); + break; + case VDEV_TRIM_ACTIVE: + (void) snprintf(zbuf, sizeof (zbuf), ", %s %s", + gettext("started at"), tbuf); + break; + case VDEV_TRIM_COMPLETE: + (void) snprintf(zbuf, sizeof (zbuf), ", %s %s", + gettext("completed at"), tbuf); + break; + } + + (void) printf(gettext(" (%d%% trimmed%s)"), + trim_pct, zbuf); + } else if (vs->vs_trim_notsup) { + (void) printf(gettext(" (trim unsupported)")); + } else { + (void) printf(gettext(" (untrimmed)")); + } + } else if (vs->vs_trim_state == VDEV_TRIM_ACTIVE) { + (void) printf(gettext(" (trimming)")); } } @@ -1534,7 +2001,7 @@ static void print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name, nvlist_t *nv, int depth, boolean_t isspare) { - nvlist_t **child; + nvlist_t **child, *root; uint_t c, children; pool_scan_stat_t *ps = NULL; vdev_stat_t *vs; @@ -1542,7 +2009,8 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name, char *vname; uint64_t notpresent; spare_cbdata_t spare_cb; - char *state; + const char *state; + char *type; char *path = NULL; if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN, @@ -1552,6 +2020,11 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name, verify(nvlist_lookup_uint64_array(nv, ZPOOL_CONFIG_VDEV_STATS, (uint64_t **)&vs, &c) == 0); + verify(nvlist_lookup_string(nv, ZPOOL_CONFIG_TYPE, &type) == 0); + + if (strcmp(type, VDEV_TYPE_INDIRECT) == 0) + return; + state = zpool_state_to_name(vs->vs_state, vs->vs_aux); if (isspare) { /* @@ -1568,10 +2041,34 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name, name, state); if (!isspare) { - zfs_nicenum(vs->vs_read_errors, rbuf, sizeof (rbuf)); - zfs_nicenum(vs->vs_write_errors, wbuf, sizeof (wbuf)); - zfs_nicenum(vs->vs_checksum_errors, cbuf, sizeof (cbuf)); - (void) printf(" %5s %5s %5s", rbuf, wbuf, cbuf); + if (cb->cb_literal) { + printf(" %5llu %5llu %5llu", + (u_longlong_t)vs->vs_read_errors, + (u_longlong_t)vs->vs_write_errors, + (u_longlong_t)vs->vs_checksum_errors); + } else { + zfs_nicenum(vs->vs_read_errors, rbuf, sizeof (rbuf)); + zfs_nicenum(vs->vs_write_errors, wbuf, sizeof (wbuf)); + zfs_nicenum(vs->vs_checksum_errors, cbuf, + sizeof (cbuf)); + printf(" %5s %5s %5s", rbuf, wbuf, cbuf); + } + + if (cb->cb_print_slow_ios) { + if (children == 0) { + /* Only leafs vdevs have slow IOs */ + zfs_nicenum(vs->vs_slow_ios, rbuf, + sizeof (rbuf)); + } else { + snprintf(rbuf, sizeof (rbuf), "-"); + } + + if (cb->cb_literal) + printf(" %5llu", (u_longlong_t)vs->vs_slow_ios); + else + printf(" %5s", rbuf); + } + } if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_NOT_PRESENT, @@ -1640,20 +2137,34 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name, (void) printf(gettext("split into new pool")); break; + case VDEV_AUX_ACTIVE: + (void) printf(gettext("currently in use")); + break; + + case VDEV_AUX_CHILDREN_OFFLINE: + (void) printf(gettext("all children offline")); + break; + default: (void) printf(gettext("corrupted data")); break; } } - (void) nvlist_lookup_uint64_array(nv, ZPOOL_CONFIG_SCAN_STATS, + /* The root vdev has the scrub/resilver stats */ + root = fnvlist_lookup_nvlist(zpool_get_config(zhp, NULL), + ZPOOL_CONFIG_VDEV_TREE); + (void) nvlist_lookup_uint64_array(root, ZPOOL_CONFIG_SCAN_STATS, (uint64_t **)&ps, &c); - if (ps && ps->pss_state == DSS_SCANNING && - vs->vs_scan_processed != 0 && children == 0) { - (void) printf(gettext(" (%s)"), - (ps->pss_func == POOL_SCAN_RESILVER) ? - "resilvering" : "repairing"); + if (ps != NULL && ps->pss_state == DSS_SCANNING && children == 0) { + if (vs->vs_scan_processed != 0) { + (void) printf(gettext(" (%s)"), + (ps->pss_func == POOL_SCAN_RESILVER) ? + "resilvering" : "repairing"); + } else if (vs->vs_resilver_deferred) { + (void) printf(gettext(" (awaiting resilver)")); + } } if (cb->vcdl != NULL) { @@ -1663,6 +2174,12 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name, } } + /* Display vdev initialization and trim status for leaves */ + if (children == 0) { + print_status_initialize(vs, cb->cb_print_vdev_init); + print_status_trim(vs, cb->cb_print_vdev_trim); + } + (void) printf("\n"); for (c = 0; c < children; c++) { @@ -1675,8 +2192,13 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name, &ishole); if (islog || ishole) continue; + /* Only print normal classes here */ + if (nvlist_exists(child[c], ZPOOL_CONFIG_ALLOCATION_BIAS)) + continue; + vname = zpool_vdev_name(g_zfs, zhp, child[c], cb->cb_name_flags | VDEV_NAME_TYPE_ID); + print_status_config(zhp, cb, vname, child[c], depth + 2, isspare); free(vname); @@ -1735,6 +2257,14 @@ print_import_config(status_cbdata_t *cb, const char *name, nvlist_t *nv, (void) printf(gettext("too many errors")); break; + case VDEV_AUX_ACTIVE: + (void) printf(gettext("currently in use")); + break; + + case VDEV_AUX_CHILDREN_OFFLINE: + (void) printf(gettext("all children offline")); + break; + default: (void) printf(gettext("corrupted data")); break; @@ -1753,6 +2283,8 @@ print_import_config(status_cbdata_t *cb, const char *name, nvlist_t *nv, &is_log); if (is_log) continue; + if (nvlist_exists(child[c], ZPOOL_CONFIG_ALLOCATION_BIAS)) + continue; vname = zpool_vdev_name(g_zfs, NULL, child[c], cb->cb_name_flags | VDEV_NAME_TYPE_ID); @@ -1784,34 +2316,56 @@ print_import_config(status_cbdata_t *cb, const char *name, nvlist_t *nv, } /* - * Print log vdevs. - * Logs are recorded as top level vdevs in the main pool child array - * but with "is_log" set to 1. We use either print_status_config() or - * print_import_config() to print the top level logs then any log - * children (eg mirrored slogs) are printed recursively - which - * works because only the top level vdev is marked "is_log" + * Print specialized class vdevs. + * + * These are recorded as top level vdevs in the main pool child array + * but with "is_log" set to 1 or an "alloc_bias" string. We use either + * print_status_config() or print_import_config() to print the top level + * class vdevs then any of their children (eg mirrored slogs) are printed + * recursively - which works because only the top level vdev is marked. */ static void -print_logs(zpool_handle_t *zhp, status_cbdata_t *cb, nvlist_t *nv) +print_class_vdevs(zpool_handle_t *zhp, status_cbdata_t *cb, nvlist_t *nv, + const char *class) { uint_t c, children; nvlist_t **child; + boolean_t printed = B_FALSE; + + assert(zhp != NULL || !cb->cb_verbose); if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN, &child, &children) != 0) return; - (void) printf(gettext("\tlogs\n")); - for (c = 0; c < children; c++) { uint64_t is_log = B_FALSE; - char *name; + char *bias = NULL; + char *type = NULL; (void) nvlist_lookup_uint64(child[c], ZPOOL_CONFIG_IS_LOG, &is_log); - if (!is_log) + + if (is_log) { + bias = VDEV_ALLOC_CLASS_LOGS; + } else { + (void) nvlist_lookup_string(child[c], + ZPOOL_CONFIG_ALLOCATION_BIAS, &bias); + (void) nvlist_lookup_string(child[c], + ZPOOL_CONFIG_TYPE, &type); + } + + if (bias == NULL || strcmp(bias, class) != 0) continue; - name = zpool_vdev_name(g_zfs, zhp, child[c], + if (!is_log && strcmp(type, VDEV_TYPE_INDIRECT) == 0) + continue; + + if (!printed) { + (void) printf("\t%s\t\n", gettext(class)); + printed = B_TRUE; + } + + char *name = zpool_vdev_name(g_zfs, zhp, child[c], cb->cb_name_flags | VDEV_NAME_TYPE_ID); if (cb->cb_print_status) print_status_config(zhp, cb, name, child[c], 2, @@ -1832,8 +2386,10 @@ show_import(nvlist_t *config) vdev_stat_t *vs; char *name; uint64_t guid; + uint64_t hostid = 0; char *msgid; - nvlist_t *nvroot; + char *hostname = "unknown"; + nvlist_t *nvroot, *nvinfo; zpool_status_t reason; zpool_errata_t errata; const char *health; @@ -1921,6 +2477,17 @@ show_import(nvlist_t *config) zpool_print_unsup_feat(config); break; + case ZPOOL_STATUS_HOSTID_ACTIVE: + (void) printf(gettext(" status: The pool is currently " + "imported by another system.\n")); + break; + + case ZPOOL_STATUS_HOSTID_REQUIRED: + (void) printf(gettext(" status: The pool has the " + "multihost property on. It cannot\n\tbe safely imported " + "when the system hostid is not set.\n")); + break; + case ZPOOL_STATUS_HOSTID_MISMATCH: (void) printf(gettext(" status: The pool was last accessed by " "another system.\n")); @@ -1990,6 +2557,30 @@ show_import(nvlist_t *config) "updating.\n")); break; + case ZPOOL_ERRATA_ZOL_6845_ENCRYPTION: + (void) printf(gettext(" action: Existing " + "encrypted datasets contain an on-disk " + "incompatibility, which\n\tneeds to be " + "corrected. Backup these datasets to new " + "encrypted datasets\n\tand destroy the " + "old ones.\n")); + break; + + case ZPOOL_ERRATA_ZOL_8308_ENCRYPTION: + (void) printf(gettext(" action: Existing " + "encrypted snapshots and bookmarks contain " + "an on-disk\n\tincompatibility. This may " + "cause on-disk corruption if they are used" + "\n\twith 'zfs recv'. To correct the " + "issue, enable the bookmark_v2 feature.\n\t" + "No additional action is needed if there " + "are no encrypted snapshots or\n\t" + "bookmarks. If preserving the encrypted " + "snapshots and bookmarks is\n\trequired, " + "use a non-raw send to backup and restore " + "them. Alternately,\n\tthey may be removed" + " to resolve the incompatibility.\n")); + break; default: /* * All errata must contain an action message. @@ -2034,6 +2625,27 @@ show_import(nvlist_t *config) "imported. Attach the missing\n\tdevices and try " "again.\n")); break; + case ZPOOL_STATUS_HOSTID_ACTIVE: + VERIFY0(nvlist_lookup_nvlist(config, + ZPOOL_CONFIG_LOAD_INFO, &nvinfo)); + + if (nvlist_exists(nvinfo, ZPOOL_CONFIG_MMP_HOSTNAME)) + hostname = fnvlist_lookup_string(nvinfo, + ZPOOL_CONFIG_MMP_HOSTNAME); + + if (nvlist_exists(nvinfo, ZPOOL_CONFIG_MMP_HOSTID)) + hostid = fnvlist_lookup_uint64(nvinfo, + ZPOOL_CONFIG_MMP_HOSTID); + + (void) printf(gettext(" action: The pool must be " + "exported from %s (hostid=%lx)\n\tbefore it " + "can be safely imported.\n"), hostname, + (unsigned long) hostid); + break; + case ZPOOL_STATUS_HOSTID_REQUIRED: + (void) printf(gettext(" action: Set a unique system " + "hostid with the zgenhostid(8) command.\n")); + break; default: (void) printf(gettext(" action: The pool cannot be " "imported due to damaged devices or data.\n")); @@ -2066,13 +2678,16 @@ show_import(nvlist_t *config) (void) printf(gettext(" config:\n\n")); - cb.cb_namewidth = max_width(NULL, nvroot, 0, 0, VDEV_NAME_TYPE_ID); + cb.cb_namewidth = max_width(NULL, nvroot, 0, strlen(name), + VDEV_NAME_TYPE_ID); if (cb.cb_namewidth < 10) cb.cb_namewidth = 10; print_import_config(&cb, name, nvroot, 0); - if (num_logs(nvroot) > 0) - print_logs(NULL, &cb, nvroot); + + print_class_vdevs(NULL, &cb, nvroot, VDEV_ALLOC_BIAS_DEDUP); + print_class_vdevs(NULL, &cb, nvroot, VDEV_ALLOC_BIAS_SPECIAL); + print_class_vdevs(NULL, &cb, nvroot, VDEV_ALLOC_CLASS_LOGS); if (reason == ZPOOL_STATUS_BAD_GUID_SUM) { (void) printf(gettext("\n\tAdditional devices are known to " @@ -2081,6 +2696,31 @@ show_import(nvlist_t *config) } } +static boolean_t +zfs_force_import_required(nvlist_t *config) +{ + uint64_t state; + uint64_t hostid = 0; + nvlist_t *nvinfo; + + state = fnvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_STATE); + (void) nvlist_lookup_uint64(config, ZPOOL_CONFIG_HOSTID, &hostid); + + if (state != POOL_STATE_EXPORTED && hostid != get_system_hostid()) + return (B_TRUE); + + nvinfo = fnvlist_lookup_nvlist(config, ZPOOL_CONFIG_LOAD_INFO); + if (nvlist_exists(nvinfo, ZPOOL_CONFIG_MMP_STATE)) { + mmp_state_t mmp_state = fnvlist_lookup_uint64(nvinfo, + ZPOOL_CONFIG_MMP_STATE); + + if (mmp_state != MMP_STATE_INACTIVE) + return (B_TRUE); + } + + return (B_FALSE); +} + /* * Perform the import for the given configuration. This passes the heavy * lifting off to zpool_import_props(), and then mounts the datasets contained @@ -2090,50 +2730,76 @@ static int do_import(nvlist_t *config, const char *newname, const char *mntopts, nvlist_t *props, int flags) { + int ret = 0; zpool_handle_t *zhp; char *name; - uint64_t state; uint64_t version; - verify(nvlist_lookup_string(config, ZPOOL_CONFIG_POOL_NAME, - &name) == 0); + name = fnvlist_lookup_string(config, ZPOOL_CONFIG_POOL_NAME); + version = fnvlist_lookup_uint64(config, ZPOOL_CONFIG_VERSION); - verify(nvlist_lookup_uint64(config, - ZPOOL_CONFIG_POOL_STATE, &state) == 0); - verify(nvlist_lookup_uint64(config, - ZPOOL_CONFIG_VERSION, &version) == 0); if (!SPA_VERSION_IS_SUPPORTED(version)) { (void) fprintf(stderr, gettext("cannot import '%s': pool " "is formatted using an unsupported ZFS version\n"), name); return (1); - } else if (state != POOL_STATE_EXPORTED && + } else if (zfs_force_import_required(config) && !(flags & ZFS_IMPORT_ANY_HOST)) { - uint64_t hostid = 0; - unsigned long system_hostid = get_system_hostid(); + mmp_state_t mmp_state = MMP_STATE_INACTIVE; + nvlist_t *nvinfo; - (void) nvlist_lookup_uint64(config, ZPOOL_CONFIG_HOSTID, - &hostid); + nvinfo = fnvlist_lookup_nvlist(config, ZPOOL_CONFIG_LOAD_INFO); + if (nvlist_exists(nvinfo, ZPOOL_CONFIG_MMP_STATE)) + mmp_state = fnvlist_lookup_uint64(nvinfo, + ZPOOL_CONFIG_MMP_STATE); - if (hostid != 0 && (unsigned long)hostid != system_hostid) { - char *hostname; - uint64_t timestamp; - time_t t; + if (mmp_state == MMP_STATE_ACTIVE) { + char *hostname = ""; + uint64_t hostid = 0; - verify(nvlist_lookup_string(config, - ZPOOL_CONFIG_HOSTNAME, &hostname) == 0); - verify(nvlist_lookup_uint64(config, - ZPOOL_CONFIG_TIMESTAMP, ×tamp) == 0); - t = timestamp; - (void) fprintf(stderr, gettext("cannot import " - "'%s': pool may be in use from other " - "system, it was last accessed by %s " - "(hostid: 0x%lx) on %s"), name, hostname, - (unsigned long)hostid, - asctime(localtime(&t))); - (void) fprintf(stderr, gettext("use '-f' to " - "import anyway\n")); - return (1); + if (nvlist_exists(nvinfo, ZPOOL_CONFIG_MMP_HOSTNAME)) + hostname = fnvlist_lookup_string(nvinfo, + ZPOOL_CONFIG_MMP_HOSTNAME); + + if (nvlist_exists(nvinfo, ZPOOL_CONFIG_MMP_HOSTID)) + hostid = fnvlist_lookup_uint64(nvinfo, + ZPOOL_CONFIG_MMP_HOSTID); + + (void) fprintf(stderr, gettext("cannot import '%s': " + "pool is imported on %s (hostid: " + "0x%lx)\nExport the pool on the other system, " + "then run 'zpool import'.\n"), + name, hostname, (unsigned long) hostid); + } else if (mmp_state == MMP_STATE_NO_HOSTID) { + (void) fprintf(stderr, gettext("Cannot import '%s': " + "pool has the multihost property on and the\n" + "system's hostid is not set. Set a unique hostid " + "with the zgenhostid(8) command.\n"), name); + } else { + char *hostname = ""; + uint64_t timestamp = 0; + uint64_t hostid = 0; + + if (nvlist_exists(config, ZPOOL_CONFIG_HOSTNAME)) + hostname = fnvlist_lookup_string(config, + ZPOOL_CONFIG_HOSTNAME); + + if (nvlist_exists(config, ZPOOL_CONFIG_TIMESTAMP)) + timestamp = fnvlist_lookup_uint64(config, + ZPOOL_CONFIG_TIMESTAMP); + + if (nvlist_exists(config, ZPOOL_CONFIG_HOSTID)) + hostid = fnvlist_lookup_uint64(config, + ZPOOL_CONFIG_HOSTID); + + (void) fprintf(stderr, gettext("cannot import '%s': " + "pool was previously in use from another system.\n" + "Last accessed by %s (hostid=%lx) at %s" + "The pool can be imported, use 'zpool import -f' " + "to import the pool.\n"), name, hostname, + (unsigned long)hostid, ctime((time_t *)×tamp)); } + + return (1); } if (zpool_import_props(g_zfs, config, newname, props, flags) != 0) @@ -2145,6 +2811,16 @@ do_import(nvlist_t *config, const char *newname, const char *mntopts, if ((zhp = zpool_open_canfail(g_zfs, name)) == NULL) return (1); + /* + * Loading keys is best effort. We don't want to return immediately + * if it fails but we do want to give the error to the caller. + */ + if (flags & ZFS_IMPORT_LOAD_KEYS) { + ret = zfs_crypto_attempt_load_keys(g_zfs, name); + if (ret != 0) + ret = 1; + } + if (zpool_get_state(zhp) != POOL_STATE_UNAVAIL && !(flags & ZFS_IMPORT_ONLY) && zpool_enable_datasets(zhp, mntopts, 0) != 0) { @@ -2153,14 +2829,121 @@ do_import(nvlist_t *config, const char *newname, const char *mntopts, } zpool_close(zhp); - return (0); + return (ret); +} + +typedef struct target_exists_args { + const char *poolname; + uint64_t poolguid; +} target_exists_args_t; + +static int +name_or_guid_exists(zpool_handle_t *zhp, void *data) +{ + target_exists_args_t *args = data; + nvlist_t *config = zpool_get_config(zhp, NULL); + int found = 0; + + if (config == NULL) + return (0); + + if (args->poolname != NULL) { + char *pool_name; + + verify(nvlist_lookup_string(config, ZPOOL_CONFIG_POOL_NAME, + &pool_name) == 0); + if (strcmp(pool_name, args->poolname) == 0) + found = 1; + } else { + uint64_t pool_guid; + + verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID, + &pool_guid) == 0); + if (pool_guid == args->poolguid) + found = 1; + } + zpool_close(zhp); + + return (found); +} +/* + * zpool checkpoint + * checkpoint --discard + * + * -d Discard the checkpoint from a checkpointed + * --discard pool. + * + * Checkpoints the specified pool, by taking a "snapshot" of its + * current state. A pool can only have one checkpoint at a time. + */ +int +zpool_do_checkpoint(int argc, char **argv) +{ + boolean_t discard; + char *pool; + zpool_handle_t *zhp; + int c, err; + + struct option long_options[] = { + {"discard", no_argument, NULL, 'd'}, + {0, 0, 0, 0} + }; + + discard = B_FALSE; + while ((c = getopt_long(argc, argv, ":d", long_options, NULL)) != -1) { + switch (c) { + case 'd': + discard = B_TRUE; + break; + case '?': + (void) fprintf(stderr, gettext("invalid option '%c'\n"), + optopt); + usage(B_FALSE); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1) { + (void) fprintf(stderr, gettext("missing pool argument\n")); + usage(B_FALSE); + } + + if (argc > 1) { + (void) fprintf(stderr, gettext("too many arguments\n")); + usage(B_FALSE); + } + + pool = argv[0]; + + if ((zhp = zpool_open(g_zfs, pool)) == NULL) { + /* As a special case, check for use of '/' in the name */ + if (strchr(pool, '/') != NULL) + (void) fprintf(stderr, gettext("'zpool checkpoint' " + "doesn't work on datasets. To save the state " + "of a dataset from a specific point in time " + "please use 'zfs snapshot'\n")); + return (1); + } + + if (discard) + err = (zpool_discard_checkpoint(zhp) != 0); + else + err = (zpool_checkpoint(zhp) != 0); + + zpool_close(zhp); + + return (err); } +#define CHECKPOINT_OPT 1024 + /* * zpool import [-d dir] [-D] - * import [-o mntopts] [-o prop=value] ... [-R root] [-D] + * import [-o mntopts] [-o prop=value] ... [-R root] [-D] [-l] * [-d dir | -c cachefile] [-f] -a - * import [-o mntopts] [-o prop=value] ... [-R root] [-D] + * import [-o mntopts] [-o prop=value] ... [-R root] [-D] [-l] * [-d dir | -c cachefile] [-f] [-n] [-F] [newpool] * * -c Read pool information from a cachefile instead of searching @@ -2195,11 +2978,16 @@ do_import(nvlist_t *config, const char *newname, const char *mntopts, * * -a Import all pools found. * + * -l Load encryption keys while importing. + * * -o Set property=value and/or temporary mount options (without '='). * * -s Scan using the default search path, the libblkid cache will * not be consulted. * + * --rewind-to-checkpoint + * Import the pool and revert back to the checkpoint. + * * The import command scans for pools to import, and import pools based on pool * name and GUID. The pool can also be renamed as part of the import process. */ @@ -2230,13 +3018,20 @@ zpool_do_import(int argc, char **argv) boolean_t do_rewind = B_FALSE; boolean_t xtreme_rewind = B_FALSE; boolean_t do_scan = B_FALSE; + boolean_t pool_exists = B_FALSE; uint64_t pool_state, txg = -1ULL; char *cachefile = NULL; importargs_t idata = { 0 }; char *endptr; + struct option long_options[] = { + {"rewind-to-checkpoint", no_argument, NULL, CHECKPOINT_OPT}, + {0, 0, 0, 0} + }; + /* check options */ - while ((c = getopt(argc, argv, ":aCc:d:DEfFmnNo:R:stT:VX")) != -1) { + while ((c = getopt_long(argc, argv, ":aCc:d:DEfFlmnNo:R:stT:VX", + long_options, NULL)) != -1) { switch (c) { case 'a': do_all = B_TRUE; @@ -2266,6 +3061,9 @@ zpool_do_import(int argc, char **argv) case 'F': do_rewind = B_TRUE; break; + case 'l': + flags |= ZFS_IMPORT_LOAD_KEYS; + break; case 'm': flags |= ZFS_IMPORT_MISSING_LOG; break; @@ -2320,6 +3118,9 @@ zpool_do_import(int argc, char **argv) case 'X': xtreme_rewind = B_TRUE; break; + case CHECKPOINT_OPT: + flags |= ZFS_IMPORT_CHECKPOINT; + break; case ':': (void) fprintf(stderr, gettext("missing argument for " "'%c' option\n"), optopt); @@ -2340,6 +3141,17 @@ zpool_do_import(int argc, char **argv) usage(B_FALSE); } + if ((flags & ZFS_IMPORT_LOAD_KEYS) && (flags & ZFS_IMPORT_ONLY)) { + (void) fprintf(stderr, gettext("-l is incompatible with -N\n")); + usage(B_FALSE); + } + + if ((flags & ZFS_IMPORT_LOAD_KEYS) && !do_all && argc == 0) { + (void) fprintf(stderr, gettext("-l is only meaningful during " + "an import\n")); + usage(B_FALSE); + } + if ((dryrun || xtreme_rewind) && !do_rewind) { (void) fprintf(stderr, gettext("-n or -X only meaningful with -F\n")); @@ -2354,8 +3166,9 @@ zpool_do_import(int argc, char **argv) /* In the future, we can capture further policy and include it here */ if (nvlist_alloc(&policy, NV_UNIQUE_NAME, 0) != 0 || - nvlist_add_uint64(policy, ZPOOL_REWIND_REQUEST_TXG, txg) != 0 || - nvlist_add_uint32(policy, ZPOOL_REWIND_REQUEST, rewind_policy) != 0) + nvlist_add_uint64(policy, ZPOOL_LOAD_REQUEST_TXG, txg) != 0 || + nvlist_add_uint32(policy, ZPOOL_LOAD_REWIND_POLICY, + rewind_policy) != 0) goto error; /* check argument count */ @@ -2413,7 +3226,8 @@ zpool_do_import(int argc, char **argv) /* * User specified a name or guid. Ensure it's unique. */ - idata.unique = B_TRUE; + target_exists_args_t search = {searchname, searchguid}; + pool_exists = zpool_iter(g_zfs, name_or_guid_exists, &search); } /* @@ -2447,18 +3261,11 @@ zpool_do_import(int argc, char **argv) idata.guid = searchguid; idata.cachefile = cachefile; idata.scan = do_scan; + idata.policy = policy; - /* - * Under Linux the zpool_find_import_impl() function leverages the - * taskq implementation to parallelize device scanning. It is - * therefore necessary to initialize this functionality for the - * duration of the zpool_search_import() function. - */ - thread_init(); - pools = zpool_search_import(g_zfs, &idata); - thread_fini(); + pools = zpool_search_import(g_zfs, &idata, &libzfs_config_ops); - if (pools != NULL && idata.exists && + if (pools != NULL && pool_exists && (argc == 1 || strcmp(argv[0], argv[1]) == 0)) { (void) fprintf(stderr, gettext("cannot import '%s': " "a pool with that name already exists\n"), @@ -2467,7 +3274,7 @@ zpool_do_import(int argc, char **argv) " ' to give it a new name\n"), "zpool import"); err = 1; - } else if (pools == NULL && idata.exists) { + } else if (pools == NULL && pool_exists) { (void) fprintf(stderr, gettext("cannot import '%s': " "a pool with that name is already created/imported,\n"), argv[0]); @@ -2513,7 +3320,7 @@ zpool_do_import(int argc, char **argv) if (do_destroyed && pool_state != POOL_STATE_DESTROYED) continue; - verify(nvlist_add_nvlist(config, ZPOOL_REWIND_POLICY, + verify(nvlist_add_nvlist(config, ZPOOL_LOAD_POLICY, policy) == 0); if (argc == 0) { @@ -2597,6 +3404,45 @@ error: return (err ? 1 : 0); } +/* + * zpool sync [-f] [pool] ... + * + * -f (undocumented) force uberblock (and config including zpool cache file) + * update. + * + * Sync the specified pool(s). + * Without arguments "zpool sync" will sync all pools. + * This command initiates TXG sync(s) and will return after the TXG(s) commit. + * + */ +static int +zpool_do_sync(int argc, char **argv) +{ + int ret; + boolean_t force = B_FALSE; + + /* check options */ + while ((ret = getopt(argc, argv, "f")) != -1) { + switch (ret) { + case 'f': + force = B_TRUE; + break; + case '?': + (void) fprintf(stderr, gettext("invalid option '%c'\n"), + optopt); + usage(B_FALSE); + } + } + + argc -= optind; + argv += optind; + + /* if argc == 0 we will execute zpool_sync_one on all pools */ + ret = for_each_pool(argc, argv, B_FALSE, NULL, zpool_sync_one, &force); + + return (ret); +} + typedef struct iostat_cbdata { uint64_t cb_flags; int cb_name_flags; @@ -2617,22 +3463,22 @@ typedef struct name_and_columns { unsigned int columns; /* Center name to this number of columns */ } name_and_columns_t; -#define IOSTAT_MAX_LABELS 11 /* Max number of labels on one line */ +#define IOSTAT_MAX_LABELS 13 /* Max number of labels on one line */ static const name_and_columns_t iostat_top_labels[][IOSTAT_MAX_LABELS] = { [IOS_DEFAULT] = {{"capacity", 2}, {"operations", 2}, {"bandwidth", 2}, {NULL}}, [IOS_LATENCY] = {{"total_wait", 2}, {"disk_wait", 2}, {"syncq_wait", 2}, - {"asyncq_wait", 2}, {"scrub"}}, + {"asyncq_wait", 2}, {"scrub", 1}, {"trim", 1}, {NULL}}, [IOS_QUEUES] = {{"syncq_read", 2}, {"syncq_write", 2}, {"asyncq_read", 2}, {"asyncq_write", 2}, {"scrubq_read", 2}, - {NULL}}, - [IOS_L_HISTO] = {{"total_wait", 2}, {"disk_wait", 2}, - {"sync_queue", 2}, {"async_queue", 2}, {NULL}}, + {"trimq_write", 2}, {NULL}}, + [IOS_L_HISTO] = {{"total_wait", 2}, {"disk_wait", 2}, {"syncq_wait", 2}, + {"asyncq_wait", 2}, {NULL}}, [IOS_RQ_HISTO] = {{"sync_read", 2}, {"sync_write", 2}, - {"async_read", 2}, {"async_write", 2}, {"scrub", 2}, {NULL}}, - + {"async_read", 2}, {"async_write", 2}, {"scrub", 2}, + {"trim", 2}, {NULL}}, }; /* Shorthand - if "columns" field not set, default to 1 column */ @@ -2641,13 +3487,14 @@ static const name_and_columns_t iostat_bottom_labels[][IOSTAT_MAX_LABELS] = [IOS_DEFAULT] = {{"alloc"}, {"free"}, {"read"}, {"write"}, {"read"}, {"write"}, {NULL}}, [IOS_LATENCY] = {{"read"}, {"write"}, {"read"}, {"write"}, {"read"}, - {"write"}, {"read"}, {"write"}, {"wait"}, {NULL}}, + {"write"}, {"read"}, {"write"}, {"wait"}, {"wait"}, {NULL}}, [IOS_QUEUES] = {{"pend"}, {"activ"}, {"pend"}, {"activ"}, {"pend"}, - {"activ"}, {"pend"}, {"activ"}, {"pend"}, {"activ"}, {NULL}}, + {"activ"}, {"pend"}, {"activ"}, {"pend"}, {"activ"}, + {"pend"}, {"activ"}, {NULL}}, [IOS_L_HISTO] = {{"read"}, {"write"}, {"read"}, {"write"}, {"read"}, - {"write"}, {"read"}, {"write"}, {"scrub"}, {NULL}}, + {"write"}, {"read"}, {"write"}, {"scrub"}, {"trim"}, {NULL}}, [IOS_RQ_HISTO] = {{"ind"}, {"agg"}, {"ind"}, {"agg"}, {"ind"}, {"agg"}, - {"ind"}, {"agg"}, {"ind"}, {"agg"}, {NULL}}, + {"ind"}, {"agg"}, {"ind"}, {"agg"}, {"ind"}, {"agg"}, {NULL}}, }; static const char *histo_to_title[] = { @@ -2706,6 +3553,8 @@ default_column_width(iostat_cbdata_t *cb, enum iostat_type type) [IOS_DEFAULT] = 15, /* 1PB capacity */ [IOS_LATENCY] = 10, /* 1B ns = 10sec */ [IOS_QUEUES] = 6, /* 1M queue entries */ + [IOS_L_HISTO] = 10, /* 1B ns = 10sec */ + [IOS_RQ_HISTO] = 6, /* 1M queue entries */ }; if (cb->cb_literal) @@ -2728,7 +3577,7 @@ print_iostat_labels(iostat_cbdata_t *cb, unsigned int force_column_width, const name_and_columns_t labels[][IOSTAT_MAX_LABELS]) { int i, idx, s; - unsigned int text_start, rw_column_width, spaces_to_end; + int text_start, rw_column_width, spaces_to_end; uint64_t flags = cb->cb_flags; uint64_t f; unsigned int column_width = force_column_width; @@ -2752,8 +3601,10 @@ print_iostat_labels(iostat_cbdata_t *cb, unsigned int force_column_width, rw_column_width = (column_width * columns) + (2 * (columns - 1)); - text_start = (int)((rw_column_width)/columns - - slen/columns); + text_start = (int)((rw_column_width) / columns - + slen / columns); + if (text_start < 0) + text_start = 0; printf(" "); /* Two spaces between columns */ @@ -2765,14 +3616,61 @@ print_iostat_labels(iostat_cbdata_t *cb, unsigned int force_column_width, /* Print space after label to end of column */ spaces_to_end = rw_column_width - text_start - slen; + if (spaces_to_end < 0) + spaces_to_end = 0; + for (s = 0; s < spaces_to_end; s++) printf(" "); + } + } +} + + +/* + * print_cmd_columns - Print custom column titles from -c + * + * If the user specified the "zpool status|iostat -c" then print their custom + * column titles in the header. For example, print_cmd_columns() would print + * the " col1 col2" part of this: + * + * $ zpool iostat -vc 'echo col1=val1; echo col2=val2' + * ... + * capacity operations bandwidth + * pool alloc free read write read write col1 col2 + * ---------- ----- ----- ----- ----- ----- ----- ---- ---- + * mypool 269K 1008M 0 0 107 946 + * mirror 269K 1008M 0 0 107 946 + * sdb - - 0 0 102 473 val1 val2 + * sdc - - 0 0 5 473 val1 val2 + * ---------- ----- ----- ----- ----- ----- ----- ---- ---- + */ +void +print_cmd_columns(vdev_cmd_data_list_t *vcdl, int use_dashes) +{ + int i, j; + vdev_cmd_data_t *data = &vcdl->data[0]; + if (vcdl->count == 0 || data == NULL) + return; + + /* + * Each vdev cmd should have the same column names unless the user did + * something weird with their cmd. Just take the column names from the + * first vdev and assume it works for all of them. + */ + for (i = 0; i < vcdl->uniq_cols_cnt; i++) { + printf(" "); + if (use_dashes) { + for (j = 0; j < vcdl->uniq_cols_width[i]; j++) + printf("-"); + } else { + printf("%*s", vcdl->uniq_cols_width[i], + vcdl->uniq_cols[i]); } } - printf("\n"); } + /* * Utility function to print out a line of dashes like: * @@ -2841,7 +3739,6 @@ print_iostat_dashes(iostat_cbdata_t *cb, unsigned int force_column_width, "--------------------"); } } - printf("\n"); } @@ -2883,12 +3780,22 @@ print_iostat_header_impl(iostat_cbdata_t *cb, unsigned int force_column_width, print_iostat_labels(cb, force_column_width, iostat_top_labels); + printf("\n"); printf("%-*s", namewidth, title); print_iostat_labels(cb, force_column_width, iostat_bottom_labels); + if (cb->vcdl != NULL) + print_cmd_columns(cb->vcdl, 0); + + printf("\n"); print_iostat_separator_impl(cb, force_column_width); + + if (cb->vcdl != NULL) + print_cmd_columns(cb->vcdl, 1); + + printf("\n"); } static void @@ -2951,7 +3858,8 @@ struct stat_array { }; static uint64_t -stat_histo_max(struct stat_array *nva, unsigned int len) { +stat_histo_max(struct stat_array *nva, unsigned int len) +{ uint64_t max = 0; int i; for (i = 0; i < len; i++) @@ -2967,7 +3875,8 @@ stat_histo_max(struct stat_array *nva, unsigned int len) { */ static int nvpair64_to_stat_array(nvlist_t *nvl, const char *name, - struct stat_array *nva) { + struct stat_array *nva) +{ nvpair_t *tmp; int ret; @@ -3200,7 +4109,7 @@ single_histo_average(uint64_t *histo, unsigned int buckets) static void print_iostat_queues(iostat_cbdata_t *cb, nvlist_t *oldnv, - nvlist_t *newnv, double scale) + nvlist_t *newnv) { int i; uint64_t val; @@ -3215,6 +4124,8 @@ print_iostat_queues(iostat_cbdata_t *cb, nvlist_t *oldnv, ZPOOL_CONFIG_VDEV_ASYNC_W_ACTIVE_QUEUE, ZPOOL_CONFIG_VDEV_SCRUB_PEND_QUEUE, ZPOOL_CONFIG_VDEV_SCRUB_ACTIVE_QUEUE, + ZPOOL_CONFIG_VDEV_TRIM_PEND_QUEUE, + ZPOOL_CONFIG_VDEV_TRIM_ACTIVE_QUEUE, }; struct stat_array *nva; @@ -3230,7 +4141,7 @@ print_iostat_queues(iostat_cbdata_t *cb, nvlist_t *oldnv, format = ZFS_NICENUM_1024; for (i = 0; i < ARRAY_SIZE(names); i++) { - val = nva[i].data[0] * scale; + val = nva[i].data[0]; print_one_stat(val, format, column_width, cb->cb_scripted); } @@ -3239,7 +4150,7 @@ print_iostat_queues(iostat_cbdata_t *cb, nvlist_t *oldnv, static void print_iostat_latency(iostat_cbdata_t *cb, nvlist_t *oldnv, - nvlist_t *newnv, double scale) + nvlist_t *newnv) { int i; uint64_t val; @@ -3253,6 +4164,7 @@ print_iostat_latency(iostat_cbdata_t *cb, nvlist_t *oldnv, ZPOOL_CONFIG_VDEV_ASYNC_R_LAT_HISTO, ZPOOL_CONFIG_VDEV_ASYNC_W_LAT_HISTO, ZPOOL_CONFIG_VDEV_SCRUB_LAT_HISTO, + ZPOOL_CONFIG_VDEV_TRIM_LAT_HISTO, }; struct stat_array *nva; @@ -3262,14 +4174,14 @@ print_iostat_latency(iostat_cbdata_t *cb, nvlist_t *oldnv, nva = calc_and_alloc_stats_ex(names, ARRAY_SIZE(names), oldnv, newnv); if (cb->cb_literal) - format = ZFS_NICENUM_RAW; + format = ZFS_NICENUM_RAWTIME; else format = ZFS_NICENUM_TIME; /* Print our avg latencies on the line */ for (i = 0; i < ARRAY_SIZE(names); i++) { /* Compute average latency for a latency histo */ - val = single_histo_average(nva[i].data, nva[i].count) * scale; + val = single_histo_average(nva[i].data, nva[i].count); print_one_stat(val, format, column_width, cb->cb_scripted); } free_calc_stats(nva, ARRAY_SIZE(names)); @@ -3317,6 +4229,12 @@ print_iostat_default(vdev_stat_t *vs, iostat_cbdata_t *cb, double scale) format, column_width, cb->cb_scripted); } +static const char *class_name[] = { + VDEV_ALLOC_BIAS_DEDUP, + VDEV_ALLOC_BIAS_SPECIAL, + VDEV_ALLOC_CLASS_LOGS +}; + /* * Print out all the statistics for the given vdev. This can either be the * toplevel configuration, or called recursively. If 'name' is NULL, then this @@ -3324,12 +4242,12 @@ print_iostat_default(vdev_stat_t *vs, iostat_cbdata_t *cb, double scale) * * Returns the number of stat lines printed. */ -unsigned int +static unsigned int print_vdev_stats(zpool_handle_t *zhp, const char *name, nvlist_t *oldnv, nvlist_t *newnv, iostat_cbdata_t *cb, int depth) { nvlist_t **oldchild, **newchild; - uint_t c, children; + uint_t c, children, oldchildren; vdev_stat_t *oldvs, *newvs, *calcvs; vdev_stat_t zerovs = { 0 }; char *vname; @@ -3340,6 +4258,9 @@ print_vdev_stats(zpool_handle_t *zhp, const char *name, nvlist_t *oldnv, calcvs = safe_malloc(sizeof (*calcvs)); + if (strcmp(name, VDEV_TYPE_INDIRECT) == 0) + return (ret); + if (oldnv != NULL) { verify(nvlist_lookup_uint64_array(oldnv, ZPOOL_CONFIG_VDEV_STATS, (uint64_t **)&oldvs, &c) == 0); @@ -3408,9 +4329,9 @@ print_vdev_stats(zpool_handle_t *zhp, const char *name, nvlist_t *oldnv, print_iostat_default(calcvs, cb, scale); } if (cb->cb_flags & IOS_LATENCY_M) - print_iostat_latency(cb, oldnv, newnv, scale); + print_iostat_latency(cb, oldnv, newnv); if (cb->cb_flags & IOS_QUEUES_M) - print_iostat_queues(cb, oldnv, newnv, scale); + print_iostat_queues(cb, oldnv, newnv); if (cb->cb_flags & IOS_ANYHISTO_M) { printf("\n"); print_iostat_histos(cb, oldnv, newnv, scale, name); @@ -3420,11 +4341,8 @@ print_vdev_stats(zpool_handle_t *zhp, const char *name, nvlist_t *oldnv, char *path; if (nvlist_lookup_string(newnv, ZPOOL_CONFIG_PATH, &path) == 0) { - if (!(cb->cb_flags & IOS_ANYHISTO_M)) - printf(" "); + printf(" "); zpool_print_cmd(cb->vcdl, zpool_get_name(zhp), path); - if (cb->cb_flags & IOS_ANYHISTO_M) - printf("\n"); } } @@ -3444,10 +4362,17 @@ children: &newchild, &children) != 0) return (ret); - if (oldnv && nvlist_lookup_nvlist_array(oldnv, ZPOOL_CONFIG_CHILDREN, - &oldchild, &c) != 0) - return (ret); + if (oldnv) { + if (nvlist_lookup_nvlist_array(oldnv, ZPOOL_CONFIG_CHILDREN, + &oldchild, &oldchildren) != 0) + return (ret); + + children = MIN(oldchildren, children); + } + /* + * print normal top-level devices + */ for (c = 0; c < children; c++) { uint64_t ishole = B_FALSE, islog = B_FALSE; @@ -3460,6 +4385,9 @@ children: if (ishole || islog) continue; + if (nvlist_exists(newchild[c], ZPOOL_CONFIG_ALLOCATION_BIAS)) + continue; + vname = zpool_vdev_name(g_zfs, zhp, newchild[c], cb->cb_name_flags); ret += print_vdev_stats(zhp, vname, oldnv ? oldchild[c] : NULL, @@ -3468,30 +4396,47 @@ children: } /* - * Log device section + * print all other top-level devices */ - - if (num_logs(newnv) > 0) { - if ((!(cb->cb_flags & IOS_ANYHISTO_M)) && !cb->cb_scripted && - !cb->cb_vdev_names) { - print_iostat_dashes(cb, 0, "logs"); - } + for (uint_t n = 0; n < 3; n++) { + boolean_t printed = B_FALSE; for (c = 0; c < children; c++) { uint64_t islog = B_FALSE; + char *bias = NULL; + char *type = NULL; + (void) nvlist_lookup_uint64(newchild[c], ZPOOL_CONFIG_IS_LOG, &islog); - if (islog) { - vname = zpool_vdev_name(g_zfs, zhp, newchild[c], - cb->cb_name_flags); - ret += print_vdev_stats(zhp, vname, oldnv ? - oldchild[c] : NULL, newchild[c], - cb, depth + 2); - free(vname); + bias = VDEV_ALLOC_CLASS_LOGS; + } else { + (void) nvlist_lookup_string(newchild[c], + ZPOOL_CONFIG_ALLOCATION_BIAS, &bias); + (void) nvlist_lookup_string(newchild[c], + ZPOOL_CONFIG_TYPE, &type); + } + if (bias == NULL || strcmp(bias, class_name[n]) != 0) + continue; + if (!islog && strcmp(type, VDEV_TYPE_INDIRECT) == 0) + continue; + + if (!printed) { + if ((!(cb->cb_flags & IOS_ANYHISTO_M)) && + !cb->cb_scripted && !cb->cb_vdev_names) { + print_iostat_dashes(cb, 0, + class_name[n]); + } + printf("\n"); + printed = B_TRUE; } - } + vname = zpool_vdev_name(g_zfs, zhp, newchild[c], + cb->cb_name_flags); + ret += print_vdev_stats(zhp, vname, oldnv ? + oldchild[c] : NULL, newchild[c], cb, depth + 2); + free(vname); + } } /* @@ -3501,15 +4446,20 @@ children: &newchild, &children) != 0) return (ret); - if (oldnv && nvlist_lookup_nvlist_array(oldnv, ZPOOL_CONFIG_L2CACHE, - &oldchild, &c) != 0) - return (ret); + if (oldnv) { + if (nvlist_lookup_nvlist_array(oldnv, ZPOOL_CONFIG_L2CACHE, + &oldchild, &oldchildren) != 0) + return (ret); + + children = MIN(oldchildren, children); + } if (children > 0) { if ((!(cb->cb_flags & IOS_ANYHISTO_M)) && !cb->cb_scripted && !cb->cb_vdev_names) { print_iostat_dashes(cb, 0, "cache"); } + printf("\n"); for (c = 0; c < children; c++) { vname = zpool_vdev_name(g_zfs, zhp, newchild[c], @@ -3571,6 +4521,10 @@ print_iostat(zpool_handle_t *zhp, void *data) if ((ret != 0) && !(cb->cb_flags & IOS_ANYHISTO_M) && !cb->cb_scripted && cb->cb_verbose && !cb->cb_vdev_names_count) { print_iostat_separator(cb); + if (cb->vcdl != NULL) { + print_cmd_columns(cb->vcdl, 1); + } + printf("\n"); } return (ret); @@ -3594,36 +4548,29 @@ get_columns(void) return (columns); } -int -get_namewidth(zpool_handle_t *zhp, void *data) +/* + * Return the required length of the pool/vdev name column. The minimum + * allowed width and output formatting flags must be provided. + */ +static int +get_namewidth(zpool_handle_t *zhp, int min_width, int flags, boolean_t verbose) { - iostat_cbdata_t *cb = data; nvlist_t *config, *nvroot; - int columns; + int width = min_width; if ((config = zpool_get_config(zhp, NULL)) != NULL) { verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &nvroot) == 0); unsigned int poolname_len = strlen(zpool_get_name(zhp)); - if (!cb->cb_verbose) - cb->cb_namewidth = poolname_len; - else - cb->cb_namewidth = MAX(poolname_len, - max_width(zhp, nvroot, 0, cb->cb_namewidth, - cb->cb_name_flags)); + if (verbose == B_FALSE) { + width = MAX(poolname_len, min_width); + } else { + width = MAX(poolname_len, + max_width(zhp, nvroot, 0, min_width, flags)); + } } - /* - * The width must be at least 10, but may be as large as the - * column width - 42 so that we can still fit in one line. - */ - columns = get_columns(); - - if (cb->cb_namewidth < 10) - cb->cb_namewidth = 10; - if (cb->cb_namewidth > columns - 42) - cb->cb_namewidth = columns - 42; - return (0); + return (width); } /* @@ -3869,7 +4816,8 @@ is_pool(char *name) /* Are all our argv[] strings pool names? If so return 1, 0 otherwise. */ static int -are_all_pools(int argc, char **argv) { +are_all_pools(int argc, char **argv) +{ if ((argc == 0) || !*argv) return (0); @@ -3950,18 +4898,123 @@ get_interval_count_filter_guids(int *argc, char **argv, float *interval, * seconds. */ static void -fsleep(float sec) { +fsleep(float sec) +{ struct timespec req; req.tv_sec = floor(sec); req.tv_nsec = (sec - (float)req.tv_sec) * NANOSEC; nanosleep(&req, NULL); } +/* + * Run one of the zpool status/iostat -c scripts with the help (-h) option and + * print the result. + * + * name: Short name of the script ('iostat'). + * path: Full path to the script ('/usr/local/etc/zfs/zpool.d/iostat'); + */ +static void +print_zpool_script_help(char *name, char *path) +{ + char *argv[] = {path, "-h", NULL}; + char **lines = NULL; + int lines_cnt = 0; + int rc; + + rc = libzfs_run_process_get_stdout_nopath(path, argv, NULL, &lines, + &lines_cnt); + if (rc != 0 || lines == NULL || lines_cnt <= 0) { + if (lines != NULL) + libzfs_free_str_array(lines, lines_cnt); + return; + } + + for (int i = 0; i < lines_cnt; i++) + if (!is_blank_str(lines[i])) + printf(" %-14s %s\n", name, lines[i]); + + libzfs_free_str_array(lines, lines_cnt); +} + +/* + * Go though the zpool status/iostat -c scripts in the user's path, run their + * help option (-h), and print out the results. + */ +static void +print_zpool_dir_scripts(char *dirpath) +{ + DIR *dir; + struct dirent *ent; + char fullpath[MAXPATHLEN]; + struct stat dir_stat; + + if ((dir = opendir(dirpath)) != NULL) { + /* print all the files and directories within directory */ + while ((ent = readdir(dir)) != NULL) { + sprintf(fullpath, "%s/%s", dirpath, ent->d_name); + + /* Print the scripts */ + if (stat(fullpath, &dir_stat) == 0) + if (dir_stat.st_mode & S_IXUSR && + S_ISREG(dir_stat.st_mode)) + print_zpool_script_help(ent->d_name, + fullpath); + } + closedir(dir); + } +} + +/* + * Print out help text for all zpool status/iostat -c scripts. + */ +static void +print_zpool_script_list(char *subcommand) +{ + char *dir, *sp; + + printf(gettext("Available 'zpool %s -c' commands:\n"), subcommand); + + sp = zpool_get_cmd_search_path(); + if (sp == NULL) + return; + + dir = strtok(sp, ":"); + while (dir != NULL) { + print_zpool_dir_scripts(dir); + dir = strtok(NULL, ":"); + } + + free(sp); +} + +/* + * Set the minimum pool/vdev name column width. The width must be at least 10, + * but may be as large as the column width - 42 so it still fits on one line. + */ +static int +get_namewidth_iostat(zpool_handle_t *zhp, void *data) +{ + iostat_cbdata_t *cb = data; + int width, columns; + + width = get_namewidth(zhp, cb->cb_namewidth, cb->cb_name_flags, + cb->cb_verbose); + columns = get_columns(); + + if (width < 10) + width = 10; + if (width > columns - 42) + width = columns - 42; + + cb->cb_namewidth = width; + + return (0); +} /* - * zpool iostat [-c CMD] [-ghHLpPvy] [[-lq]|[-r|-w]] [-n name] [-T d|u] - * [[ pool ...]|[pool vdev ...]|[vdev ...]] - * [interval [count]] + * zpool iostat [[-c [script1,script2,...]] [-lq]|[-rw]] [-ghHLpPvy] [-n name] + * [-T d|u] [[ pool ...]|[pool vdev ...]|[vdev ...]] + * [interval [count]] * * -c CMD For each vdev, run command CMD * -g Display guid for individual vdev name. @@ -3977,6 +5030,7 @@ fsleep(float sec) { * -w Display latency histograms * -r Display request size histogram * -T Display a timestamp in date(1) or Unix format + * -n Only print headers once * * This command can be tricky because we want to be able to deal with pool * creation/destruction as well as vdev configuration changes. The bulk of this @@ -3992,6 +5046,8 @@ zpool_do_iostat(int argc, char **argv) int npools; float interval = 0; unsigned long count = 0; + struct winsize win; + int winheight = 24; zpool_list_t *list; boolean_t verbose = B_FALSE; boolean_t latency = B_FALSE, l_histo = B_FALSE, rq_histo = B_FALSE; @@ -4000,6 +5056,7 @@ zpool_do_iostat(int argc, char **argv) boolean_t guid = B_FALSE; boolean_t follow_links = B_FALSE; boolean_t full_name = B_FALSE; + boolean_t headers_once = B_FALSE; iostat_cbdata_t cb = { 0 }; char *cmd = NULL; @@ -4010,10 +5067,32 @@ zpool_do_iostat(int argc, char **argv) uint64_t unsupported_flags; /* check options */ - while ((c = getopt(argc, argv, "c:gLPT:vyhplqrwH")) != -1) { + while ((c = getopt(argc, argv, "c:gLPT:vyhplqrwnH")) != -1) { switch (c) { case 'c': + if (cmd != NULL) { + fprintf(stderr, + gettext("Can't set -c flag twice\n")); + exit(1); + } + + if (getenv("ZPOOL_SCRIPTS_ENABLED") != NULL && + !libzfs_envvar_is_set("ZPOOL_SCRIPTS_ENABLED")) { + fprintf(stderr, gettext( + "Can't run -c, disabled by " + "ZPOOL_SCRIPTS_ENABLED.\n")); + exit(1); + } + + if ((getuid() <= 0 || geteuid() <= 0) && + !libzfs_envvar_is_set("ZPOOL_SCRIPTS_AS_ROOT")) { + fprintf(stderr, gettext( + "Can't run -c with root privileges " + "unless ZPOOL_SCRIPTS_AS_ROOT is set.\n")); + exit(1); + } cmd = optarg; + verbose = B_TRUE; break; case 'g': guid = B_TRUE; @@ -4051,13 +5130,16 @@ zpool_do_iostat(int argc, char **argv) case 'y': omit_since_boot = B_TRUE; break; + case 'n': + headers_once = B_TRUE; + break; case 'h': usage(B_FALSE); break; case '?': if (optopt == 'c') { - fprintf(stderr, - gettext("Missing CMD for -c\n")); + print_zpool_script_list("iostat"); + exit(0); } else { fprintf(stderr, gettext("invalid option '%c'\n"), optopt); @@ -4152,10 +5234,10 @@ zpool_do_iostat(int argc, char **argv) return (1); } - if ((l_histo || rq_histo) && (queues || latency)) { + if ((l_histo || rq_histo) && (cmd != NULL || latency || queues)) { pool_list_free(list); (void) fprintf(stderr, - gettext("[-r|-w] isn't allowed with [-q|-l]\n")); + gettext("[-r|-w] isn't allowed with [-c|-l|-q]\n")); usage(B_FALSE); return (1); } @@ -4237,12 +5319,41 @@ zpool_do_iostat(int argc, char **argv) * for the pool / device name column across all pools. */ cb.cb_namewidth = 0; - (void) pool_list_iter(list, B_FALSE, get_namewidth, - &cb); + (void) pool_list_iter(list, B_FALSE, + get_namewidth_iostat, &cb); if (timestamp_fmt != NODATE) print_timestamp(timestamp_fmt); + if (cmd != NULL && cb.cb_verbose && + !(cb.cb_flags & IOS_ANYHISTO_M)) { + cb.vcdl = all_pools_for_each_vdev_run(argc, + argv, cmd, g_zfs, cb.cb_vdev_names, + cb.cb_vdev_names_count, cb.cb_name_flags); + } else { + cb.vcdl = NULL; + } + + /* + * Are we connected to TTY? If not, headers_once + * should be true, to avoid breaking scripts. + */ + if (isatty(fileno(stdout)) == 0) + headers_once = B_TRUE; + + /* + * Check terminal size so we can print headers + * even when terminal window has its height + * changed. + */ + if (headers_once == B_FALSE) { + if (ioctl(1, TIOCGWINSZ, &win) != -1 && + win.ws_row > 0) + winheight = win.ws_row; + else + headers_once = B_TRUE; + } + /* * If it's the first time and we're not skipping it, * or either skip or verbose mode, print the header. @@ -4251,7 +5362,9 @@ zpool_do_iostat(int argc, char **argv) * every vdev, so skip this for histograms. */ if (((++cb.cb_iteration == 1 && !skip) || - (skip != verbose)) && + (skip != verbose) || + (!headers_once && + (cb.cb_iteration % winheight) == 0)) && (!(cb.cb_flags & IOS_ANYHISTO_M)) && !cb.cb_scripted) print_iostat_header(&cb); @@ -4261,16 +5374,8 @@ zpool_do_iostat(int argc, char **argv) continue; } - if (cmd != NULL && cb.cb_verbose) - cb.vcdl = all_pools_for_each_vdev_run(argc, - argv, cmd, g_zfs, cb.cb_vdev_names, - cb.cb_vdev_names_count, cb.cb_name_flags); - pool_list_iter(list, B_FALSE, print_iostat, &cb); - if (cb.vcdl != NULL) - free_vdev_cmd_data_list(cb.vcdl); - /* * If there's more than one pool, and we're not in * verbose mode (which prints a separator for us), @@ -4285,7 +5390,14 @@ zpool_do_iostat(int argc, char **argv) cb.cb_vdev_names_count)) && !cb.cb_scripted) { print_iostat_separator(&cb); + if (cb.vcdl != NULL) + print_cmd_columns(cb.vcdl, 1); + printf("\n"); } + + if (cb.vcdl != NULL) + free_vdev_cmd_data_list(cb.vcdl); + } /* @@ -4317,6 +5429,7 @@ typedef struct list_cbdata { boolean_t cb_literal; } list_cbdata_t; + /* * Given a list of columns to display, output appropriate headers for each one. */ @@ -4371,7 +5484,7 @@ print_header(list_cbdata_t *cb) /* * Given a pool and a list of properties, print out all the properties according - * to the described layout. + * to the described layout. Used by zpool_do_list(). */ static void print_pool(zpool_handle_t *zhp, list_cbdata_t *cb) @@ -4439,8 +5552,8 @@ print_pool(zpool_handle_t *zhp, list_cbdata_t *cb) } static void -print_one_column(zpool_prop_t prop, uint64_t value, boolean_t scripted, - boolean_t valid, enum zfs_nicenum_format format) +print_one_column(zpool_prop_t prop, uint64_t value, const char *str, + boolean_t scripted, boolean_t valid, enum zfs_nicenum_format format) { char propval[64]; boolean_t fixed; @@ -4448,6 +5561,8 @@ print_one_column(zpool_prop_t prop, uint64_t value, boolean_t scripted, switch (prop) { case ZPOOL_PROP_EXPANDSZ: + case ZPOOL_PROP_CHECKPOINT: + case ZPOOL_PROP_DEDUPRATIO: if (value == 0) (void) strlcpy(propval, "-", sizeof (propval)); else @@ -4466,12 +5581,18 @@ print_one_column(zpool_prop_t prop, uint64_t value, boolean_t scripted, } break; case ZPOOL_PROP_CAPACITY: + /* capacity value is in parts-per-10,000 (aka permyriad) */ if (format == ZFS_NICENUM_RAW) (void) snprintf(propval, sizeof (propval), "%llu", - (unsigned long long)value); + (unsigned long long)value / 100); else - (void) snprintf(propval, sizeof (propval), "%llu%%", - (unsigned long long)value); + (void) snprintf(propval, sizeof (propval), + value < 1000 ? "%1.2f%%" : value < 10000 ? + "%2.1f%%" : "%3.0f%%", value / 100.0); + break; + case ZPOOL_PROP_HEALTH: + width = 8; + snprintf(propval, sizeof (propval), "%-*s", (int)width, str); break; default: zfs_nicenum_format(value, propval, sizeof (propval), format); @@ -4486,9 +5607,13 @@ print_one_column(zpool_prop_t prop, uint64_t value, boolean_t scripted, (void) printf(" %*s", (int)width, propval); } +/* + * print static default line per vdev + * not compatible with '-o' option + */ void print_list_stats(zpool_handle_t *zhp, const char *name, nvlist_t *nv, - list_cbdata_t *cb, int depth) + list_cbdata_t *cb, int depth, boolean_t isspare) { nvlist_t **child; vdev_stat_t *vs; @@ -4496,8 +5621,8 @@ print_list_stats(zpool_handle_t *zhp, const char *name, nvlist_t *nv, char *vname; boolean_t scripted = cb->cb_scripted; uint64_t islog = B_FALSE; - boolean_t haslog = B_FALSE; - char *dashes = "%-*s - - - - - -\n"; + char *dashes = "%-*s - - - - " + "- - - - -\n"; verify(nvlist_lookup_uint64_array(nv, ZPOOL_CONFIG_VDEV_STATS, (uint64_t **)&vs, &c) == 0); @@ -4506,12 +5631,16 @@ print_list_stats(zpool_handle_t *zhp, const char *name, nvlist_t *nv, boolean_t toplevel = (vs->vs_space != 0); uint64_t cap; enum zfs_nicenum_format format; + const char *state; if (cb->cb_literal) format = ZFS_NICENUM_RAW; else format = ZFS_NICENUM_1024; + if (strcmp(name, VDEV_TYPE_INDIRECT) == 0) + return; + if (scripted) (void) printf("\t%s", name); else if (strlen(name) + depth > cb->cb_namewidth) @@ -4526,22 +5655,35 @@ print_list_stats(zpool_handle_t *zhp, const char *name, nvlist_t *nv, * 'toplevel' boolean value is passed to the print_one_column() * to indicate that the value is valid. */ - print_one_column(ZPOOL_PROP_SIZE, vs->vs_space, scripted, - toplevel, format); - print_one_column(ZPOOL_PROP_ALLOCATED, vs->vs_alloc, scripted, + print_one_column(ZPOOL_PROP_SIZE, vs->vs_space, NULL, scripted, toplevel, format); - print_one_column(ZPOOL_PROP_FREE, vs->vs_space - vs->vs_alloc, + print_one_column(ZPOOL_PROP_ALLOCATED, vs->vs_alloc, NULL, scripted, toplevel, format); - print_one_column(ZPOOL_PROP_EXPANDSZ, vs->vs_esize, scripted, - B_TRUE, format); + print_one_column(ZPOOL_PROP_FREE, vs->vs_space - vs->vs_alloc, + NULL, scripted, toplevel, format); + print_one_column(ZPOOL_PROP_CHECKPOINT, + vs->vs_checkpoint_space, NULL, scripted, toplevel, format); + print_one_column(ZPOOL_PROP_EXPANDSZ, vs->vs_esize, NULL, + scripted, B_TRUE, format); print_one_column(ZPOOL_PROP_FRAGMENTATION, - vs->vs_fragmentation, scripted, + vs->vs_fragmentation, NULL, scripted, (vs->vs_fragmentation != ZFS_FRAG_INVALID && toplevel), format); cap = (vs->vs_space == 0) ? 0 : - (vs->vs_alloc * 100 / vs->vs_space); - print_one_column(ZPOOL_PROP_CAPACITY, cap, scripted, toplevel, - format); + (vs->vs_alloc * 10000 / vs->vs_space); + print_one_column(ZPOOL_PROP_CAPACITY, cap, NULL, + scripted, toplevel, format); + print_one_column(ZPOOL_PROP_DEDUPRATIO, 0, NULL, + scripted, toplevel, format); + state = zpool_state_to_name(vs->vs_state, vs->vs_aux); + if (isspare) { + if (vs->vs_aux == VDEV_AUX_SPARED) + state = "INUSE"; + else if (vs->vs_state == VDEV_STATE_HEALTHY) + state = "AVAIL"; + } + print_one_column(ZPOOL_PROP_HEALTH, 0, state, scripted, + B_TRUE, format); (void) printf("\n"); } @@ -4549,6 +5691,7 @@ print_list_stats(zpool_handle_t *zhp, const char *name, nvlist_t *nv, &child, &children) != 0) return; + /* list the normal vdevs first */ for (c = 0; c < children; c++) { uint64_t ishole = B_FALSE; @@ -4557,27 +5700,50 @@ print_list_stats(zpool_handle_t *zhp, const char *name, nvlist_t *nv, continue; if (nvlist_lookup_uint64(child[c], - ZPOOL_CONFIG_IS_LOG, &islog) == 0 && islog) { - haslog = B_TRUE; + ZPOOL_CONFIG_IS_LOG, &islog) == 0 && islog) + continue; + + if (nvlist_exists(child[c], ZPOOL_CONFIG_ALLOCATION_BIAS)) continue; - } vname = zpool_vdev_name(g_zfs, zhp, child[c], cb->cb_name_flags); - print_list_stats(zhp, vname, child[c], cb, depth + 2); + print_list_stats(zhp, vname, child[c], cb, depth + 2, B_FALSE); free(vname); } - if (haslog == B_TRUE) { - /* LINTED E_SEC_PRINTF_VAR_FMT */ - (void) printf(dashes, cb->cb_namewidth, "log"); + /* list the classes: 'logs', 'dedup', and 'special' */ + for (uint_t n = 0; n < 3; n++) { + boolean_t printed = B_FALSE; + for (c = 0; c < children; c++) { + char *bias = NULL; + char *type = NULL; + if (nvlist_lookup_uint64(child[c], ZPOOL_CONFIG_IS_LOG, - &islog) != 0 || !islog) + &islog) == 0 && islog) { + bias = VDEV_ALLOC_CLASS_LOGS; + } else { + (void) nvlist_lookup_string(child[c], + ZPOOL_CONFIG_ALLOCATION_BIAS, &bias); + (void) nvlist_lookup_string(child[c], + ZPOOL_CONFIG_TYPE, &type); + } + if (bias == NULL || strcmp(bias, class_name[n]) != 0) continue; + if (!islog && strcmp(type, VDEV_TYPE_INDIRECT) == 0) + continue; + + if (!printed) { + /* LINTED E_SEC_PRINTF_VAR_FMT */ + (void) printf(dashes, cb->cb_namewidth, + class_name[n]); + printed = B_TRUE; + } vname = zpool_vdev_name(g_zfs, zhp, child[c], cb->cb_name_flags); - print_list_stats(zhp, vname, child[c], cb, depth + 2); + print_list_stats(zhp, vname, child[c], cb, depth + 2, + B_FALSE); free(vname); } } @@ -4589,7 +5755,8 @@ print_list_stats(zpool_handle_t *zhp, const char *name, nvlist_t *nv, for (c = 0; c < children; c++) { vname = zpool_vdev_name(g_zfs, zhp, child[c], cb->cb_name_flags); - print_list_stats(zhp, vname, child[c], cb, depth + 2); + print_list_stats(zhp, vname, child[c], cb, depth + 2, + B_FALSE); free(vname); } } @@ -4601,13 +5768,13 @@ print_list_stats(zpool_handle_t *zhp, const char *name, nvlist_t *nv, for (c = 0; c < children; c++) { vname = zpool_vdev_name(g_zfs, zhp, child[c], cb->cb_name_flags); - print_list_stats(zhp, vname, child[c], cb, depth + 2); + print_list_stats(zhp, vname, child[c], cb, depth + 2, + B_TRUE); free(vname); } } } - /* * Generic callback function to list a pool. */ @@ -4615,18 +5782,38 @@ int list_callback(zpool_handle_t *zhp, void *data) { list_cbdata_t *cbp = data; - nvlist_t *config; - nvlist_t *nvroot; - - config = zpool_get_config(zhp, NULL); print_pool(zhp, cbp); - if (!cbp->cb_verbose) - return (0); - verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, - &nvroot) == 0); - print_list_stats(zhp, NULL, nvroot, cbp, 0); + if (cbp->cb_verbose) { + nvlist_t *config, *nvroot; + + config = zpool_get_config(zhp, NULL); + verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, + &nvroot) == 0); + print_list_stats(zhp, NULL, nvroot, cbp, 0, B_FALSE); + } + + return (0); +} + +/* + * Set the minimum pool/vdev name column width. The width must be at least 9, + * but may be as large as needed. + */ +static int +get_namewidth_list(zpool_handle_t *zhp, void *data) +{ + list_cbdata_t *cb = data; + int width; + + width = get_namewidth(zhp, cb->cb_namewidth, cb->cb_name_flags, + cb->cb_verbose); + + if (width < 9) + width = 9; + + cb->cb_namewidth = width; return (0); } @@ -4641,7 +5828,7 @@ list_callback(zpool_handle_t *zhp, void *data) * -o List of properties to display. Defaults to * "name,size,allocated,free,expandsize,fragmentation,capacity," * "dedupratio,health,altroot" - * -p Display values in parsable (exact) format. + * -p Display values in parsable (exact) format. * -P Display full path for vdev name. * -T Display a timestamp in date(1) or Unix format * @@ -4655,8 +5842,8 @@ zpool_do_list(int argc, char **argv) int ret = 0; list_cbdata_t cb = { 0 }; static char default_props[] = - "name,size,allocated,free,expandsize,fragmentation,capacity," - "dedupratio,health,altroot"; + "name,size,allocated,free,checkpoint,expandsize,fragmentation," + "capacity,dedupratio,health,altroot"; char *props = default_props; float interval = 0; unsigned long count = 0; @@ -4689,6 +5876,7 @@ zpool_do_list(int argc, char **argv) break; case 'v': cb.cb_verbose = B_TRUE; + cb.cb_namewidth = 8; /* 8 until precalc is avail */ break; case ':': (void) fprintf(stderr, gettext("missing argument for " @@ -4718,6 +5906,9 @@ zpool_do_list(int argc, char **argv) if (pool_list_count(list) == 0) break; + cb.cb_namewidth = 0; + (void) pool_list_iter(list, B_FALSE, get_namewidth_list, &cb); + if (timestamp_fmt != NODATE) print_timestamp(timestamp_fmt); @@ -4837,6 +6028,20 @@ zpool_do_attach_or_replace(int argc, char **argv, int replacing) return (1); } + /* unless manually specified use "ashift" pool property (if set) */ + if (!nvlist_exists(props, ZPOOL_CONFIG_ASHIFT)) { + int intval; + zprop_source_t src; + char strval[ZPOOL_MAXPROPLEN]; + + intval = zpool_get_prop_int(zhp, ZPOOL_PROP_ASHIFT, &src); + if (src != ZPROP_SRC_DEFAULT) { + (void) sprintf(strval, "%" PRId32, intval); + verify(add_prop_list(ZPOOL_CONFIG_ASHIFT, strval, + &props, B_TRUE) == 0); + } + } + nvroot = make_root_vdev(zhp, props, force, B_FALSE, replacing, B_FALSE, argc, argv); if (nvroot == NULL) { @@ -4955,6 +6160,7 @@ zpool_do_detach(int argc, char **argv) * -o Set property=value, or set mount options. * -P Display full path for vdev name. * -R Mount the split-off pool under an alternate root. + * -l Load encryption keys while importing. * * Splits the named pool and gives it the new pool name. Devices to be split * off may be listed, provided that no more than one device is specified @@ -4972,6 +6178,7 @@ zpool_do_split(int argc, char **argv) char *mntopts = NULL; splitflags_t flags; int c, ret = 0; + boolean_t loadkeys = B_FALSE; zpool_handle_t *zhp; nvlist_t *config, *props = NULL; @@ -4980,7 +6187,7 @@ zpool_do_split(int argc, char **argv) flags.name_flags = 0; /* check options */ - while ((c = getopt(argc, argv, ":gLR:no:P")) != -1) { + while ((c = getopt(argc, argv, ":gLR:lno:P")) != -1) { switch (c) { case 'g': flags.name_flags |= VDEV_NAME_GUID; @@ -4997,6 +6204,9 @@ zpool_do_split(int argc, char **argv) usage(B_FALSE); } break; + case 'l': + loadkeys = B_TRUE; + break; case 'n': flags.dryrun = B_TRUE; break; @@ -5035,6 +6245,12 @@ zpool_do_split(int argc, char **argv) usage(B_FALSE); } + if (!flags.import && loadkeys) { + (void) fprintf(stderr, gettext("loading keys is only " + "valid when importing the pool\n")); + usage(B_FALSE); + } + argc -= optind; argv += optind; @@ -5065,7 +6281,7 @@ zpool_do_split(int argc, char **argv) if (flags.dryrun) { (void) printf(gettext("would create '%s' with the " "following layout:\n\n"), newpool); - print_vdev_tree(NULL, newpool, config, 0, B_FALSE, + print_vdev_tree(NULL, newpool, config, 0, "", flags.name_flags); } } @@ -5087,6 +6303,13 @@ zpool_do_split(int argc, char **argv) nvlist_free(props); return (1); } + + if (loadkeys) { + ret = zfs_crypto_attempt_load_keys(g_zfs, newpool); + if (ret != 0) + ret = 1; + } + if (zpool_get_state(zhp) != POOL_STATE_UNAVAIL && zpool_enable_datasets(zhp, mntopts, 0) != 0) { ret = 1; @@ -5177,11 +6400,9 @@ zpool_do_online(int argc, char **argv) /* * zpool offline [-ft] ... * - * -f Force the device into the offline state, even if doing - * so would appear to compromise pool availability. - * (not supported yet) + * -f Force the device into a faulted state. * - * -t Only take the device off-line temporarily. The offline + * -t Only take the device off-line temporarily. The offline/faulted * state will not be persistent across reboots. */ /* ARGSUSED */ @@ -5193,14 +6414,17 @@ zpool_do_offline(int argc, char **argv) zpool_handle_t *zhp; int ret = 0; boolean_t istmp = B_FALSE; + boolean_t fault = B_FALSE; /* check options */ while ((c = getopt(argc, argv, "ft")) != -1) { switch (c) { + case 'f': + fault = B_TRUE; + break; case 't': istmp = B_TRUE; break; - case 'f': case '?': (void) fprintf(stderr, gettext("invalid option '%c'\n"), optopt); @@ -5227,8 +6451,22 @@ zpool_do_offline(int argc, char **argv) return (1); for (i = 1; i < argc; i++) { - if (zpool_vdev_offline(zhp, argv[i], istmp) != 0) - ret = 1; + if (fault) { + uint64_t guid = zpool_vdev_path_to_guid(zhp, argv[i]); + vdev_aux_t aux; + if (istmp == B_FALSE) { + /* Force the fault to persist across imports */ + aux = VDEV_AUX_EXTERNAL_PERSIST; + } else { + aux = VDEV_AUX_EXTERNAL; + } + + if (guid == 0 || zpool_vdev_fault(zhp, guid, aux) != 0) + ret = 1; + } else { + if (zpool_vdev_offline(zhp, argv[i], istmp) != 0) + ret = 1; + } } zpool_close(zhp); @@ -5300,8 +6538,10 @@ zpool_do_clear(int argc, char **argv) /* In future, further rewind policy choices can be passed along here */ if (nvlist_alloc(&policy, NV_UNIQUE_NAME, 0) != 0 || - nvlist_add_uint32(policy, ZPOOL_REWIND_REQUEST, rewind_policy) != 0) + nvlist_add_uint32(policy, ZPOOL_LOAD_REWIND_POLICY, + rewind_policy) != 0) { return (1); + } pool = argv[0]; device = argc == 2 ? argv[1] : NULL; @@ -5377,12 +6617,14 @@ zpool_do_reopen(int argc, char **argv) { int c; int ret = 0; - zpool_handle_t *zhp; - char *pool; + boolean_t scrub_restart = B_TRUE; /* check options */ - while ((c = getopt(argc, argv, "")) != -1) { + while ((c = getopt(argc, argv, "n")) != -1) { switch (c) { + case 'n': + scrub_restart = B_FALSE; + break; case '?': (void) fprintf(stderr, gettext("invalid option '%c'\n"), optopt); @@ -5390,25 +6632,13 @@ zpool_do_reopen(int argc, char **argv) } } - argc--; - argv++; - - if (argc < 1) { - (void) fprintf(stderr, gettext("missing pool name\n")); - usage(B_FALSE); - } - - if (argc > 1) { - (void) fprintf(stderr, gettext("too many arguments\n")); - usage(B_FALSE); - } + argc -= optind; + argv += optind; - pool = argv[0]; - if ((zhp = zpool_open_canfail(g_zfs, pool)) == NULL) - return (1); + /* if argc == 0 we will execute zpool_reopen_one on all pools */ + ret = for_each_pool(argc, argv, B_TRUE, NULL, zpool_reopen_one, + &scrub_restart); - ret = zpool_reopen(zhp); - zpool_close(zhp); return (ret); } @@ -5416,8 +6646,35 @@ typedef struct scrub_cbdata { int cb_type; int cb_argc; char **cb_argv; + pool_scrub_cmd_t cb_scrub_cmd; } scrub_cbdata_t; +static boolean_t +zpool_has_checkpoint(zpool_handle_t *zhp) +{ + nvlist_t *config, *nvroot; + + config = zpool_get_config(zhp, NULL); + + if (config != NULL) { + pool_checkpoint_stat_t *pcs = NULL; + uint_t c; + + nvroot = fnvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE); + (void) nvlist_lookup_uint64_array(nvroot, + ZPOOL_CONFIG_CHECKPOINT_STATS, (uint64_t **)&pcs, &c); + + if (pcs == NULL || pcs->pcs_state == CS_NONE) + return (B_FALSE); + + assert(pcs->pcs_state == CS_CHECKPOINT_EXISTS || + pcs->pcs_state == CS_CHECKPOINT_DISCARDING); + return (B_TRUE); + } + + return (B_FALSE); +} + int scrub_callback(zpool_handle_t *zhp, void *data) { @@ -5428,35 +6685,93 @@ scrub_callback(zpool_handle_t *zhp, void *data) * Ignore faulted pools. */ if (zpool_get_state(zhp) == POOL_STATE_UNAVAIL) { - (void) fprintf(stderr, gettext("cannot scrub '%s': pool is " + (void) fprintf(stderr, gettext("cannot scan '%s': pool is " "currently unavailable\n"), zpool_get_name(zhp)); return (1); } - err = zpool_scan(zhp, cb->cb_type); - - return (err != 0); + err = zpool_scan(zhp, cb->cb_type, cb->cb_scrub_cmd); + + if (err == 0 && zpool_has_checkpoint(zhp) && + cb->cb_type == POOL_SCAN_SCRUB) { + (void) printf(gettext("warning: will not scrub state that " + "belongs to the checkpoint of pool '%s'\n"), + zpool_get_name(zhp)); + } + + return (err != 0); +} + +/* + * zpool scrub [-s | -p] ... + * + * -s Stop. Stops any in-progress scrub. + * -p Pause. Pause in-progress scrub. + */ +int +zpool_do_scrub(int argc, char **argv) +{ + int c; + scrub_cbdata_t cb; + + cb.cb_type = POOL_SCAN_SCRUB; + cb.cb_scrub_cmd = POOL_SCRUB_NORMAL; + + /* check options */ + while ((c = getopt(argc, argv, "sp")) != -1) { + switch (c) { + case 's': + cb.cb_type = POOL_SCAN_NONE; + break; + case 'p': + cb.cb_scrub_cmd = POOL_SCRUB_PAUSE; + break; + case '?': + (void) fprintf(stderr, gettext("invalid option '%c'\n"), + optopt); + usage(B_FALSE); + } + } + + if (cb.cb_type == POOL_SCAN_NONE && + cb.cb_scrub_cmd == POOL_SCRUB_PAUSE) { + (void) fprintf(stderr, gettext("invalid option combination: " + "-s and -p are mutually exclusive\n")); + usage(B_FALSE); + } + + cb.cb_argc = argc; + cb.cb_argv = argv; + argc -= optind; + argv += optind; + + if (argc < 1) { + (void) fprintf(stderr, gettext("missing pool name argument\n")); + usage(B_FALSE); + } + + return (for_each_pool(argc, argv, B_TRUE, NULL, scrub_callback, &cb)); } /* - * zpool scrub [-s] ... + * zpool resilver ... * - * -s Stop. Stops any in-progress scrub. + * Restarts any in-progress resilver */ int -zpool_do_scrub(int argc, char **argv) +zpool_do_resilver(int argc, char **argv) { int c; scrub_cbdata_t cb; - cb.cb_type = POOL_SCAN_SCRUB; + cb.cb_type = POOL_SCAN_RESILVER; + cb.cb_scrub_cmd = POOL_SCRUB_NORMAL; + cb.cb_argc = argc; + cb.cb_argv = argv; /* check options */ - while ((c = getopt(argc, argv, "s")) != -1) { + while ((c = getopt(argc, argv, "")) != -1) { switch (c) { - case 's': - cb.cb_type = POOL_SCAN_NONE; - break; case '?': (void) fprintf(stderr, gettext("invalid option '%c'\n"), optopt); @@ -5464,8 +6779,6 @@ zpool_do_scrub(int argc, char **argv) } } - cb.cb_argc = argc; - cb.cb_argv = argv; argc -= optind; argv += optind; @@ -5477,18 +6790,141 @@ zpool_do_scrub(int argc, char **argv) return (for_each_pool(argc, argv, B_TRUE, NULL, scrub_callback, &cb)); } +/* + * zpool trim [-d] [-r ] [-c | -s] [ ...] + * + * -c Cancel. Ends any in-progress trim. + * -d Secure trim. Requires kernel and device support. + * -r Sets the TRIM rate in bytes (per second). Supports + * adding a multiplier suffix such as 'k' or 'm'. + * -s Suspend. TRIM can then be restarted with no flags. + */ +int +zpool_do_trim(int argc, char **argv) +{ + struct option long_options[] = { + {"cancel", no_argument, NULL, 'c'}, + {"secure", no_argument, NULL, 'd'}, + {"rate", required_argument, NULL, 'r'}, + {"suspend", no_argument, NULL, 's'}, + {0, 0, 0, 0} + }; + + pool_trim_func_t cmd_type = POOL_TRIM_START; + uint64_t rate = 0; + boolean_t secure = B_FALSE; + + int c; + while ((c = getopt_long(argc, argv, "cdr:s", long_options, NULL)) + != -1) { + switch (c) { + case 'c': + if (cmd_type != POOL_TRIM_START && + cmd_type != POOL_TRIM_CANCEL) { + (void) fprintf(stderr, gettext("-c cannot be " + "combined with other options\n")); + usage(B_FALSE); + } + cmd_type = POOL_TRIM_CANCEL; + break; + case 'd': + if (cmd_type != POOL_TRIM_START) { + (void) fprintf(stderr, gettext("-d cannot be " + "combined with the -c or -s options\n")); + usage(B_FALSE); + } + secure = B_TRUE; + break; + case 'r': + if (cmd_type != POOL_TRIM_START) { + (void) fprintf(stderr, gettext("-r cannot be " + "combined with the -c or -s options\n")); + usage(B_FALSE); + } + if (zfs_nicestrtonum(NULL, optarg, &rate) == -1) { + (void) fprintf(stderr, + gettext("invalid value for rate\n")); + usage(B_FALSE); + } + break; + case 's': + if (cmd_type != POOL_TRIM_START && + cmd_type != POOL_TRIM_SUSPEND) { + (void) fprintf(stderr, gettext("-s cannot be " + "combined with other options\n")); + usage(B_FALSE); + } + cmd_type = POOL_TRIM_SUSPEND; + break; + case '?': + if (optopt != 0) { + (void) fprintf(stderr, + gettext("invalid option '%c'\n"), optopt); + } else { + (void) fprintf(stderr, + gettext("invalid option '%s'\n"), + argv[optind - 1]); + } + usage(B_FALSE); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1) { + (void) fprintf(stderr, gettext("missing pool name argument\n")); + usage(B_FALSE); + return (-1); + } + + char *poolname = argv[0]; + zpool_handle_t *zhp = zpool_open(g_zfs, poolname); + if (zhp == NULL) + return (-1); + + trimflags_t trim_flags = { + .secure = secure, + .rate = rate, + }; + + nvlist_t *vdevs = fnvlist_alloc(); + if (argc == 1) { + /* no individual leaf vdevs specified, so add them all */ + nvlist_t *config = zpool_get_config(zhp, NULL); + nvlist_t *nvroot = fnvlist_lookup_nvlist(config, + ZPOOL_CONFIG_VDEV_TREE); + zpool_collect_leaves(zhp, nvroot, vdevs); + trim_flags.fullpool = B_TRUE; + } else { + trim_flags.fullpool = B_FALSE; + for (int i = 1; i < argc; i++) { + fnvlist_add_boolean(vdevs, argv[i]); + } + } + + int error = zpool_trim(zhp, cmd_type, vdevs, &trim_flags); + + fnvlist_free(vdevs); + zpool_close(zhp); + + return (error); +} + /* * Print out detailed scrub status. */ -void +static void print_scan_status(pool_scan_stat_t *ps) { - time_t start, end; - uint64_t elapsed, mins_left, hours_left; - uint64_t pass_exam, examined, total; - uint_t rate; + time_t start, end, pause; + uint64_t total_secs_left; + uint64_t elapsed, secs_left, mins_left, hours_left, days_left; + uint64_t pass_scanned, scanned, pass_issued, issued, total; + uint64_t scan_rate, issue_rate; double fraction_done; - char processed_buf[7], examined_buf[7], total_buf[7], rate_buf[7]; + char processed_buf[7], scanned_buf[7], issued_buf[7], total_buf[7]; + char srate_buf[7], irate_buf[7]; (void) printf(gettext(" scan: ")); @@ -5501,30 +6937,36 @@ print_scan_status(pool_scan_stat_t *ps) start = ps->pss_start_time; end = ps->pss_end_time; - zfs_nicenum(ps->pss_processed, processed_buf, sizeof (processed_buf)); + pause = ps->pss_pass_scrub_pause; + + zfs_nicebytes(ps->pss_processed, processed_buf, sizeof (processed_buf)); assert(ps->pss_func == POOL_SCAN_SCRUB || ps->pss_func == POOL_SCAN_RESILVER); - /* - * Scan is finished or canceled. - */ + + /* Scan is finished or canceled. */ if (ps->pss_state == DSS_FINISHED) { - uint64_t minutes_taken = (end - start) / 60; - char *fmt = NULL; + total_secs_left = end - start; + days_left = total_secs_left / 60 / 60 / 24; + hours_left = (total_secs_left / 60 / 60) % 24; + mins_left = (total_secs_left / 60) % 60; + secs_left = (total_secs_left % 60); if (ps->pss_func == POOL_SCAN_SCRUB) { - fmt = gettext("scrub repaired %s in %lluh%um with " - "%llu errors on %s"); + (void) printf(gettext("scrub repaired %s " + "in %llu days %02llu:%02llu:%02llu " + "with %llu errors on %s"), processed_buf, + (u_longlong_t)days_left, (u_longlong_t)hours_left, + (u_longlong_t)mins_left, (u_longlong_t)secs_left, + (u_longlong_t)ps->pss_errors, ctime(&end)); } else if (ps->pss_func == POOL_SCAN_RESILVER) { - fmt = gettext("resilvered %s in %lluh%um with " - "%llu errors on %s"); + (void) printf(gettext("resilvered %s " + "in %llu days %02llu:%02llu:%02llu " + "with %llu errors on %s"), processed_buf, + (u_longlong_t)days_left, (u_longlong_t)hours_left, + (u_longlong_t)mins_left, (u_longlong_t)secs_left, + (u_longlong_t)ps->pss_errors, ctime(&end)); } - /* LINTED */ - (void) printf(fmt, processed_buf, - (u_longlong_t)(minutes_taken / 60), - (uint_t)(minutes_taken % 60), - (u_longlong_t)ps->pss_errors, - ctime((time_t *)&end)); return; } else if (ps->pss_state == DSS_CANCELED) { if (ps->pss_func == POOL_SCAN_SCRUB) { @@ -5539,54 +6981,257 @@ print_scan_status(pool_scan_stat_t *ps) assert(ps->pss_state == DSS_SCANNING); - /* - * Scan is in progress. - */ + /* Scan is in progress. Resilvers can't be paused. */ if (ps->pss_func == POOL_SCAN_SCRUB) { - (void) printf(gettext("scrub in progress since %s"), - ctime(&start)); + if (pause == 0) { + (void) printf(gettext("scrub in progress since %s"), + ctime(&start)); + } else { + (void) printf(gettext("scrub paused since %s"), + ctime(&pause)); + (void) printf(gettext("\tscrub started on %s"), + ctime(&start)); + } } else if (ps->pss_func == POOL_SCAN_RESILVER) { (void) printf(gettext("resilver in progress since %s"), ctime(&start)); } - examined = ps->pss_examined ? ps->pss_examined : 1; + scanned = ps->pss_examined; + pass_scanned = ps->pss_pass_exam; + issued = ps->pss_issued; + pass_issued = ps->pss_pass_issued; total = ps->pss_to_examine; - fraction_done = (double)examined / total; - /* elapsed time for this pass */ + /* we are only done with a block once we have issued the IO for it */ + fraction_done = (double)issued / total; + + /* elapsed time for this pass, rounding up to 1 if it's 0 */ elapsed = time(NULL) - ps->pss_pass_start; - elapsed = elapsed ? elapsed : 1; - pass_exam = ps->pss_pass_exam ? ps->pss_pass_exam : 1; - rate = pass_exam / elapsed; - rate = rate ? rate : 1; - mins_left = ((total - examined) / rate) / 60; - hours_left = mins_left / 60; + elapsed -= ps->pss_pass_scrub_spent_paused; + elapsed = (elapsed != 0) ? elapsed : 1; + + scan_rate = pass_scanned / elapsed; + issue_rate = pass_issued / elapsed; + total_secs_left = (issue_rate != 0 && total >= issued) ? + ((total - issued) / issue_rate) : UINT64_MAX; + + days_left = total_secs_left / 60 / 60 / 24; + hours_left = (total_secs_left / 60 / 60) % 24; + mins_left = (total_secs_left / 60) % 60; + secs_left = (total_secs_left % 60); + + /* format all of the numbers we will be reporting */ + zfs_nicebytes(scanned, scanned_buf, sizeof (scanned_buf)); + zfs_nicebytes(issued, issued_buf, sizeof (issued_buf)); + zfs_nicebytes(total, total_buf, sizeof (total_buf)); + zfs_nicebytes(scan_rate, srate_buf, sizeof (srate_buf)); + zfs_nicebytes(issue_rate, irate_buf, sizeof (irate_buf)); + + /* do not print estimated time if we have a paused scrub */ + if (pause == 0) { + (void) printf(gettext("\t%s scanned at %s/s, " + "%s issued at %s/s, %s total\n"), + scanned_buf, srate_buf, issued_buf, irate_buf, total_buf); + } else { + (void) printf(gettext("\t%s scanned, %s issued, %s total\n"), + scanned_buf, issued_buf, total_buf); + } + + if (ps->pss_func == POOL_SCAN_RESILVER) { + (void) printf(gettext("\t%s resilvered, %.2f%% done"), + processed_buf, 100 * fraction_done); + } else if (ps->pss_func == POOL_SCAN_SCRUB) { + (void) printf(gettext("\t%s repaired, %.2f%% done"), + processed_buf, 100 * fraction_done); + } + + if (pause == 0) { + if (total_secs_left != UINT64_MAX && + issue_rate >= 10 * 1024 * 1024) { + (void) printf(gettext(", %llu days " + "%02llu:%02llu:%02llu to go\n"), + (u_longlong_t)days_left, (u_longlong_t)hours_left, + (u_longlong_t)mins_left, (u_longlong_t)secs_left); + } else { + (void) printf(gettext(", no estimated " + "completion time\n")); + } + } else { + (void) printf(gettext("\n")); + } +} + +/* + * As we don't scrub checkpointed blocks, we want to warn the + * user that we skipped scanning some blocks if a checkpoint exists + * or existed at any time during the scan. + */ +static void +print_checkpoint_scan_warning(pool_scan_stat_t *ps, pool_checkpoint_stat_t *pcs) +{ + if (ps == NULL || pcs == NULL) + return; + + if (pcs->pcs_state == CS_NONE || + pcs->pcs_state == CS_CHECKPOINT_DISCARDING) + return; + + assert(pcs->pcs_state == CS_CHECKPOINT_EXISTS); + + if (ps->pss_state == DSS_NONE) + return; + + if ((ps->pss_state == DSS_FINISHED || ps->pss_state == DSS_CANCELED) && + ps->pss_end_time < pcs->pcs_start_time) + return; + + if (ps->pss_state == DSS_FINISHED || ps->pss_state == DSS_CANCELED) { + (void) printf(gettext(" scan warning: skipped blocks " + "that are only referenced by the checkpoint.\n")); + } else { + assert(ps->pss_state == DSS_SCANNING); + (void) printf(gettext(" scan warning: skipping blocks " + "that are only referenced by the checkpoint.\n")); + } +} + +/* + * Print out detailed removal status. + */ +static void +print_removal_status(zpool_handle_t *zhp, pool_removal_stat_t *prs) +{ + char copied_buf[7], examined_buf[7], total_buf[7], rate_buf[7]; + time_t start, end; + nvlist_t *config, *nvroot; + nvlist_t **child; + uint_t children; + char *vdev_name; + + if (prs == NULL || prs->prs_state == DSS_NONE) + return; + + /* + * Determine name of vdev. + */ + config = zpool_get_config(zhp, NULL); + nvroot = fnvlist_lookup_nvlist(config, + ZPOOL_CONFIG_VDEV_TREE); + verify(nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN, + &child, &children) == 0); + assert(prs->prs_removing_vdev < children); + vdev_name = zpool_vdev_name(g_zfs, zhp, + child[prs->prs_removing_vdev], B_TRUE); + + (void) printf(gettext("remove: ")); - zfs_nicenum(examined, examined_buf, sizeof (examined_buf)); - zfs_nicenum(total, total_buf, sizeof (total_buf)); - zfs_nicenum(rate, rate_buf, sizeof (rate_buf)); + start = prs->prs_start_time; + end = prs->prs_end_time; + zfs_nicenum(prs->prs_copied, copied_buf, sizeof (copied_buf)); /* - * do not print estimated time if hours_left is more than 30 days + * Removal is finished or canceled. */ - (void) printf(gettext("\t%s scanned out of %s at %s/s"), - examined_buf, total_buf, rate_buf); - if (hours_left < (30 * 24)) { - (void) printf(gettext(", %lluh%um to go\n"), - (u_longlong_t)hours_left, (uint_t)(mins_left % 60)); + if (prs->prs_state == DSS_FINISHED) { + uint64_t minutes_taken = (end - start) / 60; + + (void) printf(gettext("Removal of vdev %llu copied %s " + "in %lluh%um, completed on %s"), + (longlong_t)prs->prs_removing_vdev, + copied_buf, + (u_longlong_t)(minutes_taken / 60), + (uint_t)(minutes_taken % 60), + ctime((time_t *)&end)); + } else if (prs->prs_state == DSS_CANCELED) { + (void) printf(gettext("Removal of %s canceled on %s"), + vdev_name, ctime(&end)); } else { + uint64_t copied, total, elapsed, mins_left, hours_left; + double fraction_done; + uint_t rate; + + assert(prs->prs_state == DSS_SCANNING); + + /* + * Removal is in progress. + */ (void) printf(gettext( - ", (scan is slow, no estimated time)\n")); + "Evacuation of %s in progress since %s"), + vdev_name, ctime(&start)); + + copied = prs->prs_copied > 0 ? prs->prs_copied : 1; + total = prs->prs_to_copy; + fraction_done = (double)copied / total; + + /* elapsed time for this pass */ + elapsed = time(NULL) - prs->prs_start_time; + elapsed = elapsed > 0 ? elapsed : 1; + rate = copied / elapsed; + rate = rate > 0 ? rate : 1; + mins_left = ((total - copied) / rate) / 60; + hours_left = mins_left / 60; + + zfs_nicenum(copied, examined_buf, sizeof (examined_buf)); + zfs_nicenum(total, total_buf, sizeof (total_buf)); + zfs_nicenum(rate, rate_buf, sizeof (rate_buf)); + + /* + * do not print estimated time if hours_left is more than + * 30 days + */ + (void) printf(gettext(" %s copied out of %s at %s/s, " + "%.2f%% done"), + examined_buf, total_buf, rate_buf, 100 * fraction_done); + if (hours_left < (30 * 24)) { + (void) printf(gettext(", %lluh%um to go\n"), + (u_longlong_t)hours_left, (uint_t)(mins_left % 60)); + } else { + (void) printf(gettext( + ", (copy is slow, no estimated time)\n")); + } } - if (ps->pss_func == POOL_SCAN_RESILVER) { - (void) printf(gettext("\t%s resilvered, %.2f%% done\n"), - processed_buf, 100 * fraction_done); - } else if (ps->pss_func == POOL_SCAN_SCRUB) { - (void) printf(gettext("\t%s repaired, %.2f%% done\n"), - processed_buf, 100 * fraction_done); + if (prs->prs_mapping_memory > 0) { + char mem_buf[7]; + zfs_nicenum(prs->prs_mapping_memory, mem_buf, sizeof (mem_buf)); + (void) printf(gettext(" %s memory used for " + "removed device mappings\n"), + mem_buf); + } +} + +static void +print_checkpoint_status(pool_checkpoint_stat_t *pcs) +{ + time_t start; + char space_buf[7]; + + if (pcs == NULL || pcs->pcs_state == CS_NONE) + return; + + (void) printf(gettext("checkpoint: ")); + + start = pcs->pcs_start_time; + zfs_nicenum(pcs->pcs_space, space_buf, sizeof (space_buf)); + + if (pcs->pcs_state == CS_CHECKPOINT_EXISTS) { + char *date = ctime(&start); + + /* + * ctime() adds a newline at the end of the generated + * string, thus the weird format specifier and the + * strlen() call used to chop it off from the output. + */ + (void) printf(gettext("created %.*s, consumes %s\n"), + (int)(strlen(date) - 1), date, space_buf); + return; } + + assert(pcs->pcs_state == CS_CHECKPOINT_DISCARDING); + + (void) printf(gettext("discarding, %s remaining.\n"), + space_buf); } static void @@ -5597,11 +7242,8 @@ print_error_log(zpool_handle_t *zhp) char *pathname; size_t len = MAXPATHLEN * 2; - if (zpool_get_errlog(zhp, &nverrlist) != 0) { - (void) printf("errors: List of errors unavailable " - "(insufficient privileges)\n"); + if (zpool_get_errlog(zhp, &nverrlist) != 0) return; - } (void) printf("errors: Permanent errors have been " "detected in the following files:\n\n"); @@ -5671,6 +7313,7 @@ print_dedup_stats(nvlist_t *config) ddt_stat_t *dds; ddt_object_t *ddo; uint_t c; + char dspace[6], mspace[6]; /* * If the pool was faulted then we may not have been able to @@ -5688,10 +7331,12 @@ print_dedup_stats(nvlist_t *config) return; } - (void) printf("DDT entries %llu, size %llu on disk, %llu in core\n", + zfs_nicebytes(ddo->ddo_dspace, dspace, sizeof (dspace)); + zfs_nicebytes(ddo->ddo_mspace, mspace, sizeof (mspace)); + (void) printf("DDT entries %llu, size %s on disk, %s in core\n", (u_longlong_t)ddo->ddo_count, - (u_longlong_t)ddo->ddo_dspace, - (u_longlong_t)ddo->ddo_mspace); + dspace, + mspace); verify(nvlist_lookup_uint64_array(config, ZPOOL_CONFIG_DDT_STATS, (uint64_t **)&dds, &c) == 0); @@ -5754,11 +7399,11 @@ status_callback(zpool_handle_t *zhp, void *data) else (void) printf("\n"); - verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, - &nvroot) == 0); + nvroot = fnvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE); verify(nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_VDEV_STATS, (uint64_t **)&vs, &c) == 0); - health = zpool_state_to_name(vs->vs_state, vs->vs_aux); + + health = zpool_get_state_str(zhp); (void) printf(gettext(" pool: %s\n"), zpool_get_name(zhp)); (void) printf(gettext(" state: %s\n"), health); @@ -5927,6 +7572,15 @@ status_callback(zpool_handle_t *zhp, void *data) "to be recovered.\n")); break; + case ZPOOL_STATUS_IO_FAILURE_MMP: + (void) printf(gettext("status: The pool is suspended because " + "multihost writes failed or were delayed;\n\tanother " + "system could import the pool undetected.\n")); + (void) printf(gettext("action: Make sure the pool's devices " + "are connected, then reboot your system and\n\timport the " + "pool.\n")); + break; + case ZPOOL_STATUS_IO_FAILURE_WAIT: case ZPOOL_STATUS_IO_FAILURE_CONTINUE: (void) printf(gettext("status: One or more devices are " @@ -5970,6 +7624,32 @@ status_callback(zpool_handle_t *zhp, void *data) "run 'zpool scrub'.\n")); break; + case ZPOOL_ERRATA_ZOL_6845_ENCRYPTION: + (void) printf(gettext("\tExisting encrypted datasets " + "contain an on-disk incompatibility\n\twhich " + "needs to be corrected.\n")); + (void) printf(gettext("action: To correct the issue " + "backup existing encrypted datasets to new\n\t" + "encrypted datasets and destroy the old ones. " + "'zfs mount -o ro' can\n\tbe used to temporarily " + "mount existing encrypted datasets readonly.\n")); + break; + + case ZPOOL_ERRATA_ZOL_8308_ENCRYPTION: + (void) printf(gettext("\tExisting encrypted snapshots " + "and bookmarks contain an on-disk\n\tincompat" + "ibility. This may cause on-disk corruption if " + "they are used\n\twith 'zfs recv'.\n")); + (void) printf(gettext("action: To correct the issue, " + "enable the bookmark_v2 feature. No additional\n\t" + "action is needed if there are no encrypted " + "snapshots or bookmarks.\n\tIf preserving the " + "encrypted snapshots and bookmarks is required, " + "use\n\ta non-raw send to backup and restore them. " + "Alternately, they may be\n\tremoved to resolve " + "the incompatibility.\n")); + break; + default: /* * All errata which allow the pool to be imported @@ -5994,11 +7674,21 @@ status_callback(zpool_handle_t *zhp, void *data) uint64_t nerr; nvlist_t **spares, **l2cache; uint_t nspares, nl2cache; + pool_checkpoint_stat_t *pcs = NULL; pool_scan_stat_t *ps = NULL; + pool_removal_stat_t *prs = NULL; + (void) nvlist_lookup_uint64_array(nvroot, + ZPOOL_CONFIG_CHECKPOINT_STATS, (uint64_t **)&pcs, &c); (void) nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_SCAN_STATS, (uint64_t **)&ps, &c); + (void) nvlist_lookup_uint64_array(nvroot, + ZPOOL_CONFIG_REMOVAL_STATS, (uint64_t **)&prs, &c); + print_scan_status(ps); + print_checkpoint_scan_warning(ps, pcs); + print_removal_status(zhp, prs); + print_checkpoint_status(pcs); cbp->cb_namewidth = max_width(zhp, nvroot, 0, 0, cbp->cb_name_flags | VDEV_NAME_TYPE_ID); @@ -6006,14 +7696,25 @@ status_callback(zpool_handle_t *zhp, void *data) cbp->cb_namewidth = 10; (void) printf(gettext("config:\n\n")); - (void) printf(gettext("\t%-*s %-8s %5s %5s %5s\n"), + (void) printf(gettext("\t%-*s %-8s %5s %5s %5s"), cbp->cb_namewidth, "NAME", "STATE", "READ", "WRITE", "CKSUM"); + + if (cbp->cb_print_slow_ios) + (void) printf(" %5s", gettext("SLOW")); + + if (cbp->vcdl != NULL) + print_cmd_columns(cbp->vcdl, 0); + + printf("\n"); + print_status_config(zhp, cbp, zpool_get_name(zhp), nvroot, 0, B_FALSE); - if (num_logs(nvroot) > 0) - print_logs(zhp, cbp, nvroot); + print_class_vdevs(zhp, cbp, nvroot, VDEV_ALLOC_BIAS_DEDUP); + print_class_vdevs(zhp, cbp, nvroot, VDEV_ALLOC_BIAS_SPECIAL); + print_class_vdevs(zhp, cbp, nvroot, VDEV_ALLOC_CLASS_LOGS); + if (nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_L2CACHE, &l2cache, &nl2cache) == 0) print_l2cache(zhp, cbp, l2cache, nl2cache); @@ -6068,15 +7769,20 @@ status_callback(zpool_handle_t *zhp, void *data) } /* - * zpool status [-c CMD] [-gLPvx] [-T d|u] [pool] ... [interval [count]] + * zpool status [-c [script1,script2,...]] [-igLpPstvx] [-T d|u] [pool] ... + * [interval [count]] * * -c CMD For each vdev, run command CMD + * -i Display vdev initialization status. * -g Display guid for individual vdev name. * -L Follow links when resolving vdev path name. + * -p Display values in parsable (exact) format. * -P Display full path for vdev name. + * -s Display slow IOs column. * -v Display complete error logs * -x Display only pools with potential problems * -D Display dedup status (undocumented) + * -t Display vdev TRIM status. * -T Display a timestamp in date(1) or Unix format * * Describes the health status of all pools or some subset. @@ -6092,20 +7798,50 @@ zpool_do_status(int argc, char **argv) char *cmd = NULL; /* check options */ - while ((c = getopt(argc, argv, "c:gLPvxDT:")) != -1) { + while ((c = getopt(argc, argv, "c:igLpPsvxDtT:")) != -1) { switch (c) { case 'c': + if (cmd != NULL) { + fprintf(stderr, + gettext("Can't set -c flag twice\n")); + exit(1); + } + + if (getenv("ZPOOL_SCRIPTS_ENABLED") != NULL && + !libzfs_envvar_is_set("ZPOOL_SCRIPTS_ENABLED")) { + fprintf(stderr, gettext( + "Can't run -c, disabled by " + "ZPOOL_SCRIPTS_ENABLED.\n")); + exit(1); + } + + if ((getuid() <= 0 || geteuid() <= 0) && + !libzfs_envvar_is_set("ZPOOL_SCRIPTS_AS_ROOT")) { + fprintf(stderr, gettext( + "Can't run -c with root privileges " + "unless ZPOOL_SCRIPTS_AS_ROOT is set.\n")); + exit(1); + } cmd = optarg; break; + case 'i': + cb.cb_print_vdev_init = B_TRUE; + break; case 'g': cb.cb_name_flags |= VDEV_NAME_GUID; break; case 'L': cb.cb_name_flags |= VDEV_NAME_FOLLOW_LINKS; break; + case 'p': + cb.cb_literal = B_TRUE; + break; case 'P': cb.cb_name_flags |= VDEV_NAME_PATH; break; + case 's': + cb.cb_print_slow_ios = B_TRUE; + break; case 'v': cb.cb_verbose = B_TRUE; break; @@ -6115,13 +7851,16 @@ zpool_do_status(int argc, char **argv) case 'D': cb.cb_dedup_stats = B_TRUE; break; + case 't': + cb.cb_print_vdev_trim = B_TRUE; + break; case 'T': get_timestamp_arg(*optarg); break; case '?': if (optopt == 'c') { - fprintf(stderr, - gettext("Missing CMD for -c\n")); + print_zpool_script_list("status"); + exit(0); } else { fprintf(stderr, gettext("invalid option '%c'\n"), optopt); @@ -6766,6 +8505,11 @@ get_history_one(zpool_handle_t *zhp, void *data) dump_nvlist(fnvlist_lookup_nvlist(rec, ZPOOL_HIST_OUTPUT_NVL), 8); } + if (nvlist_exists(rec, ZPOOL_HIST_ERRNO)) { + (void) printf(" errno: %lld\n", + (longlong_t)fnvlist_lookup_int64(rec, + ZPOOL_HIST_ERRNO)); + } } else { if (!cb->internal) continue; @@ -6850,10 +8594,11 @@ typedef struct ev_opts { int scripted; int follow; int clear; + char poolname[ZFS_MAX_DATASET_NAME_LEN]; } ev_opts_t; static void -zpool_do_events_short(nvlist_t *nvl) +zpool_do_events_short(nvlist_t *nvl, ev_opts_t *opts) { char ctime_str[26], str[32], *ptr; int64_t *tv; @@ -6866,7 +8611,10 @@ zpool_do_events_short(nvlist_t *nvl) (void) strncpy(str+7, ctime_str+20, 4); /* '1993' */ (void) strncpy(str+12, ctime_str+11, 8); /* '21:49:08' */ (void) sprintf(str+20, ".%09lld", (longlong_t)tv[1]); /* '.123456789' */ - (void) printf(gettext("%s "), str); + if (opts->scripted) + (void) printf(gettext("%s\t"), str); + else + (void) printf(gettext("%s "), str); verify(nvlist_lookup_string(nvl, FM_CLASS, &ptr) == 0); (void) printf(gettext("%s\n"), ptr); @@ -7100,6 +8848,7 @@ zpool_do_events_nvprint(nvlist_t *nvl, int depth) case DATA_TYPE_BOOLEAN_ARRAY: case DATA_TYPE_BYTE_ARRAY: case DATA_TYPE_DOUBLE: + case DATA_TYPE_DONTCARE: case DATA_TYPE_UNKNOWN: printf(gettext("")); break; @@ -7114,6 +8863,7 @@ zpool_do_events_next(ev_opts_t *opts) { nvlist_t *nvl; int zevent_fd, ret, dropped; + char *pool; zevent_fd = open(ZFS_DEV, O_RDWR); VERIFY(zevent_fd >= 0); @@ -7130,7 +8880,12 @@ zpool_do_events_next(ev_opts_t *opts) if (dropped > 0) (void) printf(gettext("dropped %d events\n"), dropped); - zpool_do_events_short(nvl); + if (strlen(opts->poolname) > 0 && + nvlist_lookup_string(nvl, FM_FMRI_ZFS_POOL, &pool) == 0 && + strcmp(opts->poolname, pool) != 0) + continue; + + zpool_do_events_short(nvl, opts); if (opts->verbose) { zpool_do_events_nvprint(nvl, 8); @@ -7159,7 +8914,7 @@ zpool_do_events_clear(ev_opts_t *opts) } /* - * zpool events [-vfc] + * zpool events [-vHf [pool] | -c] * * Displays events logs by ZFS. */ @@ -7194,6 +8949,25 @@ zpool_do_events(int argc, char **argv) argc -= optind; argv += optind; + if (argc > 1) { + (void) fprintf(stderr, gettext("too many arguments\n")); + usage(B_FALSE); + } else if (argc == 1) { + (void) strlcpy(opts.poolname, argv[0], sizeof (opts.poolname)); + if (!zfs_name_valid(opts.poolname, ZFS_TYPE_POOL)) { + (void) fprintf(stderr, + gettext("invalid pool name '%s'\n"), opts.poolname); + usage(B_FALSE); + } + } + + if ((argc == 1 || opts.verbose || opts.scripted || opts.follow) && + opts.clear) { + (void) fprintf(stderr, + gettext("invalid options combined with -c\n")); + usage(B_FALSE); + } + if (opts.clear) ret = zpool_do_events_clear(&opts); else @@ -7460,19 +9234,30 @@ find_command_idx(char *command, int *idx) return (1); } +/* + * Display version message + */ +static int +zpool_do_version(int argc, char **argv) +{ + if (zfs_version_print() == -1) + return (1); + + return (0); +} + int main(int argc, char **argv) { - int ret; + int ret = 0; int i = 0; char *cmdname; + char **newargv; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); srand(time(NULL)); - dprintf_setup(&argc, argv); - opterr = 0; /* @@ -7491,6 +9276,12 @@ main(int argc, char **argv) if ((strcmp(cmdname, "-?") == 0) || strcmp(cmdname, "--help") == 0) usage(B_TRUE); + /* + * Special case '-V|--version' + */ + if ((strcmp(cmdname, "-V") == 0) || (strcmp(cmdname, "--version") == 0)) + return (zpool_do_version(argc, argv)); + if ((g_zfs = libzfs_init()) == NULL) { (void) fprintf(stderr, "%s", libzfs_error_init(errno)); return (1); @@ -7500,25 +9291,41 @@ main(int argc, char **argv) zfs_save_arguments(argc, argv, history_str, sizeof (history_str)); + /* + * Many commands modify input strings for string parsing reasons. + * We create a copy to protect the original argv. + */ + newargv = malloc((argc + 1) * sizeof (newargv[0])); + for (i = 0; i < argc; i++) + newargv[i] = strdup(argv[i]); + newargv[argc] = NULL; + /* * Run the appropriate command. */ if (find_command_idx(cmdname, &i) == 0) { current_command = &command_table[i]; - ret = command_table[i].func(argc - 1, argv + 1); + ret = command_table[i].func(argc - 1, newargv + 1); } else if (strchr(cmdname, '=')) { verify(find_command_idx("set", &i) == 0); current_command = &command_table[i]; - ret = command_table[i].func(argc, argv); + ret = command_table[i].func(argc, newargv); } else if (strcmp(cmdname, "freeze") == 0 && argc == 3) { /* * 'freeze' is a vile debugging abomination, so we treat * it as such. */ - char buf[16384]; - int fd = open(ZFS_DEV, O_RDWR); - (void) strlcpy((void *)buf, argv[2], sizeof (buf)); - return (!!ioctl(fd, ZFS_IOC_POOL_FREEZE, buf)); + zfs_cmd_t zc = {"\0"}; + + (void) strlcpy(zc.zc_name, argv[2], sizeof (zc.zc_name)); + ret = zfs_ioctl(g_zfs, ZFS_IOC_POOL_FREEZE, &zc); + if (ret != 0) { + (void) fprintf(stderr, + gettext("failed to freeze pool: %d\n"), errno); + ret = 1; + } + + log_history = 0; } else { (void) fprintf(stderr, gettext("unrecognized " "command '%s'\n"), cmdname); @@ -7526,6 +9333,10 @@ main(int argc, char **argv) ret = 1; } + for (i = 0; i < argc; i++) + free(newargv[i]); + free(newargv); + if (ret == 0 && log_history) (void) zpool_log_history(g_zfs, history_str);