diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index a640a3e91a19..01131be04fc8 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -528,8 +528,9 @@ 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 | -t | -C [-t] | " + "[-S date] [-E date] [-t]] [-w]\n" + "\t <-a | [ ...]>\n")); case HELP_RESILVER: return (gettext("\tresilver ...\n")); case HELP_TRIM: @@ -8451,6 +8452,7 @@ zpool_do_reopen(int argc, char **argv) typedef struct scrub_cbdata { int cb_type; pool_scrub_cmd_t cb_scrub_cmd; + pool_scrub_flags_t cb_scrub_flags; time_t cb_date_start; time_t cb_date_end; } scrub_cbdata_t; @@ -8497,7 +8499,7 @@ scrub_callback(zpool_handle_t *zhp, void *data) } err = zpool_scan_range(zhp, cb->cb_type, cb->cb_scrub_cmd, - cb->cb_date_start, cb->cb_date_end); + cb->cb_scrub_flags, 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 " @@ -8538,7 +8540,7 @@ date_string_to_sec(const char *timestr, boolean_t rounding) } /* - * zpool scrub [-e | -s | -p | -C | -E | -S] [-w] [-a | ...] + * zpool scrub [-e | -s | -p | -C | -E | -S | -t] [-w] [-a | ...] * * -a Scrub all pools. * -e Only scrub blocks in the error log. @@ -8547,6 +8549,7 @@ date_string_to_sec(const char *timestr, boolean_t rounding) * -s Stop. Stops any in-progress scrub. * -p Pause. Pause in-progress scrub. * -w Wait. Blocks until scrub has completed. + * -t Decompress and decrypt (if key is loaded) scrubbed blocks. * -C Scrub from last saved txg. */ int @@ -8558,17 +8561,17 @@ zpool_do_scrub(int argc, char **argv) int error; cb.cb_type = POOL_SCAN_SCRUB; - cb.cb_scrub_cmd = POOL_SCRUB_NORMAL; + cb.cb_scrub_cmd = 0; + cb.cb_scrub_flags = 0; 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; /* check options */ - while ((c = getopt(argc, argv, "aspweCE:S:")) != -1) { + while ((c = getopt(argc, argv, "aspweCE:S:t")) != -1) { switch (c) { case 'a': scrub_all = B_TRUE; @@ -8576,6 +8579,9 @@ zpool_do_scrub(int argc, char **argv) case 'e': is_error_scrub = B_TRUE; break; + case 't': + cb.cb_scrub_flags |= POOL_SCRUB_THOROUGH; + break; case 'E': /* * Round the date. It's better to scrub more data than @@ -8596,7 +8602,7 @@ zpool_do_scrub(int argc, char **argv) wait = B_TRUE; break; case 'C': - is_txg_continue = B_TRUE; + cb.cb_scrub_cmd |= POOL_SCRUB_FROM_LAST_TXG; break; case '?': (void) fprintf(stderr, gettext("invalid option '%c'\n"), @@ -8609,17 +8615,39 @@ zpool_do_scrub(int argc, char **argv) (void) fprintf(stderr, gettext("invalid option " "combination: -s and -p are mutually exclusive\n")); usage(B_FALSE); - } else if (is_pause && is_txg_continue) { + } else if (is_error_scrub && is_pause) { + (void) fprintf(stderr, gettext("invalid option " + "combination: -e and -p are mutually exclusive\n")); + usage(B_FALSE); + } else if (is_error_scrub && is_stop) { + (void) fprintf(stderr, gettext("invalid option " + "combination: -e and -s are mutually exclusive\n")); + usage(B_FALSE); + } else if (is_error_scrub && + (cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) { + (void) fprintf(stderr, gettext("invalid option " + "combination: -e and -C are mutually exclusive\n")); + usage(B_FALSE); + } else if (is_error_scrub && + (cb.cb_scrub_flags & POOL_SCRUB_THOROUGH)) { + (void) fprintf(stderr, gettext("invalid option " + "combination: -e and -t are mutually exclusive\n")); + usage(B_FALSE); + } else if (is_pause && (cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) { (void) fprintf(stderr, gettext("invalid option " "combination: -p and -C are mutually exclusive\n")); usage(B_FALSE); - } else if (is_stop && is_txg_continue) { + } else if (is_pause && (cb.cb_scrub_flags & POOL_SCRUB_THOROUGH)) { + (void) fprintf(stderr, gettext("invalid option " + "combination: -p and -t are mutually exclusive\n")); + usage(B_FALSE); + } else if (is_stop && (cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) { (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) { + } else if (is_stop && (cb.cb_scrub_flags & POOL_SCRUB_THOROUGH)) { (void) fprintf(stderr, gettext("invalid option " - "combination: -e and -C are mutually exclusive\n")); + "combination: -s and -t are mutually exclusive\n")); usage(B_FALSE); } else { if (is_error_scrub) @@ -8629,19 +8657,26 @@ zpool_do_scrub(int argc, char **argv) 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; } } + boolean_t is_thorough = + (cb.cb_scrub_flags & POOL_SCRUB_THOROUGH) != 0; 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: " - "start/end date is available only with normal scrub\n")); + (cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) { + (void) fprintf(stderr, gettext("invalid option " + "combination: -C and -S/-E date are mutually " + "exclusive\n")); + usage(B_FALSE); + } else if ((cb.cb_date_start != 0 || cb.cb_date_end != 0) && + (is_error_scrub || is_stop || is_pause || + (!is_thorough && cb.cb_scrub_cmd != POOL_SCRUB_NORMAL))) { + (void) fprintf(stderr, gettext("invalid option " + "combination: start/end date is available only " + "with normal or thorough scrub\n")); usage(B_FALSE); } + if (cb.cb_date_start != 0 && cb.cb_date_end != 0 && cb.cb_date_start > cb.cb_date_end) { (void) fprintf(stderr, gettext("invalid arguments: " @@ -8689,6 +8724,7 @@ zpool_do_resilver(int argc, char **argv) cb.cb_type = POOL_SCAN_RESILVER; cb.cb_scrub_cmd = POOL_SCRUB_NORMAL; + cb.cb_scrub_flags = 0; cb.cb_date_start = cb.cb_date_end = 0; /* check options */ @@ -9111,7 +9147,7 @@ print_err_scrub_status(pool_scan_stat_t *ps) * Print out detailed scrub status. */ static void -print_scan_scrub_resilver_status(pool_scan_stat_t *ps) +print_scan_scrub_resilver_status(pool_scan_stat_t *ps, boolean_t is_thorough) { time_t start, end, pause; uint64_t pass_scanned, scanned, pass_issued, issued, total_s, total_i; @@ -9146,10 +9182,17 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps) secs_to_dhms(end - start, time_buf); if (is_scrub) { - (void) printf(gettext("scrub repaired %s " - "in %s with %llu errors on %s"), processed_buf, - time_buf, (u_longlong_t)ps->pss_errors, - ctime(&end)); + if (is_thorough) { + (void) printf(gettext("thorough scrub " + "repaired %s in %s with %llu errors on %s"), + processed_buf, time_buf, + (u_longlong_t)ps->pss_errors, ctime(&end)); + } else { + (void) printf(gettext("scrub repaired %s " + "in %s with %llu errors on %s"), + processed_buf, time_buf, + (u_longlong_t)ps->pss_errors, ctime(&end)); + } } else if (is_resilver) { (void) printf(gettext("resilvered %s " "in %s with %llu errors on %s"), processed_buf, @@ -9159,8 +9202,13 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps) return; } else if (ps->pss_state == DSS_CANCELED) { if (is_scrub) { - (void) printf(gettext("scrub canceled on %s"), - ctime(&end)); + if (is_thorough) { + (void) printf(gettext("thorough scrub canceled " + "on %s"), ctime(&end)); + } else { + (void) printf(gettext("scrub canceled on %s"), + ctime(&end)); + } } else if (is_resilver) { (void) printf(gettext("resilver canceled on %s"), ctime(&end)); @@ -9173,13 +9221,25 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps) /* Scan is in progress. Resilvers can't be paused. */ if (is_scrub) { if (pause == 0) { - (void) printf(gettext("scrub in progress since %s"), - ctime(&start)); + if (is_thorough) { + (void) printf(gettext("thorough scrub " + "in progress since %s"), ctime(&start)); + } else { + (void) printf(gettext("scrub in progress " + "since %s"), ctime(&start)); + } } else { - (void) printf(gettext("scrub paused since %s"), - ctime(&pause)); - (void) printf(gettext("\tscrub started on %s"), - ctime(&start)); + if (is_thorough) { + (void) printf(gettext("thorough scrub paused " + "since %s"), ctime(&pause)); + (void) printf(gettext("\tthorough scrub " + "started on %s"), ctime(&start)); + } else { + (void) printf(gettext("scrub paused since %s"), + ctime(&pause)); + (void) printf(gettext("\tscrub started on %s"), + ctime(&start)); + } } } else if (is_resilver) { (void) printf(gettext("resilver in progress since %s"), @@ -10316,6 +10376,7 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot) pool_checkpoint_stat_t *pcs = NULL; pool_scan_stat_t *ps = NULL; uint_t c; + boolean_t is_thorough = B_FALSE; time_t scrub_start = 0, errorscrub_start = 0; if (nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_SCAN_STATS, @@ -10328,8 +10389,10 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot) have_resilver = (ps->pss_func == POOL_SCAN_RESILVER); have_scrub = (ps->pss_func == POOL_SCAN_SCRUB); scrub_start = ps->pss_start_time; - if (c > offsetof(pool_scan_stat_t, - pss_pass_error_scrub_pause) / 8) { + if (POOL_SCAN_STAT_VALID(pss_pass_scrub_flags, c) && + (ps->pss_pass_scrub_flags & POOL_SCRUB_THOROUGH) != 0) + is_thorough = B_TRUE; + if (POOL_SCAN_STAT_VALID(pss_pass_error_scrub_pause, c)) { have_errorscrub = (ps->pss_error_scrub_func == POOL_SCAN_ERRORSCRUB); errorscrub_start = ps->pss_error_scrub_start; @@ -10341,7 +10404,7 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot) /* Always print the scrub status when available. */ if (have_scrub && scrub_start > errorscrub_start) - print_scan_scrub_resilver_status(ps); + print_scan_scrub_resilver_status(ps, is_thorough); else if (have_errorscrub && errorscrub_start >= scrub_start) print_err_scrub_status(ps); @@ -10351,7 +10414,7 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot) */ if (active_resilver || (!active_rebuild && have_resilver && resilver_end_time && resilver_end_time > rebuild_end_time)) { - print_scan_scrub_resilver_status(ps); + print_scan_scrub_resilver_status(ps, is_thorough); } else if (active_rebuild || (!active_resilver && have_rebuild && rebuild_end_time && rebuild_end_time > resilver_end_time)) { print_rebuild_status(zhp, nvroot); diff --git a/cmd/ztest.c b/cmd/ztest.c index 13a7d91c3c49..57395a4e5d24 100644 --- a/cmd/ztest.c +++ b/cmd/ztest.c @@ -4235,7 +4235,7 @@ ztest_device_removal(ztest_ds_t *zd, uint64_t id) * strategy employed by ztest_fault_inject() when selecting which * offset are redundant and can be damaged. */ - error = spa_scan(spa, POOL_SCAN_SCRUB); + error = spa_scan(spa, POOL_SCAN_SCRUB, 0); if (error == 0) { while (dsl_scan_scrubbing(spa_get_dsl(spa))) txg_wait_synced(spa_get_dsl(spa), 0); @@ -6723,7 +6723,7 @@ out: mutex_exit(&ztest_vdev_lock); if (injected && ztest_opts.zo_raid_do_expand) { - int error = spa_scan(spa, POOL_SCAN_SCRUB); + int error = spa_scan(spa, POOL_SCAN_SCRUB, 0); if (error == 0) { while (dsl_scan_scrubbing(spa_get_dsl(spa))) txg_wait_synced(spa_get_dsl(spa), 0); @@ -6756,7 +6756,7 @@ out: static int ztest_scrub_impl(spa_t *spa) { - int error = spa_scan(spa, POOL_SCAN_SCRUB); + int error = spa_scan(spa, POOL_SCAN_SCRUB, 0); if (error) return (error); @@ -6790,7 +6790,7 @@ ztest_scrub(ztest_ds_t *zd, uint64_t id) /* * Start a scrub, wait a moment, then force a restart. */ - (void) spa_scan(spa, POOL_SCAN_SCRUB); + (void) spa_scan(spa, POOL_SCAN_SCRUB, 0); (void) poll(NULL, 0, 100); error = ztest_scrub_impl(spa); @@ -7483,7 +7483,7 @@ ztest_spa_import_export(char *oldname, char *newname) * Kick off a scrub to tickle scrub/export races. */ if (ztest_random(2) == 0) - (void) spa_scan(spa, POOL_SCAN_SCRUB); + (void) spa_scan(spa, POOL_SCAN_SCRUB, 0); pool_guid = spa_guid(spa); spa_close(spa, FTAG); diff --git a/include/libzfs.h b/include/libzfs.h index 2397c6a23370..718a58cadf1a 100644 --- a/include/libzfs.h +++ b/include/libzfs.h @@ -305,7 +305,7 @@ typedef struct initialize_cbdata { */ _LIBZFS_H int zpool_scan(zpool_handle_t *, pool_scan_func_t, pool_scrub_cmd_t); _LIBZFS_H int zpool_scan_range(zpool_handle_t *, pool_scan_func_t, - pool_scrub_cmd_t, time_t, time_t); + pool_scrub_cmd_t, pool_scrub_flags_t, time_t, time_t); _LIBZFS_H int zpool_initialize_one(zpool_handle_t *, void *); _LIBZFS_H int zpool_initialize(zpool_handle_t *, pool_initialize_func_t, nvlist_t *); diff --git a/include/sys/dsl_scan.h b/include/sys/dsl_scan.h index bcb98af40067..085a879132eb 100644 --- a/include/sys/dsl_scan.h +++ b/include/sys/dsl_scan.h @@ -76,10 +76,9 @@ typedef struct dsl_scan_phys { typedef enum dsl_scan_flags { DSF_VISIT_DS_AGAIN = 1<<0, DSF_SCRUB_PAUSED = 1<<1, + DSF_SCRUB_THOROUGH = 1<<2, } dsl_scan_flags_t; -#define DSL_SCAN_FLAGS_MASK (DSF_VISIT_DS_AGAIN) - typedef struct dsl_errorscrub_phys { uint64_t dep_func; /* pool_scan_func_t */ uint64_t dep_state; /* dsl_scan_state_t */ @@ -184,6 +183,7 @@ typedef struct { pool_scan_func_t func; uint64_t txgstart; uint64_t txgend; + dsl_scan_flags_t flags; } setup_sync_arg_t; typedef struct dsl_scan_io_queue dsl_scan_io_queue_t; @@ -197,7 +197,7 @@ void dsl_scan_fini(struct dsl_pool *dp); void dsl_scan_sync(struct dsl_pool *, dmu_tx_t *); int dsl_scan_cancel(struct dsl_pool *); int dsl_scan(struct dsl_pool *, pool_scan_func_t, uint64_t starttxg, - uint64_t txgend); + uint64_t txgend, dsl_scan_flags_t flags); void dsl_scan_assess_vdev(struct dsl_pool *dp, vdev_t *vd); boolean_t dsl_scan_scrubbing(const struct dsl_pool *dp); boolean_t dsl_errorscrubbing(const struct dsl_pool *dp); diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index 470c6de0e243..29945eadcea6 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -1227,6 +1227,10 @@ typedef enum pool_scrub_cmd { POOL_SCRUB_FLAGS_END } pool_scrub_cmd_t; +typedef enum pool_scrub_flags { + POOL_SCRUB_THOROUGH = 1 << 0, +} pool_scrub_flags_t; + typedef enum { CS_NONE, CS_CHECKPOINT_EXISTS, @@ -1317,9 +1321,15 @@ typedef struct pool_scan_stat { /* error scrub values not stored on disk */ /* error scrub pause time in milliseconds */ uint64_t pss_pass_error_scrub_pause; + uint64_t pss_pass_scrub_flags; } pool_scan_stat_t; +#define POOL_SCAN_STAT_VALID(field, uint64_t_field_count) \ + ((uint64_t_field_count * sizeof (uint64_t)) >= \ + (offsetof(pool_scan_stat_t, field) + \ + sizeof (((pool_scan_stat_t *)NULL)->field))) + typedef struct pool_removal_stat { uint64_t prs_state; /* dsl_scan_state_t */ uint64_t prs_removing_vdev; diff --git a/include/sys/spa.h b/include/sys/spa.h index afb29caa8167..51a39754f09b 100644 --- a/include/sys/spa.h +++ b/include/sys/spa.h @@ -849,9 +849,10 @@ extern void spa_l2cache_activate(vdev_t *vd); extern void spa_l2cache_drop(spa_t *spa); /* scanning */ -extern int spa_scan(spa_t *spa, pool_scan_func_t func); +extern int spa_scan(spa_t *spa, pool_scan_func_t func, + pool_scrub_flags_t flags); extern int spa_scan_range(spa_t *spa, pool_scan_func_t func, uint64_t txgstart, - uint64_t txgend); + uint64_t txgend, pool_scrub_flags_t flags); extern int spa_scan_stop(spa_t *spa); extern int spa_scrub_pause_resume(spa_t *spa, pool_scrub_cmd_t flag); diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi index 7364bedcace3..a627613c39ca 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -6490,6 +6490,11 @@ + + + + + @@ -7268,6 +7273,7 @@ + diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c index d10283327e7c..95aec70b905d 100644 --- a/lib/libzfs/libzfs_pool.c +++ b/lib/libzfs/libzfs_pool.c @@ -2936,12 +2936,13 @@ out: */ int zpool_scan(zpool_handle_t *zhp, pool_scan_func_t func, pool_scrub_cmd_t cmd) { - return (zpool_scan_range(zhp, func, cmd, 0, 0)); + return (zpool_scan_range(zhp, func, cmd, 0, 0, 0)); } int zpool_scan_range(zpool_handle_t *zhp, pool_scan_func_t func, - pool_scrub_cmd_t cmd, time_t date_start, time_t date_end) + pool_scrub_cmd_t cmd, pool_scrub_flags_t flags, + time_t date_start, time_t date_end) { char errbuf[ERRBUFLEN]; int err; @@ -2950,6 +2951,8 @@ zpool_scan_range(zpool_handle_t *zhp, pool_scan_func_t func, nvlist_t *args = fnvlist_alloc(); fnvlist_add_uint64(args, "scan_type", (uint64_t)func); fnvlist_add_uint64(args, "scan_command", (uint64_t)cmd); + if (flags != 0) + fnvlist_add_uint64(args, "scan_flags", (uint64_t)flags); if (date_start != 0 || date_end != 0) { fnvlist_add_uint64(args, "scan_date_start", (uint64_t)date_start); @@ -2961,7 +2964,7 @@ zpool_scan_range(zpool_handle_t *zhp, pool_scan_func_t func, if (err == 0) { return (0); - } else if (err == ZFS_ERR_IOC_CMD_UNAVAIL) { + } else if (err == ZFS_ERR_IOC_CMD_UNAVAIL && flags == 0) { zfs_cmd_t zc = {"\0"}; (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name)); diff --git a/man/man4/zfs.4 b/man/man4/zfs.4 index 29a953a507f0..2877b5359f23 100644 --- a/man/man4/zfs.4 +++ b/man/man4/zfs.4 @@ -2286,7 +2286,7 @@ Error blocks to be scrubbed in one txg. When enabled, ZFS counts blocks by type and indirection level during a scrub. The counts are kept in memory for debugging and are not exposed to userspace. . -.It Sy zfs_scan_checkpoint_intval Ns = Ns Sy 7200 Ns s Po 2 hour Pc Pq uint +.It Sy zfs_scan_checkpoint_intval Ns = Ns Sy 7200 Ns s Po 2 hours Pc Pq uint To preserve progress across reboots, the sequential scan algorithm periodically needs to stop metadata scanning and issue all the verification I/O to disk. The frequency of this flushing is determined by this tunable. diff --git a/man/man8/zpool-scrub.8 b/man/man8/zpool-scrub.8 index 966eb0adf344..11e6f8c94974 100644 --- a/man/man8/zpool-scrub.8 +++ b/man/man8/zpool-scrub.8 @@ -28,7 +28,7 @@ .\" Copyright (c) 2017 Open-E, Inc. All Rights Reserved. .\" Copyright (c) 2025 Hewlett Packard Enterprise Development LP. .\" -.Dd March 16, 2026 +.Dd May 1, 2026 .Dt ZPOOL-SCRUB 8 .Os . @@ -38,16 +38,36 @@ .Sh SYNOPSIS .Nm zpool .Cm scrub -.Op Ns Fl e | Ns Fl p | Fl s Ns | Fl C Ns .Op Fl w +.Op Fl t +.Oo +.Fl C | +.Xo .Op Fl S Ar date .Op Fl E Ar date +.Xc +.Oc +.Fl a Ns | Ns Ar pool Ns … +.Nm zpool +.Cm scrub +.Fl e +.Op Fl w +.Fl a Ns | Ns Ar pool Ns … +.Nm zpool +.Cm scrub +.Fl p +.Fl a Ns | Ns Ar pool Ns … +.Nm zpool +.Cm scrub +.Fl s .Fl a Ns | Ns Ar pool Ns … . .Sh DESCRIPTION Begins a scrub or resumes a paused scrub. -The scrub examines all data in the specified pools to verify that it checksums -correctly. +A normal scrub examines all data in the specified pools and verifies each +block's checksum. +A thorough scrub additionally decrypts and/or decompresses blocks as they are +read. For replicated .Pq mirror, raidz, or draid devices, ZFS automatically repairs any damage discovered during the scrub. @@ -67,8 +87,11 @@ whereas scrubbing examines all data to discover silent errors due to hardware faults or disk failure. .Pp When scrubbing a pool with encrypted filesystems the keys do not need to be -loaded. -However, if the keys are not loaded and an unrepairable checksum error is +loaded for ordinary checksum verification but are needed for thorough scrub +.Pq see Fl t +below. +.Pp +If the keys are not loaded and an unrepairable checksum error is detected the file name cannot be included in the .Nm zpool Cm status Fl v verbose error report. @@ -93,26 +116,33 @@ During this period, no completion time estimate will be provided. .Sh OPTIONS .Bl -tag -width "-s" .It Fl a , -all -Begin, pause, stop scrub on -all -pools. +Begin, pause +.Fl p , +or stop +.Fl s , +scrub on all pools. Initiating scrubs on multiple pools can put considerable load and memory pressure on the system, so this operation should be performed with caution. .It Fl s Stop scrubbing. +Cannot be combined with other options except +.Fl a . .It Fl p Pause scrubbing. +Cannot be combined with other options except +.Fl a . Scrub pause state and progress are periodically synced to disk. If the system is restarted or pool is exported during a paused scrub, even after import, scrub will remain paused until it is resumed. Once resumed the scrub will pick up from the place where it was last checkpointed to disk. To resume a paused scrub issue -.Nm zpool Cm scrub -or -.Nm zpool Cm scrub -.Fl e -again. +.Nm zpool Cm scrub . +The scrub resumes in the same mode +.Pq normal or thorough +it had when it started. +To resume paused error scrub, issue +.Nm zpool Cm scrub Fl e . .It Fl w Wait until scrub has completed before returning. .It Fl e @@ -121,12 +151,60 @@ Only scrub files with known data errors as reported by The pool must have been scrubbed at least once with the .Sy head_errlog feature enabled to use this option. -Error scrubbing cannot be run simultaneously with regular scrubbing or -resilvering, nor can it be run when a regular scrub is paused. +Error scrubbing cannot be run simultaneously with other scrubbing or +resilvering, nor can it be run when a scrub is paused. +This option cannot be combined with +.Fl C , +.Fl p , +.Fl s , +or +.Fl t . .It Fl C Continue scrub from last saved txg (see zpool .Sy last_scrubbed_txg property). +Cannot be combined with +.Fl S , +.Fl E , +.Fl e , +.Fl p , +or +.Fl s . +May be combined with thorough scrub +.Pq Fl t . +.It Fl t +Thorough scrub. +Will cause scrub to decrypt and decompress blocks it reads so that it will +catch the rare type of corruption where the checksum matches the data, but +decryption and/or decompression fails. +For encrypted datasets keys have to be loaded in order to perform +thorough scrub. +If keys are not loaded or unloaded during a thorough scrub the scrub will revert +to doing a normal scrub for the encrypted blocks with key(s) unloaded. +Cannot be combined with +.Fl e , +.Fl p , +or +.Fl s . +May be combined with +.Fl a , +.Fl w , +.Fl C +or +start/end date options +.Pq Fl S / Fl E . +.Pp +If a thorough scrub is paused, resuming with +.Nm zpool Cm scrub +.Pq with or without +.Fl t +continues as a thorough scrub. +Resuming a paused normal scrub with +.Fl t +will fail; use +.Nm zpool Cm scrub +without +.Fl t . .It Fl S Ar date , Fl E Ar date Allows specifying the date range for blocks created between these dates. .Bl -bullet -compact -offset indent @@ -164,7 +242,12 @@ The hour and minutes parameters can be omitted. The time should be provided in machine local time zone. Specifying dates prior to enabling this feature will result in scrubbing starting from the date the pool was created. -If the time was moved backward manually the data range may become inaccurate. +Large or discontinuous changes to the system clock (forward or backward) +can skew how the start and/or end dates map to pool transaction groups. +As a result, the scrub may not cover the intended dates. +.Pp +Cannot be combined with +.Fl C . .El .Sh EXAMPLES .Ss Example 1 diff --git a/module/zfs/dsl_scan.c b/module/zfs/dsl_scan.c index 03e13ca96cc4..30d7cc669b98 100644 --- a/module/zfs/dsl_scan.c +++ b/module/zfs/dsl_scan.c @@ -26,6 +26,7 @@ * Copyright (c) 2017, 2019, Datto Inc. All rights reserved. * Copyright (c) 2015, Nexenta Systems, Inc. All rights reserved. * Copyright 2019 Joyent, Inc. + * Copyright 2026 ConnectWise */ #include @@ -283,45 +284,58 @@ typedef enum { * zio for sequential scanning. This is useful because many of these will * accumulate in the sequential IO queues before being issued, so saving * memory matters here. + * + * A thorough scrub decrypts blocks as it reads them, so encrypted blocks must + * preserve the salt/IV from blk_dva[2]. Rather than grow every queued sio to + * include salt/IV we use the compact scan_io_t for the common case and the + * larger scan_io_ext_t only for encrypted blocks in thorough scrubs + * (non-thorough scrubs issue ZIO_FLAG_RAW reads and never need salt/IV). + * Encrypted blkptrs store salt/IV in blk_dva[2], so scan_io_ext_t never + * carries three DVAs. + * The two layouts share the same leading SCAN_IO_COMMON_FIELDS. */ -typedef struct scan_io { - /* fields from blkptr_t */ - uint64_t sio_blk_prop; - uint64_t sio_phys_birth; - uint64_t sio_birth; - zio_cksum_t sio_cksum; - uint32_t sio_nr_dvas; - - /* fields from zio_t */ - uint32_t sio_flags; - zbookmark_phys_t sio_zb; - - /* members for queue sorting */ - union { - avl_node_t sio_addr_node; /* link into issuing queue */ - list_node_t sio_list_node; /* link for issuing to disk */ +#define SCAN_IO_COMMON_FIELDS \ + /* fields from blkptr_t */ \ + uint64_t sio_blk_prop; \ + uint64_t sio_phys_birth; \ + uint64_t sio_birth; \ + zio_cksum_t sio_cksum; \ + uint32_t sio_nr_dvas; \ + boolean_t sio_ext; \ + \ + /* fields from zio_t */ \ + uint32_t sio_flags; \ + zbookmark_phys_t sio_zb; \ + \ + /* members for queue sorting */ \ + union { \ + avl_node_t sio_addr_node; /* link into issuing queue */ \ + list_node_t sio_list_node; /* link for issuing to disk */ \ } sio_nodes; - /* - * There may be up to SPA_DVAS_PER_BP DVAs here from the bp, - * depending on how many were in the original bp. Only the - * first DVA is really used for sorting and issuing purposes. - * The other DVAs (if provided) simply exist so that the zio - * layer can find additional copies to repair from in the - * event of an error. This array must go at the end of the - * struct to allow this for the variable number of elements. - */ +/* + * There may be up to SPA_DVAS_PER_BP DVAs in sio_dva here from the bp, + * depending on how many were in the original bp. Only the first DVA is + * really used for sorting and issuing purposes. The other DVAs (if provided) + * simply exist so that the zio layer can find additional copies to repair + * from in the event of an error. Therefore the sio_dva array must go at the + * end of the struct since it potentially has variable number of elements. + */ +typedef struct scan_io { + SCAN_IO_COMMON_FIELDS dva_t sio_dva[]; } scan_io_t; -#define SIO_SET_OFFSET(sio, x) DVA_SET_OFFSET(&(sio)->sio_dva[0], x) -#define SIO_SET_ASIZE(sio, x) DVA_SET_ASIZE(&(sio)->sio_dva[0], x) -#define SIO_GET_OFFSET(sio) DVA_GET_OFFSET(&(sio)->sio_dva[0]) -#define SIO_GET_ASIZE(sio) DVA_GET_ASIZE(&(sio)->sio_dva[0]) -#define SIO_GET_END_OFFSET(sio) \ - (SIO_GET_OFFSET(sio) + SIO_GET_ASIZE(sio)) -#define SIO_GET_MUSED(sio) \ - (sizeof (scan_io_t) + ((sio)->sio_nr_dvas * sizeof (dva_t))) +/* + * Like scan_io_t, but also carries the salt/IV of an encrypted blkptr. + */ +typedef struct scan_io_ext { + SCAN_IO_COMMON_FIELDS + uint64_t sio_salt; + uint64_t sio_iv1; + uint32_t sio_iv2; + dva_t sio_dva[]; +} scan_io_ext_t; struct dsl_scan_io_queue { dsl_scan_t *q_scn; /* associated dsl_scan_t */ @@ -347,6 +361,36 @@ struct dsl_scan_io_queue { uint64_t q_zios_this_txg; }; +/* + * scan_io_t and scan_io_ext_t share the same leading SCAN_IO_COMMON_FIELDS, + * so a scan_io_t pointer can access those fields for either layout. Only + * the salt/IV fields and the offset of the trailing sio_dva[] differ. + */ +static inline dva_t * +sio_dvas(scan_io_t *sio) +{ + if (sio->sio_ext) + return (((scan_io_ext_t *)sio)->sio_dva); + return (sio->sio_dva); +} + +static inline const dva_t * +sio_dvas_const(const scan_io_t *sio) +{ + if (sio->sio_ext) + return (((const scan_io_ext_t *)sio)->sio_dva); + return (sio->sio_dva); +} + +#define SIO_SET_OFFSET(sio, x) DVA_SET_OFFSET(sio_dvas(sio), x) +#define SIO_GET_OFFSET(sio) DVA_GET_OFFSET(sio_dvas_const(sio)) +#define SIO_GET_ASIZE(sio) DVA_GET_ASIZE(sio_dvas_const(sio)) +#define SIO_GET_END_OFFSET(sio) \ + (SIO_GET_OFFSET(sio) + SIO_GET_ASIZE(sio)) +#define SIO_GET_MUSED(sio) \ + (((sio)->sio_ext ? offsetof(scan_io_ext_t, sio_dva) : \ + offsetof(scan_io_t, sio_dva)) + ((sio)->sio_nr_dvas * sizeof (dva_t))) + /* private data for dsl_scan_prefetch_cb() */ typedef struct scan_prefetch_ctx { zfs_refcount_t spc_refcnt; /* refcount for memory management */ @@ -372,26 +416,36 @@ static void scan_io_queue_insert_impl(dsl_scan_io_queue_t *queue, static dsl_scan_io_queue_t *scan_io_queue_create(vdev_t *vd); static void scan_io_queues_destroy(dsl_scan_t *scn); -static kmem_cache_t *sio_cache[SPA_DVAS_PER_BP]; +static kmem_cache_t *sio_cache_compact[SPA_DVAS_PER_BP]; +static kmem_cache_t *sio_cache_ext[SPA_DVAS_PER_BP]; /* sio->sio_nr_dvas must be set so we know which cache to free from */ static void sio_free(scan_io_t *sio) { + kmem_cache_t **cache = sio->sio_ext ? sio_cache_ext : + sio_cache_compact; + ASSERT3U(sio->sio_nr_dvas, >, 0); ASSERT3U(sio->sio_nr_dvas, <=, SPA_DVAS_PER_BP); - kmem_cache_free(sio_cache[sio->sio_nr_dvas - 1], sio); + kmem_cache_free(cache[sio->sio_nr_dvas - 1], sio); } /* It is up to the caller to set sio->sio_nr_dvas for freeing */ static scan_io_t * -sio_alloc(unsigned short nr_dvas) +sio_alloc(unsigned short nr_dvas, boolean_t ext) { + kmem_cache_t **cache = ext ? sio_cache_ext : sio_cache_compact; + scan_io_t *sio; + ASSERT3U(nr_dvas, >, 0); ASSERT3U(nr_dvas, <=, SPA_DVAS_PER_BP); + ASSERT(!ext || nr_dvas < SPA_DVAS_PER_BP); - return (kmem_cache_alloc(sio_cache[nr_dvas - 1], KM_SLEEP)); + sio = kmem_cache_alloc(cache[nr_dvas - 1], KM_SLEEP); + sio->sio_ext = ext; + return (sio); } void @@ -407,13 +461,30 @@ scan_init(void) */ fill_weight = zfs_scan_fill_weight; - for (int i = 0; i < SPA_DVAS_PER_BP; i++) { - char name[36]; + /* + * The common fields (and thus the sio_nodes used for AVL/list links) + * must sit at the same offset in both layouts so the shared code path + * can treat either as a scan_io_t. + */ + ASSERT3U(offsetof(scan_io_t, sio_nodes), ==, + offsetof(scan_io_ext_t, sio_nodes)); - (void) snprintf(name, sizeof (name), "sio_cache_%d", i); - sio_cache[i] = kmem_cache_create(name, - (sizeof (scan_io_t) + ((i + 1) * sizeof (dva_t))), + for (int i = 0; i < SPA_DVAS_PER_BP; i++) { + char name[40]; + + (void) snprintf(name, sizeof (name), "sio_cache_compact_%d", i); + sio_cache_compact[i] = kmem_cache_create(name, + (offsetof(scan_io_t, sio_dva) + ((i + 1) * sizeof (dva_t))), 0, NULL, NULL, NULL, NULL, NULL, 0); + + if (i < SPA_DVAS_PER_BP - 1) { + (void) snprintf(name, sizeof (name), + "sio_cache_ext_%d", i); + sio_cache_ext[i] = kmem_cache_create(name, + (offsetof(scan_io_ext_t, sio_dva) + + ((i + 1) * sizeof (dva_t))), + 0, NULL, NULL, NULL, NULL, NULL, 0); + } } } @@ -421,7 +492,9 @@ void scan_fini(void) { for (int i = 0; i < SPA_DVAS_PER_BP; i++) { - kmem_cache_destroy(sio_cache[i]); + kmem_cache_destroy(sio_cache_compact[i]); + if (i < SPA_DVAS_PER_BP - 1) + kmem_cache_destroy(sio_cache_ext[i]); } } @@ -439,29 +512,54 @@ dsl_scan_resilvering(dsl_pool_t *dp) } static inline void -sio2bp(const scan_io_t *sio, blkptr_t *bp) +sio2bp(scan_io_t *sio, blkptr_t *bp) { memset(bp, 0, sizeof (*bp)); bp->blk_prop = sio->sio_blk_prop; BP_SET_PHYSICAL_BIRTH(bp, sio->sio_phys_birth); BP_SET_LOGICAL_BIRTH(bp, sio->sio_birth); bp->blk_fill = 1; /* we always only work with data pointers */ + /* + * An extended sio carries the salt/IV that an encrypted blkptr keeps + * in blk_dva[2], so restore it before the real DVAs are copied in + * below. + */ + if (sio->sio_ext) { + scan_io_ext_t *esio = (scan_io_ext_t *)sio; + + ASSERT(BP_IS_ENCRYPTED(bp)); + ASSERT3U(sio->sio_nr_dvas, <, SPA_DVAS_PER_BP); + bp->blk_dva[2].dva_word[0] = esio->sio_salt; + bp->blk_dva[2].dva_word[1] = esio->sio_iv1; + BP_SET_IV2(bp, esio->sio_iv2); + } bp->blk_cksum = sio->sio_cksum; ASSERT3U(sio->sio_nr_dvas, >, 0); ASSERT3U(sio->sio_nr_dvas, <=, SPA_DVAS_PER_BP); - memcpy(bp->blk_dva, sio->sio_dva, sio->sio_nr_dvas * sizeof (dva_t)); + memcpy(bp->blk_dva, sio_dvas(sio), sio->sio_nr_dvas * sizeof (dva_t)); } static inline void bp2sio(const blkptr_t *bp, scan_io_t *sio, int dva_i) { + dva_t *dvas = sio_dvas(sio); + sio->sio_blk_prop = bp->blk_prop; sio->sio_phys_birth = BP_GET_RAW_PHYSICAL_BIRTH(bp); sio->sio_birth = BP_GET_LOGICAL_BIRTH(bp); sio->sio_cksum = bp->blk_cksum; sio->sio_nr_dvas = BP_GET_NDVAS(bp); + if (sio->sio_ext) { + scan_io_ext_t *esio = (scan_io_ext_t *)sio; + + ASSERT(BP_IS_ENCRYPTED(bp)); + ASSERT3U(sio->sio_nr_dvas, <, SPA_DVAS_PER_BP); + esio->sio_salt = bp->blk_dva[2].dva_word[0]; + esio->sio_iv1 = bp->blk_dva[2].dva_word[1]; + esio->sio_iv2 = (uint32_t)BP_GET_IV2(bp); + } /* * Copy the DVAs to the sio. We need all copies of the block so @@ -470,7 +568,7 @@ bp2sio(const blkptr_t *bp, scan_io_t *sio, int dva_i) * in the sio since this is the primary one that we want to issue. */ for (int i = 0, j = dva_i; i < sio->sio_nr_dvas; i++, j++) { - sio->sio_dva[i] = bp->blk_dva[j % sio->sio_nr_dvas]; + dvas[i] = bp->blk_dva[j % sio->sio_nr_dvas]; } } @@ -559,7 +657,7 @@ dsl_scan_init(dsl_pool_t *dp, uint64_t txg) if (err == 0) { uint64_t overflow = zaptmp[SCAN_PHYS_NUMINTS]; - if (overflow & ~DSL_SCAN_FLAGS_MASK || + if (overflow & ~DSF_VISIT_DS_AGAIN || scn->scn_async_destroying) { spa->spa_errata = ZPOOL_ERRATA_ZOL_2094_ASYNC_DESTROY; @@ -719,6 +817,13 @@ dsl_scan_is_paused_scrub(const dsl_scan_t *scn) scn->scn_phys.scn_flags & DSF_SCRUB_PAUSED); } +static boolean_t +dsl_scan_is_thorough_scrub(const dsl_scan_t *scn) +{ + return (dsl_scan_scrubbing(scn->scn_dp) && + scn->scn_phys.scn_flags & DSF_SCRUB_THOROUGH); +} + static void dsl_errorscrub_sync_state(dsl_scan_t *scn, dmu_tx_t *tx) { @@ -886,6 +991,7 @@ dsl_scan_setup_sync(void *arg, dmu_tx_t *tx) dsl_errorscrub_sync_state(scn, tx); scn->scn_phys.scn_func = setup_sync_arg->func; + scn->scn_phys.scn_flags = setup_sync_arg->flags; scn->scn_phys.scn_state = DSS_SCANNING; scn->scn_phys.scn_min_txg = setup_sync_arg->txgstart; if (setup_sync_arg->txgend == 0) { @@ -990,7 +1096,7 @@ dsl_scan_setup_sync(void *arg, dmu_tx_t *tx) */ int dsl_scan(dsl_pool_t *dp, pool_scan_func_t func, uint64_t txgstart, - uint64_t txgend) + uint64_t txgend, dsl_scan_flags_t flags) { spa_t *spa = dp->dp_spa; dsl_scan_t *scn = dp->dp_scan; @@ -1023,6 +1129,9 @@ dsl_scan(dsl_pool_t *dp, pool_scan_func_t func, uint64_t txgstart, /* * got error scrub start cmd, resume paused error scrub. */ + if (flags != 0) + return (SET_ERROR(ENOTSUP)); + int err = dsl_scrub_set_pause_resume(scn->scn_dp, POOL_SCRUB_NORMAL); if (err == 0) { @@ -1040,6 +1149,16 @@ dsl_scan(dsl_pool_t *dp, pool_scan_func_t func, uint64_t txgstart, if (func == POOL_SCAN_SCRUB && dsl_scan_is_paused_scrub(scn)) { /* got scrub start cmd, resume paused scrub */ + if ((flags & DSF_SCRUB_THOROUGH) == 0 && flags != 0) + return (SET_ERROR(ENOTSUP)); + if ((flags & DSF_SCRUB_THOROUGH) != 0 && + !dsl_scan_is_thorough_scrub(scn)) + return (SET_ERROR(ENOTSUP)); + /* + * Thorough vs normal is fixed when the scrub begins (recorded + * as DSF_SCRUB_THOROUGH in scn_phys.scn_flags), so resume does + * not change the scrub type regardless of the flags passed. + */ int err = dsl_scrub_set_pause_resume(scn->scn_dp, POOL_SCRUB_NORMAL); if (err == 0) { @@ -1052,6 +1171,7 @@ dsl_scan(dsl_pool_t *dp, pool_scan_func_t func, uint64_t txgstart, setup_sync_arg.func = func; setup_sync_arg.txgstart = txgstart; setup_sync_arg.txgend = txgend; + setup_sync_arg.flags = flags; return (dsl_sync_task(spa_name(spa), dsl_scan_setup_check, dsl_scan_setup_sync, &setup_sync_arg, 0, @@ -3307,7 +3427,7 @@ scan_io_queue_gather(dsl_scan_io_queue_t *queue, zfs_range_seg_t *rs, ASSERT(rs != NULL); ASSERT(MUTEX_HELD(&queue->q_vd->vdev_scan_io_queue_lock)); - srch_sio = sio_alloc(1); + srch_sio = sio_alloc(1, B_FALSE); srch_sio->sio_nr_dvas = 1; SIO_SET_OFFSET(srch_sio, zfs_rs_get_start(rs, queue->q_exts_by_addr)); @@ -4031,8 +4151,15 @@ read_by_block_level(dsl_scan_t *scn, zbookmark_phys_t zb) return; } - int zio_flags = ZIO_FLAG_SCAN_THREAD | ZIO_FLAG_RAW | - ZIO_FLAG_CANFAIL | ZIO_FLAG_SCRUB; + int zio_flags = ZIO_FLAG_SCAN_THREAD | ZIO_FLAG_CANFAIL | + ZIO_FLAG_SCRUB; + + /* + * A normal scrub reads raw blocks, but a thorough scrub + * must decrypt/decompress, so it does not set ZIO_FLAG_RAW. + */ + if (!dsl_scan_is_thorough_scrub(scn)) + zio_flags |= ZIO_FLAG_RAW; /* If it's an intent log block, failure is expected. */ if (zb.zb_level == ZB_ZIL_LEVEL) @@ -4771,7 +4898,9 @@ static void scan_io_queue_insert(dsl_scan_io_queue_t *queue, const blkptr_t *bp, int dva_i, int zio_flags, const zbookmark_phys_t *zb) { - scan_io_t *sio = sio_alloc(BP_GET_NDVAS(bp)); + boolean_t ext = dsl_scan_is_thorough_scrub(queue->q_scn) && + BP_IS_ENCRYPTED(bp); + scan_io_t *sio = sio_alloc(BP_GET_NDVAS(bp), ext); ASSERT0(BP_IS_GANG(bp)); ASSERT(MUTEX_HELD(&queue->q_vd->vdev_scan_io_queue_lock)); @@ -4833,7 +4962,11 @@ dsl_scan_scrub_cb(dsl_pool_t *dp, uint64_t phys_birth = BP_GET_PHYSICAL_BIRTH(bp); size_t psize = BP_GET_PSIZE(bp); boolean_t needs_io = B_FALSE; - int zio_flags = ZIO_FLAG_SCAN_THREAD | ZIO_FLAG_RAW | ZIO_FLAG_CANFAIL; + int zio_flags = ZIO_FLAG_SCAN_THREAD | ZIO_FLAG_CANFAIL; + + /* A thorough scrub decrypts/decompresses, so it must not read raw. */ + if (!dsl_scan_is_thorough_scrub(scn)) + zio_flags |= ZIO_FLAG_RAW; count_block(dp->dp_blkstats, bp); if (phys_birth <= scn->scn_phys.scn_min_txg || @@ -4890,27 +5023,41 @@ static void dsl_scan_scrub_done(zio_t *zio) { spa_t *spa = zio->io_spa; - blkptr_t *bp = zio->io_bp; dsl_scan_io_queue_t *queue = zio->io_private; abd_free(zio->io_abd); if (queue == NULL) { mutex_enter(&spa->spa_scrub_lock); - ASSERT3U(spa->spa_scrub_inflight, >=, BP_GET_PSIZE(bp)); - spa->spa_scrub_inflight -= BP_GET_PSIZE(bp); + ASSERT3U(spa->spa_scrub_inflight, >=, zio->io_size); + spa->spa_scrub_inflight -= zio->io_size; cv_broadcast(&spa->spa_scrub_io_cv); mutex_exit(&spa->spa_scrub_lock); } else { mutex_enter(&queue->q_vd->vdev_scan_io_queue_lock); - ASSERT3U(queue->q_inflight_bytes, >=, BP_GET_PSIZE(bp)); - queue->q_inflight_bytes -= BP_GET_PSIZE(bp); + ASSERT3U(queue->q_inflight_bytes, >=, zio->io_size); + queue->q_inflight_bytes -= zio->io_size; cv_broadcast(&queue->q_zio_cv); mutex_exit(&queue->q_vd->vdev_scan_io_queue_lock); } + /* + * A normal scrub issues ZIO_FLAG_RAW reads which are never decrypted + * and so can never produce EACCES here. + */ + ASSERT(zio->io_error != EACCES || !(zio->io_flags & ZIO_FLAG_SCRUB) || + !(zio->io_flags & ZIO_FLAG_RAW)); + /* + * During a thorough scrub we read blocks without ZIO_FLAG_RAW. If the + * dataset's key is not loaded the decryption (or MAC verification) + * fails with EACCES (see spa_do_crypt_abd() and the MAC helpers). + * The checksum has already been verified, so this is as much as we + * can do for the block without keys; treat it as success. + */ if (zio->io_error && (zio->io_error != ECKSUM || - !(zio->io_flags & ZIO_FLAG_SPECULATIVE))) { + !(zio->io_flags & ZIO_FLAG_SPECULATIVE)) && + !(zio->io_error == EACCES && (zio->io_flags & ZIO_FLAG_SCRUB) && + !(zio->io_flags & ZIO_FLAG_RAW))) { if (dsl_errorscrubbing(spa->spa_dsl_pool) && !dsl_errorscrub_is_paused(spa->spa_dsl_pool->dp_scan)) { atomic_inc_64(&spa->spa_dsl_pool->dp_scan @@ -4935,7 +5082,11 @@ scan_exec_io(dsl_pool_t *dp, const blkptr_t *bp, int zio_flags, { spa_t *spa = dp->dp_spa; dsl_scan_t *scn = dp->dp_scan; - size_t size = BP_GET_PSIZE(bp); + /* + * If raw flags is not set - this is a thorough scrub. + */ + size_t size = (zio_flags & ZIO_FLAG_RAW) ? + BP_GET_PSIZE(bp) : BP_GET_LSIZE(bp); abd_t *data = abd_alloc_for_io(size, B_FALSE); zio_t *pio; @@ -4944,7 +5095,7 @@ scan_exec_io(dsl_pool_t *dp, const blkptr_t *bp, int zio_flags, mutex_enter(&spa->spa_scrub_lock); while (spa->spa_scrub_inflight >= scn->scn_maxinflight_bytes) cv_wait(&spa->spa_scrub_io_cv, &spa->spa_scrub_lock); - spa->spa_scrub_inflight += BP_GET_PSIZE(bp); + spa->spa_scrub_inflight += size; mutex_exit(&spa->spa_scrub_lock); pio = scn->scn_zio_root; } else { @@ -4954,7 +5105,7 @@ scan_exec_io(dsl_pool_t *dp, const blkptr_t *bp, int zio_flags, mutex_enter(q_lock); while (queue->q_inflight_bytes >= queue->q_maxinflight_bytes) cv_wait(&queue->q_zio_cv, q_lock); - queue->q_inflight_bytes += BP_GET_PSIZE(bp); + queue->q_inflight_bytes += size; pio = queue->q_zio; mutex_exit(q_lock); } @@ -5085,7 +5236,8 @@ static const zfs_range_tree_ops_t ext_size_ops = { /* * Comparator for the q_sios_by_addr tree. Sorting is simply performed - * based on LBA-order (from lowest to highest). + * based on LBA-order (from lowest to highest). The tree can contain compact + * and extended sios, so use the per-sio DVA helper. */ static int sio_addr_compare(const void *x, const void *y) @@ -5110,8 +5262,8 @@ scan_io_queue_create(vdev_t *vd) q->q_exts_by_addr = zfs_range_tree_create_gap(&ext_size_ops, ZFS_RANGE_SEG_GAP, &q->q_exts_by_size, 0, vd->vdev_ashift, zfs_scan_max_ext_gap); - avl_create(&q->q_sios_by_addr, sio_addr_compare, - sizeof (scan_io_t), offsetof(scan_io_t, sio_nodes.sio_addr_node)); + avl_create(&q->q_sios_by_addr, sio_addr_compare, sizeof (scan_io_t), + offsetof(scan_io_t, sio_nodes.sio_addr_node)); return (q); } @@ -5209,7 +5361,7 @@ dsl_scan_freed_dva(spa_t *spa, const blkptr_t *bp, int dva_i) return; } - srch_sio = sio_alloc(BP_GET_NDVAS(bp)); + srch_sio = sio_alloc(BP_GET_NDVAS(bp), B_FALSE); bp2sio(bp, srch_sio, dva_i); start = SIO_GET_OFFSET(srch_sio); size = SIO_GET_ASIZE(srch_sio); diff --git a/module/zfs/spa.c b/module/zfs/spa.c index d8ef699d6c54..bdaa1c1a1b07 100644 --- a/module/zfs/spa.c +++ b/module/zfs/spa.c @@ -9698,17 +9698,22 @@ spa_scan_stop(spa_t *spa) } int -spa_scan(spa_t *spa, pool_scan_func_t func) +spa_scan(spa_t *spa, pool_scan_func_t func, pool_scrub_flags_t flags) { - return (spa_scan_range(spa, func, 0, 0)); + return (spa_scan_range(spa, func, 0, 0, flags)); } int spa_scan_range(spa_t *spa, pool_scan_func_t func, uint64_t txgstart, - uint64_t txgend) + uint64_t txgend, pool_scrub_flags_t flags) { + dsl_scan_flags_t dsl_flags = 0; + ASSERT0(spa_config_held(spa, SCL_ALL, RW_WRITER)); + if (flags & POOL_SCRUB_THOROUGH) + dsl_flags |= DSF_SCRUB_THOROUGH; + if (func >= POOL_SCAN_FUNCS || func == POOL_SCAN_NONE) return (SET_ERROR(ENOTSUP)); @@ -9733,7 +9738,7 @@ spa_scan_range(spa_t *spa, pool_scan_func_t func, uint64_t txgstart, !spa_feature_is_enabled(spa, SPA_FEATURE_HEAD_ERRLOG)) return (SET_ERROR(ENOTSUP)); - return (dsl_scan(spa->spa_dsl_pool, func, txgstart, txgend)); + return (dsl_scan(spa->spa_dsl_pool, func, txgstart, txgend, dsl_flags)); } /* diff --git a/module/zfs/spa_misc.c b/module/zfs/spa_misc.c index 3389325c14f6..ecda487a8cfc 100644 --- a/module/zfs/spa_misc.c +++ b/module/zfs/spa_misc.c @@ -2887,6 +2887,9 @@ spa_scan_get_stats(spa_t *spa, pool_scan_stat_t *ps) /* error scrub data not stored on disk */ ps->pss_pass_error_scrub_pause = spa->spa_scan_pass_errorscrub_pause; + ps->pss_pass_scrub_flags = 0; + if (scn->scn_phys.scn_flags & DSF_SCRUB_THOROUGH) + ps->pss_pass_scrub_flags |= POOL_SCRUB_THOROUGH; return (0); } diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index 576b76c8f299..dbdef2643eda 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -1971,11 +1971,79 @@ zfs_ioc_pool_tryimport(zfs_cmd_t *zc) return (error); } +/* + * Validate scan-related ioctls: scan type, command bits, flags, and optional + * date range must be mutually consistent. + */ +static int +zfs_scan_ioc_validate(uint64_t scan_type, uint64_t scan_cmd, + uint64_t scan_flags, uint64_t date_start, uint64_t date_end) +{ + /* Reject invalid scan_types */ + if (scan_type >= POOL_SCAN_FUNCS) + return (SET_ERROR(EINVAL)); + + /* Reject undefined bits in scan_cmd. */ + if (scan_cmd != 0 && + (scan_cmd & ~(POOL_SCRUB_PAUSE | POOL_SCRUB_FROM_LAST_TXG)) != 0) + return (SET_ERROR(EINVAL)); + + /* Reject undefined bits in scan_flags. */ + if (scan_flags != 0 && + (scan_flags & ~POOL_SCRUB_THOROUGH) != 0) + return (SET_ERROR(EINVAL)); + + /* PAUSE must not be combined with any other scrub command. */ + if ((scan_cmd & POOL_SCRUB_PAUSE) != 0 && scan_cmd != POOL_SCRUB_PAUSE) + return (SET_ERROR(EINVAL)); + + /* Pause has no date range; dates must be zero. */ + if (scan_cmd == POOL_SCRUB_PAUSE && + (date_start != 0 || date_end != 0)) + return (SET_ERROR(EINVAL)); + + /* Scan flags only apply to scrubs. */ + if (scan_flags != 0 && scan_type != POOL_SCAN_SCRUB) + return (SET_ERROR(EINVAL)); + + /* Pause and stop must not carry scrub flags. */ + if (scan_flags != 0 && + (scan_cmd == POOL_SCRUB_PAUSE || scan_type == POOL_SCAN_NONE)) + return (SET_ERROR(EINVAL)); + + /* If it's not a scrub the dates should not be set */ + if (scan_type != POOL_SCAN_SCRUB && + (date_start != 0 || date_end != 0)) + return (SET_ERROR(EINVAL)); + + /* From last TXG scrub only valid for scrubs */ + if (scan_type != POOL_SCAN_SCRUB && + (scan_cmd & POOL_SCRUB_FROM_LAST_TXG)) + return (SET_ERROR(EINVAL)); + + /* From last TXG scrub should not have dates set */ + if ((scan_cmd & POOL_SCRUB_FROM_LAST_TXG) && + (date_start != 0 || date_end != 0)) + return (SET_ERROR(EINVAL)); + + /* Resilver should have cmd set to normal and not have dates set */ + if (scan_type == POOL_SCAN_RESILVER && (scan_cmd != POOL_SCRUB_NORMAL || + date_start != 0 || date_end != 0)) + return (SET_ERROR(EINVAL)); + + /* Scrub stop should not have scan cmd nor dates set */ + if (scan_type == POOL_SCAN_NONE && (scan_cmd != 0 || + date_start != 0 || date_end != 0)) + return (SET_ERROR(EINVAL)); + + return (0); +} + /* * inputs: * zc_name name of the pool * zc_cookie scan func (pool_scan_func_t) - * zc_flags scrub pause/resume flag (pool_scrub_cmd_t) + * zc_flags scrub command (pool_scrub_cmd_t) */ static int zfs_ioc_pool_scan(zfs_cmd_t *zc) @@ -1983,21 +2051,24 @@ zfs_ioc_pool_scan(zfs_cmd_t *zc) spa_t *spa; int error; - if (zc->zc_flags >= POOL_SCRUB_FLAGS_END) - return (SET_ERROR(EINVAL)); + if ((error = zfs_scan_ioc_validate(zc->zc_cookie, zc->zc_flags, 0, 0, + 0)) != 0) + return (error); if ((error = spa_open(zc->zc_name, &spa, FTAG)) != 0) return (error); - if (zc->zc_flags == POOL_SCRUB_PAUSE) + if (zc->zc_flags == POOL_SCRUB_PAUSE) { error = spa_scrub_pause_resume(spa, POOL_SCRUB_PAUSE); - else if (zc->zc_cookie == POOL_SCAN_NONE) + } else if (zc->zc_cookie == POOL_SCAN_NONE) error = spa_scan_stop(spa); + else if (zc->zc_flags & POOL_SCRUB_FROM_LAST_TXG) + error = spa_scan_range(spa, zc->zc_cookie, + spa_get_last_scrubbed_txg(spa), 0, 0); else - error = spa_scan(spa, zc->zc_cookie); + error = spa_scan(spa, zc->zc_cookie, 0); spa_close(spa, FTAG); - return (error); } @@ -2005,11 +2076,13 @@ zfs_ioc_pool_scan(zfs_cmd_t *zc) * inputs: * poolname name of the pool * scan_type scan func (pool_scan_func_t) - * scan_command scrub pause/resume flag (pool_scrub_cmd_t) + * scan_command scrub command (pool_scrub_cmd_t) + * scan_flags scrub flags (pool_scrub_flags_t) */ static const zfs_ioc_key_t zfs_keys_pool_scrub[] = { {"scan_type", DATA_TYPE_UINT64, 0}, {"scan_command", DATA_TYPE_UINT64, 0}, + {"scan_flags", DATA_TYPE_UINT64, ZK_OPTIONAL}, {"scan_date_start", DATA_TYPE_UINT64, ZK_OPTIONAL}, {"scan_date_end", DATA_TYPE_UINT64, ZK_OPTIONAL}, }; @@ -2019,7 +2092,7 @@ zfs_ioc_pool_scrub(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl) { spa_t *spa; int error; - uint64_t scan_type, scan_cmd; + uint64_t scan_type, scan_cmd, scan_flags; uint64_t date_start, date_end; if (nvlist_lookup_uint64(innvl, "scan_type", &scan_type) != 0) @@ -2027,14 +2100,18 @@ zfs_ioc_pool_scrub(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl) if (nvlist_lookup_uint64(innvl, "scan_command", &scan_cmd) != 0) return (SET_ERROR(EINVAL)); - if (scan_cmd >= POOL_SCRUB_FLAGS_END) - return (SET_ERROR(EINVAL)); + if (nvlist_lookup_uint64(innvl, "scan_flags", &scan_flags) != 0) + scan_flags = 0; if (nvlist_lookup_uint64(innvl, "scan_date_start", &date_start) != 0) date_start = 0; if (nvlist_lookup_uint64(innvl, "scan_date_end", &date_end) != 0) date_end = 0; + if ((error = zfs_scan_ioc_validate(scan_type, scan_cmd, scan_flags, + date_start, date_end)) != 0) + return (error); + if ((error = spa_open(poolname, &spa, FTAG)) != 0) return (error); @@ -2042,14 +2119,17 @@ zfs_ioc_pool_scrub(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl) error = spa_scrub_pause_resume(spa, POOL_SCRUB_PAUSE); } else if (scan_type == POOL_SCAN_NONE) { error = spa_scan_stop(spa); - } else if (scan_cmd == POOL_SCRUB_FROM_LAST_TXG) { - error = spa_scan_range(spa, scan_type, - spa_get_last_scrubbed_txg(spa), 0); } else { - uint64_t txg_start, txg_end; + uint64_t txg_start = 0, txg_end = 0; + + if (scan_cmd & POOL_SCRUB_FROM_LAST_TXG) { + ASSERT0(date_start); + ASSERT0(date_end); + txg_start = spa_get_last_scrubbed_txg(spa); + } - txg_start = txg_end = 0; if (date_start != 0 || date_end != 0) { + ASSERT0(scan_cmd & POOL_SCRUB_FROM_LAST_TXG); mutex_enter(&spa->spa_txg_log_time_lock); if (date_start != 0) { txg_start = dbrrd_query(&spa->spa_txg_log_time, @@ -2063,7 +2143,8 @@ zfs_ioc_pool_scrub(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl) mutex_exit(&spa->spa_txg_log_time_lock); } - error = spa_scan_range(spa, scan_type, txg_start, txg_end); + error = spa_scan_range(spa, scan_type, txg_start, txg_end, + scan_flags); } spa_close(spa, FTAG); diff --git a/module/zfs/zio.c b/module/zfs/zio.c index 9294bfc317d6..95b2fbffc02d 100644 --- a/module/zfs/zio.c +++ b/module/zfs/zio.c @@ -681,8 +681,10 @@ zio_decrypt(zio_t *zio, abd_t *data, uint64_t size) return; error: - /* assert that the key was found unless this was speculative */ - ASSERT(ret != EACCES || (zio->io_flags & ZIO_FLAG_SPECULATIVE)); + /* the key was found unless this was speculative or a thorough scrub */ + ASSERT(ret != EACCES || (zio->io_flags & ZIO_FLAG_SPECULATIVE) || + ((zio->io_flags & ZIO_FLAG_SCRUB) && + !(zio->io_flags & ZIO_FLAG_RAW))); /* * If there was a decryption / authentication error return EIO as @@ -5634,6 +5636,18 @@ zio_done(zio_t *zio) zio_pop_transforms(zio); /* note: may set zio->io_error */ + /* + * During thorough scrub, if the dataset key is not loaded, decryption + * or MAC verification fails with EACCES (spa_do_crypt_abd() and the + * MAC helpers). Since the block's checksum was already successfully + * verified by zio_checksum_verify() before we got here, treat it as + * success and move on; this is as much as we can do without the keys + * loaded. + */ + if (zio->io_error == EACCES && (zio->io_flags & ZIO_FLAG_SCRUB) && + !(zio->io_flags & ZIO_FLAG_RAW)) + zio->io_error = 0; + vdev_stat_update(zio, psize); /* diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index e1b96f7b2802..b5f0db03ff61 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -569,7 +569,8 @@ tests = ['zpool_scrub_001_neg', 'zpool_scrub_002_pos', 'zpool_scrub_003_pos', 'zpool_scrub_multiple_pools', '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_date_range_001', 'zpool_scrub_date_range_002', + 'zpool_scrub_thorough'] tags = ['functional', 'cli_root', 'zpool_scrub'] [tests/functional/cli_root/zpool_set] diff --git a/tests/zfs-tests/cmd/libzfs_input_check.c b/tests/zfs-tests/cmd/libzfs_input_check.c index 8f7e36d9efa7..604478875611 100644 --- a/tests/zfs-tests/cmd/libzfs_input_check.c +++ b/tests/zfs-tests/cmd/libzfs_input_check.c @@ -848,10 +848,20 @@ static void test_scrub(const char *pool) { nvlist_t *required = fnvlist_alloc(); + nvlist_t *optional = fnvlist_alloc(); + fnvlist_add_uint64(required, "scan_type", POOL_SCAN_FUNCS + 1); fnvlist_add_uint64(required, "scan_command", POOL_SCRUB_FLAGS_END + 1); IOC_INPUT_TEST(ZFS_IOC_POOL_SCRUB, pool, required, NULL, EINVAL); nvlist_free(required); + + required = fnvlist_alloc(); + fnvlist_add_uint64(required, "scan_type", POOL_SCAN_SCRUB); + fnvlist_add_uint64(required, "scan_command", POOL_SCRUB_NORMAL); + fnvlist_add_uint64(optional, "scan_flags", POOL_SCRUB_THOROUGH << 1); + IOC_INPUT_TEST(ZFS_IOC_POOL_SCRUB, pool, required, optional, EINVAL); + nvlist_free(optional); + nvlist_free(required); } static int diff --git a/tests/zfs-tests/include/libtest.shlib b/tests/zfs-tests/include/libtest.shlib index 39e63bed7bf0..217c49f2a1c9 100644 --- a/tests/zfs-tests/include/libtest.shlib +++ b/tests/zfs-tests/include/libtest.shlib @@ -2067,6 +2067,7 @@ function check_pool_status # pool token keyword # is_pool_scrubbed - to check if the pool scrub is completed # is_pool_scrub_stopped - to check if the pool scrub is stopped # is_pool_scrub_paused - to check if the pool scrub has paused +# pause_scrub - start and pause a scrub without racing completion # is_pool_removing - to check if the pool removing is a vdev # is_pool_removed - to check if the pool remove is completed # is_pool_discarding - to check if the pool checkpoint is being discarded @@ -2121,6 +2122,24 @@ function is_pool_error_scrub_paused #pool return $? } +# +# Start a scrub and pause it without racing completion. Pins +# SCAN_SUSPEND_PROGRESS until the scrub is paused, then clears it so a +# later resume/wait can finish. Extra args are passed to `zpool scrub` +# (e.g. -t). Callers should still reset SCAN_SUSPEND_PROGRESS in cleanup. +# +function pause_scrub # pool [scrub-args...] +{ + typeset pool=$1 + shift + + log_must set_tunable32 SCAN_SUSPEND_PROGRESS 1 + log_must zpool scrub "$@" $pool + log_must zpool scrub -p $pool + log_must is_pool_scrub_paused $pool true + log_must set_tunable32 SCAN_SUSPEND_PROGRESS 0 +} + function is_pool_removing #pool { check_pool_status "$1" "remove" "in progress since " diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index 3e431283bb43..e93f63e061e0 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -1336,6 +1336,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/cli_root/zpool_scrub/zpool_error_scrub_002_pos.ksh \ functional/cli_root/zpool_scrub/zpool_error_scrub_003_pos.ksh \ functional/cli_root/zpool_scrub/zpool_error_scrub_004_pos.ksh \ + functional/cli_root/zpool_scrub/zpool_scrub_thorough.ksh \ functional/cli_root/zpool_set/cleanup.ksh \ functional/cli_root/zpool_set/setup.ksh \ functional/cli_root/zpool/setup.ksh \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_scrub/zpool_scrub_001_neg.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_scrub/zpool_scrub_001_neg.ksh index 5ffba803342f..4b9836ab7ef7 100755 --- a/tests/zfs-tests/tests/functional/cli_root/zpool_scrub/zpool_scrub_001_neg.ksh +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_scrub/zpool_scrub_001_neg.ksh @@ -29,9 +29,11 @@ # # Copyright (c) 2016 by Delphix. All rights reserved. # Copyright (c) 2025 Hewlett Packard Enterprise Development LP. +# Copyright (c) 2026 ConnectWise # . $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/cli_root/zpool_scrub/zpool_scrub.cfg # # DESCRIPTION: @@ -42,24 +44,49 @@ # 1. Create an array containing bad 'zpool scrub' parameters. # 2. For each element, execute the sub-command. # 3. Verify it returns an error. +# 4. Verify that the mutually exclusive cli args are actually +# mutually exclusive. # verify_runnable "global" -set -A args "" "-?" "blah blah" "-%" "--?" "-*" "-=" \ - "-b" "-c" "-d" "-e" "-f" "-g" "-h" "-i" "-j" "-k" "-l" \ - "-m" "-n" "-o" "-p" "-q" "-r" "-s" "-t" "-u" "-v" "-w" "-x" "-y" "-z" \ - "-A" "-B" "-C" "-D" "-E" "-F" "-G" "-H" "-I" "-J" "-K" "-L" \ - "-M" "-N" "-O" "-P" "-Q" "-R" "-S" "-T" "-U" "-V" "-W" "-X" "-W" "-Z" +typeset -a args=("-?" "blah blah" "-%" "--?" "-*" "-=" \ + "-b" "-c" "-d" "-f" "-g" "-h" "-i" "-j" "-k" "-l" \ + "-m" "-n" "-o" "-q" "-r" "-u" "-v" "-x" "-y" "-z" \ + "-A" "-B" "-D" "-E" "-F" "-G" "-H" "-I" "-J" "-K" "-L" \ + "-M" "-N" "-O" "-P" "-Q" "-R" "-S" "-T" "-U" "-V" "-W" "-X" "-Y" "-Z") log_assert "Execute 'zpool scrub' using invalid parameters." -typeset -i i=0 -while [[ $i -lt ${#args[*]} ]]; do - log_mustnot zpool scrub ${args[i]} - - ((i = i + 1)) +for arg in "${args[@]}"; do + log_mustnot zpool scrub "$arg" "$TESTPOOL" done +log_mustnot zpool scrub -p -s $TESTPOOL + +log_mustnot zpool scrub -e -p $TESTPOOL +log_mustnot zpool scrub -e -s $TESTPOOL +log_mustnot zpool scrub -e -C $TESTPOOL +log_mustnot zpool scrub -e -t $TESTPOOL +log_mustnot zpool scrub -e -S "2000-01-01" $TESTPOOL +log_mustnot zpool scrub -e -E "2099-12-31" $TESTPOOL +log_mustnot zpool scrub -e -S "2000-01-01" -E "2099-12-31" $TESTPOOL + +log_mustnot zpool scrub -p -C $TESTPOOL +log_mustnot zpool scrub -p -t $TESTPOOL +log_mustnot zpool scrub -p -S "2000-01-01" $TESTPOOL +log_mustnot zpool scrub -p -E "2099-12-31" $TESTPOOL +log_mustnot zpool scrub -p -S "2000-01-01" -E "2099-12-31" $TESTPOOL + +log_mustnot zpool scrub -s -C $TESTPOOL +log_mustnot zpool scrub -s -t $TESTPOOL +log_mustnot zpool scrub -s -S "2000-01-01" $TESTPOOL +log_mustnot zpool scrub -s -E "2099-12-31" $TESTPOOL +log_mustnot zpool scrub -s -S "2000-01-01" -E "2099-12-31" $TESTPOOL + +log_mustnot zpool scrub -C -S "2000-01-01" $TESTPOOL +log_mustnot zpool scrub -C -E "2099-12-31" $TESTPOOL +log_mustnot zpool scrub -C -S "2000-01-01" -E "2099-12-31" $TESTPOOL + log_pass "Badly formed 'zpool scrub' parameters fail as expected." diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_scrub/zpool_scrub_thorough.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_scrub/zpool_scrub_thorough.ksh new file mode 100755 index 000000000000..30a8756c8d1e --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_scrub/zpool_scrub_thorough.ksh @@ -0,0 +1,197 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2026 ConnectWise +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/cli_root/zpool_scrub/zpool_scrub.cfg + +# +# DESCRIPTION: +# 'zpool scrub' with thorough scrub enabled works with various configurations: +# 1) Compressed dataset +# 2) Compressed and encrypted dataset +# 3) Encrypted dataset +# 4) Compressed and encrypted dataset with keys unloaded +# 5) Compressed and encrypted dataset with key unloaded during thorough scrub +# 6) Verify thorough scrub pause/resume +# 7) Verify incompatible flags with -t +# 8) Verify compatible flags with -t +# 9) Thorough scrub after plaintext then encrypted data +# 10) After a normal scrub is paused, resume with -t is rejected +# + +function cleanup +{ + datasetexists $TESTPOOL/comp && destroy_dataset $TESTPOOL/comp + datasetexists $TESTPOOL/comp_enc && destroy_dataset $TESTPOOL/comp_enc + datasetexists $TESTPOOL/enc && destroy_dataset $TESTPOOL/enc + datasetexists $TESTPOOL/comp_enc_unloaded && destroy_dataset $TESTPOOL/comp_enc_unloaded + datasetexists $TESTPOOL/comp_enc_unloading && destroy_dataset $TESTPOOL/comp_enc_unloading + datasetexists $TESTPOOL/plain_sorted && destroy_dataset $TESTPOOL/plain_sorted + datasetexists $TESTPOOL/enc_sorted && destroy_dataset $TESTPOOL/enc_sorted + log_must set_tunable32 SCAN_SUSPEND_PROGRESS 0 + [[ -n "$ORIG_SCAN_LEGACY" ]] && \ + log_must set_tunable32 SCAN_LEGACY $ORIG_SCAN_LEGACY +} + +verify_runnable "global" + +log_onexit cleanup + +log_assert "Thorough scrub works with various dataset configurations." + +ORIG_SCAN_LEGACY=$(get_tunable SCAN_LEGACY) +log_must set_tunable32 SCAN_LEGACY 0 + +# 1) Compressed dataset +log_must zfs create $TESTPOOL/comp +log_must zfs set compression=on $TESTPOOL/comp +typeset file_comp="/$TESTPOOL/comp/$TESTFILE0" +log_must dd if=/dev/urandom of=$file_comp bs=1024 count=1024 oflag=sync +# Make sure data is compressible +write_compressible $(get_prop mountpoint $TESTPOOL/comp) 16m + +# 2) Compressed and encrypted dataset +log_must eval "echo 'password' | zfs create -o encryption=on -o keyformat=passphrase $TESTPOOL/comp_enc" +log_must zfs set compression=on $TESTPOOL/comp_enc +typeset file_comp_enc="/$TESTPOOL/comp_enc/$TESTFILE0" +log_must dd if=/dev/urandom of=$file_comp_enc bs=1024 count=1024 oflag=sync +write_compressible $(get_prop mountpoint $TESTPOOL/comp_enc) 16m + +# 3) Encrypted dataset +log_must eval "echo 'password' | zfs create -o encryption=on -o keyformat=passphrase $TESTPOOL/enc" +log_must zfs set compression=off $TESTPOOL/enc +typeset file_enc="/$TESTPOOL/enc/$TESTFILE0" +log_must dd if=/dev/urandom of=$file_enc bs=1024 count=1024 oflag=sync + +# 4) Compressed and encrypted dataset with keys unloaded +log_must eval "echo 'password' | zfs create -o encryption=on -o keyformat=passphrase $TESTPOOL/comp_enc_unloaded" +log_must zfs set compression=on $TESTPOOL/comp_enc_unloaded +typeset file_comp_enc_unloaded="/$TESTPOOL/comp_enc_unloaded/$TESTFILE0" +log_must dd if=/dev/urandom of=$file_comp_enc_unloaded bs=1024 count=1024 oflag=sync +write_compressible $(get_prop mountpoint $TESTPOOL/comp_enc_unloaded) 16m + +log_must zfs unmount $TESTPOOL/comp_enc_unloaded +log_must zfs unload-key $TESTPOOL/comp_enc_unloaded + +# Run and wait for thorough scrub +log_must zpool scrub -t -w $TESTPOOL +log_must check_pool_status $TESTPOOL "scan" "thorough" + +log_must check_pool_status $TESTPOOL "scan" "with 0 errors" +log_must check_pool_status $TESTPOOL "errors" "No known data errors" + +# 5) Compressed and encrypted dataset with key unloaded during scrub +log_must eval "echo 'password' | zfs create -o encryption=on -o keyformat=passphrase $TESTPOOL/comp_enc_unloading" +log_must zfs set compression=on $TESTPOOL/comp_enc_unloading +typeset file_enc_unloading="/$TESTPOOL/comp_enc_unloading/$TESTFILE0" +# Write enough data to allow time for unloading key +log_must dd if=/dev/urandom of=$file_enc_unloading bs=1024 count=10240 oflag=sync +write_compressible $(get_prop mountpoint $TESTPOOL/comp_enc_unloading) 16m + +log_must zfs unmount $TESTPOOL/comp_enc_unloading +# Key is still loaded + +# Start scrub (async) +log_must zpool scrub -t $TESTPOOL +log_mustnot zpool scrub $TESTPOOL + +# Unload key while scrub is running +log_must zfs unload-key $TESTPOOL/comp_enc_unloading + +# Wait for scrub +log_must zpool wait -t scrub $TESTPOOL + +log_must check_pool_status $TESTPOOL "scan" "with 0 errors" +log_must check_pool_status $TESTPOOL "errors" "No known data errors" + +# Run and wait for normal scrub +log_must zpool scrub -w $TESTPOOL + +log_must check_pool_status $TESTPOOL "scan" "with 0 errors" +log_must check_pool_status $TESTPOOL "errors" "No known data errors" + +# 6) Verify thorough scrub pause/resume +log_must pause_scrub $TESTPOOL -t +log_must check_pool_status $TESTPOOL "scan" "thorough" +log_must zpool scrub $TESTPOOL +log_must zpool wait -t scrub $TESTPOOL +log_must check_pool_status $TESTPOOL "errors" "No known data errors" + +log_must pause_scrub $TESTPOOL -t +log_must check_pool_status $TESTPOOL "scan" "thorough" +log_must zpool scrub -t $TESTPOOL +log_must check_pool_status $TESTPOOL "scan" "thorough" +log_must zpool wait -t scrub $TESTPOOL +log_must check_pool_status $TESTPOOL "scan" "thorough" +log_must check_pool_status $TESTPOOL "errors" "No known data errors" + +# 7) Verify incompatible flags with -t +log_mustnot zpool scrub -s -t $TESTPOOL +log_mustnot zpool scrub -p -t $TESTPOOL +log_mustnot zpool scrub -e -t $TESTPOOL +log_mustnot zpool scrub -t -C -S "2000-01-01" $TESTPOOL +log_mustnot zpool scrub -t -C -E "2099-12-31" $TESTPOOL +log_mustnot zpool scrub -t -C -S "2000-01-01" -E "2099-12-31" $TESTPOOL + +# 8) Verify compatible flags with -t +log_must zpool scrub -t -w -a +log_must zpool sync $TESTPOOL +log_must zpool scrub -t -C $TESTPOOL +log_must zpool wait -t scrub $TESTPOOL + +# Thorough scrub with date range (-S/-E); mutually exclusive with -C only +log_must zpool scrub -t -w -S "2000-01-01" $TESTPOOL +log_must check_pool_status $TESTPOOL "scan" "thorough" +log_must check_pool_status $TESTPOOL "errors" "No known data errors" + +log_must zpool scrub -t -w -E "2099-12-31" $TESTPOOL +log_must check_pool_status $TESTPOOL "scan" "thorough" +log_must check_pool_status $TESTPOOL "errors" "No known data errors" + +log_must zpool scrub -t -w -S "2000-01-01" -E "2099-12-31" $TESTPOOL +log_must check_pool_status $TESTPOOL "scan" "thorough" +log_must check_pool_status $TESTPOOL "errors" "No known data errors" + +# 9) Thorough scrub after plaintext then encrypted data +log_must zfs create $TESTPOOL/plain_sorted +typeset mnt_plain=$(get_prop mountpoint $TESTPOOL/plain_sorted) +log_must zfs set compression=on $TESTPOOL/plain_sorted +write_compressible $mnt_plain 16m +log_must dd if=/dev/urandom of=$mnt_plain/$TESTFILE1 bs=1024 count=1024 oflag=sync + +log_must eval "echo 'password' | zfs create -o encryption=on -o keyformat=passphrase \ + $TESTPOOL/enc_sorted" +typeset mnt_enc_sorted=$(get_prop mountpoint $TESTPOOL/enc_sorted) +log_must zfs set compression=on $TESTPOOL/enc_sorted +write_compressible $mnt_enc_sorted 16m +log_must dd if=/dev/urandom of=$mnt_enc_sorted/$TESTFILE1 bs=1024 count=1024 oflag=sync +log_must zpool sync $TESTPOOL + +log_must zpool scrub -t -w $TESTPOOL +log_must check_pool_status $TESTPOOL "scan" "with 0 errors" +log_must check_pool_status $TESTPOOL "errors" "No known data errors" + +# 10) After a normal scrub is paused, resume with -t is rejected +log_must pause_scrub $TESTPOOL +log_mustnot zpool scrub -t $TESTPOOL +log_must zpool scrub $TESTPOOL +log_mustnot check_pool_status $TESTPOOL "scan" "thorough" +log_must zpool wait -t scrub $TESTPOOL +log_must check_pool_status $TESTPOOL "scan" "with 0 errors" +log_must check_pool_status $TESTPOOL "errors" "No known data errors" + +log_pass "Thorough scrub works with various dataset configurations."