Skip to content

Commit 42d967f

Browse files
committed
Add last_scrubbed_txg property and option to scrub from last saved txg
The `last_scrubbed_txg` property indicates the transaction group (TXG) up to which the most recent scrub operation has checked and repaired the dataset. This provides administrators with insight into the data integrity status of their pool at a specific point in time. Sponsored-By: Wasabi Technology, Inc. Sponsored-By: Klara Inc. Signed-off-by: Mariusz Zaborski <[email protected]>
1 parent 815ac54 commit 42d967f

File tree

16 files changed

+188
-9
lines changed

16 files changed

+188
-9
lines changed

cmd/zpool/zpool_main.c

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,8 @@ get_usage(zpool_help_t idx)
415415
return (gettext("\tinitialize [-c | -s | -u] [-w] <pool> "
416416
"[<device> ...]\n"));
417417
case HELP_SCRUB:
418-
return (gettext("\tscrub [-s | -p] [-w] [-e] <pool> ...\n"));
418+
return (gettext("\tscrub [-s | -p] [-w] [-e] [-C] "
419+
"<pool> ...\n"));
419420
case HELP_RESILVER:
420421
return (gettext("\tresilver <pool> ...\n"));
421422
case HELP_TRIM:
@@ -7741,8 +7742,9 @@ wait_callback(zpool_handle_t *zhp, void *data)
77417742
}
77427743

77437744
/*
7744-
* zpool scrub [-s | -p] [-w] [-e] <pool> ...
7745+
* zpool scrub [-s | -p] [-w] [-e] [-C] <pool> ...
77457746
*
7747+
* -C Scrub from last saved txg.
77467748
* -e Only scrub blocks in the error log.
77477749
* -s Stop. Stops any in-progress scrub.
77487750
* -p Pause. Pause in-progress scrub.
@@ -7762,10 +7764,14 @@ zpool_do_scrub(int argc, char **argv)
77627764
boolean_t is_error_scrub = B_FALSE;
77637765
boolean_t is_pause = B_FALSE;
77647766
boolean_t is_stop = B_FALSE;
7767+
boolean_t is_txg_continue = B_FALSE;
77657768

77667769
/* check options */
7767-
while ((c = getopt(argc, argv, "spwe")) != -1) {
7770+
while ((c = getopt(argc, argv, "spweC")) != -1) {
77687771
switch (c) {
7772+
case 'C':
7773+
is_txg_continue = B_TRUE;
7774+
break;
77697775
case 'e':
77707776
is_error_scrub = B_TRUE;
77717777
break;
@@ -7789,6 +7795,18 @@ zpool_do_scrub(int argc, char **argv)
77897795
(void) fprintf(stderr, gettext("invalid option "
77907796
"combination :-s and -p are mutually exclusive\n"));
77917797
usage(B_FALSE);
7798+
} else if (is_pause && is_txg_continue) {
7799+
(void) fprintf(stderr, gettext("invalid option "
7800+
"combination :-p and -C are mutually exclusive\n"));
7801+
usage(B_FALSE);
7802+
} else if (is_stop && is_txg_continue) {
7803+
(void) fprintf(stderr, gettext("invalid option "
7804+
"combination :-s and -C are mutually exclusive\n"));
7805+
usage(B_FALSE);
7806+
} else if (is_error_scrub && is_txg_continue) {
7807+
(void) fprintf(stderr, gettext("invalid option "
7808+
"combination :-e and -C are mutually exclusive\n"));
7809+
usage(B_FALSE);
77927810
} else {
77937811
if (is_error_scrub)
77947812
cb.cb_type = POOL_SCAN_ERRORSCRUB;
@@ -7797,6 +7815,8 @@ zpool_do_scrub(int argc, char **argv)
77977815
cb.cb_scrub_cmd = POOL_SCRUB_PAUSE;
77987816
} else if (is_stop) {
77997817
cb.cb_type = POOL_SCAN_NONE;
7818+
} else if (is_txg_continue) {
7819+
cb.cb_scrub_cmd = POOL_SCRUB_FROM_LAST_TXG;
78007820
} else {
78017821
cb.cb_scrub_cmd = POOL_SCRUB_NORMAL;
78027822
}

include/sys/dmu.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ typedef struct dmu_buf {
379379
#define DMU_POOL_CREATION_VERSION "creation_version"
380380
#define DMU_POOL_SCAN "scan"
381381
#define DMU_POOL_ERRORSCRUB "error_scrub"
382+
#define DMU_POOL_LAST_SCRUBBED_TXG "last_scrubbed_txg"
382383
#define DMU_POOL_FREE_BPOBJ "free_bpobj"
383384
#define DMU_POOL_BPTREE_OBJ "bptree_obj"
384385
#define DMU_POOL_EMPTY_BPOBJ "empty_bpobj"

include/sys/fs/zfs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ typedef enum {
258258
ZPOOL_PROP_BCLONEUSED,
259259
ZPOOL_PROP_BCLONESAVED,
260260
ZPOOL_PROP_BCLONERATIO,
261+
ZPOOL_PROP_LAST_SCRUBBED_TXG,
261262
ZPOOL_NUM_PROPS
262263
} zpool_prop_t;
263264

@@ -1069,6 +1070,7 @@ typedef enum pool_scan_func {
10691070
typedef enum pool_scrub_cmd {
10701071
POOL_SCRUB_NORMAL = 0,
10711072
POOL_SCRUB_PAUSE,
1073+
POOL_SCRUB_FROM_LAST_TXG,
10721074
POOL_SCRUB_FLAGS_END
10731075
} pool_scrub_cmd_t;
10741076

include/sys/spa.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,6 +1065,7 @@ extern uint64_t spa_get_deadman_failmode(spa_t *spa);
10651065
extern void spa_set_deadman_failmode(spa_t *spa, const char *failmode);
10661066
extern boolean_t spa_suspended(spa_t *spa);
10671067
extern uint64_t spa_bootfs(spa_t *spa);
1068+
extern uint64_t spa_get_last_scrubbed_txg(spa_t *spa);
10681069
extern uint64_t spa_delegation(spa_t *spa);
10691070
extern objset_t *spa_meta_objset(spa_t *spa);
10701071
extern space_map_t *spa_syncing_log_sm(spa_t *spa);

include/sys/spa_impl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ struct spa {
317317
uint64_t spa_scan_pass_scrub_spent_paused; /* total paused */
318318
uint64_t spa_scan_pass_exam; /* examined bytes per pass */
319319
uint64_t spa_scan_pass_issued; /* issued bytes per pass */
320+
uint64_t spa_scrubbed_last_txg; /* last txg scrubbed */
320321

321322
/* error scrub pause time in milliseconds */
322323
uint64_t spa_scan_pass_errorscrub_pause;

lib/libzfs/libzfs.abi

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2921,7 +2921,8 @@
29212921
<enumerator name='ZPOOL_PROP_BCLONEUSED' value='33'/>
29222922
<enumerator name='ZPOOL_PROP_BCLONESAVED' value='34'/>
29232923
<enumerator name='ZPOOL_PROP_BCLONERATIO' value='35'/>
2924-
<enumerator name='ZPOOL_NUM_PROPS' value='36'/>
2924+
<enumerator name='ZPOOL_PROP_LAST_SCRUBBED_TXG' value='36'/>
2925+
<enumerator name='ZPOOL_NUM_PROPS' value='37'/>
29252926
</enum-decl>
29262927
<typedef-decl name='zpool_prop_t' type-id='af1ba157' id='5d0c23fb'/>
29272928
<typedef-decl name='regoff_t' type-id='95e97e5e' id='54a2a2a8'/>
@@ -5770,7 +5771,8 @@
57705771
<underlying-type type-id='9cac1fee'/>
57715772
<enumerator name='POOL_SCRUB_NORMAL' value='0'/>
57725773
<enumerator name='POOL_SCRUB_PAUSE' value='1'/>
5773-
<enumerator name='POOL_SCRUB_FLAGS_END' value='2'/>
5774+
<enumerator name='POOL_SCRUB_FROM_LAST_TXG' value='2'/>
5775+
<enumerator name='POOL_SCRUB_FLAGS_END' value='3'/>
57745776
</enum-decl>
57755777
<typedef-decl name='pool_scrub_cmd_t' type-id='a1474cbd' id='b51cf3c2'/>
57765778
<enum-decl name='zpool_errata' id='d9abbf54'>

lib/libzfs/libzfs_pool.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf,
342342
case ZPOOL_PROP_MAXDNODESIZE:
343343
case ZPOOL_PROP_BCLONESAVED:
344344
case ZPOOL_PROP_BCLONEUSED:
345+
case ZPOOL_PROP_LAST_SCRUBBED_TXG:
345346
if (literal)
346347
(void) snprintf(buf, len, "%llu",
347348
(u_longlong_t)intval);

man/man7/zpoolprops.7

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
.\" Copyright (c) 2021, Colm Buckley <[email protected]>
2929
.\" Copyright (c) 2023, Klara Inc.
3030
.\"
31-
.Dd January 2, 2024
31+
.Dd June 20, 2024
3232
.Dt ZPOOLPROPS 7
3333
.Os
3434
.
@@ -129,6 +129,14 @@ A unique identifier for the pool.
129129
The current health of the pool.
130130
Health can be one of
131131
.Sy ONLINE , DEGRADED , FAULTED , OFFLINE, REMOVED , UNAVAIL .
132+
.It Sy last_scrubbed_txg
133+
Indicates the transaction group (TXG) up to which the most recent scrub
134+
operation has checked and repaired the dataset.
135+
This provides insight into the data integrity status of their pool at
136+
a specific point in time.
137+
The
138+
.Xr zpool-scrub 8
139+
might be used to utilize this property.
132140
.It Sy leaked
133141
Space not released while
134142
.Sy freeing

man/man8/zpool-scrub.8

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
.Op Fl s Ns | Ns Fl p
4040
.Op Fl w
4141
.Op Fl e
42+
.Op Fl C
4243
.Ar pool Ns
4344
.
4445
.Sh DESCRIPTION
@@ -114,6 +115,10 @@ The pool must have been scrubbed at least once with the
114115
feature enabled to use this option.
115116
Error scrubbing cannot be run simultaneously with regular scrubbing or
116117
resilvering, nor can it be run when a regular scrub is paused.
118+
.It Fl C
119+
Continue scrub from last saved txg (see zpool
120+
.Sy last_scrubbed_txg
121+
property).
117122
.El
118123
.Sh EXAMPLES
119124
.Ss Example 1

module/zcommon/zpool_prop.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ zpool_prop_init(void)
125125
zprop_register_number(ZPOOL_PROP_BCLONERATIO, "bcloneratio", 0,
126126
PROP_READONLY, ZFS_TYPE_POOL, "<1.00x or higher if cloned>",
127127
"BCLONE_RATIO", B_FALSE, sfeatures);
128+
zprop_register_number(ZPOOL_PROP_LAST_SCRUBBED_TXG,
129+
"last_scrubbed_txg", 0, PROP_READONLY, ZFS_TYPE_POOL, "<txg>",
130+
"LAST_SCRUBBED_TXG", B_FALSE, sfeatures);
128131

129132
/* default number properties */
130133
zprop_register_number(ZPOOL_PROP_VERSION, "version", SPA_VERSION,

module/zfs/dsl_scan.c

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,9 @@ static int zfs_resilver_disable_defer = B_FALSE;
228228
((scn)->scn_phys.scn_func == POOL_SCAN_SCRUB || \
229229
(scn)->scn_phys.scn_func == POOL_SCAN_RESILVER)
230230

231+
#define DSL_SCAN_IS_SCRUB(scn) \
232+
((scn)->scn_phys.scn_func == POOL_SCAN_SCRUB)
233+
231234
/*
232235
* Enable/disable the processing of the free_bpobj object.
233236
*/
@@ -1129,15 +1132,24 @@ dsl_scan_done(dsl_scan_t *scn, boolean_t complete, dmu_tx_t *tx)
11291132

11301133
spa_notify_waiters(spa);
11311134

1132-
if (dsl_scan_restarting(scn, tx))
1135+
if (dsl_scan_restarting(scn, tx)) {
11331136
spa_history_log_internal(spa, "scan aborted, restarting", tx,
11341137
"errors=%llu", (u_longlong_t)spa_approx_errlog_size(spa));
1135-
else if (!complete)
1138+
} else if (!complete) {
11361139
spa_history_log_internal(spa, "scan cancelled", tx,
11371140
"errors=%llu", (u_longlong_t)spa_approx_errlog_size(spa));
1138-
else
1141+
} else {
11391142
spa_history_log_internal(spa, "scan done", tx,
11401143
"errors=%llu", (u_longlong_t)spa_approx_errlog_size(spa));
1144+
if (DSL_SCAN_IS_SCRUB(scn)) {
1145+
VERIFY0(zap_update(dp->dp_meta_objset,
1146+
DMU_POOL_DIRECTORY_OBJECT,
1147+
DMU_POOL_LAST_SCRUBBED_TXG,
1148+
sizeof (uint64_t), 1,
1149+
&scn->scn_phys.scn_max_txg, tx));
1150+
spa->spa_scrubbed_last_txg = scn->scn_phys.scn_max_txg;
1151+
}
1152+
}
11411153

11421154
if (DSL_SCAN_IS_SCRUB_RESILVER(scn)) {
11431155
spa->spa_scrub_active = B_FALSE;

module/zfs/spa.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,9 @@ spa_prop_get_config(spa_t *spa, nvlist_t **nvp)
406406
spa_prop_add_list(*nvp, ZPOOL_PROP_BCLONERATIO, NULL,
407407
brt_get_ratio(spa), src);
408408

409+
spa_prop_add_list(*nvp, ZPOOL_PROP_LAST_SCRUBBED_TXG, NULL,
410+
spa_get_last_scrubbed_txg(spa), src);
411+
409412
spa_prop_add_list(*nvp, ZPOOL_PROP_HEALTH, NULL,
410413
rvd->vdev_state, src);
411414

@@ -4660,6 +4663,12 @@ spa_ld_get_props(spa_t *spa)
46604663
if (error != 0 && error != ENOENT)
46614664
return (spa_vdev_err(rvd, VDEV_AUX_CORRUPT_DATA, EIO));
46624665

4666+
/* Load the last scrubbed txg. */
4667+
error = spa_dir_prop(spa, DMU_POOL_LAST_SCRUBBED_TXG,
4668+
&spa->spa_scrubbed_last_txg, B_FALSE);
4669+
if (error != 0 && error != ENOENT)
4670+
return (spa_vdev_err(rvd, VDEV_AUX_CORRUPT_DATA, EIO));
4671+
46634672
/*
46644673
* Load the livelist deletion field. If a livelist is queued for
46654674
* deletion, indicate that in the spa

module/zfs/spa_misc.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2660,6 +2660,12 @@ spa_mode(spa_t *spa)
26602660
return (spa->spa_mode);
26612661
}
26622662

2663+
uint64_t
2664+
spa_get_last_scrubbed_txg(spa_t *spa)
2665+
{
2666+
return (spa->spa_scrubbed_last_txg);
2667+
}
2668+
26632669
uint64_t
26642670
spa_bootfs(spa_t *spa)
26652671
{

module/zfs/zfs_ioctl.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,6 +1719,9 @@ zfs_ioc_pool_scrub(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
17191719
error = spa_scrub_pause_resume(spa, POOL_SCRUB_PAUSE);
17201720
} else if (scan_type == POOL_SCAN_NONE) {
17211721
error = spa_scan_stop(spa);
1722+
} else if (scan_cmd == POOL_SCRUB_FROM_LAST_TXG) {
1723+
error = spa_scan_range(spa, scan_type,
1724+
spa_get_last_scrubbed_txg(spa), 0);
17221725
} else {
17231726
error = spa_scan(spa, scan_type);
17241727
}

tests/zfs-tests/tests/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,6 +1211,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
12111211
functional/cli_root/zpool_scrub/zpool_scrub_multiple_copies.ksh \
12121212
functional/cli_root/zpool_scrub/zpool_scrub_offline_device.ksh \
12131213
functional/cli_root/zpool_scrub/zpool_scrub_print_repairing.ksh \
1214+
functional/cli_root/zpool_scrub/zpool_scrub_txg_continue_from_last.ksh \
12141215
functional/cli_root/zpool_scrub/zpool_error_scrub_001_pos.ksh \
12151216
functional/cli_root/zpool_scrub/zpool_error_scrub_002_pos.ksh \
12161217
functional/cli_root/zpool_scrub/zpool_error_scrub_003_pos.ksh \
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/bin/ksh -p
2+
#
3+
# CDDL HEADER START
4+
#
5+
# The contents of this file are subject to the terms of the
6+
# Common Development and Distribution License (the "License").
7+
# You may not use this file except in compliance with the License.
8+
#
9+
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10+
# or https://opensource.org/licenses/CDDL-1.0.
11+
# See the License for the specific language governing permissions
12+
# and limitations under the License.
13+
#
14+
# When distributing Covered Code, include this CDDL HEADER in each
15+
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16+
# If applicable, add the following below this CDDL HEADER, with the
17+
# fields enclosed by brackets "[]" replaced with your own identifying
18+
# information: Portions Copyright [yyyy] [name of copyright owner]
19+
#
20+
# CDDL HEADER END
21+
#
22+
23+
# Copyright (c) 2023, Klara Inc.
24+
#
25+
# This software was developed by
26+
# Mariusz Zaborski <[email protected]>
27+
# under sponsorship from Wasabi Technology, Inc. and Klara Inc.
28+
29+
. $STF_SUITE/include/libtest.shlib
30+
. $STF_SUITE/tests/functional/cli_root/zpool_scrub/zpool_scrub.cfg
31+
. $STF_SUITE/tests/functional/cli_root/zpool_import/zpool_import.kshlib
32+
33+
#
34+
# DESCRIPTION:
35+
# Verify scrub -C
36+
#
37+
# STRATEGY:
38+
# 1. Create a pool and create one file.
39+
# 2. Verify that the last_txg_scrub is 0.
40+
# 3. Run scrub.
41+
# 4. Verify that the last_txg_scrub is set.
42+
# 5. Create second file.
43+
# 6. Invalidate both files.
44+
# 7. Run scrub only from last point.
45+
# 8. Verify that only one file, that was created with newer txg,
46+
# was detected.
47+
#
48+
49+
verify_runnable "global"
50+
51+
function cleanup
52+
{
53+
log_must zinject -c all
54+
log_must rm -f $mntpnt/f1
55+
log_must rm -f $mntpnt/f2
56+
}
57+
58+
log_onexit cleanup
59+
60+
log_assert "Verify scrub -C."
61+
62+
# Create one file.
63+
mntpnt=$(get_prop mountpoint $TESTPOOL/$TESTFS)
64+
65+
log_must file_write -b 1048576 -c 10 -o create -d 0 -f $mntpnt/f1
66+
log_must sync_pool $TESTPOOL true
67+
f1txg=$(get_last_txg_synced $TESTPOOL)
68+
69+
# Verify that last_scrubbed_txg isn't set.
70+
zpoollasttxg=$(zpool get -H -o value last_scrubbed_txg $TESTPOOL)
71+
log_must [ $zpoollasttxg -eq 0 ]
72+
73+
# Run scrub.
74+
log_must zpool scrub -w $TESTPOOL
75+
76+
# Verify that last_scrubbed_txg is set.
77+
zpoollasttxg=$(zpool get -H -o value last_scrubbed_txg $TESTPOOL)
78+
log_must [ $zpoollasttxg -ne 0 ]
79+
80+
# Create second file.
81+
log_must file_write -b 1048576 -c 10 -o create -d 0 -f $mntpnt/f2
82+
log_must sync_pool $TESTPOOL true
83+
f2txg=$(get_last_txg_synced $TESTPOOL)
84+
85+
# Make sure that the sync txg are different.
86+
log_must [ $f1txg -ne $f2txg ]
87+
88+
# Insert faults.
89+
log_must zinject -a -t data -e io -T read $mntpnt/f1
90+
log_must zinject -a -t data -e io -T read $mntpnt/f2
91+
92+
# Run scrub from last saved point.
93+
log_must zpool scrub -w -C $TESTPOOL
94+
95+
# Verify that only newer file was detected.
96+
log_mustnot eval "zpool status -v $TESTPOOL | grep '$mntpnt/f1'"
97+
log_must eval "zpool status -v $TESTPOOL | grep '$mntpnt/f2'"
98+
99+
# Verify that both files are corrupted.
100+
log_must zpool scrub -w $TESTPOOL
101+
log_must eval "zpool status -v $TESTPOOL | grep '$mntpnt/f1'"
102+
log_must eval "zpool status -v $TESTPOOL | grep '$mntpnt/f2'"
103+
104+
log_pass "Verified scrub -C show expected status."

0 commit comments

Comments
 (0)