Message ID | 20180302184704.22399-9-jeffm@suse.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On 2018年03月03日 02:47, jeffm@suse.com wrote: > From: Jeff Mahoney <jeffm@suse.com> > > One of the common requests I receive is for 'df' like facilities > for subvolume usage. Really, the request is for monitoring tools to be > able to understand when subvolumes may be approaching quota in the same > manner traditional file systems approach ENOSPC. > > This patch allows us to export the qgroups data in a machine-readable > format so that monitoring tools can parse it easily. > > There are two modes since JSON can technically handle 64-bit numbers > but JavaScript proper cannot. show -j enables JSON mode using 64-bit > integers directly. --json-compat presents 64-bit numbers as an array > of two 32-bit numbers (high followed by low). > > Signed-off-by: Jeff Mahoney <jeffm@suse.com> > --- > Documentation/btrfs-qgroup.asciidoc | 4 + > Makefile.inc.in | 4 +- > cmds-qgroup.c | 36 +++++- > configure.ac | 6 + > qgroup.c | 211 ++++++++++++++++++++++++++++++++++++ > qgroup.h | 3 + > 6 files changed, 258 insertions(+), 6 deletions(-) > > diff --git a/Documentation/btrfs-qgroup.asciidoc b/Documentation/btrfs-qgroup.asciidoc > index 360b3269..22a9c2a7 100644 > --- a/Documentation/btrfs-qgroup.asciidoc > +++ b/Documentation/btrfs-qgroup.asciidoc > @@ -105,6 +105,10 @@ list all qgroups which impact the given path(include ancestral qgroups) > list all qgroups which impact the given path(exclude ancestral qgroups) > -v:::: > Be more verbose. Print pathnames of member qgroups when nested. > +-j:::: > +If enabled, export qgroup usage information in JSON format. This implies --raw. > +--json-compat:::: > +By default, JSON output contains full 64-bit integers, which may be incompatible with some JSON parsers. This option exports those values as an array of 32-bit numbers in [high, low] format. > --raw:::: > raw numbers in bytes, without the 'B' suffix. > --human-readable:::: > diff --git a/Makefile.inc.in b/Makefile.inc.in > index 56271903..68bddbed 100644 > --- a/Makefile.inc.in > +++ b/Makefile.inc.in > @@ -18,9 +18,9 @@ BTRFSRESTORE_ZSTD = @BTRFSRESTORE_ZSTD@ > SUBST_CFLAGS = @CFLAGS@ > SUBST_LDFLAGS = @LDFLAGS@ > > -LIBS_BASE = @UUID_LIBS@ @BLKID_LIBS@ -L. -pthread > +LIBS_BASE = @UUID_LIBS@ @BLKID_LIBS@ @JSON_LIBS@ -L. -pthread > LIBS_COMP = @ZLIB_LIBS@ @LZO2_LIBS@ @ZSTD_LIBS@ > -STATIC_LIBS_BASE = @UUID_LIBS_STATIC@ @BLKID_LIBS_STATIC@ -L. -pthread > +STATIC_LIBS_BASE = @UUID_LIBS_STATIC@ @BLKID_LIBS_STATIC@ @JSON_LIBS_STATIC@ -L. -pthread > STATIC_LIBS_COMP = @ZLIB_LIBS_STATIC@ @LZO2_LIBS_STATIC@ @ZSTD_LIBS_STATIC@ > > prefix ?= @prefix@ > diff --git a/cmds-qgroup.c b/cmds-qgroup.c > index 94cd0fd3..eee15ef1 100644 > --- a/cmds-qgroup.c > +++ b/cmds-qgroup.c > @@ -282,6 +282,10 @@ static const char * const cmd_qgroup_show_usage[] = { > " (excluding ancestral qgroups)", > "-P print first-level qgroups using pathname", > "-v verbose, prints all nested subvolumes", > +#ifdef HAVE_JSON > + "-j export in JSON format", > + "--json-compat export in JSON compatibility mode", > +#endif > HELPINFO_UNITS_LONG, > "--sort=qgroupid,rfer,excl,max_rfer,max_excl,pathname", > " list qgroups sorted by specified items", > @@ -302,6 +306,8 @@ static int cmd_qgroup_show(int argc, char **argv) > unsigned unit_mode; > int sync = 0; > bool verbose = false; > + bool export_json = false; > + bool compat_json = false; > > struct btrfs_qgroup_comparer_set *comparer_set; > struct btrfs_qgroup_filter_set *filter_set; > @@ -314,16 +320,26 @@ static int cmd_qgroup_show(int argc, char **argv) > int c; > enum { > GETOPT_VAL_SORT = 256, > - GETOPT_VAL_SYNC > + GETOPT_VAL_SYNC, > + GETOPT_VAL_JSCOMPAT, > }; > static const struct option long_options[] = { > {"sort", required_argument, NULL, GETOPT_VAL_SORT}, > {"sync", no_argument, NULL, GETOPT_VAL_SYNC}, > {"verbose", no_argument, NULL, 'v'}, > +#ifdef HAVE_JSON > + {"json-compat", no_argument, NULL, GETOPT_VAL_JSCOMPAT}, > +#endif > { NULL, 0, NULL, 0 } > }; > - > - c = getopt_long(argc, argv, "pPcreFfv", long_options, NULL); > + const char getopt_chars[] = { > + 'p', 'P', 'c', 'r', 'e', 'F', 'f', 'v', > +#ifdef HAVE_JSON > + 'j', > +#endif > + '\0' }; > + > + c = getopt_long(argc, argv, getopt_chars, long_options, NULL); > if (c < 0) > break; > switch (c) { > @@ -353,6 +369,14 @@ static int cmd_qgroup_show(int argc, char **argv) > case 'f': > filter_flag |= 0x2; > break; > +#ifdef HAVE_JSON > + case GETOPT_VAL_JSCOMPAT: > + compat_json = true; > + case 'j': > + unit_mode = UNITS_RAW; > + export_json = true; > + break; > +#endif > case GETOPT_VAL_SORT: > ret = btrfs_qgroup_parse_sort_string(optarg, > &comparer_set); > @@ -405,7 +429,11 @@ static int cmd_qgroup_show(int argc, char **argv) > BTRFS_QGROUP_FILTER_PARENT, > qgroupid); > } > - ret = btrfs_show_qgroups(fd, filter_set, comparer_set, verbose); > + if (export_json) > + ret = btrfs_export_qgroups_json(fd, filter_set, comparer_set, > + compat_json); > + else > + ret = btrfs_show_qgroups(fd, filter_set, comparer_set, verbose); > close_file_or_dir(fd, dirstream); > free(filter_set); > free(comparer_set); > diff --git a/configure.ac b/configure.ac > index 56d17c3a..6aec672a 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -197,6 +197,12 @@ PKG_STATIC(UUID_LIBS_STATIC, [uuid]) > PKG_CHECK_MODULES(ZLIB, [zlib]) > PKG_STATIC(ZLIB_LIBS_STATIC, [zlib]) > > +PKG_CHECK_MODULES(JSON, [json-c], [ Json-c is quite common and also used by cryptsetup, so pretty good library choice. > + AC_DEFINE(HAVE_JSON, [1], [Have JSON]), > + PKG_STATIC(JSON_LIBS_STATIC, [json-c], [ > + AC_DEFINE(HAVE_JSON_STATIC, [1], [Have JSON static])], [true]) > + ], [true]) > + > AC_ARG_ENABLE([zstd], > AS_HELP_STRING([--disable-zstd], [build without zstd support]), > [], [enable_zstd=yes] > diff --git a/qgroup.c b/qgroup.c > index 2d0a6947..f632a45c 100644 > --- a/qgroup.c > +++ b/qgroup.c > @@ -16,12 +16,16 @@ > * Boston, MA 021110-1307, USA. > */ > > +#include "version.h" > #include "qgroup.h" > #include <sys/ioctl.h> > #include "ctree.h" > #include "ioctl.h" > #include "utils.h" > #include <errno.h> > +#ifdef HAVE_JSON > +#include <json-c/json.h> > +#endif > > #define BTRFS_QGROUP_NFILTERS_INCREASE (2 * BTRFS_QGROUP_FILTER_MAX) > #define BTRFS_QGROUP_NCOMPS_INCREASE (2 * BTRFS_QGROUP_COMP_MAX) > @@ -1346,6 +1350,213 @@ int btrfs_show_qgroups(int fd, > return ret; > } > > +#ifdef HAVE_JSON > +static void format_qgroupid(char *buf, size_t size, u64 qgroupid) > +{ > + int ret; > + > + ret = snprintf(buf, size, "%llu/%llu", > + btrfs_qgroup_level(qgroupid), > + btrfs_qgroup_subvid(qgroupid)); > + ASSERT(ret < sizeof(buf)); This is designed to catch truncated snprintf(), right? This can be addressed by setting up the @buf properly. (See below) And in fact, due to that super magic number, we won't hit this ASSERT() anyway. > +} > + > +static json_object *export_one_u64(u64 value, bool compat) > +{ > + json_object *array, *tmp; > + > + if (!compat) > + return json_object_new_int64(value); > + > + array = json_object_new_array(); > + if (!array) > + return NULL; > + > + tmp = json_object_new_int(value >> 32); > + if (!tmp) > + goto failure; > + json_object_array_add(array, tmp); > + > + tmp = json_object_new_int(value & 0xffffffff); > + if (!tmp) > + goto failure; > + json_object_array_add(array, tmp); > + > + return array; > +failure: > + json_object_put(array); > + return NULL; > +} > + > +static bool export_one_qgroup(json_object *container, > + const struct btrfs_qgroup *qgroup, bool compat) > +{ > + json_object *obj = json_object_new_object(); > + json_object *tmp; > + char buf[42]; Answer to the ultimate question of life, the universe, and everything. :) Although according to the format level/subvolid, it should be count_digits(MAX_U16) + 1 + count_digits(MAX_U48) + 1. (1 for '/' and 1 for '\n') Could be defined as a macro as: #define QGROUP_FORMAT_BUF_LEN (count_digits(1ULL<<16) + 1 + \ count_digits(1ULL<<48) + 1) BTW, the result is just 22. Despite that looks good. Thanks, Qu > + > + format_qgroupid(buf, sizeof(buf), qgroup->qgroupid); > + tmp = json_object_new_string(buf); > + if (!tmp) > + return false; > + json_object_object_add(obj, "qgroupid", tmp); > + > + tmp = export_one_u64(qgroup->qgroupid, compat); > + if (!tmp) > + goto failure; > + json_object_object_add(obj, "qgroupid_raw", tmp);> + > + tmp = export_one_u64(qgroup->info.generation, compat); > + if (!tmp) > + goto failure; > + json_object_object_add(obj, "generation", tmp); > + > + tmp = export_one_u64(qgroup->info.referenced, compat); > + if (!tmp) > + goto failure; > + json_object_object_add(obj, "referenced_bytes", tmp); > + > + tmp = export_one_u64(qgroup->info.exclusive, compat); > + if (!tmp) > + goto failure; > + json_object_object_add(obj, "exclusive_bytes", tmp); > + > + tmp = export_one_u64(qgroup->limit.max_referenced, compat); > + if (!tmp) > + goto failure; > + json_object_object_add(obj, "referenced_limit_bytes", tmp); > + > + tmp = export_one_u64(qgroup->limit.max_exclusive, compat); > + if (!tmp) > + goto failure; > + json_object_object_add(obj, "exclusive_limit_bytes", tmp); > + > + if (btrfs_qgroup_level(qgroup->qgroupid) == 0) { > + tmp = json_object_new_string(qgroup->pathname); > + if (!tmp) > + goto failure; > + json_object_object_add(obj, "pathname", tmp); > + } else { > + json_object *array = json_object_new_array(); > + struct btrfs_qgroup_list *list = NULL; > + if (!array) > + goto failure; > + json_object_object_add(obj, "members", array); > + > + list_for_each_entry(list, &qgroup->qgroups, next_qgroup) { > + struct btrfs_qgroup *member = list->qgroup; > + char buf2[42]; > + > + format_qgroupid(buf2, sizeof(buf2), member->qgroupid); > + tmp = json_object_new_string(buf2); > + if (!tmp) > + goto failure; > + > + json_object_array_add(array, tmp); > + } > + } > + > + json_object_object_add(container, buf, obj); > + return true; > +failure: > + json_object_put(obj); > + return false; > +} > + > +#define BTRFS_JSON_WARNING \ > +"This data contains 64-bit values that are incompatible with Javascript. Export in compatibility mode using --json-compat." > + > +static void export_all_qgroups(const struct qgroup_lookup *qgroup_lookup, > + bool compat) > +{ > + > + struct rb_node *n; > + const char *json; > + json_object *container, *dict, *obj; > + struct btrfs_qgroup *entry; > + > + container = json_object_new_object(); > + if (!container) > + goto failure_msg; > + > + obj = json_object_new_string(BTRFS_BUILD_VERSION); > + if (!obj) > + goto failure; > + json_object_object_add(container, "exporter", obj); > + > + if (!compat) { > + obj = json_object_new_string(BTRFS_JSON_WARNING); > + if (!obj) > + goto failure; > + json_object_object_add(container, "compatibility-warning", obj); > + > + obj = json_object_new_string("64-bit"); > + if (!obj) > + goto failure; > + json_object_object_add(container, "u64-format", obj); > + } else { > + obj = json_object_new_string("array"); > + if (!obj) > + goto failure; > + json_object_object_add(container, "u64-format", obj); > + } > + > + dict = json_object_new_object(); > + if (!dict) > + goto failure; > + json_object_object_add(container, "qgroup_data", dict); > + > + n = rb_first(&qgroup_lookup->root); > + while (n) { > + entry = rb_entry(n, struct btrfs_qgroup, sort_node); > + if (!export_one_qgroup(dict, entry, compat)) > + goto failure; > + n = rb_next(n); > + } > + > + json = json_object_to_json_string(container); > + if (!json) > + goto failure; > + > + puts(json); > + > + /* clean up container */ > + json_object_put(container); > + return; > + > +failure: > + json_object_put(container); > +failure_msg: > + error("Failed to create JSON object."); > +} > +#endif > + > +int btrfs_export_qgroups_json(int fd, > + struct btrfs_qgroup_filter_set *filter_set, > + struct btrfs_qgroup_comparer_set *comp_set, > + bool compat) > +{ > + > +#ifdef HAVE_JSON > + struct qgroup_lookup qgroup_lookup; > + struct qgroup_lookup sort_tree; > + int ret = 0; > + > + ret = qgroups_search_all(fd, &qgroup_lookup); > + if (ret) > + return ret; > + __filter_and_sort_qgroups(&qgroup_lookup, &sort_tree, > + filter_set, comp_set); > + export_all_qgroups(&sort_tree, compat); > + > + __free_all_qgroups(&qgroup_lookup); > + > + return ret; > +#else > + return 0; > +#endif > +} > + > int btrfs_qgroup_parse_sort_string(const char *opt_arg, > struct btrfs_qgroup_comparer_set **comps) > { > diff --git a/qgroup.h b/qgroup.h > index 688f92b2..2883727b 100644 > --- a/qgroup.h > +++ b/qgroup.h > @@ -97,6 +97,9 @@ int btrfs_qgroup_parse_sort_string(const char *opt_arg, > struct btrfs_qgroup_comparer_set **comps); > int btrfs_show_qgroups(int fd, struct btrfs_qgroup_filter_set *, > struct btrfs_qgroup_comparer_set *, bool verbose); > +int btrfs_export_qgroups_json(int fd, struct btrfs_qgroup_filter_set *, > + struct btrfs_qgroup_comparer_set *, > + bool compat); > void btrfs_qgroup_setup_print_column(enum btrfs_qgroup_column_enum column); > void btrfs_qgroup_setup_units(unsigned unit_mode); > struct btrfs_qgroup_filter_set *btrfs_qgroup_alloc_filter_set(void); >
On 3/7/18 1:34 AM, Qu Wenruo wrote: > > > On 2018年03月03日 02:47, jeffm@suse.com wrote: >> diff --git a/configure.ac b/configure.ac >> index 56d17c3a..6aec672a 100644 >> --- a/configure.ac >> +++ b/configure.ac >> @@ -197,6 +197,12 @@ PKG_STATIC(UUID_LIBS_STATIC, [uuid]) >> PKG_CHECK_MODULES(ZLIB, [zlib]) >> PKG_STATIC(ZLIB_LIBS_STATIC, [zlib]) >> >> +PKG_CHECK_MODULES(JSON, [json-c], [ > > Json-c is quite common and also used by cryptsetup, so pretty good > library choice. Yep, so that puts it in the base system packages of most distros. >> diff --git a/qgroup.c b/qgroup.c >> index 2d0a6947..f632a45c 100644 >> --- a/qgroup.c >> +++ b/qgroup.c >> return ret; >> } >> >> +#ifdef HAVE_JSON >> +static void format_qgroupid(char *buf, size_t size, u64 qgroupid) >> +{ >> + int ret; >> + >> + ret = snprintf(buf, size, "%llu/%llu", >> + btrfs_qgroup_level(qgroupid), >> + btrfs_qgroup_subvid(qgroupid)); >> + ASSERT(ret < sizeof(buf)); > > This is designed to catch truncated snprintf(), right? > This can be addressed by setting up the @buf properly. > (See below) > > And in fact, due to that super magic number, we won't hit this ASSERT() > anyway. Yep, but ASSERTs are there to detect/prevent developer mistakes. This should only trigger due to a simple bug, so we ASSERT rather than handle the error gracefully. [...] >> +static bool export_one_qgroup(json_object *container, >> + const struct btrfs_qgroup *qgroup, bool compat) >> +{ >> + json_object *obj = json_object_new_object(); >> + json_object *tmp; >> + char buf[42]; > > Answer to the ultimate question of life, the universe, and everything. :) > > Although according to the format level/subvolid, it should be > count_digits(MAX_U16) + 1 + count_digits(MAX_U48) + 1. (1 for '/' and 1 > for '\n') > > Could be defined as a macro as: > #define QGROUP_FORMAT_BUF_LEN (count_digits(1ULL<<16) + 1 + \ > count_digits(1ULL<<48) + 1) Which would mean we'd execute count_digits twice for every qgroup to resolve a constant. I'll replace the magic number with a define though. > BTW, the result is just 22. It's a worst-case. We're using %llu, so 42 is the length of two 64-bit numbers in base ten, plus the slash and nul terminator. In practice we won't hit the limit, but it doesn't hurt. Thanks for the review. -Jeff
diff --git a/Documentation/btrfs-qgroup.asciidoc b/Documentation/btrfs-qgroup.asciidoc index 360b3269..22a9c2a7 100644 --- a/Documentation/btrfs-qgroup.asciidoc +++ b/Documentation/btrfs-qgroup.asciidoc @@ -105,6 +105,10 @@ list all qgroups which impact the given path(include ancestral qgroups) list all qgroups which impact the given path(exclude ancestral qgroups) -v:::: Be more verbose. Print pathnames of member qgroups when nested. +-j:::: +If enabled, export qgroup usage information in JSON format. This implies --raw. +--json-compat:::: +By default, JSON output contains full 64-bit integers, which may be incompatible with some JSON parsers. This option exports those values as an array of 32-bit numbers in [high, low] format. --raw:::: raw numbers in bytes, without the 'B' suffix. --human-readable:::: diff --git a/Makefile.inc.in b/Makefile.inc.in index 56271903..68bddbed 100644 --- a/Makefile.inc.in +++ b/Makefile.inc.in @@ -18,9 +18,9 @@ BTRFSRESTORE_ZSTD = @BTRFSRESTORE_ZSTD@ SUBST_CFLAGS = @CFLAGS@ SUBST_LDFLAGS = @LDFLAGS@ -LIBS_BASE = @UUID_LIBS@ @BLKID_LIBS@ -L. -pthread +LIBS_BASE = @UUID_LIBS@ @BLKID_LIBS@ @JSON_LIBS@ -L. -pthread LIBS_COMP = @ZLIB_LIBS@ @LZO2_LIBS@ @ZSTD_LIBS@ -STATIC_LIBS_BASE = @UUID_LIBS_STATIC@ @BLKID_LIBS_STATIC@ -L. -pthread +STATIC_LIBS_BASE = @UUID_LIBS_STATIC@ @BLKID_LIBS_STATIC@ @JSON_LIBS_STATIC@ -L. -pthread STATIC_LIBS_COMP = @ZLIB_LIBS_STATIC@ @LZO2_LIBS_STATIC@ @ZSTD_LIBS_STATIC@ prefix ?= @prefix@ diff --git a/cmds-qgroup.c b/cmds-qgroup.c index 94cd0fd3..eee15ef1 100644 --- a/cmds-qgroup.c +++ b/cmds-qgroup.c @@ -282,6 +282,10 @@ static const char * const cmd_qgroup_show_usage[] = { " (excluding ancestral qgroups)", "-P print first-level qgroups using pathname", "-v verbose, prints all nested subvolumes", +#ifdef HAVE_JSON + "-j export in JSON format", + "--json-compat export in JSON compatibility mode", +#endif HELPINFO_UNITS_LONG, "--sort=qgroupid,rfer,excl,max_rfer,max_excl,pathname", " list qgroups sorted by specified items", @@ -302,6 +306,8 @@ static int cmd_qgroup_show(int argc, char **argv) unsigned unit_mode; int sync = 0; bool verbose = false; + bool export_json = false; + bool compat_json = false; struct btrfs_qgroup_comparer_set *comparer_set; struct btrfs_qgroup_filter_set *filter_set; @@ -314,16 +320,26 @@ static int cmd_qgroup_show(int argc, char **argv) int c; enum { GETOPT_VAL_SORT = 256, - GETOPT_VAL_SYNC + GETOPT_VAL_SYNC, + GETOPT_VAL_JSCOMPAT, }; static const struct option long_options[] = { {"sort", required_argument, NULL, GETOPT_VAL_SORT}, {"sync", no_argument, NULL, GETOPT_VAL_SYNC}, {"verbose", no_argument, NULL, 'v'}, +#ifdef HAVE_JSON + {"json-compat", no_argument, NULL, GETOPT_VAL_JSCOMPAT}, +#endif { NULL, 0, NULL, 0 } }; - - c = getopt_long(argc, argv, "pPcreFfv", long_options, NULL); + const char getopt_chars[] = { + 'p', 'P', 'c', 'r', 'e', 'F', 'f', 'v', +#ifdef HAVE_JSON + 'j', +#endif + '\0' }; + + c = getopt_long(argc, argv, getopt_chars, long_options, NULL); if (c < 0) break; switch (c) { @@ -353,6 +369,14 @@ static int cmd_qgroup_show(int argc, char **argv) case 'f': filter_flag |= 0x2; break; +#ifdef HAVE_JSON + case GETOPT_VAL_JSCOMPAT: + compat_json = true; + case 'j': + unit_mode = UNITS_RAW; + export_json = true; + break; +#endif case GETOPT_VAL_SORT: ret = btrfs_qgroup_parse_sort_string(optarg, &comparer_set); @@ -405,7 +429,11 @@ static int cmd_qgroup_show(int argc, char **argv) BTRFS_QGROUP_FILTER_PARENT, qgroupid); } - ret = btrfs_show_qgroups(fd, filter_set, comparer_set, verbose); + if (export_json) + ret = btrfs_export_qgroups_json(fd, filter_set, comparer_set, + compat_json); + else + ret = btrfs_show_qgroups(fd, filter_set, comparer_set, verbose); close_file_or_dir(fd, dirstream); free(filter_set); free(comparer_set); diff --git a/configure.ac b/configure.ac index 56d17c3a..6aec672a 100644 --- a/configure.ac +++ b/configure.ac @@ -197,6 +197,12 @@ PKG_STATIC(UUID_LIBS_STATIC, [uuid]) PKG_CHECK_MODULES(ZLIB, [zlib]) PKG_STATIC(ZLIB_LIBS_STATIC, [zlib]) +PKG_CHECK_MODULES(JSON, [json-c], [ + AC_DEFINE(HAVE_JSON, [1], [Have JSON]), + PKG_STATIC(JSON_LIBS_STATIC, [json-c], [ + AC_DEFINE(HAVE_JSON_STATIC, [1], [Have JSON static])], [true]) + ], [true]) + AC_ARG_ENABLE([zstd], AS_HELP_STRING([--disable-zstd], [build without zstd support]), [], [enable_zstd=yes] diff --git a/qgroup.c b/qgroup.c index 2d0a6947..f632a45c 100644 --- a/qgroup.c +++ b/qgroup.c @@ -16,12 +16,16 @@ * Boston, MA 021110-1307, USA. */ +#include "version.h" #include "qgroup.h" #include <sys/ioctl.h> #include "ctree.h" #include "ioctl.h" #include "utils.h" #include <errno.h> +#ifdef HAVE_JSON +#include <json-c/json.h> +#endif #define BTRFS_QGROUP_NFILTERS_INCREASE (2 * BTRFS_QGROUP_FILTER_MAX) #define BTRFS_QGROUP_NCOMPS_INCREASE (2 * BTRFS_QGROUP_COMP_MAX) @@ -1346,6 +1350,213 @@ int btrfs_show_qgroups(int fd, return ret; } +#ifdef HAVE_JSON +static void format_qgroupid(char *buf, size_t size, u64 qgroupid) +{ + int ret; + + ret = snprintf(buf, size, "%llu/%llu", + btrfs_qgroup_level(qgroupid), + btrfs_qgroup_subvid(qgroupid)); + ASSERT(ret < sizeof(buf)); +} + +static json_object *export_one_u64(u64 value, bool compat) +{ + json_object *array, *tmp; + + if (!compat) + return json_object_new_int64(value); + + array = json_object_new_array(); + if (!array) + return NULL; + + tmp = json_object_new_int(value >> 32); + if (!tmp) + goto failure; + json_object_array_add(array, tmp); + + tmp = json_object_new_int(value & 0xffffffff); + if (!tmp) + goto failure; + json_object_array_add(array, tmp); + + return array; +failure: + json_object_put(array); + return NULL; +} + +static bool export_one_qgroup(json_object *container, + const struct btrfs_qgroup *qgroup, bool compat) +{ + json_object *obj = json_object_new_object(); + json_object *tmp; + char buf[42]; + + format_qgroupid(buf, sizeof(buf), qgroup->qgroupid); + tmp = json_object_new_string(buf); + if (!tmp) + return false; + json_object_object_add(obj, "qgroupid", tmp); + + tmp = export_one_u64(qgroup->qgroupid, compat); + if (!tmp) + goto failure; + json_object_object_add(obj, "qgroupid_raw", tmp); + + tmp = export_one_u64(qgroup->info.generation, compat); + if (!tmp) + goto failure; + json_object_object_add(obj, "generation", tmp); + + tmp = export_one_u64(qgroup->info.referenced, compat); + if (!tmp) + goto failure; + json_object_object_add(obj, "referenced_bytes", tmp); + + tmp = export_one_u64(qgroup->info.exclusive, compat); + if (!tmp) + goto failure; + json_object_object_add(obj, "exclusive_bytes", tmp); + + tmp = export_one_u64(qgroup->limit.max_referenced, compat); + if (!tmp) + goto failure; + json_object_object_add(obj, "referenced_limit_bytes", tmp); + + tmp = export_one_u64(qgroup->limit.max_exclusive, compat); + if (!tmp) + goto failure; + json_object_object_add(obj, "exclusive_limit_bytes", tmp); + + if (btrfs_qgroup_level(qgroup->qgroupid) == 0) { + tmp = json_object_new_string(qgroup->pathname); + if (!tmp) + goto failure; + json_object_object_add(obj, "pathname", tmp); + } else { + json_object *array = json_object_new_array(); + struct btrfs_qgroup_list *list = NULL; + if (!array) + goto failure; + json_object_object_add(obj, "members", array); + + list_for_each_entry(list, &qgroup->qgroups, next_qgroup) { + struct btrfs_qgroup *member = list->qgroup; + char buf2[42]; + + format_qgroupid(buf2, sizeof(buf2), member->qgroupid); + tmp = json_object_new_string(buf2); + if (!tmp) + goto failure; + + json_object_array_add(array, tmp); + } + } + + json_object_object_add(container, buf, obj); + return true; +failure: + json_object_put(obj); + return false; +} + +#define BTRFS_JSON_WARNING \ +"This data contains 64-bit values that are incompatible with Javascript. Export in compatibility mode using --json-compat." + +static void export_all_qgroups(const struct qgroup_lookup *qgroup_lookup, + bool compat) +{ + + struct rb_node *n; + const char *json; + json_object *container, *dict, *obj; + struct btrfs_qgroup *entry; + + container = json_object_new_object(); + if (!container) + goto failure_msg; + + obj = json_object_new_string(BTRFS_BUILD_VERSION); + if (!obj) + goto failure; + json_object_object_add(container, "exporter", obj); + + if (!compat) { + obj = json_object_new_string(BTRFS_JSON_WARNING); + if (!obj) + goto failure; + json_object_object_add(container, "compatibility-warning", obj); + + obj = json_object_new_string("64-bit"); + if (!obj) + goto failure; + json_object_object_add(container, "u64-format", obj); + } else { + obj = json_object_new_string("array"); + if (!obj) + goto failure; + json_object_object_add(container, "u64-format", obj); + } + + dict = json_object_new_object(); + if (!dict) + goto failure; + json_object_object_add(container, "qgroup_data", dict); + + n = rb_first(&qgroup_lookup->root); + while (n) { + entry = rb_entry(n, struct btrfs_qgroup, sort_node); + if (!export_one_qgroup(dict, entry, compat)) + goto failure; + n = rb_next(n); + } + + json = json_object_to_json_string(container); + if (!json) + goto failure; + + puts(json); + + /* clean up container */ + json_object_put(container); + return; + +failure: + json_object_put(container); +failure_msg: + error("Failed to create JSON object."); +} +#endif + +int btrfs_export_qgroups_json(int fd, + struct btrfs_qgroup_filter_set *filter_set, + struct btrfs_qgroup_comparer_set *comp_set, + bool compat) +{ + +#ifdef HAVE_JSON + struct qgroup_lookup qgroup_lookup; + struct qgroup_lookup sort_tree; + int ret = 0; + + ret = qgroups_search_all(fd, &qgroup_lookup); + if (ret) + return ret; + __filter_and_sort_qgroups(&qgroup_lookup, &sort_tree, + filter_set, comp_set); + export_all_qgroups(&sort_tree, compat); + + __free_all_qgroups(&qgroup_lookup); + + return ret; +#else + return 0; +#endif +} + int btrfs_qgroup_parse_sort_string(const char *opt_arg, struct btrfs_qgroup_comparer_set **comps) { diff --git a/qgroup.h b/qgroup.h index 688f92b2..2883727b 100644 --- a/qgroup.h +++ b/qgroup.h @@ -97,6 +97,9 @@ int btrfs_qgroup_parse_sort_string(const char *opt_arg, struct btrfs_qgroup_comparer_set **comps); int btrfs_show_qgroups(int fd, struct btrfs_qgroup_filter_set *, struct btrfs_qgroup_comparer_set *, bool verbose); +int btrfs_export_qgroups_json(int fd, struct btrfs_qgroup_filter_set *, + struct btrfs_qgroup_comparer_set *, + bool compat); void btrfs_qgroup_setup_print_column(enum btrfs_qgroup_column_enum column); void btrfs_qgroup_setup_units(unsigned unit_mode); struct btrfs_qgroup_filter_set *btrfs_qgroup_alloc_filter_set(void);