mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
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:
1 parent
3a67db3143
commit
e903655c50
17 files changed
+886
-98
No files matched your search
+75
-4
@@ -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
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
+173
@@ -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"
|
||||
Reference in new issue
Block a user