Path: blob/main/sys/contrib/openzfs/cmd/zpool/zpool_iter.c
48288 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 <libuutil.h>33#include <stddef.h>34#include <stdio.h>35#include <stdlib.h>36#include <string.h>37#include <thread_pool.h>3839#include <libzfs.h>40#include <libzutil.h>41#include <sys/zfs_context.h>42#include <sys/wait.h>4344#include "zpool_util.h"4546/*47* Private interface for iterating over pools specified on the command line.48* Most consumers will call for_each_pool, but in order to support iostat, we49* allow fined grained control through the zpool_list_t interface.50*/5152typedef struct zpool_node {53zpool_handle_t *zn_handle;54uu_avl_node_t zn_avlnode;55hrtime_t zn_last_refresh;56} zpool_node_t;5758struct zpool_list {59boolean_t zl_findall;60boolean_t zl_literal;61uu_avl_t *zl_avl;62uu_avl_pool_t *zl_pool;63zprop_list_t **zl_proplist;64zfs_type_t zl_type;65hrtime_t zl_last_refresh;66};6768static int69zpool_compare(const void *larg, const void *rarg, void *unused)70{71(void) unused;72zpool_handle_t *l = ((zpool_node_t *)larg)->zn_handle;73zpool_handle_t *r = ((zpool_node_t *)rarg)->zn_handle;74const char *lname = zpool_get_name(l);75const char *rname = zpool_get_name(r);7677return (strcmp(lname, rname));78}7980/*81* Callback function for pool_list_get(). Adds the given pool to the AVL tree82* of known pools.83*/84static int85add_pool(zpool_handle_t *zhp, zpool_list_t *zlp)86{87zpool_node_t *node, *new = safe_malloc(sizeof (zpool_node_t));88uu_avl_index_t idx;8990new->zn_handle = zhp;91uu_avl_node_init(new, &new->zn_avlnode, zlp->zl_pool);9293node = uu_avl_find(zlp->zl_avl, new, NULL, &idx);94if (node == NULL) {95if (zlp->zl_proplist &&96zpool_expand_proplist(zhp, zlp->zl_proplist,97zlp->zl_type, zlp->zl_literal) != 0) {98zpool_close(zhp);99free(new);100return (-1);101}102new->zn_last_refresh = zlp->zl_last_refresh;103uu_avl_insert(zlp->zl_avl, new, idx);104} else {105zpool_refresh_stats_from_handle(node->zn_handle, zhp);106node->zn_last_refresh = zlp->zl_last_refresh;107zpool_close(zhp);108free(new);109return (-1);110}111112return (0);113}114115/*116* add_pool(), but always returns 0. This allows zpool_iter() to continue117* even if a pool exists in the tree, or we fail to get the properties for118* a new one.119*/120static int121add_pool_cb(zpool_handle_t *zhp, void *data)122{123(void) add_pool(zhp, data);124return (0);125}126127/*128* Create a list of pools based on the given arguments. If we're given no129* arguments, then iterate over all pools in the system and add them to the AVL130* tree. Otherwise, add only those pool explicitly specified on the command131* line.132*/133zpool_list_t *134pool_list_get(int argc, char **argv, zprop_list_t **proplist, zfs_type_t type,135boolean_t literal, int *err)136{137zpool_list_t *zlp;138139zlp = safe_malloc(sizeof (zpool_list_t));140141zlp->zl_pool = uu_avl_pool_create("zfs_pool", sizeof (zpool_node_t),142offsetof(zpool_node_t, zn_avlnode), zpool_compare, UU_DEFAULT);143144if (zlp->zl_pool == NULL)145zpool_no_memory();146147if ((zlp->zl_avl = uu_avl_create(zlp->zl_pool, NULL,148UU_DEFAULT)) == NULL)149zpool_no_memory();150151zlp->zl_proplist = proplist;152zlp->zl_type = type;153154zlp->zl_literal = literal;155zlp->zl_last_refresh = gethrtime();156157if (argc == 0) {158(void) zpool_iter(g_zfs, add_pool_cb, zlp);159zlp->zl_findall = B_TRUE;160} else {161int i;162163for (i = 0; i < argc; i++) {164zpool_handle_t *zhp;165166if ((zhp = zpool_open_canfail(g_zfs, argv[i])) !=167NULL) {168if (add_pool(zhp, zlp) != 0)169*err = B_TRUE;170} else {171*err = B_TRUE;172}173}174}175176return (zlp);177}178179/*180* Refresh the state of all pools on the list. Additionally, if no options were181* given on the command line, add any new pools and remove any that are no182* longer available.183*/184int185pool_list_refresh(zpool_list_t *zlp)186{187zlp->zl_last_refresh = gethrtime();188189if (!zlp->zl_findall) {190/*191* This list is a fixed list of pools, so we must not add192* or remove any. Just walk over them and refresh their193* state.194*/195int navail = 0;196for (zpool_node_t *node = uu_avl_first(zlp->zl_avl);197node != NULL; node = uu_avl_next(zlp->zl_avl, node)) {198boolean_t missing;199zpool_refresh_stats(node->zn_handle, &missing);200navail += !missing;201node->zn_last_refresh = zlp->zl_last_refresh;202}203return (navail);204}205206/* Search for any new pools and add them to the list. */207(void) zpool_iter(g_zfs, add_pool_cb, zlp);208209/* Walk the list of existing pools, and update or remove them. */210zpool_node_t *node, *next;211for (node = uu_avl_first(zlp->zl_avl); node != NULL; node = next) {212next = uu_avl_next(zlp->zl_avl, node);213214/*215* Skip any that were refreshed and are online; they were added216* by zpool_iter() and are already up to date.217*/218if (node->zn_last_refresh == zlp->zl_last_refresh &&219zpool_get_state(node->zn_handle) != POOL_STATE_UNAVAIL)220continue;221222/* Refresh and remove if necessary. */223boolean_t missing;224zpool_refresh_stats(node->zn_handle, &missing);225if (missing) {226uu_avl_remove(zlp->zl_avl, node);227zpool_close(node->zn_handle);228free(node);229} else {230node->zn_last_refresh = zlp->zl_last_refresh;231}232}233234return (uu_avl_numnodes(zlp->zl_avl));235}236237/*238* Iterate over all pools in the list, executing the callback for each239*/240int241pool_list_iter(zpool_list_t *zlp, int unavail, zpool_iter_f func,242void *data)243{244zpool_node_t *node, *next_node;245int ret = 0;246247for (node = uu_avl_first(zlp->zl_avl); node != NULL; node = next_node) {248next_node = uu_avl_next(zlp->zl_avl, node);249if (zpool_get_state(node->zn_handle) != POOL_STATE_UNAVAIL ||250unavail)251ret |= func(node->zn_handle, data);252}253254return (ret);255}256257/*258* Free all the handles associated with this list.259*/260void261pool_list_free(zpool_list_t *zlp)262{263uu_avl_walk_t *walk;264zpool_node_t *node;265266if ((walk = uu_avl_walk_start(zlp->zl_avl, UU_WALK_ROBUST)) == NULL) {267(void) fprintf(stderr,268gettext("internal error: out of memory"));269exit(1);270}271272while ((node = uu_avl_walk_next(walk)) != NULL) {273uu_avl_remove(zlp->zl_avl, node);274zpool_close(node->zn_handle);275free(node);276}277278uu_avl_walk_end(walk);279uu_avl_destroy(zlp->zl_avl);280uu_avl_pool_destroy(zlp->zl_pool);281282free(zlp);283}284285/*286* Returns the number of elements in the pool list.287*/288int289pool_list_count(zpool_list_t *zlp)290{291return (uu_avl_numnodes(zlp->zl_avl));292}293294/*295* High level function which iterates over all pools given on the command line,296* using the pool_list_* interfaces.297*/298int299for_each_pool(int argc, char **argv, boolean_t unavail,300zprop_list_t **proplist, zfs_type_t type, boolean_t literal,301zpool_iter_f func, void *data)302{303zpool_list_t *list;304int ret = 0;305306if ((list = pool_list_get(argc, argv, proplist, type, literal,307&ret)) == NULL)308return (1);309310if (pool_list_iter(list, unavail, func, data) != 0)311ret = 1;312313pool_list_free(list);314315return (ret);316}317318/*319* This is the equivalent of for_each_pool() for vdevs. It iterates thorough320* all vdevs in the pool, ignoring root vdevs and holes, calling func() on321* each one.322*323* @zhp: Zpool handle324* @func: Function to call on each vdev325* @data: Custom data to pass to the function326*/327int328for_each_vdev(zpool_handle_t *zhp, pool_vdev_iter_f func, void *data)329{330nvlist_t *config, *nvroot = NULL;331332if ((config = zpool_get_config(zhp, NULL)) != NULL) {333verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,334&nvroot) == 0);335}336return (for_each_vdev_cb((void *) zhp, nvroot, func, data));337}338339/*340* Process the vcdl->vdev_cmd_data[] array to figure out all the unique column341* names and their widths. When this function is done, vcdl->uniq_cols,342* vcdl->uniq_cols_cnt, and vcdl->uniq_cols_width will be filled in.343*/344static void345process_unique_cmd_columns(vdev_cmd_data_list_t *vcdl)346{347char **uniq_cols = NULL, **tmp = NULL;348int *uniq_cols_width;349vdev_cmd_data_t *data;350int cnt = 0;351int k;352353/* For each vdev */354for (int i = 0; i < vcdl->count; i++) {355data = &vcdl->data[i];356/* For each column the vdev reported */357for (int j = 0; j < data->cols_cnt; j++) {358/* Is this column in our list of unique column names? */359for (k = 0; k < cnt; k++) {360if (strcmp(data->cols[j], uniq_cols[k]) == 0)361break; /* yes it is */362}363if (k == cnt) {364/* No entry for column, add to list */365tmp = realloc(uniq_cols, sizeof (*uniq_cols) *366(cnt + 1));367if (tmp == NULL)368break; /* Nothing we can do... */369uniq_cols = tmp;370uniq_cols[cnt] = data->cols[j];371cnt++;372}373}374}375376/*377* We now have a list of all the unique column names. Figure out the378* max width of each column by looking at the column name and all its379* values.380*/381uniq_cols_width = safe_malloc(sizeof (*uniq_cols_width) * cnt);382for (int i = 0; i < cnt; i++) {383/* Start off with the column title's width */384uniq_cols_width[i] = strlen(uniq_cols[i]);385/* For each vdev */386for (int j = 0; j < vcdl->count; j++) {387/* For each of the vdev's values in a column */388data = &vcdl->data[j];389for (k = 0; k < data->cols_cnt; k++) {390/* Does this vdev have a value for this col? */391if (strcmp(data->cols[k], uniq_cols[i]) == 0) {392/* Is the value width larger? */393uniq_cols_width[i] =394MAX(uniq_cols_width[i],395strlen(data->lines[k]));396}397}398}399}400401vcdl->uniq_cols = uniq_cols;402vcdl->uniq_cols_cnt = cnt;403vcdl->uniq_cols_width = uniq_cols_width;404}405406407/*408* Process a line of command output409*410* When running 'zpool iostat|status -c' the lines of output can either be411* in the form of:412*413* column_name=value414*415* Or just:416*417* value418*419* Process the column_name (if any) and value.420*421* Returns 0 if line was processed, and there are more lines can still be422* processed.423*424* Returns 1 if this was the last line to process, or error.425*/426static int427vdev_process_cmd_output(vdev_cmd_data_t *data, char *line)428{429char *col;430char *val;431char *equals;432char **tmp;433434if (line == NULL)435return (1);436437equals = strchr(line, '=');438if (equals != NULL) {439/*440* We have a 'column=value' type line. Split it into the441* column and value strings by turning the '=' into a '\0'.442*/443*equals = '\0';444col = line;445val = equals + 1;446} else {447col = NULL;448val = line;449}450451/* Do we already have a column by this name? If so, skip it. */452if (col != NULL) {453for (int i = 0; i < data->cols_cnt; i++) {454if (strcmp(col, data->cols[i]) == 0)455return (0); /* Duplicate, skip */456}457}458459if (val != NULL) {460tmp = realloc(data->lines,461(data->lines_cnt + 1) * sizeof (*data->lines));462if (tmp == NULL)463return (1);464465data->lines = tmp;466data->lines[data->lines_cnt] = strdup(val);467data->lines_cnt++;468}469470if (col != NULL) {471tmp = realloc(data->cols,472(data->cols_cnt + 1) * sizeof (*data->cols));473if (tmp == NULL)474return (1);475476data->cols = tmp;477data->cols[data->cols_cnt] = strdup(col);478data->cols_cnt++;479}480481if (val != NULL && col == NULL)482return (1);483484return (0);485}486487/*488* Run the cmd and store results in *data.489*/490static void491vdev_run_cmd(vdev_cmd_data_t *data, char *cmd)492{493int rc;494char *argv[2] = {cmd};495char **env;496char **lines = NULL;497int lines_cnt = 0;498int i;499500env = zpool_vdev_script_alloc_env(data->pool, data->path, data->upath,501data->vdev_enc_sysfs_path, NULL, NULL);502if (env == NULL)503goto out;504505/* Run the command */506rc = libzfs_run_process_get_stdout_nopath(cmd, argv, env, &lines,507&lines_cnt);508509zpool_vdev_script_free_env(env);510511if (rc != 0)512goto out;513514/* Process the output we got */515for (i = 0; i < lines_cnt; i++)516if (vdev_process_cmd_output(data, lines[i]) != 0)517break;518519out:520if (lines != NULL)521libzfs_free_str_array(lines, lines_cnt);522}523524/*525* Generate the search path for zpool iostat/status -c scripts.526* The string returned must be freed.527*/528char *529zpool_get_cmd_search_path(void)530{531const char *env;532char *sp = NULL;533534env = getenv("ZPOOL_SCRIPTS_PATH");535if (env != NULL)536return (strdup(env));537538env = getenv("HOME");539if (env != NULL) {540if (asprintf(&sp, "%s/.zpool.d:%s",541env, ZPOOL_SCRIPTS_DIR) != -1) {542return (sp);543}544}545546if (asprintf(&sp, "%s", ZPOOL_SCRIPTS_DIR) != -1)547return (sp);548549return (NULL);550}551552/* Thread function run for each vdev */553static void554vdev_run_cmd_thread(void *cb_cmd_data)555{556vdev_cmd_data_t *data = cb_cmd_data;557char *cmd = NULL, *cmddup, *cmdrest;558559cmddup = strdup(data->cmd);560if (cmddup == NULL)561return;562563cmdrest = cmddup;564while ((cmd = strtok_r(cmdrest, ",", &cmdrest))) {565char *dir = NULL, *sp, *sprest;566char fullpath[MAXPATHLEN];567568if (strchr(cmd, '/') != NULL)569continue;570571sp = zpool_get_cmd_search_path();572if (sp == NULL)573continue;574575sprest = sp;576while ((dir = strtok_r(sprest, ":", &sprest))) {577if (snprintf(fullpath, sizeof (fullpath),578"%s/%s", dir, cmd) == -1)579continue;580581if (access(fullpath, X_OK) == 0) {582vdev_run_cmd(data, fullpath);583break;584}585}586free(sp);587}588free(cmddup);589}590591/* For each vdev in the pool run a command */592static int593for_each_vdev_run_cb(void *zhp_data, nvlist_t *nv, void *cb_vcdl)594{595vdev_cmd_data_list_t *vcdl = cb_vcdl;596vdev_cmd_data_t *data;597const char *path = NULL;598char *vname = NULL;599const char *vdev_enc_sysfs_path = NULL;600int i, match = 0;601zpool_handle_t *zhp = zhp_data;602603if (nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) != 0)604return (1);605606/* Make sure we're getting the updated enclosure sysfs path */607update_vdev_config_dev_sysfs_path(nv, path,608ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH);609610nvlist_lookup_string(nv, ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH,611&vdev_enc_sysfs_path);612613/* Spares show more than once if they're in use, so skip if exists */614for (i = 0; i < vcdl->count; i++) {615if ((strcmp(vcdl->data[i].path, path) == 0) &&616(strcmp(vcdl->data[i].pool, zpool_get_name(zhp)) == 0)) {617/* vdev already exists, skip it */618return (0);619}620}621622/* Check for selected vdevs here, if any */623for (i = 0; i < vcdl->vdev_names_count; i++) {624vname = zpool_vdev_name(g_zfs, zhp, nv, vcdl->cb_name_flags);625if (strcmp(vcdl->vdev_names[i], vname) == 0) {626free(vname);627match = 1;628break; /* match */629}630free(vname);631}632633/* If we selected vdevs, and this isn't one of them, then bail out */634if (!match && vcdl->vdev_names_count)635return (0);636637/*638* Resize our array and add in the new element.639*/640if (!(vcdl->data = realloc(vcdl->data,641sizeof (*vcdl->data) * (vcdl->count + 1))))642return (ENOMEM); /* couldn't realloc */643644data = &vcdl->data[vcdl->count];645646data->pool = strdup(zpool_get_name(zhp));647data->path = strdup(path);648data->upath = zfs_get_underlying_path(path);649data->cmd = vcdl->cmd;650data->lines = data->cols = NULL;651data->lines_cnt = data->cols_cnt = 0;652if (vdev_enc_sysfs_path)653data->vdev_enc_sysfs_path = strdup(vdev_enc_sysfs_path);654else655data->vdev_enc_sysfs_path = NULL;656657vcdl->count++;658659return (0);660}661662/* Get the names and count of the vdevs */663static int664all_pools_for_each_vdev_gather_cb(zpool_handle_t *zhp, void *cb_vcdl)665{666return (for_each_vdev(zhp, for_each_vdev_run_cb, cb_vcdl));667}668669/*670* Now that vcdl is populated with our complete list of vdevs, spawn671* off the commands.672*/673static void674all_pools_for_each_vdev_run_vcdl(vdev_cmd_data_list_t *vcdl)675{676tpool_t *t;677678t = tpool_create(1, 5 * sysconf(_SC_NPROCESSORS_ONLN), 0, NULL);679if (t == NULL)680return;681682/* Spawn off the command for each vdev */683for (int i = 0; i < vcdl->count; i++) {684(void) tpool_dispatch(t, vdev_run_cmd_thread,685(void *) &vcdl->data[i]);686}687688/* Wait for threads to finish */689tpool_wait(t);690tpool_destroy(t);691}692693/*694* Run command 'cmd' on all vdevs in all pools in argv. Saves the first line of695* output from the command in vcdk->data[].line for all vdevs. If you want696* to run the command on only certain vdevs, fill in g_zfs, vdev_names,697* vdev_names_count, and cb_name_flags. Otherwise leave them as zero.698*699* Returns a vdev_cmd_data_list_t that must be freed with700* free_vdev_cmd_data_list();701*/702vdev_cmd_data_list_t *703all_pools_for_each_vdev_run(int argc, char **argv, char *cmd,704libzfs_handle_t *g_zfs, char **vdev_names, int vdev_names_count,705int cb_name_flags)706{707vdev_cmd_data_list_t *vcdl;708vcdl = safe_malloc(sizeof (vdev_cmd_data_list_t));709vcdl->cmd = cmd;710711vcdl->vdev_names = vdev_names;712vcdl->vdev_names_count = vdev_names_count;713vcdl->cb_name_flags = cb_name_flags;714vcdl->g_zfs = g_zfs;715716/* Gather our list of all vdevs in all pools */717for_each_pool(argc, argv, B_TRUE, NULL, ZFS_TYPE_POOL,718B_FALSE, all_pools_for_each_vdev_gather_cb, vcdl);719720/* Run command on all vdevs in all pools */721all_pools_for_each_vdev_run_vcdl(vcdl);722723/*724* vcdl->data[] now contains all the column names and values for each725* vdev. We need to process that into a master list of unique column726* names, and figure out the width of each column.727*/728process_unique_cmd_columns(vcdl);729730return (vcdl);731}732733/*734* Free the vdev_cmd_data_list_t created by all_pools_for_each_vdev_run()735*/736void737free_vdev_cmd_data_list(vdev_cmd_data_list_t *vcdl)738{739free(vcdl->uniq_cols);740free(vcdl->uniq_cols_width);741742for (int i = 0; i < vcdl->count; i++) {743free(vcdl->data[i].path);744free(vcdl->data[i].pool);745free(vcdl->data[i].upath);746747for (int j = 0; j < vcdl->data[i].lines_cnt; j++)748free(vcdl->data[i].lines[j]);749750free(vcdl->data[i].lines);751752for (int j = 0; j < vcdl->data[i].cols_cnt; j++)753free(vcdl->data[i].cols[j]);754755free(vcdl->data[i].cols);756free(vcdl->data[i].vdev_enc_sysfs_path);757}758free(vcdl->data);759free(vcdl);760}761762763