yanglint FEATURE initial version of yanglint(1)

Source codes are mostly taken from libyang 1.0 and slightly modified for
libyang 2.0. A lot of functionality is still missing since it is not yet
provided by libyang 2.0
diff --git a/tools/lint/main_ni.c b/tools/lint/main_ni.c
new file mode 100644
index 0000000..892d242
--- /dev/null
+++ b/tools/lint/main_ni.c
@@ -0,0 +1,1099 @@
+/**
+ * @file main_ni.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief libyang's yanglint tool - noninteractive code
+ *
+ * Copyright (c) 2015-2018 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <errno.h>
+#include <libgen.h>
+#include <sys/stat.h>
+#include <sys/times.h>
+#include <sys/types.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "commands.h"
+#include "libyang.h"
+
+volatile uint8_t verbose = 0;
+
+#if 0
+/* from commands.c */
+int print_list(FILE *out, struct ly_ctx *ctx, LYD_FORMAT outformat);
+#endif
+
+void
+help(int shortout)
+{
+    fprintf(stdout, "Usage:\n");
+    fprintf(stdout, "    yanglint [options] [-f { yang | yin | tree | tree-rfc | jsons}] <file>...\n");
+    fprintf(stdout, "        Validates the YANG module in <file>, and all its dependencies.\n\n");
+    fprintf(stdout, "    yanglint [options] [-f { xml | json }] <schema>... <file>...\n");
+    fprintf(stdout, "        Validates the YANG modeled data in <file> according to the <schema>.\n\n");
+    fprintf(stdout, "    yanglint\n");
+    fprintf(stdout, "        Starts interactive mode with more features.\n\n");
+
+    if (shortout) {
+        return;
+    }
+    fprintf(stdout, "Options:\n"
+        "  -h, --help            Show this help message and exit.\n"
+        "  -v, --version         Show version number and exit.\n"
+        "  -V, --verbose         Show verbose messages, can be used multiple times to\n"
+        "                        increase verbosity.\n"
+#ifndef NDEBUG
+        "  -G GROUPS, --debug=GROUPS\n"
+        "                        Enable printing of specific debugging message group\n"
+        "                        (nothing will be printed unless verbosity is set to debug):\n"
+        "                        <group>[,<group>]* (dict, yang, yin, xpath, diff)\n\n"
+#endif
+        "  -p PATH, --path=PATH  Search path for schema (YANG/YIN) modules. The option can be used multiple times.\n"
+        "                        Current working directory and path of the module being added is used implicitly.\n\n"
+        "  -D, --disable-searchdir\n"
+        "                        Do not implicitly search in CWD for schema modules. If specified a second time,\n"
+        "                        do not even search the module directory (all modules must be explicitly specified).\n\n"
+        "  -s, --strict          Strict data parsing (do not skip unknown data),\n"
+        "                        has no effect for schemas.\n\n"
+        "  -m, --merge           Merge input data files into a single tree and validate at once,\n"
+        "                        has no effect for the auto, rpc, rpcreply and notif TYPEs.\n\n"
+        "  -f FORMAT, --format=FORMAT\n"
+        "                        Convert to FORMAT. Supported formats: \n"
+        "                        yang, yin, tree and jsons (JSON) for schemas,\n"
+        "                        xml, json for data.\n"
+        "  -a, --auto            Modify the xml output by adding envelopes for autodetection.\n\n"
+        "  -i, --allimplemented  Make all the imported modules implemented.\n\n"
+        "  -l, --list            Print info about the loaded schemas in ietf-yang-library format,\n"
+        "                        the -f option applies here to specify data encoding.\n"
+        "                        (i - imported module, I - implemented module)\n\n"
+        "  -o OUTFILE, --output=OUTFILE\n"
+        "                        Write the output to OUTFILE instead of stdout.\n\n"
+        "  -F FEATURES, --features=FEATURES\n"
+        "                        Features to support, default all.\n"
+        "                        <modname>:[<feature>,]*\n\n"
+        "  -d MODE, --default=MODE\n"
+        "                        Print data with default values, according to the MODE\n"
+        "                        (to print attributes, ietf-netconf-with-defaults model\n"
+        "                        must be loaded):\n"
+        "        all             - Add missing default nodes.\n"
+        "        all-tagged      - Add missing default nodes and mark all the default\n"
+        "                          nodes with the attribute.\n"
+        "        trim            - Remove all nodes with a default value.\n"
+        "        implicit-tagged - Add missing nodes and mark them with the attribute.\n\n"
+        "  -t TYPE, --type=TYPE\n"
+        "                        Specify data tree type in the input data file:\n"
+        "        auto            - Resolve data type (one of the following) automatically\n"
+        "                          (as pyang does) - applicable only on XML input data.\n"
+        "        data            - Complete datastore with status data (default type).\n"
+        "        config          - Configuration datastore (without status data).\n"
+        "        get             - Result of the NETCONF <get> operation.\n"
+        "        getconfig       - Result of the NETCONF <get-config> operation.\n"
+        "        edit            - Content of the NETCONF <edit-config> operation.\n"
+        "        rpc             - Content of the NETCONF <rpc> message, defined as YANG's rpc input statement.\n"
+        "        rpcreply        - Reply to the RPC. The input data <file>s are expected in pairs - each RPC reply\n"
+        "                          input data <file> must be followed by the origin RPC input data <file> for the reply.\n"
+        "                          The same rule of pairing applies also in case of 'auto' TYPE and input data file\n"
+        "                          containing RPC reply.\n"
+        "        notif           - Notification instance (content of the <notification> element without <eventTime>.\n\n"
+        "  -O FILE, --operational=FILE\n"
+        "                        - Optional parameter for 'rpc', 'rpcreply' and 'notif' TYPEs, the FILE contains running\n"
+        "                          configuration datastore and state data (operational datastore) referenced from\n"
+        "                          the RPC/Notification. The same data apply to all input data <file>s. Note that\n"
+        "                          the file is validated as 'data' TYPE. Special value '!' can be used as FILE argument\n"
+        "                          to ignore the external references.\n\n"
+        "  -y YANGLIB_PATH       - Path to a yang-library data describing the initial context.\n\n"
+        "Tree output specific options:\n"
+        "  --tree-help           - Print help on tree symbols and exit.\n"
+        "  --tree-print-groupings\n"
+        "                        Print top-level groupings in a separate section.\n"
+        "  --tree-print-uses     - Print uses nodes instead the resolved grouping nodes.\n"
+        "  --tree-no-leafref-target\n"
+        "                        Do not print target nodes of leafrefs.\n"
+        "  --tree-path=SCHEMA_PATH\n"
+        "                        Print only the specified subtree.\n"
+        "  --tree-line-length=LINE_LENGTH\n"
+        "                        Wrap lines if longer than the specified length (it is not a strict limit, longer lines\n"
+        "                        can often appear).\n"
+        "\n");
+}
+
+void
+tree_help(void)
+{
+    fprintf(stdout, "Each node is printed as:\n\n");
+    fprintf(stdout, "<status> <flags> <name> <opts> <type> <if-features>\n\n"
+                    "  <status> is one of:\n"
+                    "    + for current\n"
+                    "    x for deprecated\n"
+                    "    o for obsolete\n\n"
+                    "  <flags> is one of:\n"
+                    "    rw for configuration data\n"
+                    "    ro for status data\n"
+                    "    -x for RPCs\n"
+                    "    -n for Notification\n\n"
+                    "  <name> is the name of the node\n"
+                    "    (<name>) means that the node is a choice node\n"
+                    "    :(<name>) means that the node is a case node\n\n"
+                    "    if the node is augmented into the tree from another module,\n"
+                    "    it is printed with the module name as <module-name>:<name>.\n\n"
+                    "  <opts> is one of:\n"
+                    "    ? for an optional leaf or choice\n"
+                    "    ! for a presence container\n"
+                    "    * for a leaf-list or list\n"
+                    "    [<keys>] for a list's keys\n\n"
+                    "  <type> is the name of the type for leafs and leaf-lists\n"
+                    "    If there is a default value defined, it is printed within\n"
+                    "    angle brackets <default-value>.\n"
+                    "    If the type is a leafref, the type is printed as -> TARGET`\n\n"
+                    "  <if-features> is the list of features this node depends on,\n"
+                    "    printed within curly brackets and a question mark {...}?\n\n");
+}
+
+void
+version(void)
+{
+    fprintf(stdout, "yanglint %s\n", PROJECT_VERSION);
+}
+
+void
+libyang_verbclb(LY_LOG_LEVEL level, const char *msg, const char *path)
+{
+    char *levstr;
+
+    if (level <= verbose) {
+        switch(level) {
+        case LY_LLERR:
+            levstr = "err :";
+            break;
+        case LY_LLWRN:
+            levstr = "warn:";
+            break;
+        case LY_LLVRB:
+            levstr = "verb:";
+            break;
+        default:
+            levstr = "dbg :";
+            break;
+        }
+        if (path) {
+            fprintf(stderr, "%s %s (%s)\n", levstr, msg, path);
+        } else {
+            fprintf(stderr, "%s %s\n", levstr, msg);
+        }
+    }
+}
+
+/*
+ * return:
+ * 0 - error
+ * 1 - schema format
+ * 2 - data format
+ */
+static int
+get_fileformat(const char *filename, LYS_INFORMAT *schema/* TODO , LYD_FORMAT *data */)
+{
+    char *ptr;
+    LYS_INFORMAT informat_s;
+#if 0
+    LYD_FORMAT informat_d;
+#endif
+    /* get the file format */
+    if ((ptr = strrchr(filename, '.')) != NULL) {
+        ++ptr;
+        if (!strcmp(ptr, "yang")) {
+            informat_s = LYS_IN_YANG;
+#if 0
+            informat_d = 0;
+        } else if (!strcmp(ptr, "yin")) {
+            informat_s = LYS_IN_YIN;
+            informat_d = 0;
+        } else if (!strcmp(ptr, "xml")) {
+            informat_s = 0;
+            informat_d = LYD_XML;
+        } else if (!strcmp(ptr, "json")) {
+            informat_s = 0;
+            informat_d = LYD_JSON;
+#endif
+        } else {
+            fprintf(stderr, "yanglint error: input file in an unknown format \"%s\".\n", ptr);
+            return 0;
+        }
+    } else {
+        fprintf(stderr, "yanglint error: input file \"%s\" without file extension - unknown format.\n", filename);
+        return 0;
+    }
+#if 0
+    if (data) {
+        (*data) = informat_d;
+    }
+#endif
+    if (schema) {
+        (*schema) = informat_s;
+    }
+
+    if (informat_s) {
+        return 1;
+    } else {
+        return 2;
+    }
+}
+
+int
+main_ni(int argc, char* argv[])
+{
+    int ret = EXIT_FAILURE;
+    int opt, opt_index = 0, i, featsize = 0;
+    struct option options[] = {
+#if 0
+        {"auto",             no_argument,       NULL, 'a'},
+        {"default",          required_argument, NULL, 'd'},
+#endif
+        {"format",           required_argument, NULL, 'f'},
+        {"features",         required_argument, NULL, 'F'},
+#if 0
+        {"tree-print-groupings", no_argument,   NULL, 'g'},
+        {"tree-print-uses",  no_argument,       NULL, 'u'},
+        {"tree-no-leafref-target", no_argument, NULL, 'n'},
+        {"tree-path",        required_argument, NULL, 'P'},
+        {"tree-line-length", required_argument, NULL, 'L'},
+#endif
+        {"help",             no_argument,       NULL, 'h'},
+#if 0
+        {"tree-help",        no_argument,       NULL, 'H'},
+#endif
+        {"allimplemented",   no_argument,       NULL, 'i'},
+        {"disable-cwd-search", no_argument,     NULL, 'D'},
+        {"list",             no_argument,       NULL, 'l'},
+#if 0
+        {"merge",            no_argument,       NULL, 'm'},
+#endif
+        {"output",           required_argument, NULL, 'o'},
+        {"path",             required_argument, NULL, 'p'},
+#if 0
+        {"running",          required_argument, NULL, 'r'},
+        {"operational",      required_argument, NULL, 'O'},
+        {"strict",           no_argument,       NULL, 's'},
+        {"type",             required_argument, NULL, 't'},
+#endif
+        {"version",          no_argument,       NULL, 'v'},
+        {"verbose",          no_argument,       NULL, 'V'},
+#ifndef NDEBUG
+        {"debug",            required_argument, NULL, 'G'},
+#endif
+        {NULL,               required_argument, NULL, 'y'},
+        {NULL,               0,                 NULL, 0}
+    };
+    FILE *out = stdout;
+    struct ly_ctx *ctx = NULL;
+    const struct lys_module *mod;
+    LYS_OUTFORMAT outformat_s = 0;
+    LYS_INFORMAT informat_s;
+#if 0
+    LYD_FORMAT informat_d, outformat_d = 0, ylformat = 0;
+#endif
+    struct ly_set *searchpaths = NULL;
+    const char *outtarget_s = NULL;
+    char **feat = NULL, *ptr, *featlist, *dir;
+    struct stat st;
+    uint32_t u;
+    int options_ctx = LY_CTX_NOYANGLIBRARY, list = 0, outoptions_s = 0, outline_length_s = 0;
+#if 0
+    const char *oper_file = NULL, *envelope_s = NULL;
+    char *ylpath = NULL;
+    int options_dflt = 0, options_parser = 0, envelope = 0, autodetection = 0, merge = 0;
+    struct dataitem {
+        const char *filename;
+        struct lyxml_elem *xml;
+        struct lyd_node *tree;
+        struct dataitem *next;
+        LYD_FORMAT format;
+        int type;
+    } *data = NULL, *data_item, *data_prev = NULL;
+    struct lyxml_elem *iter, *elem;
+    struct lyd_node *oper = NULL, *subroot, *next, *node;
+#endif
+    struct ly_set *mods = NULL;
+    void *p;
+    int index = 0;
+
+    opterr = 0;
+#ifndef NDEBUG
+    while ((opt = getopt_long(argc, argv, "ad:f:F:gunP:L:hHiDlmo:p:r:O:st:vVG:y:", options, &opt_index)) != -1)
+#else
+    while ((opt = getopt_long(argc, argv, "ad:f:F:gunP:L:hHiDlmo:p:r:O:st:vVy:", options, &opt_index)) != -1)
+#endif
+    {
+        switch (opt) {
+#if 0
+        case 'a':
+            envelope = 1;
+            break;
+        case 'd':
+            if (!strcmp(optarg, "all")) {
+                options_dflt = (options_dflt & ~LYP_WD_MASK) | LYP_WD_ALL;
+            } else if (!strcmp(optarg, "all-tagged")) {
+                options_dflt = (options_dflt & ~LYP_WD_MASK) | LYP_WD_ALL_TAG;
+            } else if (!strcmp(optarg, "trim")) {
+                options_dflt = (options_dflt & ~LYP_WD_MASK) | LYP_WD_TRIM;
+            } else if (!strcmp(optarg, "implicit-tagged")) {
+                options_dflt = (options_dflt & ~LYP_WD_MASK) | LYP_WD_IMPL_TAG;
+            } else {
+                fprintf(stderr, "yanglint error: unknown default mode %s\n", optarg);
+                help(1);
+                goto cleanup;
+            }
+            break;
+#endif
+        case 'f':
+            if (!strcasecmp(optarg, "yang")) {
+                outformat_s = LYS_OUT_YANG;
+#if 0
+                outformat_d = 0;
+            } else if (!strcasecmp(optarg, "tree")) {
+                outformat_s = LYS_OUT_TREE;
+                outformat_d = 0;
+            } else if (!strcasecmp(optarg, "tree-rfc")) {
+                outformat_s = LYS_OUT_TREE;
+                outoptions_s |= LYS_OUTOPT_TREE_RFC;
+                outformat_d = 0;
+            } else if (!strcasecmp(optarg, "yin")) {
+                outformat_s = LYS_OUT_YIN;
+                outformat_d = 0;
+            } else if (!strcasecmp(optarg, "jsons")) {
+                outformat_s = LYS_OUT_JSON;
+                outformat_d = 0;
+            } else if (!strcasecmp(optarg, "xml")) {
+                outformat_s = 0;
+                outformat_d = LYD_XML;
+            } else if (!strcasecmp(optarg, "json")) {
+                outformat_s = 0;
+                outformat_d = LYD_JSON;
+#endif
+            } else {
+                fprintf(stderr, "yanglint error: unknown output format %s\n", optarg);
+                help(1);
+                goto cleanup;
+            }
+            break;
+        case 'F':
+            featsize++;
+            if (!feat) {
+                p = malloc(sizeof *feat);
+            } else {
+                p = realloc(feat, featsize * sizeof *feat);
+            }
+            if (!p) {
+                fprintf(stderr, "yanglint error: Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
+                goto cleanup;
+            }
+            feat = p;
+            feat[featsize - 1] = strdup(optarg);
+            ptr = strchr(feat[featsize - 1], ':');
+            if (!ptr) {
+                fprintf(stderr, "yanglint error: Invalid format of the features specification (%s)", optarg);
+                goto cleanup;
+            }
+            *ptr = '\0';
+
+            break;
+#if 0
+        case 'g':
+            outoptions_s |= LYS_OUTOPT_TREE_GROUPING;
+            break;
+        case 'u':
+            outoptions_s |= LYS_OUTOPT_TREE_USES;
+            break;
+        case 'n':
+            outoptions_s |= LYS_OUTOPT_TREE_NO_LEAFREF;
+            break;
+        case 'P':
+            outtarget_s = optarg;
+            break;
+        case 'L':
+            outline_length_s = atoi(optarg);
+            break;
+#endif
+        case 'h':
+            help(0);
+            ret = EXIT_SUCCESS;
+            goto cleanup;
+#if 0
+        case 'H':
+            tree_help();
+            ret = EXIT_SUCCESS;
+            goto cleanup;
+#endif
+        case 'i':
+            options_ctx |= LY_CTX_ALLIMPLEMENTED;
+            break;
+        case 'D':
+            if (options_ctx & LY_CTX_DISABLE_SEARCHDIRS) {
+                fprintf(stderr, "yanglint error: -D specified too many times.\n");
+                goto cleanup;
+            } else if (options_ctx & LY_CTX_DISABLE_SEARCHDIR_CWD) {
+                options_ctx &= ~LY_CTX_DISABLE_SEARCHDIR_CWD;
+                options_ctx |= LY_CTX_DISABLE_SEARCHDIRS;
+            } else {
+                options_ctx |= LY_CTX_DISABLE_SEARCHDIR_CWD;
+            }
+            break;
+        case 'l':
+            list = 1;
+            break;
+#if 0
+        case 'm':
+            merge = 1;
+            break;
+#endif
+        case 'o':
+            if (out != stdout) {
+                fclose(out);
+            }
+            out = fopen(optarg, "w");
+            if (!out) {
+                fprintf(stderr, "yanglint error: unable open output file %s (%s)\n", optarg, strerror(errno));
+                goto cleanup;
+            }
+            break;
+        case 'p':
+            if (stat(optarg, &st) == -1) {
+                fprintf(stderr, "yanglint error: Unable to use search path (%s) - %s.\n", optarg, strerror(errno));
+                goto cleanup;
+            }
+            if (!S_ISDIR(st.st_mode)) {
+                fprintf(stderr, "yanglint error: Provided search path is not a directory.\n");
+                goto cleanup;
+            }
+            if (!searchpaths) {
+                searchpaths = ly_set_new();
+            }
+            ly_set_add(searchpaths, optarg, 0);
+            break;
+#if 0
+        case 'r':
+        case 'O':
+            if (oper_file || (options_parser & LYD_OPT_NOEXTDEPS)) {
+                fprintf(stderr, "yanglint error: The operational datastore (-O) cannot be set multiple times.\n");
+                goto cleanup;
+            }
+            if (optarg[0] == '!') {
+                /* ignore extenral dependencies to the operational datastore */
+                options_parser |= LYD_OPT_NOEXTDEPS;
+            } else {
+                /* external file with the operational datastore */
+                oper_file = optarg;
+            }
+            break;
+        case 's':
+            options_parser |= LYD_OPT_STRICT;
+            break;
+        case 't':
+            if (!strcmp(optarg, "auto")) {
+                options_parser = (options_parser & ~LYD_OPT_TYPEMASK);
+                autodetection = 1;
+            } else if (!strcmp(optarg, "config")) {
+                options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_CONFIG;
+            } else if (!strcmp(optarg, "get")) {
+                options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_GET;
+            } else if (!strcmp(optarg, "getconfig")) {
+                options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_GETCONFIG;
+            } else if (!strcmp(optarg, "edit")) {
+                options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_EDIT;
+            } else if (!strcmp(optarg, "data")) {
+                options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_DATA_NO_YANGLIB;
+            } else if (!strcmp(optarg, "rpc")) {
+                options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPC;
+            } else if (!strcmp(optarg, "rpcreply")) {
+                options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPCREPLY;
+            } else if (!strcmp(optarg, "notif")) {
+                options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_NOTIF;
+            } else {
+                fprintf(stderr, "yanglint error: unknown data tree type %s\n", optarg);
+                help(1);
+                goto cleanup;
+            }
+            break;
+#endif
+        case 'v':
+            version();
+            ret = EXIT_SUCCESS;
+            goto cleanup;
+        case 'V':
+            verbose++;
+            break;
+#ifndef NDEBUG
+        case 'G':
+            u = 0;
+            ptr = optarg;
+            while (ptr[0]) {
+                if (!strncmp(ptr, "dict", 4)) {
+                    u |= LY_LDGDICT;
+                    ptr += 4;
+                } else if (!strncmp(ptr, "yang", 4)) {
+                    u |= LY_LDGYANG;
+                    ptr += 4;
+                } else if (!strncmp(ptr, "yin", 3)) {
+                    u |= LY_LDGYIN;
+                    ptr += 3;
+                } else if (!strncmp(ptr, "xpath", 5)) {
+                    u |= LY_LDGXPATH;
+                    ptr += 5;
+                } else if (!strncmp(ptr, "diff", 4)) {
+                    u |= LY_LDGDIFF;
+                    ptr += 4;
+                }
+
+                if (ptr[0]) {
+                    if (ptr[0] != ',') {
+                        fprintf(stderr, "yanglint error: unknown debug group string \"%s\"\n", optarg);
+                        goto cleanup;
+                    }
+                    ++ptr;
+                }
+            }
+            ly_verb_dbg(u);
+            break;
+#endif
+#if 0
+        case 'y':
+            ptr = strrchr(optarg, '.');
+            if (ptr) {
+                ptr++;
+                if (!strcmp(ptr, "xml")) {
+                    ylformat = LYD_XML;
+                } else if (!strcmp(ptr, "json")) {
+                    ylformat = LYD_JSON;
+                } else {
+                    fprintf(stderr, "yanglint error: yang-library file in an unknown format \"%s\".\n", ptr);
+                    goto cleanup;
+                }
+            } else {
+                fprintf(stderr, "yanglint error: yang-library file in an unknown format.\n");
+                goto cleanup;
+            }
+            ylpath = optarg;
+            break;
+#endif
+        default:
+            help(1);
+            if (optopt) {
+                fprintf(stderr, "yanglint error: invalid option: -%c\n", optopt);
+            } else {
+                fprintf(stderr, "yanglint error: invalid option: %s\n", argv[optind - 1]);
+            }
+            goto cleanup;
+        }
+    }
+
+    /* check options compatibility */
+    if (!list && optind >= argc) {
+        help(1);
+        fprintf(stderr, "yanglint error: missing <file> to process\n");
+        goto cleanup;
+    }
+    if (outformat_s && outformat_s != LYS_OUT_TREE && (optind + 1) < argc) {
+        /* we have multiple schemas to be printed as YIN or YANG */
+        fprintf(stderr, "yanglint error: too many schemas to convert and store.\n");
+        goto cleanup;
+    }
+    if (outoptions_s || outtarget_s || outline_length_s) {
+#if 0
+        if (outformat_d || (outformat_s && outformat_s != LYS_OUT_TREE)) {
+            /* we have --tree-print-grouping with other output format than tree */
+            fprintf(stderr,
+                    "yanglint warning: --tree options take effect only in case of the tree output format.\n");
+        }
+    }
+    if (merge) {
+        if (autodetection || (options_parser & (LYD_OPT_RPC | LYD_OPT_RPCREPLY | LYD_OPT_NOTIF))) {
+            fprintf(stderr, "yanglint warning: merging not allowed, ignoring option -m.\n");
+            merge = 0;
+        } else {
+            /* first, files will be parsed as trusted to allow missing data, then the data trees will be merged
+             * and the result will be validated */
+            options_parser |= LYD_OPT_TRUSTED;
+        }
+#endif
+    }
+#if 0
+    if (!outformat_d && options_dflt) {
+        /* we have options for printing default nodes, but data output not specified */
+        fprintf(stderr, "yanglint warning: default mode is ignored when not printing data.\n");
+    }
+    if (outformat_s && (options_parser || autodetection)) {
+        /* we have options for printing data tree, but output is schema */
+        fprintf(stderr, "yanglint warning: data parser options are ignored when printing schema.\n");
+    }
+    if (oper_file && (!autodetection && !(options_parser & (LYD_OPT_RPC | LYD_OPT_RPCREPLY | LYD_OPT_NOTIF)))) {
+        fprintf(stderr, "yanglint warning: operational datastore applies only to RPCs or Notifications.\n");
+        /* ignore operational datastore file */
+        oper_file = NULL;
+    }
+    if ((options_parser & LYD_OPT_TYPEMASK) == LYD_OPT_DATA) {
+        /* add option to ignore ietf-yang-library data for implicit data type */
+        options_parser |= LYD_OPT_DATA_NO_YANGLIB;
+    }
+#endif
+
+    /* set callback for printing libyang messages */
+    ly_set_log_clb(libyang_verbclb, 1);
+#if 0
+    /* create libyang context */
+    if (ylpath) {
+        ctx = ly_ctx_new_ylpath(searchpaths ? (const char*)searchpaths->set.g[0] : NULL, ylpath, ylformat, options_ctx);
+    } else {
+#else
+    {
+#endif
+        ly_ctx_new(NULL, options_ctx, &ctx);
+    }
+    if (!ctx) {
+        goto cleanup;
+    }
+
+    /* set searchpaths */
+    if (searchpaths) {
+        for (u = 0; u < searchpaths->count; u++) {
+            ly_ctx_set_searchdir(ctx, (const char*)searchpaths->objs[u]);
+        }
+        index = u + 1;
+    }
+
+    /* derefered setting of verbosity in libyang after context initiation */
+    ly_verb(verbose);
+
+    mods = ly_set_new();
+
+
+    /* divide input files */
+    for (i = 0; i < argc - optind; i++) {
+        /* get the file format */
+        if (!get_fileformat(argv[optind + i], &informat_s/* TODO, &informat_d */)) {
+            goto cleanup;
+        }
+
+        if (informat_s) {
+            /* load/validate schema */
+            if (verbose >= 2) {
+                fprintf(stdout, "Validating %s schema file.\n", argv[optind + i]);
+            }
+            dir = strdup(argv[optind + i]);
+            ly_ctx_set_searchdir(ctx, ptr = dirname(dir));
+            mod = lys_parse_path(ctx, argv[optind + i], informat_s);
+            ly_ctx_unset_searchdir(ctx, index);
+            free(dir);
+            if (!mod) {
+                goto cleanup;
+            }
+            ly_set_add(mods, (void *)mod, 0);
+#if 0
+        } else {
+            if (autodetection && informat_d != LYD_XML) {
+                /* data file content autodetection is possible only for XML input */
+                fprintf(stderr, "yanglint error: data type autodetection is applicable only to XML files.\n");
+                goto cleanup;
+            }
+
+            /* remember data filename and its format */
+            if (!data) {
+                data = data_item = malloc(sizeof *data);
+            } else {
+                for (data_item = data; data_item->next; data_item = data_item->next);
+                data_item->next = malloc(sizeof *data_item);
+                data_item = data_item->next;
+            }
+            data_item->filename = argv[optind + i];
+            data_item->format = informat_d;
+            data_item->type = options_parser & LYD_OPT_TYPEMASK;
+            data_item->tree = NULL;
+            data_item->xml = NULL;
+            data_item->next = NULL;
+#endif
+        }
+    }
+#if 0
+    if (outformat_d && !data && !list) {
+        fprintf(stderr, "yanglint error: no input data file for the specified data output format.\n");
+        goto cleanup;
+    }
+#endif
+
+    /* enable specified features, if not specified, all the module's features are enabled */
+    u = 4; /* skip internal libyang modules */
+    while ((mod = ly_ctx_get_module_iter(ctx, &u))) {
+        for (i = 0; i < featsize; i++) {
+            if (!strcmp(feat[i], mod->name)) {
+                /* parse features spec */
+                featlist = strdup(feat[i] + strlen(feat[i]) + 1);
+                ptr = NULL;
+                while((ptr = strtok(ptr ? NULL : featlist, ","))) {
+                    if (verbose >= 2) {
+                        fprintf(stdout, "Enabling feature %s in module %s.\n", ptr, mod->name);
+                    }
+                    if (lys_feature_enable(mod, ptr)) {
+                        fprintf(stderr, "Feature %s not defined in module %s.\n", ptr, mod->name);
+                    }
+                }
+                free(featlist);
+                break;
+            }
+        }
+        if (i == featsize) {
+            if (verbose >= 2) {
+                fprintf(stdout, "Enabling all features in module %s.\n", mod->name);
+            }
+            lys_feature_enable(mod, "*");
+        }
+    }
+
+    /* convert (print) to FORMAT */
+    if (outformat_s) {
+        if (outformat_s == LYS_OUT_JSON && mods->count > 1) {
+            fputs("[", out);
+        }
+        for (u = 0; u < mods->count; u++) {
+            if (u) {
+                if (outformat_s == LYS_OUT_JSON) {
+                    fputs(",\n", out);
+                } else {
+                    fputs("\n", out);
+                }
+            }
+            lys_print_file(out, (struct lys_module *)mods->objs[u], outformat_s, outline_length_s, outoptions_s);
+        }
+        if (outformat_s == LYS_OUT_JSON) {
+            if (mods->count > 1) {
+                fputs("]\n", out);
+            } else if (mods->count == 1) {
+                fputs("\n", out);
+            }
+        }
+#if 0
+    } else if (data) {
+        ly_errno = 0;
+
+        /* prepare operational datastore when specified for RPC/Notification */
+        if (oper_file) {
+            /* get the file format */
+            if (!get_fileformat(oper_file, NULL, &informat_d)) {
+                goto cleanup;
+            } else if (!informat_d) {
+                fprintf(stderr, "yanglint error: The operational data are expected in XML or JSON format.\n");
+                goto cleanup;
+            }
+            oper = lyd_parse_path(ctx, oper_file, informat_d, LYD_OPT_DATA_NO_YANGLIB | LYD_OPT_TRUSTED);
+            if (!oper) {
+                fprintf(stderr, "yanglint error: Failed to parse the operational datastore file for RPC/Notification validation.\n");
+                goto cleanup;
+            }
+        }
+
+        for (data_item = data, data_prev = NULL; data_item; data_prev = data_item, data_item = data_item->next) {
+            /* parse data file - via LYD_OPT_TRUSTED postpone validation when all data are loaded and merged */
+            if (autodetection) {
+                /* erase option not covered by LYD_OPT_TYPEMASK, but used according to the type */
+                options_parser &= ~LYD_OPT_DATA_NO_YANGLIB;
+                /* automatically detect data type from the data top level */
+                data_item->xml = lyxml_parse_path(ctx, data_item->filename, 0);
+                if (!data_item->xml) {
+                    fprintf(stderr, "yanglint error: parsing XML data for data type autodetection failed.\n");
+                    goto cleanup;
+                }
+
+                /* NOTE: namespace is ignored to simplify usage of this feature */
+                if (!strcmp(data_item->xml->name, "data")) {
+                    if (verbose >= 2) {
+                        fprintf(stdout, "Parsing %s as complete datastore.\n", data_item->filename);
+                    }
+                    options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_DATA_NO_YANGLIB;
+                    data_item->type = LYD_OPT_DATA;
+                } else if (!strcmp(data_item->xml->name, "config")) {
+                    if (verbose >= 2) {
+                        fprintf(stdout, "Parsing %s as config data.\n", data_item->filename);
+                    }
+                    options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_CONFIG;
+                    data_item->type = LYD_OPT_CONFIG;
+                } else if (!strcmp(data_item->xml->name, "get-reply")) {
+                    if (verbose >= 2) {
+                        fprintf(stdout, "Parsing %s as <get> reply data.\n", data_item->filename);
+                    }
+                    options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_GET;
+                    data_item->type = LYD_OPT_GET;
+                } else if (!strcmp(data_item->xml->name, "get-config-reply")) {
+                    if (verbose >= 2) {
+                        fprintf(stdout, "Parsing %s as <get-config> reply data.\n", data_item->filename);
+                    }
+                    options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_GETCONFIG;
+                    data_item->type = LYD_OPT_GETCONFIG;
+                } else if (!strcmp(data_item->xml->name, "edit-config")) {
+                    if (verbose >= 2) {
+                        fprintf(stdout, "Parsing %s as <edit-config> data.\n", data_item->filename);
+                    }
+                    options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_EDIT;
+                    data_item->type = LYD_OPT_EDIT;
+                } else if (!strcmp(data_item->xml->name, "rpc")) {
+                    if (verbose >= 2) {
+                        fprintf(stdout, "Parsing %s as <rpc> data.\n", data_item->filename);
+                    }
+                    options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPC;
+                    data_item->type = LYD_OPT_RPC;
+                } else if (!strcmp(data_item->xml->name, "rpc-reply")) {
+                    if (verbose >= 2) {
+                        fprintf(stdout, "Parsing %s as <rpc-reply> data.\n", data_item->filename);
+                    }
+
+                    data_item->type = LYD_OPT_RPCREPLY;
+                    if (!data_item->next || (data_prev && !data_prev->tree)) {
+                        fprintf(stderr, "RPC reply (%s) must be paired with the original RPC, see help.\n", data_item->filename);
+                        goto cleanup;
+                    }
+
+                    continue;
+                } else if (!strcmp(data_item->xml->name, "notification")) {
+                    if (verbose >= 2) {
+                        fprintf(stdout, "Parsing %s as <notification> data.\n", data_item->filename);
+                    }
+                    options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_NOTIF;
+                    data_item->type = LYD_OPT_NOTIF;
+
+                    /* ignore eventTime element if present */
+                    while (data_item->xml->child && !strcmp(data_item->xml->child->name, "eventTime")) {
+                        lyxml_free(ctx, data_item->xml->child);
+                    }
+                } else {
+                    fprintf(stderr, "yanglint error: invalid top-level element \"%s\" for data type autodetection.\n",
+                            data_item->xml->name);
+                    goto cleanup;
+                }
+
+                data_item->tree = lyd_parse_xml(ctx, &data_item->xml->child, options_parser, oper);
+                if (data_prev && data_prev->type == LYD_OPT_RPCREPLY) {
+parse_reply:
+                    /* check result of the RPC parsing, we are going to do another parsing in this step */
+                    if (ly_errno) {
+                        goto cleanup;
+                    }
+
+                    /* check that we really have RPC for the reply */
+                    if (data_item->type != LYD_OPT_RPC) {
+                        fprintf(stderr, "yanglint error: RPC reply (%s) must be paired with the original RPC, see help.\n", data_prev->filename);
+                        goto cleanup;
+                    }
+
+                    if (data_prev->format == LYD_XML) {
+                        /* ignore <ok> and <rpc-error> elements if present */
+                        u = 0;
+                        LY_TREE_FOR_SAFE(data_prev->xml->child, iter, elem) {
+                            if (!strcmp(data_prev->xml->child->name, "ok")) {
+                                if (u) {
+                                    /* rpc-error or ok already present */
+                                    u = 0x8; /* error flag */
+                                } else {
+                                    u = 0x1 | 0x4; /* <ok> flag with lyxml_free() flag */
+                                }
+                            } else if (!strcmp(data_prev->xml->child->name, "rpc-error")) {
+                                if (u && (u & 0x1)) {
+                                    /* ok already present, rpc-error can be present multiple times */
+                                    u = 0x8; /* error flag */
+                                } else {
+                                    u = 0x2 | 0x4; /* <rpc-error> flag with lyxml_free() flag */
+                                }
+                            }
+
+                            if (u == 0x8) {
+                                fprintf(stderr, "yanglint error: Invalid RPC reply (%s) content.\n", data_prev->filename);
+                                goto cleanup;
+                            } else if (u & 0x4) {
+                                lyxml_free(ctx, data_prev->xml->child);
+                                u &= ~0x4; /* unset lyxml_free() flag */
+                            }
+                        }
+
+                        /* finally, parse RPC reply from the previous step */
+                        data_prev->tree = lyd_parse_xml(ctx, &data_prev->xml->child,
+                                                        (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPCREPLY, data_item->tree, oper);
+                    } else { /* LYD_JSON */
+                        data_prev->tree = lyd_parse_path(ctx, data_prev->filename, data_item->format,
+                                                         (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPCREPLY, data_item->tree, oper);
+                    }
+                }
+            } else if ((options_parser & LYD_OPT_TYPEMASK) == LYD_OPT_RPCREPLY) {
+                if (data_prev && !data_prev->tree) {
+                    /* now we should have RPC for the preceding RPC reply */
+                    data_item->tree = lyd_parse_path(ctx, data_item->filename, data_item->format,
+                                                     (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPC, oper);
+                    data_item->type = LYD_OPT_RPC;
+                    goto parse_reply;
+                } else {
+                    /* now we have RPC reply which will be parsed in next step together with its RPC */
+                    if (!data_item->next) {
+                        fprintf(stderr, "yanglint error: RPC reply (%s) must be paired with the original RPC, see help.\n", data_item->filename);
+                        goto cleanup;
+                    }
+                    if (data_item->format == LYD_XML) {
+                        /* create rpc-reply container to unify handling with autodetection */
+                        data_item->xml = calloc(1, sizeof *data_item->xml);
+                        if (!data_item->xml) {
+                            fprintf(stderr, "yanglint error: Memory allocation failed failed.\n");
+                            goto cleanup;
+                        }
+                        data_item->xml->name = lydict_insert(ctx, "rpc-reply", 9);
+                        data_item->xml->prev = data_item->xml;
+                        data_item->xml->child = lyxml_parse_path(ctx, data_item->filename, LYXML_PARSE_MULTIROOT | LYXML_PARSE_NOMIXEDCONTENT);
+                        if (data_item->xml->child) {
+                            data_item->xml->child->parent = data_item->xml;
+                        }
+                    }
+                    continue;
+                }
+            } else {
+                data_item->tree = lyd_parse_path(ctx, data_item->filename, data_item->format, options_parser, oper);
+            }
+            if (ly_errno) {
+                goto cleanup;
+            }
+
+            if (merge && data != data_item) {
+                if (!data->tree) {
+                    data->tree = data_item->tree;
+                } else if (data_item->tree) {
+                    /* merge results */
+                    if (lyd_merge(data->tree, data_item->tree, LYD_OPT_DESTRUCT | LYD_OPT_EXPLICIT)) {
+                        fprintf(stderr, "yanglint error: merging multiple data trees failed.\n");
+                        goto cleanup;
+                    }
+                }
+                data_item->tree = NULL;
+            }
+        }
+
+        if (merge) {
+            /* validate the merged data tree, do not trust the input, invalidate all the data first */
+            LY_TREE_FOR(data->tree, subroot) {
+                LY_TREE_DFS_BEGIN(subroot, next, node) {
+                    node->validity = LYD_VAL_OK;
+                    switch (node->schema->nodetype) {
+                    case LYS_LEAFLIST:
+                    case LYS_LEAF:
+                        if (((struct lys_node_leaf *)node->schema)->type.base == LY_TYPE_LEAFREF) {
+                            node->validity |= LYD_VAL_LEAFREF;
+                        }
+                        break;
+                    case LYS_LIST:
+                        node->validity |= LYD_VAL_UNIQUE;
+                        /* falls through */
+                    case LYS_CONTAINER:
+                    case LYS_NOTIF:
+                    case LYS_RPC:
+                    case LYS_ACTION:
+                        node->validity |= LYD_VAL_MAND;
+                        break;
+                    default:
+                        break;
+                    }
+                    LY_TREE_DFS_END(subroot, next, node)
+                }
+            }
+            if (lyd_validate(&data->tree, options_parser & ~LYD_OPT_TRUSTED, ctx)) {
+                goto cleanup;
+            }
+        }
+
+        /* print only if data output format specified */
+        if (outformat_d) {
+            for (data_item = data; data_item; data_item = data_item->next) {
+                if (!merge && verbose >= 2) {
+                    fprintf(stdout, "File %s:\n", data_item->filename);
+                }
+                if (outformat_d == LYD_XML && envelope) {
+                    switch (data_item->type) {
+                    case LYD_OPT_DATA:
+                        envelope_s = "data";
+                        break;
+                    case LYD_OPT_CONFIG:
+                        envelope_s = "config";
+                        break;
+                    case LYD_OPT_GET:
+                        envelope_s = "get-reply";
+                        break;
+                    case LYD_OPT_GETCONFIG:
+                        envelope_s = "get-config-reply";
+                        break;
+                    case LYD_OPT_EDIT:
+                        envelope_s = "edit-config";
+                        break;
+                    case LYD_OPT_RPC:
+                        envelope_s = "rpc";
+                        break;
+                    case LYD_OPT_RPCREPLY:
+                        envelope_s = "rpc-reply";
+                        break;
+                    case LYD_OPT_NOTIF:
+                        envelope_s = "notification";
+                        break;
+                    }
+                    fprintf(out, "<%s>\n", envelope_s);
+                    if (data_item->type == LYD_OPT_RPC && data_item->tree->schema->nodetype != LYS_RPC) {
+                        /* action */
+                        fprintf(out, "<action xmlns=\"urn:ietf:params:xml:ns:yang:1\">\n");
+                    }
+                }
+                lyd_print_file(out, (data_item->type == LYD_OPT_RPCREPLY) ? data_item->tree->child : data_item->tree,
+                               outformat_d, LYP_WITHSIBLINGS | LYP_FORMAT | options_dflt);
+                if (envelope_s) {
+                    if (data_item->type == LYD_OPT_RPC && data_item->tree->schema->nodetype != LYS_RPC) {
+                        fprintf(out, "</action>\n");
+                    }
+                    fprintf(out, "</%s>\n", envelope_s);
+                }
+                if (merge) {
+                    /* stop after first item */
+                    break;
+                }
+            }
+        }
+#endif
+    }
+#if 0
+    if (list) {
+        print_list(out, ctx, outformat_d);
+    }
+#endif
+
+    ret = EXIT_SUCCESS;
+
+cleanup:
+    if (out && out != stdout) {
+        fclose(out);
+    }
+    ly_set_free(mods, NULL);
+    ly_set_free(searchpaths, NULL);
+    for (i = 0; i < featsize; i++) {
+        free(feat[i]);
+    }
+    free(feat);
+#if 0
+    for (; data; data = data_item) {
+        data_item = data->next;
+        lyxml_free(ctx, data->xml);
+        lyd_free_withsiblings(data->tree);
+        free(data);
+    }
+    lyd_free_withsiblings(oper);
+#endif
+    ly_ctx_destroy(ctx, NULL);
+
+    return ret;
+}