diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index c2147c8f4a..44d1478ba0 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -112,6 +112,7 @@ static int zfs_do_hold(int argc, char **argv); static int zfs_do_holds(int argc, char **argv); static int zfs_do_release(int argc, char **argv); static int zfs_do_diff(int argc, char **argv); +static int zfs_do_scrub(int argc, char **argv); static int zfs_do_bookmark(int argc, char **argv); static int zfs_do_channel_program(int argc, char **argv); static int zfs_do_load_key(int argc, char **argv); @@ -181,6 +182,7 @@ typedef enum { HELP_HOLDS, HELP_RELEASE, HELP_DIFF, + HELP_SCRUB, HELP_BOOKMARK, HELP_CHANNEL_PROGRAM, HELP_LOAD_KEY, @@ -253,6 +255,7 @@ static zfs_command_t command_table[] = { { "holds", zfs_do_holds, HELP_HOLDS }, { "release", zfs_do_release, HELP_RELEASE }, { "diff", zfs_do_diff, HELP_DIFF }, + { "scrub", zfs_do_scrub, HELP_SCRUB }, { "load-key", zfs_do_load_key, HELP_LOAD_KEY }, { "unload-key", zfs_do_unload_key, HELP_UNLOAD_KEY }, { "change-key", zfs_do_change_key, HELP_CHANGE_KEY }, @@ -400,6 +403,8 @@ get_usage(zfs_help_t idx) case HELP_DIFF: return (gettext("\tdiff [-FHth] " "[snapshot|filesystem]\n")); + case HELP_SCRUB: + return (gettext("\tscrub \n")); case HELP_BOOKMARK: return (gettext("\tbookmark " "\n")); @@ -7879,6 +7884,98 @@ out: return (err != 0); } +static int +scrub_single_file(zfs_handle_t *zhp, void *data) +{ + struct stat64 *filestat = (struct stat64 *)data; + struct stat64 mountstat; + char mountpoint[ZFS_MAXPROPLEN]; + int err; + + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint, + sizeof (mountpoint), NULL, NULL, 0, B_FALSE) != 0) { + return (0); + } + + if (stat64(mountpoint, &mountstat) < 0) { + return (0); + } + + if (mountstat.st_dev != filestat->st_dev) { + return (0); + } + + nvlist_t *args = fnvlist_alloc(); + fnvlist_add_string(args, "dataset", zfs_get_name(zhp)); + fnvlist_add_uint64(args, "object", filestat->st_ino); + + err = lzc_scrub(ZFS_IOC_POOL_SCRUB_FILE, zfs_get_pool_name(zhp), + args, NULL); + + fnvlist_free(args); + + return (err != 0 ? 2 : 1); +} + +static int +find_dataset_and_scrub(zfs_handle_t *zhp, void *data) +{ + int err; + + err = scrub_single_file(zhp, data); + if (err == 0) { + err = zfs_iter_filesystems_v2(zhp, 0, find_dataset_and_scrub, + data); + } + zfs_close(zhp); + return (err); +} + +/* + * zfs scrub + * + * Scrubs single file. + */ +static int +zfs_do_scrub(int argc, char **argv) +{ + char *filetoscrub = NULL; + struct stat64 filestat; + int err = 0; + + if (argc < 1) { + (void) fprintf(stderr, + gettext("must provide a file to scrub\n")); + usage(B_FALSE); + } + + if (argc > 2) { + (void) fprintf(stderr, gettext("too many arguments\n")); + usage(B_FALSE); + } + + filetoscrub = argv[1]; + if (stat64(filetoscrub, &filestat) < 0) { + (void) fprintf(stderr, + gettext("must provide a file to scrub\n")); + usage(B_FALSE); + } + + if (S_ISREG(filestat.st_mode) == 0) { + (void) fprintf(stderr, + gettext("scrub works only on regular files\n")); + usage(B_FALSE); + } + + err = zfs_iter_root(g_zfs, find_dataset_and_scrub, &filestat); + if (err == 0) { + (void) fprintf(stderr, + gettext("unable to scrub file %s\n"), filetoscrub); + } + + return (err == 1 ? 0 : -1); +} + /* * zfs bookmark | * diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index 025567e218..bc9f5fc18b 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -1506,6 +1506,7 @@ typedef enum zfs_ioc { ZFS_IOC_VDEV_GET_PROPS, /* 0x5a55 */ ZFS_IOC_VDEV_SET_PROPS, /* 0x5a56 */ ZFS_IOC_POOL_SCRUB, /* 0x5a57 */ + ZFS_IOC_POOL_SCRUB_FILE, /* 0x5a58 */ /* * Per-platform (Optional) - 8/128 numbers reserved. diff --git a/man/man8/zfs-scrub.8 b/man/man8/zfs-scrub.8 new file mode 100644 index 0000000000..98bace5c70 --- /dev/null +++ b/man/man8/zfs-scrub.8 @@ -0,0 +1,53 @@ +.\" +.\" 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 https://opensource.org/licenses/CDDL-1.0. +.\" 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 +.\" +.\" Copyright 2024 Klara, Inc. +.\" Copyright 2024 Mariusz Zaborski +.\" +.Dd February 21, 2024 +.Dt ZFS-SCRUB 8 +.Os +. +.Sh NAME +.Nm zfs-scrub +.Nd run scrub of file in ZFS storage pools +.Sh SYNOPSIS +.Nm zfs +.Cm scrub +.Ar filename Ns +. +.Sh DESCRIPTION +Runs a scrub for a single file. +The scrub examines all data associated with a given file. +For replicated +.Pq mirror, raidz, or draid +devices, ZFS automatically repairs any damage discovered during the scrub. +The +.Nm zpool Cm status +command reports the progress of the scrub and summarizes the results of the +scrub upon completion. +For more details about scrubing, refer +.Xr zpool 8 . +. +.Pp +ZFS allows only on scrub process at a time (pool scrub or file scrub). +.Sh SEE ALSO +.Xr zpool-scrub 8 , +.Xr zpool-status 8 diff --git a/man/man8/zfs.8 b/man/man8/zfs.8 index dd578cb74a..d709346bf1 100644 --- a/man/man8/zfs.8 +++ b/man/man8/zfs.8 @@ -275,6 +275,12 @@ Delegate permissions on the specified filesystem or volume. Remove delegated permissions on the specified filesystem or volume. .El . +.Ss Maintenance +.Bl -tag -width "" +.It Xr zfs-scrub 8 +Runs a scrub of single file. +.El +. .Ss Encryption .Bl -tag -width "" .It Xr zfs-change-key 8 @@ -818,6 +824,7 @@ don't wait. .Xr zfs-release 8 , .Xr zfs-rename 8 , .Xr zfs-rollback 8 , +.Xr zfs-scrub 8 , .Xr zfs-send 8 , .Xr zfs-set 8 , .Xr zfs-share 8 , diff --git a/man/man8/zpool-scrub.8 b/man/man8/zpool-scrub.8 index 03f3ad4991..031abd0c00 100644 --- a/man/man8/zpool-scrub.8 +++ b/man/man8/zpool-scrub.8 @@ -154,6 +154,7 @@ timer units are provided. . .Sh SEE ALSO .Xr systemd.timer 5 , +.Xr zfs-scrub 8 , .Xr zpool-iostat 8 , .Xr zpool-resilver 8 , .Xr zpool-status 8 diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index b2b06881bd..e75093b6c4 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -201,6 +201,7 @@ #include #include #include +#include #include #include #include @@ -1726,6 +1727,131 @@ zfs_ioc_pool_scrub(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl) return (error); } +/* + * inputs: + * dataset name of dataset + * object object number + */ +static const zfs_ioc_key_t zfs_keys_pool_scrub_file[] = { + {"dataset", DATA_TYPE_STRING, 0}, + {"object", DATA_TYPE_UINT64, 0}, +}; + +static void +scrub_file_traverse(spa_t *spa, blkptr_t *bp, const zbookmark_phys_t *zb) +{ + uint64_t blk_birth = bp->blk_birth; + + if (blk_birth == 0) + return; + + spa_log_error(spa, zb, &blk_birth); + if (BP_GET_LEVEL(bp) > 0 && !BP_IS_HOLE(bp)) { + arc_flags_t flags = ARC_FLAG_WAIT; + int i; + blkptr_t *cbp; + int epb = BP_GET_LSIZE(bp) >> SPA_BLKPTRSHIFT; + arc_buf_t *buf; + + if (arc_read(NULL, spa, bp, arc_getbuf_func, &buf, + ZIO_PRIORITY_ASYNC_READ, ZIO_FLAG_CANFAIL, &flags, zb)) + return; + + /* recursively visit blocks below this */ + cbp = buf->b_data; + for (i = 0; i < epb; i++, cbp++) { + zbookmark_phys_t czb; + + SET_BOOKMARK(&czb, zb->zb_objset, zb->zb_object, + zb->zb_level - 1, + zb->zb_blkid * epb + i); + + scrub_file_traverse(spa, cbp, &czb); + } + } +} + +static int +zfs_ioc_pool_scrub_file(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl) +{ + spa_t *spa; + int error; + const char *dataset; + uint64_t object; + objset_t *os; + dmu_object_info_t doi; + zbookmark_phys_t zb; + dmu_buf_t *db = NULL; + dnode_t *dn; + + if (nvlist_lookup_string(innvl, "dataset", &dataset) != 0) + return (SET_ERROR(EINVAL)); + if (nvlist_lookup_uint64(innvl, "object", &object) != 0) + return (SET_ERROR(EINVAL)); + + error = dmu_objset_hold(dataset, FTAG, &os); + if (error != 0) { + return (error); + } + dsl_dataset_long_hold(dmu_objset_ds(os), FTAG); + spa = dmu_objset_spa(os); + + if (!spa_feature_is_enabled(spa, SPA_FEATURE_HEAD_ERRLOG)) { + dsl_pool_rele(dmu_objset_pool(os), FTAG); + dsl_dataset_long_rele(dmu_objset_ds(os), FTAG); + dsl_dataset_rele_flags(dmu_objset_ds(os), 0, FTAG); + return (SET_ERROR(ENOTSUP)); + } + + error = dmu_object_info(os, object, &doi); + if (error != 0) { + dsl_pool_rele(dmu_objset_pool(os), FTAG); + dsl_dataset_long_rele(dmu_objset_ds(os), FTAG); + dsl_dataset_rele_flags(dmu_objset_ds(os), 0, FTAG); + return (error); + } + if (doi.doi_type != DMU_OT_PLAIN_FILE_CONTENTS) { + dsl_pool_rele(dmu_objset_pool(os), FTAG); + dsl_dataset_long_rele(dmu_objset_ds(os), FTAG); + dsl_dataset_rele_flags(dmu_objset_ds(os), 0, FTAG); + return (EINVAL); + } + + error = dmu_bonus_hold(os, object, FTAG, &db); + if (error != 0) { + dsl_pool_rele(dmu_objset_pool(os), FTAG); + dsl_dataset_long_rele(dmu_objset_ds(os), FTAG); + dsl_dataset_rele_flags(dmu_objset_ds(os), 0, FTAG); + return (error); + } + + dn = DB_DNODE((dmu_buf_impl_t *)db); + + zb.zb_objset = dmu_objset_id(os); + zb.zb_object = object; + + dmu_buf_rele(db, FTAG); + + for (int j = 0; j < dn->dn_phys->dn_nblkptr; j++) { + zb.zb_blkid = j; + zb.zb_level = BP_GET_LEVEL(&dn->dn_phys->dn_blkptr[j]); + scrub_file_traverse(spa, &dn->dn_phys->dn_blkptr[j], &zb); + } + + dsl_pool_rele(dmu_objset_pool(os), FTAG); + dsl_dataset_long_rele(dmu_objset_ds(os), FTAG); + dsl_dataset_rele_flags(dmu_objset_ds(os), 0, FTAG); + + txg_wait_synced(spa_get_dsl(spa), 0); + + if ((error = spa_open(poolname, &spa, FTAG)) != 0) + return (error); + error = spa_scan(spa, POOL_SCAN_ERRORSCRUB); + spa_close(spa, FTAG); + + return (error); +} + static int zfs_ioc_pool_freeze(zfs_cmd_t *zc) { @@ -7280,6 +7406,11 @@ zfs_ioctl_init(void) POOL_CHECK_NONE, B_TRUE, B_TRUE, zfs_keys_pool_scrub, ARRAY_SIZE(zfs_keys_pool_scrub)); + zfs_ioctl_register("scrub_file", ZFS_IOC_POOL_SCRUB_FILE, + zfs_ioc_pool_scrub_file, zfs_secpolicy_config, POOL_NAME, + POOL_CHECK_NONE, B_TRUE, B_TRUE, + zfs_keys_pool_scrub_file, ARRAY_SIZE(zfs_keys_pool_scrub_file)); + /* IOCTLS that use the legacy function signature */ zfs_ioctl_register_legacy(ZFS_IOC_POOL_FREEZE, zfs_ioc_pool_freeze, diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 502b4de2ba..a11a09789b 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -296,6 +296,10 @@ tests = ['zfs_rollback_001_pos', 'zfs_rollback_002_pos', 'zfs_rollback_003_neg', 'zfs_rollback_004_neg'] tags = ['functional', 'cli_root', 'zfs_rollback'] +[tests/functional/cli_root/zfs_scrub] +tests = ['zfs_error_scrub_001_pos', 'zfs_error_scrub_002_pos'] +tags = ['functional', 'cli_root', 'zfs_scrub'] + [tests/functional/cli_root/zfs_send] tests = ['zfs_send_001_pos', 'zfs_send_002_pos', 'zfs_send_003_pos', 'zfs_send_004_neg', 'zfs_send_005_pos', 'zfs_send_006_pos', diff --git a/tests/zfs-tests/include/libtest.shlib b/tests/zfs-tests/include/libtest.shlib index dfab48d2cd..fbeb25823d 100644 --- a/tests/zfs-tests/include/libtest.shlib +++ b/tests/zfs-tests/include/libtest.shlib @@ -1961,6 +1961,7 @@ function check_pool_status # pool token keyword # # The following functions are instance of check_pool_status() +# if_pool_without_errors - to check if the pool has errors # is_pool_resilvering - to check if the pool resilver is in progress # is_pool_resilvered - to check if the pool resilver is completed # is_pool_scrubbing - to check if the pool scrub is in progress @@ -1972,6 +1973,13 @@ function check_pool_status # pool token keyword # is_pool_discarding - to check if the pool checkpoint is being discarded # is_pool_replacing - to check if the pool is performing a replacement # + +function is_pool_without_errors #pool +{ + check_pool_status "$1" "errors" "No known data errors" $2 + return $? +} + function is_pool_resilvering #pool { check_pool_status "$1" "scan" \ diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index fe9c921087..ad7d262090 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -851,6 +851,10 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/cli_root/zfs_rollback/zfs_rollback_002_pos.ksh \ functional/cli_root/zfs_rollback/zfs_rollback_003_neg.ksh \ functional/cli_root/zfs_rollback/zfs_rollback_004_neg.ksh \ + functional/cli_root/zfs_scrub/cleanup.ksh \ + functional/cli_root/zfs_scrub/setup.ksh \ + functional/cli_root/zfs_scrub/zfs_error_scrub_001_pos.ksh \ + functional/cli_root/zfs_scrub/zfs_error_scrub_002_pos.ksh \ functional/cli_root/zfs_send/cleanup.ksh \ functional/cli_root/zfs_send/setup.ksh \ functional/cli_root/zfs_send/zfs_send_001_pos.ksh \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_scrub/cleanup.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_scrub/cleanup.ksh new file mode 100755 index 0000000000..51f4a1717a --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_scrub/cleanup.ksh @@ -0,0 +1,21 @@ +#!/bin/ksh -p +# +# +# 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 +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2024 Klara, Inc. +# Copyright 2024 Mariusz Zaborski +# + +. $STF_SUITE/include/libtest.shlib + +default_cleanup diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_scrub/setup.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_scrub/setup.ksh new file mode 100755 index 0000000000..cb6ea44be3 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_scrub/setup.ksh @@ -0,0 +1,22 @@ +#!/bin/ksh -p +# +# +# 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 +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2024 Klara, Inc. +# Copyright 2024 Mariusz Zaborski +# + +. $STF_SUITE/include/libtest.shlib +DISK=${DISKS%% *} + +default_setup $DISK diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_scrub/zfs_error_scrub_001_pos.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_scrub/zfs_error_scrub_001_pos.ksh new file mode 100755 index 0000000000..adf406fabf --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_scrub/zfs_error_scrub_001_pos.ksh @@ -0,0 +1,89 @@ +#!/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 +# +# Copyright 2024 Klara, Inc. +# Copyright 2024 Mariusz Zaborski +# + +. $STF_SUITE/include/libtest.shlib + +# +# DESCRIPTION: +# Verify scrubbing a single file feature. One file uses indirect +# blocks, and second doesn't. +# +# STRATEGY: +# 1. Create a pool. +# 2. Create a 10MB file in it (not using indirect blocks). +# 3. Create a 1GB file in it (using indirect blocks). +# 4. Inject write errors on the small file. +# 5. Start a scrub on a 10MB file. +# 6. Verify that the file was reported as a buggy. +# 7. Clear errors. +# 8. Inject write errors on the small file. +# 9. Start a scrub on a 1GB file. +# 10. Verify that the file was reported as a buggy. +# + +verify_runnable "global" + +function cleanup +{ + log_must zinject -c all + rm -f /$TESTPOOL/10m_file + log_must zpool clear $TESTPOOL +} + +log_onexit cleanup + +log_assert "Verify small and large file scrub" + +# To automatically determine the pool in which a file resides, access to the +# list of pools is required. +unset __ZFS_POOL_EXCLUDE +export __ZFS_POOL_RESTRICT="$TESTPOOL" + +log_must fio --rw=write --name=job --size=10M --filename=/$TESTPOOL/10m_file +log_must fio --rw=write --name=job --size=1G --filename=/$TESTPOOL/1G_file + +log_must sync_pool $TESTPOOL + +log_must zinject -t data -e checksum -f 100 -am /$TESTPOOL/10m_file + +# check that small file is faulty +log_must is_pool_without_errors $TESTPOOL true +log_must zfs scrub /$TESTPOOL/10m_file +log_must zpool wait -t scrub $TESTPOOL +log_mustnot is_pool_without_errors $TESTPOOL true + +# clear errors on small file +log_must zinject -c all +log_must zfs scrub /$TESTPOOL/10m_file +log_must zpool wait -t scrub $TESTPOOL + +# check that large file is faulty +log_must zinject -t data -e checksum -f 100 -am /$TESTPOOL/1G_file +log_must is_pool_without_errors $TESTPOOL true +log_must zfs scrub /$TESTPOOL/1G_file +log_must zpool wait -t scrub $TESTPOOL +log_mustnot is_pool_without_errors $TESTPOOL true + +log_pass "Verified file scrub shows expected status." diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_scrub/zfs_error_scrub_002_pos.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_scrub/zfs_error_scrub_002_pos.ksh new file mode 100755 index 0000000000..bf9994f128 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_scrub/zfs_error_scrub_002_pos.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 +# +# Copyright 2024 Klara, Inc. +# Copyright 2024 Mariusz Zaborski +# + +. $STF_SUITE/include/libtest.shlib + +# +# DESCRIPTION: +# Scrub a single file and self-healing using additional copies. +# +# STRATEGY: +# 1. Create a dataset with copies=3 +# 2. Write a file to the dataset +# 3. zinject errors into the first and second DVAs of that file +# 4. Scrub single file and verify the scrub repaired all errors +# 5. Remove the zinject handler +# + +verify_runnable "global" + +function cleanup +{ + log_must zinject -c all + destroy_dataset $TESTPOOL/$TESTFS2 + log_must zpool clear $TESTPOOL +} + +log_onexit cleanup + +log_assert "Verify scrubing a single file with additional copies" + +# To automatically determine the pool in which a file resides, access to the +# list of pools is required. +unset __ZFS_POOL_EXCLUDE +export __ZFS_POOL_RESTRICT="$TESTPOOL" + +log_must zfs create -o copies=3 $TESTPOOL/$TESTFS2 +typeset mntpnt=$(get_prop mountpoint $TESTPOOL/$TESTFS2) + +log_must fio --rw=write --name=job --size=10M --filename=$mntpnt/file +log_must sync_pool $TESTPOOL + +log_must zinject -a -t data -C 0,1 -e io $mntpnt/file + +log_must zfs scrub $mntpnt/file +log_must is_pool_without_errors $TESTPOOL true + +log_must fio --rw=write --name=job --size=10M --filename=$mntpnt/file +log_must is_pool_without_errors $TESTPOOL true + +log_pass "Verified file scrub shows expected status."