Skip to content

Commit fd15b18

Browse files
committed
implement thorough scrub support (zpool scrub -t)
This introduces the -t (thorough) flag to 'zpool scrub' command. A thorough scrub decrypts and decompresses blocks as they are read, allowing ZFS to catch rare corruption scenarios where the block's checksum matches the data on disk, but the block fails to decrypt or decompress. For encrypted datasets, the keys must be loaded to perform a thorough scrub. If the keys are not loaded, or are unloaded while the scrub is in progress, the scrub will fall back to a normal scrub for those encrypted blocks with key unloaded. Signed-off-by: Alek Pinchuk <apinchuk@axcient.com>
1 parent cd06f79 commit fd15b18

19 files changed

Lines changed: 593 additions & 114 deletions

File tree

cmd/zpool/zpool_main.c

Lines changed: 107 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
#include <sys/wait.h>
6060
#include <zfs_prop.h>
6161
#include <sys/fs/zfs.h>
62+
#include <sys/dsl_scan.h>
6263
#include <sys/stat.h>
6364
#include <sys/systeminfo.h>
6465
#include <sys/fm/fs/zfs.h>
@@ -512,8 +513,8 @@ get_usage(zpool_help_t idx)
512513
return (gettext("\tinitialize [-c | -s | -u] [-w] <-a | <pool> "
513514
"[<device> ...]>\n"));
514515
case HELP_SCRUB:
515-
return (gettext("\tscrub [-e | -s | -p | -C | -E | -S] [-w] "
516-
"<-a | <pool> [<pool> ...]>\n"));
516+
return (gettext("\tscrub [-e | -s | -p | -C | -E | -S | -t] "
517+
"[-w] <-a | <pool> [<pool> ...]>\n"));
517518
case HELP_RESILVER:
518519
return (gettext("\tresilver <pool> ...\n"));
519520
case HELP_TRIM:
@@ -8518,7 +8519,7 @@ date_string_to_sec(const char *timestr, boolean_t rounding)
85188519
}
85198520

85208521
/*
8521-
* zpool scrub [-e | -s | -p | -C | -E | -S] [-w] [-a | <pool> ...]
8522+
* zpool scrub [-e | -s | -p | -C | -E | -S | -t] [-w] [-a | <pool> ...]
85228523
*
85238524
* -a Scrub all pools.
85248525
* -e Only scrub blocks in the error log.
@@ -8527,6 +8528,7 @@ date_string_to_sec(const char *timestr, boolean_t rounding)
85278528
* -s Stop. Stops any in-progress scrub.
85288529
* -p Pause. Pause in-progress scrub.
85298530
* -w Wait. Blocks until scrub has completed.
8531+
* -t Decompress and decrypt (if key is loaded) scrubbed blocks.
85308532
* -C Scrub from last saved txg.
85318533
*/
85328534
int
@@ -8538,24 +8540,26 @@ zpool_do_scrub(int argc, char **argv)
85388540
int error;
85398541

85408542
cb.cb_type = POOL_SCAN_SCRUB;
8541-
cb.cb_scrub_cmd = POOL_SCRUB_NORMAL;
8543+
cb.cb_scrub_cmd = 0;
85428544
cb.cb_date_start = cb.cb_date_end = 0;
85438545

85448546
boolean_t is_error_scrub = B_FALSE;
85458547
boolean_t is_pause = B_FALSE;
85468548
boolean_t is_stop = B_FALSE;
8547-
boolean_t is_txg_continue = B_FALSE;
85488549
boolean_t scrub_all = B_FALSE;
85498550

85508551
/* check options */
8551-
while ((c = getopt(argc, argv, "aspweCE:S:")) != -1) {
8552+
while ((c = getopt(argc, argv, "aspweCE:S:t")) != -1) {
85528553
switch (c) {
85538554
case 'a':
85548555
scrub_all = B_TRUE;
85558556
break;
85568557
case 'e':
85578558
is_error_scrub = B_TRUE;
85588559
break;
8560+
case 't':
8561+
cb.cb_scrub_cmd |= POOL_SCRUB_THOROUGH;
8562+
break;
85598563
case 'E':
85608564
/*
85618565
* Round the date. It's better to scrub more data than
@@ -8576,7 +8580,7 @@ zpool_do_scrub(int argc, char **argv)
85768580
wait = B_TRUE;
85778581
break;
85788582
case 'C':
8579-
is_txg_continue = B_TRUE;
8583+
cb.cb_scrub_cmd |= POOL_SCRUB_FROM_LAST_TXG;
85808584
break;
85818585
case '?':
85828586
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
@@ -8589,17 +8593,38 @@ zpool_do_scrub(int argc, char **argv)
85898593
(void) fprintf(stderr, gettext("invalid option "
85908594
"combination: -s and -p are mutually exclusive\n"));
85918595
usage(B_FALSE);
8592-
} else if (is_pause && is_txg_continue) {
8596+
} else if (is_error_scrub && is_pause) {
8597+
(void) fprintf(stderr, gettext("invalid option "
8598+
"combination: -e and -p are mutually exclusive\n"));
8599+
usage(B_FALSE);
8600+
} else if (is_error_scrub && is_stop) {
8601+
(void) fprintf(stderr, gettext("invalid option "
8602+
"combination: -e and -s are mutually exclusive\n"));
8603+
usage(B_FALSE);
8604+
} else if (is_error_scrub &&
8605+
(cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
8606+
(void) fprintf(stderr, gettext("invalid option "
8607+
"combination: -e and -C are mutually exclusive\n"));
8608+
usage(B_FALSE);
8609+
} else if (is_error_scrub && (cb.cb_scrub_cmd & POOL_SCRUB_THOROUGH)) {
8610+
(void) fprintf(stderr, gettext("invalid option "
8611+
"combination: -e and -t are mutually exclusive\n"));
8612+
usage(B_FALSE);
8613+
} else if (is_pause && (cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
85938614
(void) fprintf(stderr, gettext("invalid option "
85948615
"combination: -p and -C are mutually exclusive\n"));
85958616
usage(B_FALSE);
8596-
} else if (is_stop && is_txg_continue) {
8617+
} else if (is_pause && (cb.cb_scrub_cmd & POOL_SCRUB_THOROUGH)) {
8618+
(void) fprintf(stderr, gettext("invalid option "
8619+
"combination: -p and -t are mutually exclusive\n"));
8620+
usage(B_FALSE);
8621+
} else if (is_stop && (cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
85978622
(void) fprintf(stderr, gettext("invalid option "
85988623
"combination: -s and -C are mutually exclusive\n"));
85998624
usage(B_FALSE);
8600-
} else if (is_error_scrub && is_txg_continue) {
8625+
} else if (is_stop && (cb.cb_scrub_cmd & POOL_SCRUB_THOROUGH)) {
86018626
(void) fprintf(stderr, gettext("invalid option "
8602-
"combination: -e and -C are mutually exclusive\n"));
8627+
"combination: -s and -t are mutually exclusive\n"));
86038628
usage(B_FALSE);
86048629
} else {
86058630
if (is_error_scrub)
@@ -8609,19 +8634,31 @@ zpool_do_scrub(int argc, char **argv)
86098634
cb.cb_scrub_cmd = POOL_SCRUB_PAUSE;
86108635
} else if (is_stop) {
86118636
cb.cb_type = POOL_SCAN_NONE;
8612-
} else if (is_txg_continue) {
8613-
cb.cb_scrub_cmd = POOL_SCRUB_FROM_LAST_TXG;
86148637
} else {
8615-
cb.cb_scrub_cmd = POOL_SCRUB_NORMAL;
8638+
if ((cb.cb_scrub_cmd & (POOL_SCRUB_NORMAL |
8639+
POOL_SCRUB_THOROUGH)) == 0)
8640+
cb.cb_scrub_cmd |= POOL_SCRUB_NORMAL;
86168641
}
86178642
}
86188643

8644+
uint64_t scrub_kind = cb.cb_scrub_cmd & (POOL_SCRUB_NORMAL |
8645+
POOL_SCRUB_THOROUGH);
86198646
if ((cb.cb_date_start != 0 || cb.cb_date_end != 0) &&
8620-
cb.cb_scrub_cmd != POOL_SCRUB_NORMAL) {
8621-
(void) fprintf(stderr, gettext("invalid option combination: "
8622-
"start/end date is available only with normal scrub\n"));
8647+
(cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
8648+
(void) fprintf(stderr, gettext("invalid option "
8649+
"combination: -C and -S/-E date are mutually "
8650+
"exclusive\n"));
8651+
usage(B_FALSE);
8652+
} else if ((cb.cb_date_start != 0 || cb.cb_date_end != 0) &&
8653+
(is_error_scrub ||
8654+
(scrub_kind != POOL_SCRUB_NORMAL &&
8655+
scrub_kind != POOL_SCRUB_THOROUGH))) {
8656+
(void) fprintf(stderr, gettext("invalid option "
8657+
"combination: start/end date is available only "
8658+
"with normal or thorough scrub\n"));
86238659
usage(B_FALSE);
86248660
}
8661+
86258662
if (cb.cb_date_start != 0 && cb.cb_date_end != 0 &&
86268663
cb.cb_date_start > cb.cb_date_end) {
86278664
(void) fprintf(stderr, gettext("invalid arguments: "
@@ -8941,7 +8978,7 @@ print_err_scrub_status(pool_scan_stat_t *ps)
89418978
* Print out detailed scrub status.
89428979
*/
89438980
static void
8944-
print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
8981+
print_scan_scrub_resilver_status(pool_scan_stat_t *ps, uint_t c)
89458982
{
89468983
time_t start, end, pause;
89478984
uint64_t pass_scanned, scanned, pass_issued, issued, total_s, total_i;
@@ -8976,10 +9013,22 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
89769013
secs_to_dhms(end - start, time_buf);
89779014

89789015
if (is_scrub) {
8979-
(void) printf(gettext("scrub repaired %s "
8980-
"in %s with %llu errors on %s"), processed_buf,
8981-
time_buf, (u_longlong_t)ps->pss_errors,
8982-
ctime(&end));
9016+
boolean_t is_thorough =
9017+
(c > offsetof(pool_scan_stat_t,
9018+
pss_pass_scrub_flags) / 8) &&
9019+
(ps->pss_pass_scrub_flags & DSF_SCRUB_THOROUGH) !=
9020+
0;
9021+
if (is_thorough) {
9022+
(void) printf(gettext("thorough scrub repaired"
9023+
" %s in %s with %llu errors on %s"),
9024+
processed_buf, time_buf,
9025+
(u_longlong_t)ps->pss_errors, ctime(&end));
9026+
} else {
9027+
(void) printf(gettext("scrub repaired %s "
9028+
"in %s with %llu errors on %s"),
9029+
processed_buf, time_buf,
9030+
(u_longlong_t)ps->pss_errors, ctime(&end));
9031+
}
89839032
} else if (is_resilver) {
89849033
(void) printf(gettext("resilvered %s "
89859034
"in %s with %llu errors on %s"), processed_buf,
@@ -8989,8 +9038,18 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
89899038
return;
89909039
} else if (ps->pss_state == DSS_CANCELED) {
89919040
if (is_scrub) {
8992-
(void) printf(gettext("scrub canceled on %s"),
8993-
ctime(&end));
9041+
boolean_t is_thorough =
9042+
(c > offsetof(pool_scan_stat_t,
9043+
pss_pass_scrub_flags) / 8) &&
9044+
(ps->pss_pass_scrub_flags & DSF_SCRUB_THOROUGH) !=
9045+
0;
9046+
if (is_thorough) {
9047+
(void) printf(gettext("thorough scrub canceled"
9048+
" on %s"), ctime(&end));
9049+
} else {
9050+
(void) printf(gettext("scrub canceled on %s"),
9051+
ctime(&end));
9052+
}
89949053
} else if (is_resilver) {
89959054
(void) printf(gettext("resilver canceled on %s"),
89969055
ctime(&end));
@@ -9002,14 +9061,30 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
90029061

90039062
/* Scan is in progress. Resilvers can't be paused. */
90049063
if (is_scrub) {
9064+
boolean_t is_thorough =
9065+
(c > offsetof(pool_scan_stat_t,
9066+
pss_pass_scrub_flags) / 8) &&
9067+
(ps->pss_pass_scrub_flags & DSF_SCRUB_THOROUGH) != 0;
90059068
if (pause == 0) {
9006-
(void) printf(gettext("scrub in progress since %s"),
9007-
ctime(&start));
9069+
if (is_thorough) {
9070+
(void) printf(gettext("thorough scrub in "
9071+
"progress since %s"), ctime(&start));
9072+
} else {
9073+
(void) printf(gettext("scrub in progress "
9074+
"since %s"), ctime(&start));
9075+
}
90089076
} else {
9009-
(void) printf(gettext("scrub paused since %s"),
9010-
ctime(&pause));
9011-
(void) printf(gettext("\tscrub started on %s"),
9012-
ctime(&start));
9077+
if (is_thorough) {
9078+
(void) printf(gettext("thorough scrub "
9079+
"paused since %s"), ctime(&pause));
9080+
(void) printf(gettext("\tthorough scrub "
9081+
"started on %s"), ctime(&start));
9082+
} else {
9083+
(void) printf(gettext("scrub paused since %s"),
9084+
ctime(&pause));
9085+
(void) printf(gettext("\tscrub started on %s"),
9086+
ctime(&start));
9087+
}
90139088
}
90149089
} else if (is_resilver) {
90159090
(void) printf(gettext("resilver in progress since %s"),
@@ -10124,7 +10199,7 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot)
1012410199

1012510200
/* Always print the scrub status when available. */
1012610201
if (have_scrub && scrub_start > errorscrub_start)
10127-
print_scan_scrub_resilver_status(ps);
10202+
print_scan_scrub_resilver_status(ps, c);
1012810203
else if (have_errorscrub && errorscrub_start >= scrub_start)
1012910204
print_err_scrub_status(ps);
1013010205

@@ -10134,7 +10209,7 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot)
1013410209
*/
1013510210
if (active_resilver || (!active_rebuild && have_resilver &&
1013610211
resilver_end_time && resilver_end_time > rebuild_end_time)) {
10137-
print_scan_scrub_resilver_status(ps);
10212+
print_scan_scrub_resilver_status(ps, c);
1013810213
} else if (active_rebuild || (!active_resilver && have_rebuild &&
1013910214
rebuild_end_time && rebuild_end_time > resilver_end_time)) {
1014010215
print_rebuild_status(zhp, nvroot);

cmd/ztest.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4230,7 +4230,7 @@ ztest_device_removal(ztest_ds_t *zd, uint64_t id)
42304230
* strategy employed by ztest_fault_inject() when selecting which
42314231
* offset are redundant and can be damaged.
42324232
*/
4233-
error = spa_scan(spa, POOL_SCAN_SCRUB);
4233+
error = spa_scan(spa, POOL_SCAN_SCRUB, POOL_SCRUB_NORMAL);
42344234
if (error == 0) {
42354235
while (dsl_scan_scrubbing(spa_get_dsl(spa)))
42364236
txg_wait_synced(spa_get_dsl(spa), 0);
@@ -6704,7 +6704,7 @@ ztest_fault_inject(ztest_ds_t *zd, uint64_t id)
67046704
mutex_exit(&ztest_vdev_lock);
67056705

67066706
if (injected && ztest_opts.zo_raid_do_expand) {
6707-
int error = spa_scan(spa, POOL_SCAN_SCRUB);
6707+
int error = spa_scan(spa, POOL_SCAN_SCRUB, POOL_SCRUB_NORMAL);
67086708
if (error == 0) {
67096709
while (dsl_scan_scrubbing(spa_get_dsl(spa)))
67106710
txg_wait_synced(spa_get_dsl(spa), 0);
@@ -6737,7 +6737,7 @@ ztest_fault_inject(ztest_ds_t *zd, uint64_t id)
67376737
static int
67386738
ztest_scrub_impl(spa_t *spa)
67396739
{
6740-
int error = spa_scan(spa, POOL_SCAN_SCRUB);
6740+
int error = spa_scan(spa, POOL_SCAN_SCRUB, POOL_SCRUB_NORMAL);
67416741
if (error)
67426742
return (error);
67436743

@@ -6771,7 +6771,7 @@ ztest_scrub(ztest_ds_t *zd, uint64_t id)
67716771
/*
67726772
* Start a scrub, wait a moment, then force a restart.
67736773
*/
6774-
(void) spa_scan(spa, POOL_SCAN_SCRUB);
6774+
(void) spa_scan(spa, POOL_SCAN_SCRUB, POOL_SCRUB_NORMAL);
67756775
(void) poll(NULL, 0, 100);
67766776

67776777
error = ztest_scrub_impl(spa);
@@ -7464,7 +7464,7 @@ ztest_spa_import_export(char *oldname, char *newname)
74647464
* Kick off a scrub to tickle scrub/export races.
74657465
*/
74667466
if (ztest_random(2) == 0)
7467-
(void) spa_scan(spa, POOL_SCAN_SCRUB);
7467+
(void) spa_scan(spa, POOL_SCAN_SCRUB, POOL_SCRUB_NORMAL);
74687468

74697469
pool_guid = spa_guid(spa);
74707470
spa_close(spa, FTAG);

include/sys/dsl_scan.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ typedef struct dsl_scan_phys {
7676
typedef enum dsl_scan_flags {
7777
DSF_VISIT_DS_AGAIN = 1<<0,
7878
DSF_SCRUB_PAUSED = 1<<1,
79+
DSF_SCRUB_THOROUGH = 1<<2,
7980
} dsl_scan_flags_t;
8081

8182
#define DSL_SCAN_FLAGS_MASK (DSF_VISIT_DS_AGAIN)
@@ -184,6 +185,7 @@ typedef struct {
184185
pool_scan_func_t func;
185186
uint64_t txgstart;
186187
uint64_t txgend;
188+
dsl_scan_flags_t flags;
187189
} setup_sync_arg_t;
188190

189191
typedef struct dsl_scan_io_queue dsl_scan_io_queue_t;
@@ -197,7 +199,7 @@ void dsl_scan_fini(struct dsl_pool *dp);
197199
void dsl_scan_sync(struct dsl_pool *, dmu_tx_t *);
198200
int dsl_scan_cancel(struct dsl_pool *);
199201
int dsl_scan(struct dsl_pool *, pool_scan_func_t, uint64_t starttxg,
200-
uint64_t txgend);
202+
uint64_t txgend, dsl_scan_flags_t flags);
201203
void dsl_scan_assess_vdev(struct dsl_pool *dp, vdev_t *vd);
202204
boolean_t dsl_scan_scrubbing(const struct dsl_pool *dp);
203205
boolean_t dsl_errorscrubbing(const struct dsl_pool *dp);
@@ -217,6 +219,7 @@ void dsl_scan_ds_clone_swapped(struct dsl_dataset *ds1, struct dsl_dataset *ds2,
217219
struct dmu_tx *tx);
218220
boolean_t dsl_scan_active(dsl_scan_t *scn);
219221
boolean_t dsl_scan_is_paused_scrub(const dsl_scan_t *scn);
222+
boolean_t dsl_scan_is_thorough_scrub(const dsl_scan_t *scn);
220223
boolean_t dsl_errorscrub_is_paused(const dsl_scan_t *scn);
221224
void dsl_scan_freed(spa_t *spa, const blkptr_t *bp);
222225
void dsl_scan_io_queue_destroy(dsl_scan_io_queue_t *queue);

include/sys/fs/zfs.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,13 +1203,16 @@ typedef enum pool_scan_func {
12031203
} pool_scan_func_t;
12041204

12051205
/*
1206-
* Used to control scrub pause and resume.
1206+
* Used to select scrub modes. POOL_SCRUB_NORMAL and POOL_SCRUB_THOROUGH
1207+
* are mutually exclusive scrub kinds; POOL_SCRUB_FROM_LAST_TXG may be ORed
1208+
* with exactly one of them.
1209+
* POOL_SCRUB_PAUSE may only be set by itself.
12071210
*/
12081211
typedef enum pool_scrub_cmd {
1209-
POOL_SCRUB_NORMAL = 0,
1210-
POOL_SCRUB_PAUSE,
1211-
POOL_SCRUB_FROM_LAST_TXG,
1212-
POOL_SCRUB_FLAGS_END
1212+
POOL_SCRUB_NORMAL = 1<<0,
1213+
POOL_SCRUB_PAUSE = 1<<1,
1214+
POOL_SCRUB_FROM_LAST_TXG = 1<<2,
1215+
POOL_SCRUB_THOROUGH = 1<<3,
12131216
} pool_scrub_cmd_t;
12141217

12151218
typedef enum {
@@ -1302,6 +1305,7 @@ typedef struct pool_scan_stat {
13021305
/* error scrub values not stored on disk */
13031306
/* error scrub pause time in milliseconds */
13041307
uint64_t pss_pass_error_scrub_pause;
1308+
uint64_t pss_pass_scrub_flags;
13051309

13061310
} pool_scan_stat_t;
13071311

include/sys/spa.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -848,9 +848,9 @@ extern void spa_l2cache_activate(vdev_t *vd);
848848
extern void spa_l2cache_drop(spa_t *spa);
849849

850850
/* scanning */
851-
extern int spa_scan(spa_t *spa, pool_scan_func_t func);
851+
extern int spa_scan(spa_t *spa, pool_scan_func_t func, uint64_t flags);
852852
extern int spa_scan_range(spa_t *spa, pool_scan_func_t func, uint64_t txgstart,
853-
uint64_t txgend);
853+
uint64_t txgend, uint64_t flags);
854854
extern int spa_scan_stop(spa_t *spa);
855855
extern int spa_scrub_pause_resume(spa_t *spa, pool_scrub_cmd_t flag);
856856

0 commit comments

Comments
 (0)