diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index e241831db5..d148516f15 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -27,6 +27,7 @@ * Copyright (c) 2013 Steven Hartland. All rights reserved. * Copyright 2016 Igor Kozhukhov . * Copyright 2016 Nexenta Systems, Inc. + * Copyright (c) 2018 Datto Inc. */ #include @@ -358,7 +359,7 @@ get_usage(zfs_help_t idx) case HELP_BOOKMARK: return (gettext("\tbookmark \n")); case HELP_CHANNEL_PROGRAM: - return (gettext("\tprogram [-n] [-t ] " + return (gettext("\tprogram [-jn] [-t ] " "[-m ] " "[lua args...]\n")); case HELP_LOAD_KEY: @@ -7220,11 +7221,11 @@ zfs_do_channel_program(int argc, char **argv) nvlist_t *outnvl; uint64_t instrlimit = ZCP_DEFAULT_INSTRLIMIT; uint64_t memlimit = ZCP_DEFAULT_MEMLIMIT; - boolean_t sync_flag = B_TRUE; + boolean_t sync_flag = B_TRUE, json_output = B_FALSE; zpool_handle_t *zhp; /* check options */ - while ((c = getopt(argc, argv, "nt:m:")) != -1) { + while ((c = getopt(argc, argv, "nt:m:j")) != -1) { switch (c) { case 't': case 'm': { @@ -7266,6 +7267,10 @@ zfs_do_channel_program(int argc, char **argv) sync_flag = B_FALSE; break; } + case 'j': { + json_output = B_TRUE; + break; + } case '?': (void) fprintf(stderr, gettext("invalid option '%c'\n"), optopt); @@ -7391,14 +7396,18 @@ zfs_do_channel_program(int argc, char **argv) gettext("Channel program execution failed:\n%s\n"), errstring); if (ret == ETIME && instructions != 0) - (void) fprintf(stderr, "%llu Lua instructions\n", + (void) fprintf(stderr, + gettext("%llu Lua instructions\n"), (u_longlong_t)instructions); } else { - (void) printf("Channel program fully executed "); - if (nvlist_empty(outnvl)) { - (void) printf("with no return value.\n"); + if (json_output) { + (void) nvlist_print_json(stdout, outnvl); + } else if (nvlist_empty(outnvl)) { + (void) fprintf(stdout, gettext("Channel program fully " + "executed and did not produce output.\n")); } else { - (void) printf("with return value:\n"); + (void) fprintf(stdout, gettext("Channel program fully " + "executed and produced output:\n")); dump_nvlist(outnvl, 4); } } diff --git a/configure.ac b/configure.ac index 9f11623863..c5585ea40d 100644 --- a/configure.ac +++ b/configure.ac @@ -205,6 +205,7 @@ AC_CONFIG_FILES([ tests/zfs-tests/tests/functional/cli_root/zfs_load-key/Makefile tests/zfs-tests/tests/functional/cli_root/zfs/Makefile tests/zfs-tests/tests/functional/cli_root/zfs_mount/Makefile + tests/zfs-tests/tests/functional/cli_root/zfs_program/Makefile tests/zfs-tests/tests/functional/cli_root/zfs_promote/Makefile tests/zfs-tests/tests/functional/cli_root/zfs_property/Makefile tests/zfs-tests/tests/functional/cli_root/zfs_receive/Makefile diff --git a/include/libnvpair.h b/include/libnvpair.h index 4c2615d924..5277f9574d 100644 --- a/include/libnvpair.h +++ b/include/libnvpair.h @@ -20,6 +20,7 @@ */ /* * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, Joyent, Inc. All rights reserved. */ #ifndef _LIBNVPAIR_H @@ -46,6 +47,7 @@ extern int nvpair_value_match_regex(nvpair_t *, int, char *, regex_t *, char **); extern void nvlist_print(FILE *, nvlist_t *); +int nvlist_print_json(FILE *, nvlist_t *); extern void dump_nvlist(nvlist_t *, int); /* diff --git a/lib/libnvpair/Makefile.am b/lib/libnvpair/Makefile.am index d6ba6f89a7..8d6519f302 100644 --- a/lib/libnvpair/Makefile.am +++ b/lib/libnvpair/Makefile.am @@ -18,6 +18,7 @@ lib_LTLIBRARIES = libnvpair.la USER_C = \ libnvpair.c \ + libnvpair_json.c \ nvpair_alloc_system.c KERNEL_C = \ diff --git a/lib/libnvpair/libnvpair_json.c b/lib/libnvpair/libnvpair_json.c new file mode 100644 index 0000000000..46fab2e3d0 --- /dev/null +++ b/lib/libnvpair/libnvpair_json.c @@ -0,0 +1,403 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ +/* + * Copyright (c) 2014, Joyent, Inc. + */ + +#include +#include +#include +#include +#include + +#include "libnvpair.h" + +#define FPRINTF(fp, ...) \ + do { \ + if (fprintf(fp, __VA_ARGS__) < 0) \ + return (-1); \ + } while (0) + +/* + * When formatting a string for JSON output we must escape certain characters, + * as described in RFC4627. This applies to both member names and + * DATA_TYPE_STRING values. + * + * This function will only operate correctly if the following conditions are + * met: + * + * 1. The input String is encoded in the current locale. + * + * 2. The current locale includes the Basic Multilingual Plane (plane 0) + * as defined in the Unicode standard. + * + * The output will be entirely 7-bit ASCII (as a subset of UTF-8) with all + * representable Unicode characters included in their escaped numeric form. + */ +static int +nvlist_print_json_string(FILE *fp, const char *input) +{ + mbstate_t mbr; + wchar_t c; + size_t sz; + + bzero(&mbr, sizeof (mbr)); + + FPRINTF(fp, "\""); + while ((sz = mbrtowc(&c, input, MB_CUR_MAX, &mbr)) > 0) { + switch (c) { + case '"': + FPRINTF(fp, "\\\""); + break; + case '\n': + FPRINTF(fp, "\\n"); + break; + case '\r': + FPRINTF(fp, "\\r"); + break; + case '\\': + FPRINTF(fp, "\\\\"); + break; + case '\f': + FPRINTF(fp, "\\f"); + break; + case '\t': + FPRINTF(fp, "\\t"); + break; + case '\b': + FPRINTF(fp, "\\b"); + break; + default: + if ((c >= 0x00 && c <= 0x1f) || + (c > 0x7f && c <= 0xffff)) { + /* + * Render both Control Characters and Unicode + * characters in the Basic Multilingual Plane + * as JSON-escaped multibyte characters. + */ + FPRINTF(fp, "\\u%04x", (int)(0xffff & c)); + } else if (c >= 0x20 && c <= 0x7f) { + /* + * Render other 7-bit ASCII characters directly + * and drop other, unrepresentable characters. + */ + FPRINTF(fp, "%c", (int)(0xff & c)); + } + break; + } + input += sz; + } + + if (sz == (size_t)-1 || sz == (size_t)-2) { + /* + * We last read an invalid multibyte character sequence, + * so return an error. + */ + return (-1); + } + + FPRINTF(fp, "\""); + return (0); +} + +/* + * Dump a JSON-formatted representation of an nvlist to the provided FILE *. + * This routine does not output any new-lines or additional whitespace other + * than that contained in strings, nor does it call fflush(3C). + */ +int +nvlist_print_json(FILE *fp, nvlist_t *nvl) +{ + nvpair_t *curr; + boolean_t first = B_TRUE; + + FPRINTF(fp, "{"); + + for (curr = nvlist_next_nvpair(nvl, NULL); curr; + curr = nvlist_next_nvpair(nvl, curr)) { + data_type_t type = nvpair_type(curr); + + if (!first) + FPRINTF(fp, ","); + else + first = B_FALSE; + + if (nvlist_print_json_string(fp, nvpair_name(curr)) == -1) + return (-1); + FPRINTF(fp, ":"); + + switch (type) { + case DATA_TYPE_STRING: { + char *string = fnvpair_value_string(curr); + if (nvlist_print_json_string(fp, string) == -1) + return (-1); + break; + } + + case DATA_TYPE_BOOLEAN: { + FPRINTF(fp, "true"); + break; + } + + case DATA_TYPE_BOOLEAN_VALUE: { + FPRINTF(fp, "%s", fnvpair_value_boolean_value(curr) == + B_TRUE ? "true" : "false"); + break; + } + + case DATA_TYPE_BYTE: { + FPRINTF(fp, "%hhu", fnvpair_value_byte(curr)); + break; + } + + case DATA_TYPE_INT8: { + FPRINTF(fp, "%hhd", fnvpair_value_int8(curr)); + break; + } + + case DATA_TYPE_UINT8: { + FPRINTF(fp, "%hhu", fnvpair_value_uint8(curr)); + break; + } + + case DATA_TYPE_INT16: { + FPRINTF(fp, "%hd", fnvpair_value_int16(curr)); + break; + } + + case DATA_TYPE_UINT16: { + FPRINTF(fp, "%hu", fnvpair_value_uint16(curr)); + break; + } + + case DATA_TYPE_INT32: { + FPRINTF(fp, "%d", fnvpair_value_int32(curr)); + break; + } + + case DATA_TYPE_UINT32: { + FPRINTF(fp, "%u", fnvpair_value_uint32(curr)); + break; + } + + case DATA_TYPE_INT64: { + FPRINTF(fp, "%lld", + (long long)fnvpair_value_int64(curr)); + break; + } + + case DATA_TYPE_UINT64: { + FPRINTF(fp, "%llu", + (unsigned long long)fnvpair_value_uint64(curr)); + break; + } + + case DATA_TYPE_HRTIME: { + hrtime_t val; + VERIFY0(nvpair_value_hrtime(curr, &val)); + FPRINTF(fp, "%llu", (unsigned long long)val); + break; + } + + case DATA_TYPE_DOUBLE: { + double val; + VERIFY0(nvpair_value_double(curr, &val)); + FPRINTF(fp, "%f", val); + break; + } + + case DATA_TYPE_NVLIST: { + if (nvlist_print_json(fp, + fnvpair_value_nvlist(curr)) == -1) + return (-1); + break; + } + + case DATA_TYPE_STRING_ARRAY: { + char **val; + uint_t valsz, i; + VERIFY0(nvpair_value_string_array(curr, &val, &valsz)); + FPRINTF(fp, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + FPRINTF(fp, ","); + if (nvlist_print_json_string(fp, val[i]) == -1) + return (-1); + } + FPRINTF(fp, "]"); + break; + } + + case DATA_TYPE_NVLIST_ARRAY: { + nvlist_t **val; + uint_t valsz, i; + VERIFY0(nvpair_value_nvlist_array(curr, &val, &valsz)); + FPRINTF(fp, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + FPRINTF(fp, ","); + if (nvlist_print_json(fp, val[i]) == -1) + return (-1); + } + FPRINTF(fp, "]"); + break; + } + + case DATA_TYPE_BOOLEAN_ARRAY: { + boolean_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_boolean_array(curr, &val, &valsz)); + FPRINTF(fp, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + FPRINTF(fp, ","); + FPRINTF(fp, val[i] == B_TRUE ? + "true" : "false"); + } + FPRINTF(fp, "]"); + break; + } + + case DATA_TYPE_BYTE_ARRAY: { + uchar_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_byte_array(curr, &val, &valsz)); + FPRINTF(fp, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + FPRINTF(fp, ","); + FPRINTF(fp, "%hhu", val[i]); + } + FPRINTF(fp, "]"); + break; + } + + case DATA_TYPE_UINT8_ARRAY: { + uint8_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_uint8_array(curr, &val, &valsz)); + FPRINTF(fp, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + FPRINTF(fp, ","); + FPRINTF(fp, "%hhu", val[i]); + } + FPRINTF(fp, "]"); + break; + } + + case DATA_TYPE_INT8_ARRAY: { + int8_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_int8_array(curr, &val, &valsz)); + FPRINTF(fp, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + FPRINTF(fp, ","); + FPRINTF(fp, "%hd", val[i]); + } + FPRINTF(fp, "]"); + break; + } + + case DATA_TYPE_UINT16_ARRAY: { + uint16_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_uint16_array(curr, &val, &valsz)); + FPRINTF(fp, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + FPRINTF(fp, ","); + FPRINTF(fp, "%hu", val[i]); + } + FPRINTF(fp, "]"); + break; + } + + case DATA_TYPE_INT16_ARRAY: { + int16_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_int16_array(curr, &val, &valsz)); + FPRINTF(fp, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + FPRINTF(fp, ","); + FPRINTF(fp, "%hd", val[i]); + } + FPRINTF(fp, "]"); + break; + } + + case DATA_TYPE_UINT32_ARRAY: { + uint32_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_uint32_array(curr, &val, &valsz)); + FPRINTF(fp, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + FPRINTF(fp, ","); + FPRINTF(fp, "%u", val[i]); + } + FPRINTF(fp, "]"); + break; + } + + case DATA_TYPE_INT32_ARRAY: { + int32_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_int32_array(curr, &val, &valsz)); + FPRINTF(fp, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + FPRINTF(fp, ","); + FPRINTF(fp, "%d", val[i]); + } + FPRINTF(fp, "]"); + break; + } + + case DATA_TYPE_UINT64_ARRAY: { + uint64_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_uint64_array(curr, &val, &valsz)); + FPRINTF(fp, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + FPRINTF(fp, ","); + FPRINTF(fp, "%llu", + (unsigned long long)val[i]); + } + FPRINTF(fp, "]"); + break; + } + + case DATA_TYPE_INT64_ARRAY: { + int64_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_int64_array(curr, &val, &valsz)); + FPRINTF(fp, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + FPRINTF(fp, ","); + FPRINTF(fp, "%lld", (long long)val[i]); + } + FPRINTF(fp, "]"); + break; + } + + case DATA_TYPE_UNKNOWN: + return (-1); + } + } + + FPRINTF(fp, "}"); + return (0); +} diff --git a/man/man8/zfs-program.8 b/man/man8/zfs-program.8 index 8a478b70fa..72a33761ba 100644 --- a/man/man8/zfs-program.8 +++ b/man/man8/zfs-program.8 @@ -18,7 +18,7 @@ .Nd executes ZFS channel programs .Sh SYNOPSIS .Cm "zfs program" -.Op Fl n +.Op Fl jn .Op Fl t Ar instruction-limit .Op Fl m Ar memory-limit .Ar pool @@ -46,6 +46,10 @@ will be run on and any attempts to access or modify other pools will cause an error. .Sh OPTIONS .Bl -tag -width "-t" +.It Fl j +Display channel program output in JSON format. When this flag is specified and +standard output is empty - channel program encountered an error. The details of +such an error will be printed to standard error in plain text. .It Fl n Executes a read-only channel program, which runs faster. The program cannot change on-disk state by calling functions from the diff --git a/man/man8/zfs.8 b/man/man8/zfs.8 index 7d7af15409..bfae494c63 100644 --- a/man/man8/zfs.8 +++ b/man/man8/zfs.8 @@ -300,7 +300,7 @@ .Ar snapshot Ar snapshot Ns | Ns Ar filesystem .Nm .Cm program -.Op Fl n +.Op Fl jn .Op Fl t Ar timeout .Op Fl m Ar memory_limit .Ar pool script @@ -4264,7 +4264,7 @@ Display the path's inode change time as the first column of output. .It Xo .Nm .Cm program -.Op Fl n +.Op Fl jn .Op Fl t Ar timeout .Op Fl m Ar memory_limit .Ar pool script @@ -4286,6 +4286,10 @@ For full documentation of the ZFS channel program interface, see the manual page for .Xr zfs-program 8 . .Bl -tag -width "" +.It Fl j +Display channel program output in JSON format. When this flag is specified and +standard output is empty - channel program encountered an error. The details of +such an error will be printed to standard error in plain text. .It Fl n Executes a read-only channel program, which runs faster. The program cannot change on-disk state by calling functions from diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index f2fdfd7534..7c2ca84bff 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -177,6 +177,10 @@ tests = ['zfs_mount_001_pos', 'zfs_mount_002_pos', 'zfs_mount_003_pos', 'zfs_mount_encrypted', 'zfs_mount_remount'] tags = ['functional', 'cli_root', 'zfs_mount'] +[tests/functional/cli_root/zfs_program] +tests = ['zfs_program_json'] +tags = ['functional', 'cli_root', 'zfs_program'] + [tests/functional/cli_root/zfs_promote] tests = ['zfs_promote_001_pos', 'zfs_promote_002_pos', 'zfs_promote_003_pos', 'zfs_promote_004_pos', 'zfs_promote_005_pos', 'zfs_promote_006_neg', diff --git a/tests/zfs-tests/tests/functional/cli_root/Makefile.am b/tests/zfs-tests/tests/functional/cli_root/Makefile.am index bc64e3bba7..a7c56b6656 100644 --- a/tests/zfs-tests/tests/functional/cli_root/Makefile.am +++ b/tests/zfs-tests/tests/functional/cli_root/Makefile.am @@ -16,6 +16,7 @@ SUBDIRS = \ zfs_inherit \ zfs_load-key \ zfs_mount \ + zfs_program \ zfs_promote \ zfs_property \ zfs_receive \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_program/Makefile.am b/tests/zfs-tests/tests/functional/cli_root/zfs_program/Makefile.am new file mode 100644 index 0000000000..d797a636b3 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_program/Makefile.am @@ -0,0 +1,5 @@ +pkgdatadir = $(datadir)/@PACKAGE@/zfs-tests/tests/functional/cli_root/zfs_program +dist_pkgdata_SCRIPTS = \ + setup.ksh \ + cleanup.ksh \ + zfs_program_json.ksh diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_program/cleanup.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_program/cleanup.ksh new file mode 100755 index 0000000000..79cd6e9f90 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_program/cleanup.ksh @@ -0,0 +1,30 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +. $STF_SUITE/include/libtest.shlib + +default_cleanup diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_program/setup.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_program/setup.ksh new file mode 100755 index 0000000000..6a9af3bc28 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_program/setup.ksh @@ -0,0 +1,32 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +. $STF_SUITE/include/libtest.shlib + +DISK=${DISKS%% *} + +default_setup $DISK diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_program/zfs_program_json.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_program/zfs_program_json.ksh new file mode 100755 index 0000000000..02fd173e1f --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_program/zfs_program_json.ksh @@ -0,0 +1,132 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy is of the CDDL is also available via the Internet +# at http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2018 Datto Inc. +# + +. $STF_SUITE/include/libtest.shlib + +# +# DESCRIPTION: +# +# STRATEGY: +# 1. Compare JSON output formatting for a channel program to template +# 2. Using bad command line option (-Z) gives correct error output +# + +verify_runnable "both" + +function cleanup +{ + log_must zfs destroy $TESTDS + return 0 +} +log_onexit cleanup + +log_assert "Channel programs output valid JSON" + +TESTDS="$TESTPOOL/zcp-json" +log_must zfs create $TESTDS + +TESTZCP="/$TESTDS/zfs_rlist.zcp" +cat > "$TESTZCP" << EOF + succeeded = {} + failed = {} + + function list_recursive(root, prop) + for child in zfs.list.children(root) do + list_recursive(child, prop) + end + val, src = zfs.get_prop(root, prop) + if (val == nil) then + failed[root] = val + else + succeeded[root] = val + end + end + + args = ... + + argv = args["argv"] + + list_recursive(argv[1], argv[2]) + + results = {} + results["succeeded"] = succeeded + results["failed"] = failed + return results +EOF + +# 1. Compare JSON output formatting for a channel program to template +typeset -a pos_cmds=("recordsize" "type") +typeset -a pos_cmds_out=( +"{ + \"return\": { + \"failed\": {}, + \"succeeded\": { + \"$TESTDS\": 131072 + } + } +}" +"{ + \"return\": { + \"failed\": {}, + \"succeeded\": { + \"$TESTDS\": \"filesystem\" + } + } +}") +typeset -i cnt=0 +typeset cmd +for cmd in ${pos_cmds[@]}; do + log_must zfs program $TESTPOOL $TESTZCP $TESTDS $cmd 2>&1 + log_must zfs program $TESTPOOL -j $TESTZCP $TESTDS $cmd 2>&1 + # json.tool is needed to guarantee consistent ordering of fields + # sed is needed to trim trailing space in CentOS 6's json.tool output + OUTPUT=$(zfs program $TESTPOOL -j $TESTZCP $TESTDS $cmd 2>&1 | python -m json.tool | sed 's/[[:space:]]*$//') + if [ "$OUTPUT" != "${pos_cmds_out[$cnt]}" ]; then + log_note "Got :$OUTPUT" + log_note "Expected:${pos_cmds_out[$cnt]}" + log_fail "Unexpected channel program output"; + fi + cnt=$((cnt + 1)) +done + +# 2. Using bad command line option (-Z) gives correct error output +typeset -a neg_cmds=("-Z") +typeset -a neg_cmds_out=( +"invalid option 'Z' +usage: + program [-jn] [-t ] [-m ] [lua args...] + +For the property list, run: zfs set|get + +For the delegated permission list, run: zfs allow|unallow") +cnt=0 +for cmd in ${neg_cmds[@]}; do + log_mustnot zfs program $TESTPOOL $TESTZCP $TESTDS $cmd 2>&1 + log_mustnot zfs program $TESTPOOL -j $TESTZCP $TESTDS $cmd 2>&1 + OUTPUT=$(zfs program $TESTPOOL -j $TESTZCP $TESTDS $cmd 2>&1) + if [ "$OUTPUT" != "${neg_cmds_out[$cnt]}" ]; then + log_note "Got :$OUTPUT" + log_note "Expected:${neg_cmds_out[$cnt]}" + log_fail "Unexpected channel program error output"; + fi + cnt=$((cnt + 1)) +done + +log_pass "Channel programs output valid JSON"