diff --git a/CHANGELOG.md b/CHANGELOG.md index e88aff81..3e4fda7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -178,6 +178,7 @@ ### Minor changes +* Extended `util/clixon_util_xml` with yang and validate functionality so it can be used as a stand-alone utility for validating XML/JSON files * JSON parse and print improvements * Integrated parsing with namespace translation and yang spec lookup * Added CLIgen tab-modes in config option CLICON_CLI_TAB_MODE, which means you can control the behaviour of `` in the CLI. diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index d91dc9ee..c37416cc 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -243,6 +243,7 @@ parse_configfile(clicon_handle h, /*! Add configuration option overriding file setting * Add to clicon_options hash, and to clicon_conf_xml tree + * Assumes clicon_conf_xml_set has been called * @param[in] h Clicon handle * @param[in] name Name of configuration option (see clixon-config.yang) * @param[in] value String value diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 2e82e923..27d099ad 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1280,8 +1280,8 @@ xml_yang_validate_all(clicon_handle h, */ int xml_yang_validate_all_top(clicon_handle h, - cxobj *xt, - cbuf *cbret) + cxobj *xt, + cbuf *cbret) { int ret; cxobj *x; @@ -2053,7 +2053,7 @@ xml_tree_prune_flagged(cxobj *xt, */ int xml_default(cxobj *xt, - void *arg) + void *arg) { int retval = -1; yang_stmt *ys; diff --git a/test/all.sh b/test/all.sh index e2ff3809..44500b2d 100755 --- a/test/all.sh +++ b/test/all.sh @@ -2,6 +2,9 @@ # Run, eg as: # ./all.sh 2>&1 | tee test.log # break on first test +# Pattern to run tests, default is all, but you may want to narrow it down +: ${pattern:=test_*.sh} + if [ $# -gt 0 ]; then echo "usage: $0 # detailed logs and stopon first error" exit -1 @@ -9,10 +12,10 @@ fi err=0 testnr=0 -for test in test_*.sh; do +for test in $pattern; do if [ $testnr != 0 ]; then echo; fi testfile=$test - . ./$test + ret=$(./$test) # . ./$test errcode=$? if [ $errcode -ne 0 ]; then err=1 diff --git a/test/mem.sh b/test/mem.sh index 02072ecd..d9c0e1dd 100755 --- a/test/mem.sh +++ b/test/mem.sh @@ -2,7 +2,7 @@ # Run valgrind leak test for cli, restconf, netconf or background. # Stop on first error -# Pattern to run tests +# Pattern to run tests, default is all, but you may want to narrow it down : ${pattern:=test_*.sh} # Run valgrindtest once, args: diff --git a/test/test_xml.sh b/test/test_xml.sh index 52784a12..f24997ac 100755 --- a/test/test_xml.sh +++ b/test/test_xml.sh @@ -6,7 +6,7 @@ # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi -: ${clixon_util_xml:=clixon_util_xml} +: ${clixon_util_xml:=clixon_util_xml -o} # -o is output new "xml parse" expecteof "$clixon_util_xml" 0 "" "^$" diff --git a/util/clixon_util_json.c b/util/clixon_util_json.c index 2a3a1c58..2ae5bed3 100644 --- a/util/clixon_util_json.c +++ b/util/clixon_util_json.c @@ -59,7 +59,7 @@ #include "clixon/clixon.h" /* - * Turn this on to get a json parse and pretty print test program + * JSON parse and pretty print test program * Usage: xpath * read json from input * Example compile: diff --git a/util/clixon_util_xml.c b/util/clixon_util_xml.c index 001c08a2..f84bc00c 100644 --- a/util/clixon_util_xml.c +++ b/util/clixon_util_xml.c @@ -34,6 +34,11 @@ * XML support functions. * @see https://www.w3.org/TR/2008/REC-xml-20081126 * https://www.w3.org/TR/2009/REC-xml-names-20091208 + * The function can do yang validation, process xml and json, etc. + * On success, nothing is printed and exitcode 0 + * On failure, an error is printed on stderr and exitcode != 0 + * Failure error prints are different, it would be nice to make them more + * uniform. (see clicon_rpc_generate_error) */ #ifdef HAVE_CONFIG_H @@ -49,7 +54,9 @@ #include #include #include +#include #include +#include /* cligen */ #include @@ -69,12 +76,20 @@ static int usage(char *argv0) { - fprintf(stderr, "usage:%s [options]\n" + fprintf(stderr, "usage:%s [options] with xml on stdin\n" "where options are\n" "\t-h \t\tHelp\n" "\t-D \tDebug\n" + "\t-f \tXML input file (overrides stdin)\n" + "\t-J \t\tInput as JSON\n" "\t-j \t\tOutput as JSON\n" - "\t-l \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n", + "\t-l \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n" + "\t-o \t\tOutput the file\n" + "\t-v \t\tValidate the result in terms of Yang model (requires -y)\n" + "\t-p \t\tPretty-print output\n" + "\t-y \tYang filename or dir (load all files)\n" + "\t-Y \tYang dirs (can be several)\n" + , argv0); exit(0); } @@ -83,17 +98,39 @@ int main(int argc, char **argv) { - int retval = -1; - cxobj *xt = NULL; - cxobj *xc; - cbuf *cb = cbuf_new(); - int c; - int logdst = CLICON_LOG_STDERR; - int json = 0; + int retval = -1; + cxobj *xt = NULL; + cxobj *xc; + cbuf *cb = cbuf_new(); + int c; + int logdst = CLICON_LOG_STDERR; + int jsonin = 0; + int jsonout = 0; + char *input_filename = NULL; + char *yang_file_dir = NULL; + yang_stmt *yspec = NULL; + cxobj *xerr = NULL; /* malloced must be freed */ + int ret; + int pretty = 0; + int validate = 0; + int output = 0; + clicon_handle h; + struct stat st; + int fd = 0; /* stdin */ + cxobj *xcfg = NULL; + cbuf *cberr; + /* In the startup, logs to stderr & debug flag set later */ + clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR); + + if ((h = clicon_handle_init()) == NULL) + goto done; + + xcfg = xml_new("clixon-config", NULL, NULL); + clicon_conf_xml_set(h, xcfg); optind = 1; opterr = 0; - while ((c = getopt(argc, argv, "hD:jl:")) != -1) + while ((c = getopt(argc, argv, "hD:f:Jjl:pvoy:Y:")) != -1) switch (c) { case 'h': usage(argv[0]); @@ -102,35 +139,129 @@ main(int argc, if (sscanf(optarg, "%d", &debug) != 1) usage(argv[0]); break; + case 'f': + input_filename = optarg; + break; + case 'J': + jsonin++; + break; case 'j': - json++; + jsonout++; break; case 'l': /* Log destination: s|e|o|f */ if ((logdst = clicon_log_opt(optarg[0])) < 0) usage(argv[0]); break; + case 'o': + output++; + break; + case 'v': + validate++; + break; + case 'p': + pretty++; + break; + case 'y': + yang_file_dir = optarg; + break; + case 'Y': + if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0) + goto done; + break; default: usage(argv[0]); break; } - clicon_log_init(__FILE__, debug?LOG_DEBUG:LOG_INFO, logdst); - if (xml_parse_file(0, "", NULL, &xt) < 0){ - fprintf(stderr, "xml parse error %s\n", clicon_err_reason); - goto done; + if (validate && !yang_file_dir){ + fprintf(stderr, "-v requires -y\n"); + usage(argv[0]); + } + clicon_log_init(__FILE__, debug?LOG_DEBUG:LOG_INFO, logdst); + /* 1. Parse yang */ + if (yang_file_dir){ + if ((yspec = yspec_new()) == NULL) + goto done; + if (stat(yang_file_dir, &st) < 0){ + clicon_err(OE_YANG, errno, "%s not found", yang_file_dir); + goto done; + } + if (S_ISDIR(st.st_mode)){ + if (yang_spec_load_dir(h, yang_file_dir, yspec) < 0) + goto done; + } + else{ + if (yang_spec_parse_file(h, yang_file_dir, yspec) < 0) + goto done; + } + } + if (input_filename){ + if ((fd = open(input_filename, O_RDONLY)) < 0){ + clicon_err(OE_YANG, errno, "open(%s)", input_filename); + goto done; + } + } + /* 2. Parse data (xml/json) */ + if (jsonin){ + if ((ret = json_parse_file(fd, yspec, &xt, &xerr)) < 0) + goto done; + if (ret == 0){ + xml_print(stderr, xerr); + goto done; + } + } + else{ + if (xml_parse_file(fd, "", NULL, &xt) < 0){ + fprintf(stderr, "xml parse error %s\n", clicon_err_reason); + goto done; + } + } + + /* Dump data structures (for debug) */ + if (debug){ + cbuf_reset(cb); + xmltree2cbuf(cb, xt, 0); + fprintf(stderr, "%s\n", cbuf_get(cb)); + cbuf_reset(cb); + } + + /* 3. Validate data (if yspec) */ + if (validate){ + xc = xml_child_i(xt, 0); + if ((cberr = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + /* Populate */ + if (xml_apply0(xc, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; + /* Sort */ + if (xml_apply0(xc, CX_ELMNT, xml_sort, h) < 0) + goto done; + /* Add default values */ + if (xml_apply(xc, CX_ELMNT, xml_default, h) < 0) + goto done; + if (xml_apply0(xc, -1, xml_sort_verify, h) < 0) + clicon_log(LOG_NOTICE, "%s: sort verify failed", __FUNCTION__); + if ((ret = xml_yang_validate_all_top(h, xc, cberr)) < 0) + goto done; + if (ret > 0 && (ret = xml_yang_validate_add(h, xc, cberr)) < 0) + goto done; + if (ret == 0){ + fprintf(stderr, "%s", cbuf_get(cberr)); + goto done; + } + } + /* 4. Output data (xml/json) */ + if (output){ + xc = NULL; + while ((xc = xml_child_each(xt, xc, -1)) != NULL) + if (jsonout) + xml2json_cbuf(cb, xc, pretty); /* print xml */ + else + clicon_xml2cbuf(cb, xc, 0, pretty); /* print xml */ + fprintf(stdout, "%s", cbuf_get(cb)); + fflush(stdout); } - xc = NULL; - while ((xc = xml_child_each(xt, xc, -1)) != NULL) - if (json) - xml2json_cbuf(cb, xc, 0); /* print xml */ - else - clicon_xml2cbuf(cb, xc, 0, 0); /* print xml */ - fprintf(stdout, "%s", cbuf_get(cb)); - fflush(stdout); -#if 0 - cbuf_reset(cb); - xmltree2cbuf(cb, xt, 0); /* dump data structures */ - fprintf(stderr, "%s\n", cbuf_get(cb)); -#endif retval = 0; done: if (xt)