diff options
author | Umer Saleem <[email protected]> | 2024-04-05 21:02:30 +0500 |
---|---|---|
committer | Brian Behlendorf <[email protected]> | 2024-08-06 12:45:45 -0700 |
commit | aa15b60e58599c8851358f6c7f85097f79fcfe55 (patch) | |
tree | 8f84e80bcec088a48b42c87a192f88524f59ac8e /cmd/zfs | |
parent | 6c7d41a64373a2df615ec804b9c5d635d5019064 (diff) |
JSON output support for zfs version and zfs get
This commit adds support for JSON output for zfs version and zfs get
commands. '-j' flag can be used to get output in JSON format.
Information is collected in nvlist objects which is later printed in
JSON format. Existing options that work for zfs get and zfs version
also work with '-j' flag.
man pages for zfs get and zfs version are updated accordingly.
Reviewed-by: Tony Hutter <[email protected]>
Reviewed-by: Ameer Hamza <[email protected]>
Signed-off-by: Umer Saleem <[email protected]>
Closes #16217
Diffstat (limited to 'cmd/zfs')
-rw-r--r-- | cmd/zfs/zfs_main.c | 228 |
1 files changed, 213 insertions, 15 deletions
diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 75c0e40b6..e3100a2f6 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -134,6 +134,10 @@ static int zfs_do_unzone(int argc, char **argv); static int zfs_do_help(int argc, char **argv); +enum zfs_options { + ZFS_OPTION_JSON_NUMS_AS_INT = 1024 +}; + /* * Enable a reasonable set of defaults for libumem debugging on DEBUG builds. */ @@ -272,6 +276,8 @@ static zfs_command_t command_table[] = { #define NCOMMAND (sizeof (command_table) / sizeof (command_table[0])) +#define MAX_CMD_LEN 256 + zfs_command_t *current_command; static const char * @@ -292,7 +298,7 @@ get_usage(zfs_help_t idx) "<filesystem|volume>@<snap>[%<snap>][,...]\n" "\tdestroy <filesystem|volume>#<bookmark>\n")); case HELP_GET: - return (gettext("\tget [-rHp] [-d max] " + return (gettext("\tget [-rHp] [-j [--json-int]] [-d max] " "[-o \"all\" | field[,...]]\n" "\t [-t type[,...]] [-s source[,...]]\n" "\t <\"all\" | property[,...]> " @@ -420,7 +426,7 @@ get_usage(zfs_help_t idx) "\t <filesystem|volume>\n" "\tchange-key -i [-l] <filesystem|volume>\n")); case HELP_VERSION: - return (gettext("\tversion\n")); + return (gettext("\tversion [-j]\n")); case HELP_REDACT: return (gettext("\tredact <snapshot> <bookmark> " "<redaction_snapshot> ...\n")); @@ -1885,7 +1891,109 @@ is_recvd_column(zprop_get_cbdata_t *cbp) } /* - * zfs get [-rHp] [-o all | field[,field]...] [-s source[,source]...] + * Generates an nvlist with output version for every command based on params. + * Purpose of this is to add a version of JSON output, considering the schema + * format might be updated for each command in future. + * + * Schema: + * + * "output_version": { + * "command": string, + * "vers_major": integer, + * "vers_minor": integer, + * } + */ +static nvlist_t * +zfs_json_schema(int maj_v, int min_v) +{ + nvlist_t *sch = NULL; + nvlist_t *ov = NULL; + char cmd[MAX_CMD_LEN]; + snprintf(cmd, MAX_CMD_LEN, "zfs %s", current_command->name); + + sch = fnvlist_alloc(); + ov = fnvlist_alloc(); + fnvlist_add_string(ov, "command", cmd); + fnvlist_add_uint32(ov, "vers_major", maj_v); + fnvlist_add_uint32(ov, "vers_minor", min_v); + fnvlist_add_nvlist(sch, "output_version", ov); + fnvlist_free(ov); + return (sch); +} + +static void +fill_dataset_info(nvlist_t *list, zfs_handle_t *zhp, boolean_t as_int) +{ + char createtxg[ZFS_MAXPROPLEN]; + zfs_type_t type = zfs_get_type(zhp); + nvlist_add_string(list, "name", zfs_get_name(zhp)); + + switch (type) { + case ZFS_TYPE_FILESYSTEM: + fnvlist_add_string(list, "type", "FILESYSTEM"); + break; + case ZFS_TYPE_VOLUME: + fnvlist_add_string(list, "type", "VOLUME"); + break; + case ZFS_TYPE_SNAPSHOT: + fnvlist_add_string(list, "type", "SNAPSHOT"); + break; + case ZFS_TYPE_POOL: + fnvlist_add_string(list, "type", "POOL"); + break; + case ZFS_TYPE_BOOKMARK: + fnvlist_add_string(list, "type", "BOOKMARK"); + break; + default: + fnvlist_add_string(list, "type", "UNKNOWN"); + break; + } + + if (type != ZFS_TYPE_POOL) + fnvlist_add_string(list, "pool", zfs_get_pool_name(zhp)); + + if (as_int) { + fnvlist_add_uint64(list, "createtxg", zfs_prop_get_int(zhp, + ZFS_PROP_CREATETXG)); + } else { + if (zfs_prop_get(zhp, ZFS_PROP_CREATETXG, createtxg, + sizeof (createtxg), NULL, NULL, 0, B_TRUE) == 0) + fnvlist_add_string(list, "createtxg", createtxg); + } + + if (type == ZFS_TYPE_SNAPSHOT) { + char *ds, *snap; + ds = snap = strdup(zfs_get_name(zhp)); + ds = strsep(&snap, "@"); + fnvlist_add_string(list, "dataset", ds); + fnvlist_add_string(list, "snapshot_name", snap); + free(ds); + } +} + +static int +zprop_collect_property(const char *name, zprop_get_cbdata_t *cbp, + const char *propname, const char *value, zprop_source_t sourcetype, + const char *source, const char *recvd_value, nvlist_t *nvl) +{ + if (cbp->cb_json) { + if ((sourcetype & cbp->cb_sources) == 0) + return (0); + else { + return (zprop_nvlist_one_property(propname, value, + sourcetype, source, recvd_value, nvl, + cbp->cb_json_as_int)); + } + } else { + zprop_print_one_property(name, cbp, + propname, value, sourcetype, source, recvd_value); + return (0); + } +} + +/* + * zfs get [-rHp] [-j [--json-int]] [-o all | field[,field]...] + * [-s source[,source]...] * < all | property[,property]... > < fs | snap | vol > ... * * -r recurse over any child datasets @@ -1898,6 +2006,8 @@ is_recvd_column(zprop_get_cbdata_t *cbp) * "local,default,inherited,received,temporary,none". Default is * all six. * -p Display values in parsable (literal) format. + * -j Display output in JSON format. + * --json-int Display numbers as integers instead of strings. * * Prints properties for the given datasets. The user can control which * columns to display as well as which property types to allow. @@ -1917,9 +2027,21 @@ get_callback(zfs_handle_t *zhp, void *data) nvlist_t *user_props = zfs_get_user_props(zhp); zprop_list_t *pl = cbp->cb_proplist; nvlist_t *propval; + nvlist_t *item, *d, *props; + item = d = props = NULL; const char *strval; const char *sourceval; boolean_t received = is_recvd_column(cbp); + int err = 0; + + if (cbp->cb_json) { + d = fnvlist_lookup_nvlist(cbp->cb_jsobj, "datasets"); + if (d == NULL) { + fprintf(stderr, "datasets obj not found.\n"); + exit(1); + } + props = fnvlist_alloc(); + } for (; pl != NULL; pl = pl->pl_next) { char *recvdval = NULL; @@ -1954,9 +2076,9 @@ get_callback(zfs_handle_t *zhp, void *data) cbp->cb_literal) == 0)) recvdval = rbuf; - zprop_print_one_property(zfs_get_name(zhp), cbp, + err = zprop_collect_property(zfs_get_name(zhp), cbp, zfs_prop_to_name(pl->pl_prop), - buf, sourcetype, source, recvdval); + buf, sourcetype, source, recvdval, props); } else if (zfs_prop_userquota(pl->pl_user_prop)) { sourcetype = ZPROP_SRC_LOCAL; @@ -1966,8 +2088,9 @@ get_callback(zfs_handle_t *zhp, void *data) (void) strlcpy(buf, "-", sizeof (buf)); } - zprop_print_one_property(zfs_get_name(zhp), cbp, - pl->pl_user_prop, buf, sourcetype, source, NULL); + err = zprop_collect_property(zfs_get_name(zhp), cbp, + pl->pl_user_prop, buf, sourcetype, source, NULL, + props); } else if (zfs_prop_written(pl->pl_user_prop)) { sourcetype = ZPROP_SRC_LOCAL; @@ -1977,8 +2100,9 @@ get_callback(zfs_handle_t *zhp, void *data) (void) strlcpy(buf, "-", sizeof (buf)); } - zprop_print_one_property(zfs_get_name(zhp), cbp, - pl->pl_user_prop, buf, sourcetype, source, NULL); + err = zprop_collect_property(zfs_get_name(zhp), cbp, + pl->pl_user_prop, buf, sourcetype, source, NULL, + props); } else { if (nvlist_lookup_nvlist(user_props, pl->pl_user_prop, &propval) != 0) { @@ -2010,9 +2134,24 @@ get_callback(zfs_handle_t *zhp, void *data) cbp->cb_literal) == 0)) recvdval = rbuf; - zprop_print_one_property(zfs_get_name(zhp), cbp, + err = zprop_collect_property(zfs_get_name(zhp), cbp, pl->pl_user_prop, strval, sourcetype, - source, recvdval); + source, recvdval, props); + } + if (err != 0) + return (err); + } + + if (cbp->cb_json) { + if (!nvlist_empty(props)) { + item = fnvlist_alloc(); + fill_dataset_info(item, zhp, cbp->cb_json_as_int); + fnvlist_add_nvlist(item, "properties", props); + fnvlist_add_nvlist(d, zfs_get_name(zhp), item); + fnvlist_free(props); + fnvlist_free(item); + } else { + fnvlist_free(props); } } @@ -2029,6 +2168,7 @@ zfs_do_get(int argc, char **argv) int ret = 0; int limit = 0; zprop_list_t fake_name = { 0 }; + nvlist_t *data; /* * Set up default columns and sources. @@ -2040,8 +2180,14 @@ zfs_do_get(int argc, char **argv) cb.cb_columns[3] = GET_COL_SOURCE; cb.cb_type = ZFS_TYPE_DATASET; + struct option long_options[] = { + {"json-int", no_argument, NULL, ZFS_OPTION_JSON_NUMS_AS_INT}, + {0, 0, 0, 0} + }; + /* check options */ - while ((c = getopt(argc, argv, ":d:o:s:rt:Hp")) != -1) { + while ((c = getopt_long(argc, argv, ":d:o:s:jrt:Hp", long_options, + NULL)) != -1) { switch (c) { case 'p': cb.cb_literal = B_TRUE; @@ -2055,6 +2201,17 @@ zfs_do_get(int argc, char **argv) case 'H': cb.cb_scripted = B_TRUE; break; + case 'j': + cb.cb_json = B_TRUE; + cb.cb_jsobj = zfs_json_schema(0, 1); + data = fnvlist_alloc(); + fnvlist_add_nvlist(cb.cb_jsobj, "datasets", data); + fnvlist_free(data); + break; + case ZFS_OPTION_JSON_NUMS_AS_INT: + cb.cb_json_as_int = B_TRUE; + cb.cb_literal = B_TRUE; + break; case ':': (void) fprintf(stderr, gettext("missing argument for " "'%c' option\n"), optopt); @@ -2178,7 +2335,6 @@ found2:; found3:; } break; - case '?': (void) fprintf(stderr, gettext("invalid option '%c'\n"), optopt); @@ -2195,6 +2351,12 @@ found3:; usage(B_FALSE); } + if (!cb.cb_json && cb.cb_json_as_int) { + (void) fprintf(stderr, gettext("'--json-int' only works with" + " '-j' option\n")); + usage(B_FALSE); + } + fields = argv[0]; /* @@ -2235,6 +2397,11 @@ found3:; ret = zfs_for_each(argc, argv, flags, types, NULL, &cb.cb_proplist, limit, get_callback, &cb); + if (ret == 0 && cb.cb_json) + zcmd_print_json(cb.cb_jsobj); + else if (ret != 0 && cb.cb_json) + nvlist_free(cb.cb_jsobj); + if (cb.cb_proplist == &fake_name) zprop_free_list(fake_name.pl_next); else @@ -8811,8 +8978,39 @@ found:; static int zfs_do_version(int argc, char **argv) { - (void) argc, (void) argv; - return (zfs_version_print() != 0); + int c; + nvlist_t *jsobj = NULL, *zfs_ver = NULL; + boolean_t json = B_FALSE; + while ((c = getopt(argc, argv, "j")) != -1) { + switch (c) { + case 'j': + json = B_TRUE; + jsobj = zfs_json_schema(0, 1); + break; + case '?': + (void) fprintf(stderr, gettext("invalid option '%c'\n"), + optopt); + usage(B_FALSE); + } + } + + argc -= optind; + if (argc != 0) { + (void) fprintf(stderr, "too many arguments\n"); + usage(B_FALSE); + } + + if (json) { + zfs_ver = zfs_version_nvlist(); + if (zfs_ver) { + fnvlist_add_nvlist(jsobj, "zfs_version", zfs_ver); + zcmd_print_json(jsobj); + fnvlist_free(zfs_ver); + return (0); + } else + return (-1); + } else + return (zfs_version_print() != 0); } /* Display documentation */ |