From 2ed07129cdab1ae67e08972e1323b4fa197ed724 Mon Sep 17 00:00:00 2001 From: Mariusz Zaborski Date: Fri, 29 Aug 2025 12:13:49 +0000 Subject: [PATCH 1/3] Simplify scrub options parser Sponsored-By: Wasabi Technology, Inc. Sponsored-By: Klara Inc. Signed-off-by: Mariusz Zaborski --- cmd/zpool/zpool_main.c | 96 +++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 43 deletions(-) diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index 237e558da65b..a9f8f663167c 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -8446,6 +8446,11 @@ date_string_to_sec(const char *timestr, boolean_t rounding) return (mktime(&tm) + adjustment); } +struct zpool_scrub_option { + char name; + boolean_t enabled; +}; + /* * zpool scrub [-e | -s | -p | -C | -E | -S] [-w] [-a | ...] * @@ -8463,27 +8468,27 @@ zpool_do_scrub(int argc, char **argv) { int c; scrub_cbdata_t cb; - boolean_t wait = B_FALSE; int error; cb.cb_type = POOL_SCAN_SCRUB; cb.cb_scrub_cmd = POOL_SCRUB_NORMAL; cb.cb_date_start = cb.cb_date_end = 0; - boolean_t is_error_scrub = B_FALSE; - boolean_t is_pause = B_FALSE; - boolean_t is_stop = B_FALSE; - boolean_t is_txg_continue = B_FALSE; - boolean_t scrub_all = B_FALSE; + struct zpool_scrub_option wait = {'w', B_FALSE}; + struct zpool_scrub_option is_error_scrub = {'e', B_FALSE}; + struct zpool_scrub_option is_pause = {'p', B_FALSE}; + struct zpool_scrub_option is_stop = {'s', B_FALSE}; + struct zpool_scrub_option is_txg_continue = {'C', B_FALSE}; + struct zpool_scrub_option scrub_all = {'a', B_FALSE}; /* check options */ while ((c = getopt(argc, argv, "aspweCE:S:")) != -1) { switch (c) { case 'a': - scrub_all = B_TRUE; + scrub_all.enabled = B_TRUE; break; case 'e': - is_error_scrub = B_TRUE; + is_error_scrub.enabled = B_TRUE; break; case 'E': /* @@ -8493,19 +8498,19 @@ zpool_do_scrub(int argc, char **argv) cb.cb_date_end = date_string_to_sec(optarg, B_TRUE); break; case 's': - is_stop = B_TRUE; + is_stop.enabled = B_TRUE; break; case 'S': cb.cb_date_start = date_string_to_sec(optarg, B_FALSE); break; case 'p': - is_pause = B_TRUE; + is_pause.enabled = B_TRUE; break; case 'w': - wait = B_TRUE; + wait.enabled = B_TRUE; break; case 'C': - is_txg_continue = B_TRUE; + is_txg_continue.enabled = B_TRUE; break; case '?': (void) fprintf(stderr, gettext("invalid option '%c'\n"), @@ -8514,37 +8519,42 @@ zpool_do_scrub(int argc, char **argv) } } - if (is_pause && is_stop) { - (void) fprintf(stderr, gettext("invalid option " - "combination: -s and -p are mutually exclusive\n")); - usage(B_FALSE); - } else if (is_pause && is_txg_continue) { - (void) fprintf(stderr, gettext("invalid option " - "combination: -p and -C are mutually exclusive\n")); - usage(B_FALSE); - } else if (is_stop && is_txg_continue) { - (void) fprintf(stderr, gettext("invalid option " - "combination: -s and -C are mutually exclusive\n")); - usage(B_FALSE); - } else if (is_error_scrub && is_txg_continue) { - (void) fprintf(stderr, gettext("invalid option " - "combination: -e and -C are mutually exclusive\n")); - usage(B_FALSE); - } else { - if (is_error_scrub) - cb.cb_type = POOL_SCAN_ERRORSCRUB; - - if (is_pause) { - cb.cb_scrub_cmd = POOL_SCRUB_PAUSE; - } else if (is_stop) { - cb.cb_type = POOL_SCAN_NONE; - } else if (is_txg_continue) { - cb.cb_scrub_cmd = POOL_SCRUB_FROM_LAST_TXG; - } else { - cb.cb_scrub_cmd = POOL_SCRUB_NORMAL; + struct { + struct zpool_scrub_option *op1; + struct zpool_scrub_option *op2; + } scrub_exclusive_options[] = { + {&is_stop, &is_pause}, + {&is_stop, &is_txg_continue}, + {&is_pause, &is_txg_continue}, + {&is_error_scrub, &is_txg_continue}, + }; + + for (int i = 0; i < sizeof (scrub_exclusive_options) / + sizeof (scrub_exclusive_options[0]); i++) { + if (scrub_exclusive_options[i].op1->enabled && + scrub_exclusive_options[i].op2->enabled) { + (void) fprintf(stderr, gettext("invalid option " + "combination: -%c and -%c are mutually " + "exclusive\n"), + scrub_exclusive_options[i].op1->name, + scrub_exclusive_options[i].op2->name); + usage(B_FALSE); } } + if (is_error_scrub.enabled) + cb.cb_type = POOL_SCAN_ERRORSCRUB; + + if (is_pause.enabled) { + cb.cb_scrub_cmd = POOL_SCRUB_PAUSE; + } else if (is_stop.enabled) { + cb.cb_type = POOL_SCAN_NONE; + } else if (is_txg_continue.enabled) { + cb.cb_scrub_cmd = POOL_SCRUB_FROM_LAST_TXG; + } else { + cb.cb_scrub_cmd = POOL_SCRUB_NORMAL; + } + if ((cb.cb_date_start != 0 || cb.cb_date_end != 0) && cb.cb_scrub_cmd != POOL_SCRUB_NORMAL) { (void) fprintf(stderr, gettext("invalid option combination: " @@ -8558,7 +8568,7 @@ zpool_do_scrub(int argc, char **argv) usage(B_FALSE); } - if (wait && (cb.cb_type == POOL_SCAN_NONE || + if (wait.enabled && (cb.cb_type == POOL_SCAN_NONE || cb.cb_scrub_cmd == POOL_SCRUB_PAUSE)) { (void) fprintf(stderr, gettext("invalid option combination: " "-w cannot be used with -p or -s\n")); @@ -8568,7 +8578,7 @@ zpool_do_scrub(int argc, char **argv) argc -= optind; argv += optind; - if (argc < 1 && !scrub_all) { + if (argc < 1 && !scrub_all.enabled) { (void) fprintf(stderr, gettext("missing pool name argument\n")); usage(B_FALSE); } @@ -8576,7 +8586,7 @@ zpool_do_scrub(int argc, char **argv) error = for_each_pool(argc, argv, B_TRUE, NULL, ZFS_TYPE_POOL, B_FALSE, scrub_callback, &cb); - if (wait && !error) { + if (wait.enabled && !error) { zpool_wait_activity_t act = ZPOOL_WAIT_SCRUB; error = for_each_pool(argc, argv, B_TRUE, NULL, ZFS_TYPE_POOL, B_FALSE, wait_callback, &act); From db6742d6f3c2474b09f63c121f7540489610de30 Mon Sep 17 00:00:00 2001 From: Mariusz Zaborski Date: Fri, 29 Aug 2025 12:26:03 +0000 Subject: [PATCH 2/3] Don't allow to do error scrub with stop or pause Sponsored-By: Wasabi Technology, Inc. Sponsored-By: Klara Inc. Signed-off-by: Mariusz Zaborski --- cmd/zpool/zpool_main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index a9f8f663167c..cc71de6a80cd 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -8525,7 +8525,9 @@ zpool_do_scrub(int argc, char **argv) } scrub_exclusive_options[] = { {&is_stop, &is_pause}, {&is_stop, &is_txg_continue}, + {&is_stop, &is_error_scrub}, {&is_pause, &is_txg_continue}, + {&is_pause, &is_error_scrub}, {&is_error_scrub, &is_txg_continue}, }; From d4de3cad5a5abbcf3920e7b194d8da6dda7b0b47 Mon Sep 17 00:00:00 2001 From: Mariusz Zaborski Date: Fri, 5 Sep 2025 11:14:13 +0000 Subject: [PATCH 3/3] scrub: add option to scrub only recent data Recent data is defined as the last known point in the TXG database, minus a user-defined time interval (default: 4h). This feature can be triggered using either of the following commands: `zpool clean -s` or `zpool scrub -R`. Sponsored-By: Wasabi Technology, Inc. Sponsored-By: Klara Inc. Signed-off-by: Mariusz Zaborski --- cmd/zpool/zpool_main.c | 164 +++++++++++------- include/os/freebsd/spl/sys/mod.h | 9 + include/sys/dsl_scan.h | 7 + include/sys/fs/zfs.h | 1 + include/zfs_crrd.h | 1 + lib/libuutil/libuutil.abi | 105 +---------- lib/libzfs/libzfs.abi | 161 ++++------------- lib/libzfs_core/libzfs_core.abi | 105 +---------- man/man4/zfs.4 | 18 ++ module/os/freebsd/zfs/sysctl_os.c | 59 ++++++- module/os/linux/zfs/spa_misc_os.c | 56 ++++++ module/zfs/dsl_scan.c | 25 +++ module/zfs/zfs_crrd.c | 19 ++ module/zfs/zfs_ioctl.c | 14 ++ tests/runfiles/common.run | 3 +- tests/zfs-tests/include/tunables.cfg | 3 + tests/zfs-tests/tests/Makefile.am | 1 + .../zpool_clear/zpool_clear_with_scrub.ksh | 96 ++++++++++ .../zpool_scrub_recent_time_settings.ksh | 147 ++++++++++++++++ 19 files changed, 586 insertions(+), 408 deletions(-) create mode 100755 tests/zfs-tests/tests/functional/cli_root/zpool_clear/zpool_clear_with_scrub.ksh create mode 100755 tests/zfs-tests/tests/functional/cli_root/zpool_scrub/zpool_scrub_recent_time_settings.ksh diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index cc71de6a80cd..da39153ca875 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -458,7 +458,8 @@ get_usage(zpool_help_t idx) return (gettext("\tattach [-fsw] [-o property=value] " " \n")); case HELP_CLEAR: - return (gettext("\tclear [[--power]|[-nF]] [device]\n")); + return (gettext("\tclear [[--power]|[-nsF]] " + "[device]\n")); case HELP_CREATE: return (gettext("\tcreate [-fnd] [-o property=value] ... \n" "\t [-O file-system-property=value] ... \n" @@ -513,8 +514,8 @@ get_usage(zpool_help_t idx) return (gettext("\tinitialize [-c | -s | -u] [-w] <-a | " "[ ...]>\n")); case HELP_SCRUB: - return (gettext("\tscrub [-e | -s | -p | -C | -E | -S] [-w] " - "<-a | [ ...]>\n")); + return (gettext("\tscrub [-e | -s | -p | -C | -E | -S | -R] " + "[-w] <-a | [ ...]>\n")); case HELP_RESILVER: return (gettext("\tresilver ...\n")); case HELP_TRIM: @@ -8155,8 +8156,74 @@ zpool_do_offline(int argc, char **argv) return (ret); } +typedef struct scrub_cbdata { + int cb_type; + pool_scrub_cmd_t cb_scrub_cmd; + time_t cb_date_start; + time_t cb_date_end; +} scrub_cbdata_t; + +static boolean_t +zpool_has_checkpoint(zpool_handle_t *zhp) +{ + nvlist_t *config, *nvroot; + + config = zpool_get_config(zhp, NULL); + + if (config != NULL) { + pool_checkpoint_stat_t *pcs = NULL; + uint_t c; + + nvroot = fnvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE); + (void) nvlist_lookup_uint64_array(nvroot, + ZPOOL_CONFIG_CHECKPOINT_STATS, (uint64_t **)&pcs, &c); + + if (pcs == NULL || pcs->pcs_state == CS_NONE) + return (B_FALSE); + + assert(pcs->pcs_state == CS_CHECKPOINT_EXISTS || + pcs->pcs_state == CS_CHECKPOINT_DISCARDING); + return (B_TRUE); + } + + return (B_FALSE); +} + +static int +zpool_scrub(zpool_handle_t *zhp, scrub_cbdata_t *cb) +{ + int err; + + /* + * Ignore faulted pools. + */ + if (zpool_get_state(zhp) == POOL_STATE_UNAVAIL) { + (void) fprintf(stderr, gettext("cannot scan '%s': pool is " + "currently unavailable\n"), zpool_get_name(zhp)); + return (1); + } + + err = zpool_scan_range(zhp, cb->cb_type, cb->cb_scrub_cmd, + cb->cb_date_start, cb->cb_date_end); + if (err == 0 && zpool_has_checkpoint(zhp) && + cb->cb_type == POOL_SCAN_SCRUB) { + (void) printf(gettext("warning: will not scrub state that " + "belongs to the checkpoint of pool '%s'\n"), + zpool_get_name(zhp)); + } + + return (err != 0); +} + +static int +scrub_callback(zpool_handle_t *zhp, void *data) +{ + scrub_cbdata_t *cb = data; + return (zpool_scrub(zhp, cb)); +} + /* - * zpool clear [-nF]|[--power] [device] + * zpool clear [-nsF]|[--power] [device] * * Clear all errors associated with a pool or a particular device. */ @@ -8169,6 +8236,7 @@ zpool_do_clear(int argc, char **argv) boolean_t do_rewind = B_FALSE; boolean_t xtreme_rewind = B_FALSE; boolean_t is_power_on = B_FALSE; + boolean_t scrub = B_FALSE; uint32_t rewind_policy = ZPOOL_NO_REWIND; nvlist_t *policy = NULL; zpool_handle_t *zhp; @@ -8180,7 +8248,7 @@ zpool_do_clear(int argc, char **argv) }; /* check options */ - while ((c = getopt_long(argc, argv, "FnX", long_options, + while ((c = getopt_long(argc, argv, "FnsX", long_options, NULL)) != -1) { switch (c) { case 'F': @@ -8189,6 +8257,9 @@ zpool_do_clear(int argc, char **argv) case 'n': dryrun = B_TRUE; break; + case 's': + scrub = B_TRUE; + break; case 'X': xtreme_rewind = B_TRUE; break; @@ -8256,6 +8327,14 @@ zpool_do_clear(int argc, char **argv) if (zpool_clear(zhp, device, policy) != 0) ret = 1; + if (ret == 0 && !dryrun && scrub) { + scrub_cbdata_t cbdata = { + .cb_type = POOL_SCAN_SCRUB, + .cb_scrub_cmd = POOL_SCRUB_RECENT, + }; + ret = scrub_callback(zhp, &cbdata); + } + zpool_close(zhp); nvlist_free(policy); @@ -8357,66 +8436,6 @@ zpool_do_reopen(int argc, char **argv) return (ret); } -typedef struct scrub_cbdata { - int cb_type; - pool_scrub_cmd_t cb_scrub_cmd; - time_t cb_date_start; - time_t cb_date_end; -} scrub_cbdata_t; - -static boolean_t -zpool_has_checkpoint(zpool_handle_t *zhp) -{ - nvlist_t *config, *nvroot; - - config = zpool_get_config(zhp, NULL); - - if (config != NULL) { - pool_checkpoint_stat_t *pcs = NULL; - uint_t c; - - nvroot = fnvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE); - (void) nvlist_lookup_uint64_array(nvroot, - ZPOOL_CONFIG_CHECKPOINT_STATS, (uint64_t **)&pcs, &c); - - if (pcs == NULL || pcs->pcs_state == CS_NONE) - return (B_FALSE); - - assert(pcs->pcs_state == CS_CHECKPOINT_EXISTS || - pcs->pcs_state == CS_CHECKPOINT_DISCARDING); - return (B_TRUE); - } - - return (B_FALSE); -} - -static int -scrub_callback(zpool_handle_t *zhp, void *data) -{ - scrub_cbdata_t *cb = data; - int err; - - /* - * Ignore faulted pools. - */ - if (zpool_get_state(zhp) == POOL_STATE_UNAVAIL) { - (void) fprintf(stderr, gettext("cannot scan '%s': pool is " - "currently unavailable\n"), zpool_get_name(zhp)); - return (1); - } - - err = zpool_scan_range(zhp, cb->cb_type, cb->cb_scrub_cmd, - cb->cb_date_start, cb->cb_date_end); - if (err == 0 && zpool_has_checkpoint(zhp) && - cb->cb_type == POOL_SCAN_SCRUB) { - (void) printf(gettext("warning: will not scrub state that " - "belongs to the checkpoint of pool '%s'\n"), - zpool_get_name(zhp)); - } - - return (err != 0); -} - static int wait_callback(zpool_handle_t *zhp, void *data) { @@ -8452,7 +8471,7 @@ struct zpool_scrub_option { }; /* - * zpool scrub [-e | -s | -p | -C | -E | -S] [-w] [-a | ...] + * zpool scrub [-e | -s | -p | -C | -E | -S | -R] [-w] [-a | ...] * * -a Scrub all pools. * -e Only scrub blocks in the error log. @@ -8460,6 +8479,7 @@ struct zpool_scrub_option { * -S Start date of scrub. * -s Stop. Stops any in-progress scrub. * -p Pause. Pause in-progress scrub. + * -R Scrub only recent data. * -w Wait. Blocks until scrub has completed. * -C Scrub from last saved txg. */ @@ -8478,11 +8498,12 @@ zpool_do_scrub(int argc, char **argv) struct zpool_scrub_option is_error_scrub = {'e', B_FALSE}; struct zpool_scrub_option is_pause = {'p', B_FALSE}; struct zpool_scrub_option is_stop = {'s', B_FALSE}; + struct zpool_scrub_option is_recent = {'R', B_FALSE}; struct zpool_scrub_option is_txg_continue = {'C', B_FALSE}; struct zpool_scrub_option scrub_all = {'a', B_FALSE}; /* check options */ - while ((c = getopt(argc, argv, "aspweCE:S:")) != -1) { + while ((c = getopt(argc, argv, "aspweCE:S:R")) != -1) { switch (c) { case 'a': scrub_all.enabled = B_TRUE; @@ -8506,6 +8527,9 @@ zpool_do_scrub(int argc, char **argv) case 'p': is_pause.enabled = B_TRUE; break; + case 'R': + is_recent.enabled = B_TRUE; + break; case 'w': wait.enabled = B_TRUE; break; @@ -8526,9 +8550,13 @@ zpool_do_scrub(int argc, char **argv) {&is_stop, &is_pause}, {&is_stop, &is_txg_continue}, {&is_stop, &is_error_scrub}, + {&is_stop, &is_recent}, {&is_pause, &is_txg_continue}, {&is_pause, &is_error_scrub}, + {&is_pause, &is_recent}, {&is_error_scrub, &is_txg_continue}, + {&is_error_scrub, &is_recent}, + {&is_recent, &is_txg_continue}, }; for (int i = 0; i < sizeof (scrub_exclusive_options) / @@ -8553,6 +8581,8 @@ zpool_do_scrub(int argc, char **argv) cb.cb_type = POOL_SCAN_NONE; } else if (is_txg_continue.enabled) { cb.cb_scrub_cmd = POOL_SCRUB_FROM_LAST_TXG; + } else if (is_recent.enabled) { + cb.cb_scrub_cmd = POOL_SCRUB_RECENT; } else { cb.cb_scrub_cmd = POOL_SCRUB_NORMAL; } diff --git a/include/os/freebsd/spl/sys/mod.h b/include/os/freebsd/spl/sys/mod.h index 4214189c32df..993052e6de83 100644 --- a/include/os/freebsd/spl/sys/mod.h +++ b/include/os/freebsd/spl/sys/mod.h @@ -83,6 +83,15 @@ #define param_set_deadman_ziotime_args(var) \ CTLTYPE_U64, NULL, 0, param_set_deadman_ziotime, "QU" +#define scrub_param_set_recent_time_args(var) \ + CTLTYPE_U64, NULL, 0, scrub_param_set_recent_time, "QU" + +#define scrub_param_set_recent_time_hours_args(var) \ + CTLTYPE_U64, NULL, 0, scrub_param_set_recent_time_hours, "QU" + +#define scrub_param_set_recent_time_days_args(var) \ + CTLTYPE_U64, NULL, 0, scrub_param_set_recent_time_days, "QU" + #define param_set_multihost_interval_args(var) \ CTLTYPE_U64, NULL, 0, param_set_multihost_interval, "QU" diff --git a/include/sys/dsl_scan.h b/include/sys/dsl_scan.h index 44cca14794e0..177dd11562d9 100644 --- a/include/sys/dsl_scan.h +++ b/include/sys/dsl_scan.h @@ -45,6 +45,9 @@ struct dsl_pool; struct dmu_tx; extern int zfs_scan_suspend_progress; +extern uint64_t zfs_scrub_recent_time; +extern uint64_t zfs_scrub_recent_time_hours; +extern uint64_t zfs_scrub_recent_time_days; /* * All members of this structure must be uint64_t, for byteswap @@ -222,6 +225,10 @@ void dsl_scan_freed(spa_t *spa, const blkptr_t *bp); void dsl_scan_io_queue_destroy(dsl_scan_io_queue_t *queue); void dsl_scan_io_queue_vdev_xfer(vdev_t *svd, vdev_t *tvd); +int scrub_param_set_recent_time(ZFS_MODULE_PARAM_ARGS); +int scrub_param_set_recent_time_hours(ZFS_MODULE_PARAM_ARGS); +int scrub_param_set_recent_time_days(ZFS_MODULE_PARAM_ARGS); + #ifdef __cplusplus } #endif diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index 49ab9d3db795..c3acf0bf89b3 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -1098,6 +1098,7 @@ typedef enum pool_scrub_cmd { POOL_SCRUB_NORMAL = 0, POOL_SCRUB_PAUSE, POOL_SCRUB_FROM_LAST_TXG, + POOL_SCRUB_RECENT, POOL_SCRUB_FLAGS_END } pool_scrub_cmd_t; diff --git a/include/zfs_crrd.h b/include/zfs_crrd.h index ba192a2062ea..c3a255466079 100644 --- a/include/zfs_crrd.h +++ b/include/zfs_crrd.h @@ -71,5 +71,6 @@ void rrd_add(rrd_t *rrd, hrtime_t time, uint64_t txg); void dbrrd_add(dbrrd_t *db, hrtime_t time, uint64_t txg); uint64_t dbrrd_query(dbrrd_t *r, hrtime_t tv, dbrrd_rounding_t rouding); +hrtime_t dbrrd_tail_time(dbrrd_t *r); #endif diff --git a/lib/libuutil/libuutil.abi b/lib/libuutil/libuutil.abi index 6c736c61e4a5..2a740afa07ca 100644 --- a/lib/libuutil/libuutil.abi +++ b/lib/libuutil/libuutil.abi @@ -616,6 +616,7 @@ + @@ -1020,13 +1021,6 @@ - - - - - - - @@ -1061,93 +1055,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1237,8 +1144,6 @@ - - @@ -1254,14 +1159,6 @@ - - - - - - - - diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi index 184ea4a55b43..887fb73f4f67 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -641,7 +641,7 @@ - + @@ -1458,103 +1458,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1566,14 +1471,6 @@ - - - - - - - - @@ -6188,7 +6085,8 @@ - + + @@ -9774,8 +9672,8 @@ - - + + @@ -9805,30 +9703,31 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/libzfs_core/libzfs_core.abi b/lib/libzfs_core/libzfs_core.abi index 7464b3adb254..263cad045f7a 100644 --- a/lib/libzfs_core/libzfs_core.abi +++ b/lib/libzfs_core/libzfs_core.abi @@ -617,6 +617,7 @@ + @@ -988,13 +989,6 @@ - - - - - - - @@ -1029,93 +1023,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1191,8 +1098,6 @@ - - @@ -1208,14 +1113,6 @@ - - - - - - - - diff --git a/man/man4/zfs.4 b/man/man4/zfs.4 index 11bcbf430210..1163a3697136 100644 --- a/man/man4/zfs.4 +++ b/man/man4/zfs.4 @@ -1967,6 +1967,24 @@ simply doing a metadata crawl of the pool instead. .It Sy zfs_no_scrub_prefetch Ns = Ns Sy 0 Ns | Ns 1 Pq int Set to disable block prefetching for scrubs. . +.It Sy zfs_scrub_recent_time Ns = Ns Sy 14400 Pq uint +.It Sy zfs_scrub_recent_time_hours Ns = Ns Sy 4 Pq uint +.It Sy zfs_scrub_recent_time_days Ns = Ns Sy 0 Pq uint +Set the time interval that defines which data is considered most recent. +The interval is measured relative to the last known TXG in the TXG database. +.Pp +This setting can be specified using one of three properties: seconds, hours, +or days. +These properties are provided for user convenience. +Each represents the same value, expressed in different units of time. +For example, setting the +.Sy zfs_scrub_recent_time_hours +property to 1 will automatically set the +.Sy zfs_scrub_recent_time +to 3600 and +.Sy zfs_scrub_recent_time_days +to 0. +. .It Sy zfs_nocacheflush Ns = Ns Sy 0 Ns | Ns 1 Pq int Disable cache flush operations on disks when writing. Setting this will cause pool corruption on power loss diff --git a/module/os/freebsd/zfs/sysctl_os.c b/module/os/freebsd/zfs/sysctl_os.c index 393bfaa65ff5..15afa781ec7f 100644 --- a/module/os/freebsd/zfs/sysctl_os.c +++ b/module/os/freebsd/zfs/sysctl_os.c @@ -253,6 +253,64 @@ param_set_arc_no_grow_shift(SYSCTL_HANDLER_ARGS) return (0); } +/* dsl_scan.c */ + +static void +scrub_set_recent_time(uint64_t sec) +{ + zfs_scrub_recent_time = sec; + zfs_scrub_recent_time_hours = sec / 3600; + zfs_scrub_recent_time_days = sec / 86400; +} + +int +scrub_param_set_recent_time(SYSCTL_HANDLER_ARGS) +{ + uint64_t val; + int err; + + val = zfs_scrub_recent_time; + err = sysctl_handle_64(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + + scrub_set_recent_time(val); + + return (0); +} + +int +scrub_param_set_recent_time_hours(SYSCTL_HANDLER_ARGS) +{ + uint64_t val; + int err; + + val = zfs_scrub_recent_time_hours; + err = sysctl_handle_64(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + + scrub_set_recent_time(val * 3600); + + return (0); +} + +int +scrub_param_set_recent_time_days(SYSCTL_HANDLER_ARGS) +{ + uint64_t val; + int err; + + val = zfs_scrub_recent_time_days; + err = sysctl_handle_64(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + + scrub_set_recent_time(val * 86400); + + return (0); +} + /* metaslab.c */ int @@ -509,7 +567,6 @@ param_set_slop_shift(SYSCTL_HANDLER_ARGS) /* spacemap.c */ extern int space_map_ibs; - SYSCTL_INT(_vfs_zfs, OID_AUTO, space_map_ibs, CTLFLAG_RWTUN, &space_map_ibs, 0, "Space map indirect block shift"); diff --git a/module/os/linux/zfs/spa_misc_os.c b/module/os/linux/zfs/spa_misc_os.c index d6323fd56a8f..2c989028439c 100644 --- a/module/os/linux/zfs/spa_misc_os.c +++ b/module/os/linux/zfs/spa_misc_os.c @@ -112,10 +112,66 @@ param_set_active_allocator(const char *val, zfs_kernel_param_t *kp) error = -param_set_active_allocator_common(val); if (error == 0) error = param_set_charp(val, kp); + error = spl_param_set_u64(val, kp); + if (error < 0) + return (SET_ERROR(error)); return (error); } +static void +scrub_set_recent_time(uint64_t sec) +{ + zfs_scrub_recent_time = sec; + zfs_scrub_recent_time_hours = sec / 3600; + zfs_scrub_recent_time_days = sec / 86400; +} + +int +scrub_param_set_recent_time(const char *buf, zfs_kernel_param_t *kp) +{ + uint64_t val; + int error; + + error = kstrtoull(buf, 0, &val); + if (error) + return (SET_ERROR(error)); + + scrub_set_recent_time(val); + + return (0); +} + +int +scrub_param_set_recent_time_hours(const char *buf, zfs_kernel_param_t *kp) +{ + uint64_t val; + int error; + + error = kstrtoull(buf, 0, &val); + if (error) + return (SET_ERROR(error)); + + scrub_set_recent_time(val * 3600); + + return (0); +} + +int +scrub_param_set_recent_time_days(const char *buf, zfs_kernel_param_t *kp) +{ + uint64_t val; + int error; + + error = kstrtoull(buf, 0, &val); + if (error) + return (SET_ERROR(error)); + + scrub_set_recent_time(val * 86400); + + return (0); +} + const char * spa_history_zone(void) { diff --git a/module/zfs/dsl_scan.c b/module/zfs/dsl_scan.c index fcd50c459d07..a148cee56bec 100644 --- a/module/zfs/dsl_scan.c +++ b/module/zfs/dsl_scan.c @@ -246,6 +246,14 @@ static int zfs_free_bpobj_enabled = 1; /* Error blocks to be scrubbed in one txg. */ static uint_t zfs_scrub_error_blocks_per_txg = 1 << 12; +/* + * The number of TXGs should be scrubbed while scrubbing recent data. + * Default: 4 hours. + */ +uint64_t zfs_scrub_recent_time = 14400; +uint64_t zfs_scrub_recent_time_hours = 4; +uint64_t zfs_scrub_recent_time_days = 0; + /* the order has to match pool_scan_type */ static scan_cb_t *scan_funcs[POOL_SCAN_FUNCS] = { NULL, @@ -5362,3 +5370,20 @@ ZFS_MODULE_PARAM(zfs, zfs_, resilver_defer_percent, UINT, ZMOD_RW, ZFS_MODULE_PARAM(zfs, zfs_, scrub_error_blocks_per_txg, UINT, ZMOD_RW, "Error blocks to be scrubbed in one txg"); + +ZFS_MODULE_PARAM_CALL(zfs, zfs_, scrub_recent_time, scrub_param_set_recent_time, + spl_param_get_u64, ZMOD_RW, + "Defines the time window for recent scrub in seconds. " + "Adjusting this value will also update the hours and days equivalents"); + +ZFS_MODULE_PARAM_CALL(zfs, zfs_, scrub_recent_time_hours, + scrub_param_set_recent_time_hours, spl_param_get_u64, ZMOD_RW, + "Defines the time window for recent scrub in hours. " + "Adjusting this value will also update the seconds and days " + "equivalents"); + +ZFS_MODULE_PARAM_CALL(zfs, zfs_, scrub_recent_time_days, + scrub_param_set_recent_time_days, spl_param_get_u64, ZMOD_RW, + "Defines the time window for recent scrub in days. " + "Adjusting this value will also update the seconds and hours " + "equivalents"); diff --git a/module/zfs/zfs_crrd.c b/module/zfs/zfs_crrd.c index 30d4c7c36897..6a8bce44b2c6 100644 --- a/module/zfs/zfs_crrd.c +++ b/module/zfs/zfs_crrd.c @@ -226,3 +226,22 @@ dbrrd_query(dbrrd_t *r, hrtime_t tv, dbrrd_rounding_t rounding) return (data == NULL ? 0 : data->rrdd_txg); } + +hrtime_t +dbrrd_tail_time(dbrrd_t *r) +{ + const rrd_data_t *data, *dd, *dy; + + data = rrd_tail_entry(&r->dbr_minutes); + dd = rrd_tail_entry(&r->dbr_days); + dy = rrd_tail_entry(&r->dbr_months); + + if (data == NULL || (dd != NULL && data->rrdd_time < dd->rrdd_time)) { + data = dd; + } + if (data == NULL || (dy != NULL && data->rrdd_time < dy->rrdd_time)) { + data = dy; + } + + return (data == NULL ? 0 : data->rrdd_time); +} diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index 5ca7c2320c4e..5120acb6afd7 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -1752,6 +1752,20 @@ zfs_ioc_pool_scrub(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl) } else if (scan_cmd == POOL_SCRUB_FROM_LAST_TXG) { error = spa_scan_range(spa, scan_type, spa_get_last_scrubbed_txg(spa), 0); + } else if (scan_cmd == POOL_SCRUB_RECENT) { + uint64_t txg_start; + hrtime_t lasttime; + + txg_start = 0; + mutex_enter(&spa->spa_txg_log_time_lock); + lasttime = dbrrd_tail_time(&spa->spa_txg_log_time); + if (lasttime > zfs_scrub_recent_time) { + txg_start = dbrrd_query(&spa->spa_txg_log_time, + lasttime - zfs_scrub_recent_time, DBRRD_FLOOR); + } + mutex_exit(&spa->spa_txg_log_time_lock); + + error = spa_scan_range(spa, scan_type, txg_start, 0); } else { uint64_t txg_start, txg_end; diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 2b002830c82f..70947702b309 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -405,7 +405,7 @@ tags = ['functional', 'cli_root', 'zpool_attach'] [tests/functional/cli_root/zpool_clear] tests = ['zpool_clear_001_pos', 'zpool_clear_002_neg', 'zpool_clear_003_neg', - 'zpool_clear_readonly'] + 'zpool_clear_readonly', 'zpool_clear_with_scrub'] tags = ['functional', 'cli_root', 'zpool_clear'] [tests/functional/cli_root/zpool_create] @@ -551,6 +551,7 @@ tests = ['zpool_scrub_001_neg', 'zpool_scrub_002_pos', 'zpool_scrub_003_pos', 'zpool_error_scrub_001_pos', 'zpool_error_scrub_002_pos', 'zpool_error_scrub_003_pos', 'zpool_error_scrub_004_pos', 'zpool_scrub_date_range_001', 'zpool_scrub_date_range_002'] + 'zpool_scrub_recent_time_settings'] tags = ['functional', 'cli_root', 'zpool_scrub'] [tests/functional/cli_root/zpool_set] diff --git a/tests/zfs-tests/include/tunables.cfg b/tests/zfs-tests/include/tunables.cfg index 54b50c9dba77..1185cb8b1372 100644 --- a/tests/zfs-tests/include/tunables.cfg +++ b/tests/zfs-tests/include/tunables.cfg @@ -84,6 +84,9 @@ SCAN_LEGACY scan_legacy zfs_scan_legacy SCAN_SUSPEND_PROGRESS scan_suspend_progress zfs_scan_suspend_progress SCAN_VDEV_LIMIT scan_vdev_limit zfs_scan_vdev_limit SCRUB_AFTER_EXPAND scrub_after_expand zfs_scrub_after_expand +SCRUB_RECENT_TIME scrub_recent_time zfs_scrub_recent_time +SCRUB_RECENT_TIME_HOURS scrub_recent_time_hours zfs_scrub_recent_time_hours +SCRUB_RECENT_TIME_DAYS scrub_recent_time_days zfs_scrub_recent_time_days SEND_HOLES_WITHOUT_BIRTH_TIME send_holes_without_birth_time send_holes_without_birth_time SLOW_IO_EVENTS_PER_SECOND slow_io_events_per_second zfs_slow_io_events_per_second SPA_ASIZE_INFLATION spa.asize_inflation spa_asize_inflation diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index 1517f90e99a5..6c715b8da40c 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -1249,6 +1249,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/cli_root/zpool_scrub/zpool_scrub_multiple_pools.ksh \ functional/cli_root/zpool_scrub/zpool_scrub_offline_device.ksh \ functional/cli_root/zpool_scrub/zpool_scrub_print_repairing.ksh \ + functional/cli_root/zpool_scrub/zpool_scrub_recent_time_settings.ksh \ functional/cli_root/zpool_scrub/zpool_scrub_txg_continue_from_last.ksh \ functional/cli_root/zpool_scrub/zpool_scrub_date_range_001.ksh \ functional/cli_root/zpool_scrub/zpool_scrub_date_range_002.ksh \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_clear/zpool_clear_with_scrub.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_clear/zpool_clear_with_scrub.ksh new file mode 100755 index 000000000000..8daea33ba016 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_clear/zpool_clear_with_scrub.ksh @@ -0,0 +1,96 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2025 Klara Inc. +# +# This software was developed by Mariusz Zaborski +# under sponsorship from Wasabi Technology, Inc. and Klara Inc. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/cli_root/zpool_clear/zpool_clear.cfg + +# +# DESCRIPTION: +# Verify that `zpool clean -s` clears any errors from the pool +# and scrubs only recent data.` +# +# STRATEGY: +# 1. Adjust the scrub_recent_time to a small value to make the test quick +# and concise. +# 2. Create the first file. +# 3. Export, wait, and re-import the pool to record entries in the TXG +# database and to add some time margin between the files. +# 4. Create the second file. +# 5. Inject errors into the first file. +# 6. Run a scrub to detect the errors to the pool. +# 7. Verify that errors in the first file are detected and that +# the second file is not corrupted. +# 8. Inject errors into the second file. +# 9. Run `zpool clear -s`. +# 10. Confirm that errors from the first file are cleared from the error +# list, and only new errors corresponding to recent data +# (the second file) are detected. +# + +verify_runnable "global" + +function cleanup +{ + log_must zinject -c all + rm -f $TESTDIR/*_file + log_must restore_tunable SCRUB_RECENT_TIME +} + +log_onexit cleanup + +log_assert "Verify cleanup of errors and scrubbing of recent data." + +log_must save_tunable SCRUB_RECENT_TIME +log_must set_tunable64 SCRUB_RECENT_TIME 30 + +log_must file_write -o create -f"$TESTDIR/0_file" \ + -b 512 -c 2048 -dR +log_must zpool export $TESTPOOL +log_must sleep 60 +log_must zpool import $TESTPOOL +log_must file_write -o create -f"$TESTDIR/1_file" \ + -b 512 -c 2048 -dR + +log_must zinject -t data -e checksum -f 100 $TESTDIR/0_file +log_must zpool scrub $TESTPOOL +log_must eval "zpool status -v $TESTPOOL | grep '0_file'" +log_mustnot eval "zpool status -v $TESTPOOL | grep '1_file'" + +log_must zinject -t data -e checksum -f 100 $TESTDIR/1_file +log_must zpool clear -s $TESTPOOL +log_mustnot eval "zpool status -v $TESTPOOL | grep '0_file'" +log_must eval "zpool status -v $TESTPOOL | grep '1_file'" + +log_pass "Verified cleanup of errors and scrubbing of recent data." diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_scrub/zpool_scrub_recent_time_settings.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_scrub/zpool_scrub_recent_time_settings.ksh new file mode 100755 index 000000000000..e33a931251d1 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_scrub/zpool_scrub_recent_time_settings.ksh @@ -0,0 +1,147 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# 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 (c) 2025 Klara Inc. +# +# This software was developed by Mariusz Zaborski +# under sponsorship from Wasabi Technology, Inc. and Klara Inc. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/cli_root/zpool_scrub/zpool_scrub.cfg + +# +# DESCRIPTION: +# Verify that scrub_recent_time is correctly calculated. +# +# STRATEGY: +# 1. Set scrub_recent_time and check if the scrub_recent_time_hours are +# scrub_recent_time_day are set correctly. +# 2. Set scrub_recent_time_hours and check if the scrub_recent_time are +# scrub_recent_time_day are set correctly. +# 3. Set scrub_recent_time_day and check if the scrub_recent_time are +# scrub_recent_time_hours are set correctly. +# + +verify_runnable "global" + +function cleanup +{ + log_must restore_tunable SCRUB_RECENT_TIME +} + +function check_scrub_recent_time +{ + recent_time="${1}" + recent_time_hours="${2}" + recent_time_days="${3}" + + [[ "${recent_time}" == "$(get_tunable SCRUB_RECENT_TIME)" ]] || \ + log_fail "scrub_recent_time has wrong value" + [[ "${recent_time_hours}" == "$(get_tunable SCRUB_RECENT_TIME_HOURS)" ]] || \ + log_fail "scrub_recent_time_hours has wrong value" + [[ "${recent_time_days}" == "$(get_tunable SCRUB_RECENT_TIME_DAYS)" ]] || \ + log_fail "scrub_recent_time_days has wrong value" +} + +log_onexit cleanup + +log_assert "Verify "\ + "scrub_recent_time/scrub_recent_time_hours/scrub_recent_time_days is "\ + "calculated correctly." + +log_must save_tunable SCRUB_RECENT_TIME + +log_must set_tunable64 SCRUB_RECENT_TIME 1 +check_scrub_recent_time "1" "0" "0" + +log_must set_tunable64 SCRUB_RECENT_TIME "30" +check_scrub_recent_time "30" "0" "0" + +log_must set_tunable64 SCRUB_RECENT_TIME "59" +check_scrub_recent_time "59" "0" "0" + +log_must set_tunable64 SCRUB_RECENT_TIME "60" +check_scrub_recent_time "60" "0" "0" + +log_must set_tunable64 SCRUB_RECENT_TIME "3600" +check_scrub_recent_time "3600" "1" "0" + +log_must set_tunable64 SCRUB_RECENT_TIME "86400" +check_scrub_recent_time "86400" "24" "1" + +log_must set_tunable64 SCRUB_RECENT_TIME "172800" +check_scrub_recent_time "172800" "48" "2" + +log_must set_tunable64 SCRUB_RECENT_TIME "100000" +check_scrub_recent_time "100000" "27" "1" + +log_must set_tunable64 SCRUB_RECENT_TIME "200000" +check_scrub_recent_time "200000" "55" "2" + +log_must set_tunable64 SCRUB_RECENT_TIME "86399" +check_scrub_recent_time "86399" "23" "0" + +log_must set_tunable64 SCRUB_RECENT_TIME "604799" +check_scrub_recent_time "604799" "167" "6" + +log_must set_tunable64 SCRUB_RECENT_TIME_HOURS "1" +check_scrub_recent_time "3600" "1" "0" + +log_must set_tunable64 SCRUB_RECENT_TIME_HOURS "12" +check_scrub_recent_time "43200" "12" "0" + +log_must set_tunable64 SCRUB_RECENT_TIME_HOURS "23" +check_scrub_recent_time "82800" "23" "0" + +log_must set_tunable64 SCRUB_RECENT_TIME_HOURS "24" +check_scrub_recent_time "86400" "24" "1" + +log_must set_tunable64 SCRUB_RECENT_TIME_HOURS "25" +check_scrub_recent_time "90000" "25" "1" + +log_must set_tunable64 SCRUB_RECENT_TIME_HOURS "48" +check_scrub_recent_time "172800" "48" "2" + +log_must set_tunable64 SCRUB_RECENT_TIME_HOURS "100" +check_scrub_recent_time "360000" "100" "4" + +log_must set_tunable64 SCRUB_RECENT_TIME_DAYS "1" +check_scrub_recent_time "86400" "24" "1" + +log_must set_tunable64 SCRUB_RECENT_TIME_DAYS "2" +check_scrub_recent_time "172800" "48" "2" + +log_must set_tunable64 SCRUB_RECENT_TIME_DAYS "7" +check_scrub_recent_time "604800" "168" "7" + +log_must set_tunable64 SCRUB_RECENT_TIME_DAYS "30" +check_scrub_recent_time "2592000" "720" "30" + +log_must set_tunable64 SCRUB_RECENT_TIME_DAYS "365" +check_scrub_recent_time "31536000" "8760" "365" + +log_pass "Verified "\ + "scrub_recent_time/scrub_recent_time_hours/scrub_recent_time_days is "\ + "calculated correctly."