Skip to content

Commit 204d261

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 204d261

19 files changed

Lines changed: 561 additions & 108 deletions

File tree

cmd/zpool/zpool_main.c

Lines changed: 74 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,9 @@ 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 | -t | -C [-t] | "
517+
"(-E | -S) [-t]] [-w]\n"
518+
"\t <-a | <pool> [<pool> ...]>\n"));
517519
case HELP_RESILVER:
518520
return (gettext("\tresilver <pool> ...\n"));
519521
case HELP_TRIM:
@@ -8518,7 +8520,7 @@ date_string_to_sec(const char *timestr, boolean_t rounding)
85188520
}
85198521

85208522
/*
8521-
* zpool scrub [-e | -s | -p | -C | -E | -S] [-w] [-a | <pool> ...]
8523+
* zpool scrub [-e | -s | -p | -C | -E | -S | -t] [-w] [-a | <pool> ...]
85228524
*
85238525
* -a Scrub all pools.
85248526
* -e Only scrub blocks in the error log.
@@ -8527,6 +8529,7 @@ date_string_to_sec(const char *timestr, boolean_t rounding)
85278529
* -s Stop. Stops any in-progress scrub.
85288530
* -p Pause. Pause in-progress scrub.
85298531
* -w Wait. Blocks until scrub has completed.
8532+
* -t Decompress and decrypt (if key is loaded) scrubbed blocks.
85308533
* -C Scrub from last saved txg.
85318534
*/
85328535
int
@@ -8538,24 +8541,26 @@ zpool_do_scrub(int argc, char **argv)
85388541
int error;
85398542

85408543
cb.cb_type = POOL_SCAN_SCRUB;
8541-
cb.cb_scrub_cmd = POOL_SCRUB_NORMAL;
8544+
cb.cb_scrub_cmd = 0;
85428545
cb.cb_date_start = cb.cb_date_end = 0;
85438546

85448547
boolean_t is_error_scrub = B_FALSE;
85458548
boolean_t is_pause = B_FALSE;
85468549
boolean_t is_stop = B_FALSE;
8547-
boolean_t is_txg_continue = B_FALSE;
85488550
boolean_t scrub_all = B_FALSE;
85498551

85508552
/* check options */
8551-
while ((c = getopt(argc, argv, "aspweCE:S:")) != -1) {
8553+
while ((c = getopt(argc, argv, "aspweCE:S:t")) != -1) {
85528554
switch (c) {
85538555
case 'a':
85548556
scrub_all = B_TRUE;
85558557
break;
85568558
case 'e':
85578559
is_error_scrub = B_TRUE;
85588560
break;
8561+
case 't':
8562+
cb.cb_scrub_cmd |= POOL_SCRUB_THOROUGH;
8563+
break;
85598564
case 'E':
85608565
/*
85618566
* Round the date. It's better to scrub more data than
@@ -8576,7 +8581,7 @@ zpool_do_scrub(int argc, char **argv)
85768581
wait = B_TRUE;
85778582
break;
85788583
case 'C':
8579-
is_txg_continue = B_TRUE;
8584+
cb.cb_scrub_cmd |= POOL_SCRUB_FROM_LAST_TXG;
85808585
break;
85818586
case '?':
85828587
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
@@ -8589,17 +8594,38 @@ zpool_do_scrub(int argc, char **argv)
85898594
(void) fprintf(stderr, gettext("invalid option "
85908595
"combination: -s and -p are mutually exclusive\n"));
85918596
usage(B_FALSE);
8592-
} else if (is_pause && is_txg_continue) {
8597+
} else if (is_error_scrub && is_pause) {
8598+
(void) fprintf(stderr, gettext("invalid option "
8599+
"combination: -e and -p are mutually exclusive\n"));
8600+
usage(B_FALSE);
8601+
} else if (is_error_scrub && is_stop) {
8602+
(void) fprintf(stderr, gettext("invalid option "
8603+
"combination: -e and -s are mutually exclusive\n"));
8604+
usage(B_FALSE);
8605+
} else if (is_error_scrub &&
8606+
(cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
8607+
(void) fprintf(stderr, gettext("invalid option "
8608+
"combination: -e and -C are mutually exclusive\n"));
8609+
usage(B_FALSE);
8610+
} else if (is_error_scrub && (cb.cb_scrub_cmd & POOL_SCRUB_THOROUGH)) {
8611+
(void) fprintf(stderr, gettext("invalid option "
8612+
"combination: -e and -t are mutually exclusive\n"));
8613+
usage(B_FALSE);
8614+
} else if (is_pause && (cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
85938615
(void) fprintf(stderr, gettext("invalid option "
85948616
"combination: -p and -C are mutually exclusive\n"));
85958617
usage(B_FALSE);
8596-
} else if (is_stop && is_txg_continue) {
8618+
} else if (is_pause && (cb.cb_scrub_cmd & POOL_SCRUB_THOROUGH)) {
8619+
(void) fprintf(stderr, gettext("invalid option "
8620+
"combination: -p and -t are mutually exclusive\n"));
8621+
usage(B_FALSE);
8622+
} else if (is_stop && (cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
85978623
(void) fprintf(stderr, gettext("invalid option "
85988624
"combination: -s and -C are mutually exclusive\n"));
85998625
usage(B_FALSE);
8600-
} else if (is_error_scrub && is_txg_continue) {
8626+
} else if (is_stop && (cb.cb_scrub_cmd & POOL_SCRUB_THOROUGH)) {
86018627
(void) fprintf(stderr, gettext("invalid option "
8602-
"combination: -e and -C are mutually exclusive\n"));
8628+
"combination: -s and -t are mutually exclusive\n"));
86038629
usage(B_FALSE);
86048630
} else {
86058631
if (is_error_scrub)
@@ -8609,19 +8635,28 @@ zpool_do_scrub(int argc, char **argv)
86098635
cb.cb_scrub_cmd = POOL_SCRUB_PAUSE;
86108636
} else if (is_stop) {
86118637
cb.cb_type = POOL_SCAN_NONE;
8612-
} else if (is_txg_continue) {
8613-
cb.cb_scrub_cmd = POOL_SCRUB_FROM_LAST_TXG;
8614-
} else {
8615-
cb.cb_scrub_cmd = POOL_SCRUB_NORMAL;
86168638
}
86178639
}
86188640

8641+
uint64_t scrub_kind = cb.cb_scrub_cmd & POOL_SCRUB_THOROUGH;
8642+
if (scrub_kind == 0)
8643+
scrub_kind = POOL_SCRUB_NORMAL;
86198644
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"));
8645+
(cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
8646+
(void) fprintf(stderr, gettext("invalid option "
8647+
"combination: -C and -S/-E date are mutually "
8648+
"exclusive\n"));
8649+
usage(B_FALSE);
8650+
} else if ((cb.cb_date_start != 0 || cb.cb_date_end != 0) &&
8651+
(is_error_scrub ||
8652+
(scrub_kind != POOL_SCRUB_NORMAL &&
8653+
scrub_kind != POOL_SCRUB_THOROUGH))) {
8654+
(void) fprintf(stderr, gettext("invalid option "
8655+
"combination: start/end date is available only "
8656+
"with normal or thorough scrub\n"));
86238657
usage(B_FALSE);
86248658
}
8659+
86258660
if (cb.cb_date_start != 0 && cb.cb_date_end != 0 &&
86268661
cb.cb_date_start > cb.cb_date_end) {
86278662
(void) fprintf(stderr, gettext("invalid arguments: "
@@ -8941,7 +8976,7 @@ print_err_scrub_status(pool_scan_stat_t *ps)
89418976
* Print out detailed scrub status.
89428977
*/
89438978
static void
8944-
print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
8979+
print_scan_scrub_resilver_status(pool_scan_stat_t *ps, boolean_t is_thorough)
89458980
{
89468981
time_t start, end, pause;
89478982
uint64_t pass_scanned, scanned, pass_issued, issued, total_s, total_i;
@@ -8976,10 +9011,11 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
89769011
secs_to_dhms(end - start, time_buf);
89779012

89789013
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));
9014+
(void) printf(gettext("%sscrub repaired %s "
9015+
"in %s with %llu errors on %s"),
9016+
is_thorough ? "thorough " : "",
9017+
processed_buf, time_buf,
9018+
(u_longlong_t)ps->pss_errors, ctime(&end));
89839019
} else if (is_resilver) {
89849020
(void) printf(gettext("resilvered %s "
89859021
"in %s with %llu errors on %s"), processed_buf,
@@ -8989,7 +9025,8 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
89899025
return;
89909026
} else if (ps->pss_state == DSS_CANCELED) {
89919027
if (is_scrub) {
8992-
(void) printf(gettext("scrub canceled on %s"),
9028+
(void) printf(gettext("%sscrub canceled on %s"),
9029+
is_thorough ? "thorough " : "",
89939030
ctime(&end));
89949031
} else if (is_resilver) {
89959032
(void) printf(gettext("resilver canceled on %s"),
@@ -9003,12 +9040,15 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
90039040
/* Scan is in progress. Resilvers can't be paused. */
90049041
if (is_scrub) {
90059042
if (pause == 0) {
9006-
(void) printf(gettext("scrub in progress since %s"),
9043+
(void) printf(gettext("%sscrub in progress since %s"),
9044+
is_thorough ? "thorough " : "",
90079045
ctime(&start));
90089046
} else {
9009-
(void) printf(gettext("scrub paused since %s"),
9047+
(void) printf(gettext("%sscrub paused since %s"),
9048+
is_thorough ? "thorough " : "",
90109049
ctime(&pause));
9011-
(void) printf(gettext("\tscrub started on %s"),
9050+
(void) printf(gettext("\t%sscrub started on %s"),
9051+
is_thorough ? "thorough " : "",
90129052
ctime(&start));
90139053
}
90149054
} else if (is_resilver) {
@@ -10099,6 +10139,7 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot)
1009910139
pool_checkpoint_stat_t *pcs = NULL;
1010010140
pool_scan_stat_t *ps = NULL;
1010110141
uint_t c;
10142+
boolean_t is_thorough = B_FALSE;
1010210143
time_t scrub_start = 0, errorscrub_start = 0;
1010310144

1010410145
if (nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_SCAN_STATS,
@@ -10111,8 +10152,10 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot)
1011110152
have_resilver = (ps->pss_func == POOL_SCAN_RESILVER);
1011210153
have_scrub = (ps->pss_func == POOL_SCAN_SCRUB);
1011310154
scrub_start = ps->pss_start_time;
10114-
if (c > offsetof(pool_scan_stat_t,
10115-
pss_pass_error_scrub_pause) / 8) {
10155+
if (POOL_SCAN_STAT_VALID(pss_pass_scrub_flags, c) &&
10156+
(ps->pss_pass_scrub_flags & DSF_SCRUB_THOROUGH) != 0)
10157+
is_thorough = B_TRUE;
10158+
if (POOL_SCAN_STAT_VALID(pss_pass_error_scrub_pause, c)) {
1011610159
have_errorscrub = (ps->pss_error_scrub_func ==
1011710160
POOL_SCAN_ERRORSCRUB);
1011810161
errorscrub_start = ps->pss_error_scrub_start;
@@ -10124,7 +10167,7 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot)
1012410167

1012510168
/* Always print the scrub status when available. */
1012610169
if (have_scrub && scrub_start > errorscrub_start)
10127-
print_scan_scrub_resilver_status(ps);
10170+
print_scan_scrub_resilver_status(ps, is_thorough);
1012810171
else if (have_errorscrub && errorscrub_start >= scrub_start)
1012910172
print_err_scrub_status(ps);
1013010173

@@ -10134,7 +10177,7 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot)
1013410177
*/
1013510178
if (active_resilver || (!active_rebuild && have_resilver &&
1013610179
resilver_end_time && resilver_end_time > rebuild_end_time)) {
10137-
print_scan_scrub_resilver_status(ps);
10180+
print_scan_scrub_resilver_status(ps, is_thorough);
1013810181
} else if (active_rebuild || (!active_resilver && have_rebuild &&
1013910182
rebuild_end_time && rebuild_end_time > resilver_end_time)) {
1014010183
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: 3 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);

include/sys/fs/zfs.h

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

12051205
/*
1206-
* Used to control scrub pause and resume.
1206+
* POOL_SCRUB_NORMAL, POOL_SCRUB_PAUSE, and POOL_SCRUB_FROM_LAST_TXG have
1207+
* their historical integer values (0, 1, 2) for user/kernel ABI
1208+
* compatibility.
1209+
*
1210+
* POOL_SCRUB_THOROUGH uses a non-colliding value so it can be combined with
1211+
* POOL_SCRUB_FROM_LAST_TXG. "Normal" scrub is implied when the thorough
1212+
* bit is clear (command 0, 2, or date-scrub with command 0).
1213+
*
1214+
* POOL_SCRUB_PAUSE must appear alone (scan_command == 1).
12071215
*/
12081216
typedef enum pool_scrub_cmd {
12091217
POOL_SCRUB_NORMAL = 0,
1210-
POOL_SCRUB_PAUSE,
1211-
POOL_SCRUB_FROM_LAST_TXG,
1212-
POOL_SCRUB_FLAGS_END
1218+
POOL_SCRUB_PAUSE = 1,
1219+
POOL_SCRUB_FROM_LAST_TXG = 2,
1220+
POOL_SCRUB_FLAGS_END = 3,
1221+
POOL_SCRUB_THOROUGH = 8,
12131222
} pool_scrub_cmd_t;
12141223

12151224
typedef enum {
@@ -1302,9 +1311,15 @@ typedef struct pool_scan_stat {
13021311
/* error scrub values not stored on disk */
13031312
/* error scrub pause time in milliseconds */
13041313
uint64_t pss_pass_error_scrub_pause;
1314+
uint64_t pss_pass_scrub_flags;
13051315

13061316
} pool_scan_stat_t;
13071317

1318+
#define POOL_SCAN_STAT_VALID(field, uint64_t_field_count) \
1319+
((uint64_t_field_count * sizeof (uint64_t)) >= \
1320+
(offsetof(pool_scan_stat_t, field) + \
1321+
sizeof (((pool_scan_stat_t *)NULL)->field)))
1322+
13081323
typedef struct pool_removal_stat {
13091324
uint64_t prs_state; /* dsl_scan_state_t */
13101325
uint64_t prs_removing_vdev;

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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6482,6 +6482,7 @@
64826482
<enumerator name='POOL_SCRUB_PAUSE' value='1'/>
64836483
<enumerator name='POOL_SCRUB_FROM_LAST_TXG' value='2'/>
64846484
<enumerator name='POOL_SCRUB_FLAGS_END' value='3'/>
6485+
<enumerator name='POOL_SCRUB_THOROUGH' value='8'/>
64856486
</enum-decl>
64866487
<typedef-decl name='pool_scrub_cmd_t' type-id='a1474cbd' id='b51cf3c2'/>
64876488
<enum-decl name='zpool_errata' id='d9abbf54'>

0 commit comments

Comments
 (0)