aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmd/zfs/zfs_main.c228
-rw-r--r--include/libzfs.h9
-rw-r--r--lib/libzfs/libzfs_util.c134
-rw-r--r--man/man8/zfs-set.851
-rw-r--r--man/man8/zfs.85
5 files changed, 412 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 */
diff --git a/include/libzfs.h b/include/libzfs.h
index 979b919ce..42054d74b 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 b865af71a..e2623577f 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)
{
@@ -1394,6 +1403,103 @@ zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type)
}
/*
+ * 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.
*/
@@ -2000,6 +2106,34 @@ zfs_version_print(void)
}
/*
+ * 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 8cc19caf3..204450d72 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 dd578cb74..2ee15ab21 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