zpool: Add zpool status -vv error ranges

Print the byte error ranges with 'zpool status -vv'.  This works
with the normal zpool status formatting flags (-p, -j, --json-int).

In addition:

- Modify ZFS_IOC_OBJ_TO_STATS ioctl to optionally return "extended"
  object stats.
- Let zinject corrupt zvol data.
- Add test case.
- Add DIOCGPHYSPATH geom attribute to FreeBSD zvols.  This works
  basically the same as Linux's BLKZNAME ioctl, and returns the
  dataset name for a zvol (like "tank/vol").  This is used by zinject.

This commit takes code from these PRs: #17502 #9781 #8902

Reviewed-by: Alek Pinchuk <[email protected]>
Reviewed-by: Akash B <[email protected]>
Reviewed-by: Brian Behlendorf <[email protected]>
Signed-off-by: Tony Hutter <[email protected]>
Co-authored-by: Alan Somers <[email protected]>
Co-authored-by: TulsiJain <[email protected]>
Closes #3730
Closes #17864
This commit is contained in:
Tony Hutter authored and GitHub committed 2026-09-16 15:12:23 -07:00
1 parent 3a67db3143
commit e903655c50
17 files changed
+886 -98

No files matched your search

+75 -4
View File
@@ -15,6 +15,7 @@
*/
#include <libzfs.h>
#include <libzutil.h>
#include <errno.h>
#include <fcntl.h>
@@ -33,6 +34,7 @@
#include <sys/dmu_objset.h>
#include <sys/dnode.h>
#include <sys/vdev_impl.h>
#include <sys/zvol.h>
#include <sys/mkdev.h>
@@ -66,10 +68,46 @@ compress_slashes(const char *src, char *dest)
}
/*
* Given a full path to a file, translate into a dataset name and a relative
* path within the dataset. 'dataset' must be at least MAXNAMELEN characters,
* and 'relpath' must be at least MAXPATHLEN characters. We also pass a stat64
* buffer, which we need later to get the object ID.
* If 'inpath' points to a zvol block device, copy the zvol dataset path to 'ds'
* (like "tank/vol"), and return true. If 'inpath' does not point to a zvol
* block device, then return false.
*/
static boolean_t
get_zvol_dataset(const char *inpath, char *ds)
{
boolean_t rc = B_FALSE;
char *buf = realpath(inpath, NULL);
if (buf == NULL)
return (B_FALSE);
int fd = open(buf, O_RDONLY | O_CLOEXEC);
free(buf);
if (fd == -1)
return (B_FALSE);
/*
* We want to determine if 'inpath' is a zvol device. There's no
* universal way to do that, so we use OS-specific zvol ioctls.
*/
#if defined(__FreeBSD__)
if (ioctl(fd, DIOCGPHYSPATH, ds) == 0)
rc = B_TRUE;
#else
if (ioctl(fd, BLKZNAME, ds) == 0)
rc = B_TRUE;
#endif
close(fd);
return (rc);
}
/*
* Given a full path to a file or zvol device, translate into a dataset name and
* a relative path within the dataset. 'dataset' must be at least MAXNAMELEN
* characters, and 'relpath' must be at least MAXPATHLEN characters. We also
* pass a stat64 buffer, which we need later to get the object ID.
*/
static int
parse_pathname(const char *inpath, char *dataset, char *relpath,
@@ -88,6 +126,39 @@ parse_pathname(const char *inpath, char *dataset, char *relpath,
return (-1);
}
/* special case: inject errors into zvol */
if (get_zvol_dataset(inpath, fullpath)) {
char *slash;
/*
* The zvol's inode does not contain its object number.
* However, it has long been the case that the zvol data is
* object number 1 (ZVOL_OBJ):
*
* Object lvl iblk dblk dsize lsize %full type
* 0 6 128K 16K 11K 16K 6.25 DMU dnode
* 1 2 128K 16K 20.1M 20M 100.00 zvol object
* 2 1 128K 512 0 512 100.00 zvol prop
*
* So we hardcode that in the statbuf inode field as workaround.
*/
statbuf->st_ino = ZVOL_OBJ;
(void) strlcpy(dataset, fullpath, MAXNAMELEN);
/*
* fullpath contains string like 'tank/zvol'. Strip off the
* 'tank' and 'zvol' parts.
*/
slash = strchr(fullpath, '/');
if (slash == NULL) {
(void) fprintf(stderr, "invalid volume name: '%s'\n",
fullpath);
return (-1);
}
(void) strlcpy(relpath, slash + 1, MAXPATHLEN);
return (0);
}
if (getextmntent(fullpath, &mp, statbuf) != 0) {
(void) fprintf(stderr, "cannot find mountpoint for '%s'\n",
fullpath);
+186 -55
View File
@@ -527,7 +527,7 @@ get_usage(zpool_help_t idx)
return (gettext("\ttrim [-dw] [-r <rate>] [-c | -s] "
"<-a | <pool> [<device> ...]>\n"));
case HELP_STATUS:
return (gettext("\tstatus [-DdegiLPpstvx] "
return (gettext("\tstatus [-DdegiLPpstx] [-v|-vv] "
"[-c script1[,script2,...]] ...\n"
"\t [-j|--json [--json-flat-vdevs] [--json-int] "
"[--json-pool-key-guid]] ...\n"
@@ -1075,6 +1075,9 @@ nice_num_str_nvlist(nvlist_t *item, const char *key, uint64_t value,
case ZFS_NICENUM_BYTES:
zfs_nicenum_format(value, buf, 256, ZFS_NICENUM_BYTES);
break;
case ZFS_NICENUM_RAW:
zfs_nicenum_format(value, buf, 256, ZFS_NICENUM_RAW);
break;
case ZFS_NICENUM_TIME:
zfs_nicenum_format(value, buf, 256, ZFS_NICENUM_TIME);
break;
@@ -2616,8 +2619,8 @@ typedef struct status_cbdata {
int cb_count;
int cb_name_flags;
int cb_namewidth;
int cb_verbosity;
boolean_t cb_allpools;
boolean_t cb_verbose;
boolean_t cb_literal;
boolean_t cb_explain;
boolean_t cb_first;
@@ -3349,7 +3352,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)
@@ -9794,7 +9797,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)
@@ -9898,57 +9901,133 @@ spares_nvlist(zpool_handle_t *zhp, status_cbdata_t *cb, nvlist_t *nv,
}
}
/*
* Take a uint64 nvpair named 'name' from nverrlist, nicenum-ify it, and
* put it back in 'nverrlist', possibly as a string, with the same 'name'.
*/
static void
convert_nvlist_uint64_to_nicenum(status_cbdata_t *cb, nvlist_t *parent,
const char *name, enum zfs_nicenum_format format)
{
uint64_t val;
nvpair_t *nvp;
if (nvlist_lookup_nvpair(parent, name, &nvp) != 0)
return; /* nothing by that name, ignore */
val = fnvpair_value_uint64(nvp);
nvlist_remove_nvpair(parent, nvp);
nice_num_str_nvlist(parent, name, val,
cb->cb_literal, cb->cb_json_as_int, format);
}
static void
errors_nvlist(zpool_handle_t *zhp, status_cbdata_t *cb, nvlist_t *item)
{
int verbosity = cb->cb_verbosity;
nvlist_t *nverrlist = NULL;
nvpair_t *elem;
char *pathname;
size_t len = MAXPATHLEN * 2;
nvlist_t **ranges;
uint_t count;
uint_t objs = 0, i;
nvlist_t **nv_arr = NULL;
char **str_arr = NULL;
uint64_t nerr;
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,
if (zpool_get_errlog(zhp, &nverrlist) != 0) {
/* We're expected to always return an error count */
nice_num_str_nvlist(item, ZPOOL_CONFIG_ERRCOUNT, 0,
cb->cb_literal, cb->cb_json_as_int, ZFS_NICENUM_1024);
if (nerr != 0 && cb->cb_verbose) {
nvlist_t *nverrlist = NULL;
if (zpool_get_errlog(zhp, &nverrlist) == 0) {
int i = 0;
int count = 0;
size_t len = MAXPATHLEN * 2;
nvpair_t *elem = 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 *));
while ((elem = nvlist_next_nvpair(nverrlist,
elem)) != NULL) {
nvlist_t *nv;
uint64_t dsobj, obj;
verify(nvpair_value_nvlist(elem,
&nv) == 0);
verify(nvlist_lookup_uint64(nv,
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);
}
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);
} else
fnvlist_add_string(item, "errlist",
strerror(errno));
}
return;
}
/*
* This 'error_count' entry is just the full error log block count.
* This includes duplicate and overlapping entries so it can be
* inaccurate. It's only included for historical reasons.
*/
nvlist_t *config = zpool_get_config(zhp, NULL);
nerr = fnvlist_lookup_uint64(config, ZPOOL_CONFIG_ERRCOUNT);
nice_num_str_nvlist(item, ZPOOL_CONFIG_ERRCOUNT, nerr,
cb->cb_literal, cb->cb_json_as_int, ZFS_NICENUM_1024);
pathname = safe_malloc(len);
/* Get an initial count of all the objects (files) with errors */
elem = NULL;
while ((elem = nvlist_next_nvpair(nverrlist, elem)) != NULL)
objs++;
if (verbosity <= 1)
str_arr = safe_malloc(objs * sizeof (*str_arr));
else
nv_arr = safe_malloc(objs * sizeof (*nv_arr));
elem = NULL;
for (i = 0; i < objs; i++) {
nvlist_t *nv;
uint64_t dsobj, obj;
elem = nvlist_next_nvpair(nverrlist, elem);
nv = fnvpair_value_nvlist(elem);
dsobj = fnvlist_lookup_uint64(nv, ZPOOL_ERR_DATASET);
obj = fnvlist_lookup_uint64(nv, ZPOOL_ERR_OBJECT);
zpool_obj_to_path(zhp, dsobj, obj, pathname, len);
/*
* Each JSON entry is a different file/zvol. If user has
* verbosity = 1, then just we're just constructing an array
* of strings.
*/
if (str_arr != NULL) {
str_arr[i] = strdup(pathname);
continue;
}
fnvlist_add_string(nv, ZPOOL_ERR_NAME, pathname);
/* nicenum-ify our nvlist */
convert_nvlist_uint64_to_nicenum(cb, nv, ZPOOL_ERR_OBJECT,
ZFS_NICENUM_RAW);
convert_nvlist_uint64_to_nicenum(cb, nv, ZPOOL_ERR_DATASET,
ZFS_NICENUM_RAW);
convert_nvlist_uint64_to_nicenum(cb, nv, ZPOOL_ERR_BLOCK_SIZE,
ZFS_NICENUM_1024);
if (nvlist_lookup_nvlist_array(nv, ZPOOL_ERR_RANGES, &ranges,
&count) == 0) {
for (uint_t i = 0; i < count; i++) {
convert_nvlist_uint64_to_nicenum(cb, ranges[i],
ZPOOL_ERR_START_BYTE, ZFS_NICENUM_1024);
convert_nvlist_uint64_to_nicenum(cb, ranges[i],
ZPOOL_ERR_END_BYTE, ZFS_NICENUM_1024);
}
}
nv_arr[i] = fnvlist_alloc();
fnvlist_add_nvlist(nv_arr[i], pathname, nv);
}
/* Place our error list in a top level "errlist" JSON array object. */
if (str_arr != NULL) {
fnvlist_add_string_array(item, ZPOOL_ERR_JSON,
(const char **) str_arr, objs);
for (i = 0; i < objs; i++)
free(str_arr[i]);
free(str_arr);
} else {
fnvlist_add_nvlist_array(item, ZPOOL_ERR_JSON,
(const nvlist_t **) nv_arr, objs);
for (i = 0; i < objs; i++)
nvlist_free(nv_arr[i]);
free(nv_arr);
}
free(pathname);
nvlist_free(nverrlist);
}
static void
@@ -10714,12 +10793,14 @@ print_condense_status(nvlist_t *nv)
}
static void
print_error_log(zpool_handle_t *zhp)
print_error_log(zpool_handle_t *zhp, int verbosity, boolean_t literal)
{
nvlist_t *nverrlist = NULL;
nvpair_t *elem;
char *pathname;
size_t len = MAXPATHLEN * 2;
boolean_t started = B_FALSE;
char last_pathname[MAXPATHLEN] = "";
if (zpool_get_errlog(zhp, &nverrlist) != 0)
return;
@@ -10739,8 +10820,51 @@ 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[0] == '\0' ||
strncmp(pathname, last_pathname, sizeof (last_pathname))
!= 0) {
strlcpy(last_pathname, pathname,
sizeof (last_pathname));
if (started)
(void) printf("\n");
else
started = B_TRUE;
(void) printf("%7s %s ", "", pathname);
} else if (verbosity > 1) {
(void) printf(",");
}
if (verbosity > 1) {
nvlist_t **arr;
uint_t count;
if (nvlist_lookup_nvlist_array(nv, ZPOOL_ERR_RANGES,
&arr, &count) != 0) {
(void) printf("(no ranges)");
continue;
}
for (uint_t i = 0; i < count; i++) {
uint64_t start;
uint64_t end;
start = fnvlist_lookup_uint64(arr[i],
ZPOOL_ERR_START_BYTE);
end = fnvlist_lookup_uint64(arr[i],
ZPOOL_ERR_END_BYTE);
if (literal) {
(void) printf("%llu-%llu",
(u_longlong_t)start,
(u_longlong_t)end);
} else {
char s1[32], s2[32];
zfs_nicenum(start, s1, sizeof (s1));
zfs_nicenum(end, s2, sizeof (s2));
(void) printf("%s-%s", s1, s2);
}
if (i != count - 1)
(void) printf(",");
}
}
}
(void) printf("\n");
free(pathname);
nvlist_free(nverrlist);
}
@@ -11322,7 +11446,13 @@ status_callback_json(zpool_handle_t *zhp, void *data)
spares_nvlist(zhp, cbp, nvroot, item);
}
dedup_stats_nvlist(zhp, cbp, item);
errors_nvlist(zhp, cbp, item);
/*
* Historically, -j would always print the number of errors
* so check for that in addition to verbosity.
*/
if (cbp->cb_verbosity > 0 || cbp->cb_json)
errors_nvlist(zhp, cbp, item);
}
if (cbp->cb_json_pool_key_guid) {
fnvlist_add_nvlist(d, pool_guid, item);
@@ -11496,14 +11626,15 @@ 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 (cbp->cb_verbosity == 0) {
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,
cbp->cb_literal);
}
}
@@ -11630,7 +11761,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;
+18
View File
@@ -1875,6 +1875,24 @@ 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" /* unused */
/* Additional nvpairs from zpool_get_errlog() nvlist */
#define ZPOOL_ERR_BLOCK_SIZE "block_size"
#define ZPOOL_ERR_OBJECT_TYPE "object_type"
#define ZPOOL_ERR_RANGES "ranges"
#define ZPOOL_ERR_START_BYTE "start_byte"
#define ZPOOL_ERR_END_BYTE "end_byte"
#define ZPOOL_ERR_NAME "name"
/*
* For the zpool status JSON output, we collect all the error lists and put
* them in a separate top level element so they're easier to iterate over.
* That way the error lists don't get interspersed with the zpool status
* objects.
*/
#define ZPOOL_ERR_JSON "errlist"
#define HIS_MAX_RECORD_LEN (MAXPATHLEN + MAXPATHLEN + 1)
+23
View File
@@ -354,6 +354,29 @@ typedef enum bp_embedded_type {
#define SPA_DVAS_PER_BP 3 /* Number of DVAs in a bp */
#define SPA_SYNC_MIN_VDEVS 3 /* min vdevs to update during sync */
/*
* Get number of data block pointers an indirect block could point to (given
* the block level and block size shift).
*
* For example, an L1 block with a blocksize of 128kb could point to:
*
* BP_SPANB(17, 1) = 1024 L0 block pointers
*/
#define BP_SPANB(indblkshift, level) \
(((uint64_t)1) << ((level) * ((indblkshift) - SPA_BLKPTRSHIFT)))
/*
* Helper function to lookup the byte range covered by a block pointer of any
* level (L0, L1, L2 ... etc).
*
* For example if you have an L1 block, and your blocksize is 128kb (shift 17),
* then your block can cover this many bytes:
*
* BP_BYTE_RANGE(17, 1) = 134217728 bytes
*/
#define BP_BYTE_RANGE(indblkshift, level) \
(BP_SPANB(indblkshift, level) * ((uint64_t)1 << indblkshift))
/*
* A block is a hole when it has either 1) never been written to, or
* 2) is zero-filled. In both cases, ZFS can return all zeroes for all reads
+22 -1
View File
@@ -38,7 +38,28 @@ typedef struct zfs_stat {
} zfs_stat_t;
extern int zfs_obj_to_stats(objset_t *osp, uint64_t obj, zfs_stat_t *sb,
char *buf, int len);
char *buf, int len, nvlist_t *nv);
/*
* The legacy behavior of ZFS_IOC_OBJ_TO_STATS is to return a zfs_stat_t struct.
* However, if the user passes in a nvlist dst buffer, we also return
* "extended" object stats. Currently, these extended stats are handpicked
* fields from dmu_object_info_t, but they could be expanded to include
* anything.
*/
#define ZFS_OBJ_STAT_DATA_BLOCK_SIZE "data_block_size"
#define ZFS_OBJ_STAT_METADATA_BLOCK_SIZE "metadata_block_size"
#define ZFS_OBJ_STAT_DNODE_SIZE "dnode_size"
#define ZFS_OBJ_STAT_TYPE "type"
#define ZFS_OBJ_STAT_TYPE_STR "type_str"
#define ZFS_OBJ_STAT_BONUS_TYPE "bonus_type"
#define ZFS_OBJ_STAT_BONUS_TYPE_STR "bonus_type_str"
#define ZFS_OBJ_STAT_BONUS_SIZE "bonus_size"
#define ZFS_OBJ_STAT_CHECKSUM "checksum"
#define ZFS_OBJ_STAT_COMPRESS "compress"
#define ZFS_OBJ_STAT_PHYSICAL_BLOCKS_512 "physical_blocks_512"
#define ZFS_OBJ_STAT_MAX_OFFSET "max_offset"
#define ZFS_OBJ_STAT_FILL_COUNT "fill_count"
#ifdef __cplusplus
}
+292 -25
View File
@@ -35,6 +35,7 @@
#include <zone.h>
#include <sys/stat.h>
#include <sys/efi_partition.h>
#include <sys/range_tree.h>
#include <sys/systeminfo.h>
#include <sys/zfs_ioctl.h>
#include <sys/zfs_sysfs.h>
@@ -51,6 +52,11 @@
#include "zfeature_common.h"
static boolean_t zpool_vdev_is_interior(const char *name);
static nvlist_t *zpool_get_extended_objset_stat(zpool_handle_t *zhp,
uint64_t dsobj, uint64_t obj);
static nvlist_t *
zpool_get_extended_obj_stat(zpool_handle_t *zhp, uint64_t dsobj, uint64_t obj);
typedef struct prop_flags {
unsigned int create:1; /* Validate property on creation */
@@ -4889,6 +4895,201 @@ zpool_add_propname(zpool_handle_t *zhp, const char *propname)
zhp->zpool_n_propnames++;
}
/*
* Given a properties nvlist like:
*
* refreservation:
* source: 'tank/vol'
* value: 1092616192
* recordsize:
* value: 4096
* source: 'tank'
* refcompressratio:
* value: 100
* logicalreferenced:
* value: 1076408320
* compressratio:
* value: 100
* ...
*
* Lookup the 'value' field for a uint64_t and return it into *val. For
* example, if you pass "recordsize" for the name, it will store 4096 into *val.
*/
static int
zpool_get_from_prop_nvlist_uint64(nvlist_t *nv, const char *name, uint64_t *val)
{
nvlist_t *tmp;
int rc;
rc = nvlist_lookup_nvlist(nv, name, &tmp);
if (rc != 0)
return (rc);
return (nvlist_lookup_uint64(tmp, "value", val));
}
/*
* Given a dataset object and object number, return its data blocks size
* and type string (type_str). type_str must be freed when no longer needed.
*/
static int
zpool_get_extended_obj_stat_helper(zpool_handle_t *zhp, uint64_t dsobj,
uint64_t obj, uint64_t *data_block_size, char **type_str)
{
nvlist_t *nv;
boolean_t is_zvol = B_FALSE;
uint64_t val;
int rc;
nv = zpool_get_extended_obj_stat(zhp, dsobj, obj);
if (nv == NULL) {
nv = zpool_get_extended_objset_stat(zhp, dsobj, obj);
if (nv == NULL) {
return (-1);
}
is_zvol = B_TRUE;
}
if (is_zvol) {
rc = zpool_get_from_prop_nvlist_uint64(nv,
zfs_prop_to_name(ZFS_PROP_VOLBLOCKSIZE), &val);
} else {
uint32_t val32 = 0;
rc = nvlist_lookup_uint32(nv, ZFS_OBJ_STAT_DATA_BLOCK_SIZE,
&val32);
val = val32;
}
if (rc != 0) {
nvlist_free(nv);
return (rc);
}
*data_block_size = val;
const char *tmp;
if (is_zvol)
tmp = "zvol";
else
tmp = fnvlist_lookup_string(nv, ZFS_OBJ_STAT_TYPE_STR);
*type_str = strdup(tmp);
nvlist_free(nv);
return (0);
}
static void zpool_get_errlog_process_file_cb(void *arg, uint64_t start,
uint64_t size) {
nvlist_t ***tmp = arg;
nvlist_t **nva_next = *tmp;
nvlist_t *nv;
nv = fnvlist_alloc();
fnvlist_add_uint64(nv, ZPOOL_ERR_START_BYTE, start);
fnvlist_add_uint64(nv, ZPOOL_ERR_END_BYTE, start + size - 1);
*nva_next = nv;
/* Advance to next array entry */
*tmp = nva_next + 1;
}
static void
zpool_get_errlog_process_file(zpool_handle_t *zhp,
nvlist_t **nverrlistp, zbookmark_phys_t *zb_start, zbookmark_phys_t *zb_end)
{
uint64_t data_block_size = 0;
char *type_str = NULL;
nvlist_t *nv, **nva, **nva_next;
uint64_t count = 0;
/* Make the linter happy */
VERIFY(zb_start != NULL);
VERIFY(zb_end != NULL);
nv = fnvlist_alloc();
fnvlist_add_uint64(nv, ZPOOL_ERR_DATASET, zb_start->zb_objset);
fnvlist_add_uint64(nv, ZPOOL_ERR_OBJECT, zb_start->zb_object);
if (zpool_get_extended_obj_stat_helper(zhp, zb_start->zb_objset,
zb_start->zb_object, &data_block_size, &type_str) != 0) {
/*
* If the kernel supports extended stats, then include them.
* If not, it's still OK.
*/
goto end;
}
fnvlist_add_string(nv, ZPOOL_ERR_OBJECT_TYPE, type_str);
fnvlist_add_uint64(nv, ZPOOL_ERR_BLOCK_SIZE, data_block_size);
free(type_str);
zfs_range_tree_t *range_tree;
range_tree = zfs_range_tree_create(NULL, ZFS_RANGE_SEG64, NULL, 0, 0);
if (range_tree == NULL)
goto end;
do {
int data_block_size_shift;
uint64_t data_block_range;
/*
* If an L1 (or higher block) is damaged, it will affect a much
* larger byte range than a simple L0 block. Calculate these
* ranges correctly.
*/
if (highbit64(data_block_size) != lowbit64(data_block_size)) {
/*
* Our data block size is not power-of-two. This is
* probably a single block file.
*/
data_block_size_shift = 0;
data_block_range = data_block_size;
} else {
data_block_size_shift = highbit64(data_block_size) - 1;
data_block_range = BP_BYTE_RANGE(data_block_size_shift,
zb_start->zb_level);
}
/*
* The range we are adding could overlap other entries. Do
* a clear first to punch a hole in any existing ranges,
* then do the add. We have to do this since our add can't
* overlap an existing range.
*/
zfs_range_tree_clear(range_tree,
zb_start->zb_blkid * data_block_range, data_block_range);
zfs_range_tree_add(range_tree,
zb_start->zb_blkid * data_block_range, data_block_range);
if (zb_start == zb_end)
break;
} while (zb_start++);
/*
* Our range tree has all our ranges. Construct an array of start/end
* entries.
*/
count = zfs_range_tree_numsegs(range_tree);
nva = zfs_alloc(zhp->zpool_hdl, sizeof (nvlist_t *) * count);
nva_next = &nva[0];
zfs_range_tree_walk(range_tree, zpool_get_errlog_process_file_cb,
&nva_next);
zfs_range_tree_vacate(range_tree, NULL, NULL);
zfs_range_tree_destroy(range_tree);
fnvlist_add_nvlist_array(nv, ZPOOL_ERR_RANGES, (const nvlist_t **) nva,
count);
for (uint64_t i = 0; i < count; i++)
nvlist_free(nva[i]);
free(nva);
end:
fnvlist_add_nvlist(*nverrlistp, "ejk", nv);
nvlist_free(nv);
}
/*
* Retrieve the persistent error log, uniquify the members, and return to the
* caller.
@@ -4900,6 +5101,7 @@ zpool_get_errlog(zpool_handle_t *zhp, nvlist_t **nverrlistp)
libzfs_handle_t *hdl = zhp->zpool_hdl;
zbookmark_phys_t *buf;
uint64_t buflen = 10000; /* approx. 1MB of RAM */
uint64_t i;
if (fnvlist_lookup_uint64(zhp->zpool_config,
ZPOOL_CONFIG_ERRCOUNT) == 0)
@@ -4939,6 +5141,10 @@ zpool_get_errlog(zpool_handle_t *zhp, nvlist_t **nverrlistp)
*/
zbookmark_phys_t *zb = buf + zc.zc_nvlist_dst_size;
uint64_t zblen = buflen - zc.zc_nvlist_dst_size;
if (zblen == 0) {
free(buf);
return (0); /* nothing to do */
}
qsort(zb, zblen, sizeof (zbookmark_phys_t), zbookmark_mem_compare);
@@ -4947,39 +5153,35 @@ zpool_get_errlog(zpool_handle_t *zhp, nvlist_t **nverrlistp)
/*
* Fill in the nverrlistp with nvlist's of dataset and object numbers.
*/
for (uint64_t i = 0; i < zblen; i++) {
nvlist_t *nv;
zbookmark_phys_t *start = NULL;
/* ignoring zb_blkid and zb_level for now */
if (i > 0 && zb[i-1].zb_objset == zb[i].zb_objset &&
zb[i-1].zb_object == zb[i].zb_object)
for (i = 0; i < zblen; i++) {
if (start == NULL) {
start = &zb[i];
continue;
}
if (nvlist_alloc(&nv, NV_UNIQUE_NAME, KM_SLEEP) != 0)
goto nomem;
if (nvlist_add_uint64(nv, ZPOOL_ERR_DATASET,
zb[i].zb_objset) != 0) {
nvlist_free(nv);
goto nomem;
/* filter out duplicate files and levels */
if (zb[i-1].zb_objset == zb[i].zb_objset &&
zb[i-1].zb_object == zb[i].zb_object) {
/* same file, new error block */
continue;
} else {
/*
* Every time we see a new object, process the
* previous one.
*/
zpool_get_errlog_process_file(zhp, nverrlistp,
start, &zb[i-1]);
start = &zb[i];
}
if (nvlist_add_uint64(nv, ZPOOL_ERR_OBJECT,
zb[i].zb_object) != 0) {
nvlist_free(nv);
goto nomem;
}
if (nvlist_add_nvlist(*nverrlistp, "ejk", nv) != 0) {
nvlist_free(nv);
goto nomem;
}
nvlist_free(nv);
}
/* Process the last entry */
zpool_get_errlog_process_file(zhp, nverrlistp, start, &zb[i-1]);
free(buf);
return (0);
nomem:
free(buf);
return (no_memory(zhp->zpool_hdl));
}
/*
@@ -5265,6 +5467,71 @@ zpool_events_seek(libzfs_handle_t *hdl, uint64_t eid, int zevent_fd)
return (error);
}
/*
* Return extended information about an object. This calls the "extended"
* variant of ZFS_IOC_OBJ_TO_STATS to return things things like block size,
* dmu type, dnone size, etc (see dmu_object_info_t).
*
* Returned nvlist must be freed by the user when they are done with it.
*/
static nvlist_t *
zpool_get_extended_obj_stat_impl(zpool_handle_t *zhp, uint64_t dsobj,
uint64_t obj, enum zfs_ioc stats_ioctl)
{
zfs_cmd_t zc = {"\0"};
char dsname[ZFS_MAX_DATASET_NAME_LEN];
nvlist_t *nv = NULL;
int error;
/* get the dataset's name */
zc.zc_obj = dsobj;
(void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
error = zfs_ioctl(zhp->zpool_hdl, ZFS_IOC_DSOBJ_TO_DSNAME, &zc);
if (error)
return (NULL);
(void) strlcpy(dsname, zc.zc_value, sizeof (dsname));
(void) strlcpy(zc.zc_name, dsname, sizeof (zc.zc_name));
zcmd_alloc_dst_nvlist(zhp->zpool_hdl, &zc, 1024);
zc.zc_obj = obj;
while (zfs_ioctl(zhp->zpool_hdl, stats_ioctl, &zc) != 0) {
if (errno == ENOMEM) {
zcmd_expand_dst_nvlist(zhp->zpool_hdl, &zc);
} else {
zcmd_free_nvlists(&zc);
return (NULL);
}
}
zcmd_read_dst_nvlist(zhp->zpool_hdl, &zc, &nv);
zcmd_free_nvlists(&zc);
return (nv);
}
/*
* Return extended information about an object. This calls the "extended"
* variant of ZFS_IOC_OBJ_TO_STATS to return things things like block size,
* dmu type, dnone size, etc (see dmu_object_info_t).
*
* Returned nvlist must be freed by the user when they are done with it.
*/
static nvlist_t *
zpool_get_extended_obj_stat(zpool_handle_t *zhp, uint64_t dsobj, uint64_t obj)
{
return (zpool_get_extended_obj_stat_impl(zhp, dsobj, obj,
ZFS_IOC_OBJ_TO_STATS));
}
static nvlist_t *
zpool_get_extended_objset_stat(zpool_handle_t *zhp, uint64_t dsobj,
uint64_t obj)
{
return (zpool_get_extended_obj_stat_impl(zhp, dsobj, obj,
ZFS_IOC_OBJSET_STATS));
}
static void
zpool_obj_to_path_impl(zpool_handle_t *zhp, uint64_t dsobj, uint64_t obj,
char *pathname, size_t len, boolean_t always_unmounted)
+2
View File
@@ -1086,6 +1086,7 @@ libzfs_init(void)
vdev_prop_init();
libzfs_mnttab_init(hdl);
fletcher_4_init();
zfs_btree_init();
if (getenv("ZFS_PROP_DEBUG") != NULL) {
hdl->libzfs_prop_debug = B_TRUE;
@@ -1130,6 +1131,7 @@ libzfs_fini(libzfs_handle_t *hdl)
libzfs_mnttab_fini(hdl);
libzfs_core_fini();
regfree(&hdl->libzfs_urire);
zfs_btree_fini();
fletcher_4_fini();
#if LIBFETCH_DYNAMIC
if (hdl->libfetch != (void *)-1 && hdl->libfetch != NULL)
+3 -2
View File
@@ -164,9 +164,10 @@ Panic inside the specified function.
.Op Fl l Ar level
.Op Fl r Ar range
.Op Fl amq
.Ar path
.Ar path|zvol
.Xc
Force an error into the contents of a file.
Force an error into the contents of a file or zvol.
For zvols, pass the path to the zvol block device.
.
.It Xo
.Nm zinject
+9 -4
View File
@@ -19,7 +19,7 @@
.\" Copyright (c) 2024-2026, Klara, Inc.
.\" Copyright (c) 2026, TrueNAS.
.\"
.Dd May 26, 2026
.Dd June 16, 2026
.Dt ZPOOL-STATUS 8
.Os
.
@@ -29,7 +29,8 @@
.Sh SYNOPSIS
.Nm zpool
.Cm status
.Op Fl DdegiLPpstvx
.Op Fl DdegiLPpstx
.Op Fl v|-vv
.Op Fl c Ar script1 Ns Oo , Ns Ar script2 Ns ,… Oc
.Oo Fl j|--json
.Oo Ns Fl -json-flat-vdevs Oc
@@ -148,9 +149,13 @@ See
.Xr time 1 .
.It Fl t
Display vdev TRIM status.
.It Fl v
.It Fl v|-vv
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
.Pq Sy -vv
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.
+10
View File
@@ -502,6 +502,16 @@ zvol_geom_bio_getattr(struct bio *bp)
if (g_handleattr_int(bp, "GEOM::candelete", 1))
return (0);
if (strcmp(bp->bio_attribute, "GEOM::physpath") == 0) {
mutex_enter(&zv->zv_state_lock);
if (g_handleattr_str(bp, "GEOM::physpath", zv->zv_name)) {
mutex_exit(&zv->zv_state_lock);
return (0);
}
mutex_exit(&zv->zv_state_lock);
}
if (strcmp(bp->bio_attribute, "blocksavail") == 0) {
dmu_objset_space(zv->zv_objset, &refd, &avail,
&usedobjs, &availobjs);
+23 -1
View File
@@ -2305,12 +2305,22 @@ zfs_ioc_obj_to_path(zfs_cmd_t *zc)
* outputs:
* zc_stat stats on object
* zc_value path to object
*
* -------------------------
* Optional extended stats
* -------------------------
* The legacy behavior of ZFS_IOC_OBJ_TO_STATS is return a zfs_stat_t struct.
* However, if the user passes in a nvlist dst buffer, we also return
* "extended" object stats. Currently, these extended stats are handpicked
* fields from dmu_object_info_t, but they could be expanded to include
* anything. See the ZFS_OBJ_STAT_* macros for the current list.
*/
static int
zfs_ioc_obj_to_stats(zfs_cmd_t *zc)
{
objset_t *os;
int error;
nvlist_t *nv = NULL;
/* XXX reading from objset not owned */
if ((error = dmu_objset_hold_flags(zc->zc_name, B_TRUE,
@@ -2320,10 +2330,22 @@ zfs_ioc_obj_to_stats(zfs_cmd_t *zc)
dmu_objset_rele_flags(os, B_TRUE, FTAG);
return (SET_ERROR(EINVAL));
}
if (zc->zc_nvlist_dst != 0 && zc->zc_nvlist_dst_size != 0)
nv = fnvlist_alloc();
error = zfs_obj_to_stats(os, zc->zc_obj, &zc->zc_stat, zc->zc_value,
sizeof (zc->zc_value));
sizeof (zc->zc_value), nv);
dmu_objset_rele_flags(os, B_TRUE, FTAG);
if (nv != NULL) {
int tmp = put_nvlist(zc, nv);
if (error == 0)
error = tmp;
nvlist_free(nv);
}
return (error);
}
+46 -3
View File
@@ -142,7 +142,7 @@ zfs_obj_to_pobj(objset_t *osp, sa_handle_t *hdl, sa_attr_type_t *sa_table,
* Given an object number, return some zpl level statistics
*/
static int
zfs_obj_to_stats_impl(sa_handle_t *hdl, sa_attr_type_t *sa_table,
zfs_obj_to_stats_legacy(sa_handle_t *hdl, sa_attr_type_t *sa_table,
zfs_stat_t *sb)
{
sa_bulk_attr_t bulk[4];
@@ -270,9 +270,48 @@ zfs_obj_to_path(objset_t *osp, uint64_t obj, char *buf, int len)
return (error);
}
/* Taken from ZDB */
static const char *
zfs_obj_type_name(dmu_object_type_t type)
{
if (type < DMU_OT_NUMTYPES)
return (dmu_ot[type].ot_name);
else if ((type & DMU_OT_NEWTYPE) &&
((type & DMU_OT_BYTESWAP_MASK) < DMU_BSWAP_NUMFUNCS))
return (dmu_ot_byteswap[type & DMU_OT_BYTESWAP_MASK].ob_name);
else
return ("UNKNOWN");
}
static void
zfs_obj_to_stats_extended(sa_handle_t *hdl, nvlist_t *nv)
{
dmu_object_info_t doi;
sa_object_info(hdl, &doi);
fnvlist_add_uint32(nv, ZFS_OBJ_STAT_DATA_BLOCK_SIZE,
doi.doi_data_block_size);
fnvlist_add_uint32(nv, ZFS_OBJ_STAT_METADATA_BLOCK_SIZE,
doi.doi_metadata_block_size);
fnvlist_add_uint64(nv, ZFS_OBJ_STAT_TYPE, doi.doi_type);
fnvlist_add_string(nv, ZFS_OBJ_STAT_TYPE_STR,
zfs_obj_type_name(doi.doi_type));
fnvlist_add_uint64(nv, ZFS_OBJ_STAT_BONUS_TYPE, doi.doi_bonus_type);
fnvlist_add_string(nv, ZFS_OBJ_STAT_BONUS_TYPE_STR,
zfs_obj_type_name(doi.doi_bonus_type));
fnvlist_add_uint64(nv, ZFS_OBJ_STAT_BONUS_SIZE, doi.doi_bonus_size);
fnvlist_add_uint8(nv, ZFS_OBJ_STAT_CHECKSUM, doi.doi_checksum);
fnvlist_add_uint8(nv, ZFS_OBJ_STAT_COMPRESS, doi.doi_compress);
fnvlist_add_uint64(nv, ZFS_OBJ_STAT_DNODE_SIZE, doi.doi_dnodesize);
fnvlist_add_uint64(nv, ZFS_OBJ_STAT_PHYSICAL_BLOCKS_512,
doi.doi_physical_blocks_512);
fnvlist_add_uint64(nv, ZFS_OBJ_STAT_MAX_OFFSET, doi.doi_max_offset);
fnvlist_add_uint64(nv, ZFS_OBJ_STAT_FILL_COUNT, doi.doi_fill_count);
}
int
zfs_obj_to_stats(objset_t *osp, uint64_t obj, zfs_stat_t *sb,
char *buf, int len)
char *buf, int len, nvlist_t *nv)
{
char *path = buf + len - 1;
sa_attr_type_t *sa_table;
@@ -290,12 +329,16 @@ zfs_obj_to_stats(objset_t *osp, uint64_t obj, zfs_stat_t *sb,
if (error != 0)
return (error);
error = zfs_obj_to_stats_impl(hdl, sa_table, sb);
error = zfs_obj_to_stats_legacy(hdl, sa_table, sb);
if (error != 0) {
zfs_release_sa_handle(hdl, db, FTAG);
return (error);
}
if (nv != NULL)
zfs_obj_to_stats_extended(hdl, nv);
error = zfs_obj_to_path_impl(osp, obj, hdl, sa_table, buf, len);
zfs_release_sa_handle(hdl, db, FTAG);
-2
View File
@@ -78,8 +78,6 @@ static uint64_t zio_buf_cache_frees[SPA_MAXBLOCKSIZE >> SPA_MINBLOCKSHIFT];
/* Mark IOs as "slow" if they take longer than 30 seconds */
static uint_t zio_slow_io_ms = (30 * MILLISEC);
#define BP_SPANB(indblkshift, level) \
(((uint64_t)1) << ((level) * ((indblkshift) - SPA_BLKPTRSHIFT)))
#define COMPARE_META_LEVEL 0x80000000ul
/*
* The following actions directly effect the spa's sync-to-convergence logic.
+2 -1
View File
@@ -599,7 +599,8 @@ tests = ['zpool_status_001_pos', 'zpool_status_002_pos',
'zpool_status_005_pos', 'zpool_status_006_pos',
'zpool_status_007_pos', 'zpool_status_008_pos',
'zpool_status_009_pos', 'zpool_status_010_pos',
'zpool_status_011_pos', 'zpool_status_features_001_pos']
'zpool_status_011_pos', 'zpool_status_features_001_pos',
'zpool_status_-v']
tags = ['functional', 'cli_root', 'zpool_status']
[tests/functional/cli_root/zpool_sync]
+1
View File
@@ -113,6 +113,7 @@ VDEV_DIRECT_WR_VERIFY vdev.direct_write_verify zfs_vdev_direct_write_verify
VDEV_VALIDATE_SKIP vdev.validate_skip vdev_validate_skip
VOL_INHIBIT_DEV vol.inhibit_dev zvol_inhibit_dev
VOL_MODE vol.mode zvol_volmode
VOL_PREFETCH_BYTES vol.prefetch_bytes zvol_prefetch_bytes
VOL_RECURSIVE vol.recursive UNSUPPORTED
VOL_REQUEST_SYNC vol.request_sync zvol_request_sync
VOL_USE_BLK_MQ UNSUPPORTED zvol_use_blk_mq
+1
View File
@@ -1411,6 +1411,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/cli_root/zpool_status/zpool_status_010_pos.ksh \
functional/cli_root/zpool_status/zpool_status_011_pos.ksh \
functional/cli_root/zpool_status/zpool_status_features_001_pos.ksh \
functional/cli_root/zpool_status/zpool_status_-v.ksh \
functional/cli_root/zpool_sync/cleanup.ksh \
functional/cli_root/zpool_sync/setup.ksh \
functional/cli_root/zpool_sync/zpool_sync_001_pos.ksh \
@@ -0,0 +1,173 @@
#!/bin/ksh -p
# SPDX-License-Identifier: CDDL-1.0
#
# This file and its contents are supplied under the terms of the
# Common Development and Distribution License ("CDDL"), version 1.0.
# You may only use this file in accordance with the terms of version
# 1.0 of the CDDL.
#
# A full copy of the text of the CDDL should have accompanied this
# source. A copy of the CDDL is also available via the Internet at
# https://opensource.org/license/CDDL-1.0.
# Copyright (c) 2025 Lawrence Livermore National Security, LLC.
. $STF_SUITE/include/libtest.shlib
#
# DESCRIPTION:
# Verify error log ranges from 'zpool status -vv' after corrupting a file
#
# STRATEGY:
# 1. Create files with different record sizes
# 2. Create a zvol
# 3. zinject errors into files, zvol, and MOS. Inject at different byte ranges.
# 4. Verify error reporting in zpool status with different flags
verify_runnable "both"
log_assert "Verify zpool status prints error log ranges"
# Given a list of lines in $1, look for each line somewhere in stdin (the
# zpool status output).
function check_status
{
# Read stdin
out="$(cat -)"
lines="$1"
while IFS= read -r line; do
if ! echo "$out" | grep -Fq "$line" ; then
log_fail "Didn't see '$line' string in: '$out'"
fi
done <<< "$lines"
log_note "Successfully saw '$lines'"
}
function cleanup
{
log_must restore_tunable VOL_PREFETCH_BYTES
log_must restore_tunable PREFETCH_DISABLE
log_must zinject -c all
log_must zfs destroy $TESTPOOL/4k
log_must zfs destroy $TESTPOOL/1m
log_must zfs destroy $TESTPOOL/$TESTVOL
}
log_onexit cleanup
log_must save_tunable PREFETCH_DISABLE
log_must set_tunable32 PREFETCH_DISABLE 1
log_must save_tunable VOL_PREFETCH_BYTES
log_must set_tunable32 VOL_PREFETCH_BYTES 0
log_must zfs set compression=off $TESTPOOL
log_must zfs create -o recordsize=4k $TESTPOOL/4k
log_must zfs create -o recordsize=1M $TESTPOOL/1m
log_must mkfile 1m /$TESTPOOL/4k/4k_file1
log_must mkfile 1m /$TESTPOOL/4k/4k_file2
log_must mkfile 10m /$TESTPOOL/1m/1m_file
export TESTVOL=testvol
log_must zfs create -V 200M -o compression=off -o volblocksize=4k $TESTPOOL/$TESTVOL
block_device_wait ${ZVOL_DEVDIR}/$TESTPOOL/$TESTVOL
log_must dd if=/dev/zero of=${ZVOL_DEVDIR}/$TESTPOOL/$TESTVOL bs=1M count=10 conv=fsync
log_must zpool sync $TESTPOOL
log_must_busy zpool export $TESTPOOL
log_must zpool import $TESTPOOL
# Corrupt the MOS. We do two scrubs here since the MOS error doesn't always
# show up after the first scrub for some reason.
log_must zinject -t mosdir $TESTPOOL
log_must zpool scrub $TESTPOOL
log_must wait_scrubbed $TESTPOOL
log_must zpool scrub $TESTPOOL
log_must wait_scrubbed $TESTPOOL
log_must zinject -t data -e checksum -f 100 /$TESTPOOL/4k/4k_file1
log_must zinject -t data -e checksum -f 100 /$TESTPOOL/4k/4k_file2
log_must zinject -t data -e checksum -f 100 /$TESTPOOL/1m/1m_file
log_must zinject -t data -e checksum -f 100 ${ZVOL_DEVDIR}/$TESTPOOL/$TESTVOL
# Try to read first 10 blocks of '4k_file1'. The read should fail.
log_mustnot dd conv=fsync if=/$TESTPOOL/4k/4k_file1 of=/dev/null bs=4k count=10
log_must zpool sync
# Try to read blocks 0 and blocks 4-5 and 6-7 on '4k_file2' to create multiple
# ranges. The read should fail.
log_mustnot dd conv=fsync if=/$TESTPOOL/4k/4k_file2 of=/dev/null bs=4k count=1 skip=0
log_must zpool sync
log_mustnot dd conv=fsync if=/$TESTPOOL/4k/4k_file2 of=/dev/null bs=4k count=2 skip=4
log_must zpool sync
log_mustnot dd conv=fsync if=/$TESTPOOL/4k/4k_file2 of=/dev/null bs=4k count=2 skip=6
log_must zpool sync
# Try to read the 2nd megabyte of '1m_file'
log_mustnot dd conv=fsync if=/$TESTPOOL/1m/1m_file of=/dev/null bs=1M skip=1 count=1
log_must zpool sync
# Try to read some ranges of the zvol.
log_mustnot dd conv=fsync if=${ZVOL_DEVDIR}/$TESTPOOL/$TESTVOL of=/dev/null bs=4k count=1 skip=1000
log_must zpool sync
log_mustnot dd conv=fsync if=${ZVOL_DEVDIR}/$TESTPOOL/$TESTVOL of=/dev/null bs=4k count=3 skip=1100
log_must zpool sync
log_must zinject -c all
# Look for each these lines somewhere in the zpool status output
val=$(cat << END
<metadata>:<0x1> (no ranges)
/testpool/4k/4k_file1 0-4.00K
/testpool/4k/4k_file2 0-4.00K,16K-20.0K,24K-28.0K
/testpool/1m/1m_file 1M-2.00M
testpool/testvol:<0x1> 3.91M-3.91M,4.30M-4.30M
END
)
zpool status -vv | check_status "$val"
val=$(cat << END
<metadata>:<0x1> (no ranges)
/testpool/4k/4k_file1 0-4095
/testpool/4k/4k_file2 0-4095,16384-20479,24576-28671
/testpool/1m/1m_file 1048576-2097151
testpool/testvol:<0x1> 4096000-4100095,4505600-4509695
END
)
zpool status -vvp | check_status "$val"
# Look only at the '.pools.testpool.errlist' JSON object for output. We
# remove the .object and .dataset objects since they are non-deterministic
# values.
#
# Look at four variants of the JSON output (-vj -vvj, -vvjp, -vvjp --json-int)
val=$(cat << END
["/testpool/1m/1m_file","/testpool/4k/4k_file1","/testpool/4k/4k_file2","<metadata>:<0x1>","testpool/testvol:<0x1>"]
END
)
zpool status -vj | jq -c '.pools.testpool.errlist | sort' | check_status "$val"
val=$(cat << END
[{"/testpool/1m/1m_file":{"object_type":"ZFS plain file","ranges":[{"start_byte":"1M","end_byte":"2.00M"}],"name":"/testpool/1m/1m_file","block_size":"1M"}},{"/testpool/4k/4k_file1":{"object_type":"ZFS plain file","ranges":[{"start_byte":"0","end_byte":"4.00K"}],"name":"/testpool/4k/4k_file1","block_size":"4K"}},{"/testpool/4k/4k_file2":{"object_type":"ZFS plain file","ranges":[{"start_byte":"0","end_byte":"4.00K"},{"start_byte":"16K","end_byte":"20.0K"},{"start_byte":"24K","end_byte":"28.0K"}],"name":"/testpool/4k/4k_file2","block_size":"4K"}},{"<metadata>:<0x1>":{"name":"<metadata>:<0x1>"}},{"testpool/testvol:<0x1>":{"object_type":"zvol","ranges":[{"start_byte":"3.91M","end_byte":"3.91M"},{"start_byte":"4.30M","end_byte":"4.30M"}],"name":"testpool/testvol:<0x1>","block_size":"4K"}}]
END
)
zpool status -vvj | jq -c '.pools.testpool.errlist | map(del(.[].dataset)) | map(del(.[].object)) | sort' | check_status "$val"
val=$(cat << END
[{"/testpool/1m/1m_file":{"object_type":"ZFS plain file","ranges":[{"start_byte":"1048576","end_byte":"2097151"}],"name":"/testpool/1m/1m_file","block_size":"1048576"}},{"/testpool/4k/4k_file1":{"object_type":"ZFS plain file","ranges":[{"start_byte":"0","end_byte":"4095"}],"name":"/testpool/4k/4k_file1","block_size":"4096"}},{"/testpool/4k/4k_file2":{"object_type":"ZFS plain file","ranges":[{"start_byte":"0","end_byte":"4095"},{"start_byte":"16384","end_byte":"20479"},{"start_byte":"24576","end_byte":"28671"}],"name":"/testpool/4k/4k_file2","block_size":"4096"}},{"<metadata>:<0x1>":{"name":"<metadata>:<0x1>"}},{"testpool/testvol:<0x1>":{"object_type":"zvol","ranges":[{"start_byte":"4096000","end_byte":"4100095"},{"start_byte":"4505600","end_byte":"4509695"}],"name":"testpool/testvol:<0x1>","block_size":"4096"}}]
END
)
zpool status -vvjp | jq -c '.pools.testpool.errlist | map(del(.[].dataset)) | map(del(.[].object)) | sort' | check_status "$val"
val=$(cat << END
[{"/testpool/1m/1m_file":{"object_type":"ZFS plain file","ranges":[{"start_byte":1048576,"end_byte":2097151}],"name":"/testpool/1m/1m_file","block_size":1048576}},{"/testpool/4k/4k_file1":{"object_type":"ZFS plain file","ranges":[{"start_byte":0,"end_byte":4095}],"name":"/testpool/4k/4k_file1","block_size":4096}},{"/testpool/4k/4k_file2":{"object_type":"ZFS plain file","ranges":[{"start_byte":0,"end_byte":4095},{"start_byte":16384,"end_byte":20479},{"start_byte":24576,"end_byte":28671}],"name":"/testpool/4k/4k_file2","block_size":4096}},{"<metadata>:<0x1>":{"name":"<metadata>:<0x1>"}},{"testpool/testvol:<0x1>":{"object_type":"zvol","ranges":[{"start_byte":4096000,"end_byte":4100095},{"start_byte":4505600,"end_byte":4509695}],"name":"testpool/testvol:<0x1>","block_size":4096}}]
END
)
zpool status -vvjp --json-int | jq -c '.pools.testpool.errlist | map(del(.[].dataset)) | map(del(.[].object)) | sort' | check_status "$val"
# Clear the error log from our pool
log_must zpool scrub $TESTPOOL
log_must zpool clear $TESTPOOL
log_must wait_scrubbed $TESTPOOL
log_pass "zpool status error log output is correct"