Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support single files scrubbing #16018

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions cmd/zfs/zfs_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ static int zfs_do_hold(int argc, char **argv);
static int zfs_do_holds(int argc, char **argv);
static int zfs_do_release(int argc, char **argv);
static int zfs_do_diff(int argc, char **argv);
static int zfs_do_scrub(int argc, char **argv);
static int zfs_do_bookmark(int argc, char **argv);
static int zfs_do_channel_program(int argc, char **argv);
static int zfs_do_load_key(int argc, char **argv);
Expand Down Expand Up @@ -181,6 +182,7 @@ typedef enum {
HELP_HOLDS,
HELP_RELEASE,
HELP_DIFF,
HELP_SCRUB,
HELP_BOOKMARK,
HELP_CHANNEL_PROGRAM,
HELP_LOAD_KEY,
Expand Down Expand Up @@ -253,6 +255,7 @@ static zfs_command_t command_table[] = {
{ "holds", zfs_do_holds, HELP_HOLDS },
{ "release", zfs_do_release, HELP_RELEASE },
{ "diff", zfs_do_diff, HELP_DIFF },
{ "scrub", zfs_do_scrub, HELP_SCRUB },
{ "load-key", zfs_do_load_key, HELP_LOAD_KEY },
{ "unload-key", zfs_do_unload_key, HELP_UNLOAD_KEY },
{ "change-key", zfs_do_change_key, HELP_CHANGE_KEY },
Expand Down Expand Up @@ -400,6 +403,8 @@ get_usage(zfs_help_t idx)
case HELP_DIFF:
return (gettext("\tdiff [-FHth] <snapshot> "
"[snapshot|filesystem]\n"));
case HELP_SCRUB:
return (gettext("\tscrub <file>\n"));
case HELP_BOOKMARK:
return (gettext("\tbookmark <snapshot|bookmark> "
"<newbookmark>\n"));
Expand Down Expand Up @@ -7879,6 +7884,98 @@ zfs_do_diff(int argc, char **argv)
return (err != 0);
}

oshogbo marked this conversation as resolved.
Show resolved Hide resolved
static int
scrub_single_file(zfs_handle_t *zhp, void *data)
{
struct stat64 *filestat = (struct stat64 *)data;
struct stat64 mountstat;
char mountpoint[ZFS_MAXPROPLEN];
int err;

if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint,
sizeof (mountpoint), NULL, NULL, 0, B_FALSE) != 0) {
return (0);
}

if (stat64(mountpoint, &mountstat) < 0) {
return (0);
}

if (mountstat.st_dev != filestat->st_dev) {
return (0);
}

nvlist_t *args = fnvlist_alloc();
fnvlist_add_string(args, "dataset", zfs_get_name(zhp));
fnvlist_add_uint64(args, "object", filestat->st_ino);

err = lzc_scrub(ZFS_IOC_POOL_SCRUB_FILE, zfs_get_pool_name(zhp),
args, NULL);

fnvlist_free(args);

return (err != 0 ? 2 : 1);
}

static int
find_dataset_and_scrub(zfs_handle_t *zhp, void *data)
{
int err;

err = scrub_single_file(zhp, data);
if (err == 0) {
err = zfs_iter_filesystems_v2(zhp, 0, find_dataset_and_scrub,
data);
}
zfs_close(zhp);
return (err);
}

/*
* zfs scrub <file>
*
* Scrubs single file.
*/
static int
zfs_do_scrub(int argc, char **argv)
{
char *filetoscrub = NULL;
struct stat64 filestat;
int err = 0;

if (argc < 1) {
(void) fprintf(stderr,
gettext("must provide a file to scrub\n"));
usage(B_FALSE);
}

if (argc > 2) {
(void) fprintf(stderr, gettext("too many arguments\n"));
usage(B_FALSE);
}

filetoscrub = argv[1];
if (stat64(filetoscrub, &filestat) < 0) {
(void) fprintf(stderr,
gettext("must provide a file to scrub\n"));
usage(B_FALSE);
}

if (S_ISREG(filestat.st_mode) == 0) {
(void) fprintf(stderr,
gettext("scrub works only on regular files\n"));
usage(B_FALSE);
}

err = zfs_iter_root(g_zfs, find_dataset_and_scrub, &filestat);
if (err == 0) {
(void) fprintf(stderr,
gettext("unable to scrub file %s\n"), filetoscrub);
}

return (err == 1 ? 0 : -1);
}

/*
* zfs bookmark <fs@source>|<fs#source> <fs#bookmark>
*
Expand Down
1 change: 1 addition & 0 deletions include/sys/fs/zfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -1506,6 +1506,7 @@ typedef enum zfs_ioc {
ZFS_IOC_VDEV_GET_PROPS, /* 0x5a55 */
ZFS_IOC_VDEV_SET_PROPS, /* 0x5a56 */
ZFS_IOC_POOL_SCRUB, /* 0x5a57 */
ZFS_IOC_POOL_SCRUB_FILE, /* 0x5a58 */

/*
* Per-platform (Optional) - 8/128 numbers reserved.
Expand Down
53 changes: 53 additions & 0 deletions man/man8/zfs-scrub.8
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
.\"
.\" CDDL HEADER START
.\"
.\" The contents of this file are subject to the terms of the
.\" Common Development and Distribution License (the "License").
.\" You may not use this file except in compliance with the License.
.\"
.\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
.\" or https://opensource.org/licenses/CDDL-1.0.
.\" See the License for the specific language governing permissions
.\" and limitations under the License.
.\"
.\" When distributing Covered Code, include this CDDL HEADER in each
.\" file and include the License file at usr/src/OPENSOLARIS.LICENSE.
.\" If applicable, add the following below this CDDL HEADER, with the
.\" fields enclosed by brackets "[]" replaced with your own identifying
.\" information: Portions Copyright [yyyy] [name of copyright owner]
.\"
.\" CDDL HEADER END
.\"
.\" Copyright 2024 Klara, Inc.
.\" Copyright 2024 Mariusz Zaborski <[email protected]>
.\"
.Dd February 21, 2024
.Dt ZFS-SCRUB 8
.Os
.
.Sh NAME
.Nm zfs-scrub
.Nd run scrub of file in ZFS storage pools
.Sh SYNOPSIS
.Nm zfs
.Cm scrub
.Ar filename Ns
.
.Sh DESCRIPTION
Runs a scrub for a single file.
The scrub examines all data associated with a given file.
For replicated
.Pq mirror, raidz, or draid
devices, ZFS automatically repairs any damage discovered during the scrub.
The
.Nm zpool Cm status
command reports the progress of the scrub and summarizes the results of the
scrub upon completion.
For more details about scrubing, refer
.Xr zpool 8 .
.
.Pp
ZFS allows only on scrub process at a time (pool scrub or file scrub).
.Sh SEE ALSO
.Xr zpool-scrub 8 ,
.Xr zpool-status 8
7 changes: 7 additions & 0 deletions man/man8/zfs.8
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ Delegate permissions on the specified filesystem or volume.
Remove delegated permissions on the specified filesystem or volume.
.El
.
.Ss Maintenance
.Bl -tag -width ""
.It Xr zfs-scrub 8
Runs a scrub of single file.
.El
.
.Ss Encryption
.Bl -tag -width ""
.It Xr zfs-change-key 8
Expand Down Expand Up @@ -818,6 +824,7 @@ don't wait.
.Xr zfs-release 8 ,
.Xr zfs-rename 8 ,
.Xr zfs-rollback 8 ,
.Xr zfs-scrub 8 ,
.Xr zfs-send 8 ,
.Xr zfs-set 8 ,
.Xr zfs-share 8 ,
Expand Down
1 change: 1 addition & 0 deletions man/man8/zpool-scrub.8
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ timer units are provided.
.
.Sh SEE ALSO
.Xr systemd.timer 5 ,
.Xr zfs-scrub 8 ,
.Xr zpool-iostat 8 ,
.Xr zpool-resilver 8 ,
.Xr zpool-status 8
131 changes: 131 additions & 0 deletions module/zfs/zfs_ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@
#include <sys/dmu_recv.h>
#include <sys/dmu_send.h>
#include <sys/dmu_recv.h>
#include <sys/dbuf.h>
#include <sys/dsl_destroy.h>
#include <sys/dsl_bookmark.h>
#include <sys/dsl_userhold.h>
Expand Down Expand Up @@ -1726,6 +1727,131 @@ zfs_ioc_pool_scrub(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
return (error);
}

/*
* inputs:
* dataset name of dataset
* object object number
*/
static const zfs_ioc_key_t zfs_keys_pool_scrub_file[] = {
{"dataset", DATA_TYPE_STRING, 0},
{"object", DATA_TYPE_UINT64, 0},
};

static void
scrub_file_traverse(spa_t *spa, blkptr_t *bp, const zbookmark_phys_t *zb)
{
uint64_t blk_birth = bp->blk_birth;

if (blk_birth == 0)
return;

spa_log_error(spa, zb, &blk_birth);
if (BP_GET_LEVEL(bp) > 0 && !BP_IS_HOLE(bp)) {
arc_flags_t flags = ARC_FLAG_WAIT;
int i;
blkptr_t *cbp;
int epb = BP_GET_LSIZE(bp) >> SPA_BLKPTRSHIFT;
arc_buf_t *buf;

if (arc_read(NULL, spa, bp, arc_getbuf_func, &buf,
ZIO_PRIORITY_ASYNC_READ, ZIO_FLAG_CANFAIL, &flags, zb))
return;

/* recursively visit blocks below this */
cbp = buf->b_data;
for (i = 0; i < epb; i++, cbp++) {
zbookmark_phys_t czb;

SET_BOOKMARK(&czb, zb->zb_objset, zb->zb_object,
zb->zb_level - 1,
zb->zb_blkid * epb + i);

scrub_file_traverse(spa, cbp, &czb);
oshogbo marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

static int
zfs_ioc_pool_scrub_file(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
{
spa_t *spa;
int error;
const char *dataset;
uint64_t object;
objset_t *os;
dmu_object_info_t doi;
zbookmark_phys_t zb;
dmu_buf_t *db = NULL;
dnode_t *dn;

if (nvlist_lookup_string(innvl, "dataset", &dataset) != 0)
return (SET_ERROR(EINVAL));
if (nvlist_lookup_uint64(innvl, "object", &object) != 0)
return (SET_ERROR(EINVAL));

error = dmu_objset_hold(dataset, FTAG, &os);
if (error != 0) {
return (error);
}
dsl_dataset_long_hold(dmu_objset_ds(os), FTAG);
spa = dmu_objset_spa(os);

if (!spa_feature_is_enabled(spa, SPA_FEATURE_HEAD_ERRLOG)) {
dsl_pool_rele(dmu_objset_pool(os), FTAG);
dsl_dataset_long_rele(dmu_objset_ds(os), FTAG);
dsl_dataset_rele_flags(dmu_objset_ds(os), 0, FTAG);
return (SET_ERROR(ENOTSUP));
}

error = dmu_object_info(os, object, &doi);
if (error != 0) {
dsl_pool_rele(dmu_objset_pool(os), FTAG);
dsl_dataset_long_rele(dmu_objset_ds(os), FTAG);
dsl_dataset_rele_flags(dmu_objset_ds(os), 0, FTAG);
return (error);
}
if (doi.doi_type != DMU_OT_PLAIN_FILE_CONTENTS) {
dsl_pool_rele(dmu_objset_pool(os), FTAG);
dsl_dataset_long_rele(dmu_objset_ds(os), FTAG);
dsl_dataset_rele_flags(dmu_objset_ds(os), 0, FTAG);
return (EINVAL);
}

error = dmu_bonus_hold(os, object, FTAG, &db);
if (error != 0) {
dsl_pool_rele(dmu_objset_pool(os), FTAG);
dsl_dataset_long_rele(dmu_objset_ds(os), FTAG);
dsl_dataset_rele_flags(dmu_objset_ds(os), 0, FTAG);
return (error);
}

dn = DB_DNODE((dmu_buf_impl_t *)db);

zb.zb_objset = dmu_objset_id(os);
zb.zb_object = object;

dmu_buf_rele(db, FTAG);

for (int j = 0; j < dn->dn_phys->dn_nblkptr; j++) {
zb.zb_blkid = j;
zb.zb_level = BP_GET_LEVEL(&dn->dn_phys->dn_blkptr[j]);
scrub_file_traverse(spa, &dn->dn_phys->dn_blkptr[j], &zb);
}

dsl_pool_rele(dmu_objset_pool(os), FTAG);
dsl_dataset_long_rele(dmu_objset_ds(os), FTAG);
dsl_dataset_rele_flags(dmu_objset_ds(os), 0, FTAG);

txg_wait_synced(spa_get_dsl(spa), 0);

if ((error = spa_open(poolname, &spa, FTAG)) != 0)
return (error);
error = spa_scan(spa, POOL_SCAN_ERRORSCRUB);
spa_close(spa, FTAG);

return (error);
}

static int
zfs_ioc_pool_freeze(zfs_cmd_t *zc)
{
Expand Down Expand Up @@ -7280,6 +7406,11 @@ zfs_ioctl_init(void)
POOL_CHECK_NONE, B_TRUE, B_TRUE,
zfs_keys_pool_scrub, ARRAY_SIZE(zfs_keys_pool_scrub));

zfs_ioctl_register("scrub_file", ZFS_IOC_POOL_SCRUB_FILE,
zfs_ioc_pool_scrub_file, zfs_secpolicy_config, POOL_NAME,
POOL_CHECK_NONE, B_TRUE, B_TRUE,
zfs_keys_pool_scrub_file, ARRAY_SIZE(zfs_keys_pool_scrub_file));

/* IOCTLS that use the legacy function signature */

zfs_ioctl_register_legacy(ZFS_IOC_POOL_FREEZE, zfs_ioc_pool_freeze,
Expand Down
4 changes: 4 additions & 0 deletions tests/runfiles/common.run
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ tests = ['zfs_rollback_001_pos', 'zfs_rollback_002_pos',
'zfs_rollback_003_neg', 'zfs_rollback_004_neg']
tags = ['functional', 'cli_root', 'zfs_rollback']

[tests/functional/cli_root/zfs_scrub]
tests = ['zfs_error_scrub_001_pos', 'zfs_error_scrub_002_pos']
tags = ['functional', 'cli_root', 'zfs_scrub']

[tests/functional/cli_root/zfs_send]
tests = ['zfs_send_001_pos', 'zfs_send_002_pos', 'zfs_send_003_pos',
'zfs_send_004_neg', 'zfs_send_005_pos', 'zfs_send_006_pos',
Expand Down
Loading
Loading