diff --git a/Makefile b/Makefile index 8ae28c4d..2ffe89f2 100644 --- a/Makefile +++ b/Makefile @@ -134,7 +134,7 @@ objects = ctree.o disk-io.o kernel-lib/radix-tree.o extent-tree.o print-tree.o \ common/task-utils.o \ inode.o file.o find-root.o free-space-tree.o common/help.o send-dump.o \ common/fsfeatures.o kernel-lib/tables.o kernel-lib/raid56.o transaction.o \ - delayed-ref.o + delayed-ref.o common/format-output.o cmds_objects = cmds/subvolume.o cmds/filesystem.o cmds/device.o cmds/scrub.o \ cmds/inspect.o cmds/balance.o cmds/send.o cmds/receive.o \ cmds/quota.o cmds/qgroup.o cmds/replace.o check/main.o \ diff --git a/common/format-output.c b/common/format-output.c new file mode 100644 index 00000000..c5f1b51f --- /dev/null +++ b/common/format-output.c @@ -0,0 +1,314 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License v2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + +#include "kerncompat.h" +#include +#include +#include "common-defs.h" +#include "common/format-output.h" +#include "common/utils.h" +#include "cmds/commands.h" + +static void print_uuid(const u8 *uuid) +{ + char uuidparse[BTRFS_UUID_UNPARSED_SIZE]; + + if (uuid_is_null(uuid)) { + putchar('-'); + } else { + uuid_unparse(uuid, uuidparse); + printf("%s", uuidparse); + } +} + +static void fmt_indent1(int indent) +{ + while (indent--) + putchar(' '); +} + +static void fmt_indent2(int indent) +{ + while (indent--) { + putchar(' '); + putchar(' '); + } +} + +static void fmt_error(struct format_ctx *fctx) +{ + printf("INTERNAL ERROR: formatting json: depth=%d\n", fctx->depth); + exit(1); +} + +static void fmt_inc_depth(struct format_ctx *fctx) +{ + if (fctx->depth >= JSON_NESTING_LIMIT - 1) { + printf("INTERNAL ERROR: nesting too deep, limit %d\n", + JSON_NESTING_LIMIT); + exit(1); + } + fctx->depth++; +} + +static void fmt_dec_depth(struct format_ctx *fctx) +{ + if (fctx->depth < 1) { + printf("INTERNAL ERROR: nesting below first level\n"); + exit(1); + } + fctx->depth--; +} + +static void fmt_separator(struct format_ctx *fctx) +{ + if (bconf.output_format == CMD_FORMAT_JSON) { + /* Check current depth */ + if (fctx->memb[fctx->depth] == 0) { + /* First member, only indent */ + putchar('\n'); + fmt_indent2(fctx->depth); + fctx->memb[fctx->depth] = 1; + } else if (fctx->memb[fctx->depth] == 1) { + /* Something has been printed already */ + printf(",\n"); + fmt_indent2(fctx->depth); + fctx->memb[fctx->depth] = 2; + } else { + /* N-th member */ + printf(",\n"); + fmt_indent2(fctx->depth); + } + } +} + +void fmt_start(struct format_ctx *fctx, const struct rowspec *spec, int width, + int indent) +{ + fctx->width = width; + fctx->indent = indent; + fctx->rowspec = spec; + fctx->depth = 1; + + if (bconf.output_format & CMD_FORMAT_JSON) { + putchar('{'); + /* The top level is a map and is the first one */ + fctx->jtype[fctx->depth] = JSON_TYPE_MAP; + fctx->memb[fctx->depth] = 0; + fmt_print_start_group(fctx, "__header", JSON_TYPE_MAP); + fmt_separator(fctx); + printf("\"version\": \"1\""); + fctx->memb[fctx->depth] = 1; + fmt_print_end_group(fctx, "__header"); + } +} + +void fmt_end(struct format_ctx *fctx) +{ + if (fctx->depth != 1) + fprintf(stderr, "WARNING: wrong nesting\n"); + + /* Close, no continuation to print */ + + if (bconf.output_format & CMD_FORMAT_TEXT) + putchar('\n'); + else if (bconf.output_format & CMD_FORMAT_JSON) { + fmt_dec_depth(fctx); + fmt_separator(fctx); + printf("}\n"); + } +} + +void fmt_start_list_value(struct format_ctx *fctx) +{ + if (bconf.output_format == CMD_FORMAT_TEXT) { + fmt_indent1(fctx->indent); + } else if (bconf.output_format == CMD_FORMAT_JSON) { + fmt_separator(fctx); + fmt_indent2(fctx->depth); + putchar('"'); + } +} + +void fmt_end_list_value(struct format_ctx *fctx) +{ + if (bconf.output_format == CMD_FORMAT_TEXT) + putchar('\n'); + else if (bconf.output_format == CMD_FORMAT_JSON) + putchar('"'); +} + +void fmt_start_value(struct format_ctx *fctx, const struct rowspec *row) +{ + if (bconf.output_format == CMD_FORMAT_TEXT) { + if (strcmp(row->fmt, "list") == 0) + putchar('\n'); + else if (strcmp(row->fmt, "map") == 0) + putchar('\n'); + } else if (bconf.output_format == CMD_FORMAT_JSON) { + if (strcmp(row->fmt, "list") == 0) { + } else if (strcmp(row->fmt, "map") == 0) { + } else { + putchar('"'); + } + } +} + +void fmt_end_value(struct format_ctx *fctx, const struct rowspec *row) +{ + if (bconf.output_format == CMD_FORMAT_JSON) { + if (strcmp(row->fmt, "list") == 0) { + } else if (strcmp(row->fmt, "map") == 0) { + } else { + putchar('"'); + } + } +} + +void fmt_print_start_group(struct format_ctx *fctx, const char *name, + enum json_type jtype) +{ + if (bconf.output_format == CMD_FORMAT_JSON) { + fmt_separator(fctx); + fmt_inc_depth(fctx); + fctx->jtype[fctx->depth] = jtype; + fctx->memb[fctx->depth] = 0; + if (jtype == JSON_TYPE_MAP) + printf("\"%s\": {", name); + else if (jtype == JSON_TYPE_ARRAY) + printf("\"%s\": [", name); + else + fmt_error(fctx); + } +} + +void fmt_print_end_group(struct format_ctx *fctx, const char *name) +{ + if (bconf.output_format == CMD_FORMAT_JSON) { + /* Whatever was on previous line won't continue with "," */ + const enum json_type jtype = fctx->jtype[fctx->depth]; + + fmt_dec_depth(fctx); + putchar('\n'); + fmt_indent2(fctx->depth); + if (jtype == JSON_TYPE_MAP) + putchar('}'); + else if (jtype == JSON_TYPE_ARRAY) + putchar(']'); + else + fmt_error(fctx); + } +} + +/* Use rowspec to print according to currently set output format */ +void fmt_print(struct format_ctx *fctx, const char* key, ...) +{ + va_list args; + const struct rowspec *row; + bool found = false; + + va_start(args, key); + row = &fctx->rowspec[0]; + + while (row->key) { + if (strcmp(key, row->key) == 0) { + found = true; + break; + } + row++; + } + if (!found) { + printf("INTERNAL ERROR: unknown key: %s\n", key); + exit(1); + } + + if (bconf.output_format == CMD_FORMAT_TEXT) { + const bool print_colon = row->out_text[0]; + int len; + + putchar('\n'); + fmt_indent1(fctx->indent); + len = strlen(row->out_text); + + printf("%s", row->out_text); + if (print_colon) { + putchar(':'); + len++; + } + fmt_indent1(fctx->width - len); + } else if (bconf.output_format == CMD_FORMAT_JSON) { + if (strcmp(row->fmt, "list") == 0) { + fmt_print_start_group(fctx, row->out_json, + JSON_TYPE_ARRAY); + } else if (strcmp(row->fmt, "map") == 0) { + fmt_print_start_group(fctx, row->out_json, + JSON_TYPE_MAP); + } else { + /* Simple key/values */ + fmt_separator(fctx); + printf("\"%s\": ", row->out_json); + } + } + + fmt_start_value(fctx, row); + + if (row->fmt[0] == '%') { + vprintf(row->fmt, args); + } else if (strcmp(row->fmt, "uuid") == 0) { + const u8 *uuid = va_arg(args, const u8*); + + print_uuid(uuid); + } else if (strcmp(row->fmt, "time-long") == 0) { + const time_t ts = va_arg(args, time_t); + + if (ts) { + char tstr[256]; + struct tm tm; + + localtime_r(&ts, &tm); + strftime(tstr, 256, "%Y-%m-%d %X %z", &tm); + printf(tstr); + } else { + putchar('-'); + } + } else if (strcmp(row->fmt, "list") == 0) { + } else if (strcmp(row->fmt, "map") == 0) { + } else if (strcmp(row->fmt, "qgroupid") == 0) { + const u64 level = va_arg(args, u64); + const u64 id = va_arg(args, u64); + + printf("%llu/%llu", level, id); + } else if (strcmp(row->fmt, "size-or-none") == 0) { + const u64 size = va_arg(args, u64); + const unsigned int unit_mode = va_arg(args, unsigned int); + + if (size) + printf("%s", pretty_size_mode(size, unit_mode)); + else + putchar('-'); + } else if (strcmp(row->fmt, "size") == 0) { + const u64 size = va_arg(args, u64); + const unsigned int unit_mode = va_arg(args, unsigned int); + + printf("%s", pretty_size_mode(size, unit_mode)); + } else { + printf("INTERNAL ERROR: unknown format %s\n", row->fmt); + } + + fmt_end_value(fctx, row); + /* No newline here, the line is closed by next value or group end */ + va_end(args); +} diff --git a/common/format-output.h b/common/format-output.h new file mode 100644 index 00000000..b73f53ba --- /dev/null +++ b/common/format-output.h @@ -0,0 +1,90 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License v2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + +#ifndef __BTRFS_FORMAT_OUTPUT_H__ +#define __BTRFS_FORMAT_OUTPUT_H__ + +struct rowspec { + /* Identifier for the row */ + const char *key; + /* + * Format to print: + * - starting with %: must be a valid printf spec + * (values: va_args) + * - uuid: format UUID as text + * (value: u8 *uuid) + * - list: print list opening bracket [ + * (values printed separately) + * - map: start a new group, opens { + * (values printed separately) + * - size: pretty print size according to + * (values: u64 size, u32 unit_type) + * - size-or-none: pretty print non-zero values, "-" otherwise + * (values: same as "size") + * - qgroupid: print qgroup from separate level and id + * (values: u64 level, u64 id) + */ + const char *fmt; + /* String to print in format:text */ + const char *out_text; + /* String to print in format:json, is quoted */ + const char *out_json; +}; + +#define ROWSPEC_END { .key = NULL }, +#define JSON_NESTING_LIMIT 16 + +/* + * Nested types + */ +enum json_type { + JSON_TYPE_INVALID, + JSON_TYPE_MAP, + JSON_TYPE_ARRAY, +}; + +struct format_ctx { + /* Preferred width of the first column with key (format: text) */ + int width; + /* Initial indentation before the first column (format: text) */ + int indent; + /* Nesting of groups like lists or maps (format: json) */ + int depth; + + /* Array of named output fileds as defined by the command */ + const struct rowspec *rowspec; + + char jtype[JSON_NESTING_LIMIT]; + enum json_type memb[JSON_NESTING_LIMIT]; +}; + +void fmt_start(struct format_ctx *fctx, const struct rowspec *spec, int width, + int indent); +void fmt_end(struct format_ctx *fctx); + +void fmt_print(struct format_ctx *fctx, const char* key, ...); + +void fmt_start_list_value(struct format_ctx *fctx); +void fmt_end_list_value(struct format_ctx *fctx); + +void fmt_start_value(struct format_ctx *fctx, const struct rowspec *row); +void fmt_end_value(struct format_ctx *fctx, const struct rowspec *row); + +void fmt_print_start_group(struct format_ctx *fctx, const char *name, + enum json_type jtype); +void fmt_print_end_group(struct format_ctx *fctx, const char *name); + +#endif