From 2f5218c404665d15515d63da0b2d539e139d69c6 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Wed, 20 Mar 2024 20:37:44 +0100 Subject: [PATCH 1/6] btrfs-progs: filesystem show: introduce printing helper functions Introduce helper functions for printing the filesystem data and list of devices. This prepares the both printing code paths of filesystem show to support json output. Signed-off-by: Jelle van der Waa --- cmds/filesystem.c | 80 +++++++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/cmds/filesystem.c b/cmds/filesystem.c index d2605bda3..24c5d27e5 100644 --- a/cmds/filesystem.c +++ b/cmds/filesystem.c @@ -310,6 +310,34 @@ static void splice_device_list(struct list_head *seed_devices, list_splice(seed_devices, all_devices); } +static void print_filesystem_info(char *label, char uuidbuf[BTRFS_UUID_UNPARSED_SIZE], + u64 bytes_used, u64 num_devices, + unsigned unit_mode) +{ + if (label) + pr_verbose(LOG_DEFAULT, "Label: '%s' ", label); + else + pr_verbose(LOG_DEFAULT, "Label: none "); + + pr_verbose(LOG_DEFAULT, " uuid: %s\n\tTotal devices %llu FS bytes used %s\n", uuidbuf, + num_devices, + pretty_size_mode(bytes_used, + unit_mode)); +} + +static void print_filesystem_device(u64 devid, u64 total_bytes, u64 bytes_used, + char *path, + bool missing, + unsigned unit_mode) +{ + pr_verbose(LOG_DEFAULT, "\tdevid %4llu size %s used %s path %s%s\n", + devid, + pretty_size_mode(total_bytes, unit_mode), + pretty_size_mode(bytes_used, unit_mode), + path, + missing ? " MISSING" : ""); +} + static void print_devices(struct btrfs_fs_devices *fs_devices, u64 *devs_found, unsigned unit_mode) { @@ -327,12 +355,11 @@ static void print_devices(struct btrfs_fs_devices *fs_devices, list_sort(NULL, all_devices, cmp_device_id); list_for_each_entry(device, all_devices, dev_list) { - pr_verbose(LOG_DEFAULT, "\tdevid %4llu size %s used %s path %s\n", - device->devid, - pretty_size_mode(device->total_bytes, unit_mode), - pretty_size_mode(device->bytes_used, unit_mode), - device->name); - + print_filesystem_device(device->devid, + device->total_bytes, device->bytes_used, + device->name, + false, + unit_mode); (*devs_found)++; } } @@ -351,14 +378,11 @@ static void print_one_uuid(struct btrfs_fs_devices *fs_devices, uuid_unparse(fs_devices->fsid, uuidbuf); device = list_entry(fs_devices->devices.next, struct btrfs_device, dev_list); - if (device->label && device->label[0]) - pr_verbose(LOG_DEFAULT, "Label: '%s' ", device->label); - else - pr_verbose(LOG_DEFAULT, "Label: none "); - total = device->total_devs; - pr_verbose(LOG_DEFAULT, " uuid: %s\n\tTotal devices %llu FS bytes used %s\n", uuidbuf, - total, pretty_size_mode(device->super_bytes_used, unit_mode)); + + print_filesystem_info(device->label && device->label[0] ? device->label : NULL, uuidbuf, + device->super_bytes_used, total, + unit_mode); print_devices(fs_devices, &devs_found, unit_mode); @@ -396,15 +420,9 @@ static int print_one_fs(struct btrfs_ioctl_fs_info_args *fs_info, return ret; uuid_unparse(fs_info->fsid, uuidbuf); - if (label && *label) - pr_verbose(LOG_DEFAULT, "Label: '%s' ", label); - else - pr_verbose(LOG_DEFAULT, "Label: none "); - - pr_verbose(LOG_DEFAULT, " uuid: %s\n\tTotal devices %llu FS bytes used %s\n", uuidbuf, - fs_info->num_devices, - pretty_size_mode(calc_used_bytes(space_info), - unit_mode)); + print_filesystem_info(label && *label ? label : NULL, uuidbuf, + calc_used_bytes(space_info), fs_info->num_devices, + unit_mode); for (i = 0; i < fs_info->num_devices; i++) { char *canonical_path; @@ -414,18 +432,20 @@ static int print_one_fs(struct btrfs_ioctl_fs_info_args *fs_info, /* Add check for missing devices even mounted */ fd = open((char *)tmp_dev_info->path, O_RDONLY); if (fd < 0) { - pr_verbose(LOG_DEFAULT, "\tdevid %4llu size 0 used 0 path %s MISSING\n", - tmp_dev_info->devid, tmp_dev_info->path); + print_filesystem_device(tmp_dev_info->devid, + 0, 0, + (char *)tmp_dev_info->path, + true, + unit_mode); continue; - } close(fd); canonical_path = path_canonicalize((char *)tmp_dev_info->path); - pr_verbose(LOG_DEFAULT, "\tdevid %4llu size %s used %s path %s\n", - tmp_dev_info->devid, - pretty_size_mode(tmp_dev_info->total_bytes, unit_mode), - pretty_size_mode(tmp_dev_info->bytes_used, unit_mode), - canonical_path); + print_filesystem_device(tmp_dev_info->devid, + tmp_dev_info->total_bytes, tmp_dev_info->bytes_used, + canonical_path, + false, + unit_mode); free(canonical_path); } From ea90ae1fd2d9c5538f84e8819985b8126df7bfec Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Thu, 21 Mar 2024 12:10:51 +0100 Subject: [PATCH 2/6] btrfs-progs: convert missing device printf() to a warning() To support JSON formatted output for `filesystem show` stdout can't contain warnings, the warning macro prints to stderr. --- kernel-shared/volumes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel-shared/volumes.c b/kernel-shared/volumes.c index b21231efe..93336841c 100644 --- a/kernel-shared/volumes.c +++ b/kernel-shared/volumes.c @@ -2434,7 +2434,7 @@ static int read_one_chunk(struct btrfs_fs_info *fs_info, struct btrfs_key *key, NULL); if (!map->stripes[i].dev) { map->stripes[i].dev = fill_missing_device(devid, uuid); - printf("warning, device %llu is missing\n", + warning("warning, device %llu is missing\n", (unsigned long long)devid); list_add(&map->stripes[i].dev->dev_list, &fs_info->fs_devices->devices); From 7750b6dee719468f2312bab9353aaffcae56ae4e Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Wed, 20 Mar 2024 20:44:10 +0100 Subject: [PATCH 3/6] btrfs-progs: filesystem show: implement json format output Implements JSON-formatted output for the `filesystem show` command using the `--format json` global option. Devices are in a `device-list` array, optionally showing if a device is missing. Signed-off-by: Jelle van der Waa --- Documentation/dev/dev-json.rst | 1 + cmds/filesystem.c | 178 ++++++++++++++++++++++++--------- 2 files changed, 130 insertions(+), 49 deletions(-) diff --git a/Documentation/dev/dev-json.rst b/Documentation/dev/dev-json.rst index 09d853313..cfcf1202f 100644 --- a/Documentation/dev/dev-json.rst +++ b/Documentation/dev/dev-json.rst @@ -15,6 +15,7 @@ Commands that support json output * :command:`btrfs device stats` * :command:`btrfs filesystem df` +* :command:`btrfs filesystem show` * :command:`btrfs qgroup show` * :command:`btrfs subvolume get-default` * :command:`btrfs subvolume list` diff --git a/cmds/filesystem.c b/cmds/filesystem.c index 24c5d27e5..3a1edd235 100644 --- a/cmds/filesystem.c +++ b/cmds/filesystem.c @@ -310,36 +310,75 @@ static void splice_device_list(struct list_head *seed_devices, list_splice(seed_devices, all_devices); } -static void print_filesystem_info(char *label, char uuidbuf[BTRFS_UUID_UNPARSED_SIZE], - u64 bytes_used, u64 num_devices, - unsigned unit_mode) +static const struct rowspec filesystem_show_data_rowspec[] = { + { .key = "label", .fmt = "%s", .out_json = "label" }, + { .key = "uuid", .fmt = "%s", .out_json = "uuid" }, + { .key = "num_devices", .fmt = "%llu", .out_json = "total_devices" }, + { .key = "used", .fmt = "%llu", .out_json = "used" }, + /* device list */ + { .key = "devid", .fmt = "%llu", .out_json = "devid" }, + { .key = "size", .fmt = "%llu", .out_json = "size" }, + { .key = "used", .fmt = "%llu", .out_json = "used" }, + { .key = "path", .fmt = "%s", .out_json = "path" }, + { .key = "missing", .fmt = "bool", .out_json = "missing" }, + ROWSPEC_END +}; + +static void print_filesystem_info(struct format_ctx *fctx, + char *label, char uuidbuf[BTRFS_UUID_UNPARSED_SIZE], + u64 bytes_used, u64 num_devices, + unsigned unit_mode) { - if (label) - pr_verbose(LOG_DEFAULT, "Label: '%s' ", label); - else - pr_verbose(LOG_DEFAULT, "Label: none "); - - pr_verbose(LOG_DEFAULT, " uuid: %s\n\tTotal devices %llu FS bytes used %s\n", uuidbuf, - num_devices, - pretty_size_mode(bytes_used, - unit_mode)); + if (bconf.output_format == CMD_FORMAT_JSON) { + if (label) + fmt_print(fctx, "label", label); + else + fmt_print(fctx, "label", "none"); + + fmt_print(fctx, "uuid", uuidbuf); + fmt_print(fctx, "num_devices", num_devices); + fmt_print(fctx, "used", bytes_used); + } else { + if (label) + pr_verbose(LOG_DEFAULT, "Label: '%s' ", label); + else + pr_verbose(LOG_DEFAULT, "Label: none "); + + pr_verbose(LOG_DEFAULT, " uuid: %s\n\tTotal devices %llu FS bytes used %s\n", uuidbuf, + num_devices, + pretty_size_mode(bytes_used, + unit_mode)); + } } -static void print_filesystem_device(u64 devid, u64 total_bytes, u64 bytes_used, +static void print_filesystem_device(struct format_ctx *fctx, + u64 devid, u64 total_bytes, u64 bytes_used, char *path, bool missing, unsigned unit_mode) { - pr_verbose(LOG_DEFAULT, "\tdevid %4llu size %s used %s path %s%s\n", - devid, - pretty_size_mode(total_bytes, unit_mode), - pretty_size_mode(bytes_used, unit_mode), - path, - missing ? " MISSING" : ""); + if (bconf.output_format == CMD_FORMAT_JSON) { + fmt_print_start_group(fctx, NULL, JSON_TYPE_MAP); + fmt_print(fctx, "devid", devid); + fmt_print(fctx, "size", 0); + fmt_print(fctx, "used", 0); + fmt_print(fctx, "path", path); + if (missing) + fmt_print(fctx, "missing", 0); + fmt_print_end_group(fctx, NULL); + } else { + pr_verbose(LOG_DEFAULT, "\tdevid %4llu size %s used %s path %s%s\n", + devid, + pretty_size_mode(total_bytes, unit_mode), + pretty_size_mode(bytes_used, unit_mode), + path, + missing ? " MISSING" : ""); + } } static void print_devices(struct btrfs_fs_devices *fs_devices, - u64 *devs_found, unsigned unit_mode) + u64 *devs_found, unsigned unit_mode, + struct format_ctx *fctx) { struct btrfs_device *device; struct btrfs_fs_devices *cur_fs; @@ -355,16 +394,17 @@ static void print_devices(struct btrfs_fs_devices *fs_devices, list_sort(NULL, all_devices, cmp_device_id); list_for_each_entry(device, all_devices, dev_list) { - print_filesystem_device(device->devid, - device->total_bytes, device->bytes_used, - device->name, - false, - unit_mode); + print_filesystem_device(fctx, device->devid, + device->total_bytes, device->bytes_used, + device->name, + false, + unit_mode); (*devs_found)++; } } -static void print_one_uuid(struct btrfs_fs_devices *fs_devices, +static void print_one_uuid(struct format_ctx *fctx, + struct btrfs_fs_devices *fs_devices, unsigned unit_mode) { char uuidbuf[BTRFS_UUID_UNPARSED_SIZE]; @@ -380,14 +420,27 @@ static void print_one_uuid(struct btrfs_fs_devices *fs_devices, dev_list); total = device->total_devs; - print_filesystem_info(device->label && device->label[0] ? device->label : NULL, uuidbuf, - device->super_bytes_used, total, - unit_mode); + if (bconf.output_format == CMD_FORMAT_JSON) + fmt_print_start_group(fctx, NULL, JSON_TYPE_MAP); + + print_filesystem_info(fctx, device->label && device->label[0] ? device->label : NULL, uuidbuf, + device->super_bytes_used, total, + unit_mode); - print_devices(fs_devices, &devs_found, unit_mode); + if (bconf.output_format == CMD_FORMAT_JSON) + fmt_print_start_group(fctx, "device-list", JSON_TYPE_ARRAY); - if (devs_found < total) { - pr_verbose(LOG_DEFAULT, "\t*** Some devices missing\n"); + print_devices(fs_devices, &devs_found, unit_mode, fctx); + + // TODO: global missing option? + if (bconf.output_format == CMD_FORMAT_JSON) { + fmt_print_end_group(fctx, NULL); + fmt_print_end_group(fctx, "device-list"); + } else { + if (devs_found < total) { + pr_verbose(LOG_DEFAULT, "\t*** Some devices missing\n"); + } + pr_verbose(LOG_DEFAULT, "\n"); } } @@ -402,7 +455,8 @@ static u64 calc_used_bytes(struct btrfs_ioctl_space_args *si) return ret; } -static int print_one_fs(struct btrfs_ioctl_fs_info_args *fs_info, +static int print_one_fs(struct format_ctx *fctx, + struct btrfs_ioctl_fs_info_args *fs_info, struct btrfs_ioctl_dev_info_args *dev_info, struct btrfs_ioctl_space_args *space_info, char *label, unsigned unit_mode) @@ -419,10 +473,16 @@ static int print_one_fs(struct btrfs_ioctl_fs_info_args *fs_info, else if (ret) return ret; + if (bconf.output_format == CMD_FORMAT_JSON) + fmt_print_start_group(fctx, NULL, JSON_TYPE_MAP); + uuid_unparse(fs_info->fsid, uuidbuf); - print_filesystem_info(label && *label ? label : NULL, uuidbuf, - calc_used_bytes(space_info), fs_info->num_devices, - unit_mode); + print_filesystem_info(fctx, label && *label ? label : NULL, uuidbuf, + calc_used_bytes(space_info), fs_info->num_devices, + unit_mode); + + if (bconf.output_format == CMD_FORMAT_JSON) + fmt_print_start_group(fctx, "device-list", JSON_TYPE_ARRAY); for (i = 0; i < fs_info->num_devices; i++) { char *canonical_path; @@ -432,7 +492,8 @@ static int print_one_fs(struct btrfs_ioctl_fs_info_args *fs_info, /* Add check for missing devices even mounted */ fd = open((char *)tmp_dev_info->path, O_RDONLY); if (fd < 0) { - print_filesystem_device(tmp_dev_info->devid, + print_filesystem_device(fctx, + tmp_dev_info->devid, 0, 0, (char *)tmp_dev_info->path, true, @@ -441,19 +502,24 @@ static int print_one_fs(struct btrfs_ioctl_fs_info_args *fs_info, } close(fd); canonical_path = path_canonicalize((char *)tmp_dev_info->path); - print_filesystem_device(tmp_dev_info->devid, - tmp_dev_info->total_bytes, tmp_dev_info->bytes_used, - canonical_path, - false, - unit_mode); + print_filesystem_device(fctx, tmp_dev_info->devid, + tmp_dev_info->total_bytes, tmp_dev_info->bytes_used, + canonical_path, + false, + unit_mode); free(canonical_path); } + if (bconf.output_format == CMD_FORMAT_JSON) { + fmt_print_end_group(fctx, "device-list"); + fmt_print_end_group(fctx, NULL); + } + return 0; } -static int btrfs_scan_kernel(void *search, unsigned unit_mode) +static int btrfs_scan_kernel(struct format_ctx *fctx, void *search, unsigned unit_mode) { int ret = 0, fd; int found = 0; @@ -500,10 +566,10 @@ static int btrfs_scan_kernel(void *search, unsigned unit_mode) fd = open(mnt->mnt_dir, O_RDONLY); if ((fd != -1) && !get_df(fd, &space_info_arg)) { /* Put space between filesystem entries for readability. */ - if (found != 0) + if (found != 0 && bconf.output_format != CMD_FORMAT_JSON) pr_verbose(LOG_DEFAULT, "\n"); - print_one_fs(&fs_info_arg, dev_info_arg, + print_one_fs(fctx, &fs_info_arg, dev_info_arg, space_info_arg, label, unit_mode); free(space_info_arg); memset(label, 0, sizeof(label)); @@ -767,6 +833,7 @@ static int cmd_filesystem_show(const struct cmd_struct *cmd, LIST_HEAD(all_uuids); struct btrfs_fs_devices *fs_devices; struct btrfs_root *root = NULL; + struct format_ctx fctx; char *search = NULL; char *canon_path = NULL; int ret; @@ -810,6 +877,11 @@ static int cmd_filesystem_show(const struct cmd_struct *cmd, if (check_argc_max(argc, optind + 1)) return 1; + if (bconf.output_format == CMD_FORMAT_JSON) { + fmt_start(&fctx, filesystem_show_data_rowspec, 1, 0); + fmt_print_start_group(&fctx, "filesystem-list", JSON_TYPE_ARRAY); + } + if (argc > optind) { search = argv[optind]; if (*search == 0) @@ -862,7 +934,7 @@ static int cmd_filesystem_show(const struct cmd_struct *cmd, } /* show mounted btrfs */ - ret = btrfs_scan_kernel(search, unit_mode); + ret = btrfs_scan_kernel(&fctx, search, unit_mode); if (search && !ret) { /* since search is found we are done */ goto out; @@ -913,10 +985,10 @@ static int cmd_filesystem_show(const struct cmd_struct *cmd, list_for_each_entry(fs_devices, &all_uuids, fs_list) { /* Put space between filesystem entries for readability. */ - if (needs_newline) + if (needs_newline && bconf.output_format != CMD_FORMAT_JSON) pr_verbose(LOG_DEFAULT, "\n"); - print_one_uuid(fs_devices, unit_mode); + print_one_uuid(&fctx, fs_devices, unit_mode); needs_newline = true; } @@ -930,13 +1002,21 @@ static int cmd_filesystem_show(const struct cmd_struct *cmd, free_fs_devices(fs_devices); } out: + if (bconf.output_format == CMD_FORMAT_JSON) { + fmt_print_end_group(&fctx, "filesystem-list"); + fmt_end(&fctx); + } free(canon_path); if (root) close_ctree(root); free_seen_fsid(seen_fsid_hash); return !!ret; } -static DEFINE_SIMPLE_COMMAND(filesystem_show, "show"); +#if EXPERIMENTAL +static DEFINE_COMMAND_WITH_FLAGS(filesystem_show, "show", CMD_FORMAT_JSON); +#else +DEFINE_SIMPLE_COMMAND(filesystem_show, "show"); +#endif static const char * const cmd_filesystem_sync_usage[] = { "btrfs filesystem sync ", From 04d12e229d54cf595ba11779b2e6ad868dd8427c Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Mon, 13 Jan 2025 21:30:30 +0100 Subject: [PATCH 4/6] cmds: show device used/size --- cmds/filesystem.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmds/filesystem.c b/cmds/filesystem.c index 3a1edd235..37441932f 100644 --- a/cmds/filesystem.c +++ b/cmds/filesystem.c @@ -360,8 +360,8 @@ static void print_filesystem_device(struct format_ctx *fctx, if (bconf.output_format == CMD_FORMAT_JSON) { fmt_print_start_group(fctx, NULL, JSON_TYPE_MAP); fmt_print(fctx, "devid", devid); - fmt_print(fctx, "size", 0); - fmt_print(fctx, "used", 0); + fmt_print(fctx, "size", total_bytes); + fmt_print(fctx, "used", bytes_used); fmt_print(fctx, "path", path); if (missing) fmt_print(fctx, "missing", 0); From 7999e15edbc5b48a2cbef11bd5eb34b0b35c3530 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Mon, 13 Jan 2025 22:06:38 +0100 Subject: [PATCH 5/6] Always show the missing property of a device --- cmds/filesystem.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmds/filesystem.c b/cmds/filesystem.c index 37441932f..43b53b49d 100644 --- a/cmds/filesystem.c +++ b/cmds/filesystem.c @@ -363,8 +363,7 @@ static void print_filesystem_device(struct format_ctx *fctx, fmt_print(fctx, "size", total_bytes); fmt_print(fctx, "used", bytes_used); fmt_print(fctx, "path", path); - if (missing) - fmt_print(fctx, "missing", 0); + fmt_print(fctx, "missing", missing); fmt_print_end_group(fctx, NULL); } else { pr_verbose(LOG_DEFAULT, "\tdevid %4llu size %s used %s path %s%s\n", From df1bd56915ca6b8f82bc7c7d2b288f1d4d0147c3 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Mon, 13 Jan 2025 22:06:54 +0100 Subject: [PATCH 6/6] WIP: show missing devices in json --- cmds/filesystem.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cmds/filesystem.c b/cmds/filesystem.c index 43b53b49d..6842b6967 100644 --- a/cmds/filesystem.c +++ b/cmds/filesystem.c @@ -431,8 +431,16 @@ static void print_one_uuid(struct format_ctx *fctx, print_devices(fs_devices, &devs_found, unit_mode, fctx); - // TODO: global missing option? if (bconf.output_format == CMD_FORMAT_JSON) { + if (devs_found < total) { + // TODO: Iterate over devs_found to find the missing device ids? + print_filesystem_device(fctx, 0, + 0, 0, + "", + true, + unit_mode); + } + fmt_print_end_group(fctx, NULL); fmt_print_end_group(fctx, "device-list"); } else {