diff --git a/c_src/bcachefs.c b/c_src/bcachefs.c index d91e0475..4fd66241 100644 --- a/c_src/bcachefs.c +++ b/c_src/bcachefs.c @@ -77,6 +77,11 @@ void bcachefs_usage(void) " subvolume snapshot Create a snapshot\n" "\n" "Commands for managing filesystem data:\n" + " reconcile status Show status of background data processing\n" + " reconcile wait Wait for background data processing (of a specified type) to complete\n" + " scrub Verify checksums and correct errors, if possible\n" + "\n" + "Commands for managing filesystem data (obsolete):\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" diff --git a/c_src/cmd_data.c b/c_src/cmd_data.c index e5f7338e..e209bc53 100644 --- a/c_src/cmd_data.c +++ b/c_src/cmd_data.c @@ -2,14 +2,18 @@ #include #include #include +#include #include "bcachefs_ioctl.h" +#include "alloc/accounting.h" #include "btree/cache.h" #include "data/move.h" #include "cmds.h" #include "libbcachefs.h" +/* Obsolete, will be deleted */ + static void data_rereplicate_usage(void) { puts("bcachefs data rereplicate\n" @@ -40,6 +44,9 @@ static int cmd_data_rereplicate(int argc, char *argv[]) } args_shift(optind); + if (bcachefs_kernel_version() >= bcachefs_metadata_version_reconcile) + die("rereplicate no longer required or support >= reconcile; use 'bcachefs reconcile wait'"); + char *fs_path = arg_pop(); if (!fs_path) die("Please supply a filesystem"); @@ -56,10 +63,122 @@ static int cmd_data_rereplicate(int argc, char *argv[]) }); } -static void data_scrub_usage(void) +static void data_job_usage(void) { - puts("bcachefs data scrub\n" - "Usage: bcachefs data scrub [filesystem|device]\n" + puts("bcachefs data job\n" + "Usage: bcachefs data job [job} filesystem\n" + "\n" + "Kick off a data job and report progress\n" + "\n" + "job: one of scrub, rereplicate, migrate, rewrite_old_nodes, or drop_extra_replicas\n" + "\n" + "Options:\n" + " -b, --btree btree Btree to operate on\n" + " -s, --start inode:offset Start position\n" + " -e, --end inode:offset End position\n" + " -h, --help Display this help and exit\n" + "\n" + "Report bugs to "); + exit(EXIT_SUCCESS); +} + +static int cmd_data_job(int argc, char *argv[]) +{ + static const struct option longopts[] = { + { "btree", required_argument, NULL, 'b' }, + { "start", required_argument, NULL, 's' }, + { "end", required_argument, NULL, 'e' }, + { "help", no_argument, NULL, 'h' }, + { NULL } + }; + struct bch_ioctl_data op = { + .start_btree = 0, + .start_pos = POS_MIN, + .end_btree = BTREE_ID_NR, + .end_pos = POS_MAX, + }; + int opt; + + while ((opt = getopt_long(argc, argv, "b:s:e:h", longopts, NULL)) != -1) + switch (opt) { + case 'b': + op.start_btree = read_string_list_or_die(optarg, + __bch2_btree_ids, "btree id"); + op.end_btree = op.start_btree; + break; + case 's': + op.start_pos = bpos_parse(optarg); + break; + case 'e': + op.end_pos = bpos_parse(optarg); + break; + case 'h': + data_job_usage(); + } + args_shift(optind); + + char *job = arg_pop(); + if (!job) + die("please specify which type of job"); + + op.op = read_string_list_or_die(job, bch2_data_ops_strs, "bad job type"); + + if (op.op == BCH_DATA_OP_scrub) + die("scrub should be invoked with 'bcachefs data scrub'"); + + if ((op.op == BCH_DATA_OP_rereplicate || + op.op == BCH_DATA_OP_migrate || + op.op == BCH_DATA_OP_drop_extra_replicas) && + bcachefs_kernel_version() >= bcachefs_metadata_version_reconcile) + die("%s no longer required or support >= reconcile; use 'bcachefs reconcile wait'", job); + + char *fs_path = arg_pop(); + if (!fs_path) + fs_path = "."; + + if (argc) + die("too many arguments"); + + return bchu_data(bcache_fs_open(fs_path), op); +} + +static int data_usage(void) +{ + puts("bcachefs data - manage filesystem data\n" + "Usage: bcachefs data [OPTIONS]\n" + "\n" + "Commands:\n" + " rereplicate Rereplicate degraded data\n" + " scrub Verify checksums and correct errors, if possible\n" + " job Kick off low level data jobs\n" + "\n" + "Report bugs to "); + return 0; +} + +int data_cmds(int argc, char *argv[]) +{ + char *cmd = pop_cmd(&argc, argv); + + if (argc < 1) + return data_usage(); + if (!strcmp(cmd, "rereplicate")) + return cmd_data_rereplicate(argc, argv); + if (!strcmp(cmd, "scrub")) + return cmd_scrub(argc, argv); + if (!strcmp(cmd, "job")) + return cmd_data_job(argc, argv); + + data_usage(); + return -EINVAL; +} + +/* Scrub */ + +static void scrub_usage(void) +{ + puts("bcachefs scrub\n" + "Usage: bcachefs scrub [filesystem|device]\n" "\n" "Check data for errors, fix from another replica if possible\n" "\n" @@ -71,7 +190,7 @@ static void data_scrub_usage(void) exit(EXIT_SUCCESS); } -static int cmd_data_scrub(int argc, char *argv[]) +int cmd_scrub(int argc, char *argv[]) { static const struct option longopts[] = { { "metadata", no_argument, NULL, 'm' }, @@ -90,7 +209,7 @@ static int cmd_data_scrub(int argc, char *argv[]) cmd.scrub.data_types = BIT(BCH_DATA_btree); break; case 'h': - data_scrub_usage(); + scrub_usage(); break; } args_shift(optind); @@ -263,106 +382,208 @@ static int cmd_data_scrub(int argc, char *argv[]) return ret; } -static void data_job_usage(void) +/* Nwe reconcile commands */ + +static void reconcile_wait_usage(void) { - puts("bcachefs data job\n" - "Usage: bcachefs data job [job} filesystem\n" + CLASS(printbuf, buf)(); + prt_bitflags(&buf, __bch2_reconcile_accounting_types, ~0UL); + + printf("bcachefs reconcile wait\n" + "Usage: bcachefs reconcile wait [OPTION]... \n" "\n" - "Kick off a data job and report progress\n" - "\n" - "job: one of scrub, rereplicate, migrate, rewrite_old_nodes, or drop_extra_replicas\n" + "Wait for reconcile to finish background data processing of one or more types\n" "\n" "Options:\n" - " -b, --btree btree Btree to operate on\n" - " -s, --start inode:offset Start position\n" - " -e, --end inode:offset End position\n" + " -t, --types=TYPES List of reconcile types to wait on\n" + " %s\n" " -h, --help Display this help and exit\n" "\n" - "Report bugs to "); + "Report bugs to \n", + buf.buf); exit(EXIT_SUCCESS); } -static int cmd_data_job(int argc, char *argv[]) +static bool reconcile_status(struct printbuf *out, + struct bchfs_handle fs, + unsigned types) +{ + bool scan_pending = read_file_u64(fs.sysfs_fd, "reconcile_scan_pending"); + + u64 v[BCH_REBALANCE_ACCOUNTING_NR]; + memset(v, 0, sizeof(v)); + + struct bch_ioctl_query_accounting *a = + bchu_fs_accounting(fs, BIT(BCH_DISK_ACCOUNTING_reconcile_work)); + + /* + * This would be cleaner if we had an interface for doing + * lookups on specific keys + */ + for (struct bkey_i_accounting *k = a->accounting; + k < (struct bkey_i_accounting *) ((u64 *) a->accounting + a->accounting_u64s); + k = bkey_i_to_accounting(bkey_next(&k->k_i))) { + struct disk_accounting_pos acc_k; + bpos_to_disk_accounting_pos(&acc_k, k->k.p); + + v[acc_k.reconcile_work.type] = k->v.d[0]; + } + free(a); + + if (!out->nr_tabstops) + printbuf_tabstop_push(out, 32); + + prt_printf(out, "Scan pending:\t%u\n", scan_pending); + bool have_pending = scan_pending; + + for (unsigned i = 0; i < ARRAY_SIZE(v); i++) + if (types & BIT(i)) { + prt_printf(out, "Pending %s:\t", __bch2_reconcile_accounting_types[i]); + prt_human_readable_u64(out, v[i] << 9); + prt_newline(out); + have_pending |= v[i] != 0; + } + + return have_pending; +} + +static size_t count_newlines(const char *str) +{ + size_t ret = 0; + const char *n; + while ((n = strchr(str, '\n'))) { + str = n + 1; + ret++; + } + + return ret; +} + +int cmd_reconcile_wait(int argc, char *argv[]) { static const struct option longopts[] = { - { "btree", required_argument, NULL, 'b' }, - { "start", required_argument, NULL, 's' }, - { "end", required_argument, NULL, 'e' }, + { "types", required_argument, NULL, 't' }, { "help", no_argument, NULL, 'h' }, { NULL } }; - struct bch_ioctl_data op = { - .start_btree = 0, - .start_pos = POS_MIN, - .end_btree = BTREE_ID_NR, - .end_pos = POS_MAX, - }; + unsigned types = ~0U; int opt; - while ((opt = getopt_long(argc, argv, "b:s:e:h", longopts, NULL)) != -1) + while ((opt = getopt_long(argc, argv, "t:h", longopts, NULL)) != -1) switch (opt) { - case 'b': - op.start_btree = read_string_list_or_die(optarg, - __bch2_btree_ids, "btree id"); - op.end_btree = op.start_btree; - break; - case 's': - op.start_pos = bpos_parse(optarg); - break; - case 'e': - op.end_pos = bpos_parse(optarg); + case 't': + types = read_flag_list_or_die(optarg, + __bch2_reconcile_accounting_types, "reconcile type"); break; case 'h': - data_job_usage(); + reconcile_wait_usage(); } args_shift(optind); - char *job = arg_pop(); - if (!job) - die("please specify which type of job"); - - op.op = read_string_list_or_die(job, bch2_data_ops_strs, "bad job type"); - - if (op.op == BCH_DATA_OP_scrub) - die("scrub should be invoked with 'bcachefs data scrub'"); - char *fs_path = arg_pop(); if (!fs_path) - fs_path = "."; + die("Please supply a filesystem"); if (argc) die("too many arguments"); - return bchu_data(bcache_fs_open(fs_path), op); + struct bchfs_handle fs = bcache_fs_open(fs_path); + + while (true) { + CLASS(printbuf, buf)(); + bool pending = reconcile_status(&buf, fs, types); + + printf("\033[%zuF\033[J", count_newlines(buf.buf)); + fputs(buf.buf, stdout); + + if (!pending) + break; + + sleep(1); + } + + return 0; } -static int data_usage(void) +static void reconcile_status_usage(void) { - puts("bcachefs data - manage filesystem data\n" - "Usage: bcachefs data [OPTIONS]\n" + CLASS(printbuf, buf)(); + prt_bitflags(&buf, __bch2_reconcile_accounting_types, ~0UL); + + printf("bcachefs reconcile status\n" + "Usage: bcachefs reconcile status [OPTION]... \n" + "\n" + "Options:\n" + " -t, --types=TYPES List of reconcile types to wait on\n" + " %s\n" + " -h, --help Display this help and exit\n" + "\n" + "Report bugs to \n", + buf.buf); + exit(EXIT_SUCCESS); +} + +int cmd_reconcile_status(int argc, char *argv[]) +{ + static const struct option longopts[] = { + { "types", required_argument, NULL, 't' }, + { "help", no_argument, NULL, 'h' }, + { NULL } + }; + unsigned types = ~0U; + int opt; + + while ((opt = getopt_long(argc, argv, "t:h", longopts, NULL)) != -1) + switch (opt) { + case 't': + types = read_flag_list_or_die(optarg, + __bch2_reconcile_accounting_types, "reconcile type"); + break; + case 'h': + reconcile_status_usage(); + } + args_shift(optind); + + char *fs_path = arg_pop(); + if (!fs_path) + die("Please supply a filesystem"); + + if (argc) + die("too many arguments"); + + struct bchfs_handle fs = bcache_fs_open(fs_path); + + CLASS(printbuf, buf)(); + reconcile_status(&buf, fs, types); + fputs(buf.buf, stdout); + + return 0; +} + +static int reconcile_usage(void) +{ + puts("bcachefs reconcile - manage data reconcile\n" + "Usage: bcachefs reconcile [OPTIONS]\n" "\n" "Commands:\n" - " rereplicate Rereplicate degraded data\n" - " scrub Verify checksums and correct errors, if possible\n" - " job Kick off low level data jobs\n" + " status Show status of background data processing\n" + " wait Wait on background data processing to complete\n" "\n" "Report bugs to "); return 0; } -int data_cmds(int argc, char *argv[]) +int reconcile_cmds(int argc, char *argv[]) { char *cmd = pop_cmd(&argc, argv); if (argc < 1) - 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); + return reconcile_usage(); + if (!strcmp(cmd, "status")) + return cmd_reconcile_status(argc, argv); + if (!strcmp(cmd, "wait")) + return cmd_reconcile_wait(argc, argv); - data_usage(); + reconcile_usage(); return -EINVAL; } diff --git a/c_src/cmd_device.c b/c_src/cmd_device.c index c6134ab8..c37ef7ea 100644 --- a/c_src/cmd_device.c +++ b/c_src/cmd_device.c @@ -295,26 +295,42 @@ static void device_evacuate_usage(void) "Usage: bcachefs device evacuate [OPTION]... device\n" "\n" "Options:\n" - " -f, --force Force if data redundancy will be degraded\n" " -h, --help Display this help and exit\n" "\n" "Report bugs to "); } +static int evacuate_v0(struct bchfs_handle fs, unsigned dev_idx, const char *dev_path) +{ + struct bch_ioctl_dev_usage_v2 *u = bchu_dev_usage(fs, dev_idx); + + if (u->state == BCH_MEMBER_STATE_rw) { + printf("Setting %s readonly\n", dev_path); + bchu_disk_set_state(fs, dev_idx, BCH_MEMBER_STATE_ro, BCH_FORCE_IF_DEGRADED); + } + + free(u); + + return bchu_data(fs, (struct bch_ioctl_data) { + .op = BCH_DATA_OP_migrate, + .start_btree = 0, + .start_pos = POS_MIN, + .end_btree = BTREE_ID_NR, + .end_pos = POS_MAX, + .migrate.dev = dev_idx, + }); +} + static int cmd_device_evacuate(int argc, char *argv[]) { static const struct option longopts[] = { - { "force", no_argument, NULL, 'f' }, { "help", no_argument, NULL, 'h' }, { NULL } }; - int opt, flags = 0; + int opt; while ((opt = getopt_long(argc, argv, "fh", longopts, NULL)) != -1) switch (opt) { - case 'f': - flags |= BCH_FORCE_IF_DEGRADED; - break; case 'h': device_evacuate_usage(); exit(EXIT_SUCCESS); @@ -331,23 +347,34 @@ static int cmd_device_evacuate(int argc, char *argv[]) int dev_idx; struct bchfs_handle fs = bchu_fs_open_by_dev(dev_path, &dev_idx); - struct bch_ioctl_dev_usage_v2 *u = bchu_dev_usage(fs, dev_idx); + if (bcachefs_kernel_version() < bcachefs_metadata_version_reconcile) + return evacuate_v0(fs, dev_idx, dev_path); - if (u->state == BCH_MEMBER_STATE_rw) { - printf("Setting %s readonly\n", dev_path); - bchu_disk_set_state(fs, dev_idx, BCH_MEMBER_STATE_ro, flags); + printf("Setting %s failed\n", dev_path); + bchu_disk_set_state(fs, dev_idx, BCH_MEMBER_STATE_failed, BCH_FORCE_IF_DEGRADED); + + while (true) { + struct bch_ioctl_dev_usage_v2 *u = bchu_dev_usage(fs, dev_idx); + + u64 data = 0; + for (unsigned type = 0; type < u->nr_data_types; type++) + if (!data_type_is_empty(type) && + !data_type_is_hidden(type)) + data += u->d[type].sectors; + free(u); + + printf("\33[2K\r"); + CLASS(printbuf, buf)(); + prt_units_u64(&buf, data << 9); + + fputs(buf.buf, stdout); + fflush(stdout); + + if (!data) + return 0; + + sleep(1); } - - free(u); - - return bchu_data(fs, (struct bch_ioctl_data) { - .op = BCH_DATA_OP_migrate, - .start_btree = 0, - .start_pos = POS_MIN, - .end_btree = BTREE_ID_NR, - .end_pos = POS_MAX, - .migrate.dev = dev_idx, - }); } static void device_set_state_usage(void) diff --git a/c_src/cmds.h b/c_src/cmds.h index fa4b87a9..f2097b0c 100644 --- a/c_src/cmds.h +++ b/c_src/cmds.h @@ -24,6 +24,8 @@ int cmd_fs_top(int argc, char *argv[]); int device_cmds(int argc, char *argv[]); +int cmd_scrub(int argc, char *argv[]); +int reconcile_cmds(int argc, char *argv[]); int data_cmds(int argc, char *argv[]); int cmd_unlock(int argc, char *argv[]); diff --git a/src/bcachefs.rs b/src/bcachefs.rs index d0c1d6e8..c9e43571 100644 --- a/src/bcachefs.rs +++ b/src/bcachefs.rs @@ -59,6 +59,7 @@ fn handle_c_command(mut argv: Vec, symlink_cmd: Option<&str>) -> i32 { "migrate" => c::cmd_migrate(argc, argv), "migrate-superblock" => c::cmd_migrate_superblock(argc, argv), "mkfs" => c::cmd_format(argc, argv), + "reconcile" => c::reconcile_cmds(argc, argv), "remove-passphrase" => c::cmd_remove_passphrase(argc, argv), "reset-counters" => c::cmd_reset_counters(argc, argv), "set-fs-option" => c::cmd_set_option(argc, argv),