From 1512020604cd3eafdff869c229352556a18be332 Mon Sep 17 00:00:00 2001 From: Mariusz Zaborski Date: Wed, 25 Oct 2023 09:43:51 +0000 Subject: [PATCH] Support single files scrubbing This introduces the functionality of scrubbing data from files. Originally, the `errloglist` structure was utilized for tracking error blocks identified during scrub operations. Same structure can be used record blocks from specific files. By generating these records we can simbply scurb it. This allows scrubbing a single file, leveraging existing infrastructure. Sponsored-by: Klara, Inc. Sponsored-by: Wasabi Technology, Inc. Signed-off-by: Mariusz Zaborski --- cmd/zfs/zfs_main.c | 97 +++++++++++++ include/sys/fs/zfs.h | 1 + man/man8/zfs-scrub.8 | 53 +++++++ man/man8/zfs.8 | 7 + man/man8/zpool-scrub.8 | 1 + module/zfs/zfs_ioctl.c | 131 ++++++++++++++++++ tests/runfiles/common.run | 4 + tests/zfs-tests/include/libtest.shlib | 8 ++ tests/zfs-tests/tests/Makefile.am | 4 + .../functional/cli_root/zfs_scrub/cleanup.ksh | 21 +++ .../functional/cli_root/zfs_scrub/setup.ksh | 22 +++ .../zfs_scrub/zfs_error_scrub_001_pos.ksh | 89 ++++++++++++ .../zfs_scrub/zfs_error_scrub_002_pos.ksh | 72 ++++++++++ 13 files changed, 510 insertions(+) create mode 100644 man/man8/zfs-scrub.8 create mode 100755 tests/zfs-tests/tests/functional/cli_root/zfs_scrub/cleanup.ksh create mode 100755 tests/zfs-tests/tests/functional/cli_root/zfs_scrub/setup.ksh create mode 100755 tests/zfs-tests/tests/functional/cli_root/zfs_scrub/zfs_error_scrub_001_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/cli_root/zfs_scrub/zfs_error_scrub_002_pos.ksh diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index c2147c8f4acd..44d1478ba053 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 @@ zfs_do_diff(int argc, char **argv) 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 025567e2183f..bc9f5fc18b27 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 000000000000..98bace5c70ec --- /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 dd578cb74aac..d709346bf1ae 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 03f3ad4991f9..031abd0c0046 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 b2b06881bdd4..e75093b6c4dd 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 502b4de2bae9..a11a09789b04 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 dfab48d2cdaf..fbeb25823df7 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 fe9c92108725..ad7d26209010 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 000000000000..51f4a1717a68 --- /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 000000000000..cb6ea44be319 --- /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 000000000000..adf406fabfc8 --- /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 000000000000..bf9994f12835 --- /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."