diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c
index 237e558da65b..0bb16444591a 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 && 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 (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 (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 (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,15 @@ 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;
+ char *pathname, *last_pathname = NULL;
size_t len = MAXPATHLEN * 2;
+ boolean_t started = B_FALSE;
- if (zpool_get_errlog(zhp, &nverrlist) != 0)
+ if (zpool_get_errlog(zhp, &nverrlist, verbosity) != 0)
return;
(void) printf("errors: Permanent errors have been "
@@ -10329,9 +10372,65 @@ 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 (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;
+
+ 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("%lu-%lu", blkid_start, blkid);
+ else
+ (void) printf("%lu", blkid);
+ }
}
+ (void) printf("\n");
free(pathname);
+ free(last_pathname);
nvlist_free(nverrlist);
}
@@ -11027,14 +11126,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 +11260,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.