1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3 * This file and its contents are supplied under the terms of the
4 * Common Development and Distribution License ("CDDL"), version 1.0.
5 * You may only use this file in accordance with the terms of version
6 * 1.0 of the CDDL.
7 *
8 * A full copy of the text of the CDDL should have accompanied this
9 * source. A copy of the CDDL is also available via the Internet at
10 * https://opensource.org/license/CDDL-1.0.
11 */
12 /*
13 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
14 * Use is subject to license terms.
15 */
16
17 /*
18 * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>.
19 * Copyright (c) 2025, Klara, Inc.
20 */
21
22 #include <libintl.h>
23 #include <stddef.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include <libzfs.h>
29 #include <libzutil.h>
30 #include <sys/zfs_context.h>
31 #include <sys/wait.h>
32
33 #include "zpool_util.h"
34
35 /*
36 * Private interface for iterating over pools specified on the command line.
37 * Most consumers will call for_each_pool, but in order to support iostat, we
38 * allow fined grained control through the zpool_list_t interface.
39 */
40
41 typedef struct zpool_node {
42 zpool_handle_t *zn_handle;
43 avl_node_t zn_avlnode;
44 hrtime_t zn_last_refresh;
45 } zpool_node_t;
46
47 struct zpool_list {
48 boolean_t zl_findall;
49 boolean_t zl_literal;
50 avl_tree_t zl_avl;
51 zprop_list_t **zl_proplist;
52 zfs_type_t zl_type;
53 hrtime_t zl_last_refresh;
54 };
55
56 static int
zpool_compare(const void * larg,const void * rarg)57 zpool_compare(const void *larg, const void *rarg)
58 {
59 zpool_handle_t *l = ((zpool_node_t *)larg)->zn_handle;
60 zpool_handle_t *r = ((zpool_node_t *)rarg)->zn_handle;
61 const char *lname = zpool_get_name(l);
62 const char *rname = zpool_get_name(r);
63
64 return (TREE_ISIGN(strcmp(lname, rname)));
65 }
66
67 /*
68 * Callback function for pool_list_get(). Adds the given pool to the AVL tree
69 * of known pools.
70 */
71 static int
add_pool(zpool_handle_t * zhp,zpool_list_t * zlp)72 add_pool(zpool_handle_t *zhp, zpool_list_t *zlp)
73 {
74 zpool_node_t *node, *new = safe_malloc(sizeof (zpool_node_t));
75 avl_index_t idx;
76
77 new->zn_handle = zhp;
78
79 node = avl_find(&zlp->zl_avl, new, &idx);
80 if (node == NULL) {
81 if (zlp->zl_proplist &&
82 zpool_expand_proplist(zhp, zlp->zl_proplist,
83 zlp->zl_type, zlp->zl_literal) != 0) {
84 zpool_close(zhp);
85 free(new);
86 return (-1);
87 }
88 new->zn_last_refresh = zlp->zl_last_refresh;
89 avl_insert(&zlp->zl_avl, new, idx);
90 } else {
91 zpool_refresh_stats_from_handle(node->zn_handle, zhp);
92 node->zn_last_refresh = zlp->zl_last_refresh;
93 zpool_close(zhp);
94 free(new);
95 return (-1);
96 }
97
98 return (0);
99 }
100
101 /*
102 * add_pool(), but always returns 0. This allows zpool_iter() to continue
103 * even if a pool exists in the tree, or we fail to get the properties for
104 * a new one.
105 */
106 static int
add_pool_cb(zpool_handle_t * zhp,void * data)107 add_pool_cb(zpool_handle_t *zhp, void *data)
108 {
109 (void) add_pool(zhp, data);
110 return (0);
111 }
112
113 /*
114 * Create a list of pools based on the given arguments. If we're given no
115 * arguments, then iterate over all pools in the system and add them to the AVL
116 * tree. Otherwise, add only those pool explicitly specified on the command
117 * line.
118 */
119 zpool_list_t *
pool_list_get(int argc,char ** argv,zprop_list_t ** proplist,zfs_type_t type,boolean_t literal,int * err)120 pool_list_get(int argc, char **argv, zprop_list_t **proplist, zfs_type_t type,
121 boolean_t literal, int *err)
122 {
123 zpool_list_t *zlp;
124
125 zlp = safe_malloc(sizeof (zpool_list_t));
126
127 avl_create(&zlp->zl_avl, zpool_compare,
128 sizeof (zpool_node_t), offsetof(zpool_node_t, zn_avlnode));
129
130 zlp->zl_proplist = proplist;
131 zlp->zl_type = type;
132
133 zlp->zl_literal = literal;
134 zlp->zl_last_refresh = gethrtime();
135
136 if (argc == 0) {
137 (void) zpool_iter(g_zfs, add_pool_cb, zlp);
138 zlp->zl_findall = B_TRUE;
139 } else {
140 int i;
141
142 for (i = 0; i < argc; i++) {
143 zpool_handle_t *zhp;
144
145 if ((zhp = zpool_open_canfail(g_zfs, argv[i])) !=
146 NULL) {
147 if (add_pool(zhp, zlp) != 0)
148 *err = B_TRUE;
149 } else {
150 *err = B_TRUE;
151 }
152 }
153 }
154
155 return (zlp);
156 }
157
158 /*
159 * Refresh the state of all pools on the list. Additionally, if no options were
160 * given on the command line, add any new pools and remove any that are no
161 * longer available.
162 */
163 int
pool_list_refresh(zpool_list_t * zlp)164 pool_list_refresh(zpool_list_t *zlp)
165 {
166 zlp->zl_last_refresh = gethrtime();
167
168 if (!zlp->zl_findall) {
169 /*
170 * This list is a fixed list of pools, so we must not add
171 * or remove any. Just walk over them and refresh their
172 * state.
173 */
174 int navail = 0;
175 for (zpool_node_t *node = avl_first(&zlp->zl_avl);
176 node != NULL; node = AVL_NEXT(&zlp->zl_avl, node)) {
177 boolean_t missing;
178 zpool_refresh_stats(node->zn_handle, &missing);
179 navail += !missing;
180 node->zn_last_refresh = zlp->zl_last_refresh;
181 }
182 return (navail);
183 }
184
185 /* Search for any new pools and add them to the list. */
186 (void) zpool_iter(g_zfs, add_pool_cb, zlp);
187
188 /* Walk the list of existing pools, and update or remove them. */
189 zpool_node_t *node, *next;
190 for (node = avl_first(&zlp->zl_avl); node != NULL; node = next) {
191 next = AVL_NEXT(&zlp->zl_avl, node);
192
193 /*
194 * Skip any that were refreshed and are online; they were added
195 * by zpool_iter() and are already up to date.
196 */
197 if (node->zn_last_refresh == zlp->zl_last_refresh &&
198 zpool_get_state(node->zn_handle) != POOL_STATE_UNAVAIL)
199 continue;
200
201 /* Refresh and remove if necessary. */
202 boolean_t missing;
203 zpool_refresh_stats(node->zn_handle, &missing);
204 if (missing) {
205 avl_remove(&zlp->zl_avl, node);
206 zpool_close(node->zn_handle);
207 free(node);
208 } else {
209 node->zn_last_refresh = zlp->zl_last_refresh;
210 }
211 }
212
213 return (avl_numnodes(&zlp->zl_avl));
214 }
215
216 /*
217 * Iterate over all pools in the list, executing the callback for each
218 */
219 int
pool_list_iter(zpool_list_t * zlp,int unavail,zpool_iter_f func,void * data)220 pool_list_iter(zpool_list_t *zlp, int unavail, zpool_iter_f func,
221 void *data)
222 {
223 zpool_node_t *node, *next_node;
224 int ret = 0;
225
226 for (node = avl_first(&zlp->zl_avl); node != NULL; node = next_node) {
227 next_node = AVL_NEXT(&zlp->zl_avl, node);
228 if (zpool_get_state(node->zn_handle) != POOL_STATE_UNAVAIL ||
229 unavail)
230 ret |= func(node->zn_handle, data);
231 }
232
233 return (ret);
234 }
235
236 /*
237 * Free all the handles associated with this list.
238 */
239 void
pool_list_free(zpool_list_t * zlp)240 pool_list_free(zpool_list_t *zlp)
241 {
242 zpool_node_t *node;
243 void *cookie = NULL;
244
245 while ((node = avl_destroy_nodes(&zlp->zl_avl, &cookie)) != NULL) {
246 zpool_close(node->zn_handle);
247 free(node);
248 }
249
250 avl_destroy(&zlp->zl_avl);
251 free(zlp);
252 }
253
254 /*
255 * Returns the number of elements in the pool list.
256 */
257 int
pool_list_count(zpool_list_t * zlp)258 pool_list_count(zpool_list_t *zlp)
259 {
260 return (avl_numnodes(&zlp->zl_avl));
261 }
262
263 /*
264 * High level function which iterates over all pools given on the command line,
265 * using the pool_list_* interfaces.
266 */
267 int
for_each_pool(int argc,char ** argv,boolean_t unavail,zprop_list_t ** proplist,zfs_type_t type,boolean_t literal,zpool_iter_f func,void * data)268 for_each_pool(int argc, char **argv, boolean_t unavail,
269 zprop_list_t **proplist, zfs_type_t type, boolean_t literal,
270 zpool_iter_f func, void *data)
271 {
272 zpool_list_t *list;
273 int ret = 0;
274
275 if ((list = pool_list_get(argc, argv, proplist, type, literal,
276 &ret)) == NULL)
277 return (1);
278
279 if (pool_list_iter(list, unavail, func, data) != 0)
280 ret = 1;
281
282 pool_list_free(list);
283
284 return (ret);
285 }
286
287 /*
288 * This is the equivalent of for_each_pool() for vdevs. It iterates thorough
289 * all vdevs in the pool, ignoring root vdevs and holes, calling func() on
290 * each one.
291 *
292 * @zhp: Zpool handle
293 * @func: Function to call on each vdev
294 * @data: Custom data to pass to the function
295 */
296 int
for_each_vdev(zpool_handle_t * zhp,pool_vdev_iter_f func,void * data)297 for_each_vdev(zpool_handle_t *zhp, pool_vdev_iter_f func, void *data)
298 {
299 nvlist_t *config, *nvroot = NULL;
300
301 if ((config = zpool_get_config(zhp, NULL)) != NULL) {
302 verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
303 &nvroot) == 0);
304 }
305 return (for_each_vdev_cb((void *) zhp, nvroot, func, data));
306 }
307
308 /*
309 * Process the vcdl->vdev_cmd_data[] array to figure out all the unique column
310 * names and their widths. When this function is done, vcdl->uniq_cols,
311 * vcdl->uniq_cols_cnt, and vcdl->uniq_cols_width will be filled in.
312 */
313 static void
process_unique_cmd_columns(vdev_cmd_data_list_t * vcdl)314 process_unique_cmd_columns(vdev_cmd_data_list_t *vcdl)
315 {
316 char **uniq_cols = NULL, **tmp = NULL;
317 int *uniq_cols_width;
318 vdev_cmd_data_t *data;
319 int cnt = 0;
320 int k;
321
322 /* For each vdev */
323 for (int i = 0; i < vcdl->count; i++) {
324 data = &vcdl->data[i];
325 /* For each column the vdev reported */
326 for (int j = 0; j < data->cols_cnt; j++) {
327 /* Is this column in our list of unique column names? */
328 for (k = 0; k < cnt; k++) {
329 if (strcmp(data->cols[j], uniq_cols[k]) == 0)
330 break; /* yes it is */
331 }
332 if (k == cnt) {
333 /* No entry for column, add to list */
334 tmp = realloc(uniq_cols, sizeof (*uniq_cols) *
335 (cnt + 1));
336 if (tmp == NULL)
337 break; /* Nothing we can do... */
338 uniq_cols = tmp;
339 uniq_cols[cnt] = data->cols[j];
340 cnt++;
341 }
342 }
343 }
344
345 /*
346 * We now have a list of all the unique column names. Figure out the
347 * max width of each column by looking at the column name and all its
348 * values.
349 */
350 uniq_cols_width = safe_malloc(sizeof (*uniq_cols_width) * cnt);
351 for (int i = 0; i < cnt; i++) {
352 /* Start off with the column title's width */
353 uniq_cols_width[i] = strlen(uniq_cols[i]);
354 /* For each vdev */
355 for (int j = 0; j < vcdl->count; j++) {
356 /* For each of the vdev's values in a column */
357 data = &vcdl->data[j];
358 for (k = 0; k < data->cols_cnt; k++) {
359 /* Does this vdev have a value for this col? */
360 if (strcmp(data->cols[k], uniq_cols[i]) == 0) {
361 /* Is the value width larger? */
362 uniq_cols_width[i] =
363 MAX(uniq_cols_width[i],
364 strlen(data->lines[k]));
365 }
366 }
367 }
368 }
369
370 vcdl->uniq_cols = uniq_cols;
371 vcdl->uniq_cols_cnt = cnt;
372 vcdl->uniq_cols_width = uniq_cols_width;
373 }
374
375
376 /*
377 * Process a line of command output
378 *
379 * When running 'zpool iostat|status -c' the lines of output can either be
380 * in the form of:
381 *
382 * column_name=value
383 *
384 * Or just:
385 *
386 * value
387 *
388 * Process the column_name (if any) and value.
389 *
390 * Returns 0 if line was processed, and there are more lines can still be
391 * processed.
392 *
393 * Returns 1 if this was the last line to process, or error.
394 */
395 static int
vdev_process_cmd_output(vdev_cmd_data_t * data,char * line)396 vdev_process_cmd_output(vdev_cmd_data_t *data, char *line)
397 {
398 char *col;
399 char *val;
400 char *equals;
401 char **tmp;
402
403 if (line == NULL)
404 return (1);
405
406 equals = strchr(line, '=');
407 if (equals != NULL) {
408 /*
409 * We have a 'column=value' type line. Split it into the
410 * column and value strings by turning the '=' into a '\0'.
411 */
412 *equals = '\0';
413 col = line;
414 val = equals + 1;
415 } else {
416 col = NULL;
417 val = line;
418 }
419
420 /* Do we already have a column by this name? If so, skip it. */
421 if (col != NULL) {
422 for (int i = 0; i < data->cols_cnt; i++) {
423 if (strcmp(col, data->cols[i]) == 0)
424 return (0); /* Duplicate, skip */
425 }
426 }
427
428 if (val != NULL) {
429 tmp = realloc(data->lines,
430 (data->lines_cnt + 1) * sizeof (*data->lines));
431 if (tmp == NULL)
432 return (1);
433
434 data->lines = tmp;
435 data->lines[data->lines_cnt] = strdup(val);
436 data->lines_cnt++;
437 }
438
439 if (col != NULL) {
440 tmp = realloc(data->cols,
441 (data->cols_cnt + 1) * sizeof (*data->cols));
442 if (tmp == NULL)
443 return (1);
444
445 data->cols = tmp;
446 data->cols[data->cols_cnt] = strdup(col);
447 data->cols_cnt++;
448 }
449
450 if (val != NULL && col == NULL)
451 return (1);
452
453 return (0);
454 }
455
456 /*
457 * Run the cmd and store results in *data.
458 */
459 static void
vdev_run_cmd(vdev_cmd_data_t * data,char * cmd)460 vdev_run_cmd(vdev_cmd_data_t *data, char *cmd)
461 {
462 int rc;
463 char *argv[2] = {cmd};
464 char **env;
465 char **lines = NULL;
466 int lines_cnt = 0;
467 int i;
468
469 env = zpool_vdev_script_alloc_env(data->pool, data->path, data->upath,
470 data->vdev_enc_sysfs_path, NULL, NULL);
471 if (env == NULL)
472 goto out;
473
474 /* Run the command */
475 rc = libzfs_run_process_get_stdout_nopath(cmd, argv, env, &lines,
476 &lines_cnt);
477
478 zpool_vdev_script_free_env(env);
479
480 if (rc != 0)
481 goto out;
482
483 /* Process the output we got */
484 for (i = 0; i < lines_cnt; i++)
485 if (vdev_process_cmd_output(data, lines[i]) != 0)
486 break;
487
488 out:
489 if (lines != NULL)
490 libzfs_free_str_array(lines, lines_cnt);
491 }
492
493 /*
494 * Generate the search path for zpool iostat/status -c scripts.
495 * The string returned must be freed.
496 */
497 char *
zpool_get_cmd_search_path(void)498 zpool_get_cmd_search_path(void)
499 {
500 const char *env;
501 char *sp = NULL;
502
503 env = getenv("ZPOOL_SCRIPTS_PATH");
504 if (env != NULL)
505 return (strdup(env));
506
507 env = getenv("HOME");
508 if (env != NULL) {
509 if (asprintf(&sp, "%s/.zpool.d:%s",
510 env, ZPOOL_SCRIPTS_DIR) != -1) {
511 return (sp);
512 }
513 }
514
515 if (asprintf(&sp, "%s", ZPOOL_SCRIPTS_DIR) != -1)
516 return (sp);
517
518 return (NULL);
519 }
520
521 /* Thread function run for each vdev */
522 static void
vdev_run_cmd_thread(void * cb_cmd_data)523 vdev_run_cmd_thread(void *cb_cmd_data)
524 {
525 vdev_cmd_data_t *data = cb_cmd_data;
526 char *cmd = NULL, *cmddup, *cmdrest;
527
528 cmddup = strdup(data->cmd);
529 if (cmddup == NULL)
530 return;
531
532 cmdrest = cmddup;
533 while ((cmd = strtok_r(cmdrest, ",", &cmdrest))) {
534 char *dir = NULL, *sp, *sprest;
535 char fullpath[MAXPATHLEN];
536
537 if (strchr(cmd, '/') != NULL)
538 continue;
539
540 sp = zpool_get_cmd_search_path();
541 if (sp == NULL)
542 continue;
543
544 sprest = sp;
545 while ((dir = strtok_r(sprest, ":", &sprest))) {
546 if (snprintf(fullpath, sizeof (fullpath),
547 "%s/%s", dir, cmd) == -1)
548 continue;
549
550 if (access(fullpath, X_OK) == 0) {
551 vdev_run_cmd(data, fullpath);
552 break;
553 }
554 }
555 free(sp);
556 }
557 free(cmddup);
558 }
559
560 /* For each vdev in the pool run a command */
561 static int
for_each_vdev_run_cb(void * zhp_data,nvlist_t * nv,void * cb_vcdl)562 for_each_vdev_run_cb(void *zhp_data, nvlist_t *nv, void *cb_vcdl)
563 {
564 vdev_cmd_data_list_t *vcdl = cb_vcdl;
565 vdev_cmd_data_t *data;
566 const char *path = NULL;
567 char *vname = NULL;
568 const char *vdev_enc_sysfs_path = NULL;
569 int i, match = 0;
570 zpool_handle_t *zhp = zhp_data;
571
572 if (nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) != 0)
573 return (1);
574
575 /* Make sure we're getting the updated enclosure sysfs path */
576 update_vdev_config_dev_sysfs_path(nv, path,
577 ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH);
578
579 nvlist_lookup_string(nv, ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH,
580 &vdev_enc_sysfs_path);
581
582 /* Spares show more than once if they're in use, so skip if exists */
583 for (i = 0; i < vcdl->count; i++) {
584 if ((strcmp(vcdl->data[i].path, path) == 0) &&
585 (strcmp(vcdl->data[i].pool, zpool_get_name(zhp)) == 0)) {
586 /* vdev already exists, skip it */
587 return (0);
588 }
589 }
590
591 /* Check for selected vdevs here, if any */
592 for (i = 0; i < vcdl->vdev_names_count; i++) {
593 vname = zpool_vdev_name(g_zfs, zhp, nv, vcdl->cb_name_flags);
594 if (strcmp(vcdl->vdev_names[i], vname) == 0) {
595 free(vname);
596 match = 1;
597 break; /* match */
598 }
599 free(vname);
600 }
601
602 /* If we selected vdevs, and this isn't one of them, then bail out */
603 if (!match && vcdl->vdev_names_count)
604 return (0);
605
606 /*
607 * Resize our array and add in the new element.
608 */
609 if (!(vcdl->data = realloc(vcdl->data,
610 sizeof (*vcdl->data) * (vcdl->count + 1))))
611 return (ENOMEM); /* couldn't realloc */
612
613 data = &vcdl->data[vcdl->count];
614
615 data->pool = strdup(zpool_get_name(zhp));
616 data->path = strdup(path);
617 data->upath = zfs_get_underlying_path(path);
618 data->cmd = vcdl->cmd;
619 data->lines = data->cols = NULL;
620 data->lines_cnt = data->cols_cnt = 0;
621 if (vdev_enc_sysfs_path)
622 data->vdev_enc_sysfs_path = strdup(vdev_enc_sysfs_path);
623 else
624 data->vdev_enc_sysfs_path = NULL;
625
626 vcdl->count++;
627
628 return (0);
629 }
630
631 /* Get the names and count of the vdevs */
632 static int
all_pools_for_each_vdev_gather_cb(zpool_handle_t * zhp,void * cb_vcdl)633 all_pools_for_each_vdev_gather_cb(zpool_handle_t *zhp, void *cb_vcdl)
634 {
635 return (for_each_vdev(zhp, for_each_vdev_run_cb, cb_vcdl));
636 }
637
638 /*
639 * Now that vcdl is populated with our complete list of vdevs, spawn
640 * off the commands.
641 */
642 static void
all_pools_for_each_vdev_run_vcdl(vdev_cmd_data_list_t * vcdl)643 all_pools_for_each_vdev_run_vcdl(vdev_cmd_data_list_t *vcdl)
644 {
645 taskq_t *tq = taskq_create("vdev_run_cmd",
646 5 * sysconf(_SC_NPROCESSORS_ONLN), minclsyspri, 1, INT_MAX,
647 TASKQ_DYNAMIC);
648 if (tq == NULL)
649 return;
650
651 /* Spawn off the command for each vdev */
652 for (int i = 0; i < vcdl->count; i++) {
653 (void) taskq_dispatch(tq, vdev_run_cmd_thread,
654 (void *) &vcdl->data[i], TQ_SLEEP);
655 }
656
657 /* Wait for threads to finish */
658 taskq_wait(tq);
659 taskq_destroy(tq);
660 }
661
662 /*
663 * Run command 'cmd' on all vdevs in all pools in argv. Saves the first line of
664 * output from the command in vcdk->data[].line for all vdevs. If you want
665 * to run the command on only certain vdevs, fill in g_zfs, vdev_names,
666 * vdev_names_count, and cb_name_flags. Otherwise leave them as zero.
667 *
668 * Returns a vdev_cmd_data_list_t that must be freed with
669 * free_vdev_cmd_data_list();
670 */
671 vdev_cmd_data_list_t *
all_pools_for_each_vdev_run(int argc,char ** argv,char * cmd,libzfs_handle_t * g_zfs,char ** vdev_names,int vdev_names_count,int cb_name_flags)672 all_pools_for_each_vdev_run(int argc, char **argv, char *cmd,
673 libzfs_handle_t *g_zfs, char **vdev_names, int vdev_names_count,
674 int cb_name_flags)
675 {
676 vdev_cmd_data_list_t *vcdl;
677 vcdl = safe_malloc(sizeof (vdev_cmd_data_list_t));
678 vcdl->cmd = cmd;
679
680 vcdl->vdev_names = vdev_names;
681 vcdl->vdev_names_count = vdev_names_count;
682 vcdl->cb_name_flags = cb_name_flags;
683 vcdl->g_zfs = g_zfs;
684
685 /* Gather our list of all vdevs in all pools */
686 for_each_pool(argc, argv, B_TRUE, NULL, ZFS_TYPE_POOL,
687 B_FALSE, all_pools_for_each_vdev_gather_cb, vcdl);
688
689 /* Run command on all vdevs in all pools */
690 all_pools_for_each_vdev_run_vcdl(vcdl);
691
692 /*
693 * vcdl->data[] now contains all the column names and values for each
694 * vdev. We need to process that into a master list of unique column
695 * names, and figure out the width of each column.
696 */
697 process_unique_cmd_columns(vcdl);
698
699 return (vcdl);
700 }
701
702 /*
703 * Free the vdev_cmd_data_list_t created by all_pools_for_each_vdev_run()
704 */
705 void
free_vdev_cmd_data_list(vdev_cmd_data_list_t * vcdl)706 free_vdev_cmd_data_list(vdev_cmd_data_list_t *vcdl)
707 {
708 free(vcdl->uniq_cols);
709 free(vcdl->uniq_cols_width);
710
711 for (int i = 0; i < vcdl->count; i++) {
712 free(vcdl->data[i].path);
713 free(vcdl->data[i].pool);
714 free(vcdl->data[i].upath);
715
716 for (int j = 0; j < vcdl->data[i].lines_cnt; j++)
717 free(vcdl->data[i].lines[j]);
718
719 free(vcdl->data[i].lines);
720
721 for (int j = 0; j < vcdl->data[i].cols_cnt; j++)
722 free(vcdl->data[i].cols[j]);
723
724 free(vcdl->data[i].cols);
725 free(vcdl->data[i].vdev_enc_sysfs_path);
726 }
727 free(vcdl->data);
728 free(vcdl);
729 }
730