/*
 *
  ***** BEGIN LICENSE BLOCK *****
 
  Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
  Copyright (C) 2017-2019 Olof Hagsand
  Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)

  This file is part of CLIXON.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

  Alternatively, the contents of this file may be used under the terms of
  the GNU General Public License Version 3 or later (the "GPL"),
  in which case the provisions of the GPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of the GPL, and not to allow others to
  use your version of this file under the terms of Apache License version 2, 
  indicate your decision by deleting the provisions above and replace them with
  the  notice and other provisions required by the GPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the Apache License version 2 or the GPL.

  ***** END LICENSE BLOCK *****

 * 
 */

#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <stdarg.h>
#include <time.h>
#include <ctype.h>
#include <unistd.h>
#include <dirent.h>
#include <syslog.h>
#include <pwd.h>
#include <inttypes.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/mount.h>

/* cligen */
#include <cligen/cligen.h>

/* clixon */
#include <clixon/clixon.h>

/* Exported functions in this file are in clixon_cli_api.h */
#include "clixon_cli_api.h"
#include "cli_autocli.h"
#include "cli_common.h" /* internal functions */

/*! Given an xpath encoded in a cbuf, append a second xpath into the first (unless absolute path)
 *
 * The method reuses prefixes from xpath1 if they exist, otherwise the module prefix
 * from y is used. Unless the element is .., .
 * @param[in,out] cb0     Result XPath as cbuf
 * @param[in]     xpath1  Input XPath
 * @param[in]     y       Yang of xpath1
 * @param[in,out] nsc     Namespace
 * @retval        0       OK
 * @retval       -1       Error
 *
 * XXX: Predicates not handled
 * The algorithm is not fool-proof, there are many cases it may not work
 * To make it more complete, maybe parse the xpath to a tree and put it
 * back to an xpath after modifcations, something like:
   if (xpath_parse(yang_argument_get(ypath), &xpt) < 0)
     goto done;
   if (xpath_tree2cbuf(xpt, xcb) < 0)
     goto done;
 */
static int
xpath_append(cbuf      *cb0,
             char      *xpath1,
             yang_stmt *y,
             cvec      *nsc)
{
    int    retval = -1;
    char **vec = NULL;
    char  *v;
    int    nvec;
    int    i;
    char  *myprefix;
    char  *id = NULL;
    char  *prefix = NULL;
    int    initialups = 1; /* If starts with ../../.. */
    char  *xpath0;
    char  *ns;
    int    ret;
    int    j;

    if (cb0 == NULL){
        clixon_err(OE_XML, EINVAL, "cb0 is NULL");
        goto done;
    }
    if (xpath1 == NULL || strlen(xpath1)==0)
        goto ok;
    if ((myprefix = yang_find_myprefix(y)) == NULL)
        goto done;
    if ((vec = clicon_strsep(xpath1, "/", &nvec)) == NULL)
        goto done;
    if (xpath1[0] == '/')
        cbuf_reset(cb0);
    xpath0 = cbuf_get(cb0);
    for (i=0; i<nvec; i++){
        v = vec[i];
        if (strlen(v) == 0)
            continue;
        if (nodeid_split(v, &prefix, &id) < 0)
            goto done;
        if (strcmp(id, ".") == 0)
            initialups = 0;
        else if (strcmp(id, "..") == 0){
            if (initialups){
                /* Subtract from xpath0 */
                for (j=cbuf_len(cb0); j >= 0; j--){
                    if (xpath0[j] != '/')
                        continue;
                    cbuf_trunc(cb0, j);
                    break;
                }
            }
            else{
                initialups = 0;
                cprintf(cb0, "/%s", id);
            }
        }
        else{
            initialups = 0;
            /* If prefix is not in nsc, it needs to be added */
            if (prefix && cvec_find(nsc, prefix) == NULL){
                ns = NULL;
                if ((ret = yang_find_namespace_by_prefix(y, prefix, &ns)) < 0)
                    goto done;
                if (ret == 0){
                    clixon_err(OE_DB, 0, "Prefix %s does not have an associated namespace", prefix);
                    goto done;
                }
                if (xml_nsctx_add(nsc, prefix, ns) < 0)
                    goto done;
            }
            cprintf(cb0, "/%s:%s", prefix?prefix:myprefix, id);
        }
        if (prefix){
            free(prefix);
            prefix = NULL;
        }
        if (id){
            free(id);
            id = NULL;
        }
    }
 ok:
    retval = 0;
 done:
    if (prefix)
        free(prefix);
    if (id)
        free(id);
    free(vec);
    return retval;
}

/*! Completion callback of variable for configured data and automatically generated data model
 *
 * Returns an expand-type list of commands as used by cligen 'expand' 
 * functionality.
 *
 * Assume callback given in a cligen spec: a <x:int expand_dbvar("db" "<xmlkeyfmt>")
 * @param[in]   h        clicon handle
 * @param[in]   name     Name of this function (eg "expand_dbvar")
 * @param[in]   cvv      The command so far. Eg: cvec [0]:"a 5 b"; [1]: x=5;
 * @param[in]   argv     Arguments given at the callback:
 *   <db>            Name of datastore, such as "running"
 *   <api_path_fmt>  Generated API PATH (this is sometimes added implicitly)
 *   [<mt-point>]    Optional YANG path-arg/xpath from mount-point
 * @param[out]  commands vector of function pointers to callback functions
 * @param[out]  helptxt  vector of pointers to helptexts
 * @retval      0        OK
 * @retval     -1        Error
 * @see cli_expand_var_generate where api_path_fmt + mt-point are generated
 * The syntax of <api_path_fmt> is of RFC8040 api-path with the following extension:
 *   %s  Represents the values of cvv in order starting from element 1
 *   %k  Represents the (first) key of the (previous) list
 */
int
expand_dbvar(void   *h,
             char   *name,
             cvec   *cvv,
             cvec   *argv,
             cvec   *commands,
             cvec   *helptexts)
{
    int              retval = -1;
    cbuf            *api_path_fmt_cb = NULL;
    char            *api_path_fmt;
    char            *api_path = NULL;
    char            *api_path_fmt01 = NULL;
    char            *dbstr;
    cxobj           *xt = NULL;
    char            *xpath = NULL;
    cxobj          **xvec = NULL;
    cxobj           *xe; /* direct ptr */
    cxobj           *xerr = NULL; /* free */
    size_t           xlen = 0;
    cxobj           *x;
    char            *bodystr;
    int              i;
    char            *bodystr0 = NULL; /* previous */
    cg_var          *cv;
    cxobj           *xtop = NULL; /* xpath root */
    cxobj           *xbot = NULL; /* xpath, NULL if datastore */
    yang_stmt       *y = NULL; /* yang spec of xpath */
    yang_stmt       *yp;
    cvec            *nsc = NULL;
    int              ret;
    int              cvvi = 0;
    cbuf            *cbxpath = NULL;
    yang_stmt       *ypath;
    yang_stmt       *ytype;
    char            *mtpoint = NULL;
    yang_stmt       *yspec0 = NULL;
    cvec            *nsc0 = NULL;
    char            *str;
    int              grouping_treeref;
    cvec            *callback_cvv;

    if (argv == NULL || (cvec_len(argv) != 2 && cvec_len(argv) != 3)){
        clixon_err(OE_PLUGIN, EINVAL, "requires arguments: <db> <apipathfmt> [<mountpt>]");
        goto done;
    }
    if ((yspec0 = clicon_dbspec_yang(h)) == NULL){
        clixon_err(OE_FATAL, 0, "No DB_SPEC");
        goto done;
    }
    if ((cv = cvec_i(argv, 0)) == NULL){
        clixon_err(OE_PLUGIN, 0, "Error when accessing argument <db>");
        goto done;
    }
    dbstr = cv_string_get(cv);
    if (strcmp(dbstr, "running") != 0 &&
        strcmp(dbstr, "candidate") != 0 &&
        strcmp(dbstr, "startup") != 0){
        clixon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
        goto done;
    }
    if ((cv = cvec_i(argv, 1)) == NULL){
        clixon_err(OE_PLUGIN, 0, "Error when accessing argument <api_path>");
        goto done;
    }
    if (autocli_grouping_treeref(h, &grouping_treeref) < 0)
        goto done;
    if ((api_path_fmt_cb = cbuf_new()) == NULL){
        clixon_err(OE_PLUGIN, errno, "cbuf_new");
        goto done;
    }
    if (grouping_treeref &&
        (callback_cvv = cligen_callback_arguments_get(cli_cligen(h))) != NULL){
        /* Concatenate callback arguments to a single prepend string */
        if (cvec_concat_cb(callback_cvv, api_path_fmt_cb) < 0)
            goto done;
    }
    cprintf(api_path_fmt_cb, "%s", cv_string_get(cv));
    api_path_fmt = cbuf_get(api_path_fmt_cb);
    if (cvec_len(argv) > 2){ /* mountpoint */
        /* api_path_fmt is without top-level */
        cv = cvec_i(argv, 2);
        str = cv_string_get(cv);
        if (strncmp(str, "mtpoint:", strlen("mtpoint:")) != 0){
            clixon_err(OE_PLUGIN, 0, "mtpoint does not begin with 'mtpoint:'");
            goto done;
        }
        mtpoint = str + strlen("mtpoint:");
        /* Get and combined api-path01 */
        if (mtpoint_paths(yspec0, mtpoint, api_path_fmt, &api_path_fmt01) < 0)
            goto done;
        if (api_path_fmt2api_path(api_path_fmt01, cvv, yspec0, &api_path, &cvvi) < 0)
            goto done;
    }
    else{
        if (api_path_fmt2api_path(api_path_fmt, cvv, yspec0, &api_path, &cvvi) < 0)
            goto done;
    }
    if (api_path == NULL)
        goto ok;
    /* Create config top-of-tree */
    if ((xtop = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
        goto done;
    xbot = xtop;
    /* This is primarily to get "y", 
     * xpath2xml would have worked!!
     * XXX: but y is just the first in this list, there could be other y:s?
     */
    if ((ret = api_path2xml(api_path, yspec0, xtop, YC_DATANODE, 0, &xbot, &y, &xerr)) < 0)
        goto done;
    if (ret == 0){
        clixon_err_netconf(h, OE_NETCONF, 0, xerr, "Expand datastore symbol");
        goto done;
    }
    if (y == NULL){
        clixon_err(OE_YANG, 0, "y is NULL");
        goto done;
    }
    /* Transform api-path to xpath for netconf  */
    if ((ret = api_path2xpath(api_path, yspec0, &xpath, &nsc, &xerr)) < 0)
        goto done;
    if (ret == 0){
        clixon_err_netconf(h, OE_NETCONF, 0, xerr, "Expand datastore symbol");
        goto done;
    }
    if ((cbxpath = cbuf_new()) == NULL){
        clixon_err(OE_UNIX, errno, "cbuf_new");
        goto done;
    }
    if (mtpoint){
        if (xml_nsctx_yangspec(yspec0, &nsc0) < 0)
            goto done;
        cv = NULL;      /* Append nsc0 to nsc */
        while ((cv = cvec_each(nsc0, cv)) != NULL)
            cvec_append_var(nsc, cv);
    }
    cprintf(cbxpath, "%s", xpath);
    if (clicon_option_bool(h, "CLICON_CLI_EXPAND_LEAFREF") &&
        (ytype = yang_find(y, Y_TYPE, NULL)) != NULL &&
        strcmp(yang_argument_get(ytype), "leafref") == 0){
        /* Special case for leafref. Detect leafref via Yang-type, 
         * Get Yang path element, tentatively add the new syntax to the whole
         * tree and apply the path to that.
         * Last, the reference point for the xpath code below is changed to 
         * the point of the tentative new xml.
         * Here the whole syntax tree is loaded, and it would be better to offload
         * such operations to the datastore by a generic xpath function.
         */

        /* 
         * The syntax for a path argument is a subset of the XPath abbreviated
         * syntax.  Predicates are used only for constraining the values for the
         * key nodes for list entries.  Each predicate consists of exactly one
         * equality test per key, and multiple adjacent predicates MAY be
         * present if a list has multiple keys.  The syntax is formally defined
         * by the rule "path-arg" in Section 14.
         * The "path" XPath expression is conceptually evaluated in the
         * following context, in addition to the definition in Section 6.4.1:
         *
         * - If the "path" statement is defined within a typedef, the context
         * node is the leaf or leaf-list node in the data tree that
         * references the typedef.
         * - Otherwise, the context node is the node in the data tree for which
         * the "path" statement is defined.
         */
        if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){
            clixon_err(OE_DB, 0, "Leafref %s requires path statement", yang_argument_get(ytype));
            goto done;
        }
        /* Extend xpath with leafref path: Append yang_argument_get(ypath) to xpath
         */
        if (xpath_append(cbxpath, yang_argument_get(ypath), y, nsc) < 0)
            goto done;
    }
    /* Get configuration based on cbxpath */
    if (clicon_rpc_get_config(h, NULL, dbstr, cbuf_get(cbxpath), nsc, NULL, &xt) < 0)
        goto done;
    if ((xe = xpath_first(xt, NULL, "/rpc-error")) != NULL){
        clixon_err_netconf(h, OE_NETCONF, 0, xe, "Get configuration");
        goto ok;
    }
    if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, cbuf_get(cbxpath)) < 0)
        goto done;
    /* Loop for inserting into commands cvec. 
     * Detect duplicates: for ordered-by system assume list is ordered, so you need
     * just remember previous
     * but for ordered-by system, check the whole list
     */
    bodystr0 = NULL;
    for (i = 0; i < xlen; i++) {
        x = xvec[i];
        if (xml_type(x) == CX_BODY)
            bodystr = xml_value(x);
        else
            bodystr = xml_body(x);
        if (bodystr == NULL)
            continue; /* no body, cornercase */
        if ((y = xml_spec(x)) != NULL &&
            (yp = yang_parent_get(y)) != NULL &&
            yang_keyword_get(yp) == Y_LIST &&
            yang_find(yp, Y_ORDERED_BY, "user") != NULL){
            /* Detect duplicates linearly in existing values */
            {
                cg_var *cv = NULL;
                while ((cv = cvec_each(commands, cv)) != NULL)
                    if (strcmp(cv_string_get(cv), bodystr) == 0)
                        break;
                if (cv == NULL)
                    cvec_add_string(commands, NULL, bodystr);
            }
        }
        else{
            if (bodystr0 && strcmp(bodystr, bodystr0) == 0)
                continue; /* duplicate, assume sorted */
            bodystr0 = bodystr;
            /* RFC3986 decode */
            cvec_add_string(commands, NULL, bodystr);
        }
    }
 ok:
    retval = 0;
 done:
    if (nsc0)
        cvec_free(nsc0);
    if (api_path_fmt_cb)
        cbuf_free(api_path_fmt_cb);
    if (api_path_fmt01)
        free(api_path_fmt01);
    if (cbxpath)
        cbuf_free(cbxpath);
    if (xerr)
        xml_free(xerr);
    if (nsc)
        xml_nsctx_free(nsc);
    if (api_path)
        free(api_path);
    if (xvec)
        free(xvec);
    if (xtop)
        xml_free(xtop);
    if (xt)
        xml_free(xt);
    if (xpath)
        free(xpath);
    return retval;
}

/*! Completion callback of variable for yang schema list nodes
 *
 * Typical yang: 
 *     container foo { list bar; }
 *   modA:
 *     augment foo bar;
 *   modB:
 *     augment foo fie;
 * This function expands foo to: bar, fie...
 * Or (if <module> is true): modA:bar, modB:fie...
 * @param[in]   h        clicon handle
 * @param[in]   name     Name of this function
 * @param[in]   cvv      The command so far. Eg: cvec [0]:"a 5 b"; [1]: x=5;
 * @param[in]   argv     Arguments given at the callback:
 *   <schemanode>        Absolute YANG schema-node (eg: /ctrl:services)
 *   <modname>           true|false: Show with api-path module-name, eg moda:foo, modb:fie
 * @param[out]  commands vector of function pointers to callback functions
 * @param[out]  helptxt  vector of pointers to helptexts
 * @retval      0        OK
 * @retval     -1        Error
 */
int
expand_yang_list(void   *h,
                 char   *name,
                 cvec   *cvv,
                 cvec   *argv,
                 cvec   *commands,
                 cvec   *helptexts)
{
    int        retval = -1;
    int        argc = 0;
    cg_var    *cv;
    char      *schema_nodeid;
    yang_stmt *yspec0 = NULL;
    yang_stmt *yres = NULL;
    yang_stmt *yn = NULL;
    yang_stmt *ydesc;
    yang_stmt *ymod;
    int        modname = 0;
    cbuf      *cb = NULL;
    int        inext;

    if (argv == NULL || cvec_len(argv) < 1 || cvec_len(argv) > 2){
        clixon_err(OE_PLUGIN, EINVAL, "requires arguments: <schemanode> [<modname>]");
        goto done;
    }
    if ((cv = cvec_i(argv, argc++)) == NULL){
        clixon_err(OE_PLUGIN, 0, "Error when accessing argument <schemanode>");
        goto done;
    }
    schema_nodeid = cv_string_get(cv);
    if (cvec_len(argv) > argc){
        if (cli_show_option_bool(argv, argc++, &modname) < 0)
            goto done;
    }
    if ((yspec0 = clicon_dbspec_yang(h)) == NULL){
        clixon_err(OE_FATAL, 0, "No DB_SPEC");
        goto done;
    }
    if (yang_abs_schema_nodeid(yspec0, schema_nodeid, &yres) < 0)
        goto done;
    if ((cb = cbuf_new()) == NULL){
        clixon_err(OE_UNIX, errno, "cbuf_new");
        goto done;
    }
    inext = 0;
    while ((yn = yn_iter(yres, &inext)) != NULL) {
        if (yang_keyword_get(yn) != Y_LIST)
            continue;
        cbuf_reset(cb);
        if (modname){
            if (ys_real_module(yn, &ymod) < 0)
                goto done;
            cprintf(cb, "%s:", yang_argument_get(ymod));
        }
        cprintf(cb, "%s", yang_argument_get(yn));
        cvec_add_string(commands, NULL, cbuf_get(cb));
        if ((ydesc = yang_find(yn, Y_DESCRIPTION, NULL)) != NULL)
            cvec_add_string(helptexts, NULL, yang_argument_get(ydesc));
        else
            cvec_add_string(helptexts, NULL, "Service");
    }
    retval = 0;
 done:    
    if (cb)
        cbuf_free(cb);
    return retval;
}

/*! CLI callback show yang spec. If arg given matches yang argument string 
 *
 * @param[in]  h     Clixon handle
 * @param[in]  cvv   Vector of command variables
 * @param[in]  argv  
 * @retval     0     OK
 * @retval    -1     Error
 */
int
show_yang(clixon_handle h,
          cvec         *cvv,
          cvec         *argv)
{
    int        retval = -1;
    yang_stmt *yn;
    char      *str = NULL;
    yang_stmt *yspec;
    int        inext;

    yspec = clicon_dbspec_yang(h);
    if (cvec_len(argv) > 0){
        if ((str = cv_string_get(cvec_i(argv, 0))) != NULL &&
            (yn = yang_find(yspec, 0, str)) != NULL)
            if (yang_print_cb(stdout, yn, cligen_output) < 0)
                goto done;
    }
    else{
        inext = 0;
        while ((yn = yn_iter(yspec, &inext)) != NULL) {
            if (yang_print_cb(stdout, yn, cligen_output) < 0)
                goto done;
        }
    }
    retval = 0;
 done:
    return retval;
}

/*! Common internal show routine for several show cli callbacks
 *
 * @param[in] h            Clixon handle
 * @param[in] db           Datastore
 * @param[in] format       Output format
 * @param[in] pretty        
 * @param[in] state
 * @param[in] withdefault  RFC 6243 with-default modes
 * @param[in] extdefault   with-defaults with propriatary extensions
 * @param[in] prepend      CLI prefix to prepend cli syntax, eg "set "
 * @param[in] xpath        XPath
 * @param[in] fromroot     If 0, display config from node of XPATH, if 1 display from root
 * @param[in] nsc          Namespace mapping for xpath
 * @param[in] skiptop      If set, do not show object itself, only its children
 * @retval    0            OK
 * @retval   -1            Error
  */
int
cli_show_common(clixon_handle    h,
                char            *db,
                enum format_enum format,
                int              pretty,
                int              state,
                char            *withdefault,
                char            *extdefault,
                char            *prepend,
                char            *xpath,
                int              fromroot,
                cvec            *nsc,
                int              skiptop
                )
{
    int              retval = -1;
    cxobj           *xt = NULL;
    cxobj           *xerr;
    cxobj          **vec = NULL;
    size_t           veclen;
    cxobj           *xp;
    int              i;

    if (state && strcmp(db, "running") != 0){
        clixon_err(OE_FATAL, 0, "Show state only for running database, not %s", db);
        goto done;
    }
    if (state == 0){     /* Get configuration-only from a database */
        if (clicon_rpc_get_config(h, NULL, db, xpath, nsc, withdefault, &xt) < 0)
            goto done;
    }
    else {               /* Get configuration and state from running */
        if (clicon_rpc_get(h, xpath, nsc, CONTENT_ALL, -1, withdefault, &xt) < 0)
            goto done;
    }
    if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){
        clixon_err_netconf(h, OE_NETCONF, 0, xerr, "Get configuration");
        goto done;
    }
    /* Special tagged modes: strip wd:default=true attribute and (optionally) nodes associated with it */
    if (extdefault &&
        (strcmp(extdefault, "report-all-tagged-strip") == 0 ||
         strcmp(extdefault, "report-all-tagged-default") == 0)){
        if (purge_tagged_nodes(xt, IETF_NETCONF_WITH_DEFAULTS_ATTR_NAMESPACE, "default", "true",
                               strcmp(extdefault, "report-all-tagged-strip")
                               ) < 0)
            goto done;
        /* Remove empty containers */
        if (xml_default_nopresence(xt, 2, 0) < 0)
            goto done;
    }
    if (fromroot)
        xpath="/";
    if (xpath_vec(xt, nsc, "%s", &vec, &veclen, xpath) < 0)
        goto done;
    if (veclen){
        /* Special case LIST */
        if (format == FORMAT_JSON){
            switch (format){
            case FORMAT_JSON:
                if (xml2json_vec(stdout, vec, veclen, pretty, cligen_output, skiptop) < 0)
                    goto done;
                break;
            default:
                break;
            }
        }
        else    /* Default */
            for (i=0; i<veclen; i++){
                xp = vec[i];
                /* Print configuration according to format */
                switch (format){
                case FORMAT_XML:
                    if (clixon_xml2file(stdout, xp, 0, pretty, NULL, cligen_output, skiptop, 1) < 0)
                        goto done;
                    if (!pretty && i == veclen-1)
                        cligen_output(stdout, "\n");
                    break;
                case FORMAT_TEXT: /* XXX does not handle multiple leaf-list */
                    if (clixon_text2file(stdout, xp, 0, cligen_output, skiptop, 1) < 0)
                        goto done;
                    break;
                case FORMAT_CLI:
                    /* If xp is not bound, cli prints are skipped */
                    if (clixon_cli2file(h, stdout, xp, prepend, cligen_output, skiptop) < 0) /* cli syntax */
                        goto done;
                    break;
                case FORMAT_NETCONF:
                    if (i==0){
                        cligen_output(stdout, "<rpc xmlns=\"%s\" %s><edit-config><target><candidate/></target><config>",
                                      NETCONF_BASE_NAMESPACE, NETCONF_MESSAGE_ID_ATTR);
                        if (pretty)
                            cligen_output(stdout, "\n");
                    }
                    if (clixon_xml2file(stdout, xp, 2, pretty, NULL, cligen_output, skiptop, 1) < 0)
                        goto done;
                    if (i == veclen-1)
                        cligen_output(stdout, "</config></edit-config></rpc>]]>]]>\n");
                    break;
                default:
                    break;
                }
            }
    }
    else if (format == FORMAT_JSON)
        cligen_output(stdout, "{}\n");
    retval = 0;
done:
    if (vec)
        free(vec);
    if (xt)
        xml_free(xt);
    return retval;
}

/*! Common internal parse cli show format option
 *
 * @param[in]  h      Clixon handle
 * @param[in]  argv   String vector: <dbname> <format> <xpath> [<varname>]
 * @param[in]  argc   Index into argv
 * @param[out] format Output format
 * @retval     0      OK
 * @retval    -1      Error
 */
int
cli_show_option_format(clixon_handle     h,
                       cvec             *argv,
                       int               argc,
                       enum format_enum *formatp)
{
    int              retval = -1;
    enum format_enum format = FORMAT_XML;
    char            *formatstr;

    formatstr = cv_string_get(cvec_i(argv, argc));
    if ((int)(format = format_str2int(formatstr)) < 0){
        clixon_err(OE_PLUGIN, 0, "Not valid format: %s", formatstr);
        goto done;
    }
    /* Special default format handling */
    if (format == FORMAT_DEFAULT){
        formatstr = clicon_option_str(h, "CLICON_CLI_OUTPUT_FORMAT");
        if ((int)(format = format_str2int(formatstr)) < 0){
            clixon_err(OE_PLUGIN, 0, "Not valid format: %s", formatstr);
            goto done;
        }
    }
    *formatp = format;
    retval = 0;
 done:
    return retval;
}

/*! Common internal parse cli show boolean option
 *
 * @param[in]  argv   String vector: <dbname> <format> <xpath> [<varname>]
 * @param[in]  argc   Index into argv
 * @param[out] result result boolean: 0 or 1
 * @retval     0      OK
 * @retval    -1      Error
 */
int
cli_show_option_bool(cvec *argv,
                     int   argc,
                     int  *result
                     )
{
    int     retval = -1;
    char   *boolstr;
    cg_var *boolcv = NULL;

    boolstr = cv_string_get(cvec_i(argv, argc));
    if ((boolcv = cv_new(CGV_BOOL)) == NULL){
        clixon_err(OE_UNIX, errno, "cv_new");
        goto done;
    }
    if (cv_parse(boolstr, boolcv) < 0){
        clixon_err(OE_UNIX, errno, "Parse boolean %s", boolstr);
        goto done;
    }
    *result = cv_bool_get(boolcv);
    retval = 0;
 done:
    if (boolcv)
        cv_free(boolcv);
    return retval;
}

/*! Common internal parse cli show with-default option
 *
 * Default modes accorsing to RFC6243 + three extra modes based on report-all-tagged:
 * 1) NULL
 * 2) report-all-tagged-default  Strip "default" attribute (=report-all)
 * 3) report-all-tagged-strip Strip "default" attribute and all nodes tagged with it (=trim)
 * @param[in]  argv         String vector: <dbname> <format> <xpath> [<varname>]
 * @param[in]  argc         Index into argv
 * @param[in]  withdefault  RFC 6243 with-default modes
 * @param[in]  extdefault   with-defaults with propriatary extensions
 * @retval     0     OK
 * @retval    -1     Error
 */
int
cli_show_option_withdefault(cvec  *argv,
                            int    argc,
                            char **withdefault,
                            char **extdefault)
{
    int   retval = -1;
    char *e;

    e = cv_string_get(cvec_i(argv, argc));
    /* From extended to RFC6243 withdefault modes */
    if (strcmp(e, "report-all-tagged-strip") == 0)
        *withdefault = "report-all-tagged";
    else if (strcmp(e, "report-all-tagged-default") == 0)
        *withdefault = "report-all-tagged";
    else if (strcmp(e, "NULL") == 0){
        e = NULL;
        *withdefault = NULL;
    }
    else if (strcmp(e, "report-all") != 0 &&
             strcmp(e, "trim") != 0 &&
             strcmp(e, "explicit") != 0 &&
             strcmp(e, "report-all-tagged") != 0){
        clixon_err(OE_YANG, EINVAL, "Unexpected with-default option: %s", e);
        goto done;
    }
    else
        *withdefault = e;
    *extdefault = e;
    retval = 0;
 done:
    return retval;
}

/*! Generic show configuration callback
 *
 * Does not need to be used with the autocli as cli_show_auto does
 * @param[in]  h     Clixon handle
 * @param[in]  cvv   Vector of variables from CLIgen command-line
 * @param[in]  argv  String vector of show options, format:
 *   <dbname>        Name of datastore, such as "running"
 * -- from here optional:
 *   <format>        "text"|"xml"|"json"|"cli"|"netconf" (see format_enum), default: xml
 *   <xpath>         xpath expression, that may contain one %, eg "/sender[name='foo']"
 *   <namespace>     xpath default namespace (or NULL) not needed for xpath=NULL
 *   <pretty>        true|false: pretty-print or not
 *   <state>         true|false: also print state
 *   <default>       Retrieval mode: report-all, trim, explicit, report-all-tagged, 
 *                   NULL, report-all-tagged-default, report-all-tagged-strip (extended)
 *   <prepend>       CLI prefix: prepend before cli syntax output
 * @retval      0    OK
 * @retval     -1    Error
 * @code
 *   clispec:
 *      show config, cli_show_config("running","xml");
 *   cli run:
 *      > set table parameter a value x
 *      > show config
 *        <table xmlns="urn:example:clixon">
 *           <parameter>
 *              <name>a</name>
 *              <value>x</value>
 *           </parameter>
 *        </table>
 * @endcode
 * @see cli_show_auto       autocli with expansion
 * @see cli_show_auto_mode  autocli with edit menu support
 */
int
cli_show_config(clixon_handle h,
                cvec         *cvv,
                cvec         *argv)
{
    int              retval = -1;
    char            *dbname;
    enum format_enum format = FORMAT_XML;
    cvec            *nsc = NULL;
    int              pretty = 1;
    char            *prepend = NULL;
    int              state = 0;
    char            *withdefault = NULL; /* RFC 6243 modes */
    char            *extdefault = NULL; /* with extended tagged modes */
    int              argc = 0;
    char            *xpath = "/";
    char            *namespace = NULL;
    int              fromroot = 0;

    if (cvec_len(argv) < 2 || cvec_len(argv) > 8){
        clixon_err(OE_PLUGIN, EINVAL, "Received %d arguments. Expected: <dbname> [<format><xpath> <namespace> <pretty> <state> <default> <prepend>]", cvec_len(argv));
        goto done;
    }
    dbname = cv_string_get(cvec_i(argv, argc++));
    if (cvec_len(argv) > argc)
        if (cli_show_option_format(h, argv, argc++, &format) < 0)
            goto done;
    if (cvec_len(argv) > argc)
        xpath = cv_string_get(cvec_i(argv, argc++));
    if (cvec_len(argv) > argc){
        namespace = cv_string_get(cvec_i(argv, argc++));
        /* Special symbol NULL means no namespace */
        if (strcmp(namespace, "NULL") != 0)
            if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL)
                goto done;
    }
    if (cvec_len(argv) > argc){
        if (cli_show_option_bool(argv, argc++, &pretty) < 0)
            goto done;
    }
    if (cvec_len(argv) > argc){
        if (cli_show_option_bool(argv, argc++, &state) < 0)
            goto done;
    }
    if (cvec_len(argv) > argc){
        if (cli_show_option_withdefault(argv, argc++,
                                        &withdefault,
                                        &extdefault) < 0)
            goto done;
    }
    if (cvec_len(argv) > argc){
        prepend = cv_string_get(cvec_i(argv, argc++));
    }
    if (cli_show_common(h, dbname, format, pretty, state,
                        withdefault, extdefault,
                        prepend, xpath, fromroot, nsc, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (nsc)
        xml_nsctx_free(nsc);
    return retval;
}

/*! Show configuration xpath
 *
 * @param[in]  h     Clixon handle
 * @param[in]  cvv   Vector of variables from CLIgen command-line
 * @param[in]  argv  String vector of show options, format:
 *   <dbname>  "running"|"candidate"|"startup"
 * @retval     0     OK
 * @retval    -1     Error
 */
int
show_conf_xpath(clixon_handle h,
                cvec         *cvv,
                cvec         *argv)
{
    int              retval = -1;
    char            *dbname;
    char            *xpath;
    cg_var          *cv;
    cvec            *nsc = NULL;
    yang_stmt       *yspec;
    int              fromroot = 0;

    if (cvec_len(argv) != 1){
        clixon_err(OE_PLUGIN, EINVAL, "Requires one element to be <dbname>");
        goto done;
    }
    if ((yspec = clicon_dbspec_yang(h)) == NULL){
        clixon_err(OE_FATAL, 0, "No DB_SPEC");
        goto done;
    }
    dbname = cv_string_get(cvec_i(argv, 0));
    /* Look for xpath in command (kludge: cv must be called "xpath") */
    if ((cv = cvec_find(cvv, "xpath")) == NULL){
        clixon_err(OE_PLUGIN, EINVAL, "Requires one variable to be <xpath>");
        goto done;
    }
    xpath = cv_string_get(cv);
    /* Create canonical namespace */
    if (xml_nsctx_yangspec(yspec, &nsc) < 0)
        goto done;
    /* Look for and add default namespace variable in command */
    if ((cv = cvec_find(cvv, "ns")) != NULL){
        if (xml_nsctx_add(nsc, NULL, cv_string_get(cv)) < 0)
            goto done;
    }
    if (cli_show_common(h, dbname, FORMAT_XML, 1, 0,
                        NULL, NULL,
                        NULL, xpath, fromroot, nsc, 0) < 0)
        goto done;
    retval = 0;
done:
    if (nsc)
        xml_nsctx_free(nsc);
    return retval;
}

/*! Show clixon and CLIgen versions
 */
int
cli_show_version(clixon_handle h,
                 cvec         *cvv,
                 cvec         *argv)
{
    cligen_output(stdout, "Clixon: %s\n", CLIXON_GITHASH);
    cligen_output(stdout, "CLIgen: %s\n", CLIGEN_VERSION);
    return 0;
}

/*! Show configuration callback using auto CLI syntax with expansion
 *
 * Can be used only in context of an autocli generated syntax tree, such as:
 *   show @datamodel, cli_show_auto();
 * This show command can use expansion to "TAB" inside the syntax tree to show
 * portions of the syntax.
 * @param[in]  h     Clixon handle
 * @param[in]  cvv   Vector of variables from CLIgen command-line
 * @param[in]  argv  String vector of show options, format:
 *   <api_path_fmt>  Generated API PATH (this is added implicitly, not actually given in argv)
 *  [<mt-point>]     Optional YANG path-arg/xpath from mount-point
 *   <dbname>        Name of datastore, such as "running"
 * -- from here optional:
 *   <format>        "text"|"xml"|"json"|"cli"|"netconf" (see format_enum), default: xml
 *   <pretty>        true|false: pretty-print or not
 *   <state>         true|false: also print state
 *   <default>       Retrieval mode: report-all, trim, explicit, report-all-tagged, 
 *                   NULL, report-all-tagged-default, report-all-tagged-strip (extended)
 *   <prepend>       CLI prefix: prepend before cli syntax output
 *   <fromroot>      true|false: Show from root
 * @retval     0     OK
 * @retval    -1     Error
 * @code
 *   clispec: 
 *      show config @datamodelshow, cli_show_auto("candidate", "xml");
 *   cli run:
 *      > set table parameter a value x
 *      > show config table parameter a
 *        <parameter>
 *           <name>a</name>
 *           <value>x</value>
 *        </parameter>
 * @endcode
 * @see cli_show_config     with no autocli coupling
 * @see cli_show_auto_mode  autocli with edit menu support
 *
 * XXX merge cli_show_auto and cli_show_auto_mode
 * @see cli_callback_generate where api_path_fmt + mt-point are generated
 */
int
cli_show_auto(clixon_handle h,
              cvec         *cvv,
              cvec         *argv)
{
    int              retval = -1;
    char            *dbname;
    enum format_enum format = FORMAT_XML;
    cvec            *nsc = NULL;
    int              pretty = 1;
    char            *prepend = NULL;
    int              state = 0;
    char            *withdefault = NULL; /* RFC 6243 modes */
    char            *extdefault = NULL; /* with extended tagged modes */
    int              argc = 0;
    char            *xpath = NULL;
    yang_stmt       *yspec0;
    char            *api_path = NULL;
    int              cvvi = 0;
    char            *api_path_fmt;  /* xml key format */
    char            *api_path_fmt01 = NULL;
    char            *str;
    char            *mtpoint = NULL;
    int              fromroot = 0;

    if (cvec_len(argv) < 2 || cvec_len(argv) > 9){
        clixon_err(OE_PLUGIN, EINVAL, "Received %d arguments. Expected:: <api-path-fmt>* <database> [<format> <pretty> <state> <default> <prepend> <fromroot>]", cvec_len(argv));
        goto done;
    }
    api_path_fmt = cv_string_get(cvec_i(argv, argc++));
    str = cv_string_get(cvec_i(argv, argc++));
    if (str && strncmp(str, "mtpoint:", strlen("mtpoint:")) == 0){
        mtpoint = str + strlen("mtpoint:");
        dbname = cv_string_get(cvec_i(argv, argc++));
    }
    else
        dbname = str;
    if (cvec_len(argv) > argc)
        if (cli_show_option_format(h, argv, argc++, &format) < 0)
            goto done;
    if (cvec_len(argv) > argc){
        if (cli_show_option_bool(argv, argc++, &pretty) < 0)
            goto done;
    }
    if (cvec_len(argv) > argc){
        if (cli_show_option_bool(argv, argc++, &state) < 0)
            goto done;
    }
    if (cvec_len(argv) > argc){
        if (cli_show_option_withdefault(argv, argc++,
                                        &withdefault,
                                        &extdefault) < 0)
            goto done;
    }
    if (cvec_len(argv) > argc){
        prepend = cv_string_get(cvec_i(argv, argc++));
    }
    if (cvec_len(argv) > argc){
        if (cli_show_option_bool(argv, argc++, &fromroot) < 0)
            goto done;
    }
    if ((yspec0 = clicon_dbspec_yang(h)) == NULL){
        clixon_err(OE_FATAL, 0, "No DB_SPEC");
        goto done;
    }
    if (mtpoint){
        /* Get and combined api-path01 */
        if (mtpoint_paths(yspec0, mtpoint, api_path_fmt, &api_path_fmt01) < 0)
            goto done;
        if (api_path_fmt2api_path(api_path_fmt01, cvv, yspec0, &api_path, &cvvi) < 0)
            goto done;
    }
    else{
        if (api_path_fmt2api_path(api_path_fmt, cvv, yspec0, &api_path, &cvvi) < 0)
            goto done;
    }
    if (api_path2xpath(api_path, yspec0, &xpath, &nsc, NULL) < 0)
        goto done;
    if (xpath == NULL){
        clixon_err(OE_FATAL, 0, "Invalid api-path: %s", api_path);
        goto done;
    }
    if (cli_show_common(h, dbname, format, pretty, state,
                        withdefault, extdefault,
                        prepend, xpath, fromroot, nsc, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (api_path_fmt01)
        free(api_path_fmt01);
    if (nsc)
        xml_nsctx_free(nsc);
    if (xpath)
        free(xpath);
    if (api_path)
        free(api_path);
    return retval;
}

/*! Show configuration callback for autocli edit modes using tree working point
 *
 * Can be used together with "edit modes". The xpath is derived from
 * the current "cli-edit-mode" as described here: 
 *     https://clixon-docs.readthedocs.io/en/latest/cli.html#edit-modes
 * @param[in]  h     Clixon handle
 * @param[in]  cvv   Vector of variables from CLIgen command-line
 * @param[in]  argv  String vector of show options, format:
 *   <dbname>        Name of datastore, such as "running"
 * -- from here optional:
 *   <format>        "text"|"xml"|"json"|"cli"|"netconf" (see format_enum), default: xml
 *   <pretty>        true|false: pretty-print or not
 *   <state>         true|false: also print state
 *   <default>       Retrieval mode: report-all, trim, explicit, report-all-tagged, 
 *                   NULL, report-all-tagged-default, report-all-tagged-strip (extended)
 *   <prepend>       CLI prefix: prepend before cli syntax output
 * @retval     0     OK
 * @retval    -1     Error
 * @cli_show_auto_ctrl
code
 *   clispec:
 *      show config, cli_show_auto_mode("candidate");
 *   cli run:
 *      > set table parameter a value x
 *      > edit table
 *      > show config
 *        <parameter>
 *           <name>a</name>
 *           <value>x</value>
 *        </parameter>
 * @endcode
 * @see cli_show_auto    autocli with expansion 
 * @see cli_show_config  with no autocli coupling
 */
int
cli_show_auto_mode(clixon_handle h,
                   cvec         *cvv,
                   cvec         *argv)
{
    int              retval = -1;
    char            *dbname;
    enum format_enum format = FORMAT_XML;
    cvec            *nsc = NULL;
    int              pretty = 1;
    char            *prepend = NULL;
    int              state = 0;
    char            *withdefault = NULL; /* RFC 6243 modes */
    char            *extdefault = NULL; /* with extended tagged modes */
    int              argc = 0;
    int              skiptop = 0;
    char            *xpath = NULL;
    yang_stmt       *yspec0;
    yang_stmt       *yspec;
    char            *api_path = NULL;
    char            *mtpoint = NULL;
    yang_stmt       *yu;
    cbuf            *cbxpath = NULL;
    cvec            *nsc0 = NULL;
    cg_var          *cv;
    int              fromroot = 0;

    if (cvec_len(argv) < 2 || cvec_len(argv) > 7){
        clixon_err(OE_PLUGIN, EINVAL, "Received %d arguments. Expected: <database> [ <format> <pretty> <state> <default> <cli-prefix>]", cvec_len(argv));
        goto done;
    }
    if ((yspec0 = clicon_dbspec_yang(h)) == NULL){
        clixon_err(OE_FATAL, 0, "No DB_SPEC");
        goto done;
    }
    dbname = cv_string_get(cvec_i(argv, argc++));
    if (cvec_len(argv) > argc)
        if (cli_show_option_format(h, argv, argc++, &format) < 0)
            goto done;
    if (cvec_len(argv) > argc){
        if (cli_show_option_bool(argv, argc++, &pretty) < 0)
            goto done;
    }
    if (cvec_len(argv) > argc){
        if (cli_show_option_bool(argv, argc++, &state) < 0)
            goto done;
    }
    if (cvec_len(argv) > argc){
        if (cli_show_option_withdefault(argv, argc++,
                                        &withdefault,
                                        &extdefault) < 0)
            goto done;
    }
    if (cvec_len(argv) > argc){
        prepend = cv_string_get(cvec_i(argv, argc++));
    }
    /* Store this as edit-mode */
    if (clicon_data_get(h, "cli-edit-mode", &api_path) == 0 && strlen(api_path))
        ;
    else
        api_path = "/";
    if (clicon_data_get(h, "cli-edit-mtpoint", &mtpoint) == 0 && strlen(mtpoint)){
        if (yang_path_arg(yspec0, mtpoint, &yu) < 0)
            goto done;
        if (yang_mount_get(yu, mtpoint, &yspec) < 0)
            goto done;
    }
    yspec = yspec0;
    if (api_path2xpath(api_path, yspec, &xpath, &nsc, NULL) < 0)
        goto done;
    if (xpath == NULL){
        clixon_err(OE_FATAL, 0, "Invalid api-path: %s", api_path);
        goto done;
    }
    if ((cbxpath = cbuf_new()) == NULL){
        clixon_err(OE_UNIX, errno, "cbuf_new");
        goto done;
    }
    if (mtpoint){
        /*
         * XXX disabled the line below, because otherwise the path up to the
         * mount point would be added twice to cbxpath. This is because the
         * api_path and thus also the xpath already include the path up to the
         * mount point. (at least since cli_auto_edit() was changed)
         */
        //cprintf(cbxpath, "%s", mtpoint);
        if (xml_nsctx_yangspec(yspec0, &nsc0) < 0)
            goto done;
        cv = NULL;      /* Append cvv1 to cvv2 */
        while ((cv = cvec_each(nsc0, cv)) != NULL)
            cvec_append_var(nsc, cv);
    }
    cprintf(cbxpath, "%s", xpath);
    skiptop = (strcmp(xpath,"/") != 0);
    if (cli_show_common(h, dbname, format, pretty, state,
                        withdefault, extdefault,
                        prepend, cbuf_get(cbxpath), fromroot, nsc, skiptop) < 0)
        goto done;
    retval = 0;
 done:
    if (nsc0)
        cvec_free(nsc0);
    if (cbxpath)
        cbuf_free(cbxpath);
    if (nsc)
        xml_nsctx_free(nsc);
    if (xpath)
        free(xpath);
    return retval;
}

/*! Show clixon configuration options as loaded
 *
 * @param[in]  h    Clixon handle
 * @param[in]  cvv  Vector of command variables
 * @param[in]  argv  
 * @retval     0    OK
 * @retval    -1    Error
'* @see clicon_option_dump clicon_option_dump1
 */
int
cli_show_options(clixon_handle h,
                 cvec         *cvv,
                 cvec         *argv)
{
    int            retval = -1;
    clicon_hash_t *hash = clicon_options(h);
    int            i;
    char         **keys = NULL;
    void          *val;
    size_t         klen;
    size_t         vlen;
    cxobj         *x = NULL;

    if (clicon_hash_keys(hash, &keys, &klen) < 0)
        goto done;
    for(i = 0; i < klen; i++) {
        val = clicon_hash_value(hash, keys[i], &vlen);
        if (vlen){
            if (((char*)val)[vlen-1]=='\0') /* assume string */
                fprintf(stdout, "%s: \"%s\"\n", keys[i], (char*)val);
            else
                fprintf(stdout, "%s: 0x%p , length %zu\n", keys[i], val, vlen);
        }
        else
            fprintf(stdout, "%s: NULL\n", keys[i]);
    }
    /* Next print CLICON_FEATURE, CLICON_YANG_DIR and CLICON_SNMP_MIB from config tree
     * Since they are lists they are placed in the config tree.
     */
    x = NULL;
    while ((x = xml_child_each(clicon_conf_xml(h), x, CX_ELMNT)) != NULL) {
        if (strcmp(xml_name(x), "CLICON_YANG_DIR") != 0)
            continue;
        fprintf(stdout, "%s: \"%s\"\n", xml_name(x), xml_body(x));
    }
    x = NULL;
    while ((x = xml_child_each(clicon_conf_xml(h), x, CX_ELMNT)) != NULL) {
        if (strcmp(xml_name(x), "CLICON_FEATURE") != 0)
            continue;
        fprintf(stdout, "%s: \"%s\"\n", xml_name(x), xml_body(x));
    }
    x = NULL;
    while ((x = xml_child_each(clicon_conf_xml(h), x, CX_ELMNT)) != NULL) {
        if (strcmp(xml_name(x), "CLICON_SNMP_MIB") != 0)
            continue;
        fprintf(stdout, "%s: \"%s\"\n", xml_name(x), xml_body(x));
    }
   retval = 0;
 done:
    if (keys)
        free(keys);
    return retval;
}

/*! Show pagination
 *
 * @param[in]  h    Clixon handle
 * @param[in]  cvv  Vector of cli string and instantiated variables 
 * @param[in]  argv Vector. Format: <xpath> <prefix> <namespace> <format> <limit>
 * @retval     0    OK
 * @retval    -1    Error
 * Also, if there is a cligen variable called "xpath" it will override argv xpath arg
 */
int
cli_pagination(clixon_handle h,
               cvec         *cvv,
               cvec         *argv)
{
    int              retval = -1;
    cbuf            *cb = NULL;
    char            *xpath = NULL;
    char            *prefix = NULL;
    char            *namespace = NULL;
    cxobj           *xret = NULL;
    cxobj           *xerr;
    cvec            *nsc = NULL;
    char            *str;
    enum format_enum format;
    cxobj           *xc;
    cg_var          *cv;
    int              i;
    int              j;
    uint32_t         limit = 0;
    cxobj          **xvec = NULL;
    size_t           xlen;
    int              locked = 0;
    int              argc = 0;

    if (cvec_len(argv) != 5){
        clixon_err(OE_PLUGIN, 0, "Expected usage: <xpath> <prefix> <namespace> <format> <limit>");
        goto done;
    }
    /* prefix:variable overrides argv */
    if ((cv = cvec_find(cvv, "xpath")) != NULL)
        xpath = cv_string_get(cv);
    else
        xpath = cvec_i_str(argv, argc);
    argc++;
    prefix = cvec_i_str(argv, argc++);
    namespace = cvec_i_str(argv, argc++);
    if (cli_show_option_format(h, argv, argc++, &format) < 0)
        goto done;
    if ((str = cv_string_get(cvec_i(argv, 4))) != NULL){
        if (parse_uint32(str, &limit, NULL) < 1){
            clixon_err(OE_UNIX, errno, "error parsing limit:%s", str);
            goto done;
        }
    }
    if (limit == 0){
        clixon_err(OE_UNIX, EINVAL, "limit is 0");
        goto done;
    }
    if ((nsc = xml_nsctx_init(prefix, namespace)) == NULL)
        goto done;
    if (clicon_rpc_lock(h, "running") < 0)
        goto done;
    locked++;
    for (i = 0;; i++){
        if (clicon_rpc_get_pageable_list(h, "running", xpath, nsc,
                                         CONTENT_ALL,
                                         -1,       /* depth */
                                         NULL,     /* with-default */
                                         limit*i,  /* offset */
                                         limit,    /* limit */
                                         NULL, NULL, NULL, /* nyi */
                                         &xret) < 0){
            goto done;
        }
        if ((xerr = xpath_first(xret, NULL, "/rpc-error")) != NULL){
            clixon_err_netconf(h, OE_NETCONF, 0, xerr, "Get configuration");
            goto done;
        }
        if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath) < 0)
            goto done;
        for (j = 0; j<xlen; j++){
            xc = xvec[j];
            switch (format){
            case FORMAT_XML:
                if (clixon_xml2file(stdout, xc, 0, 1, NULL, cligen_output, 0, 1) < 0)
                    goto done;
                break;
            case FORMAT_JSON:
                if (clixon_json2file(stdout, xc, 1, cligen_output, 0, 1) < 0)
                    goto done;
                break;
            case FORMAT_TEXT:
                if (clixon_text2file(stdout, xc, 0, cligen_output, 0, 1) < 0)
                    goto done;
                break;
            case FORMAT_CLI:
                /* hardcoded to compress and list-keyword = nokey */
                if (clixon_cli2file(h, stdout, xc, NULL, cligen_output, 0) < 0)
                    goto done;
                break;
            default:
                break;
            }
            if (cli_output_status() < 0)
                break;
        } /* for j */
        if (cli_output_status() < 0)
            break;
        if (xlen != limit) /* Break if fewer elements than requested */
            break;
        if (xret){
            xml_free(xret);
            xret = NULL;
        }
        if (xvec){
            free(xvec);
            xvec = NULL;
        }
    } /* for i */
    retval = 0;
 done:
    if (locked)
        clicon_rpc_unlock(h, "running");
    if (xvec)
        free(xvec);
    if (xret)
        xml_free(xret);
    if (nsc)
        cvec_free(nsc);
    if (cb)
        cbuf_free(cb);
    return retval;
}

/*! Translate to CLI commands in cbuf
 *
 * Howto: join strings and pass them down. 
 * Identify unique/index keywords for correct set syntax.
 * @param[in]     h       Clixon handle
 * @param[in,out] cb      Cligen buffer to write to
 * @param[in]     xn      XML Parse-tree (to translate)
 * @param[in]     prepend Print this text in front of all commands.
 * @retval        0       OK
 * @retval       -1       Error
 * @see clixon_cli2file
 */
static int
cli2cbuf(clixon_handle     h,
         cbuf             *cb,
         cxobj            *xn,
         char             *prepend)
{
    int              retval = -1;
    cxobj           *xe = NULL;
    cbuf            *cbpre = NULL;
    yang_stmt       *ys;
    int              match;
    char            *body;
    int              compress = 0;
    autocli_listkw_t listkw;
    int              exist = 0;
    char            *name;

    if (autocli_list_keyword(h, &listkw) < 0)
        goto done;
    if (xml_type(xn)==CX_ATTR)
        goto ok;
    if ((ys = xml_spec(xn)) == NULL)
        goto ok;
    if (yang_extension_value(ys, "hide-show", CLIXON_AUTOCLI_NS, &exist, NULL) < 0)
        goto done;
    if (exist)
        goto ok;
    exist = 0;
    if (yang_extension_value(ys, "alias", CLIXON_AUTOCLI_NS, &exist, &name) < 0)
        goto done;
    if (!exist)
        name = xml_name(xn);
    /* If leaf/leaf-list or presence container, then print line */
    if (yang_keyword_get(ys) == Y_LEAF ||
        yang_keyword_get(ys) == Y_LEAF_LIST){
        if (prepend)
            cprintf(cb, "%s", prepend);
        if (listkw != AUTOCLI_LISTKW_NONE)
            cprintf(cb, "%s ", name);
        if ((body = xml_body(xn)) != NULL){
            if (index(body, ' '))
                cprintf(cb, "\"%s\"", body);
            else
                cprintf(cb, "%s", body);
        }
        cprintf(cb, "\n");
        goto ok;
    }
    /* Create prepend variable string */
    if ((cbpre = cbuf_new()) == NULL){
        clixon_err(OE_PLUGIN, errno, "cbuf_new");
        goto done;
    }
    if (prepend)
        cprintf(cbpre, "%s", prepend);

    /* If non-presence container && HIDE mode && only child is 
     * a list, then skip container keyword
     * See also yang2cli_container */
    if (autocli_compress(h, ys, &compress) < 0)
        goto done;
    if (!compress)
        cprintf(cbpre, "%s ", xml_name(xn));

    /* If list then first loop through keys */
    if (yang_keyword_get(ys) == Y_LIST){
        xe = NULL;
        while ((xe = xml_child_each(xn, xe, -1)) != NULL){
            if ((match = yang_key_match(ys, xml_name(xe), NULL)) < 0)
                goto done;
            if (!match)
                continue;
            if (listkw == AUTOCLI_LISTKW_ALL)
                cprintf(cbpre, "%s ", xml_name(xe));
            cprintf(cbpre, "%s ", xml_body(xe));
        }
    }
    else if ((yang_keyword_get(ys) == Y_CONTAINER) &&
             yang_find(ys, Y_PRESENCE, NULL) != NULL){
        /* If presence container, then print as leaf (but continue to children) */
        if (prepend)
            cprintf(cb, "%s", prepend);
        if (listkw != AUTOCLI_LISTKW_NONE)
            cprintf(cb, "%s ", xml_name(xn));
        if ((body = xml_body(xn)) != NULL){
            if (index(body, ' '))
                cprintf(cb, "\"%s\"", body);
            else
                cprintf(cb, "%s", body);
        }
        cprintf(cb, "\n");
    }

    /* For lists, print cbpre before its elements */
    if (yang_keyword_get(ys) == Y_LIST)
        cprintf(cb, "%s\n", cbuf_get(cbpre));
    /* Then loop through all other (non-keys) */
    xe = NULL;
    while ((xe = xml_child_each(xn, xe, -1)) != NULL){
        if (yang_keyword_get(ys) == Y_LIST){
            if ((match = yang_key_match(ys, xml_name(xe), NULL)) < 0)
                goto done;
            if (match)
                continue; /* Not key itself */
        }
        if (cli2cbuf(h, cb, xe, cbuf_get(cbpre)) < 0)
            goto done;
    }
 ok:
    retval = 0;
 done:
    if (cbpre)
        cbuf_free(cbpre);
    return retval;
}

/*! Translate from XML to CLI commands, internal
 *
 * Howto: join strings and pass them down. 
 * Identify unique/index keywords for correct set syntax.
 * @param[in] h        Clixon handle
 * @param[in] f        Output FILE (eg stdout)
 * @param[in] xn       XML Parse-tree (to translate)
 * @param[in] prepend  Print this text in front of all commands.
 * @param[in] fn       Callback to make print function
 * @retval    0        OK
 * @retval   -1        Error
 */
static int
cli2file(clixon_handle     h,
         FILE             *f,
         cxobj            *xn,
         char             *prepend,
         clicon_output_cb *fn)
{
    int              retval = -1;
    cxobj           *xe = NULL;
    cbuf            *cbpre = NULL;
    yang_stmt       *ys;
    int              match;
    char            *body;
    int              compress = 0;
    autocli_listkw_t listkw;
    int              exist = 0;
    char            *name;

    if (autocli_list_keyword(h, &listkw) < 0)
        goto done;
    if (xml_type(xn)==CX_ATTR)
        goto ok;
    if ((ys = xml_spec(xn)) == NULL)
        goto ok;
    if (yang_extension_value(ys, "hide-show", CLIXON_AUTOCLI_NS, &exist, NULL) < 0)
        goto done;
    if (exist)
        goto ok;
    exist = 0;
    if (yang_extension_value(ys, "alias", CLIXON_AUTOCLI_NS, &exist, &name) < 0)
        goto done;
    if (!exist)
        name = xml_name(xn);
    /* If leaf/leaf-list or presence container, then print line */
    if (yang_keyword_get(ys) == Y_LEAF ||
        yang_keyword_get(ys) == Y_LEAF_LIST){
        if (prepend)
            (*fn)(f, "%s", prepend);
        if (listkw != AUTOCLI_LISTKW_NONE)
            (*fn)(f, "%s ", name);
        if ((body = xml_body(xn)) != NULL){
            if (index(body, ' '))
                (*fn)(f, "\"%s\"", body);
            else
                (*fn)(f, "%s", body);
        }
        (*fn)(f, "\n");
        goto ok;
    }
    /* Create prepend variable string */
    if ((cbpre = cbuf_new()) == NULL){
        clixon_err(OE_PLUGIN, errno, "cbuf_new");
        goto done;
    }
    if (prepend)
        cprintf(cbpre, "%s", prepend);

    /* If non-presence container && HIDE mode && only child is 
     * a list, then skip container keyword
     * See also yang2cli_container */
    if (autocli_compress(h, ys, &compress) < 0)
        goto done;
    if (!compress)
        cprintf(cbpre, "%s ", xml_name(xn));

    /* If list then first loop through keys */
    if (yang_keyword_get(ys) == Y_LIST){
        xe = NULL;
        while ((xe = xml_child_each(xn, xe, -1)) != NULL){
            if ((match = yang_key_match(ys, xml_name(xe), NULL)) < 0)
                goto done;
            if (!match)
                continue;
            if (listkw == AUTOCLI_LISTKW_ALL)
                cprintf(cbpre, "%s ", xml_name(xe));
            cprintf(cbpre, "%s ", xml_body(xe));
        }
    }
    else if ((yang_keyword_get(ys) == Y_CONTAINER) &&
             yang_find(ys, Y_PRESENCE, NULL) != NULL){
        /* If presence container, then print as leaf (but continue to children) */
        if (prepend)
            (*fn)(f, "%s", prepend);
        if (listkw != AUTOCLI_LISTKW_NONE)
            (*fn)(f, "%s ", xml_name(xn));
        if ((body = xml_body(xn)) != NULL){
            if (index(body, ' '))
                (*fn)(f, "\"%s\"", body);
            else
                (*fn)(f, "%s", body);
        }
        (*fn)(f, "\n");
    }

    /* For lists, print cbpre before its elements */
    if (yang_keyword_get(ys) == Y_LIST)
        (*fn)(f, "%s\n", cbuf_get(cbpre));
    /* Then loop through all other (non-keys) */
    xe = NULL;
    while ((xe = xml_child_each(xn, xe, -1)) != NULL){
        if (yang_keyword_get(ys) == Y_LIST){
            if ((match = yang_key_match(ys, xml_name(xe), NULL)) < 0)
                goto done;
            if (match)
                continue; /* Not key itself */
        }
        if (cli2file(h, f, xe, cbuf_get(cbpre), fn) < 0)
            goto done;
    }
 ok:
    retval = 0;
 done:
    if (cbpre)
        cbuf_free(cbpre);
    return retval;
}

/*! Translate from XML to CLI commands
 *
 * Howto: join strings and pass them down. 
 * Identify unique/index keywords for correct set syntax.
 * @param[in] h        Clixon handle
 * @param[in] f        Output FILE (eg stdout)
 * @param[in] xn       XML Parse-tree (to translate)
 * @param[in] prepend  Print this text in front of all commands.
 * @param[in] fn       File print function (if NULL, use fprintf)
 * @param[in] skiptop  0: Include top object 1: Skip top-object, only children, 
 * @retval    0        OK
 * @retval   -1        Error
 * @see clixon_cli2cbuf
 */
int
clixon_cli2file(clixon_handle     h,
                FILE             *f,
                cxobj            *xn,
                char             *prepend,
                clicon_output_cb *fn,
                int               skiptop)
{
    int   retval = 1;
    cxobj *xc;

    if (fn == NULL)
        fn = fprintf;
    if (skiptop){
        xc = NULL;
        while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL)
            if (cli2file(h, f, xc, prepend, fn) < 0)
                goto done;
    }
    else {
        if (cli2file(h, f, xn, prepend, fn) < 0)
            goto done;
    }
    retval = 0;
 done:
    return retval;
}

/*! Translate from XML to CLI commands
 *
 * Howto: join strings and pass them down. 
 * Identify unique/index keywords for correct set syntax.
 * @param[in] h        Clixon handle
 * @param[in] f        Output FILE (eg stdout)
 * @param[in] xn       XML Parse-tree (to translate)
 * @param[in] prepend  Print this text in front of all commands.
 * @param[in] fn       File print function (if NULL, use fprintf)
 * @param[in] skiptop  0: Include top object 1: Skip top-object, only children, 
 * @retval    0        OK
 * @retval   -1        Error
 * @see clixon_cli2file
 */
int
clixon_cli2cbuf(clixon_handle     h,
                cbuf             *cb,
                cxobj            *xn,
                char             *prepend,
                int               skiptop)
{
    int   retval = 1;
    cxobj *xc;

    if (skiptop){
        xc = NULL;
        while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL)
            if (cli2cbuf(h, cb, xc, prepend) < 0)
                goto done;
    }
    else {
        if (cli2cbuf(h, cb, xn, prepend) < 0)
            goto done;
    }
    retval = 0;
 done:
    return retval;
}

/* Transform uin64 to KiB / MiB / GiB / TiB and postfix
 *
 * @param[in]  inr  Input number
 * @param[out] onr  Translated number
 * @param[out] unit Multiple byte unit: K/M/G/T
 * @retval     0
 */
static int
translatenumber(uint64_t   inr,
                uint64_t  *onr,
                char     **unit)
{
    if (inr/(10*1024) == 0) {
        *onr = inr;
        *unit = "";
    }
    else if (inr/(10LL*1024LL*1024LL*1024LL*1024LL) != 0) {
        *onr = inr/(1024LL*1024LL*1024LL*1024LL);
        *unit = "T";
    }
    else if (inr/(10LL*1024LL*1024LL*1024LL) != 0) {
        *onr = inr/(1024LL*1024LL*1024LL);
        *unit = "G";
    }
    else if (inr/(10LL*1024LL*1024LL) != 0) {
        *onr = inr/(1024LL*1024LL);
        *unit = "M";
    }
    else if (inr/(10LL*1024LL) != 0) {
        *onr = inr/(1024LL);
        *unit = "K";
    }
    else{
        *onr = inr;
        *unit = "";
    }
    return 0;
}

/*! CLI callback show memory statistics (and numbers)
 *
 * mempry in KiB
 * @param[in]  h     Clixon handle
 * @param[in]  cvv   Vector of cli string and instantiated variables
 * @param[in]  argv  Arguments given at the callback: [(cli|backend|all) [detail]]
 * @retval     0     OK
 * @retval    -1     Error
 */
int
cli_show_statistics(clixon_handle h,
                    cvec         *cvv,
                    cvec         *argv)
{
    int         retval = -1;
    cbuf       *cb = NULL;
    cxobj      *xret = NULL;
    cxobj      *xerr;
    char       *what = NULL;
    int         cli = 0;
    int         backend = 0;
    int         detail = 0;
    pt_head    *ph;
    parse_tree *pt;
    uint64_t    nr;
    uint64_t    tnr0;
    uint64_t    tnr;
    size_t      sz;
    size_t      tsz0;
    size_t      tsz;
    yang_stmt  *yspec;
    cvec       *cvv1 = NULL;
    cg_var     *cv;
    cxobj      *xp;
    char       *name;
    cxobj      *x;
    uint64_t    u64;
    char       *unit;
    yang_stmt  *ymounts;
    int         inext;
    int         i;

    if (argv == NULL || (cvec_len(argv) < 1 || cvec_len(argv) > 2)){
        clixon_err(OE_PLUGIN, EINVAL, "Expected arguments: [(cli|backend|all) [detail]]");
        goto done;
    }
    yspec = clicon_dbspec_yang(h);
    cv = cvec_i(argv, 0);
    what = cv_string_get(cv);
    if (strcmp(what, "cli") == 0)
        cli++;
    else if (strcmp(what, "backend") == 0)
        backend++;
    else if (strcmp(what, "all") == 0){
        cli++;
        backend++;
    }
    else {
        clixon_err(OE_PLUGIN, EINVAL, "Unexpected argument: %s, expected: cli|backend|all", what);
        goto done;
    }
    if (cvec_len(argv) > 1 &&
        (cv = cvec_i(argv, 1)) != NULL){
        if (strcmp(cv_string_get(cv), "detail") != 0){
            clixon_err(OE_PLUGIN, EINVAL, "Unexpected argument: %s, expected: detail",
                       cv_string_get(cv));
            goto done;
        }
        detail = 1;
    }
    if ((cb = cbuf_new()) == NULL){
        clixon_err(OE_PLUGIN, errno, "cbuf_new");
        goto done;
    }
    if (cli) {
        if (!detail) {
            cligen_output(stdout, "%-25s %-10s\n", "YANG", "Mem");
        }
        nr = 0; sz = 0;
        if (yang_stats(yspec, 0, &nr, &sz) < 0)
            goto done;
        tnr = nr;
        tsz = sz;
        if (detail) {
            cligen_output(stdout, "YANG-top-level-size: %" PRIu64 "\n", sz);
            cligen_output(stdout, "YANG-top-level-nr: %" PRIu64 "\n", nr);
        }
        else{
            translatenumber(sz, &u64, &unit);
            cligen_output(stdout, "%-25s %" PRIu64 "%-10s\n", "Top-level", u64, unit);
        }
        if (clicon_option_bool(h, "CLICON_YANG_SCHEMA_MOUNT")) {
            if ((ymounts = clixon_yang_mounts_get(h)) == NULL){
                clixon_err(OE_YANG, ENOENT, "Top-level yang mounts not found");
                goto done;
            }
            inext = 0;
            while ((yspec = yn_iter(ymounts, &inext)) != NULL) {
                cvv1 = NULL;
                if (yang_keyword_get(yspec) == Y_SPEC &&
                    yang_flag_get(yspec, YANG_FLAG_SPEC_MOUNT) != 0x0 &&
                    (cvv1 = yang_cvec_get(yspec)) != NULL){
                    nr = 0; sz = 0;
                    if (yang_stats(yspec, 0, &nr, &sz) < 0)
                        goto done;
                    tnr += nr;
                    tsz += sz;
                    i = 0;
                    cv = NULL;
                    while ((cv = cvec_each(cvv1, cv)) != NULL) {
                        if (detail){
                            if (i == cvec_len(cvv1)-1) {
                                cligen_output(stdout, "YANG-mount-point-%s-size: %"  PRIu64 "\n", cv_name_get(cv), sz);
                                cligen_output(stdout, "YANG-mount-point-%s-nr: %"  PRIu64 "\n", cv_name_get(cv), nr);
                            }
                            else {
                                cligen_output(stdout, "YANG-mount-point-%s-size: shared\n", cv_name_get(cv));
                                cligen_output(stdout, "YANG-mount-point-%s-nr: shared\n", cv_name_get(cv));
                            }
                        }
                        else {
                            if (i == cvec_len(cvv1)-1) {
                                if (strlen(cv_name_get(cv)) > 25)
                                    cligen_output(stdout, "%s \n %-25s", cv_name_get(cv), "");
                                else
                                    cligen_output(stdout, "%-25s", cv_name_get(cv));
                                translatenumber(sz, &u64, &unit);
                                cligen_output(stdout, "%" PRIu64 "%-10s\n", u64, unit);
                            }
                            else
                                cligen_output(stdout, "%s\n", cv_name_get(cv));
                        }
                        i++;
                    }
                }
            }
        }
        if (detail){
            cligen_output(stdout, "YANG-total-size: %" PRIu64 "\n", sz);
            cligen_output(stdout, "YANG-total-nr: %" PRIu64 "\n", sz);
        }
        else {
            translatenumber(tsz, &u64, &unit);
            cligen_output(stdout, "%-25s %" PRIu64 "%-10s\n", "YANG Total", u64, unit);
        }
        tnr0 = tnr;
        tsz0 = tsz;
        tnr = 0;
        tsz = 0;
        ph = NULL;
        while ((ph = cligen_ph_each(cli_cligen(h), ph)) != NULL) {
            if ((pt = cligen_ph_parsetree_get(ph)) == NULL)
                continue;
            nr = 0; sz = 0;
            pt_stats(pt, &nr, &sz);
            tnr += nr;
            tsz += sz;
            if (detail){
                cligen_output(stdout, "CLIspec-%s-size: %" PRIu64 "\n", cligen_ph_name_get(ph), sz);
                cligen_output(stdout, "CLIspec-%s-nr: %" PRIu64 "\n", cligen_ph_name_get(ph), nr);
            }
        }
        if (detail){
            cligen_output(stdout, "CLIspec-total-size: %" PRIu64 "\n", tsz);
            cligen_output(stdout, "CLIspec-total-nr: %" PRIu64 "\n", tnr);
            cligen_output(stdout, "Mem-Total-size: %" PRIu64 "\n", tsz0+tsz);
            cligen_output(stdout, "Mem-Total-nr: %" PRIu64 "\n", tnr0+tnr);
        }
        else {
            translatenumber(tsz, &u64, &unit);
            cligen_output(stdout, "%-25s %" PRIu64 "%-10s\n", "CLIspec Total", u64, unit);
            translatenumber(tsz0+tsz, &u64, &unit);
            cligen_output(stdout, "%-25s %" PRIu64 "%-10s\n", "Mem Total", u64, unit);
        }
    }
    if (backend) {
        cprintf(cb, "<rpc xmlns=\"%s\"", NETCONF_BASE_NAMESPACE);
        cprintf(cb, " %s", NETCONF_MESSAGE_ID_ATTR); /* XXX: use incrementing sequence */
        cprintf(cb, ">");
        cprintf(cb, "<stats xmlns=\"%s\">", CLIXON_LIB_NS);
        if (detail)
            cprintf(cb, "<modules>true</modules>");
        cprintf(cb, "</stats>");
        cprintf(cb, "</rpc>");
        if (clicon_rpc_netconf(h, cbuf_get(cb), &xret, NULL) < 0)
            goto done;
        if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
            clixon_err_netconf(h, OE_NETCONF, 0, xerr, "Get configuration");
            goto done;
        }
        if (xml_rootchild(xret, 0, &xret) < 0)
            goto done;
        if (detail) {
            cligen_output(stdout, "Backend (nr nodes, size of mem\n");
            if (clixon_xml2file(stdout, xret, 0, 1, NULL, cligen_output, 0, 1) < 0)
                goto done;
        }
        else {
            cligen_output(stdout, "%-25s %-10s\n", "XML Datastore", "Mem");
            tsz = 0;
            if ((xp = xml_find_type(xret, NULL, "datastores", CX_ELMNT)) != NULL){
                x = NULL;
                while ((x = xml_child_each(xp, x, CX_ELMNT)) != NULL) {
                    if (strcmp(xml_name(x), "datastore") != 0)
                        continue;
                    parse_uint64(xml_find_body(x, "size"), &sz, NULL);
                    tsz += sz;
                    name = xml_find_body(x, "name");
                    translatenumber(sz, &u64, &unit);
                    if (strlen(name) > 25){
                        cligen_output(stdout, "%s \\\n", name);
                        cligen_output(stdout, "%-25s %" PRIu64 "%-10s\n", "", u64, unit);
                    }
                    else{
                        cligen_output(stdout, "%-25s %" PRIu64 "%-10s\n", name, u64, unit);
                    }
                }
            }
            translatenumber(tsz, &u64, &unit);
            cligen_output(stdout, "%-25s %" PRIu64 "%-10s\n", "XML Total", u64, unit);
            tsz0 = tsz;
            tsz = 0;
            cligen_output(stdout, "%s\n", "YANG");
            if ((xp = xml_find_type(xret, NULL, "module-sets", CX_ELMNT)) != NULL){
                x = NULL;
                while ((x = xml_child_each(xp, x, CX_ELMNT)) != NULL) {
                    if (strcmp(xml_name(x), "module-set") != 0)
                        continue;
                    parse_uint64(xml_find_body(x, "size"), &sz, NULL);
                    tsz += sz;
                    name = xml_find_body(x, "name");
                    translatenumber(sz, &u64, &unit);
                    if (strlen(name) > 25){
                        cligen_output(stdout, "%s\n", name);
                        if (sz != 0){
                            cligen_output(stdout, "%-25s %" PRIu64 "%-10s\n", "", u64, unit);
                        }
                    }
                    else{
                        cligen_output(stdout, "%-25s %" PRIu64 "%-10s\n", name, u64, unit);
                    }
                }
            }
            translatenumber(tsz, &u64, &unit);
            cligen_output(stdout, "%-25s %" PRIu64 "%-10s\n", "YANG Total", u64, unit);
            translatenumber(tsz0+tsz, &u64, &unit);
            cligen_output(stdout, "%-25s %" PRIu64 "%-10s\n", "Mem Total", u64, unit);
        }
    }
    retval = 0;
 done:
    if (xret)
        xml_free(xret);
    if (cb)
        cbuf_free(cb);
    return retval;
}

/*! CLI set default output format
 *
 * @param[in]  h    Clixon handle
 * @param[in]  cvv  Vector of cli string and instantiated variables, expected: 1: format
 * @param[in]  argv Vector, expected NULL
 * @retval     0    OK
 * @retval    -1    Error
 * Format of argv:
 *   <api-path-fmt> Generated
 */
int
cli_format_set(clixon_handle h,
               cvec         *cvv,
               cvec         *argv)
{
    int              retval = -1;
    cg_var          *cv;
    char            *str;
    enum format_enum fmt = FORMAT_XML;

    if ((cv = cvec_find(cvv, "fmt")) == NULL){
        clixon_err(OE_PLUGIN, EINVAL, "Requires one variable to be <format>");
        goto done;
    }
    str = cv_string_get(cv);
    if ((fmt = format_str2int(str)) < 0){
        clixon_err(OE_PLUGIN, EINVAL, "Invalid format: %s", str);
        goto done;
    }
    /* Alt make a int option/data */
    retval = clicon_option_str_set(h, "CLICON_CLI_OUTPUT_FORMAT", str);
 done:
    return retval;
}

/*! CLI set default output format
 *
 * @param[in]  h    Clixon handle
 * @param[in]  cvv  Vector of cli string and instantiated variables, expected: 1: format
 * @param[in]  argv Vector, expected NULL
 * @retval     0    OK
 * @retval    -1    Error
 * Format of argv:
 *   <api-path-fmt> Generated
 */
int
cli_format_show(clixon_handle h,
               cvec         *cvv,
               cvec         *argv)
{
    char *str;

    str = clicon_option_str(h, "CLICON_CLI_OUTPUT_FORMAT");
    cligen_output(stderr, "%s\n", str);
    return 0;
}
