Skip to content

Commit

Permalink
Scrub
Browse files Browse the repository at this point in the history
Implement 'bcachefs data scrub', frontend for
BCH_IOCTL_DATA.BCH_DATA_OP_scrub.

Takes a path to a device, mountpoint, or filesystem uuid. Can be run on
a specific device by passing a device, or if run on a filesystem scrubs
all devices in parallel.

Metadata only scrubbing is supported via -m.

Signed-off-by: Kent Overstreet <[email protected]>
  • Loading branch information
Kent Overstreet committed Dec 31, 2024
1 parent 6f1429b commit 43a1380
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 36 deletions.
3 changes: 3 additions & 0 deletions c_src/bcachefs.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ void bcachefs_usage(void)
"\n"
"Commands for managing filesystem data:\n"
" data rereplicate Rereplicate degraded data\n"
" data scrub Verify checksums and correct errors, if possible\n"
" data job Kick off low level data jobs\n"
"\n"
"Encryption:\n"
Expand Down Expand Up @@ -157,6 +158,8 @@ int data_cmds(int argc, char *argv[])
return data_usage();
if (!strcmp(cmd, "rereplicate"))
return cmd_data_rereplicate(argc, argv);
if (!strcmp(cmd, "scrub"))
return cmd_data_scrub(argc, argv);
if (!strcmp(cmd, "job"))
return cmd_data_job(argc, argv);

Expand Down
203 changes: 202 additions & 1 deletion c_src/cmd_data.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@


#include <getopt.h>
#include <stdio.h>
#include <sys/ioctl.h>

Expand Down Expand Up @@ -64,6 +64,207 @@ int cmd_data_rereplicate(int argc, char *argv[])
});
}

static void data_scrub_usage(void)
{
puts("bcachefs data scrub\n"
"Usage: bcachefs data scrub [filesystem|device]\n"
"\n"
"Check data for errors, fix from another replica if possible\n"
"\n"
"Options:\n"
" -m, --metadata check metadata only\n"
" -h, --help display this help and exit\n"
"Report bugs to <[email protected]>");
exit(EXIT_SUCCESS);
}

int cmd_data_scrub(int argc, char *argv[])
{
static const struct option longopts[] = {
{ "metadata", no_argument, NULL, 'm' },
{ "help", no_argument, NULL, 'h' },
{ NULL }
};
struct bch_ioctl_data cmd = {
.op = BCH_DATA_OP_scrub,
.scrub.data_types = ~0,
};
int opt;

while ((opt = getopt_long(argc, argv, "hm", longopts, NULL)) != -1)
switch (opt) {
case 'm':
cmd.scrub.data_types = BIT(BCH_DATA_btree);
break;
case 'h':
data_scrub_usage();
break;
}
args_shift(optind);

char *path = arg_pop();
if (!path)
die("Please supply a filesystem");

if (argc)
die("too many arguments");

printf("Starting scrub on");

struct bchfs_handle fs = bcache_fs_open(path);
dev_names dev_names = bchu_fs_get_devices(fs);

struct scrub_device {
const char *name;
int progress_fd;
u64 done, corrected, uncorrected, total;
enum bch_ioctl_data_event_ret ret;
};
DARRAY(struct scrub_device) scrub_devs = {};

if (fs.dev_idx >= 0) {
cmd.scrub.dev = fs.dev_idx;
struct scrub_device d = {
.name = dev_idx_to_name(&dev_names, fs.dev_idx)->dev,
.progress_fd = xioctl(fs.ioctl_fd, BCH_IOCTL_DATA, &cmd),
};
darray_push(&scrub_devs, d);
} else {
/* Scrubbing every device */
darray_for_each(dev_names, dev) {
cmd.scrub.dev = dev->idx;
struct scrub_device d = {
.name = dev->dev,
.progress_fd = xioctl(fs.ioctl_fd, BCH_IOCTL_DATA, &cmd),
};
darray_push(&scrub_devs, d);
}
}

printf(" %zu devices: ", scrub_devs.nr);
darray_for_each(scrub_devs, dev)
printf(" %s", dev->name);
printf("\n");

struct timespec now, last;
bool first = true;

struct printbuf buf = PRINTBUF;
printbuf_tabstop_push(&buf, 16);
printbuf_tabstop_push(&buf, 12);
printbuf_tabstop_push(&buf, 12);
printbuf_tabstop_push(&buf, 12);
printbuf_tabstop_push(&buf, 12);
printbuf_tabstop_push(&buf, 6);

prt_printf(&buf, "device\t");
prt_printf(&buf, "checked\r");
prt_printf(&buf, "corrected\r");
prt_printf(&buf, "uncorrected\r");
prt_printf(&buf, "total\r");
puts(buf.buf);

while (1) {
bool done = true;

printbuf_reset_keep_tabstops(&buf);

clock_gettime(CLOCK_MONOTONIC, &now);
u64 ns_since_last = 0;
if (!first)
ns_since_last = (now.tv_sec - last.tv_sec) * NSEC_PER_SEC +
now.tv_nsec - last.tv_nsec;

darray_for_each(scrub_devs, dev) {
struct bch_ioctl_data_event e;

if (dev->progress_fd >= 0 &&
read(dev->progress_fd, &e, sizeof(e)) != sizeof(e)) {
close(dev->progress_fd);
dev->progress_fd = -1;
}

u64 rate = 0;

if (dev->progress_fd >= 0) {
if (ns_since_last)
rate = ((e.p.sectors_done - dev->done) << 9)
* NSEC_PER_SEC
/ ns_since_last;

dev->done = e.p.sectors_done;
dev->corrected = e.p.sectors_error_corrected;
dev->uncorrected= e.p.sectors_error_uncorrected;
dev->total = e.p.sectors_total;
}

if (dev->progress_fd >= 0 && e.ret) {
close(dev->progress_fd);
dev->progress_fd = -1;
dev->ret = e.ret;
}

if (dev->progress_fd >= 0)
done = false;

prt_printf(&buf, "%s\t", dev->name ?: "(offline)");

prt_human_readable_u64(&buf, dev->done << 9);
prt_tab_rjust(&buf);

prt_human_readable_u64(&buf, dev->corrected << 9);
prt_tab_rjust(&buf);

prt_human_readable_u64(&buf, dev->uncorrected << 9);
prt_tab_rjust(&buf);

prt_human_readable_u64(&buf, dev->total << 9);
prt_tab_rjust(&buf);

prt_printf(&buf, "%llu%%",
dev->total
? dev->done * 100 / dev->total
: 0);
prt_tab_rjust(&buf);

prt_str(&buf, " ");

if (dev->progress_fd >= 0) {
prt_human_readable_u64(&buf, rate);
prt_str(&buf, "/sec");
} else if (dev->ret == BCH_IOCTL_DATA_EVENT_RET_device_offline) {
prt_str(&buf, "offline");
} else {
prt_str(&buf, "complete");
}

if (dev != &darray_last(scrub_devs))
prt_newline(&buf);
}

fputs(buf.buf, stdout);
fflush(stdout);

if (done)
break;

last = now;
first = false;
sleep(1);

for (unsigned i = 0; i < scrub_devs.nr; i++) {
if (i)
printf("\033[1A");
printf("\33[2K\r");
}
}

fputs("\n", stdout);
printbuf_exit(&buf);

return 0;
}

static void data_job_usage(void)
{
puts("bcachefs data job\n"
Expand Down
8 changes: 0 additions & 8 deletions c_src/cmd_fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,6 @@ static int dev_by_label_cmp(const void *_l, const void *_r)
cmp_int(l->idx, r->idx);
}

static struct dev_name *dev_idx_to_name(dev_names *dev_names, unsigned idx)
{
darray_for_each(*dev_names, dev)
if (dev->idx == idx)
return dev;
return NULL;
}

static void devs_usage_to_text(struct printbuf *out,
struct bchfs_handle fs,
dev_names dev_names)
Expand Down
12 changes: 9 additions & 3 deletions c_src/cmd_list_journal.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ static inline bool entry_is_transaction_start(struct jset_entry *entry)
return entry->type == BCH_JSET_ENTRY_log && !entry->level;
}

static inline bool entry_is_log_msg(struct jset_entry *entry)
{
return entry->type == BCH_JSET_ENTRY_log && entry->level;
}

typedef DARRAY(struct bbpos_range) d_bbpos_range;
typedef DARRAY(enum btree_id) d_btree_id;

Expand All @@ -60,8 +65,8 @@ static bool bkey_matches_filter(d_bbpos_range filter, struct jset_entry *entry,
struct bbpos k_start = BBPOS(entry->btree_id, bkey_start_pos(&k->k));
struct bbpos k_end = BBPOS(entry->btree_id, k->k.p);

if (bbpos_cmp(k_start, i->end) < 0 &&
bbpos_cmp(k_end, i->start) > 0)
if (bbpos_cmp(k_start, i->start) >= 0 &&
bbpos_cmp(k_end, i->end) <= 0)
return true;
}
return false;
Expand Down Expand Up @@ -96,7 +101,8 @@ static bool should_print_transaction(struct jset_entry *entry, struct jset_entry
for (entry = vstruct_next(entry);
entry != end && !entry_is_transaction_start(entry);
entry = vstruct_next(entry))
if (entry_matches_transaction_filter(entry, key_filter))
if (entry_is_log_msg(entry) ||
entry_matches_transaction_filter(entry, key_filter))
return true;

return false;
Expand Down
1 change: 1 addition & 0 deletions c_src/cmds.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ int cmd_device_resize_journal(int argc, char *argv[]);

int data_usage(void);
int cmd_data_rereplicate(int argc, char *argv[]);
int cmd_data_scrub(int argc, char *argv[]);
int cmd_data_job(int argc, char *argv[]);

int cmd_unlock(int argc, char *argv[]);
Expand Down
Loading

0 comments on commit 43a1380

Please sign in to comment.