freebsd-nq/contrib/opensolaris/cmd/zpool/zpool_main.c
2007-04-08 16:29:25 +00:00

3603 lines
84 KiB
C

/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <solaris.h>
#include <assert.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <libintl.h>
#include <libuutil.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <priv.h>
#include <sys/time.h>
#include <sys/fs/zfs.h>
#include <sys/stat.h>
#include <libzfs.h>
#include "zpool_util.h"
static int zpool_do_create(int, char **);
static int zpool_do_destroy(int, char **);
static int zpool_do_add(int, char **);
static int zpool_do_remove(int, char **);
static int zpool_do_list(int, char **);
static int zpool_do_iostat(int, char **);
static int zpool_do_status(int, char **);
static int zpool_do_online(int, char **);
static int zpool_do_offline(int, char **);
static int zpool_do_clear(int, char **);
static int zpool_do_attach(int, char **);
static int zpool_do_detach(int, char **);
static int zpool_do_replace(int, char **);
static int zpool_do_scrub(int, char **);
static int zpool_do_import(int, char **);
static int zpool_do_export(int, char **);
static int zpool_do_upgrade(int, char **);
static int zpool_do_history(int, char **);
static int zpool_do_get(int, char **);
static int zpool_do_set(int, char **);
/*
* These libumem hooks provide a reasonable set of defaults for the allocator's
* debugging facilities.
*/
const char *
_umem_debug_init(void)
{
return ("default,verbose"); /* $UMEM_DEBUG setting */
}
const char *
_umem_logging_init(void)
{
return ("fail,contents"); /* $UMEM_LOGGING setting */
}
typedef enum {
HELP_ADD,
HELP_ATTACH,
HELP_CLEAR,
HELP_CREATE,
HELP_DESTROY,
HELP_DETACH,
HELP_EXPORT,
HELP_HISTORY,
HELP_IMPORT,
HELP_IOSTAT,
HELP_LIST,
HELP_OFFLINE,
HELP_ONLINE,
HELP_REPLACE,
HELP_REMOVE,
HELP_SCRUB,
HELP_STATUS,
HELP_UPGRADE,
HELP_GET,
HELP_SET
} zpool_help_t;
typedef struct zpool_command {
const char *name;
int (*func)(int, char **);
zpool_help_t usage;
} zpool_command_t;
/*
* Master command table. Each ZFS command has a name, associated function, and
* usage message. The usage messages need to be internationalized, so we have
* to have a function to return the usage message based on a command index.
*
* These commands are organized according to how they are displayed in the usage
* message. An empty command (one with a NULL name) indicates an empty line in
* the generic usage message.
*/
static zpool_command_t command_table[] = {
{ "create", zpool_do_create, HELP_CREATE },
{ "destroy", zpool_do_destroy, HELP_DESTROY },
{ NULL },
{ "add", zpool_do_add, HELP_ADD },
{ "remove", zpool_do_remove, HELP_REMOVE },
{ NULL },
{ "list", zpool_do_list, HELP_LIST },
{ "iostat", zpool_do_iostat, HELP_IOSTAT },
{ "status", zpool_do_status, HELP_STATUS },
{ NULL },
{ "online", zpool_do_online, HELP_ONLINE },
{ "offline", zpool_do_offline, HELP_OFFLINE },
{ "clear", zpool_do_clear, HELP_CLEAR },
{ NULL },
{ "attach", zpool_do_attach, HELP_ATTACH },
{ "detach", zpool_do_detach, HELP_DETACH },
{ "replace", zpool_do_replace, HELP_REPLACE },
{ NULL },
{ "scrub", zpool_do_scrub, HELP_SCRUB },
{ NULL },
{ "import", zpool_do_import, HELP_IMPORT },
{ "export", zpool_do_export, HELP_EXPORT },
{ "upgrade", zpool_do_upgrade, HELP_UPGRADE },
{ NULL },
{ "history", zpool_do_history, HELP_HISTORY },
{ "get", zpool_do_get, HELP_GET },
{ "set", zpool_do_set, HELP_SET },
};
#define NCOMMAND (sizeof (command_table) / sizeof (command_table[0]))
zpool_command_t *current_command;
static const char *
get_usage(zpool_help_t idx) {
switch (idx) {
case HELP_ADD:
return (gettext("\tadd [-fn] <pool> <vdev> ...\n"));
case HELP_ATTACH:
return (gettext("\tattach [-f] <pool> <device> "
"<new_device>\n"));
case HELP_CLEAR:
return (gettext("\tclear <pool> [device]\n"));
case HELP_CREATE:
return (gettext("\tcreate [-fn] [-R root] [-m mountpoint] "
"<pool> <vdev> ...\n"));
case HELP_DESTROY:
return (gettext("\tdestroy [-f] <pool>\n"));
case HELP_DETACH:
return (gettext("\tdetach <pool> <device>\n"));
case HELP_EXPORT:
return (gettext("\texport [-f] <pool> ...\n"));
case HELP_HISTORY:
return (gettext("\thistory [<pool>]\n"));
case HELP_IMPORT:
return (gettext("\timport [-d dir] [-D]\n"
"\timport [-d dir] [-D] [-f] [-o opts] [-R root] -a\n"
"\timport [-d dir] [-D] [-f] [-o opts] [-R root ]"
" <pool | id> [newpool]\n"));
case HELP_IOSTAT:
return (gettext("\tiostat [-v] [pool] ... [interval "
"[count]]\n"));
case HELP_LIST:
return (gettext("\tlist [-H] [-o field[,field]*] "
"[pool] ...\n"));
case HELP_OFFLINE:
return (gettext("\toffline [-t] <pool> <device> ...\n"));
case HELP_ONLINE:
return (gettext("\tonline <pool> <device> ...\n"));
case HELP_REPLACE:
return (gettext("\treplace [-f] <pool> <device> "
"[new_device]\n"));
case HELP_REMOVE:
return (gettext("\tremove <pool> <device>\n"));
case HELP_SCRUB:
return (gettext("\tscrub [-s] <pool> ...\n"));
case HELP_STATUS:
return (gettext("\tstatus [-vx] [pool] ...\n"));
case HELP_UPGRADE:
return (gettext("\tupgrade\n"
"\tupgrade -v\n"
"\tupgrade <-a | pool>\n"));
case HELP_GET:
return (gettext("\tget <all | property[,property]...> "
"<pool> ...\n"));
case HELP_SET:
return (gettext("\tset <property=value> <pool> \n"));
}
abort();
/* NOTREACHED */
}
/*
* Fields available for 'zpool list'.
*/
typedef enum {
ZPOOL_FIELD_NAME,
ZPOOL_FIELD_SIZE,
ZPOOL_FIELD_USED,
ZPOOL_FIELD_AVAILABLE,
ZPOOL_FIELD_CAPACITY,
ZPOOL_FIELD_HEALTH,
ZPOOL_FIELD_ROOT
} zpool_field_t;
#define MAX_FIELDS 10
typedef struct column_def {
const char *cd_title;
size_t cd_width;
enum {
left_justify,
right_justify
} cd_justify;
} column_def_t;
static column_def_t column_table[] = {
{ "NAME", 20, left_justify },
{ "SIZE", 6, right_justify },
{ "USED", 6, right_justify },
{ "AVAIL", 6, right_justify },
{ "CAP", 5, right_justify },
{ "HEALTH", 9, left_justify },
{ "ALTROOT", 15, left_justify }
};
static char *column_subopts[] = {
"name",
"size",
"used",
"available",
"capacity",
"health",
"root",
NULL
};
/*
* Callback routine that will print out a pool property value.
*/
static zpool_prop_t
print_prop_cb(zpool_prop_t prop, void *cb)
{
FILE *fp = cb;
(void) fprintf(fp, "\t%-13s ", zpool_prop_to_name(prop));
if (zpool_prop_values(prop) == NULL)
(void) fprintf(fp, "-\n");
else
(void) fprintf(fp, "%s\n", zpool_prop_values(prop));
return (ZFS_PROP_CONT);
}
/*
* Display usage message. If we're inside a command, display only the usage for
* that command. Otherwise, iterate over the entire command table and display
* a complete usage message.
*/
void
usage(boolean_t requested)
{
int i;
FILE *fp = requested ? stdout : stderr;
if (current_command == NULL) {
int i;
(void) fprintf(fp, gettext("usage: zpool command args ...\n"));
(void) fprintf(fp,
gettext("where 'command' is one of the following:\n\n"));
for (i = 0; i < NCOMMAND; i++) {
if (command_table[i].name == NULL)
(void) fprintf(fp, "\n");
else
(void) fprintf(fp, "%s",
get_usage(command_table[i].usage));
}
} else {
(void) fprintf(fp, gettext("usage:\n"));
(void) fprintf(fp, "%s", get_usage(current_command->usage));
if (strcmp(current_command->name, "list") == 0) {
(void) fprintf(fp, gettext("\nwhere 'field' is one "
"of the following:\n\n"));
for (i = 0; column_subopts[i] != NULL; i++)
(void) fprintf(fp, "\t%s\n", column_subopts[i]);
}
}
if (current_command != NULL &&
((strcmp(current_command->name, "set") == 0) ||
(strcmp(current_command->name, "get") == 0))) {
(void) fprintf(fp,
gettext("\nthe following properties are supported:\n"));
(void) fprintf(fp, "\n\t%-13s %s\n\n",
"PROPERTY", "VALUES");
/* Iterate over all properties */
(void) zpool_prop_iter(print_prop_cb, fp, B_FALSE);
}
/*
* See comments at end of main().
*/
if (getenv("ZFS_ABORT") != NULL) {
(void) printf("dumping core by request\n");
abort();
}
exit(requested ? 0 : 2);
}
const char *
state_to_health(int vs_state)
{
switch (vs_state) {
case VDEV_STATE_CLOSED:
case VDEV_STATE_CANT_OPEN:
case VDEV_STATE_OFFLINE:
return (dgettext(TEXT_DOMAIN, "FAULTED"));
case VDEV_STATE_DEGRADED:
return (dgettext(TEXT_DOMAIN, "DEGRADED"));
case VDEV_STATE_HEALTHY:
return (dgettext(TEXT_DOMAIN, "ONLINE"));
}
return (dgettext(TEXT_DOMAIN, "UNKNOWN"));
}
const char *
state_to_name(vdev_stat_t *vs)
{
switch (vs->vs_state) {
case VDEV_STATE_CLOSED:
case VDEV_STATE_CANT_OPEN:
if (vs->vs_aux == VDEV_AUX_CORRUPT_DATA)
return (gettext("FAULTED"));
else
return (gettext("UNAVAIL"));
case VDEV_STATE_OFFLINE:
return (gettext("OFFLINE"));
case VDEV_STATE_DEGRADED:
return (gettext("DEGRADED"));
case VDEV_STATE_HEALTHY:
return (gettext("ONLINE"));
}
return (gettext("UNKNOWN"));
}
void
print_vdev_tree(zpool_handle_t *zhp, const char *name, nvlist_t *nv, int indent)
{
nvlist_t **child;
uint_t c, children;
char *vname;
if (name != NULL)
(void) printf("\t%*s%s\n", indent, "", name);
if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
&child, &children) != 0)
return;
for (c = 0; c < children; c++) {
vname = zpool_vdev_name(g_zfs, zhp, child[c]);
print_vdev_tree(zhp, vname, child[c], indent + 2);
free(vname);
}
}
/*
* zpool add [-fn] <pool> <vdev> ...
*
* -f Force addition of devices, even if they appear in use
* -n Do not add the devices, but display the resulting layout if
* they were to be added.
*
* Adds the given vdevs to 'pool'. As with create, the bulk of this work is
* handled by get_vdev_spec(), which constructs the nvlist needed to pass to
* libzfs.
*/
int
zpool_do_add(int argc, char **argv)
{
boolean_t force = B_FALSE;
boolean_t dryrun = B_FALSE;
int c;
nvlist_t *nvroot;
char *poolname;
int ret;
zpool_handle_t *zhp;
nvlist_t *config;
/* check options */
while ((c = getopt(argc, argv, "fn")) != -1) {
switch (c) {
case 'f':
force = B_TRUE;
break;
case 'n':
dryrun = 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 vdev specification\n"));
usage(B_FALSE);
}
poolname = argv[0];
argc--;
argv++;
if ((zhp = zpool_open(g_zfs, poolname)) == NULL)
return (1);
if ((config = zpool_get_config(zhp, NULL)) == NULL) {
(void) fprintf(stderr, gettext("pool '%s' is unavailable\n"),
poolname);
zpool_close(zhp);
return (1);
}
/* pass off to get_vdev_spec for processing */
nvroot = make_root_vdev(config, force, !force, B_FALSE, argc, argv);
if (nvroot == NULL) {
zpool_close(zhp);
return (1);
}
if (dryrun) {
nvlist_t *poolnvroot;
verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
&poolnvroot) == 0);
(void) printf(gettext("would update '%s' to the following "
"configuration:\n"), zpool_get_name(zhp));
print_vdev_tree(zhp, poolname, poolnvroot, 0);
print_vdev_tree(zhp, NULL, nvroot, 0);
ret = 0;
} else {
ret = (zpool_add(zhp, nvroot) != 0);
if (!ret) {
zpool_log_history(g_zfs, argc + 1 + optind,
argv - 1 - optind, poolname, B_TRUE, B_FALSE);
}
}
nvlist_free(nvroot);
zpool_close(zhp);
return (ret);
}
/*
* zpool remove <pool> <vdev>
*
* Removes the given vdev from the pool. Currently, this only supports removing
* spares from the pool. Eventually, we'll want to support removing leaf vdevs
* (as an alias for 'detach') as well as toplevel vdevs.
*/
int
zpool_do_remove(int argc, char **argv)
{
char *poolname;
int ret;
zpool_handle_t *zhp;
argc--;
argv++;
/* 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);
ret = (zpool_vdev_remove(zhp, argv[1]) != 0);
if (!ret) {
zpool_log_history(g_zfs, ++argc, --argv, poolname, B_TRUE,
B_FALSE);
}
return (ret);
}
/*
* zpool create [-fn] [-R root] [-m mountpoint] <pool> <dev> ...
*
* -f Force creation, even if devices appear in use
* -n Do not create the pool, but display the resulting layout if it
* were to be created.
* -R Create a pool under an alternate root
* -m Set default mountpoint for the root dataset. By default it's
* '/<pool>'
*
* Creates the named pool according to the given vdev specification. The
* bulk of the vdev processing is done in get_vdev_spec() in zpool_vdev.c. Once
* we get the nvlist back from get_vdev_spec(), we either print out the contents
* (if '-n' was specified), or pass it to libzfs to do the creation.
*/
int
zpool_do_create(int argc, char **argv)
{
boolean_t force = B_FALSE;
boolean_t dryrun = B_FALSE;
int c;
nvlist_t *nvroot;
char *poolname;
int ret;
char *altroot = NULL;
char *mountpoint = NULL;
nvlist_t **child;
uint_t children;
/* check options */
while ((c = getopt(argc, argv, ":fnR:m:")) != -1) {
switch (c) {
case 'f':
force = B_TRUE;
break;
case 'n':
dryrun = B_TRUE;
break;
case 'R':
altroot = optarg;
break;
case 'm':
mountpoint = optarg;
break;
case ':':
(void) fprintf(stderr, gettext("missing argument for "
"'%c' option\n"), optopt);
usage(B_FALSE);
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 vdev specification\n"));
usage(B_FALSE);
}
poolname = argv[0];
/*
* As a special case, check for use of '/' in the name, and direct the
* user to use 'zfs create' instead.
*/
if (strchr(poolname, '/') != NULL) {
(void) fprintf(stderr, gettext("cannot create '%s': invalid "
"character '/' in pool name\n"), poolname);
(void) fprintf(stderr, gettext("use 'zfs create' to "
"create a dataset\n"));
return (1);
}
/* pass off to get_vdev_spec for bulk processing */
nvroot = make_root_vdev(NULL, force, !force, B_FALSE, argc - 1,
argv + 1);
if (nvroot == NULL)
return (1);
/* make_root_vdev() allows 0 toplevel children if there are spares */
verify(nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN,
&child, &children) == 0);
if (children == 0) {
(void) fprintf(stderr, gettext("invalid vdev "
"specification: at least one toplevel vdev must be "
"specified\n"));
return (1);
}
if (altroot != NULL && altroot[0] != '/') {
(void) fprintf(stderr, gettext("invalid alternate root '%s': "
"must be an absolute path\n"), altroot);
nvlist_free(nvroot);
return (1);
}
/*
* Check the validity of the mountpoint and direct the user to use the
* '-m' mountpoint option if it looks like its in use.
*/
if (mountpoint == NULL ||
(strcmp(mountpoint, ZFS_MOUNTPOINT_LEGACY) != 0 &&
strcmp(mountpoint, ZFS_MOUNTPOINT_NONE) != 0)) {
char buf[MAXPATHLEN];
struct stat64 statbuf;
if (mountpoint && mountpoint[0] != '/') {
(void) fprintf(stderr, gettext("invalid mountpoint "
"'%s': must be an absolute path, 'legacy', or "
"'none'\n"), mountpoint);
nvlist_free(nvroot);
return (1);
}
if (mountpoint == NULL) {
if (altroot != NULL)
(void) snprintf(buf, sizeof (buf), "%s/%s",
altroot, poolname);
else
(void) snprintf(buf, sizeof (buf), "/%s",
poolname);
} else {
if (altroot != NULL)
(void) snprintf(buf, sizeof (buf), "%s%s",
altroot, mountpoint);
else
(void) snprintf(buf, sizeof (buf), "%s",
mountpoint);
}
if (stat64(buf, &statbuf) == 0 &&
statbuf.st_nlink != 2) {
if (mountpoint == NULL)
(void) fprintf(stderr, gettext("default "
"mountpoint '%s' exists and is not "
"empty\n"), buf);
else
(void) fprintf(stderr, gettext("mountpoint "
"'%s' exists and is not empty\n"), buf);
(void) fprintf(stderr, gettext("use '-m' "
"option to provide a different default\n"));
nvlist_free(nvroot);
return (1);
}
}
if (dryrun) {
/*
* For a dry run invocation, print out a basic message and run
* through all the vdevs in the list and print out in an
* appropriate hierarchy.
*/
(void) printf(gettext("would create '%s' with the "
"following layout:\n\n"), poolname);
print_vdev_tree(NULL, poolname, nvroot, 0);
ret = 0;
} else {
ret = 1;
/*
* Hand off to libzfs.
*/
if (zpool_create(g_zfs, poolname, nvroot, altroot) == 0) {
zfs_handle_t *pool = zfs_open(g_zfs, poolname,
ZFS_TYPE_FILESYSTEM);
if (pool != NULL) {
if (mountpoint != NULL)
verify(zfs_prop_set(pool,
zfs_prop_to_name(
ZFS_PROP_MOUNTPOINT),
mountpoint) == 0);
if (zfs_mount(pool, NULL, 0) == 0)
ret = zfs_share_nfs(pool);
zfs_close(pool);
}
zpool_log_history(g_zfs, argc + optind, argv - optind,
poolname, B_TRUE, B_TRUE);
} else if (libzfs_errno(g_zfs) == EZFS_INVALIDNAME) {
(void) fprintf(stderr, gettext("pool name may have "
"been omitted\n"));
}
}
nvlist_free(nvroot);
return (ret);
}
/*
* zpool destroy <pool>
*
* -f Forcefully unmount any datasets
*
* Destroy the given pool. Automatically unmounts any datasets in the pool.
*/
int
zpool_do_destroy(int argc, char **argv)
{
boolean_t force = B_FALSE;
int c;
char *pool;
zpool_handle_t *zhp;
int ret;
/* check options */
while ((c = getopt(argc, argv, "f")) != -1) {
switch (c) {
case 'f':
force = B_TRUE;
break;
case '?':
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
optopt);
usage(B_FALSE);
}
}
argc -= optind;
argv += optind;
/* check arguments */
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_canfail(g_zfs, pool)) == NULL) {
/*
* As a special case, check for use of '/' in the name, and
* direct the user to use 'zfs destroy' instead.
*/
if (strchr(pool, '/') != NULL)
(void) fprintf(stderr, gettext("use 'zfs destroy' to "
"destroy a dataset\n"));
return (1);
}
if (zpool_disable_datasets(zhp, force) != 0) {
(void) fprintf(stderr, gettext("could not destroy '%s': "
"could not unmount datasets\n"), zpool_get_name(zhp));
return (1);
}
zpool_log_history(g_zfs, argc + optind, argv - optind, pool, B_TRUE,
B_FALSE);
ret = (zpool_destroy(zhp) != 0);
zpool_close(zhp);
return (ret);
}
/*
* zpool export [-f] <pool> ...
*
* -f Forcefully unmount datasets
*
* Export the given pools. By default, the command will attempt to cleanly
* unmount any active datasets within the pool. If the '-f' flag is specified,
* then the datasets will be forcefully unmounted.
*/
int
zpool_do_export(int argc, char **argv)
{
boolean_t force = B_FALSE;
int c;
zpool_handle_t *zhp;
int ret;
int i;
/* check options */
while ((c = getopt(argc, argv, "f")) != -1) {
switch (c) {
case 'f':
force = B_TRUE;
break;
case '?':
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
optopt);
usage(B_FALSE);
}
}
argc -= optind;
argv += optind;
/* check arguments */
if (argc < 1) {
(void) fprintf(stderr, gettext("missing pool argument\n"));
usage(B_FALSE);
}
ret = 0;
for (i = 0; i < argc; i++) {
if ((zhp = zpool_open_canfail(g_zfs, argv[i])) == NULL) {
ret = 1;
continue;
}
if (zpool_disable_datasets(zhp, force) != 0) {
ret = 1;
zpool_close(zhp);
continue;
}
zpool_log_history(g_zfs, argc + optind, argv - optind, argv[i],
B_TRUE, B_FALSE);
if (zpool_export(zhp) != 0)
ret = 1;
zpool_close(zhp);
}
return (ret);
}
/*
* Given a vdev configuration, determine the maximum width needed for the device
* name column.
*/
static int
max_width(zpool_handle_t *zhp, nvlist_t *nv, int depth, int max)
{
char *name = zpool_vdev_name(g_zfs, zhp, nv);
nvlist_t **child;
uint_t c, children;
int ret;
if (strlen(name) + depth > max)
max = strlen(name) + depth;
free(name);
if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_SPARES,
&child, &children) == 0) {
for (c = 0; c < children; c++)
if ((ret = max_width(zhp, child[c], depth + 2,
max)) > max)
max = ret;
}
if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
&child, &children) == 0) {
for (c = 0; c < children; c++)
if ((ret = max_width(zhp, child[c], depth + 2,
max)) > max)
max = ret;
}
return (max);
}
/*
* Print the configuration of an exported pool. Iterate over all vdevs in the
* pool, printing out the name and status for each one.
*/
void
print_import_config(const char *name, nvlist_t *nv, int namewidth, int depth)
{
nvlist_t **child;
uint_t c, children;
vdev_stat_t *vs;
char *type, *vname;
verify(nvlist_lookup_string(nv, ZPOOL_CONFIG_TYPE, &type) == 0);
if (strcmp(type, VDEV_TYPE_MISSING) == 0)
return;
verify(nvlist_lookup_uint64_array(nv, ZPOOL_CONFIG_STATS,
(uint64_t **)&vs, &c) == 0);
(void) printf("\t%*s%-*s", depth, "", namewidth - depth, name);
if (vs->vs_aux != 0) {
(void) printf(" %-8s ", state_to_name(vs));
switch (vs->vs_aux) {
case VDEV_AUX_OPEN_FAILED:
(void) printf(gettext("cannot open"));
break;
case VDEV_AUX_BAD_GUID_SUM:
(void) printf(gettext("missing device"));
break;
case VDEV_AUX_NO_REPLICAS:
(void) printf(gettext("insufficient replicas"));
break;
case VDEV_AUX_VERSION_NEWER:
(void) printf(gettext("newer version"));
break;
default:
(void) printf(gettext("corrupted data"));
break;
}
} else {
(void) printf(" %s", state_to_name(vs));
}
(void) printf("\n");
if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
&child, &children) != 0)
return;
for (c = 0; c < children; c++) {
vname = zpool_vdev_name(g_zfs, NULL, child[c]);
print_import_config(vname, child[c],
namewidth, depth + 2);
free(vname);
}
if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_SPARES,
&child, &children) != 0)
return;
(void) printf(gettext("\tspares\n"));
for (c = 0; c < children; c++) {
vname = zpool_vdev_name(g_zfs, NULL, child[c]);
(void) printf("\t %s\n", vname);
free(vname);
}
}
/*
* Display the status for the given pool.
*/
static void
show_import(nvlist_t *config)
{
uint64_t pool_state;
vdev_stat_t *vs;
char *name;
uint64_t guid;
char *msgid;
nvlist_t *nvroot;
int reason;
const char *health;
uint_t vsc;
int namewidth;
verify(nvlist_lookup_string(config, ZPOOL_CONFIG_POOL_NAME,
&name) == 0);
verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID,
&guid) == 0);
verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_STATE,
&pool_state) == 0);
verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
&nvroot) == 0);
verify(nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_STATS,
(uint64_t **)&vs, &vsc) == 0);
health = state_to_health(vs->vs_state);
reason = zpool_import_status(config, &msgid);
(void) printf(gettext(" pool: %s\n"), name);
(void) printf(gettext(" id: %llu\n"), (u_longlong_t)guid);
(void) printf(gettext(" state: %s"), health);
if (pool_state == POOL_STATE_DESTROYED)
(void) printf(gettext(" (DESTROYED)"));
(void) printf("\n");
switch (reason) {
case ZPOOL_STATUS_MISSING_DEV_R:
case ZPOOL_STATUS_MISSING_DEV_NR:
case ZPOOL_STATUS_BAD_GUID_SUM:
(void) printf(gettext("status: One or more devices are missing "
"from the system.\n"));
break;
case ZPOOL_STATUS_CORRUPT_LABEL_R:
case ZPOOL_STATUS_CORRUPT_LABEL_NR:
(void) printf(gettext("status: One or more devices contains "
"corrupted data.\n"));
break;
case ZPOOL_STATUS_CORRUPT_DATA:
(void) printf(gettext("status: The pool data is corrupted.\n"));
break;
case ZPOOL_STATUS_OFFLINE_DEV:
(void) printf(gettext("status: One or more devices "
"are offlined.\n"));
break;
case ZPOOL_STATUS_CORRUPT_POOL:
(void) printf(gettext("status: The pool metadata is "
"corrupted.\n"));
break;
case ZPOOL_STATUS_VERSION_OLDER:
(void) printf(gettext("status: The pool is formatted using an "
"older on-disk version.\n"));
break;
case ZPOOL_STATUS_VERSION_NEWER:
(void) printf(gettext("status: The pool is formatted using an "
"incompatible version.\n"));
break;
case ZPOOL_STATUS_HOSTID_MISMATCH:
(void) printf(gettext("status: The pool was last accessed by "
"another system.\n"));
break;
default:
/*
* No other status can be seen when importing pools.
*/
assert(reason == ZPOOL_STATUS_OK);
}
/*
* Print out an action according to the overall state of the pool.
*/
if (vs->vs_state == VDEV_STATE_HEALTHY) {
if (reason == ZPOOL_STATUS_VERSION_OLDER)
(void) printf(gettext("action: The pool can be "
"imported using its name or numeric identifier, "
"though\n\tsome features will not be available "
"without an explicit 'zpool upgrade'.\n"));
else if (reason == ZPOOL_STATUS_HOSTID_MISMATCH)
(void) printf(gettext("action: The pool can be "
"imported using its name or numeric "
"identifier and\n\tthe '-f' flag.\n"));
else
(void) printf(gettext("action: The pool can be "
"imported using its name or numeric "
"identifier.\n"));
} else if (vs->vs_state == VDEV_STATE_DEGRADED) {
(void) printf(gettext("action: The pool can be imported "
"despite missing or damaged devices. The\n\tfault "
"tolerance of the pool may be compromised if imported.\n"));
} else {
switch (reason) {
case ZPOOL_STATUS_VERSION_NEWER:
(void) printf(gettext("action: The pool cannot be "
"imported. Access the pool on a system running "
"newer\n\tsoftware, or recreate the pool from "
"backup.\n"));
break;
case ZPOOL_STATUS_MISSING_DEV_R:
case ZPOOL_STATUS_MISSING_DEV_NR:
case ZPOOL_STATUS_BAD_GUID_SUM:
(void) printf(gettext("action: The pool cannot be "
"imported. Attach the missing\n\tdevices and try "
"again.\n"));
break;
default:
(void) printf(gettext("action: The pool cannot be "
"imported due to damaged devices or data.\n"));
}
}
/*
* If the state is "closed" or "can't open", and the aux state
* is "corrupt data":
*/
if (((vs->vs_state == VDEV_STATE_CLOSED) ||
(vs->vs_state == VDEV_STATE_CANT_OPEN)) &&
(vs->vs_aux == VDEV_AUX_CORRUPT_DATA)) {
if (pool_state == POOL_STATE_DESTROYED)
(void) printf(gettext("\tThe pool was destroyed, "
"but can be imported using the '-Df' flags.\n"));
else if (pool_state != POOL_STATE_EXPORTED)
(void) printf(gettext("\tThe pool may be active on "
"on another system, but can be imported using\n\t"
"the '-f' flag.\n"));
}
if (msgid != NULL)
(void) printf(gettext(" see: http://www.sun.com/msg/%s\n"),
msgid);
(void) printf(gettext("config:\n\n"));
namewidth = max_width(NULL, nvroot, 0, 0);
if (namewidth < 10)
namewidth = 10;
print_import_config(name, nvroot, namewidth, 0);
if (reason == ZPOOL_STATUS_BAD_GUID_SUM) {
(void) printf(gettext("\n\tAdditional devices are known to "
"be part of this pool, though their\n\texact "
"configuration cannot be determined.\n"));
}
}
/*
* Perform the import for the given configuration. This passes the heavy
* lifting off to zpool_import(), and then mounts the datasets contained within
* the pool.
*/
static int
do_import(nvlist_t *config, const char *newname, const char *mntopts,
const char *altroot, int force, int argc, char **argv)
{
zpool_handle_t *zhp;
char *name;
uint64_t state;
uint64_t version;
verify(nvlist_lookup_string(config, ZPOOL_CONFIG_POOL_NAME,
&name) == 0);
verify(nvlist_lookup_uint64(config,
ZPOOL_CONFIG_POOL_STATE, &state) == 0);
verify(nvlist_lookup_uint64(config,
ZPOOL_CONFIG_VERSION, &version) == 0);
if (version > ZFS_VERSION) {
(void) fprintf(stderr, gettext("cannot import '%s': pool "
"is formatted using a newer ZFS version\n"), name);
return (1);
} else if (state != POOL_STATE_EXPORTED && !force) {
uint64_t hostid;
if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_HOSTID,
&hostid) == 0) {
if ((unsigned long)hostid != gethostid()) {
char *hostname;
uint64_t timestamp;
time_t t;
verify(nvlist_lookup_string(config,
ZPOOL_CONFIG_HOSTNAME, &hostname) == 0);
verify(nvlist_lookup_uint64(config,
ZPOOL_CONFIG_TIMESTAMP, &timestamp) == 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);
}
} else {
(void) fprintf(stderr, gettext("cannot import '%s': "
"pool may be in use from other system\n"), name);
(void) fprintf(stderr, gettext("use '-f' to import "
"anyway\n"));
return (1);
}
}
if (zpool_import(g_zfs, config, newname, altroot) != 0)
return (1);
if (newname != NULL)
name = (char *)newname;
zpool_log_history(g_zfs, argc, argv, name, B_TRUE, B_FALSE);
verify((zhp = zpool_open(g_zfs, name)) != NULL);
if (zpool_enable_datasets(zhp, mntopts, 0) != 0) {
zpool_close(zhp);
return (1);
}
zpool_close(zhp);
return (0);
}
/*
* zpool import [-d dir] [-D]
* import [-R root] [-D] [-d dir] [-f] -a
* import [-R root] [-D] [-d dir] [-f] <pool | id> [newpool]
*
* -d Scan in a specific directory, other than /dev/dsk. More than
* one directory can be specified using multiple '-d' options.
*
* -D Scan for previously destroyed pools or import all or only
* specified destroyed pools.
*
* -R Temporarily import the pool, with all mountpoints relative to
* the given root. The pool will remain exported when the machine
* is rebooted.
*
* -f Force import, even if it appears that the pool is active.
*
* -a Import all pools found.
*
* 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.
*/
int
zpool_do_import(int argc, char **argv)
{
char **searchdirs = NULL;
int nsearch = 0;
int c;
int err;
nvlist_t *pools;
boolean_t do_all = B_FALSE;
boolean_t do_destroyed = B_FALSE;
char *altroot = NULL;
char *mntopts = NULL;
boolean_t do_force = B_FALSE;
nvpair_t *elem;
nvlist_t *config;
uint64_t searchguid;
char *searchname;
nvlist_t *found_config;
boolean_t first;
uint64_t pool_state;
/* check options */
while ((c = getopt(argc, argv, ":Dfd:R:ao:")) != -1) {
switch (c) {
case 'a':
do_all = B_TRUE;
break;
case 'd':
if (searchdirs == NULL) {
searchdirs = safe_malloc(sizeof (char *));
} else {
char **tmp = safe_malloc((nsearch + 1) *
sizeof (char *));
bcopy(searchdirs, tmp, nsearch *
sizeof (char *));
free(searchdirs);
searchdirs = tmp;
}
searchdirs[nsearch++] = optarg;
break;
case 'D':
do_destroyed = B_TRUE;
break;
case 'f':
do_force = B_TRUE;
break;
case 'o':
mntopts = optarg;
break;
case 'R':
altroot = optarg;
break;
case ':':
(void) fprintf(stderr, gettext("missing argument for "
"'%c' option\n"), optopt);
usage(B_FALSE);
break;
case '?':
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
optopt);
usage(B_FALSE);
}
}
argc -= optind;
argv += optind;
if (searchdirs == NULL) {
searchdirs = safe_malloc(sizeof (char *));
searchdirs[0] = "/dev";
nsearch = 1;
}
/* check argument count */
if (do_all) {
if (argc != 0) {
(void) fprintf(stderr, gettext("too many arguments\n"));
usage(B_FALSE);
}
} else {
if (argc > 2) {
(void) fprintf(stderr, gettext("too many arguments\n"));
usage(B_FALSE);
}
/*
* Check for the SYS_CONFIG privilege. We do this explicitly
* here because otherwise any attempt to discover pools will
* silently fail.
*/
if (argc == 0 && !priv_ineffect(PRIV_SYS_CONFIG)) {
(void) fprintf(stderr, gettext("cannot "
"discover pools: permission denied\n"));
free(searchdirs);
return (1);
}
}
if ((pools = zpool_find_import(g_zfs, nsearch, searchdirs)) == NULL) {
free(searchdirs);
return (1);
}
/*
* We now have a list of all available pools in the given directories.
* Depending on the arguments given, we do one of the following:
*
* <none> Iterate through all pools and display information about
* each one.
*
* -a Iterate through all pools and try to import each one.
*
* <id> Find the pool that corresponds to the given GUID/pool
* name and import that one.
*
* -D Above options applies only to destroyed pools.
*/
if (argc != 0) {
char *endptr;
errno = 0;
searchguid = strtoull(argv[0], &endptr, 10);
if (errno != 0 || *endptr != '\0')
searchname = argv[0];
else
searchname = NULL;
found_config = NULL;
}
err = 0;
elem = NULL;
first = B_TRUE;
while ((elem = nvlist_next_nvpair(pools, elem)) != NULL) {
verify(nvpair_value_nvlist(elem, &config) == 0);
verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_STATE,
&pool_state) == 0);
if (!do_destroyed && pool_state == POOL_STATE_DESTROYED)
continue;
if (do_destroyed && pool_state != POOL_STATE_DESTROYED)
continue;
if (argc == 0) {
if (first)
first = B_FALSE;
else if (!do_all)
(void) printf("\n");
if (do_all)
err |= do_import(config, NULL, mntopts,
altroot, do_force, argc + optind,
argv - optind);
else
show_import(config);
} else if (searchname != NULL) {
char *name;
/*
* We are searching for a pool based on name.
*/
verify(nvlist_lookup_string(config,
ZPOOL_CONFIG_POOL_NAME, &name) == 0);
if (strcmp(name, searchname) == 0) {
if (found_config != NULL) {
(void) fprintf(stderr, gettext(
"cannot import '%s': more than "
"one matching pool\n"), searchname);
(void) fprintf(stderr, gettext(
"import by numeric ID instead\n"));
err = B_TRUE;
}
found_config = config;
}
} else {
uint64_t guid;
/*
* Search for a pool by guid.
*/
verify(nvlist_lookup_uint64(config,
ZPOOL_CONFIG_POOL_GUID, &guid) == 0);
if (guid == searchguid)
found_config = config;
}
}
/*
* If we were searching for a specific pool, verify that we found a
* pool, and then do the import.
*/
if (argc != 0 && err == 0) {
if (found_config == NULL) {
(void) fprintf(stderr, gettext("cannot import '%s': "
"no such pool available\n"), argv[0]);
err = B_TRUE;
} else {
err |= do_import(found_config, argc == 1 ? NULL :
argv[1], mntopts, altroot, do_force, argc + optind,
argv - optind);
}
}
/*
* If we were just looking for pools, report an error if none were
* found.
*/
if (argc == 0 && first)
(void) fprintf(stderr,
gettext("no pools available to import\n"));
nvlist_free(pools);
free(searchdirs);
return (err ? 1 : 0);
}
typedef struct iostat_cbdata {
zpool_list_t *cb_list;
int cb_verbose;
int cb_iteration;
int cb_namewidth;
} iostat_cbdata_t;
static void
print_iostat_separator(iostat_cbdata_t *cb)
{
int i = 0;
for (i = 0; i < cb->cb_namewidth; i++)
(void) printf("-");
(void) printf(" ----- ----- ----- ----- ----- -----\n");
}
static void
print_iostat_header(iostat_cbdata_t *cb)
{
(void) printf("%*s capacity operations bandwidth\n",
cb->cb_namewidth, "");
(void) printf("%-*s used avail read write read write\n",
cb->cb_namewidth, "pool");
print_iostat_separator(cb);
}
/*
* Display a single statistic.
*/
void
print_one_stat(uint64_t value)
{
char buf[64];
zfs_nicenum(value, buf, sizeof (buf));
(void) printf(" %5s", buf);
}
/*
* 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
* is a verbose output, and we don't want to display the toplevel pool stats.
*/
void
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;
vdev_stat_t *oldvs, *newvs;
vdev_stat_t zerovs = { 0 };
uint64_t tdelta;
double scale;
char *vname;
if (oldnv != NULL) {
verify(nvlist_lookup_uint64_array(oldnv, ZPOOL_CONFIG_STATS,
(uint64_t **)&oldvs, &c) == 0);
} else {
oldvs = &zerovs;
}
verify(nvlist_lookup_uint64_array(newnv, ZPOOL_CONFIG_STATS,
(uint64_t **)&newvs, &c) == 0);
if (strlen(name) + depth > cb->cb_namewidth)
(void) printf("%*s%s", depth, "", name);
else
(void) printf("%*s%s%*s", depth, "", name,
(int)(cb->cb_namewidth - strlen(name) - depth), "");
tdelta = newvs->vs_timestamp - oldvs->vs_timestamp;
if (tdelta == 0)
scale = 1.0;
else
scale = (double)NANOSEC / tdelta;
/* only toplevel vdevs have capacity stats */
if (newvs->vs_space == 0) {
(void) printf(" - -");
} else {
print_one_stat(newvs->vs_alloc);
print_one_stat(newvs->vs_space - newvs->vs_alloc);
}
print_one_stat((uint64_t)(scale * (newvs->vs_ops[ZIO_TYPE_READ] -
oldvs->vs_ops[ZIO_TYPE_READ])));
print_one_stat((uint64_t)(scale * (newvs->vs_ops[ZIO_TYPE_WRITE] -
oldvs->vs_ops[ZIO_TYPE_WRITE])));
print_one_stat((uint64_t)(scale * (newvs->vs_bytes[ZIO_TYPE_READ] -
oldvs->vs_bytes[ZIO_TYPE_READ])));
print_one_stat((uint64_t)(scale * (newvs->vs_bytes[ZIO_TYPE_WRITE] -
oldvs->vs_bytes[ZIO_TYPE_WRITE])));
(void) printf("\n");
if (!cb->cb_verbose)
return;
if (nvlist_lookup_nvlist_array(newnv, ZPOOL_CONFIG_CHILDREN,
&newchild, &children) != 0)
return;
if (oldnv && nvlist_lookup_nvlist_array(oldnv, ZPOOL_CONFIG_CHILDREN,
&oldchild, &c) != 0)
return;
for (c = 0; c < children; c++) {
vname = zpool_vdev_name(g_zfs, zhp, newchild[c]);
print_vdev_stats(zhp, vname, oldnv ? oldchild[c] : NULL,
newchild[c], cb, depth + 2);
free(vname);
}
}
static int
refresh_iostat(zpool_handle_t *zhp, void *data)
{
iostat_cbdata_t *cb = data;
boolean_t missing;
/*
* If the pool has disappeared, remove it from the list and continue.
*/
if (zpool_refresh_stats(zhp, &missing) != 0)
return (-1);
if (missing)
pool_list_remove(cb->cb_list, zhp);
return (0);
}
/*
* Callback to print out the iostats for the given pool.
*/
int
print_iostat(zpool_handle_t *zhp, void *data)
{
iostat_cbdata_t *cb = data;
nvlist_t *oldconfig, *newconfig;
nvlist_t *oldnvroot, *newnvroot;
newconfig = zpool_get_config(zhp, &oldconfig);
if (cb->cb_iteration == 1)
oldconfig = NULL;
verify(nvlist_lookup_nvlist(newconfig, ZPOOL_CONFIG_VDEV_TREE,
&newnvroot) == 0);
if (oldconfig == NULL)
oldnvroot = NULL;
else
verify(nvlist_lookup_nvlist(oldconfig, ZPOOL_CONFIG_VDEV_TREE,
&oldnvroot) == 0);
/*
* Print out the statistics for the pool.
*/
print_vdev_stats(zhp, zpool_get_name(zhp), oldnvroot, newnvroot, cb, 0);
if (cb->cb_verbose)
print_iostat_separator(cb);
return (0);
}
int
get_namewidth(zpool_handle_t *zhp, void *data)
{
iostat_cbdata_t *cb = data;
nvlist_t *config, *nvroot;
if ((config = zpool_get_config(zhp, NULL)) != NULL) {
verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
&nvroot) == 0);
if (!cb->cb_verbose)
cb->cb_namewidth = strlen(zpool_get_name(zhp));
else
cb->cb_namewidth = max_width(zhp, nvroot, 0, 0);
}
/*
* The width must fall into the range [10,38]. The upper limit is the
* maximum we can have and still fit in 80 columns.
*/
if (cb->cb_namewidth < 10)
cb->cb_namewidth = 10;
if (cb->cb_namewidth > 38)
cb->cb_namewidth = 38;
return (0);
}
/*
* zpool iostat [-v] [pool] ... [interval [count]]
*
* -v Display statistics for individual vdevs
*
* 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
* processing is handled by the pool_list_* routines in zpool_iter.c. We rely
* on pool_list_update() to detect the addition of new pools. Configuration
* changes are all handled within libzfs.
*/
int
zpool_do_iostat(int argc, char **argv)
{
int c;
int ret;
int npools;
unsigned long interval = 0, count = 0;
zpool_list_t *list;
boolean_t verbose = B_FALSE;
iostat_cbdata_t cb;
/* check options */
while ((c = getopt(argc, argv, "v")) != -1) {
switch (c) {
case 'v':
verbose = B_TRUE;
break;
case '?':
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
optopt);
usage(B_FALSE);
}
}
argc -= optind;
argv += optind;
/*
* Determine if the last argument is an integer or a pool name
*/
if (argc > 0 && isdigit(argv[argc - 1][0])) {
char *end;
errno = 0;
interval = strtoul(argv[argc - 1], &end, 10);
if (*end == '\0' && errno == 0) {
if (interval == 0) {
(void) fprintf(stderr, gettext("interval "
"cannot be zero\n"));
usage(B_FALSE);
}
/*
* Ignore the last parameter
*/
argc--;
} else {
/*
* If this is not a valid number, just plow on. The
* user will get a more informative error message later
* on.
*/
interval = 0;
}
}
/*
* If the last argument is also an integer, then we have both a count
* and an integer.
*/
if (argc > 0 && isdigit(argv[argc - 1][0])) {
char *end;
errno = 0;
count = interval;
interval = strtoul(argv[argc - 1], &end, 10);
if (*end == '\0' && errno == 0) {
if (interval == 0) {
(void) fprintf(stderr, gettext("interval "
"cannot be zero\n"));
usage(B_FALSE);
}
/*
* Ignore the last parameter
*/
argc--;
} else {
interval = 0;
}
}
/*
* Construct the list of all interesting pools.
*/
ret = 0;
if ((list = pool_list_get(argc, argv, NULL, &ret)) == NULL)
return (1);
if (pool_list_count(list) == 0 && argc != 0) {
pool_list_free(list);
return (1);
}
if (pool_list_count(list) == 0 && interval == 0) {
pool_list_free(list);
(void) fprintf(stderr, gettext("no pools available\n"));
return (1);
}
/*
* Enter the main iostat loop.
*/
cb.cb_list = list;
cb.cb_verbose = verbose;
cb.cb_iteration = 0;
cb.cb_namewidth = 0;
for (;;) {
pool_list_update(list);
if ((npools = pool_list_count(list)) == 0)
break;
/*
* Refresh all statistics. This is done as an explicit step
* before calculating the maximum name width, so that any
* configuration changes are properly accounted for.
*/
(void) pool_list_iter(list, B_FALSE, refresh_iostat, &cb);
/*
* Iterate over all pools to determine the maximum width
* for the pool / device name column across all pools.
*/
cb.cb_namewidth = 0;
(void) pool_list_iter(list, B_FALSE, get_namewidth, &cb);
/*
* If it's the first time, or verbose mode, print the header.
*/
if (++cb.cb_iteration == 1 || verbose)
print_iostat_header(&cb);
(void) pool_list_iter(list, B_FALSE, print_iostat, &cb);
/*
* If there's more than one pool, and we're not in verbose mode
* (which prints a separator for us), then print a separator.
*/
if (npools > 1 && !verbose)
print_iostat_separator(&cb);
if (verbose)
(void) printf("\n");
/*
* Flush the output so that redirection to a file isn't buffered
* indefinitely.
*/
(void) fflush(stdout);
if (interval == 0)
break;
if (count != 0 && --count == 0)
break;
(void) sleep(interval);
}
pool_list_free(list);
return (ret);
}
typedef struct list_cbdata {
boolean_t cb_scripted;
boolean_t cb_first;
int cb_fields[MAX_FIELDS];
int cb_fieldcount;
} list_cbdata_t;
/*
* Given a list of columns to display, output appropriate headers for each one.
*/
void
print_header(int *fields, size_t count)
{
int i;
column_def_t *col;
const char *fmt;
for (i = 0; i < count; i++) {
col = &column_table[fields[i]];
if (i != 0)
(void) printf(" ");
if (col->cd_justify == left_justify)
fmt = "%-*s";
else
fmt = "%*s";
(void) printf(fmt, i == count - 1 ? strlen(col->cd_title) :
col->cd_width, col->cd_title);
}
(void) printf("\n");
}
int
list_callback(zpool_handle_t *zhp, void *data)
{
list_cbdata_t *cbp = data;
nvlist_t *config;
int i;
char buf[ZPOOL_MAXNAMELEN];
uint64_t total;
uint64_t used;
const char *fmt;
column_def_t *col;
if (cbp->cb_first) {
if (!cbp->cb_scripted)
print_header(cbp->cb_fields, cbp->cb_fieldcount);
cbp->cb_first = B_FALSE;
}
if (zpool_get_state(zhp) == POOL_STATE_UNAVAIL) {
config = NULL;
} else {
config = zpool_get_config(zhp, NULL);
total = zpool_get_space_total(zhp);
used = zpool_get_space_used(zhp);
}
for (i = 0; i < cbp->cb_fieldcount; i++) {
if (i != 0) {
if (cbp->cb_scripted)
(void) printf("\t");
else
(void) printf(" ");
}
col = &column_table[cbp->cb_fields[i]];
switch (cbp->cb_fields[i]) {
case ZPOOL_FIELD_NAME:
(void) strlcpy(buf, zpool_get_name(zhp), sizeof (buf));
break;
case ZPOOL_FIELD_SIZE:
if (config == NULL)
(void) strlcpy(buf, "-", sizeof (buf));
else
zfs_nicenum(total, buf, sizeof (buf));
break;
case ZPOOL_FIELD_USED:
if (config == NULL)
(void) strlcpy(buf, "-", sizeof (buf));
else
zfs_nicenum(used, buf, sizeof (buf));
break;
case ZPOOL_FIELD_AVAILABLE:
if (config == NULL)
(void) strlcpy(buf, "-", sizeof (buf));
else
zfs_nicenum(total - used, buf, sizeof (buf));
break;
case ZPOOL_FIELD_CAPACITY:
if (config == NULL) {
(void) strlcpy(buf, "-", sizeof (buf));
} else {
uint64_t capacity = (total == 0 ? 0 :
(used * 100 / total));
(void) snprintf(buf, sizeof (buf), "%llu%%",
(u_longlong_t)capacity);
}
break;
case ZPOOL_FIELD_HEALTH:
if (config == NULL) {
(void) strlcpy(buf, "FAULTED", sizeof (buf));
} else {
nvlist_t *nvroot;
vdev_stat_t *vs;
uint_t vsc;
verify(nvlist_lookup_nvlist(config,
ZPOOL_CONFIG_VDEV_TREE, &nvroot) == 0);
verify(nvlist_lookup_uint64_array(nvroot,
ZPOOL_CONFIG_STATS, (uint64_t **)&vs,
&vsc) == 0);
(void) strlcpy(buf, state_to_name(vs),
sizeof (buf));
}
break;
case ZPOOL_FIELD_ROOT:
if (config == NULL)
(void) strlcpy(buf, "-", sizeof (buf));
else if (zpool_get_root(zhp, buf, sizeof (buf)) != 0)
(void) strlcpy(buf, "-", sizeof (buf));
break;
}
if (cbp->cb_scripted)
(void) printf("%s", buf);
else {
if (col->cd_justify == left_justify)
fmt = "%-*s";
else
fmt = "%*s";
(void) printf(fmt, i == cbp->cb_fieldcount - 1 ?
strlen(buf) : col->cd_width, buf);
}
}
(void) printf("\n");
return (0);
}
/*
* zpool list [-H] [-o field[,field]*] [pool] ...
*
* -H Scripted mode. Don't display headers, and separate fields by
* a single tab.
* -o List of fields to display. Defaults to all fields, or
* "name,size,used,available,capacity,health,root"
*
* List all pools in the system, whether or not they're healthy. Output space
* statistics for each one, as well as health status summary.
*/
int
zpool_do_list(int argc, char **argv)
{
int c;
int ret;
list_cbdata_t cb = { 0 };
static char default_fields[] =
"name,size,used,available,capacity,health,root";
char *fields = default_fields;
char *value;
/* check options */
while ((c = getopt(argc, argv, ":Ho:")) != -1) {
switch (c) {
case 'H':
cb.cb_scripted = B_TRUE;
break;
case 'o':
fields = optarg;
break;
case ':':
(void) fprintf(stderr, gettext("missing argument for "
"'%c' option\n"), optopt);
usage(B_FALSE);
break;
case '?':
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
optopt);
usage(B_FALSE);
}
}
argc -= optind;
argv += optind;
while (*fields != '\0') {
if (cb.cb_fieldcount == MAX_FIELDS) {
(void) fprintf(stderr, gettext("too many "
"properties given to -o option\n"));
usage(B_FALSE);
}
if ((cb.cb_fields[cb.cb_fieldcount] = getsubopt(&fields,
column_subopts, &value)) == -1) {
(void) fprintf(stderr, gettext("invalid property "
"'%s'\n"), value);
usage(B_FALSE);
}
cb.cb_fieldcount++;
}
cb.cb_first = B_TRUE;
ret = for_each_pool(argc, argv, B_TRUE, NULL, list_callback, &cb);
if (argc == 0 && cb.cb_first) {
(void) printf(gettext("no pools available\n"));
return (0);
}
return (ret);
}
static nvlist_t *
zpool_get_vdev_by_name(nvlist_t *nv, char *name)
{
nvlist_t **child;
uint_t c, children;
nvlist_t *match;
char *path;
if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
&child, &children) != 0) {
verify(nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) == 0);
if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV)-1) == 0)
name += sizeof(_PATH_DEV)-1;
if (strncmp(path, _PATH_DEV, sizeof(_PATH_DEV)-1) == 0)
path += sizeof(_PATH_DEV)-1;
if (strcmp(name, path) == 0)
return (nv);
return (NULL);
}
for (c = 0; c < children; c++)
if ((match = zpool_get_vdev_by_name(child[c], name)) != NULL)
return (match);
return (NULL);
}
static int
zpool_do_attach_or_replace(int argc, char **argv, int replacing)
{
boolean_t force = B_FALSE;
int c;
nvlist_t *nvroot;
char *poolname, *old_disk, *new_disk;
zpool_handle_t *zhp;
nvlist_t *config;
int ret;
int log_argc;
char **log_argv;
/* check options */
while ((c = getopt(argc, argv, "f")) != -1) {
switch (c) {
case 'f':
force = B_TRUE;
break;
case '?':
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
optopt);
usage(B_FALSE);
}
}
log_argc = argc;
log_argv = argv;
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);
}
poolname = argv[0];
if (argc < 2) {
(void) fprintf(stderr,
gettext("missing <device> specification\n"));
usage(B_FALSE);
}
old_disk = argv[1];
if (argc < 3) {
if (!replacing) {
(void) fprintf(stderr,
gettext("missing <new_device> specification\n"));
usage(B_FALSE);
}
new_disk = old_disk;
argc -= 1;
argv += 1;
} else {
new_disk = argv[2];
argc -= 2;
argv += 2;
}
if (argc > 1) {
(void) fprintf(stderr, gettext("too many arguments\n"));
usage(B_FALSE);
}
if ((zhp = zpool_open(g_zfs, poolname)) == NULL)
return (1);
if ((config = zpool_get_config(zhp, NULL)) == NULL) {
(void) fprintf(stderr, gettext("pool '%s' is unavailable\n"),
poolname);
zpool_close(zhp);
return (1);
}
nvroot = make_root_vdev(config, force, B_FALSE, replacing, argc, argv);
if (nvroot == NULL) {
zpool_close(zhp);
return (1);
}
ret = zpool_vdev_attach(zhp, old_disk, new_disk, nvroot, replacing);
if (!ret) {
zpool_log_history(g_zfs, log_argc, log_argv, poolname, B_TRUE,
B_FALSE);
}
nvlist_free(nvroot);
zpool_close(zhp);
return (ret);
}
/*
* zpool replace [-f] <pool> <device> <new_device>
*
* -f Force attach, even if <new_device> appears to be in use.
*
* Replace <device> with <new_device>.
*/
/* ARGSUSED */
int
zpool_do_replace(int argc, char **argv)
{
return (zpool_do_attach_or_replace(argc, argv, B_TRUE));
}
/*
* zpool attach [-f] <pool> <device> <new_device>
*
* -f Force attach, even if <new_device> appears to be in use.
*
* Attach <new_device> to the mirror containing <device>. If <device> is not
* part of a mirror, then <device> will be transformed into a mirror of
* <device> and <new_device>. In either case, <new_device> will begin life
* with a DTL of [0, now], and will immediately begin to resilver itself.
*/
int
zpool_do_attach(int argc, char **argv)
{
return (zpool_do_attach_or_replace(argc, argv, B_FALSE));
}
/*
* zpool detach [-f] <pool> <device>
*
* -f Force detach of <device>, even if DTLs argue against it
* (not supported yet)
*
* Detach a device from a mirror. The operation will be refused if <device>
* is the last device in the mirror, or if the DTLs indicate that this device
* has the only valid copy of some data.
*/
/* ARGSUSED */
int
zpool_do_detach(int argc, char **argv)
{
int c;
char *poolname, *path;
zpool_handle_t *zhp;
int ret;
/* check options */
while ((c = getopt(argc, argv, "f")) != -1) {
switch (c) {
case 'f':
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> specification\n"));
usage(B_FALSE);
}
poolname = argv[0];
path = argv[1];
if ((zhp = zpool_open(g_zfs, poolname)) == NULL)
return (1);
ret = zpool_vdev_detach(zhp, path);
if (!ret) {
zpool_log_history(g_zfs, argc + optind, argv - optind, poolname,
B_TRUE, B_FALSE);
}
zpool_close(zhp);
return (ret);
}
/*
* zpool online <pool> <device> ...
*/
/* ARGSUSED */
int
zpool_do_online(int argc, char **argv)
{
int c, i;
char *poolname;
zpool_handle_t *zhp;
int ret = 0;
/* check options */
while ((c = getopt(argc, argv, "t")) != -1) {
switch (c) {
case 't':
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\n"));
usage(B_FALSE);
}
if (argc < 2) {
(void) fprintf(stderr, gettext("missing device name\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_online(zhp, argv[i]) == 0)
(void) printf(gettext("Bringing device %s online\n"),
argv[i]);
else
ret = 1;
if (!ret) {
zpool_log_history(g_zfs, argc + optind, argv - optind, poolname,
B_TRUE, B_FALSE);
}
zpool_close(zhp);
return (ret);
}
/*
* zpool offline [-ft] <pool> <device> ...
*
* -f Force the device into the offline state, even if doing
* so would appear to compromise pool availability.
* (not supported yet)
*
* -t Only take the device off-line temporarily. The offline
* state will not be persistent across reboots.
*/
/* ARGSUSED */
int
zpool_do_offline(int argc, char **argv)
{
int c, i;
char *poolname;
zpool_handle_t *zhp;
int ret = 0;
boolean_t istmp = B_FALSE;
/* check options */
while ((c = getopt(argc, argv, "ft")) != -1) {
switch (c) {
case 't':
istmp = B_TRUE;
break;
case 'f':
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\n"));
usage(B_FALSE);
}
if (argc < 2) {
(void) fprintf(stderr, gettext("missing device name\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_offline(zhp, argv[i], istmp) == 0)
(void) printf(gettext("Bringing device %s offline\n"),
argv[i]);
else
ret = 1;
if (!ret) {
zpool_log_history(g_zfs, argc + optind, argv - optind, poolname,
B_TRUE, B_FALSE);
}
zpool_close(zhp);
return (ret);
}
/*
* zpool clear <pool> [device]
*
* Clear all errors associated with a pool or a particular device.
*/
int
zpool_do_clear(int argc, char **argv)
{
int ret = 0;
zpool_handle_t *zhp;
char *pool, *device;
if (argc < 2) {
(void) fprintf(stderr, gettext("missing pool name\n"));
usage(B_FALSE);
}
if (argc > 3) {
(void) fprintf(stderr, gettext("too many arguments\n"));
usage(B_FALSE);
}
pool = argv[1];
device = argc == 3 ? argv[2] : NULL;
if ((zhp = zpool_open(g_zfs, pool)) == NULL)
return (1);
if (zpool_clear(zhp, device) != 0)
ret = 1;
if (!ret)
zpool_log_history(g_zfs, argc, argv, pool, B_TRUE, B_FALSE);
zpool_close(zhp);
return (ret);
}
typedef struct scrub_cbdata {
int cb_type;
int cb_argc;
char **cb_argv;
} scrub_cbdata_t;
int
scrub_callback(zpool_handle_t *zhp, void *data)
{
scrub_cbdata_t *cb = data;
int err;
/*
* Ignore faulted pools.
*/
if (zpool_get_state(zhp) == POOL_STATE_UNAVAIL) {
(void) fprintf(stderr, gettext("cannot scrub '%s': pool is "
"currently unavailable\n"), zpool_get_name(zhp));
return (1);
}
err = zpool_scrub(zhp, cb->cb_type);
if (!err) {
zpool_log_history(g_zfs, cb->cb_argc, cb->cb_argv,
zpool_get_name(zhp), B_TRUE, B_FALSE);
}
return (err != 0);
}
/*
* zpool scrub [-s] <pool> ...
*
* -s Stop. Stops any in-progress scrub.
*/
int
zpool_do_scrub(int argc, char **argv)
{
int c;
scrub_cbdata_t cb;
cb.cb_type = POOL_SCRUB_EVERYTHING;
/* check options */
while ((c = getopt(argc, argv, "s")) != -1) {
switch (c) {
case 's':
cb.cb_type = POOL_SCRUB_NONE;
break;
case '?':
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
optopt);
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));
}
typedef struct status_cbdata {
int cb_count;
boolean_t cb_allpools;
boolean_t cb_verbose;
boolean_t cb_explain;
boolean_t cb_first;
} status_cbdata_t;
/*
* Print out detailed scrub status.
*/
void
print_scrub_status(nvlist_t *nvroot)
{
vdev_stat_t *vs;
uint_t vsc;
time_t start, end, now;
double fraction_done;
uint64_t examined, total, minutes_left;
char *scrub_type;
verify(nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_STATS,
(uint64_t **)&vs, &vsc) == 0);
/*
* If there's never been a scrub, there's not much to say.
*/
if (vs->vs_scrub_end == 0 && vs->vs_scrub_type == POOL_SCRUB_NONE) {
(void) printf(gettext("none requested\n"));
return;
}
scrub_type = (vs->vs_scrub_type == POOL_SCRUB_RESILVER) ?
"resilver" : "scrub";
start = vs->vs_scrub_start;
end = vs->vs_scrub_end;
now = time(NULL);
examined = vs->vs_scrub_examined;
total = vs->vs_alloc;
if (end != 0) {
(void) printf(gettext("%s %s with %llu errors on %s"),
scrub_type, vs->vs_scrub_complete ? "completed" : "stopped",
(u_longlong_t)vs->vs_scrub_errors, ctime(&end));
return;
}
if (examined == 0)
examined = 1;
if (examined > total)
total = examined;
fraction_done = (double)examined / total;
minutes_left = (uint64_t)((now - start) *
(1 - fraction_done) / fraction_done / 60);
(void) printf(gettext("%s in progress, %.2f%% done, %lluh%um to go\n"),
scrub_type, 100 * fraction_done,
(u_longlong_t)(minutes_left / 60), (uint_t)(minutes_left % 60));
}
typedef struct spare_cbdata {
uint64_t cb_guid;
zpool_handle_t *cb_zhp;
} spare_cbdata_t;
static boolean_t
find_vdev(nvlist_t *nv, uint64_t search)
{
uint64_t guid;
nvlist_t **child;
uint_t c, children;
if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_GUID, &guid) == 0 &&
search == guid)
return (B_TRUE);
if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
&child, &children) == 0) {
for (c = 0; c < children; c++)
if (find_vdev(child[c], search))
return (B_TRUE);
}
return (B_FALSE);
}
static int
find_spare(zpool_handle_t *zhp, void *data)
{
spare_cbdata_t *cbp = data;
nvlist_t *config, *nvroot;
config = zpool_get_config(zhp, NULL);
verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
&nvroot) == 0);
if (find_vdev(nvroot, cbp->cb_guid)) {
cbp->cb_zhp = zhp;
return (1);
}
zpool_close(zhp);
return (0);
}
/*
* Print out configuration state as requested by status_callback.
*/
void
print_status_config(zpool_handle_t *zhp, const char *name, nvlist_t *nv,
int namewidth, int depth, boolean_t isspare)
{
nvlist_t **child;
uint_t c, children;
vdev_stat_t *vs;
char rbuf[6], wbuf[6], cbuf[6], repaired[7];
char *vname;
uint64_t notpresent;
spare_cbdata_t cb;
const char *state;
verify(nvlist_lookup_uint64_array(nv, ZPOOL_CONFIG_STATS,
(uint64_t **)&vs, &c) == 0);
if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
&child, &children) != 0)
children = 0;
state = state_to_name(vs);
if (isspare) {
/*
* For hot spares, we use the terms 'INUSE' and 'AVAILABLE' for
* online drives.
*/
if (vs->vs_aux == VDEV_AUX_SPARED)
state = "INUSE";
else if (vs->vs_state == VDEV_STATE_HEALTHY)
state = "AVAIL";
}
(void) printf("\t%*s%-*s %-8s", depth, "", namewidth - depth,
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 (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_NOT_PRESENT,
&notpresent) == 0) {
char *path;
verify(nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) == 0);
(void) printf(" was %s", path);
} else if (vs->vs_aux != 0) {
(void) printf(" ");
switch (vs->vs_aux) {
case VDEV_AUX_OPEN_FAILED:
(void) printf(gettext("cannot open"));
break;
case VDEV_AUX_BAD_GUID_SUM:
(void) printf(gettext("missing device"));
break;
case VDEV_AUX_NO_REPLICAS:
(void) printf(gettext("insufficient replicas"));
break;
case VDEV_AUX_VERSION_NEWER:
(void) printf(gettext("newer version"));
break;
case VDEV_AUX_SPARED:
verify(nvlist_lookup_uint64(nv, ZPOOL_CONFIG_GUID,
&cb.cb_guid) == 0);
if (zpool_iter(g_zfs, find_spare, &cb) == 1) {
if (strcmp(zpool_get_name(cb.cb_zhp),
zpool_get_name(zhp)) == 0)
(void) printf(gettext("currently in "
"use"));
else
(void) printf(gettext("in use by "
"pool '%s'"),
zpool_get_name(cb.cb_zhp));
zpool_close(cb.cb_zhp);
} else {
(void) printf(gettext("currently in use"));
}
break;
default:
(void) printf(gettext("corrupted data"));
break;
}
} else if (vs->vs_scrub_repaired != 0 && children == 0) {
/*
* Report bytes resilvered/repaired on leaf devices.
*/
zfs_nicenum(vs->vs_scrub_repaired, repaired, sizeof (repaired));
(void) printf(gettext(" %s %s"), repaired,
(vs->vs_scrub_type == POOL_SCRUB_RESILVER) ?
"resilvered" : "repaired");
}
(void) printf("\n");
for (c = 0; c < children; c++) {
vname = zpool_vdev_name(g_zfs, zhp, child[c]);
print_status_config(zhp, vname, child[c],
namewidth, depth + 2, isspare);
free(vname);
}
}
static void
print_error_log(zpool_handle_t *zhp)
{
nvlist_t *nverrlist;
nvpair_t *elem;
char *pathname;
size_t len = MAXPATHLEN * 2;
if (zpool_get_errlog(zhp, &nverrlist) != 0) {
(void) printf("errors: List of errors unavailable "
"(insufficient privileges)\n");
return;
}
(void) printf("errors: Permanent errors have been "
"detected in the following files:\n\n");
pathname = safe_malloc(len);
elem = NULL;
while ((elem = nvlist_next_nvpair(nverrlist, elem)) != NULL) {
nvlist_t *nv;
uint64_t dsobj, obj;
verify(nvpair_value_nvlist(elem, &nv) == 0);
verify(nvlist_lookup_uint64(nv, ZPOOL_ERR_DATASET,
&dsobj) == 0);
verify(nvlist_lookup_uint64(nv, ZPOOL_ERR_OBJECT,
&obj) == 0);
zpool_obj_to_path(zhp, dsobj, obj, pathname, len);
(void) printf("%7s %s\n", "", pathname);
}
free(pathname);
nvlist_free(nverrlist);
}
static void
print_spares(zpool_handle_t *zhp, nvlist_t **spares, uint_t nspares,
int namewidth)
{
uint_t i;
char *name;
if (nspares == 0)
return;
(void) printf(gettext("\tspares\n"));
for (i = 0; i < nspares; i++) {
name = zpool_vdev_name(g_zfs, zhp, spares[i]);
print_status_config(zhp, name, spares[i],
namewidth, 2, B_TRUE);
free(name);
}
}
/*
* Display a summary of pool status. Displays a summary such as:
*
* pool: tank
* status: DEGRADED
* reason: One or more devices ...
* see: http://www.sun.com/msg/ZFS-xxxx-01
* config:
* mirror DEGRADED
* c1t0d0 OK
* c2t0d0 UNAVAIL
*
* When given the '-v' option, we print out the complete config. If the '-e'
* option is specified, then we print out error rate information as well.
*/
int
status_callback(zpool_handle_t *zhp, void *data)
{
status_cbdata_t *cbp = data;
nvlist_t *config, *nvroot;
char *msgid;
int reason;
const char *health;
uint_t c;
vdev_stat_t *vs;
config = zpool_get_config(zhp, NULL);
reason = zpool_get_status(zhp, &msgid);
cbp->cb_count++;
/*
* If we were given 'zpool status -x', only report those pools with
* problems.
*/
if (reason == ZPOOL_STATUS_OK && cbp->cb_explain) {
if (!cbp->cb_allpools) {
(void) printf(gettext("pool '%s' is healthy\n"),
zpool_get_name(zhp));
if (cbp->cb_first)
cbp->cb_first = B_FALSE;
}
return (0);
}
if (cbp->cb_first)
cbp->cb_first = B_FALSE;
else
(void) printf("\n");
verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
&nvroot) == 0);
verify(nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_STATS,
(uint64_t **)&vs, &c) == 0);
health = state_to_name(vs);
(void) printf(gettext(" pool: %s\n"), zpool_get_name(zhp));
(void) printf(gettext(" state: %s\n"), health);
switch (reason) {
case ZPOOL_STATUS_MISSING_DEV_R:
(void) printf(gettext("status: One or more devices could not "
"be opened. Sufficient replicas exist for\n\tthe pool to "
"continue functioning in a degraded state.\n"));
(void) printf(gettext("action: Attach the missing device and "
"online it using 'zpool online'.\n"));
break;
case ZPOOL_STATUS_MISSING_DEV_NR:
(void) printf(gettext("status: One or more devices could not "
"be opened. There are insufficient\n\treplicas for the "
"pool to continue functioning.\n"));
(void) printf(gettext("action: Attach the missing device and "
"online it using 'zpool online'.\n"));
break;
case ZPOOL_STATUS_CORRUPT_LABEL_R:
(void) printf(gettext("status: One or more devices could not "
"be used because the label is missing or\n\tinvalid. "
"Sufficient replicas exist for the pool to continue\n\t"
"functioning in a degraded state.\n"));
(void) printf(gettext("action: Replace the device using "
"'zpool replace'.\n"));
break;
case ZPOOL_STATUS_CORRUPT_LABEL_NR:
(void) printf(gettext("status: One or more devices could not "
"be used because the label is missing \n\tor invalid. "
"There are insufficient replicas for the pool to "
"continue\n\tfunctioning.\n"));
(void) printf(gettext("action: Destroy and re-create the pool "
"from a backup source.\n"));
break;
case ZPOOL_STATUS_FAILING_DEV:
(void) printf(gettext("status: One or more devices has "
"experienced an unrecoverable error. An\n\tattempt was "
"made to correct the error. Applications are "
"unaffected.\n"));
(void) printf(gettext("action: Determine if the device needs "
"to be replaced, and clear the errors\n\tusing "
"'zpool clear' or replace the device with 'zpool "
"replace'.\n"));
break;
case ZPOOL_STATUS_OFFLINE_DEV:
(void) printf(gettext("status: One or more devices has "
"been taken offline by the administrator.\n\tSufficient "
"replicas exist for the pool to continue functioning in "
"a\n\tdegraded state.\n"));
(void) printf(gettext("action: Online the device using "
"'zpool online' or replace the device with\n\t'zpool "
"replace'.\n"));
break;
case ZPOOL_STATUS_RESILVERING:
(void) printf(gettext("status: One or more devices is "
"currently being resilvered. The pool will\n\tcontinue "
"to function, possibly in a degraded state.\n"));
(void) printf(gettext("action: Wait for the resilver to "
"complete.\n"));
break;
case ZPOOL_STATUS_CORRUPT_DATA:
(void) printf(gettext("status: One or more devices has "
"experienced an error resulting in data\n\tcorruption. "
"Applications may be affected.\n"));
(void) printf(gettext("action: Restore the file in question "
"if possible. Otherwise restore the\n\tentire pool from "
"backup.\n"));
break;
case ZPOOL_STATUS_CORRUPT_POOL:
(void) printf(gettext("status: The pool metadata is corrupted "
"and the pool cannot be opened.\n"));
(void) printf(gettext("action: Destroy and re-create the pool "
"from a backup source.\n"));
break;
case ZPOOL_STATUS_VERSION_OLDER:
(void) printf(gettext("status: The pool is formatted using an "
"older on-disk format. The pool can\n\tstill be used, but "
"some features are unavailable.\n"));
(void) printf(gettext("action: Upgrade the pool using 'zpool "
"upgrade'. Once this is done, the\n\tpool will no longer "
"be accessible on older software versions.\n"));
break;
case ZPOOL_STATUS_VERSION_NEWER:
(void) printf(gettext("status: The pool has been upgraded to a "
"newer, incompatible on-disk version.\n\tThe pool cannot "
"be accessed on this system.\n"));
(void) printf(gettext("action: Access the pool from a system "
"running more recent software, or\n\trestore the pool from "
"backup.\n"));
break;
default:
/*
* The remaining errors can't actually be generated, yet.
*/
assert(reason == ZPOOL_STATUS_OK);
}
if (msgid != NULL)
(void) printf(gettext(" see: http://www.sun.com/msg/%s\n"),
msgid);
if (config != NULL) {
int namewidth;
uint64_t nerr;
nvlist_t **spares;
uint_t nspares;
(void) printf(gettext(" scrub: "));
print_scrub_status(nvroot);
namewidth = max_width(zhp, nvroot, 0, 0);
if (namewidth < 10)
namewidth = 10;
(void) printf(gettext("config:\n\n"));
(void) printf(gettext("\t%-*s %-8s %5s %5s %5s\n"), namewidth,
"NAME", "STATE", "READ", "WRITE", "CKSUM");
print_status_config(zhp, zpool_get_name(zhp), nvroot,
namewidth, 0, B_FALSE);
if (nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_SPARES,
&spares, &nspares) == 0)
print_spares(zhp, spares, nspares, namewidth);
if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_ERRCOUNT,
&nerr) == 0) {
nvlist_t *nverrlist = NULL;
/*
* If the approximate error count is small, get a
* precise count by fetching the entire log and
* uniquifying the results.
*/
if (nerr < 100 && !cbp->cb_verbose &&
zpool_get_errlog(zhp, &nverrlist) == 0) {
nvpair_t *elem;
elem = NULL;
nerr = 0;
while ((elem = nvlist_next_nvpair(nverrlist,
elem)) != NULL) {
nerr++;
}
}
nvlist_free(nverrlist);
(void) printf("\n");
if (nerr == 0)
(void) printf(gettext("errors: No known data "
"errors\n"));
else if (!cbp->cb_verbose)
(void) printf(gettext("errors: %llu data "
"errors, use '-v' for a list\n"),
(u_longlong_t)nerr);
else
print_error_log(zhp);
}
} else {
(void) printf(gettext("config: The configuration cannot be "
"determined.\n"));
}
return (0);
}
/*
* zpool status [-vx] [pool] ...
*
* -v Display complete error logs
* -x Display only pools with potential problems
*
* Describes the health status of all pools or some subset.
*/
int
zpool_do_status(int argc, char **argv)
{
int c;
int ret;
status_cbdata_t cb = { 0 };
/* check options */
while ((c = getopt(argc, argv, "vx")) != -1) {
switch (c) {
case 'v':
cb.cb_verbose = B_TRUE;
break;
case 'x':
cb.cb_explain = B_TRUE;
break;
case '?':
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
optopt);
usage(B_FALSE);
}
}
argc -= optind;
argv += optind;
cb.cb_first = B_TRUE;
if (argc == 0)
cb.cb_allpools = B_TRUE;
ret = for_each_pool(argc, argv, B_TRUE, NULL, status_callback, &cb);
if (argc == 0 && cb.cb_count == 0)
(void) printf(gettext("no pools available\n"));
else if (cb.cb_explain && cb.cb_first && cb.cb_allpools)
(void) printf(gettext("all pools are healthy\n"));
return (ret);
}
typedef struct upgrade_cbdata {
int cb_all;
int cb_first;
int cb_newer;
int cb_argc;
char **cb_argv;
} upgrade_cbdata_t;
static int
upgrade_cb(zpool_handle_t *zhp, void *arg)
{
upgrade_cbdata_t *cbp = arg;
nvlist_t *config;
uint64_t version;
int ret = 0;
config = zpool_get_config(zhp, NULL);
verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_VERSION,
&version) == 0);
if (!cbp->cb_newer && version < ZFS_VERSION) {
if (!cbp->cb_all) {
if (cbp->cb_first) {
(void) printf(gettext("The following pools are "
"out of date, and can be upgraded. After "
"being\nupgraded, these pools will no "
"longer be accessible by older software "
"versions.\n\n"));
(void) printf(gettext("VER POOL\n"));
(void) printf(gettext("--- ------------\n"));
cbp->cb_first = B_FALSE;
}
(void) printf("%2llu %s\n", (u_longlong_t)version,
zpool_get_name(zhp));
} else {
cbp->cb_first = B_FALSE;
ret = zpool_upgrade(zhp);
if (!ret) {
zpool_log_history(g_zfs, cbp->cb_argc,
cbp->cb_argv, zpool_get_name(zhp), B_TRUE,
B_FALSE);
(void) printf(gettext("Successfully upgraded "
"'%s'\n"), zpool_get_name(zhp));
}
}
} else if (cbp->cb_newer && version > ZFS_VERSION) {
assert(!cbp->cb_all);
if (cbp->cb_first) {
(void) printf(gettext("The following pools are "
"formatted using a newer software version and\n"
"cannot be accessed on the current system.\n\n"));
(void) printf(gettext("VER POOL\n"));
(void) printf(gettext("--- ------------\n"));
cbp->cb_first = B_FALSE;
}
(void) printf("%2llu %s\n", (u_longlong_t)version,
zpool_get_name(zhp));
}
zpool_close(zhp);
return (ret);
}
/* ARGSUSED */
static int
upgrade_one(zpool_handle_t *zhp, void *data)
{
nvlist_t *config;
uint64_t version;
int ret;
upgrade_cbdata_t *cbp = data;
config = zpool_get_config(zhp, NULL);
verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_VERSION,
&version) == 0);
if (version == ZFS_VERSION) {
(void) printf(gettext("Pool '%s' is already formatted "
"using the current version.\n"), zpool_get_name(zhp));
return (0);
}
ret = zpool_upgrade(zhp);
if (!ret) {
zpool_log_history(g_zfs, cbp->cb_argc, cbp->cb_argv,
zpool_get_name(zhp), B_TRUE, B_FALSE);
(void) printf(gettext("Successfully upgraded '%s' "
"from version %llu to version %llu\n"), zpool_get_name(zhp),
(u_longlong_t)version, (u_longlong_t)ZFS_VERSION);
}
return (ret != 0);
}
/*
* zpool upgrade
* zpool upgrade -v
* zpool upgrade <-a | pool>
*
* With no arguments, display downrev'd ZFS pool available for upgrade.
* Individual pools can be upgraded by specifying the pool, and '-a' will
* upgrade all pools.
*/
int
zpool_do_upgrade(int argc, char **argv)
{
int c;
upgrade_cbdata_t cb = { 0 };
int ret = 0;
boolean_t showversions = B_FALSE;
/* check options */
while ((c = getopt(argc, argv, "av")) != -1) {
switch (c) {
case 'a':
cb.cb_all = B_TRUE;
break;
case 'v':
showversions = B_TRUE;
break;
case '?':
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
optopt);
usage(B_FALSE);
}
}
cb.cb_argc = argc;
cb.cb_argv = argv;
argc -= optind;
argv += optind;
if (showversions) {
if (cb.cb_all || argc != 0) {
(void) fprintf(stderr, gettext("-v option is "
"incompatible with other arguments\n"));
usage(B_FALSE);
}
} else if (cb.cb_all) {
if (argc != 0) {
(void) fprintf(stderr, gettext("-a option is "
"incompatible with other arguments\n"));
usage(B_FALSE);
}
}
(void) printf(gettext("This system is currently running ZFS version "
"%llu.\n\n"), ZFS_VERSION);
cb.cb_first = B_TRUE;
if (showversions) {
(void) printf(gettext("The following versions are "
"supported:\n\n"));
(void) printf(gettext("VER DESCRIPTION\n"));
(void) printf("--- -----------------------------------------"
"---------------\n");
(void) printf(gettext(" 1 Initial ZFS version\n"));
(void) printf(gettext(" 2 Ditto blocks "
"(replicated metadata)\n"));
(void) printf(gettext(" 3 Hot spares and double parity "
"RAID-Z\n"));
(void) printf(gettext(" 4 zpool history\n"));
(void) printf(gettext(" 5 Compression using the gzip "
"algorithm\n"));
(void) printf(gettext(" 6 bootfs pool property "));
(void) printf(gettext("\nFor more information on a particular "
"version, including supported releases, see:\n\n"));
(void) printf("http://www.opensolaris.org/os/community/zfs/"
"version/N\n\n");
(void) printf(gettext("Where 'N' is the version number.\n"));
} else if (argc == 0) {
int notfound;
ret = zpool_iter(g_zfs, upgrade_cb, &cb);
notfound = cb.cb_first;
if (!cb.cb_all && ret == 0) {
if (!cb.cb_first)
(void) printf("\n");
cb.cb_first = B_TRUE;
cb.cb_newer = B_TRUE;
ret = zpool_iter(g_zfs, upgrade_cb, &cb);
if (!cb.cb_first) {
notfound = B_FALSE;
(void) printf("\n");
}
}
if (ret == 0) {
if (notfound)
(void) printf(gettext("All pools are formatted "
"using this version.\n"));
else if (!cb.cb_all)
(void) printf(gettext("Use 'zpool upgrade -v' "
"for a list of available versions and "
"their associated\nfeatures.\n"));
}
} else {
ret = for_each_pool(argc, argv, B_FALSE, NULL,
upgrade_one, &cb);
}
return (ret);
}
/*
* Print out the command history for a specific pool.
*/
static int
get_history_one(zpool_handle_t *zhp, void *data)
{
nvlist_t *nvhis;
nvlist_t **records;
uint_t numrecords;
char *cmdstr;
uint64_t dst_time;
time_t tsec;
struct tm t;
char tbuf[30];
int ret, i;
*(boolean_t *)data = B_FALSE;
(void) printf(gettext("History for '%s':\n"), zpool_get_name(zhp));
if ((ret = zpool_get_history(zhp, &nvhis)) != 0)
return (ret);
verify(nvlist_lookup_nvlist_array(nvhis, ZPOOL_HIST_RECORD,
&records, &numrecords) == 0);
for (i = 0; i < numrecords; i++) {
if (nvlist_lookup_uint64(records[i], ZPOOL_HIST_TIME,
&dst_time) == 0) {
verify(nvlist_lookup_string(records[i], ZPOOL_HIST_CMD,
&cmdstr) == 0);
tsec = dst_time;
(void) localtime_r(&tsec, &t);
(void) strftime(tbuf, sizeof (tbuf), "%F.%T", &t);
(void) printf("%s %s\n", tbuf, cmdstr);
}
}
(void) printf("\n");
nvlist_free(nvhis);
return (ret);
}
/*
* zpool history <pool>
*
* Displays the history of commands that modified pools.
*/
int
zpool_do_history(int argc, char **argv)
{
boolean_t first = B_TRUE;
int ret;
argc -= optind;
argv += optind;
ret = for_each_pool(argc, argv, B_FALSE, NULL, get_history_one,
&first);
if (argc == 0 && first == B_TRUE) {
(void) printf(gettext("no pools available\n"));
return (0);
}
return (ret);
}
static int
get_callback(zpool_handle_t *zhp, void *data)
{
libzfs_get_cbdata_t *cbp = (libzfs_get_cbdata_t *)data;
char value[MAXNAMELEN];
zfs_source_t srctype;
zpool_proplist_t *pl;
for (pl = cbp->cb_proplist; pl != NULL; pl = pl->pl_next) {
/*
* Skip the special fake placeholder.
*/
if (pl->pl_prop == ZFS_PROP_NAME &&
pl == cbp->cb_proplist)
continue;
if (zpool_get_prop(zhp, pl->pl_prop,
value, sizeof (value), &srctype) != 0)
continue;
libzfs_print_one_property(zpool_get_name(zhp), cbp,
zpool_prop_to_name(pl->pl_prop), value, srctype, NULL);
}
return (0);
}
int
zpool_do_get(int argc, char **argv)
{
libzfs_get_cbdata_t cb = { 0 };
zpool_proplist_t fake_name = { 0 };
int ret;
if (argc < 3)
usage(B_FALSE);
cb.cb_first = B_TRUE;
cb.cb_sources = ZFS_SRC_ALL;
cb.cb_columns[0] = GET_COL_NAME;
cb.cb_columns[1] = GET_COL_PROPERTY;
cb.cb_columns[2] = GET_COL_VALUE;
cb.cb_columns[3] = GET_COL_SOURCE;
if (zpool_get_proplist(g_zfs, argv[1], &cb.cb_proplist) != 0)
usage(B_FALSE);
if (cb.cb_proplist != NULL) {
fake_name.pl_prop = ZFS_PROP_NAME;
fake_name.pl_width = strlen(gettext("NAME"));
fake_name.pl_next = cb.cb_proplist;
cb.cb_proplist = &fake_name;
}
ret = for_each_pool(argc - 2, argv + 2, B_TRUE, &cb.cb_proplist,
get_callback, &cb);
if (cb.cb_proplist == &fake_name)
zfs_free_proplist(fake_name.pl_next);
else
zfs_free_proplist(cb.cb_proplist);
return (ret);
}
typedef struct set_cbdata {
char *cb_propname;
char *cb_value;
boolean_t cb_any_successful;
} set_cbdata_t;
int
set_callback(zpool_handle_t *zhp, void *data)
{
int error;
set_cbdata_t *cb = (set_cbdata_t *)data;
error = zpool_set_prop(zhp, cb->cb_propname, cb->cb_value);
if (!error)
cb->cb_any_successful = B_TRUE;
return (error);
}
int
zpool_do_set(int argc, char **argv)
{
set_cbdata_t cb = { 0 };
int error;
if (argc > 1 && argv[1][0] == '-') {
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
argv[1][1]);
usage(B_FALSE);
}
if (argc < 2) {
(void) fprintf(stderr, gettext("missing property=value "
"argument\n"));
usage(B_FALSE);
}
if (argc < 3) {
(void) fprintf(stderr, gettext("missing pool name\n"));
usage(B_FALSE);
}
if (argc > 3) {
(void) fprintf(stderr, gettext("too many pool names\n"));
usage(B_FALSE);
}
cb.cb_propname = argv[1];
cb.cb_value = strchr(cb.cb_propname, '=');
if (cb.cb_value == NULL) {
(void) fprintf(stderr, gettext("missing value in "
"property=value argument\n"));
usage(B_FALSE);
}
*(cb.cb_value) = '\0';
cb.cb_value++;
error = for_each_pool(argc - 2, argv + 2, B_TRUE, NULL,
set_callback, &cb);
if (cb.cb_any_successful) {
*(cb.cb_value - 1) = '=';
zpool_log_history(g_zfs, argc, argv, argv[2], B_FALSE, B_FALSE);
}
return (error);
}
static int
find_command_idx(char *command, int *idx)
{
int i;
for (i = 0; i < NCOMMAND; i++) {
if (command_table[i].name == NULL)
continue;
if (strcmp(command, command_table[i].name) == 0) {
*idx = i;
return (0);
}
}
return (1);
}
int
main(int argc, char **argv)
{
int ret;
int i;
char *cmdname;
int found = 0;
(void) setlocale(LC_ALL, "");
(void) textdomain(TEXT_DOMAIN);
if ((g_zfs = libzfs_init()) == NULL) {
(void) fprintf(stderr, gettext("internal error: failed to "
"initialize ZFS library\n"));
return (1);
}
libzfs_print_on_error(g_zfs, B_TRUE);
opterr = 0;
/*
* Make sure the user has specified some command.
*/
if (argc < 2) {
(void) fprintf(stderr, gettext("missing command\n"));
usage(B_FALSE);
}
cmdname = argv[1];
/*
* Special case '-?'
*/
if (strcmp(cmdname, "-?") == 0)
usage(B_TRUE);
/*
* 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);
found++;
}
/*
* 'freeze' is a vile debugging abomination, so we treat it as such.
*/
if (strcmp(cmdname, "freeze") == 0 && argc == 3) {
char buf[16384];
int fd = open(ZFS_DEV, O_RDWR);
(void) strcpy((void *)buf, argv[2]);
return (!!ioctl(fd, ZFS_IOC_POOL_FREEZE, buf));
}
if (!found) {
(void) fprintf(stderr, gettext("unrecognized "
"command '%s'\n"), cmdname);
usage(B_FALSE);
}
libzfs_fini(g_zfs);
/*
* The 'ZFS_ABORT' environment variable causes us to dump core on exit
* for the purposes of running ::findleaks.
*/
if (getenv("ZFS_ABORT") != NULL) {
(void) printf("dumping core by request\n");
abort();
}
return (ret);
}