diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 75c0e40b61..e3100a2f6e 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) "@[%][,...]\n" "\tdestroy #\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 \n" "\tchange-key -i [-l] \n")); case HELP_VERSION: - return (gettext("\tversion\n")); + return (gettext("\tversion [-j]\n")); case HELP_REDACT: return (gettext("\tredact " " ...\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 */ diff --git a/include/libzfs.h b/include/libzfs.h index 979b919ce2..42054d74b4 100644 --- a/include/libzfs.h +++ b/include/libzfs.h @@ -631,6 +631,8 @@ _LIBZFS_H int zprop_get_list(libzfs_handle_t *, char *, zprop_list_t **, zfs_type_t); _LIBZFS_H void zprop_free_list(zprop_list_t *); +_LIBZFS_H void zcmd_print_json(nvlist_t *); + #define ZFS_GET_NCOLS 5 typedef enum { @@ -658,9 +660,12 @@ typedef struct zprop_get_cbdata { boolean_t cb_scripted; boolean_t cb_literal; boolean_t cb_first; + boolean_t cb_json; zprop_list_t *cb_proplist; zfs_type_t cb_type; vdev_cbdata_t cb_vdevs; + nvlist_t *cb_jsobj; + boolean_t cb_json_as_int; } zprop_get_cbdata_t; #define ZFS_SET_NOMOUNT 1 @@ -674,6 +679,9 @@ _LIBZFS_H void zprop_print_one_property(const char *, zprop_get_cbdata_t *, const char *, const char *, zprop_source_t, const char *, const char *); +_LIBZFS_H int zprop_nvlist_one_property(const char *, const char *, + zprop_source_t, const char *, const char *, nvlist_t *, boolean_t); + /* * Iterator functions. */ @@ -979,6 +987,7 @@ _LIBZFS_H boolean_t libzfs_envvar_is_set(const char *); _LIBZFS_H const char *zfs_version_userland(void); _LIBZFS_H char *zfs_version_kernel(void); _LIBZFS_H int zfs_version_print(void); +_LIBZFS_H nvlist_t *zfs_version_nvlist(void); /* * Given a device or file, determine if it is part of a pool. diff --git a/lib/libzfs/libzfs_util.c b/lib/libzfs/libzfs_util.c index b865af71a1..e2623577fc 100644 --- a/lib/libzfs/libzfs_util.c +++ b/lib/libzfs/libzfs_util.c @@ -68,6 +68,7 @@ * as necessary. */ #define URI_REGEX "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):" +#define STR_NUMS "0123456789" int libzfs_errno(libzfs_handle_t *hdl) @@ -1267,6 +1268,14 @@ zcmd_read_dst_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc, nvlist_t **nvlp) * ================================================================ */ +void +zcmd_print_json(nvlist_t *nvl) +{ + nvlist_print_json(stdout, nvl); + (void) putchar('\n'); + nvlist_free(nvl); +} + static void zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type) { @@ -1393,6 +1402,103 @@ zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type) (void) printf("\n"); } +/* + * Add property value and source to provided nvlist, according to + * settings in cb structure. Later to be printed in JSON format. + */ +int +zprop_nvlist_one_property(const char *propname, + const char *value, zprop_source_t sourcetype, const char *source, + const char *recvd_value, nvlist_t *nvl, boolean_t as_int) +{ + int ret = 0; + nvlist_t *src_nv, *prop; + boolean_t all_numeric = strspn(value, STR_NUMS) == strlen(value); + src_nv = prop = NULL; + + if ((nvlist_alloc(&prop, NV_UNIQUE_NAME, 0) != 0) || + (nvlist_alloc(&src_nv, NV_UNIQUE_NAME, 0) != 0)) { + ret = -1; + goto err; + } + + if (as_int && all_numeric) { + uint64_t val; + sscanf(value, "%lld", (u_longlong_t *)&val); + if (nvlist_add_uint64(prop, "value", val) != 0) { + ret = -1; + goto err; + } + } else { + if (nvlist_add_string(prop, "value", value) != 0) { + ret = -1; + goto err; + } + } + + switch (sourcetype) { + case ZPROP_SRC_NONE: + if (nvlist_add_string(src_nv, "type", "NONE") != 0 || + (nvlist_add_string(src_nv, "data", "-") != 0)) { + ret = -1; + goto err; + } + break; + case ZPROP_SRC_DEFAULT: + if (nvlist_add_string(src_nv, "type", "DEFAULT") != 0 || + (nvlist_add_string(src_nv, "data", "-") != 0)) { + ret = -1; + goto err; + } + break; + case ZPROP_SRC_LOCAL: + if (nvlist_add_string(src_nv, "type", "LOCAL") != 0 || + (nvlist_add_string(src_nv, "data", "-") != 0)) { + ret = -1; + goto err; + } + break; + case ZPROP_SRC_TEMPORARY: + if (nvlist_add_string(src_nv, "type", "TEMPORARY") != 0 || + (nvlist_add_string(src_nv, "data", "-") != 0)) { + ret = -1; + goto err; + } + break; + case ZPROP_SRC_INHERITED: + if (nvlist_add_string(src_nv, "type", "INHERITED") != 0 || + (nvlist_add_string(src_nv, "data", source) != 0)) { + ret = -1; + goto err; + } + break; + case ZPROP_SRC_RECEIVED: + if (nvlist_add_string(src_nv, "type", "RECEIVED") != 0 || + (nvlist_add_string(src_nv, "data", + (recvd_value == NULL ? "-" : recvd_value)) != 0)) { + ret = -1; + goto err; + } + break; + default: + assert(!"unhandled zprop_source_t"); + if (nvlist_add_string(src_nv, "type", + "unhandled zprop_source_t") != 0) { + ret = -1; + goto err; + } + } + if ((nvlist_add_nvlist(prop, "source", src_nv) != 0) || + (nvlist_add_nvlist(nvl, propname, prop)) != 0) { + ret = -1; + goto err; + } +err: + nvlist_free(src_nv); + nvlist_free(prop); + return (ret); +} + /* * Display a single line of output, according to the settings in the callback * structure. @@ -1999,6 +2105,34 @@ zfs_version_print(void) return (0); } +/* + * Returns an nvlist with both zfs userland and kernel versions. + * Returns NULL on error. + */ +nvlist_t * +zfs_version_nvlist(void) +{ + nvlist_t *nvl; + char kmod_ver[64]; + if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) + return (NULL); + if (nvlist_add_string(nvl, "userland", ZFS_META_ALIAS) != 0) + goto err; + char *kver = zfs_version_kernel(); + if (kver == NULL) { + fprintf(stderr, "zfs_version_kernel() failed: %s\n", + zfs_strerror(errno)); + goto err; + } + (void) snprintf(kmod_ver, 64, "zfs-kmod-%s", kver); + if (nvlist_add_string(nvl, "kernel", kmod_ver) != 0) + goto err; + return (nvl); +err: + nvlist_free(nvl); + return (NULL); +} + /* * Return 1 if the user requested ANSI color output, and our terminal supports * it. Return 0 for no color. diff --git a/man/man8/zfs-set.8 b/man/man8/zfs-set.8 index 8cc19caf3f..204450d72e 100644 --- a/man/man8/zfs-set.8 +++ b/man/man8/zfs-set.8 @@ -46,6 +46,7 @@ .Cm get .Op Fl r Ns | Ns Fl d Ar depth .Op Fl Hp +.Op Fl j Op Ar --json-int .Oo Fl o Ar field Ns Oo , Ns Ar field Oc Ns … Oc .Oo Fl s Ar source Ns Oo , Ns Ar source Oc Ns … Oc .Oo Fl t Ar type Ns Oo , Ns Ar type Oc Ns … Oc @@ -91,6 +92,7 @@ dataset. .Cm get .Op Fl r Ns | Ns Fl d Ar depth .Op Fl Hp +.Op Fl j Op Ar --json-int .Oo Fl o Ar field Ns Oo , Ns Ar field Oc Ns … Oc .Oo Fl s Ar source Ns Oo , Ns Ar source Oc Ns … Oc .Oo Fl t Ar type Ns Oo , Ns Ar type Oc Ns … Oc @@ -128,6 +130,11 @@ The value can be used to display all properties that apply to the given dataset's type .Pq Sy filesystem , volume , snapshot , No or Sy bookmark . .Bl -tag -width "-s source" +.It Fl j Op Ar --json-int +Display the output in JSON format. +Specify +.Sy --json-int +to display numbers in integer format instead of strings for JSON output. .It Fl H Display output in a form more easily parsed by scripts. Any headers are omitted, and fields are explicitly separated by a single tab @@ -283,6 +290,50 @@ The following command gets a single property value: on .Ed .Pp +The following command gets a single property value recursively in JSON format: +.Bd -literal -compact -offset Ds +.No # Nm zfs Cm get Fl j Fl r Sy mountpoint Ar pool/home | Nm jq +{ + "output_version": { + "command": "zfs get", + "vers_major": 0, + "vers_minor": 1 + }, + "datasets": { + "pool/home": { + "name": "pool/home", + "type": "FILESYSTEM", + "pool": "pool", + "createtxg": "10", + "properties": { + "mountpoint": { + "value": "/pool/home", + "source": { + "type": "DEFAULT", + "data": "-" + } + } + } + }, + "pool/home/bob": { + "name": "pool/home/bob", + "type": "FILESYSTEM", + "pool": "pool", + "createtxg": "1176", + "properties": { + "mountpoint": { + "value": "/pool/home/bob", + "source": { + "type": "DEFAULT", + "data": "-" + } + } + } + } + } +} +.Ed +.Pp The following command lists all properties with local settings for .Ar pool/home/bob : .Bd -literal -compact -offset Ds diff --git a/man/man8/zfs.8 b/man/man8/zfs.8 index dd578cb74a..2ee15ab218 100644 --- a/man/man8/zfs.8 +++ b/man/man8/zfs.8 @@ -48,6 +48,7 @@ .Fl ?V .Nm .Cm version +.Op Fl j .Nm .Cm subcommand .Op Ar arguments @@ -153,10 +154,14 @@ Displays a help message. .It Xo .Nm .Cm version +.Op Fl j .Xc Displays the software version of the .Nm userland utility and the zfs kernel module. +Use +.Fl j +option to output in JSON format. .El . .Ss Dataset Management