diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index e3100a2f6..dca18853d 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -310,8 +310,9 @@ get_usage(zfs_help_t idx) return (gettext("\tupgrade [-v]\n" "\tupgrade [-r] [-V version] <-a | filesystem ...>\n")); case HELP_LIST: - return (gettext("\tlist [-Hp] [-r|-d max] [-o property[,...]] " - "[-s property]...\n\t [-S property]... [-t type[,...]] " + return (gettext("\tlist [-Hp] [-j [--json-int]] [-r|-d max] " + "[-o property[,...]] [-s property]...\n\t " + "[-S property]... [-t type[,...]] " "[filesystem|volume|snapshot] ...\n")); case HELP_MOUNT: return (gettext("\tmount\n" @@ -3609,6 +3610,9 @@ typedef struct list_cbdata { boolean_t cb_literal; boolean_t cb_scripted; zprop_list_t *cb_proplist; + boolean_t cb_json; + nvlist_t *cb_jsobj; + boolean_t cb_json_as_int; } list_cbdata_t; /* @@ -3679,10 +3683,11 @@ zfs_list_avail_color(zfs_handle_t *zhp) /* * Given a dataset and a list of fields, print out all the properties according - * to the described layout. + * to the described layout, or return an nvlist containing all the fields, later + * to be printed out as JSON object. */ static void -print_dataset(zfs_handle_t *zhp, list_cbdata_t *cb) +collect_dataset(zfs_handle_t *zhp, list_cbdata_t *cb) { zprop_list_t *pl = cb->cb_proplist; boolean_t first = B_TRUE; @@ -3691,9 +3696,23 @@ print_dataset(zfs_handle_t *zhp, list_cbdata_t *cb) nvlist_t *propval; const char *propstr; boolean_t right_justify; + nvlist_t *item, *d, *props; + item = d = props = NULL; + zprop_source_t sourcetype = ZPROP_SRC_NONE; + char source[ZFS_MAX_DATASET_NAME_LEN]; + if (cb->cb_json) { + d = fnvlist_lookup_nvlist(cb->cb_jsobj, "datasets"); + if (d == NULL) { + fprintf(stderr, "datasets obj not found.\n"); + exit(1); + } + item = fnvlist_alloc(); + props = fnvlist_alloc(); + fill_dataset_info(item, zhp, cb->cb_json_as_int); + } for (; pl != NULL; pl = pl->pl_next) { - if (!first) { + if (!cb->cb_json && !first) { if (cb->cb_scripted) (void) putchar('\t'); else @@ -3709,69 +3728,112 @@ print_dataset(zfs_handle_t *zhp, list_cbdata_t *cb) right_justify = zfs_prop_align_right(pl->pl_prop); } else if (pl->pl_prop != ZPROP_USERPROP) { if (zfs_prop_get(zhp, pl->pl_prop, property, - sizeof (property), NULL, NULL, 0, - cb->cb_literal) != 0) + sizeof (property), &sourcetype, source, + sizeof (source), cb->cb_literal) != 0) propstr = "-"; else propstr = property; right_justify = zfs_prop_align_right(pl->pl_prop); } else if (zfs_prop_userquota(pl->pl_user_prop)) { + sourcetype = ZPROP_SRC_LOCAL; if (zfs_prop_get_userquota(zhp, pl->pl_user_prop, - property, sizeof (property), cb->cb_literal) != 0) + property, sizeof (property), cb->cb_literal) != 0) { + sourcetype = ZPROP_SRC_NONE; propstr = "-"; - else + } else { propstr = property; + } right_justify = B_TRUE; } else if (zfs_prop_written(pl->pl_user_prop)) { + sourcetype = ZPROP_SRC_LOCAL; if (zfs_prop_get_written(zhp, pl->pl_user_prop, - property, sizeof (property), cb->cb_literal) != 0) + property, sizeof (property), cb->cb_literal) != 0) { + sourcetype = ZPROP_SRC_NONE; propstr = "-"; - else + } else { propstr = property; + } right_justify = B_TRUE; } else { if (nvlist_lookup_nvlist(userprops, - pl->pl_user_prop, &propval) != 0) + pl->pl_user_prop, &propval) != 0) { propstr = "-"; - else + } else { propstr = fnvlist_lookup_string(propval, ZPROP_VALUE); + strlcpy(source, + fnvlist_lookup_string(propval, + ZPROP_SOURCE), ZFS_MAX_DATASET_NAME_LEN); + if (strcmp(source, + zfs_get_name(zhp)) == 0) { + sourcetype = ZPROP_SRC_LOCAL; + } else if (strcmp(source, + ZPROP_SOURCE_VAL_RECVD) == 0) { + sourcetype = ZPROP_SRC_RECEIVED; + } else { + sourcetype = ZPROP_SRC_INHERITED; + } + } right_justify = B_FALSE; } - /* - * zfs_list_avail_color() needs ZFS_PROP_AVAILABLE + USED - * - so we need another for() search for the USED part - * - when no colors wanted, we can skip the whole thing - */ - if (use_color() && pl->pl_prop == ZFS_PROP_AVAILABLE) { - zprop_list_t *pl2 = cb->cb_proplist; - for (; pl2 != NULL; pl2 = pl2->pl_next) { - if (pl2->pl_prop == ZFS_PROP_USED) { - color_start(zfs_list_avail_color(zhp)); - /* found it, no need for more loops */ - break; + if (cb->cb_json) { + if (pl->pl_prop == ZFS_PROP_NAME) + continue; + if (zprop_nvlist_one_property( + zfs_prop_to_name(pl->pl_prop), propstr, + sourcetype, source, NULL, props, + cb->cb_json_as_int) != 0) + nomem(); + } else { + /* + * zfs_list_avail_color() needs + * ZFS_PROP_AVAILABLE + USED, so we need another + * for() search for the USED part when no colors + * wanted, we can skip the whole thing + */ + if (use_color() && pl->pl_prop == ZFS_PROP_AVAILABLE) { + zprop_list_t *pl2 = cb->cb_proplist; + for (; pl2 != NULL; pl2 = pl2->pl_next) { + if (pl2->pl_prop == ZFS_PROP_USED) { + color_start( + zfs_list_avail_color(zhp)); + /* + * found it, no need for more + * loops + */ + break; + } } } + + /* + * If this is being called in scripted mode, or if + * this is the last column and it is left-justified, + * don't include a width format specifier. + */ + if (cb->cb_scripted || (pl->pl_next == NULL && + !right_justify)) + (void) fputs(propstr, stdout); + else if (right_justify) { + (void) printf("%*s", (int)pl->pl_width, + propstr); + } else { + (void) printf("%-*s", (int)pl->pl_width, + propstr); + } + + if (pl->pl_prop == ZFS_PROP_AVAILABLE) + color_end(); } - - /* - * If this is being called in scripted mode, or if this is the - * last column and it is left-justified, don't include a width - * format specifier. - */ - if (cb->cb_scripted || (pl->pl_next == NULL && !right_justify)) - (void) fputs(propstr, stdout); - else if (right_justify) - (void) printf("%*s", (int)pl->pl_width, propstr); - else - (void) printf("%-*s", (int)pl->pl_width, propstr); - - if (pl->pl_prop == ZFS_PROP_AVAILABLE) - color_end(); } - - (void) putchar('\n'); + if (cb->cb_json) { + fnvlist_add_nvlist(item, "properties", props); + fnvlist_add_nvlist(d, zfs_get_name(zhp), item); + fnvlist_free(props); + fnvlist_free(item); + } else + (void) putchar('\n'); } /* @@ -3783,12 +3845,12 @@ list_callback(zfs_handle_t *zhp, void *data) list_cbdata_t *cbp = data; if (cbp->cb_first) { - if (!cbp->cb_scripted) + if (!cbp->cb_scripted && !cbp->cb_json) print_header(cbp); cbp->cb_first = B_FALSE; } - print_dataset(zhp, cbp); + collect_dataset(zhp, cbp); return (0); } @@ -3807,9 +3869,16 @@ zfs_do_list(int argc, char **argv) int ret = 0; zfs_sort_column_t *sortcol = NULL; int flags = ZFS_ITER_PROP_LISTSNAPS | ZFS_ITER_ARGS_CAN_BE_PATHS; + nvlist_t *data = NULL; + + 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, "HS:d:o:prs:t:")) != -1) { + while ((c = getopt_long(argc, argv, "jHS:d:o:prs:t:", long_options, + NULL)) != -1) { switch (c) { case 'o': fields = optarg; @@ -3824,6 +3893,17 @@ zfs_do_list(int argc, char **argv) case 'r': flags |= ZFS_ITER_RECURSE; 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 'H': cb.cb_scripted = B_TRUE; break; @@ -3897,6 +3977,12 @@ found3:; argc -= optind; argv += optind; + if (!cb.cb_json && cb.cb_json_as_int) { + (void) fprintf(stderr, gettext("'--json-int' only works with" + " '-j' option\n")); + usage(B_FALSE); + } + /* * If "-o space" and no types were specified, don't display snapshots. */ @@ -3936,6 +4022,11 @@ found3:; ret = zfs_for_each(argc, argv, flags, types, sortcol, &cb.cb_proplist, limit, list_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); + zprop_free_list(cb.cb_proplist); zfs_free_sort_columns(sortcol); diff --git a/man/man8/zfs-list.8 b/man/man8/zfs-list.8 index 85bd3fbaf..b49def08b 100644 --- a/man/man8/zfs-list.8 +++ b/man/man8/zfs-list.8 @@ -41,6 +41,7 @@ .Cm list .Op Fl r Ns | Ns Fl d Ar depth .Op Fl Hp +.Op Fl j Op Ar --json-int .Oo Fl o Ar property Ns Oo , Ns Ar property Oc Ns … Oc .Oo Fl s Ar property Oc Ns … .Oo Fl S Ar property Oc Ns … @@ -70,6 +71,11 @@ The following fields are displayed: Used for scripting mode. Do not print headers and separate fields by a single tab instead of arbitrary white space. +.It Fl j Op Ar --json-int +Print the output in JSON format. +Specify +.Sy --json-int +to print the numbers in integer format instead of strings in JSON output. .It Fl d Ar depth Recursively display any children of the dataset, limiting the recursion to .Ar depth . @@ -186,6 +192,161 @@ pool/home 315K 457G 21K /export/home pool/home/anne 18K 457G 18K /export/home/anne pool/home/bob 276K 457G 276K /export/home/bob .Ed +.Ss Example 2 : No Listing ZFS filesystems and snapshots in JSON format +.Bd -literal -compact -offset Ds +.No # Nm zfs Cm list Fl j Fl t Ar filesystem,snapshot | Cm jq +{ + "output_version": { + "command": "zfs list", + "vers_major": 0, + "vers_minor": 1 + }, + "datasets": { + "pool": { + "name": "pool", + "type": "FILESYSTEM", + "pool": "pool", + "properties": { + "used": { + "value": "290K", + "source": { + "type": "NONE", + "data": "-" + } + }, + "available": { + "value": "30.5G", + "source": { + "type": "NONE", + "data": "-" + } + }, + "referenced": { + "value": "24K", + "source": { + "type": "NONE", + "data": "-" + } + }, + "mountpoint": { + "value": "/pool", + "source": { + "type": "DEFAULT", + "data": "-" + } + } + } + }, + "pool/home": { + "name": "pool/home", + "type": "FILESYSTEM", + "pool": "pool", + "properties": { + "used": { + "value": "48K", + "source": { + "type": "NONE", + "data": "-" + } + }, + "available": { + "value": "30.5G", + "source": { + "type": "NONE", + "data": "-" + } + }, + "referenced": { + "value": "24K", + "source": { + "type": "NONE", + "data": "-" + } + }, + "mountpoint": { + "value": "/mnt/home", + "source": { + "type": "LOCAL", + "data": "-" + } + } + } + }, + "pool/home/bob": { + "name": "pool/home/bob", + "type": "FILESYSTEM", + "pool": "pool", + "properties": { + "used": { + "value": "24K", + "source": { + "type": "NONE", + "data": "-" + } + }, + "available": { + "value": "30.5G", + "source": { + "type": "NONE", + "data": "-" + } + }, + "referenced": { + "value": "24K", + "source": { + "type": "NONE", + "data": "-" + } + }, + "mountpoint": { + "value": "/mnt/home/bob", + "source": { + "type": "INHERITED", + "data": "pool/home" + } + } + } + }, + "pool/home/bob@v1": { + "name": "pool/home/bob@v1", + "type": "SNAPSHOT", + "pool": "pool", + "dataset": "pool/home/bob", + "snapshot_name": "v1", + "properties": { + "used": { + "value": "0B", + "source": { + "type": "NONE", + "data": "-" + } + }, + "available": { + "value": "-", + "source": { + "type": "NONE", + "data": "-" + } + }, + "referenced": { + "value": "24K", + "source": { + "type": "NONE", + "data": "-" + } + }, + "mountpoint": { + "value": "-", + "source": { + "type": "NONE", + "data": "-" + } + } + } + } + } +} +.Ed . .Sh SEE ALSO .Xr zfsprops 7 ,