Path: blob/main/sys/contrib/openzfs/cmd/zpool/zpool_iter.c
107935 views
// SPDX-License-Identifier: CDDL-1.01/*2* CDDL HEADER START3*4* The contents of this file are subject to the terms of the5* Common Development and Distribution License (the "License").6* You may not use this file except in compliance with the License.7*8* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE9* or https://opensource.org/licenses/CDDL-1.0.10* See the License for the specific language governing permissions11* and limitations under the License.12*13* When distributing Covered Code, include this CDDL HEADER in each14* file and include the License file at usr/src/OPENSOLARIS.LICENSE.15* If applicable, add the following below this CDDL HEADER, with the16* fields enclosed by brackets "[]" replaced with your own identifying17* information: Portions Copyright [yyyy] [name of copyright owner]18*19* CDDL HEADER END20*/21/*22* Copyright 2007 Sun Microsystems, Inc. All rights reserved.23* Use is subject to license terms.24*/2526/*27* Copyright 2016 Igor Kozhukhov <[email protected]>.28* Copyright (c) 2025, Klara, Inc.29*/3031#include <libintl.h>32#include <stddef.h>33#include <stdio.h>34#include <stdlib.h>35#include <string.h>3637#include <libzfs.h>38#include <libzutil.h>39#include <sys/zfs_context.h>40#include <sys/wait.h>4142#include "zpool_util.h"4344/*45* Private interface for iterating over pools specified on the command line.46* Most consumers will call for_each_pool, but in order to support iostat, we47* allow fined grained control through the zpool_list_t interface.48*/4950typedef struct zpool_node {51zpool_handle_t *zn_handle;52avl_node_t zn_avlnode;53hrtime_t zn_last_refresh;54} zpool_node_t;5556struct zpool_list {57boolean_t zl_findall;58boolean_t zl_literal;59avl_tree_t zl_avl;60zprop_list_t **zl_proplist;61zfs_type_t zl_type;62hrtime_t zl_last_refresh;63};6465static int66zpool_compare(const void *larg, const void *rarg)67{68zpool_handle_t *l = ((zpool_node_t *)larg)->zn_handle;69zpool_handle_t *r = ((zpool_node_t *)rarg)->zn_handle;70const char *lname = zpool_get_name(l);71const char *rname = zpool_get_name(r);7273return (TREE_ISIGN(strcmp(lname, rname)));74}7576/*77* Callback function for pool_list_get(). Adds the given pool to the AVL tree78* of known pools.79*/80static int81add_pool(zpool_handle_t *zhp, zpool_list_t *zlp)82{83zpool_node_t *node, *new = safe_malloc(sizeof (zpool_node_t));84avl_index_t idx;8586new->zn_handle = zhp;8788node = avl_find(&zlp->zl_avl, new, &idx);89if (node == NULL) {90if (zlp->zl_proplist &&91zpool_expand_proplist(zhp, zlp->zl_proplist,92zlp->zl_type, zlp->zl_literal) != 0) {93zpool_close(zhp);94free(new);95return (-1);96}97new->zn_last_refresh = zlp->zl_last_refresh;98avl_insert(&zlp->zl_avl, new, idx);99} else {100zpool_refresh_stats_from_handle(node->zn_handle, zhp);101node->zn_last_refresh = zlp->zl_last_refresh;102zpool_close(zhp);103free(new);104return (-1);105}106107return (0);108}109110/*111* add_pool(), but always returns 0. This allows zpool_iter() to continue112* even if a pool exists in the tree, or we fail to get the properties for113* a new one.114*/115static int116add_pool_cb(zpool_handle_t *zhp, void *data)117{118(void) add_pool(zhp, data);119return (0);120}121122/*123* Create a list of pools based on the given arguments. If we're given no124* arguments, then iterate over all pools in the system and add them to the AVL125* tree. Otherwise, add only those pool explicitly specified on the command126* line.127*/128zpool_list_t *129pool_list_get(int argc, char **argv, zprop_list_t **proplist, zfs_type_t type,130boolean_t literal, int *err)131{132zpool_list_t *zlp;133134zlp = safe_malloc(sizeof (zpool_list_t));135136avl_create(&zlp->zl_avl, zpool_compare,137sizeof (zpool_node_t), offsetof(zpool_node_t, zn_avlnode));138139zlp->zl_proplist = proplist;140zlp->zl_type = type;141142zlp->zl_literal = literal;143zlp->zl_last_refresh = gethrtime();144145if (argc == 0) {146(void) zpool_iter(g_zfs, add_pool_cb, zlp);147zlp->zl_findall = B_TRUE;148} else {149int i;150151for (i = 0; i < argc; i++) {152zpool_handle_t *zhp;153154if ((zhp = zpool_open_canfail(g_zfs, argv[i])) !=155NULL) {156if (add_pool(zhp, zlp) != 0)157*err = B_TRUE;158} else {159*err = B_TRUE;160}161}162}163164return (zlp);165}166167/*168* Refresh the state of all pools on the list. Additionally, if no options were169* given on the command line, add any new pools and remove any that are no170* longer available.171*/172int173pool_list_refresh(zpool_list_t *zlp)174{175zlp->zl_last_refresh = gethrtime();176177if (!zlp->zl_findall) {178/*179* This list is a fixed list of pools, so we must not add180* or remove any. Just walk over them and refresh their181* state.182*/183int navail = 0;184for (zpool_node_t *node = avl_first(&zlp->zl_avl);185node != NULL; node = AVL_NEXT(&zlp->zl_avl, node)) {186boolean_t missing;187zpool_refresh_stats(node->zn_handle, &missing);188navail += !missing;189node->zn_last_refresh = zlp->zl_last_refresh;190}191return (navail);192}193194/* Search for any new pools and add them to the list. */195(void) zpool_iter(g_zfs, add_pool_cb, zlp);196197/* Walk the list of existing pools, and update or remove them. */198zpool_node_t *node, *next;199for (node = avl_first(&zlp->zl_avl); node != NULL; node = next) {200next = AVL_NEXT(&zlp->zl_avl, node);201202/*203* Skip any that were refreshed and are online; they were added204* by zpool_iter() and are already up to date.205*/206if (node->zn_last_refresh == zlp->zl_last_refresh &&207zpool_get_state(node->zn_handle) != POOL_STATE_UNAVAIL)208continue;209210/* Refresh and remove if necessary. */211boolean_t missing;212zpool_refresh_stats(node->zn_handle, &missing);213if (missing) {214avl_remove(&zlp->zl_avl, node);215zpool_close(node->zn_handle);216free(node);217} else {218node->zn_last_refresh = zlp->zl_last_refresh;219}220}221222return (avl_numnodes(&zlp->zl_avl));223}224225/*226* Iterate over all pools in the list, executing the callback for each227*/228int229pool_list_iter(zpool_list_t *zlp, int unavail, zpool_iter_f func,230void *data)231{232zpool_node_t *node, *next_node;233int ret = 0;234235for (node = avl_first(&zlp->zl_avl); node != NULL; node = next_node) {236next_node = AVL_NEXT(&zlp->zl_avl, node);237if (zpool_get_state(node->zn_handle) != POOL_STATE_UNAVAIL ||238unavail)239ret |= func(node->zn_handle, data);240}241242return (ret);243}244245/*246* Free all the handles associated with this list.247*/248void249pool_list_free(zpool_list_t *zlp)250{251zpool_node_t *node;252void *cookie = NULL;253254while ((node = avl_destroy_nodes(&zlp->zl_avl, &cookie)) != NULL) {255zpool_close(node->zn_handle);256free(node);257}258259avl_destroy(&zlp->zl_avl);260free(zlp);261}262263/*264* Returns the number of elements in the pool list.265*/266int267pool_list_count(zpool_list_t *zlp)268{269return (avl_numnodes(&zlp->zl_avl));270}271272/*273* High level function which iterates over all pools given on the command line,274* using the pool_list_* interfaces.275*/276int277for_each_pool(int argc, char **argv, boolean_t unavail,278zprop_list_t **proplist, zfs_type_t type, boolean_t literal,279zpool_iter_f func, void *data)280{281zpool_list_t *list;282int ret = 0;283284if ((list = pool_list_get(argc, argv, proplist, type, literal,285&ret)) == NULL)286return (1);287288if (pool_list_iter(list, unavail, func, data) != 0)289ret = 1;290291pool_list_free(list);292293return (ret);294}295296/*297* This is the equivalent of for_each_pool() for vdevs. It iterates thorough298* all vdevs in the pool, ignoring root vdevs and holes, calling func() on299* each one.300*301* @zhp: Zpool handle302* @func: Function to call on each vdev303* @data: Custom data to pass to the function304*/305int306for_each_vdev(zpool_handle_t *zhp, pool_vdev_iter_f func, void *data)307{308nvlist_t *config, *nvroot = NULL;309310if ((config = zpool_get_config(zhp, NULL)) != NULL) {311verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,312&nvroot) == 0);313}314return (for_each_vdev_cb((void *) zhp, nvroot, func, data));315}316317/*318* Process the vcdl->vdev_cmd_data[] array to figure out all the unique column319* names and their widths. When this function is done, vcdl->uniq_cols,320* vcdl->uniq_cols_cnt, and vcdl->uniq_cols_width will be filled in.321*/322static void323process_unique_cmd_columns(vdev_cmd_data_list_t *vcdl)324{325char **uniq_cols = NULL, **tmp = NULL;326int *uniq_cols_width;327vdev_cmd_data_t *data;328int cnt = 0;329int k;330331/* For each vdev */332for (int i = 0; i < vcdl->count; i++) {333data = &vcdl->data[i];334/* For each column the vdev reported */335for (int j = 0; j < data->cols_cnt; j++) {336/* Is this column in our list of unique column names? */337for (k = 0; k < cnt; k++) {338if (strcmp(data->cols[j], uniq_cols[k]) == 0)339break; /* yes it is */340}341if (k == cnt) {342/* No entry for column, add to list */343tmp = realloc(uniq_cols, sizeof (*uniq_cols) *344(cnt + 1));345if (tmp == NULL)346break; /* Nothing we can do... */347uniq_cols = tmp;348uniq_cols[cnt] = data->cols[j];349cnt++;350}351}352}353354/*355* We now have a list of all the unique column names. Figure out the356* max width of each column by looking at the column name and all its357* values.358*/359uniq_cols_width = safe_malloc(sizeof (*uniq_cols_width) * cnt);360for (int i = 0; i < cnt; i++) {361/* Start off with the column title's width */362uniq_cols_width[i] = strlen(uniq_cols[i]);363/* For each vdev */364for (int j = 0; j < vcdl->count; j++) {365/* For each of the vdev's values in a column */366data = &vcdl->data[j];367for (k = 0; k < data->cols_cnt; k++) {368/* Does this vdev have a value for this col? */369if (strcmp(data->cols[k], uniq_cols[i]) == 0) {370/* Is the value width larger? */371uniq_cols_width[i] =372MAX(uniq_cols_width[i],373strlen(data->lines[k]));374}375}376}377}378379vcdl->uniq_cols = uniq_cols;380vcdl->uniq_cols_cnt = cnt;381vcdl->uniq_cols_width = uniq_cols_width;382}383384385/*386* Process a line of command output387*388* When running 'zpool iostat|status -c' the lines of output can either be389* in the form of:390*391* column_name=value392*393* Or just:394*395* value396*397* Process the column_name (if any) and value.398*399* Returns 0 if line was processed, and there are more lines can still be400* processed.401*402* Returns 1 if this was the last line to process, or error.403*/404static int405vdev_process_cmd_output(vdev_cmd_data_t *data, char *line)406{407char *col;408char *val;409char *equals;410char **tmp;411412if (line == NULL)413return (1);414415equals = strchr(line, '=');416if (equals != NULL) {417/*418* We have a 'column=value' type line. Split it into the419* column and value strings by turning the '=' into a '\0'.420*/421*equals = '\0';422col = line;423val = equals + 1;424} else {425col = NULL;426val = line;427}428429/* Do we already have a column by this name? If so, skip it. */430if (col != NULL) {431for (int i = 0; i < data->cols_cnt; i++) {432if (strcmp(col, data->cols[i]) == 0)433return (0); /* Duplicate, skip */434}435}436437if (val != NULL) {438tmp = realloc(data->lines,439(data->lines_cnt + 1) * sizeof (*data->lines));440if (tmp == NULL)441return (1);442443data->lines = tmp;444data->lines[data->lines_cnt] = strdup(val);445data->lines_cnt++;446}447448if (col != NULL) {449tmp = realloc(data->cols,450(data->cols_cnt + 1) * sizeof (*data->cols));451if (tmp == NULL)452return (1);453454data->cols = tmp;455data->cols[data->cols_cnt] = strdup(col);456data->cols_cnt++;457}458459if (val != NULL && col == NULL)460return (1);461462return (0);463}464465/*466* Run the cmd and store results in *data.467*/468static void469vdev_run_cmd(vdev_cmd_data_t *data, char *cmd)470{471int rc;472char *argv[2] = {cmd};473char **env;474char **lines = NULL;475int lines_cnt = 0;476int i;477478env = zpool_vdev_script_alloc_env(data->pool, data->path, data->upath,479data->vdev_enc_sysfs_path, NULL, NULL);480if (env == NULL)481goto out;482483/* Run the command */484rc = libzfs_run_process_get_stdout_nopath(cmd, argv, env, &lines,485&lines_cnt);486487zpool_vdev_script_free_env(env);488489if (rc != 0)490goto out;491492/* Process the output we got */493for (i = 0; i < lines_cnt; i++)494if (vdev_process_cmd_output(data, lines[i]) != 0)495break;496497out:498if (lines != NULL)499libzfs_free_str_array(lines, lines_cnt);500}501502/*503* Generate the search path for zpool iostat/status -c scripts.504* The string returned must be freed.505*/506char *507zpool_get_cmd_search_path(void)508{509const char *env;510char *sp = NULL;511512env = getenv("ZPOOL_SCRIPTS_PATH");513if (env != NULL)514return (strdup(env));515516env = getenv("HOME");517if (env != NULL) {518if (asprintf(&sp, "%s/.zpool.d:%s",519env, ZPOOL_SCRIPTS_DIR) != -1) {520return (sp);521}522}523524if (asprintf(&sp, "%s", ZPOOL_SCRIPTS_DIR) != -1)525return (sp);526527return (NULL);528}529530/* Thread function run for each vdev */531static void532vdev_run_cmd_thread(void *cb_cmd_data)533{534vdev_cmd_data_t *data = cb_cmd_data;535char *cmd = NULL, *cmddup, *cmdrest;536537cmddup = strdup(data->cmd);538if (cmddup == NULL)539return;540541cmdrest = cmddup;542while ((cmd = strtok_r(cmdrest, ",", &cmdrest))) {543char *dir = NULL, *sp, *sprest;544char fullpath[MAXPATHLEN];545546if (strchr(cmd, '/') != NULL)547continue;548549sp = zpool_get_cmd_search_path();550if (sp == NULL)551continue;552553sprest = sp;554while ((dir = strtok_r(sprest, ":", &sprest))) {555if (snprintf(fullpath, sizeof (fullpath),556"%s/%s", dir, cmd) == -1)557continue;558559if (access(fullpath, X_OK) == 0) {560vdev_run_cmd(data, fullpath);561break;562}563}564free(sp);565}566free(cmddup);567}568569/* For each vdev in the pool run a command */570static int571for_each_vdev_run_cb(void *zhp_data, nvlist_t *nv, void *cb_vcdl)572{573vdev_cmd_data_list_t *vcdl = cb_vcdl;574vdev_cmd_data_t *data;575const char *path = NULL;576char *vname = NULL;577const char *vdev_enc_sysfs_path = NULL;578int i, match = 0;579zpool_handle_t *zhp = zhp_data;580581if (nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) != 0)582return (1);583584/* Make sure we're getting the updated enclosure sysfs path */585update_vdev_config_dev_sysfs_path(nv, path,586ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH);587588nvlist_lookup_string(nv, ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH,589&vdev_enc_sysfs_path);590591/* Spares show more than once if they're in use, so skip if exists */592for (i = 0; i < vcdl->count; i++) {593if ((strcmp(vcdl->data[i].path, path) == 0) &&594(strcmp(vcdl->data[i].pool, zpool_get_name(zhp)) == 0)) {595/* vdev already exists, skip it */596return (0);597}598}599600/* Check for selected vdevs here, if any */601for (i = 0; i < vcdl->vdev_names_count; i++) {602vname = zpool_vdev_name(g_zfs, zhp, nv, vcdl->cb_name_flags);603if (strcmp(vcdl->vdev_names[i], vname) == 0) {604free(vname);605match = 1;606break; /* match */607}608free(vname);609}610611/* If we selected vdevs, and this isn't one of them, then bail out */612if (!match && vcdl->vdev_names_count)613return (0);614615/*616* Resize our array and add in the new element.617*/618if (!(vcdl->data = realloc(vcdl->data,619sizeof (*vcdl->data) * (vcdl->count + 1))))620return (ENOMEM); /* couldn't realloc */621622data = &vcdl->data[vcdl->count];623624data->pool = strdup(zpool_get_name(zhp));625data->path = strdup(path);626data->upath = zfs_get_underlying_path(path);627data->cmd = vcdl->cmd;628data->lines = data->cols = NULL;629data->lines_cnt = data->cols_cnt = 0;630if (vdev_enc_sysfs_path)631data->vdev_enc_sysfs_path = strdup(vdev_enc_sysfs_path);632else633data->vdev_enc_sysfs_path = NULL;634635vcdl->count++;636637return (0);638}639640/* Get the names and count of the vdevs */641static int642all_pools_for_each_vdev_gather_cb(zpool_handle_t *zhp, void *cb_vcdl)643{644return (for_each_vdev(zhp, for_each_vdev_run_cb, cb_vcdl));645}646647/*648* Now that vcdl is populated with our complete list of vdevs, spawn649* off the commands.650*/651static void652all_pools_for_each_vdev_run_vcdl(vdev_cmd_data_list_t *vcdl)653{654taskq_t *tq = taskq_create("vdev_run_cmd",6555 * sysconf(_SC_NPROCESSORS_ONLN), minclsyspri, 1, INT_MAX,656TASKQ_DYNAMIC);657if (tq == NULL)658return;659660/* Spawn off the command for each vdev */661for (int i = 0; i < vcdl->count; i++) {662(void) taskq_dispatch(tq, vdev_run_cmd_thread,663(void *) &vcdl->data[i], TQ_SLEEP);664}665666/* Wait for threads to finish */667taskq_wait(tq);668taskq_destroy(tq);669}670671/*672* Run command 'cmd' on all vdevs in all pools in argv. Saves the first line of673* output from the command in vcdk->data[].line for all vdevs. If you want674* to run the command on only certain vdevs, fill in g_zfs, vdev_names,675* vdev_names_count, and cb_name_flags. Otherwise leave them as zero.676*677* Returns a vdev_cmd_data_list_t that must be freed with678* free_vdev_cmd_data_list();679*/680vdev_cmd_data_list_t *681all_pools_for_each_vdev_run(int argc, char **argv, char *cmd,682libzfs_handle_t *g_zfs, char **vdev_names, int vdev_names_count,683int cb_name_flags)684{685vdev_cmd_data_list_t *vcdl;686vcdl = safe_malloc(sizeof (vdev_cmd_data_list_t));687vcdl->cmd = cmd;688689vcdl->vdev_names = vdev_names;690vcdl->vdev_names_count = vdev_names_count;691vcdl->cb_name_flags = cb_name_flags;692vcdl->g_zfs = g_zfs;693694/* Gather our list of all vdevs in all pools */695for_each_pool(argc, argv, B_TRUE, NULL, ZFS_TYPE_POOL,696B_FALSE, all_pools_for_each_vdev_gather_cb, vcdl);697698/* Run command on all vdevs in all pools */699all_pools_for_each_vdev_run_vcdl(vcdl);700701/*702* vcdl->data[] now contains all the column names and values for each703* vdev. We need to process that into a master list of unique column704* names, and figure out the width of each column.705*/706process_unique_cmd_columns(vcdl);707708return (vcdl);709}710711/*712* Free the vdev_cmd_data_list_t created by all_pools_for_each_vdev_run()713*/714void715free_vdev_cmd_data_list(vdev_cmd_data_list_t *vcdl)716{717free(vcdl->uniq_cols);718free(vcdl->uniq_cols_width);719720for (int i = 0; i < vcdl->count; i++) {721free(vcdl->data[i].path);722free(vcdl->data[i].pool);723free(vcdl->data[i].upath);724725for (int j = 0; j < vcdl->data[i].lines_cnt; j++)726free(vcdl->data[i].lines[j]);727728free(vcdl->data[i].lines);729730for (int j = 0; j < vcdl->data[i].cols_cnt; j++)731free(vcdl->data[i].cols[j]);732733free(vcdl->data[i].cols);734free(vcdl->data[i].vdev_enc_sysfs_path);735}736free(vcdl->data);737free(vcdl);738}739740741