diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index 0ac8c2a5e4ee..c36df633e830 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -7349,16 +7349,72 @@ print_error_log(zpool_handle_t *zhp) pathname = safe_malloc(len); elem = NULL; 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); + uint64_t data_block_size, indirect_block_size; + unsigned int error_count_blkids, error_count_levels; + uint64_t *block_ids; + int64_t *indirect_levels; + + nvlist_t *object_nv = fnvpair_value_nvlist(elem); + uint64_t dsobj = fnvlist_lookup_uint64(object_nv, + ZPOOL_ERR_DATASET); + uint64_t obj = fnvlist_lookup_uint64(object_nv, + ZPOOL_ERR_OBJECT); zpool_obj_to_path(zhp, dsobj, obj, pathname, len); - (void) printf("%7s %s\n", "", pathname); + + if (zpool_get_block_size(zhp, dsobj, obj, &data_block_size, + &indirect_block_size) == 0 && nvlist_lookup_uint64_array( + object_nv, ZPOOL_ERR_BLOCKID, &block_ids, + &error_count_blkids) == 0 && nvlist_lookup_int64_array( + object_nv, ZPOOL_ERR_LEVEL, &indirect_levels, + &error_count_levels) == 0) { + + ASSERT(error_count_blkids == error_count_levels); + + uint64_t blkptr_size = (uint64_t)sizeof (blkptr_t); + uint8_t blkptr_size_shift = 0; + uint8_t indirect_block_shift = 0; + uint64_t min_offset_blk = UINT64_MAX; + uint64_t max_offset_blk = 0; + while (indirect_block_size > 1) { + indirect_block_size = indirect_block_size >> 1; + indirect_block_shift++; + } + + while (blkptr_size > 1) { + blkptr_size = blkptr_size >> 1; + blkptr_size_shift++; + } + /* + * Iterate through the error blockids and find minimum + * and maximum offset. + */ + for (int i = 0; i < error_count_blkids; i++) { + uint64_t start_offset = block_ids[i] << + ((indirect_block_shift - + blkptr_size_shift) * indirect_levels[i]); + uint64_t end_offset = (block_ids[i] + 1) << + ((indirect_block_shift - + blkptr_size_shift) * indirect_levels[i]); + min_offset_blk = + MIN(min_offset_blk, start_offset); + max_offset_blk = + MAX(max_offset_blk, end_offset); + } + uint64_t min_offset_byte = + data_block_size * min_offset_blk; + uint64_t max_offset_byte = + data_block_size * max_offset_blk; + char size_buf[16]; + nicenum(data_block_size, size_buf, sizeof (size_buf)); + (void) printf("%7s %s: errors in %u blocks " + "(size %s), between offset %#llx and %#llx " + "bytes\n", "", pathname, error_count_blkids, + size_buf, (u_longlong_t)min_offset_byte, + (u_longlong_t)max_offset_byte); + } else { + (void) printf("%7s %s %s\n", "", pathname, " (can not " + "determine error offset)"); + } } free(pathname); nvlist_free(nverrlist); diff --git a/include/libzfs.h b/include/libzfs.h index 8e9f6fb3fc1b..8818baca4037 100644 --- a/include/libzfs.h +++ b/include/libzfs.h @@ -440,6 +440,8 @@ extern int zpool_events_clear(libzfs_handle_t *, int *); extern int zpool_events_seek(libzfs_handle_t *, uint64_t, int); extern void zpool_obj_to_path(zpool_handle_t *, uint64_t, uint64_t, char *, size_t len); +extern int zpool_get_block_size(zpool_handle_t *, uint64_t, uint64_t, + uint64_t *, uint64_t *); extern int zfs_ioctl(libzfs_handle_t *, int, struct zfs_cmd *); extern int zpool_get_physpath(zpool_handle_t *, char *, size_t); extern void zpool_explain_recover(libzfs_handle_t *, const char *, int, diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index 1474e1f049d8..0eb4bdb3856c 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -675,6 +675,7 @@ typedef struct zpool_load_policy { #define ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH "vdev_enc_sysfs_path" #define ZPOOL_CONFIG_WHOLE_DISK "whole_disk" +#define ZPOOL_CONFIG_ERRLIST "error_list" #define ZPOOL_CONFIG_ERRCOUNT "error_count" #define ZPOOL_CONFIG_NOT_PRESENT "not_present" #define ZPOOL_CONFIG_SPARES "spares" @@ -1359,6 +1360,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_BLOCKID "block id" #define HIS_MAX_RECORD_LEN (MAXPATHLEN + MAXPATHLEN + 1) diff --git a/include/sys/zfs_stat.h b/include/sys/zfs_stat.h index 465aefaa2063..36367a5d6956 100644 --- a/include/sys/zfs_stat.h +++ b/include/sys/zfs_stat.h @@ -44,6 +44,8 @@ typedef struct zfs_stat { uint64_t zs_mode; uint64_t zs_links; uint64_t zs_ctime[2]; + uint64_t zs_data_block_size; + uint64_t zs_indirect_block_size; } zfs_stat_t; extern int zfs_obj_to_stats(objset_t *osp, uint64_t obj, zfs_stat_t *sb, diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c index 7f3ec5d0d4fa..c30ae81af8aa 100644 --- a/lib/libzfs/libzfs_pool.c +++ b/lib/libzfs/libzfs_pool.c @@ -3956,9 +3956,33 @@ zbookmark_mem_compare(const void *a, const void *b) return (memcmp(a, b, sizeof (zbookmark_phys_t))); } +/* + * Either adds both the arrays in the nvlist or none. + */ +static void +add_error_block_ids_and_levels(nvlist_t *object_nv, uint64_t *object_block_ids, + int64_t *object_indirect_levels, uint64_t object_errors) +{ + int err = nvlist_add_uint64_array(object_nv, ZPOOL_ERR_BLOCKID, + object_block_ids, object_errors); + + if (err != 0) + return; + + err = nvlist_add_int64_array(object_nv, ZPOOL_ERR_LEVEL, + object_indirect_levels, object_errors); + + if (err != 0) { + nvlist_remove(object_nv, ZPOOL_ERR_BLOCKID, + DATA_TYPE_UINT64_ARRAY); + } +} + /* * Retrieve the persistent error log, uniquify the members, and return to the - * caller. + * caller. Return nvlist_t is a list of corrupted objects. Each entity in + * nvlist_t contains four elements: dataset number, object number, an array of + * corrupted blockids and another array of corresponding level. */ int zpool_get_errlog(zpool_handle_t *zhp, nvlist_t **nverrlistp) @@ -3967,7 +3991,7 @@ zpool_get_errlog(zpool_handle_t *zhp, nvlist_t **nverrlistp) libzfs_handle_t *hdl = zhp->zpool_hdl; uint64_t count; zbookmark_phys_t *zb = NULL; - int i; + uint64_t i; /* * Retrieve the raw error list from the kernel. If the number of errors @@ -4019,39 +4043,79 @@ zpool_get_errlog(zpool_handle_t *zhp, nvlist_t **nverrlistp) verify(nvlist_alloc(nverrlistp, 0, KM_SLEEP) == 0); /* - * Fill in the nverrlistp with nvlist's of dataset and object numbers. + * Fill in the nverrlistp with nvlist's of dataset number, object number + * level position, and block id. */ + nvlist_t *object_nv; + boolean_t mem_alloc_failed = B_FALSE; + + uint64_t *object_block_ids = zfs_alloc(zhp->zpool_hdl, + count * sizeof (uint64_t)); + int64_t *object_indirect_levels = zfs_alloc(zhp->zpool_hdl, + count * sizeof (int64_t)); + if (object_block_ids == NULL || object_indirect_levels == NULL) { + mem_alloc_failed = B_TRUE; + } + uint64_t object_errors = 0; for (i = 0; i < count; i++) { - nvlist_t *nv; + if (i == 0 || zb[i - 1].zb_objset != zb[i].zb_objset || + zb[i - 1].zb_object != zb[i].zb_object) { + if (i != 0) { + /* If no memory is available do not add this. */ + if (!mem_alloc_failed) { + add_error_block_ids_and_levels( + object_nv, + object_block_ids, + object_indirect_levels, + object_errors); + } - /* 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) - continue; + if (nvlist_add_nvlist(*nverrlistp, + ZPOOL_CONFIG_ERRLIST, object_nv) != 0) + goto nomem; + nvlist_free(object_nv); + object_errors = 0; + } - 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; + if (nvlist_alloc(&object_nv, NV_UNIQUE_NAME, + KM_SLEEP) != 0) + goto nomem; + if (nvlist_add_uint64(object_nv, ZPOOL_ERR_DATASET, + zb[i].zb_objset) != 0) + goto nomem; + if (nvlist_add_uint64(object_nv, ZPOOL_ERR_OBJECT, + zb[i].zb_object) != 0) + goto nomem; } - if (nvlist_add_uint64(nv, ZPOOL_ERR_OBJECT, - zb[i].zb_object) != 0) { - nvlist_free(nv); - goto nomem; + object_errors++; + if (mem_alloc_failed) + continue; + object_block_ids[object_errors] = zb[i].zb_blkid; + object_indirect_levels[object_errors] = zb[i].zb_level; + } + if (object_errors > 0) { + /* If no memory is available do not add this. */ + if (!mem_alloc_failed) { + add_error_block_ids_and_levels(object_nv, + object_block_ids, object_indirect_levels, + object_errors); } - if (nvlist_add_nvlist(*nverrlistp, "ejk", nv) != 0) { - nvlist_free(nv); + + if (nvlist_add_nvlist(*nverrlistp, ZPOOL_CONFIG_ERRLIST, + object_nv) != 0) goto nomem; - } - nvlist_free(nv); + nvlist_free(object_nv); + object_errors = 0; } - + free(object_block_ids); + free(object_indirect_levels); free((void *)(uintptr_t)zc.zc_nvlist_dst); return (0); nomem: + nvlist_free(object_nv); + free(object_block_ids); + free(object_indirect_levels); free((void *)(uintptr_t)zc.zc_nvlist_dst); return (no_memory(zhp->zpool_hdl)); } @@ -4390,6 +4454,33 @@ zpool_obj_to_path(zpool_handle_t *zhp, uint64_t dsobj, uint64_t obj, free(mntpnt); } +/* + * Given an dataset object number, return data block and indirect block size. + */ +int +zpool_get_block_size(zpool_handle_t *zhp, uint64_t dsobj, uint64_t obj, + uint64_t *data_blk_size, uint64_t *indrt_blk_size) +{ + zfs_cmd_t zc = {"\0"}; + /* get the dataset's name */ + zc.zc_obj = dsobj; + (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name)); + int error = ioctl(zhp->zpool_hdl->libzfs_fd, + ZFS_IOC_DSOBJ_TO_DSNAME, &zc); + if (error != 0) { + return (error); + } + /* get data block and indirect block size */ + (void) strlcpy(zc.zc_name, zc.zc_value, sizeof (zc.zc_name)); + zc.zc_obj = obj; + error = ioctl(zhp->zpool_hdl->libzfs_fd, ZFS_IOC_OBJ_TO_STATS, &zc); + if (error == 0) { + *data_blk_size = zc.zc_stat.zs_data_block_size; + *indrt_blk_size = zc.zc_stat.zs_indirect_block_size; + } + return (error); +} + /* * Wait while the specified activity is in progress in the pool. */ diff --git a/module/os/linux/zfs/zfs_znode.c b/module/os/linux/zfs/zfs_znode.c index 45f19785d4ec..999ce0e49e03 100644 --- a/module/os/linux/zfs/zfs_znode.c +++ b/module/os/linux/zfs/zfs_znode.c @@ -2087,6 +2087,11 @@ zfs_obj_to_stats_impl(sa_handle_t *hdl, sa_attr_type_t *sa_table, sa_bulk_attr_t bulk[4]; int count = 0; + dmu_object_info_t doi; + sa_object_info(hdl, &doi); + sb->zs_data_block_size = doi.doi_data_block_size; + sb->zs_indirect_block_size = doi.doi_metadata_block_size; + SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_MODE], NULL, &sb->zs_mode, sizeof (sb->zs_mode)); SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_GEN], NULL, diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 89e4492ddb99..a827a1aeda44 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -437,7 +437,7 @@ tests = ['zpool_split_cliargs', 'zpool_split_devices', tags = ['functional', 'cli_root', 'zpool_split'] [tests/functional/cli_root/zpool_status] -tests = ['zpool_status_001_pos', 'zpool_status_002_pos'] +tests = ['zpool_status_001_pos', 'zpool_status_002_pos', 'zpool_status_-v'] tags = ['functional', 'cli_root', 'zpool_status'] [tests/functional/cli_root/zpool_sync] diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_status/Makefile.am b/tests/zfs-tests/tests/functional/cli_root/zpool_status/Makefile.am index beb59e3d066b..39e451fed945 100644 --- a/tests/zfs-tests/tests/functional/cli_root/zpool_status/Makefile.am +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_status/Makefile.am @@ -3,4 +3,5 @@ dist_pkgdata_SCRIPTS = \ setup.ksh \ cleanup.ksh \ zpool_status_001_pos.ksh \ - zpool_status_002_pos.ksh + zpool_status_002_pos.ksh \ + zpool_status_-v.ksh diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-v.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-v.ksh new file mode 100755 index 000000000000..6b52d2ab984c --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-v.ksh @@ -0,0 +1,72 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Use is subject to license terms. +# + +# +# Copyright (c) 2019 by Delphix. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib + +# +# DESCRIPTION: +# Verify correct output with 'zpool status -v' after corrupting a file +# +# STRATEGY: +# 1. Create a file +# 2. zinject checksum errors +# 3. Read the file +# 4. Verify we see "file corrupted" output in 'zpool status -v' +# + +verify_runnable "both" + + +log_assert "Verify correct 'zpool status -v' output with a corrupted file" + +log_must mkfile 10m $TESTDIR/10m_file +log_must mkfile 1m $TESTDIR/1m_file + +log_must zpool export $TESTPOOL +log_must zpool import $TESTPOOL + +log_must zinject -t data -e checksum -f 100 $TESTDIR/10m_file +log_must zinject -t data -e checksum -f 100 $TESTDIR/1m_file + +# Try to read the entire file. This should stop after the first 128k block +# of each file errors out. +cat $TESTDIR/*file || true + +# Try to read the 2nd megabyte of 10m_file +dd if=$TESTDIR/10m_file bs=1M skip=1 count=1 || true +dd if=$TESTDIR/1m_file bs=128K count=1 || true + +log_must zinject -c all + +# Look to see that both our files report errors +log_must eval "zpool status -v | grep '10m_file: errors'" +log_must eval "zpool status -v | grep '1m_file: errors'" + +log_pass "'zpool status -v' output is correct"