From b07f5193bbd395e07d6e34377c4d73d77204df48 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Tue, 1 Jul 2025 10:43:58 -0600 Subject: [PATCH 1/3] Add "zpool status -vv" Specifying the verbose flag twice will display a list of all corrupt sectors within each corrupt file, as opposed to just the name of the file. Signed-off-by: Alan Somers Sponsored by: ConnectWise --- cmd/zpool/zpool_main.c | 93 +++++++++++++++++++++++++++++++--------- include/libzfs.h | 2 +- include/sys/fs/zfs.h | 2 + lib/libzfs/libzfs.abi | 1 + lib/libzfs/libzfs_pool.c | 24 ++++++++++- man/man8/zpool-status.8 | 6 ++- 6 files changed, 102 insertions(+), 26 deletions(-) diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index 237e558da65b..83fb768cde8c 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -2590,7 +2590,7 @@ typedef struct status_cbdata { int cb_name_flags; int cb_namewidth; boolean_t cb_allpools; - boolean_t cb_verbose; + int cb_verbosity; boolean_t cb_literal; boolean_t cb_explain; boolean_t cb_first; @@ -3322,7 +3322,7 @@ print_class_vdevs(zpool_handle_t *zhp, status_cbdata_t *cb, nvlist_t *nv, nvlist_t **child; boolean_t printed = B_FALSE; - assert(zhp != NULL || !cb->cb_verbose); + assert(zhp != NULL || cb->cb_verbosity == 0); if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN, &child, &children) != 0) @@ -9478,7 +9478,7 @@ class_vdevs_nvlist(zpool_handle_t *zhp, status_cbdata_t *cb, nvlist_t *nv, if (!cb->cb_flat_vdevs) class_obj = fnvlist_alloc(); - assert(zhp != NULL || !cb->cb_verbose); + assert(zhp != NULL || cb->cb_verbosity == 0); if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN, &child, &children) != 0) @@ -9586,26 +9586,38 @@ static void errors_nvlist(zpool_handle_t *zhp, status_cbdata_t *cb, nvlist_t *item) { uint64_t nerr; + int verbosity = cb->cb_verbosity; nvlist_t *config = zpool_get_config(zhp, NULL); if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_ERRCOUNT, &nerr) == 0) { nice_num_str_nvlist(item, ZPOOL_CONFIG_ERRCOUNT, nerr, cb->cb_literal, cb->cb_json_as_int, ZFS_NICENUM_1024); - if (nerr != 0 && cb->cb_verbose) { + if (nerr != 0 && cb->cb_verbosity > 0) { nvlist_t *nverrlist = NULL; - if (zpool_get_errlog(zhp, &nverrlist) == 0) { + if (zpool_get_errlog(zhp, &nverrlist, verbosity) == 0) { int i = 0; int count = 0; size_t len = MAXPATHLEN * 2; nvpair_t *elem = NULL; + char **errl = NULL, *pathbuf = NULL; + nvlist_t **errnvl = NULL; for (nvpair_t *pair = nvlist_next_nvpair(nverrlist, NULL); pair != NULL; pair = nvlist_next_nvpair(nverrlist, pair)) count++; - char **errl = (char **)malloc( - count * sizeof (char *)); + if (cb->cb_verbosity < 2) + errl = calloc(count, sizeof (char *)); + else { + pathbuf = safe_malloc(len); + errnvl = calloc(count, + sizeof (nvlist_t *)); + } + if (errl == NULL && errnvl == NULL) { + perror("calloc"); + exit(1); + } while ((elem = nvlist_next_nvpair(nverrlist, elem)) != NULL) { @@ -9618,16 +9630,46 @@ errors_nvlist(zpool_handle_t *zhp, status_cbdata_t *cb, nvlist_t *item) ZPOOL_ERR_DATASET, &dsobj) == 0); verify(nvlist_lookup_uint64(nv, ZPOOL_ERR_OBJECT, &obj) == 0); - errl[i] = safe_malloc(len); - zpool_obj_to_path(zhp, dsobj, obj, - errl[i++], len); + if (cb->cb_verbosity < 2) { + errl[i] = safe_malloc(len); + zpool_obj_to_path(zhp, dsobj, + obj, errl[i++], len); + } else { + uint64_t lvl, blkid; + + errnvl[i] = fnvlist_alloc(); + lvl = fnvlist_lookup_uint64(nv, + ZPOOL_ERR_LEVEL); + blkid = fnvlist_lookup_uint64( + nv, ZPOOL_ERR_BLKID); + zpool_obj_to_path(zhp, dsobj, + obj, pathbuf, len); + fnvlist_add_string(errnvl[i], + "path", pathbuf); + fnvlist_add_uint64(errnvl[i], + "level", lvl); + fnvlist_add_uint64(errnvl[i++], + "record", blkid); + } } nvlist_free(nverrlist); - fnvlist_add_string_array(item, "errlist", - (const char **)errl, count); - for (int i = 0; i < count; ++i) - free(errl[i]); - free(errl); + if (cb->cb_verbosity < 2) { + fnvlist_add_string_array(item, + "errlist", (const char **)errl, + count); + for (int i = 0; i < count; ++i) + free(errl[i]); + free(errl); + } else { + fnvlist_add_nvlist_array(item, + "errlist", + (const nvlist_t **)errnvl, + count); + for (int i = 0; i < count; ++i) + free(errnvl[i]); + free(errnvl); + free(pathbuf); + } } else fnvlist_add_string(item, "errlist", strerror(errno)); @@ -10304,14 +10346,14 @@ print_checkpoint_status(pool_checkpoint_stat_t *pcs) } static void -print_error_log(zpool_handle_t *zhp) +print_error_log(zpool_handle_t *zhp, int verbosity) { nvlist_t *nverrlist = NULL; nvpair_t *elem; char *pathname; size_t len = MAXPATHLEN * 2; - if (zpool_get_errlog(zhp, &nverrlist) != 0) + if (zpool_get_errlog(zhp, &nverrlist, verbosity) != 0) return; (void) printf("errors: Permanent errors have been " @@ -10329,7 +10371,16 @@ print_error_log(zpool_handle_t *zhp) 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); + if (verbosity > 1) { + uint64_t level, blkid; + + blkid = fnvlist_lookup_uint64(nv, ZPOOL_ERR_BLKID); + level = fnvlist_lookup_uint64(nv, ZPOOL_ERR_LEVEL); + (void) printf("%7s %s L%lu record %lu\n", "", pathname, + level, blkid); + } else { + (void) printf("%7s %s\n", "", pathname); + } } free(pathname); nvlist_free(nverrlist); @@ -11027,14 +11078,14 @@ status_callback(zpool_handle_t *zhp, void *data) if (nerr == 0) { (void) printf(gettext( "errors: No known data errors\n")); - } else if (!cbp->cb_verbose) { + } else if (0 == cbp->cb_verbosity) { color_start(ANSI_RED); (void) printf(gettext("errors: %llu data " "errors, use '-v' for a list\n"), (u_longlong_t)nerr); color_end(); } else { - print_error_log(zhp); + print_error_log(zhp, cbp->cb_verbosity); } } @@ -11161,7 +11212,7 @@ zpool_do_status(int argc, char **argv) get_timestamp_arg(*optarg); break; case 'v': - cb.cb_verbose = B_TRUE; + cb.cb_verbosity++; break; case 'j': cb.cb_json = B_TRUE; diff --git a/include/libzfs.h b/include/libzfs.h index 3fcdc176a621..432d38d43c8b 100644 --- a/include/libzfs.h +++ b/include/libzfs.h @@ -479,7 +479,7 @@ _LIBZFS_H zpool_status_t zpool_import_status(nvlist_t *, const char **, _LIBZFS_H nvlist_t *zpool_get_config(zpool_handle_t *, nvlist_t **); _LIBZFS_H nvlist_t *zpool_get_features(zpool_handle_t *); _LIBZFS_H int zpool_refresh_stats(zpool_handle_t *, boolean_t *); -_LIBZFS_H int zpool_get_errlog(zpool_handle_t *, nvlist_t **); +_LIBZFS_H int zpool_get_errlog(zpool_handle_t *, nvlist_t **, int); _LIBZFS_H void zpool_add_propname(zpool_handle_t *, const char *); /* diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index fc359c10365a..25bf1c3fa49b 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -1723,6 +1723,8 @@ typedef enum { #define ZPOOL_ERR_LIST "error list" #define ZPOOL_ERR_DATASET "dataset" #define ZPOOL_ERR_OBJECT "object" +#define ZPOOL_ERR_LEVEL "level" +#define ZPOOL_ERR_BLKID "blkid" #define HIS_MAX_RECORD_LEN (MAXPATHLEN + MAXPATHLEN + 1) diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi index ba161d1ef10f..c6249e017a4e 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -7103,6 +7103,7 @@ + diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c index 10b42720e963..7196b146d938 100644 --- a/lib/libzfs/libzfs_pool.c +++ b/lib/libzfs/libzfs_pool.c @@ -4618,7 +4618,7 @@ zpool_add_propname(zpool_handle_t *zhp, const char *propname) * caller. */ int -zpool_get_errlog(zpool_handle_t *zhp, nvlist_t **nverrlistp) +zpool_get_errlog(zpool_handle_t *zhp, nvlist_t **nverrlistp, int verbosity) { zfs_cmd_t zc = {"\0"}; libzfs_handle_t *hdl = zhp->zpool_hdl; @@ -4674,8 +4674,16 @@ zpool_get_errlog(zpool_handle_t *zhp, nvlist_t **nverrlistp) for (uint64_t i = 0; i < zblen; i++) { nvlist_t *nv; - /* ignoring zb_blkid and zb_level for now */ + /* filter out duplicate records */ if (i > 0 && zb[i-1].zb_objset == zb[i].zb_objset && + zb[i-1].zb_object == zb[i].zb_object && + zb[i-1].zb_level == zb[i].zb_level && + zb[i-1].zb_blkid == zb[i].zb_blkid) + continue; + + /* filter out duplicate files */ + if (verbosity < 2 && i > 0 && + zb[i-1].zb_objset == zb[i].zb_objset && zb[i-1].zb_object == zb[i].zb_object) continue; @@ -4691,6 +4699,18 @@ zpool_get_errlog(zpool_handle_t *zhp, nvlist_t **nverrlistp) nvlist_free(nv); goto nomem; } + if (verbosity > 1) { + if (nvlist_add_uint64(nv, ZPOOL_ERR_LEVEL, + zb[i].zb_level) != 0) { + nvlist_free(nv); + goto nomem; + } + if (nvlist_add_uint64(nv, ZPOOL_ERR_BLKID, + zb[i].zb_blkid) != 0) { + nvlist_free(nv); + goto nomem; + } + } if (nvlist_add_nvlist(*nverrlistp, "ejk", nv) != 0) { nvlist_free(nv); goto nomem; diff --git a/man/man8/zpool-status.8 b/man/man8/zpool-status.8 index a7f3e088043b..410604a662f1 100644 --- a/man/man8/zpool-status.8 +++ b/man/man8/zpool-status.8 @@ -27,7 +27,7 @@ .\" Copyright 2017 Nexenta Systems, Inc. .\" Copyright (c) 2017 Open-E, Inc. All Rights Reserved. .\" -.Dd February 14, 2024 +.Dd July 1, 2025 .Dt ZPOOL-STATUS 8 .Os . @@ -156,7 +156,9 @@ See Display vdev TRIM status. .It Fl v Displays verbose data error information, printing out a complete list of all -data errors since the last complete pool scrub. +files containing data errors since the last complete pool scrub. +Specified twice, prints out the complete list of all corrupt records within +each corrupt file. If the head_errlog feature is enabled and files containing errors have been removed then the respective filenames will not be reported in subsequent runs of this command. From 1e469dc91685793b513d736b162d75b2f8c9945d Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Wed, 20 Aug 2025 14:55:38 -0600 Subject: [PATCH 2/3] Respond to review comments: * Use a local variable more consistently * Condense error reports into runs of contiguous blocks --- cmd/zpool/zpool_main.c | 50 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index 83fb768cde8c..64777f751d2f 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -9592,7 +9592,7 @@ errors_nvlist(zpool_handle_t *zhp, status_cbdata_t *cb, nvlist_t *item) &nerr) == 0) { nice_num_str_nvlist(item, ZPOOL_CONFIG_ERRCOUNT, nerr, cb->cb_literal, cb->cb_json_as_int, ZFS_NICENUM_1024); - if (nerr != 0 && cb->cb_verbosity > 0) { + if (nerr != 0 && verbosity > 0) { nvlist_t *nverrlist = NULL; if (zpool_get_errlog(zhp, &nverrlist, verbosity) == 0) { int i = 0; @@ -9607,7 +9607,7 @@ errors_nvlist(zpool_handle_t *zhp, status_cbdata_t *cb, nvlist_t *item) pair != NULL; pair = nvlist_next_nvpair(nverrlist, pair)) count++; - if (cb->cb_verbosity < 2) + if (verbosity < 2) errl = calloc(count, sizeof (char *)); else { pathbuf = safe_malloc(len); @@ -9630,7 +9630,7 @@ errors_nvlist(zpool_handle_t *zhp, status_cbdata_t *cb, nvlist_t *item) ZPOOL_ERR_DATASET, &dsobj) == 0); verify(nvlist_lookup_uint64(nv, ZPOOL_ERR_OBJECT, &obj) == 0); - if (cb->cb_verbosity < 2) { + if (verbosity < 2) { errl[i] = safe_malloc(len); zpool_obj_to_path(zhp, dsobj, obj, errl[i++], len); @@ -9653,7 +9653,7 @@ errors_nvlist(zpool_handle_t *zhp, status_cbdata_t *cb, nvlist_t *item) } } nvlist_free(nverrlist); - if (cb->cb_verbosity < 2) { + if (verbosity < 2) { fnvlist_add_string_array(item, "errlist", (const char **)errl, count); @@ -10372,12 +10372,48 @@ print_error_log(zpool_handle_t *zhp, int verbosity) &obj) == 0); zpool_obj_to_path(zhp, dsobj, obj, pathname, len); if (verbosity > 1) { - uint64_t level, blkid; + uint64_t level, blkid, blkid_next, blkid_start; blkid = fnvlist_lookup_uint64(nv, ZPOOL_ERR_BLKID); + blkid_start = blkid; level = fnvlist_lookup_uint64(nv, ZPOOL_ERR_LEVEL); - (void) printf("%7s %s L%lu record %lu\n", "", pathname, - level, blkid); + do { + uint64_t level_next; + uint64_t dsobj_next, obj_next; + + elem = nvlist_next_nvpair(nverrlist, elem); + if (elem == NULL) + { + elem = nvlist_prev_nvpair(nverrlist, + elem); + break; + } + nv = fnvpair_value_nvlist(elem); + dsobj_next = fnvlist_lookup_uint64(nv, + ZPOOL_ERR_DATASET); + obj_next = fnvlist_lookup_uint64(nv, + ZPOOL_ERR_OBJECT); + blkid_next = fnvlist_lookup_uint64(nv, + ZPOOL_ERR_BLKID); + level_next = fnvlist_lookup_uint64(nv, + ZPOOL_ERR_LEVEL); + if (dsobj != dsobj_next || obj != obj_next || + level != level_next || + blkid + 1 != blkid_next) + { + elem = nvlist_prev_nvpair(nverrlist, + elem); + break; + } + blkid = blkid_next; + } while(1) ; + + if (blkid > blkid_start) + (void) printf("%7s %s L%lu=%lu-%lu\n", "", + pathname, level, blkid_start, blkid); + else + (void) printf("%7s %s L%lu=%lu\n", "", pathname, + level, blkid); } else { (void) printf("%7s %s\n", "", pathname); } From c9f179fc8a0d1064629df1edfaad7117caf436df Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Wed, 20 Aug 2025 17:26:41 -0600 Subject: [PATCH 3/3] Condense error reports to a single line for each file. --- cmd/zpool/zpool_main.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index 64777f751d2f..0bb16444591a 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -10350,8 +10350,9 @@ print_error_log(zpool_handle_t *zhp, int verbosity) { nvlist_t *nverrlist = NULL; nvpair_t *elem; - char *pathname; + char *pathname, *last_pathname = NULL; size_t len = MAXPATHLEN * 2; + boolean_t started = B_FALSE; if (zpool_get_errlog(zhp, &nverrlist, verbosity) != 0) return; @@ -10371,12 +10372,25 @@ print_error_log(zpool_handle_t *zhp, int verbosity) verify(nvlist_lookup_uint64(nv, ZPOOL_ERR_OBJECT, &obj) == 0); zpool_obj_to_path(zhp, dsobj, obj, pathname, len); + if (last_pathname == NULL || + 0 != strncmp(pathname, last_pathname, len)) + { + last_pathname = strdup(pathname); + if (started) + (void) printf("\n"); + else + started = B_TRUE; + (void) printf("%7s %s ", "", pathname); + } else if (verbosity > 1) { + (void) printf(","); + } if (verbosity > 1) { uint64_t level, blkid, blkid_next, blkid_start; blkid = fnvlist_lookup_uint64(nv, ZPOOL_ERR_BLKID); blkid_start = blkid; level = fnvlist_lookup_uint64(nv, ZPOOL_ERR_LEVEL); + (void) printf("L%lu=", level); do { uint64_t level_next; uint64_t dsobj_next, obj_next; @@ -10409,16 +10423,14 @@ print_error_log(zpool_handle_t *zhp, int verbosity) } while(1) ; if (blkid > blkid_start) - (void) printf("%7s %s L%lu=%lu-%lu\n", "", - pathname, level, blkid_start, blkid); + (void) printf("%lu-%lu", blkid_start, blkid); else - (void) printf("%7s %s L%lu=%lu\n", "", pathname, - level, blkid); - } else { - (void) printf("%7s %s\n", "", pathname); + (void) printf("%lu", blkid); } } + (void) printf("\n"); free(pathname); + free(last_pathname); nvlist_free(nverrlist); }