Skip to content

Commit 2d0d459

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 2d0d459

18 files changed

Lines changed: 499 additions & 91 deletions

File tree

cmd/zpool/zpool_main.c

Lines changed: 97 additions & 31 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,18 +8593,31 @@ 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_pause && (cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
85938597
(void) fprintf(stderr, gettext("invalid option "
85948598
"combination: -p and -C are mutually exclusive\n"));
85958599
usage(B_FALSE);
8596-
} else if (is_stop && is_txg_continue) {
8600+
} else if (is_stop && (cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
85978601
(void) fprintf(stderr, gettext("invalid option "
85988602
"combination: -s and -C are mutually exclusive\n"));
85998603
usage(B_FALSE);
8600-
} else if (is_error_scrub && is_txg_continue) {
8604+
} else if (is_error_scrub &&
8605+
(cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
86018606
(void) fprintf(stderr, gettext("invalid option "
86028607
"combination: -e and -C are mutually exclusive\n"));
86038608
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_THOROUGH)) {
8614+
(void) fprintf(stderr, gettext("invalid option "
8615+
"combination: -p and -t are mutually exclusive\n"));
8616+
usage(B_FALSE);
8617+
} else if (is_stop && (cb.cb_scrub_cmd & POOL_SCRUB_THOROUGH)) {
8618+
(void) fprintf(stderr, gettext("invalid option "
8619+
"combination: -s and -t are mutually exclusive\n"));
8620+
usage(B_FALSE);
86048621
} else {
86058622
if (is_error_scrub)
86068623
cb.cb_type = POOL_SCAN_ERRORSCRUB;
@@ -8609,19 +8626,30 @@ zpool_do_scrub(int argc, char **argv)
86098626
cb.cb_scrub_cmd = POOL_SCRUB_PAUSE;
86108627
} else if (is_stop) {
86118628
cb.cb_type = POOL_SCAN_NONE;
8612-
} else if (is_txg_continue) {
8613-
cb.cb_scrub_cmd = POOL_SCRUB_FROM_LAST_TXG;
86148629
} else {
8615-
cb.cb_scrub_cmd = POOL_SCRUB_NORMAL;
8630+
if ((cb.cb_scrub_cmd & (POOL_SCRUB_NORMAL |
8631+
POOL_SCRUB_THOROUGH)) == 0)
8632+
cb.cb_scrub_cmd |= POOL_SCRUB_NORMAL;
86168633
}
86178634
}
86188635

8636+
uint64_t scrub_kind = cb.cb_scrub_cmd & (POOL_SCRUB_NORMAL |
8637+
POOL_SCRUB_THOROUGH);
86198638
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"));
8639+
(cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
8640+
(void) fprintf(stderr, gettext("invalid option "
8641+
"combination: -C and -S/-E date are mutually "
8642+
"exclusive\n"));
8643+
usage(B_FALSE);
8644+
} else if ((cb.cb_date_start != 0 || cb.cb_date_end != 0) &&
8645+
scrub_kind != POOL_SCRUB_NORMAL &&
8646+
scrub_kind != POOL_SCRUB_THOROUGH) {
8647+
(void) fprintf(stderr, gettext("invalid option "
8648+
"combination: start/end date is available only "
8649+
"with normal or thorough scrub\n"));
86238650
usage(B_FALSE);
86248651
}
8652+
86258653
if (cb.cb_date_start != 0 && cb.cb_date_end != 0 &&
86268654
cb.cb_date_start > cb.cb_date_end) {
86278655
(void) fprintf(stderr, gettext("invalid arguments: "
@@ -8941,7 +8969,7 @@ print_err_scrub_status(pool_scan_stat_t *ps)
89418969
* Print out detailed scrub status.
89428970
*/
89438971
static void
8944-
print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
8972+
print_scan_scrub_resilver_status(pool_scan_stat_t *ps, uint_t c)
89458973
{
89468974
time_t start, end, pause;
89478975
uint64_t pass_scanned, scanned, pass_issued, issued, total_s, total_i;
@@ -8976,10 +9004,22 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
89769004
secs_to_dhms(end - start, time_buf);
89779005

89789006
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));
9007+
boolean_t is_thorough =
9008+
(c > offsetof(pool_scan_stat_t,
9009+
pss_pass_scrub_flags) / 8) &&
9010+
(ps->pss_pass_scrub_flags & DSF_SCRUB_THOROUGH) !=
9011+
0;
9012+
if (is_thorough) {
9013+
(void) printf(gettext("thorough scrub repaired"
9014+
" %s in %s with %llu errors on %s"),
9015+
processed_buf, time_buf,
9016+
(u_longlong_t)ps->pss_errors, ctime(&end));
9017+
} else {
9018+
(void) printf(gettext("scrub repaired %s "
9019+
"in %s with %llu errors on %s"),
9020+
processed_buf, time_buf,
9021+
(u_longlong_t)ps->pss_errors, ctime(&end));
9022+
}
89839023
} else if (is_resilver) {
89849024
(void) printf(gettext("resilvered %s "
89859025
"in %s with %llu errors on %s"), processed_buf,
@@ -8989,8 +9029,18 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
89899029
return;
89909030
} else if (ps->pss_state == DSS_CANCELED) {
89919031
if (is_scrub) {
8992-
(void) printf(gettext("scrub canceled on %s"),
8993-
ctime(&end));
9032+
boolean_t is_thorough =
9033+
(c > offsetof(pool_scan_stat_t,
9034+
pss_pass_scrub_flags) / 8) &&
9035+
(ps->pss_pass_scrub_flags & DSF_SCRUB_THOROUGH) !=
9036+
0;
9037+
if (is_thorough) {
9038+
(void) printf(gettext("thorough scrub canceled"
9039+
" on %s"), ctime(&end));
9040+
} else {
9041+
(void) printf(gettext("scrub canceled on %s"),
9042+
ctime(&end));
9043+
}
89949044
} else if (is_resilver) {
89959045
(void) printf(gettext("resilver canceled on %s"),
89969046
ctime(&end));
@@ -9002,14 +9052,30 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
90029052

90039053
/* Scan is in progress. Resilvers can't be paused. */
90049054
if (is_scrub) {
9055+
boolean_t is_thorough =
9056+
(c > offsetof(pool_scan_stat_t,
9057+
pss_pass_scrub_flags) / 8) &&
9058+
(ps->pss_pass_scrub_flags & DSF_SCRUB_THOROUGH) != 0;
90059059
if (pause == 0) {
9006-
(void) printf(gettext("scrub in progress since %s"),
9007-
ctime(&start));
9060+
if (is_thorough) {
9061+
(void) printf(gettext("thorough scrub in "
9062+
"progress since %s"), ctime(&start));
9063+
} else {
9064+
(void) printf(gettext("scrub in progress "
9065+
"since %s"), ctime(&start));
9066+
}
90089067
} else {
9009-
(void) printf(gettext("scrub paused since %s"),
9010-
ctime(&pause));
9011-
(void) printf(gettext("\tscrub started on %s"),
9012-
ctime(&start));
9068+
if (is_thorough) {
9069+
(void) printf(gettext("thorough scrub "
9070+
"paused since %s"), ctime(&pause));
9071+
(void) printf(gettext("\tthorough scrub "
9072+
"started on %s"), ctime(&start));
9073+
} else {
9074+
(void) printf(gettext("scrub paused since %s"),
9075+
ctime(&pause));
9076+
(void) printf(gettext("\tscrub started on %s"),
9077+
ctime(&start));
9078+
}
90139079
}
90149080
} else if (is_resilver) {
90159081
(void) printf(gettext("resilver in progress since %s"),
@@ -10124,7 +10190,7 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot)
1012410190

1012510191
/* Always print the scrub status when available. */
1012610192
if (have_scrub && scrub_start > errorscrub_start)
10127-
print_scan_scrub_resilver_status(ps);
10193+
print_scan_scrub_resilver_status(ps, c);
1012810194
else if (have_errorscrub && errorscrub_start >= scrub_start)
1012910195
print_err_scrub_status(ps);
1013010196

@@ -10134,7 +10200,7 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot)
1013410200
*/
1013510201
if (active_resilver || (!active_rebuild && have_resilver &&
1013610202
resilver_end_time && resilver_end_time > rebuild_end_time)) {
10137-
print_scan_scrub_resilver_status(ps);
10203+
print_scan_scrub_resilver_status(ps, c);
1013810204
} else if (active_rebuild || (!active_resilver && have_rebuild &&
1013910205
rebuild_end_time && rebuild_end_time > resilver_end_time)) {
1014010206
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

lib/libzfs/libzfs.abi

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6478,10 +6478,10 @@
64786478
<typedef-decl name='pool_scan_func_t' type-id='1b092565' id='7313fbe2'/>
64796479
<enum-decl name='pool_scrub_cmd' id='a1474cbd'>
64806480
<underlying-type type-id='9cac1fee'/>
6481-
<enumerator name='POOL_SCRUB_NORMAL' value='0'/>
6482-
<enumerator name='POOL_SCRUB_PAUSE' value='1'/>
6483-
<enumerator name='POOL_SCRUB_FROM_LAST_TXG' value='2'/>
6484-
<enumerator name='POOL_SCRUB_FLAGS_END' value='3'/>
6481+
<enumerator name='POOL_SCRUB_NORMAL' value='1'/>
6482+
<enumerator name='POOL_SCRUB_PAUSE' value='2'/>
6483+
<enumerator name='POOL_SCRUB_FROM_LAST_TXG' value='4'/>
6484+
<enumerator name='POOL_SCRUB_THOROUGH' value='8'/>
64856485
</enum-decl>
64866486
<typedef-decl name='pool_scrub_cmd_t' type-id='a1474cbd' id='b51cf3c2'/>
64876487
<enum-decl name='zpool_errata' id='d9abbf54'>

0 commit comments

Comments
 (0)