summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 04:20:26 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 04:20:26 +0000
commit044203039cebe3c05161f8f104a039d4744ca6d0 (patch)
tree1073c2308492e6aea4c66cb7436ee92db2abfd42 /tools
parentInitial commit. (diff)
downloadlibyang2-044203039cebe3c05161f8f104a039d4744ca6d0.tar.xz
libyang2-044203039cebe3c05161f8f104a039d4744ca6d0.zip
Adding upstream version 2.1.30.upstream/2.1.30
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools')
-rw-r--r--tools/CMakeLists.txt15
-rw-r--r--tools/config.h.in20
-rw-r--r--tools/lint/CMakeLists.txt90
-rw-r--r--tools/lint/cmd.c259
-rw-r--r--tools/lint/cmd.h69
-rw-r--r--tools/lint/cmd_add.c176
-rw-r--r--tools/lint/cmd_clear.c99
-rw-r--r--tools/lint/cmd_data.c328
-rw-r--r--tools/lint/cmd_feature.c135
-rw-r--r--tools/lint/cmd_list.c89
-rw-r--r--tools/lint/cmd_load.c139
-rw-r--r--tools/lint/cmd_print.c264
-rw-r--r--tools/lint/cmd_searchpath.c90
-rw-r--r--tools/lint/common.c868
-rw-r--r--tools/lint/common.h257
-rw-r--r--tools/lint/completion.c379
-rw-r--r--tools/lint/completion.h25
-rw-r--r--tools/lint/configuration.c125
-rw-r--r--tools/lint/configuration.h36
-rw-r--r--tools/lint/examples/README.md471
-rw-r--r--tools/lint/examples/action-reply.xml8
-rw-r--r--tools/lint/examples/action.xml8
-rw-r--r--tools/lint/examples/config-acm.xml24
-rw-r--r--tools/lint/examples/config-missing-key.xml24
-rw-r--r--tools/lint/examples/config-unknown-element.xml27
-rw-r--r--tools/lint/examples/data-acm.xml27
-rw-r--r--tools/lint/examples/data-ip.xml12
-rw-r--r--tools/lint/examples/data-malformed-xml.xml27
-rw-r--r--tools/lint/examples/data-malformed-xml2.xml26
-rw-r--r--tools/lint/examples/data-missing-key.xml26
-rw-r--r--tools/lint/examples/data-out-of-range-value.xml27
-rw-r--r--tools/lint/examples/datastore.xml29
-rw-r--r--tools/lint/examples/iana-if-type.yang1547
-rw-r--r--tools/lint/examples/ietf-interfaces.yang725
-rw-r--r--tools/lint/examples/ietf-ip.yang758
-rw-r--r--tools/lint/examples/ietf-netconf-acm-when.yang412
-rw-r--r--tools/lint/examples/ietf-netconf-acm-when.yin447
-rw-r--r--tools/lint/examples/ietf-netconf-acm-when2.yin447
-rw-r--r--tools/lint/examples/ietf-netconf-acm.yang411
-rw-r--r--tools/lint/examples/module1.yang5
-rw-r--r--tools/lint/examples/module1b.yang5
-rw-r--r--tools/lint/examples/module2.yang5
-rw-r--r--tools/lint/examples/module2.yin10
-rw-r--r--tools/lint/examples/module3.yang8
-rw-r--r--tools/lint/examples/module4.yang52
-rw-r--r--tools/lint/examples/nested-notification.xml8
-rw-r--r--tools/lint/examples/notification.xml3
-rw-r--r--tools/lint/examples/rpc-reply.xml5
-rw-r--r--tools/lint/examples/rpc.xml3
-rw-r--r--tools/lint/examples/sm-context-extension.xml64
-rw-r--r--tools/lint/examples/sm-context-main.xml54
-rw-r--r--tools/lint/examples/sm-data.xml19
-rw-r--r--tools/lint/examples/sm-extension.yang39
-rw-r--r--tools/lint/examples/sm-main.yang32
-rw-r--r--tools/lint/examples/sm-mod.yang21
-rw-r--r--tools/lint/linenoise/LICENSE25
-rw-r--r--tools/lint/linenoise/linenoise.c1218
-rw-r--r--tools/lint/linenoise/linenoise.h94
-rw-r--r--tools/lint/main.c102
-rw-r--r--tools/lint/main_ni.c1027
-rw-r--r--tools/lint/main_ni_only.c22
-rw-r--r--tools/lint/tests/expect/common.exp68
-rwxr-xr-xtools/lint/tests/expect/completion.exp60
-rwxr-xr-xtools/lint/tests/expect/feature.exp20
-rwxr-xr-xtools/lint/tests/expect/list.exp20
-rwxr-xr-xtools/lint/tests/shunit2/feature.sh19
-rwxr-xr-xtools/lint/tests/shunit2/list.sh28
-rw-r--r--tools/lint/yanglint.1136
-rw-r--r--tools/re/CMakeLists.txt20
-rw-r--r--tools/re/main.c309
-rw-r--r--tools/re/yangre.1118
71 files changed, 12565 insertions, 0 deletions
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
new file mode 100644
index 0000000..b92f80c
--- /dev/null
+++ b/tools/CMakeLists.txt
@@ -0,0 +1,15 @@
+# config file for tools
+configure_file(${PROJECT_SOURCE_DIR}/tools/config.h.in ${PROJECT_BINARY_DIR}/tools/config.h @ONLY)
+
+if(WIN32)
+ find_library(GETOPT_LIBRARY NAMES getopt REQUIRED)
+ find_path(GETOPT_INCLUDE_DIR NAMES getopt.h REQUIRED)
+ message(STATUS "Found <getopt.h> at ${GETOPT_INCLUDE_DIR}, library at ${GETOPT_LIBRARY}")
+endif()
+
+add_subdirectory(lint)
+add_subdirectory(re)
+
+set(format_sources
+ ${format_sources}
+ PARENT_SCOPE)
diff --git a/tools/config.h.in b/tools/config.h.in
new file mode 100644
index 0000000..603a42c
--- /dev/null
+++ b/tools/config.h.in
@@ -0,0 +1,20 @@
+/**
+ * @file config.h
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief various variables detected by cmake
+ *
+ * Copyright (c) 2019 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
+ */
+
+#ifndef YANGLINT_CONFIG_H_
+#define YANGLINT_CONFIG_H_
+
+#define PROJECT_VERSION "@LIBYANG_VERSION@" /**< libyang project version string */
+
+#endif /* YANGLINT_CONFIG_H_ */
diff --git a/tools/lint/CMakeLists.txt b/tools/lint/CMakeLists.txt
new file mode 100644
index 0000000..32cdcbf
--- /dev/null
+++ b/tools/lint/CMakeLists.txt
@@ -0,0 +1,90 @@
+# yanglint
+
+if(WIN32)
+ set(YANGLINT_INTERACTIVE OFF)
+else()
+ set(YANGLINT_INTERACTIVE ON)
+endif()
+
+set(lintsrc
+ main_ni.c
+ cmd.c
+ cmd_add.c
+ cmd_clear.c
+ cmd_data.c
+ cmd_list.c
+ cmd_feature.c
+ cmd_load.c
+ cmd_print.c
+ cmd_searchpath.c
+ common.c
+)
+if(YANGLINT_INTERACTIVE)
+ set(lintsrc ${lintsrc}
+ main.c
+ completion.c
+ configuration.c
+ linenoise/linenoise.c)
+else()
+ set(lintsrc ${lintsrc}
+ main_ni_only.c)
+endif()
+
+set(format_sources
+ ${format_sources}
+ ${CMAKE_CURRENT_SOURCE_DIR}/*.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/*.h
+ PARENT_SCOPE)
+
+add_executable(yanglint ${lintsrc} ${compatsrc})
+target_link_libraries(yanglint yang)
+install(TARGETS yanglint DESTINATION ${CMAKE_INSTALL_BINDIR})
+install(FILES ${PROJECT_SOURCE_DIR}/tools/lint/yanglint.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
+target_include_directories(yanglint BEFORE PRIVATE ${PROJECT_BINARY_DIR})
+
+if(WIN32)
+ target_include_directories(yanglint PRIVATE ${GETOPT_INCLUDE_DIR})
+ target_link_libraries(yanglint ${GETOPT_LIBRARY})
+endif()
+
+#
+# tests
+#
+function(add_yanglint_test)
+ cmake_parse_arguments(ADDTEST "" "NAME;VIA;SCRIPT" "" ${ARGN})
+ set(TEST_NAME yanglint_${ADDTEST_NAME})
+
+ if(${ADDTEST_VIA} STREQUAL "bash")
+ set(WRAPPER /usr/bin/env bash)
+ elseif(${ADDTEST_VIA} STREQUAL "expect")
+ set(WRAPPER ${PATH_EXPECT})
+ else()
+ message(FATAL_ERROR "build: unexpected wrapper '${ADDTEST_VIA}'")
+ endif()
+
+ add_test(NAME ${TEST_NAME} COMMAND ${WRAPPER} ${CMAKE_CURRENT_SOURCE_DIR}/tests/${ADDTEST_SCRIPT})
+ set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANGLINT=${PROJECT_BINARY_DIR}/yanglint")
+ set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANG_MODULES_DIR=${PROJECT_SOURCE_DIR}/tests/modules/yang")
+ set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}")
+endfunction(add_yanglint_test)
+
+if(ENABLE_TESTS)
+ # tests of non-interactive mode using shunit2
+ find_program(PATH_SHUNIT NAMES shunit2)
+ if(NOT PATH_SHUNIT)
+ message(WARNING "'shunit2' not found! The yanglint(1) non-interactive tests will not be available.")
+ else()
+ add_yanglint_test(NAME ni_list VIA bash SCRIPT shunit2/list.sh)
+ add_yanglint_test(NAME ni_feature VIA bash SCRIPT shunit2/feature.sh)
+ endif()
+
+ # tests of interactive mode using expect
+ find_program(PATH_EXPECT NAMES expect)
+ if(NOT PATH_EXPECT)
+ message(WARNING "'expect' not found! The yanglint(1) interactive tests will not be available.")
+ elseif(YANGLINT_INTERACTIVE)
+ add_yanglint_test(NAME in_list VIA expect SCRIPT expect/list.exp)
+ add_yanglint_test(NAME in_feature VIA expect SCRIPT expect/feature.exp)
+ add_yanglint_test(NAME in_completion VIA expect SCRIPT expect/completion.exp)
+ endif()
+endif()
diff --git a/tools/lint/cmd.c b/tools/lint/cmd.c
new file mode 100644
index 0000000..10e7446
--- /dev/null
+++ b/tools/lint/cmd.c
@@ -0,0 +1,259 @@
+/**
+ * @file cmd.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief libyang's yanglint tool general commands
+ *
+ * Copyright (c) 2015-2020 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
+ */
+
+#define _GNU_SOURCE
+
+#include "cmd.h"
+
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+
+#include "common.h"
+#include "compat.h"
+#include "libyang.h"
+
+COMMAND commands[];
+extern int done;
+
+#ifndef NDEBUG
+
+void
+cmd_debug_help(void)
+{
+ printf("Usage: debug (dict | xpath)+\n");
+}
+
+void
+cmd_debug(struct ly_ctx **UNUSED(ctx), const char *cmdline)
+{
+ int argc = 0;
+ char **argv = NULL;
+ int opt, opt_index;
+ struct option options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+ uint32_t dbg_groups = 0;
+
+ if (parse_cmdline(cmdline, &argc, &argv)) {
+ goto cleanup;
+ }
+
+ while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
+ switch (opt) {
+ case 'h':
+ cmd_debug_help();
+ goto cleanup;
+ default:
+ YLMSG_E("Unknown option.\n");
+ goto cleanup;
+ }
+ }
+ if (argc == optind) {
+ /* no argument */
+ cmd_debug_help();
+ goto cleanup;
+ }
+
+ for (int i = 0; i < argc - optind; i++) {
+ if (!strcasecmp("dict", argv[optind + i])) {
+ dbg_groups |= LY_LDGDICT;
+ } else if (!strcasecmp("xpath", argv[optind + i])) {
+ dbg_groups |= LY_LDGXPATH;
+ } else {
+ YLMSG_E("Unknown debug group \"%s\"\n", argv[optind + 1]);
+ goto cleanup;
+ }
+ }
+
+ ly_log_dbg_groups(dbg_groups);
+
+cleanup:
+ free_cmdline(argv);
+}
+
+#endif
+
+void
+cmd_verb_help(void)
+{
+ printf("Usage: verb (error | warning | verbose | debug)\n");
+}
+
+void
+cmd_verb(struct ly_ctx **UNUSED(ctx), const char *cmdline)
+{
+ int argc = 0;
+ char **argv = NULL;
+ int opt, opt_index;
+ struct option options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ if (parse_cmdline(cmdline, &argc, &argv)) {
+ goto cleanup;
+ }
+
+ while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
+ switch (opt) {
+ case 'h':
+ cmd_verb_help();
+ goto cleanup;
+ default:
+ YLMSG_E("Unknown option.\n");
+ goto cleanup;
+ }
+ }
+
+ if (argc - optind > 1) {
+ YLMSG_E("Only a single verbosity level can be set.\n");
+ cmd_verb_help();
+ goto cleanup;
+ } else if (argc == optind) {
+ /* no argument - print current value */
+ LY_LOG_LEVEL level = ly_log_level(LY_LLERR);
+
+ ly_log_level(level);
+ printf("Current verbosity level: ");
+ if (level == LY_LLERR) {
+ printf("error\n");
+ } else if (level == LY_LLWRN) {
+ printf("warning\n");
+ } else if (level == LY_LLVRB) {
+ printf("verbose\n");
+ } else if (level == LY_LLDBG) {
+ printf("debug\n");
+ }
+ goto cleanup;
+ }
+
+ if (!strcasecmp("error", argv[optind]) || !strcmp("0", argv[optind])) {
+ ly_log_level(LY_LLERR);
+ } else if (!strcasecmp("warning", argv[optind]) || !strcmp("1", argv[optind])) {
+ ly_log_level(LY_LLWRN);
+ } else if (!strcasecmp("verbose", argv[optind]) || !strcmp("2", argv[optind])) {
+ ly_log_level(LY_LLVRB);
+ } else if (!strcasecmp("debug", argv[optind]) || !strcmp("3", argv[optind])) {
+ ly_log_level(LY_LLDBG);
+ } else {
+ YLMSG_E("Unknown verbosity \"%s\"\n", argv[optind]);
+ goto cleanup;
+ }
+
+cleanup:
+ free_cmdline(argv);
+}
+
+void
+cmd_quit(struct ly_ctx **UNUSED(ctx), const char *UNUSED(cmdline))
+{
+ done = 1;
+ return;
+}
+
+void
+cmd_help_help(void)
+{
+ printf("Usage: help [cmd ...]\n");
+}
+
+void
+cmd_help(struct ly_ctx **UNUSED(ctx), const char *cmdline)
+{
+ int argc = 0;
+ char **argv = NULL;
+ int opt, opt_index;
+ struct option options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ if (parse_cmdline(cmdline, &argc, &argv)) {
+ goto cleanup;
+ }
+
+ while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
+ switch (opt) {
+ case 'h':
+ cmd_help_help();
+ goto cleanup;
+ default:
+ YLMSG_E("Unknown option.\n");
+ goto cleanup;
+ }
+ }
+
+ if (argc == optind) {
+generic_help:
+ printf("Available commands:\n");
+ for (uint16_t i = 0; commands[i].name; i++) {
+ if (commands[i].helpstring != NULL) {
+ printf(" %-15s %s\n", commands[i].name, commands[i].helpstring);
+ }
+ }
+ } else {
+ /* print specific help for the selected command(s) */
+
+ for (int c = 0; c < argc - optind; ++c) {
+ int8_t match = 0;
+
+ /* get the command of the specified name */
+ for (uint16_t i = 0; commands[i].name; i++) {
+ if (strcmp(argv[optind + c], commands[i].name) == 0) {
+ match = 1;
+ if (commands[i].help_func != NULL) {
+ commands[i].help_func();
+ } else {
+ printf("%s: %s\n", argv[optind + c], commands[i].helpstring);
+ }
+ break;
+ }
+ }
+ if (!match) {
+ /* if unknown command specified, print the list of commands */
+ printf("Unknown command \'%s\'\n", argv[optind + c]);
+ goto generic_help;
+ }
+ }
+ }
+
+cleanup:
+ free_cmdline(argv);
+}
+
+COMMAND commands[] = {
+ {"help", cmd_help, cmd_help_help, "Display commands description"},
+ {"add", cmd_add, cmd_add_help, "Add a new module from a specific file"},
+ {"load", cmd_load, cmd_load_help, "Load a new schema from the searchdirs"},
+ {"print", cmd_print, cmd_print_help, "Print a module"},
+ {"data", cmd_data, cmd_data_help, "Load, validate and optionally print instance data"},
+ {"list", cmd_list, cmd_list_help, "List all the loaded modules"},
+ {"feature", cmd_feature, cmd_feature_help, "Print all features of module(s) with their state"},
+ {"searchpath", cmd_searchpath, cmd_searchpath_help, "Print/set the search path(s) for schemas"},
+ {"clear", cmd_clear, cmd_clear_help, "Clear the context - remove all the loaded modules"},
+ {"verb", cmd_verb, cmd_verb_help, "Change verbosity"},
+#ifndef NDEBUG
+ {"debug", cmd_debug, cmd_debug_help, "Display specific debug message groups"},
+#endif
+ {"quit", cmd_quit, NULL, "Quit the program"},
+ /* synonyms for previous commands */
+ {"?", cmd_help, NULL, "Display commands description"},
+ {"exit", cmd_quit, NULL, "Quit the program"},
+ {NULL, NULL, NULL, NULL}
+};
diff --git a/tools/lint/cmd.h b/tools/lint/cmd.h
new file mode 100644
index 0000000..9f6f88d
--- /dev/null
+++ b/tools/lint/cmd.h
@@ -0,0 +1,69 @@
+/**
+ * @file cmd.h
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief libyang's yanglint tool commands header
+ *
+ * Copyright (c) 2015-2020 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
+ */
+
+#ifndef COMMANDS_H_
+#define COMMANDS_H_
+
+#include "libyang.h"
+
+/**
+ * @brief command information
+ */
+typedef struct {
+ char *name; /* User printable name of the function. */
+
+ void (*func)(struct ly_ctx **ctx, const char *); /* Function to call to do the command. */
+ void (*help_func)(void); /* Display command help. */
+ char *helpstring; /* Documentation for this function. */
+} COMMAND;
+
+/**
+ * @brief The list of available commands.
+ */
+extern COMMAND commands[];
+
+/* cmd_add.c */
+void cmd_add(struct ly_ctx **ctx, const char *cmdline);
+void cmd_add_help(void);
+
+/* cmd_clear.c */
+void cmd_clear(struct ly_ctx **ctx, const char *cmdline);
+void cmd_clear_help(void);
+
+/* cmd_data.c */
+void cmd_data(struct ly_ctx **ctx, const char *cmdline);
+void cmd_data_help(void);
+
+/* cmd_list.c */
+void cmd_list(struct ly_ctx **ctx, const char *cmdline);
+void cmd_list_help(void);
+
+/* cmd_feature.c */
+void cmd_feature(struct ly_ctx **ctx, const char *cmdline);
+void cmd_feature_help(void);
+
+/* cmd_load.c */
+void cmd_load(struct ly_ctx **ctx, const char *cmdline);
+void cmd_load_help(void);
+
+/* cmd_print.c */
+void cmd_print(struct ly_ctx **ctx, const char *cmdline);
+void cmd_print_help(void);
+
+/* cmd_searchpath.c */
+void cmd_searchpath(struct ly_ctx **ctx, const char *cmdline);
+void cmd_searchpath_help(void);
+
+#endif /* COMMANDS_H_ */
diff --git a/tools/lint/cmd_add.c b/tools/lint/cmd_add.c
new file mode 100644
index 0000000..bbfdfd6
--- /dev/null
+++ b/tools/lint/cmd_add.c
@@ -0,0 +1,176 @@
+/**
+ * @file cmd_add.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief 'add' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2015-2020 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
+ */
+
+#define _GNU_SOURCE
+
+#include "cmd.h"
+
+#include <getopt.h>
+#include <libgen.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "libyang.h"
+
+#include "common.h"
+
+void
+cmd_add_help(void)
+{
+ printf("Usage: add [-iD] <schema1> [<schema2> ...]\n"
+ " Add a new module from a specific file.\n\n"
+ " -D, --disable-searchdir\n"
+ " Do not implicitly search in current working directory for\n"
+ " the import schema modules. If specified a second time, do not\n"
+ " even search in the module directory (all modules must be \n"
+ " explicitly specified).\n"
+ " -F FEATURES, --features=FEATURES\n"
+ " Features to support, default all in all implemented modules.\n"
+ " Specify separately for each module.\n"
+ " <modname>:[<feature>,]*\n"
+ " -i, --make-implemented\n"
+ " Make the imported modules \"referenced\" from any loaded\n"
+ " <schema> module also implemented. If specified a second time,\n"
+ " all the modules are set implemented.\n");
+}
+
+void
+cmd_add(struct ly_ctx **ctx, const char *cmdline)
+{
+ int argc = 0;
+ char **argv = NULL;
+ int opt, opt_index;
+ struct option options[] = {
+ {"disable-searchdir", no_argument, NULL, 'D'},
+ {"features", required_argument, NULL, 'F'},
+ {"help", no_argument, NULL, 'h'},
+ {"make-implemented", no_argument, NULL, 'i'},
+ {NULL, 0, NULL, 0}
+ };
+ uint16_t options_ctx = 0;
+ const char *all_features[] = {"*", NULL};
+ struct ly_set fset = {0};
+
+ if (parse_cmdline(cmdline, &argc, &argv)) {
+ goto cleanup;
+ }
+
+ while ((opt = getopt_long(argc, argv, "D:F:hi", options, &opt_index)) != -1) {
+ switch (opt) {
+ case 'D': /* --disable--search */
+ if (options_ctx & LY_CTX_DISABLE_SEARCHDIRS) {
+ YLMSG_W("The -D option specified too many times.\n");
+ }
+ 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 'F': /* --features */
+ if (parse_features(optarg, &fset)) {
+ goto cleanup;
+ }
+ break;
+
+ case 'h':
+ cmd_add_help();
+ goto cleanup;
+
+ case 'i': /* --make-implemented */
+ if (options_ctx & LY_CTX_REF_IMPLEMENTED) {
+ options_ctx &= ~LY_CTX_REF_IMPLEMENTED;
+ options_ctx |= LY_CTX_ALL_IMPLEMENTED;
+ } else {
+ options_ctx |= LY_CTX_REF_IMPLEMENTED;
+ }
+ break;
+
+ default:
+ YLMSG_E("Unknown option.\n");
+ goto cleanup;
+ }
+ }
+
+ if (argc == optind) {
+ /* no argument */
+ cmd_add_help();
+ goto cleanup;
+ }
+
+ if (!fset.count) {
+ /* no features, enable all of them */
+ options_ctx |= LY_CTX_ENABLE_IMP_FEATURES;
+ }
+
+ if (options_ctx) {
+ ly_ctx_set_options(*ctx, options_ctx);
+ }
+
+ for (int i = 0; i < argc - optind; i++) {
+ /* process the schema module files */
+ LY_ERR ret;
+ uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */
+ char *dir, *module;
+ const char **features = NULL;
+ struct ly_in *in = NULL;
+
+ if (parse_schema_path(argv[optind + i], &dir, &module)) {
+ goto cleanup;
+ }
+
+ /* add temporarily also the path of the module itself */
+ if (ly_ctx_set_searchdir(*ctx, dirname(dir)) == LY_EEXIST) {
+ path_unset = 0;
+ }
+
+ /* get features list for this module */
+ if (!fset.count) {
+ features = all_features;
+ } else {
+ get_features(&fset, module, &features);
+ }
+
+ /* temporary cleanup */
+ free(dir);
+ free(module);
+
+ /* prepare input handler */
+ ret = ly_in_new_filepath(argv[optind + i], 0, &in);
+ if (ret) {
+ goto cleanup;
+ }
+
+ /* parse the file */
+ ret = lys_parse(*ctx, in, LYS_IN_UNKNOWN, features, NULL);
+ ly_in_free(in, 1);
+ ly_ctx_unset_searchdir_last(*ctx, path_unset);
+
+ if (ret) {
+ /* libyang printed the error messages */
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ if (options_ctx) {
+ ly_ctx_unset_options(*ctx, options_ctx);
+ }
+ ly_set_erase(&fset, free_features);
+ free_cmdline(argv);
+}
diff --git a/tools/lint/cmd_clear.c b/tools/lint/cmd_clear.c
new file mode 100644
index 0000000..5eed6ff
--- /dev/null
+++ b/tools/lint/cmd_clear.c
@@ -0,0 +1,99 @@
+/**
+ * @file cmd_clear.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief 'clear' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2015-2020 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
+ */
+
+#define _GNU_SOURCE
+
+#include "cmd.h"
+
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "libyang.h"
+
+#include "common.h"
+
+void
+cmd_clear_help(void)
+{
+ printf("Usage: clear [-i] [-y]\n"
+ " Replace the current context with an empty one, searchpaths\n"
+ " are not kept.\n\n"
+ " -i, --make-implemented\n"
+ " Make the imported modules \"referenced\" from any loaded\n"
+ " module also implemented. If specified a second time, all the\n"
+ " modules are set implemented.\n"
+ " -y, --yang-library\n"
+ " Load and implement internal \"ietf-yang-library\" YANG module.\n"
+ " Note that this module includes definitions of mandatory state\n"
+ " data that can result in unexpected data validation errors.\n");
+#if 0
+ " If <yang-library-data> path specified, load the modules\n"
+ " according to the provided yang library data.\n"
+#endif
+}
+
+void
+cmd_clear(struct ly_ctx **ctx, const char *cmdline)
+{
+ int argc = 0;
+ char **argv = NULL;
+ int opt, opt_index;
+ struct option options[] = {
+ {"make-implemented", no_argument, NULL, 'i'},
+ {"yang-library", no_argument, NULL, 'y'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+ uint16_t options_ctx = LY_CTX_NO_YANGLIBRARY;
+ struct ly_ctx *ctx_new;
+
+ if (parse_cmdline(cmdline, &argc, &argv)) {
+ goto cleanup;
+ }
+
+ while ((opt = getopt_long(argc, argv, "iyh", options, &opt_index)) != -1) {
+ switch (opt) {
+ case 'i':
+ if (options_ctx & LY_CTX_REF_IMPLEMENTED) {
+ options_ctx &= ~LY_CTX_REF_IMPLEMENTED;
+ options_ctx |= LY_CTX_ALL_IMPLEMENTED;
+ } else {
+ options_ctx |= LY_CTX_REF_IMPLEMENTED;
+ }
+ break;
+ case 'y':
+ options_ctx &= ~LY_CTX_NO_YANGLIBRARY;
+ break;
+ case 'h':
+ cmd_clear_help();
+ goto cleanup;
+ default:
+ YLMSG_E("Unknown option.\n");
+ goto cleanup;
+ }
+ }
+
+ if (ly_ctx_new(NULL, options_ctx, &ctx_new)) {
+ YLMSG_W("Failed to create context.\n");
+ goto cleanup;
+ }
+
+ ly_ctx_destroy(*ctx);
+ *ctx = ctx_new;
+
+cleanup:
+ free_cmdline(argv);
+}
diff --git a/tools/lint/cmd_data.c b/tools/lint/cmd_data.c
new file mode 100644
index 0000000..25449f5
--- /dev/null
+++ b/tools/lint/cmd_data.c
@@ -0,0 +1,328 @@
+/**
+ * @file cmd_data.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief 'data' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2015-2020 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
+ */
+
+#define _GNU_SOURCE
+
+#include "cmd.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+
+#include "libyang.h"
+
+#include "common.h"
+
+void
+cmd_data_help(void)
+{
+ printf("Usage: data [-emn] [-t TYPE]\n"
+ " [-F FORMAT] [-f FORMAT] [-d DEFAULTS] [-o OUTFILE] <data1> ...\n"
+ " data [-n] -t (rpc | notif | reply) [-O FILE]\n"
+ " [-F FORMAT] [-f FORMAT] [-d DEFAULTS] [-o OUTFILE] <data1> ...\n"
+ " data [-en] [-t TYPE] [-F FORMAT] -x XPATH [-o OUTFILE] <data1> ...\n"
+ " Parse, validate and optionally print data instances\n\n"
+
+ " -t TYPE, --type=TYPE\n"
+ " Specify data tree type in the input data file(s):\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\n"
+ " RPC/Action input statement.\n"
+ " reply - Reply to the RPC/Action. Note that the reply data are\n"
+ " expected inside a container representing the original\n"
+ " RPC/Action. This is necessary to identify appropriate\n"
+ " data definitions in the schema module.\n"
+ " notif - Notification instance (content of the <notification>\n"
+ " element without <eventTime>).\n\n"
+
+ " -e, --present Validate only with the schema modules whose data actually\n"
+ " exist in the provided input data files. Takes effect only\n"
+ " with the 'data' or 'config' TYPEs. Used to avoid requiring\n"
+ " mandatory nodes from modules which data are not present in the\n"
+ " provided input data files.\n"
+ " -m, --merge Merge input data files into a single tree and validate at\n"
+ " once.The option has effect only for 'data' and 'config' TYPEs.\n"
+ " In case of using -x option, the data are always merged.\n"
+ " -n, --not-strict\n"
+ " Do not require strict data parsing (silently skip unknown data),\n"
+ " has no effect for schemas.\n\n"
+ " -O FILE, --operational=FILE\n"
+ " Provide optional data to extend validation of the 'rpc',\n"
+ " 'reply' or 'notif' TYPEs. The FILE is supposed to contain\n"
+ " the :running configuration datastore and state data\n"
+ " (operational datastore) referenced from the RPC/Notification.\n\n"
+
+ " -f FORMAT, --format=FORMAT\n"
+ " Print the data in one of the following formats:\n"
+ " xml, json, lyb\n"
+ " Note that the LYB format requires the -o option specified.\n"
+ " -F FORMAT, --in-format=FORMAT\n"
+ " Load the data in one of the following formats:\n"
+ " xml, json, lyb\n"
+ " If input format not specified, it is detected from the file extension.\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"
+ " -o OUTFILE, --output=OUTFILE\n"
+ " Write the output to OUTFILE instead of stdout.\n\n"
+
+ " -x XPATH, --xpath=XPATH\n"
+ " Evaluate XPATH expression and print the nodes satysfying the.\n"
+ " expression. The output format is specific and the option cannot\n"
+ " be combined with the -f and -d options. Also all the data\n"
+ " inputs are merged into a single data tree where the expression\n"
+ " is evaluated, so the -m option is always set implicitly.\n\n");
+
+}
+
+void
+cmd_data(struct ly_ctx **ctx, const char *cmdline)
+{
+ int argc = 0;
+ char **argv = NULL;
+ int opt, opt_index;
+ struct option options[] = {
+ {"defaults", required_argument, NULL, 'd'},
+ {"present", no_argument, NULL, 'e'},
+ {"format", required_argument, NULL, 'f'},
+ {"in-format", required_argument, NULL, 'F'},
+ {"help", no_argument, NULL, 'h'},
+ {"merge", no_argument, NULL, 'm'},
+ {"output", required_argument, NULL, 'o'},
+ {"operational", required_argument, NULL, 'O'},
+ {"not-strict", no_argument, NULL, 'n'},
+ {"type", required_argument, NULL, 't'},
+ {"xpath", required_argument, NULL, 'x'},
+ {NULL, 0, NULL, 0}
+ };
+
+ uint8_t data_merge = 0;
+ uint32_t options_print = 0;
+ uint32_t options_parse = YL_DEFAULT_DATA_PARSE_OPTIONS;
+ uint32_t options_validate = 0;
+ enum lyd_type data_type = 0;
+ uint8_t data_type_set = 0;
+ LYD_FORMAT outformat = LYD_UNKNOWN;
+ LYD_FORMAT informat = LYD_UNKNOWN;
+ struct ly_out *out = NULL;
+ struct cmdline_file *operational = NULL;
+ struct ly_set inputs = {0};
+ struct ly_set xpaths = {0};
+
+ if (parse_cmdline(cmdline, &argc, &argv)) {
+ goto cleanup;
+ }
+
+ while ((opt = getopt_long(argc, argv, "d:ef:F:hmo:O:r:nt:x:", options, &opt_index)) != -1) {
+ switch (opt) {
+ case 'd': /* --default */
+ if (!strcasecmp(optarg, "all")) {
+ options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL;
+ } else if (!strcasecmp(optarg, "all-tagged")) {
+ options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG;
+ } else if (!strcasecmp(optarg, "trim")) {
+ options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM;
+ } else if (!strcasecmp(optarg, "implicit-tagged")) {
+ options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG;
+ } else {
+ YLMSG_E("Unknown default mode %s\n", optarg);
+ cmd_data_help();
+ goto cleanup;
+ }
+ break;
+ case 'f': /* --format */
+ if (!strcasecmp(optarg, "xml")) {
+ outformat = LYD_XML;
+ } else if (!strcasecmp(optarg, "json")) {
+ outformat = LYD_JSON;
+ } else if (!strcasecmp(optarg, "lyb")) {
+ outformat = LYD_LYB;
+ } else {
+ YLMSG_E("Unknown output format %s\n", optarg);
+ cmd_data_help();
+ goto cleanup;
+ }
+ break;
+ case 'F': /* --in-format */
+ if (!strcasecmp(optarg, "xml")) {
+ informat = LYD_XML;
+ } else if (!strcasecmp(optarg, "json")) {
+ informat = LYD_JSON;
+ } else if (!strcasecmp(optarg, "lyb")) {
+ informat = LYD_LYB;
+ } else {
+ YLMSG_E("Unknown input format %s\n", optarg);
+ cmd_data_help();
+ goto cleanup;
+ }
+ break;
+ case 'o': /* --output */
+ if (out) {
+ YLMSG_E("Only a single output can be specified.\n");
+ goto cleanup;
+ } else {
+ if (ly_out_new_filepath(optarg, &out)) {
+ YLMSG_E("Unable open output file %s (%s)\n", optarg, strerror(errno));
+ goto cleanup;
+ }
+ }
+ break;
+ case 'O': { /* --operational */
+ struct ly_in *in;
+ LYD_FORMAT f;
+
+ if (operational) {
+ YLMSG_E("The operational datastore (-O) cannot be set multiple times.\n");
+ goto cleanup;
+ }
+ if (get_input(optarg, NULL, &f, &in)) {
+ goto cleanup;
+ }
+ operational = fill_cmdline_file(NULL, in, optarg, f);
+ break;
+ } /* case 'O' */
+
+ case 'e': /* --present */
+ options_validate |= LYD_VALIDATE_PRESENT;
+ break;
+ case 'm': /* --merge */
+ data_merge = 1;
+ break;
+ case 'n': /* --not-strict */
+ options_parse &= ~LYD_PARSE_STRICT;
+ break;
+ case 't': /* --type */
+ if (data_type_set) {
+ YLMSG_E("The data type (-t) cannot be set multiple times.\n");
+ goto cleanup;
+ }
+
+ if (!strcasecmp(optarg, "config")) {
+ options_parse |= LYD_PARSE_NO_STATE;
+ options_validate |= LYD_VALIDATE_NO_STATE;
+ } else if (!strcasecmp(optarg, "get")) {
+ options_parse |= LYD_PARSE_ONLY;
+ } else if (!strcasecmp(optarg, "getconfig") || !strcasecmp(optarg, "get-config") || !strcasecmp(optarg, "edit")) {
+ options_parse |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE;
+ } else if (!strcasecmp(optarg, "rpc") || !strcasecmp(optarg, "action")) {
+ data_type = LYD_TYPE_RPC_YANG;
+ } else if (!strcasecmp(optarg, "reply") || !strcasecmp(optarg, "rpcreply")) {
+ data_type = LYD_TYPE_REPLY_YANG;
+ } else if (!strcasecmp(optarg, "notif") || !strcasecmp(optarg, "notification")) {
+ data_type = LYD_TYPE_NOTIF_YANG;
+ } else if (!strcasecmp(optarg, "data")) {
+ /* default option */
+ } else {
+ YLMSG_E("Unknown data tree type %s.\n", optarg);
+ cmd_data_help();
+ goto cleanup;
+ }
+
+ data_type_set = 1;
+ break;
+
+ case 'x': /* --xpath */
+ if (ly_set_add(&xpaths, optarg, 0, NULL)) {
+ YLMSG_E("Storing XPath \"%s\" failed.\n", optarg);
+ goto cleanup;
+ }
+ break;
+
+ case 'h': /* --help */
+ cmd_data_help();
+ goto cleanup;
+ default:
+ YLMSG_E("Unknown option.\n");
+ goto cleanup;
+ }
+ }
+
+ if (optind == argc) {
+ YLMSG_E("Missing the data file to process.\n");
+ goto cleanup;
+ }
+
+ if (data_merge) {
+ if (data_type || (options_parse & LYD_PARSE_ONLY)) {
+ /* switch off the option, incompatible input data type */
+ data_merge = 0;
+ } else {
+ /* postpone validation after the merge of all the input data */
+ options_parse |= LYD_PARSE_ONLY;
+ }
+ } else if (xpaths.count) {
+ data_merge = 1;
+ }
+
+ if (xpaths.count && outformat) {
+ YLMSG_E("The --format option cannot be combined with --xpath option.\n");
+ cmd_data_help();
+ goto cleanup;
+ }
+
+ if (operational && !data_type) {
+ YLMSG_W("Operational datastore takes effect only with RPCs/Actions/Replies/Notifications input data types.\n");
+ free_cmdline_file(operational);
+ operational = NULL;
+ }
+
+ /* process input data files provided as standalone command line arguments */
+ for (int i = 0; i < argc - optind; i++) {
+ struct ly_in *in;
+
+ if (get_input(argv[optind + i], NULL, &informat, &in)) {
+ goto cleanup;
+ }
+
+ if (!fill_cmdline_file(&inputs, in, argv[optind + i], informat)) {
+ ly_in_free(in, 1);
+ goto cleanup;
+ }
+ }
+
+ /* default output stream */
+ if (!out) {
+ if (ly_out_new_file(stdout, &out)) {
+ YLMSG_E("Unable to set stdout as output.\n");
+ goto cleanup;
+ }
+ }
+
+ /* parse, validate and print data */
+ if (process_data(*ctx, data_type, data_merge, outformat, out, options_parse, options_validate, options_print,
+ operational, NULL, &inputs, &xpaths)) {
+ goto cleanup;
+ }
+
+cleanup:
+ ly_out_free(out, NULL, 0);
+ ly_set_erase(&inputs, free_cmdline_file);
+ ly_set_erase(&xpaths, NULL);
+ free_cmdline_file(operational);
+ free_cmdline(argv);
+}
diff --git a/tools/lint/cmd_feature.c b/tools/lint/cmd_feature.c
new file mode 100644
index 0000000..6b332ab
--- /dev/null
+++ b/tools/lint/cmd_feature.c
@@ -0,0 +1,135 @@
+/**
+ * @file cmd_feature.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief 'feature' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2015-2021 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
+ */
+
+#define _GNU_SOURCE
+
+#include "cmd.h"
+
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "libyang.h"
+
+#include "common.h"
+
+void
+cmd_feature_help(void)
+{
+ printf("Usage: feature [-f] <module> [<module>]*\n"
+ " feature -a [-f]\n"
+ " Print features of all the modules with state of each one.\n\n"
+ " -f <module1, module2, ...>, --feature-param <module1, module2, ...>\n"
+ " Generate features parameter for the command \"add\" \n"
+ " in the form of -F <module-name>:<features>\n"
+ " -a, --all \n"
+ " Print features of all implemented modules.\n");
+}
+
+void
+cmd_feature(struct ly_ctx **ctx, const char *cmdline)
+{
+ int argc = 0;
+ char **argv = NULL;
+ char *features_output = NULL;
+ int opt, opt_index, i;
+ ly_bool generate_features = 0, print_all = 0;
+ struct ly_set set = {0};
+ const struct lys_module *mod;
+ struct ly_out *out = NULL;
+ struct option options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"all", no_argument, NULL, 'a'},
+ {"feature-param", no_argument, NULL, 'f'},
+ {NULL, 0, NULL, 0}
+ };
+
+ if (parse_cmdline(cmdline, &argc, &argv)) {
+ goto cleanup;
+ }
+
+ while ((opt = getopt_long(argc, argv, "haf", options, &opt_index)) != -1) {
+ switch (opt) {
+ case 'h':
+ cmd_feature_help();
+ goto cleanup;
+ case 'a':
+ print_all = 1;
+ break;
+ case 'f':
+ generate_features = 1;
+ break;
+ default:
+ YLMSG_E("Unknown option.\n");
+ goto cleanup;
+ }
+ }
+
+ if (ly_out_new_file(stdout, &out)) {
+ YLMSG_E("Unable to print to the standard output.\n");
+ goto cleanup;
+ }
+
+ if (print_all) {
+ if (print_all_features(out, *ctx, generate_features, &features_output)) {
+ YLMSG_E("Printing all features failed.\n");
+ goto cleanup;
+ }
+ if (generate_features) {
+ printf("%s\n", features_output);
+ }
+ goto cleanup;
+ }
+
+ if (argc == optind) {
+ YLMSG_E("Missing modules to print.\n");
+ goto cleanup;
+ }
+
+ for (i = 0; i < argc - optind; i++) {
+ /* always erase the set, so the previous module's features don't carry over to the next module's features */
+ ly_set_erase(&set, NULL);
+
+ mod = ly_ctx_get_module_latest(*ctx, argv[optind + i]);
+ if (!mod) {
+ YLMSG_E("Module \"%s\" not found.\n", argv[optind + i]);
+ goto cleanup;
+ }
+
+ /* collect features of the module */
+ if (collect_features(mod, &set)) {
+ goto cleanup;
+ }
+
+ if (generate_features) {
+ if (generate_features_output(mod, &set, &features_output)) {
+ goto cleanup;
+ }
+ /* don't print features and their state of each module if generating features parameter */
+ continue;
+ }
+
+ print_features(out, mod, &set);
+ }
+
+ if (generate_features) {
+ printf("%s\n", features_output);
+ }
+
+cleanup:
+ free_cmdline(argv);
+ ly_out_free(out, NULL, 0);
+ ly_set_erase(&set, NULL);
+ free(features_output);
+}
diff --git a/tools/lint/cmd_list.c b/tools/lint/cmd_list.c
new file mode 100644
index 0000000..ec7a021
--- /dev/null
+++ b/tools/lint/cmd_list.c
@@ -0,0 +1,89 @@
+/**
+ * @file cmd_list.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief 'list' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2015-2020 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
+ */
+
+#define _GNU_SOURCE
+
+#include "cmd.h"
+
+#include <getopt.h>
+#include <stdio.h>
+#include <strings.h>
+
+#include "libyang.h"
+
+#include "common.h"
+
+void
+cmd_list_help(void)
+{
+ printf("Usage: list [-f (xml | json)]\n"
+ " Print the list of modules in the current context\n\n"
+ " -f FORMAT, --format=FORMAT\n"
+ " Print the list as ietf-yang-library data in the specified\n"
+ " data FORMAT. If format not specified, a simple list is\n"
+ " printed with an indication of imported (i) / implemented (I)\n"
+ " modules.\n");
+}
+
+void
+cmd_list(struct ly_ctx **ctx, const char *cmdline)
+{
+ int argc = 0;
+ char **argv = NULL;
+ int opt, opt_index;
+ struct option options[] = {
+ {"format", required_argument, NULL, 'f'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+ LYD_FORMAT format = LYD_UNKNOWN;
+ struct ly_out *out = NULL;
+
+ if (parse_cmdline(cmdline, &argc, &argv)) {
+ goto cleanup;
+ }
+
+ while ((opt = getopt_long(argc, argv, "f:h", options, &opt_index)) != -1) {
+ switch (opt) {
+ case 'f': /* --format */
+ if (!strcasecmp(optarg, "xml")) {
+ format = LYD_XML;
+ } else if (!strcasecmp(optarg, "json")) {
+ format = LYD_JSON;
+ } else {
+ YLMSG_E("Unknown output format %s\n", optarg);
+ cmd_list_help();
+ goto cleanup;
+ }
+ break;
+ case 'h':
+ cmd_list_help();
+ goto cleanup;
+ default:
+ YLMSG_E("Unknown option.\n");
+ goto cleanup;
+ }
+ }
+
+ if (!ly_out_new_file(stdout, &out)) {
+ print_list(out, *ctx, format);
+ ly_out_free(out, NULL, 0);
+ } else {
+ YLMSG_E("Unable to print to the standard output.\n");
+ }
+
+cleanup:
+ free_cmdline(argv);
+}
diff --git a/tools/lint/cmd_load.c b/tools/lint/cmd_load.c
new file mode 100644
index 0000000..f5883e9
--- /dev/null
+++ b/tools/lint/cmd_load.c
@@ -0,0 +1,139 @@
+/**
+ * @file cmd_load.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief 'load' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2015-2020 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
+ */
+
+#define _GNU_SOURCE
+
+#include "cmd.h"
+
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libyang.h"
+
+#include "common.h"
+
+void
+cmd_load_help(void)
+{
+ printf("Usage: load [-i] <module-name1>[@<revision>] [<module-name2>[@revision] ...]\n"
+ " Add a new module of the specified name, yanglint will find\n"
+ " them in searchpaths. if the <revision> of the module not\n"
+ " specified, the latest revision available is loaded.\n\n"
+ " -F FEATURES, --features=FEATURES\n"
+ " Features to support, default all in all implemented modules.\n"
+ " <modname>:[<feature>,]*\n"
+ " -i, --make-implemented\n"
+ " Make the imported modules \"referenced\" from any loaded\n"
+ " <schema> module also implemented. If specified a second time,\n"
+ " all the modules are set implemented.\n");
+}
+
+void
+cmd_load(struct ly_ctx **ctx, const char *cmdline)
+{
+ int argc = 0;
+ char **argv = NULL;
+ int opt, opt_index;
+ struct option options[] = {
+ {"features", required_argument, NULL, 'F'},
+ {"help", no_argument, NULL, 'h'},
+ {"make-implemented", no_argument, NULL, 'i'},
+ {NULL, 0, NULL, 0}
+ };
+ uint16_t options_ctx = 0;
+ const char *all_features[] = {"*", NULL};
+ struct ly_set fset = {0};
+
+ if (parse_cmdline(cmdline, &argc, &argv)) {
+ goto cleanup;
+ }
+
+ while ((opt = getopt_long(argc, argv, "F:hi", options, &opt_index)) != -1) {
+ switch (opt) {
+ case 'F': /* --features */
+ if (parse_features(optarg, &fset)) {
+ goto cleanup;
+ }
+ break;
+
+ case 'h':
+ cmd_load_help();
+ goto cleanup;
+
+ case 'i': /* --make-implemented */
+ if (options_ctx & LY_CTX_REF_IMPLEMENTED) {
+ options_ctx &= ~LY_CTX_REF_IMPLEMENTED;
+ options_ctx |= LY_CTX_ALL_IMPLEMENTED;
+ } else {
+ options_ctx |= LY_CTX_REF_IMPLEMENTED;
+ }
+ break;
+
+ default:
+ YLMSG_E("Unknown option.\n");
+ goto cleanup;
+ }
+ }
+
+ if (argc == optind) {
+ /* no argument */
+ cmd_add_help();
+ goto cleanup;
+ }
+
+ if (!fset.count) {
+ /* no features, enable all of them */
+ options_ctx |= LY_CTX_ENABLE_IMP_FEATURES;
+ }
+
+ if (options_ctx) {
+ ly_ctx_set_options(*ctx, options_ctx);
+ }
+
+ for (int i = 0; i < argc - optind; i++) {
+ /* process the schema module files */
+ char *revision;
+ const char **features = NULL;
+
+ /* get revision */
+ revision = strchr(argv[optind + i], '@');
+ if (revision) {
+ revision[0] = '\0';
+ ++revision;
+ }
+
+ /* get features list for this module */
+ if (!fset.count) {
+ features = all_features;
+ } else {
+ get_features(&fset, argv[optind + i], &features);
+ }
+
+ /* load the module */
+ if (!ly_ctx_load_module(*ctx, argv[optind + i], revision, features)) {
+ /* libyang printed the error messages */
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ if (options_ctx) {
+ ly_ctx_unset_options(*ctx, options_ctx);
+ }
+ ly_set_erase(&fset, free_features);
+ free_cmdline(argv);
+}
diff --git a/tools/lint/cmd_print.c b/tools/lint/cmd_print.c
new file mode 100644
index 0000000..c1a5359
--- /dev/null
+++ b/tools/lint/cmd_print.c
@@ -0,0 +1,264 @@
+/**
+ * @file cmd_print.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief 'print' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2015-2020 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
+ */
+
+#define _GNU_SOURCE
+
+#include "cmd.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+
+#include "libyang.h"
+
+#include "common.h"
+
+void
+cmd_print_help(void)
+{
+ printf("Usage: print [-f (yang | yin | tree [-q -P PATH -L LINE_LENGTH ] | info [-q -P PATH])]\n"
+ " [-o OUTFILE] [<module-name1>[@revision]] ...\n"
+ " Print a schema module. The <module-name> is not required\n"
+ " only in case the -P option is specified.\n\n"
+ " -f FORMAT, --format=FORMAT\n"
+ " Print the module in the specified FORMAT. If format not\n"
+ " specified, the 'tree' format is used.\n"
+ " -L LINE_LENGTH, --tree-line-length=LINE_LENGTH\n"
+ " The limit of the maximum line length on which the 'tree'\n"
+ " format will try to be printed.\n"
+ " -P PATH, --schema-node=PATH\n"
+ " Print only the specified subtree of the schema.\n"
+ " The PATH is the XPath subset mentioned in documentation as\n"
+ " the Path format. The option can be combined with --single-node\n"
+ " option to print information only about the specified node.\n"
+ " -q, --single-node\n"
+ " Supplement to the --schema-node option to print information\n"
+ " only about a single node specified as PATH argument.\n"
+ " -o OUTFILE, --output=OUTFILE\n"
+ " Write the output to OUTFILE instead of stdout.\n");
+}
+
+static LY_ERR
+cmd_print_submodule(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options)
+{
+ LY_ERR erc;
+ const struct lysp_submodule *submodule;
+
+ submodule = revision ?
+ ly_ctx_get_submodule(*ctx, name, revision) :
+ ly_ctx_get_submodule_latest(*ctx, name);
+
+ erc = submodule ?
+ lys_print_submodule(out, submodule, format, line_length, options) :
+ LY_ENOTFOUND;
+
+ return erc;
+}
+
+static LY_ERR
+cmd_print_module(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options)
+{
+ LY_ERR erc;
+ struct lys_module *module;
+
+ module = revision ?
+ ly_ctx_get_module(*ctx, name, revision) :
+ ly_ctx_get_module_latest(*ctx, name);
+
+ erc = module ?
+ lys_print_module(out, module, format, line_length, options) :
+ LY_ENOTFOUND;
+
+ return erc;
+}
+
+static void
+cmd_print_modules(int argc, char **argv, struct ly_out *out, struct ly_ctx **ctx, LYS_OUTFORMAT format, size_t line_length, uint32_t options)
+{
+ LY_ERR erc;
+ char *name, *revision;
+ ly_bool search_submodul;
+ const int stop = argc - optind;
+
+ for (int i = 0; i < stop; i++) {
+ name = argv[optind + i];
+ /* get revision */
+ revision = strchr(name, '@');
+ if (revision) {
+ revision[0] = '\0';
+ ++revision;
+ }
+
+ erc = cmd_print_module(out, ctx, name, revision, format, line_length, options);
+
+ if (erc == LY_ENOTFOUND) {
+ search_submodul = 1;
+ erc = cmd_print_submodule(out, ctx, name, revision, format, line_length, options);
+ } else {
+ search_submodul = 0;
+ }
+
+ if (erc == LY_SUCCESS) {
+ /* for YANG Tree Diagrams printing it's more readable to print a blank line between modules. */
+ if ((format == LYS_OUT_TREE) && (i + 1 < stop)) {
+ ly_print(out, "\n");
+ }
+ continue;
+ } else if (erc == LY_ENOTFOUND) {
+ if (revision) {
+ YLMSG_E("No (sub)module \"%s\" in revision %s found.\n", name, revision);
+ } else {
+ YLMSG_E("No (sub)module \"%s\" found.\n", name);
+ }
+ break;
+ } else {
+ if (search_submodul) {
+ YLMSG_E("Unable to print submodule %s.\n", name);
+ } else {
+ YLMSG_E("Unable to print module %s.\n", name);
+ }
+ break;
+ }
+ }
+}
+
+void
+cmd_print(struct ly_ctx **ctx, const char *cmdline)
+{
+ int argc = 0;
+ char **argv = NULL;
+ int opt, opt_index;
+ struct option options[] = {
+ {"format", required_argument, NULL, 'f'},
+ {"help", no_argument, NULL, 'h'},
+ {"tree-line-length", required_argument, NULL, 'L'},
+ {"output", required_argument, NULL, 'o'},
+ {"schema-node", required_argument, NULL, 'P'},
+ {"single-node", no_argument, NULL, 'q'},
+ {NULL, 0, NULL, 0}
+ };
+ uint16_t options_print = 0;
+ const char *node_path = NULL;
+ LYS_OUTFORMAT format = LYS_OUT_TREE;
+ struct ly_out *out = NULL;
+ ly_bool out_stdout = 0;
+ size_t line_length = 0;
+
+ if (parse_cmdline(cmdline, &argc, &argv)) {
+ goto cleanup;
+ }
+
+ while ((opt = getopt_long(argc, argv, "f:hL:o:P:q", options, &opt_index)) != -1) {
+ switch (opt) {
+ case 'o': /* --output */
+ if (out) {
+ if (ly_out_filepath(out, optarg) != NULL) {
+ YLMSG_E("Unable to use output file %s for printing output.\n", optarg);
+ goto cleanup;
+ }
+ } else {
+ if (ly_out_new_filepath(optarg, &out)) {
+ YLMSG_E("Unable to use output file %s for printing output.\n", optarg);
+ goto cleanup;
+ }
+ }
+ break;
+
+ case 'f': /* --format */
+ if (!strcasecmp(optarg, "yang")) {
+ format = LYS_OUT_YANG;
+ } else if (!strcasecmp(optarg, "yin")) {
+ format = LYS_OUT_YIN;
+ } else if (!strcasecmp(optarg, "info")) {
+ format = LYS_OUT_YANG_COMPILED;
+ } else if (!strcasecmp(optarg, "tree")) {
+ format = LYS_OUT_TREE;
+ } else {
+ YLMSG_E("Unknown output format %s\n", optarg);
+ cmd_print_help();
+ goto cleanup;
+ }
+ break;
+
+ case 'L': /* --tree-line-length */
+ line_length = atoi(optarg);
+ break;
+
+ case 'P': /* --schema-node */
+ node_path = optarg;
+ break;
+
+ case 'q': /* --single-node */
+ options_print |= LYS_PRINT_NO_SUBSTMT;
+ break;
+
+ case 'h':
+ cmd_print_help();
+ goto cleanup;
+ default:
+ YLMSG_E("Unknown option.\n");
+ goto cleanup;
+ }
+ }
+
+ /* file name */
+ if ((argc == optind) && !node_path) {
+ YLMSG_E("Missing the name of the module to print.\n");
+ goto cleanup;
+ }
+
+ if ((format != LYS_OUT_TREE) && line_length) {
+ YLMSG_E("--tree-line-length take effect only in case of the tree output format.\n");
+ goto cleanup;
+ }
+
+ if (!out) {
+ if (ly_out_new_file(stdout, &out)) {
+ YLMSG_E("Could not use stdout to print output.\n");
+ goto cleanup;
+ }
+ out_stdout = 1;
+ }
+
+ if (format == LYS_OUT_TREE) {
+ /* print tree from lysc_nodes */
+ ly_ctx_set_options(*ctx, LY_CTX_SET_PRIV_PARSED);
+ }
+
+ if (node_path) {
+ const struct lysc_node *node;
+
+ node = find_schema_path(*ctx, node_path);
+ if (!node) {
+ YLMSG_E("The requested schema node \"%s\" does not exists.\n", node_path);
+ goto cleanup;
+ }
+
+ if (lys_print_node(out, node, format, 0, options_print)) {
+ YLMSG_E("Unable to print schema node %s.\n", node_path);
+ goto cleanup;
+ }
+ } else {
+ cmd_print_modules(argc, argv, out, ctx, format, line_length, options_print);
+ goto cleanup;
+ }
+
+cleanup:
+ free_cmdline(argv);
+ ly_out_free(out, NULL, out_stdout ? 0 : 1);
+}
diff --git a/tools/lint/cmd_searchpath.c b/tools/lint/cmd_searchpath.c
new file mode 100644
index 0000000..529e05d
--- /dev/null
+++ b/tools/lint/cmd_searchpath.c
@@ -0,0 +1,90 @@
+/**
+ * @file cmd_searchpath.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief 'searchpath' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2015-2020 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
+ */
+
+#define _GNU_SOURCE
+
+#include "cmd.h"
+
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "libyang.h"
+
+#include "common.h"
+
+void
+cmd_searchpath_help(void)
+{
+ printf("Usage: searchpath [--clear] [<modules-dir-path> ...]\n"
+ " Set paths of directories where to search for imports and\n"
+ " includes of the schema modules. The current working directory\n"
+ " and the path of the module being added is used implicitly.\n"
+ " The 'load' command uses these paths to search even for the\n"
+ " schema modules to be loaded.\n");
+}
+
+void
+cmd_searchpath(struct ly_ctx **ctx, const char *cmdline)
+{
+ int argc = 0;
+ char **argv = NULL;
+ int opt, opt_index;
+ struct option options[] = {
+ {"clear", no_argument, NULL, 'c'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+ int8_t cleared = 0;
+
+ if (parse_cmdline(cmdline, &argc, &argv)) {
+ goto cleanup;
+ }
+
+ while ((opt = getopt_long(argc, argv, "ch", options, &opt_index)) != -1) {
+ switch (opt) {
+ case 'c':
+ ly_ctx_unset_searchdir(*ctx, NULL);
+ cleared = 1;
+ break;
+ case 'h':
+ cmd_searchpath_help();
+ goto cleanup;
+ default:
+ YLMSG_E("Unknown option.\n");
+ goto cleanup;
+ }
+ }
+
+ if (!cleared && (argc == optind)) {
+ /* no argument - print the paths */
+ const char * const *dirs = ly_ctx_get_searchdirs(*ctx);
+
+ printf("List of the searchpaths:\n");
+ for (uint32_t i = 0; dirs[i]; ++i) {
+ printf(" %s\n", dirs[i]);
+ }
+ goto cleanup;
+ }
+
+ for (int i = 0; i < argc - optind; i++) {
+ if (ly_ctx_set_searchdir(*ctx, argv[optind + i])) {
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ free_cmdline(argv);
+}
diff --git a/tools/lint/common.c b/tools/lint/common.c
new file mode 100644
index 0000000..fc9b1cd
--- /dev/null
+++ b/tools/lint/common.c
@@ -0,0 +1,868 @@
+/**
+ * @file common.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief libyang's yanglint tool - common functions for both interactive and non-interactive mode.
+ *
+ * Copyright (c) 2020 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
+ */
+
+#define _GNU_SOURCE
+#define _POSIX_C_SOURCE 200809L /* strdup, strndup */
+
+#include "common.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "compat.h"
+#include "libyang.h"
+
+int
+parse_schema_path(const char *path, char **dir, char **module)
+{
+ char *p;
+
+ assert(dir);
+ assert(module);
+
+ /* split the path to dirname and basename for further work */
+ *dir = strdup(path);
+ *module = strrchr(*dir, '/');
+ if (!(*module)) {
+ *module = *dir;
+ *dir = strdup("./");
+ } else {
+ *module[0] = '\0'; /* break the dir */
+ *module = strdup((*module) + 1);
+ }
+ /* get the pure module name without suffix or revision part of the filename */
+ if ((p = strchr(*module, '@'))) {
+ /* revision */
+ *p = '\0';
+ } else if ((p = strrchr(*module, '.'))) {
+ /* fileformat suffix */
+ *p = '\0';
+ }
+
+ return 0;
+}
+
+int
+get_input(const char *filepath, LYS_INFORMAT *format_schema, LYD_FORMAT *format_data, struct ly_in **in)
+{
+ struct stat st;
+
+ /* check that the filepath exists and is a regular file */
+ if (stat(filepath, &st) == -1) {
+ YLMSG_E("Unable to use input filepath (%s) - %s.\n", filepath, strerror(errno));
+ return -1;
+ }
+ if (!S_ISREG(st.st_mode)) {
+ YLMSG_E("Provided input file (%s) is not a regular file.\n", filepath);
+ return -1;
+ }
+
+ if ((format_schema && !*format_schema) || (format_data && !*format_data)) {
+ /* get the file format */
+ if (get_format(filepath, format_schema, format_data)) {
+ return -1;
+ }
+ }
+
+ if (ly_in_new_filepath(filepath, 0, in)) {
+ YLMSG_E("Unable to process input file.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+free_features(void *flist)
+{
+ struct schema_features *rec = (struct schema_features *)flist;
+
+ if (rec) {
+ free(rec->mod_name);
+ if (rec->features) {
+ for (uint32_t u = 0; rec->features[u]; ++u) {
+ free(rec->features[u]);
+ }
+ free(rec->features);
+ }
+ free(rec);
+ }
+}
+
+void
+get_features(struct ly_set *fset, const char *module, const char ***features)
+{
+ /* get features list for this module */
+ for (uint32_t u = 0; u < fset->count; ++u) {
+ struct schema_features *sf = (struct schema_features *)fset->objs[u];
+
+ if (!strcmp(module, sf->mod_name)) {
+ /* matched module - explicitly set features */
+ *features = (const char **)sf->features;
+ sf->applied = 1;
+ return;
+ }
+ }
+
+ /* features not set so disable all */
+ *features = NULL;
+}
+
+int
+parse_features(const char *fstring, struct ly_set *fset)
+{
+ struct schema_features *rec;
+ uint32_t count;
+ char *p, **fp;
+
+ rec = calloc(1, sizeof *rec);
+ if (!rec) {
+ YLMSG_E("Unable to allocate features information record (%s).\n", strerror(errno));
+ return -1;
+ }
+ if (ly_set_add(fset, rec, 1, NULL)) {
+ YLMSG_E("Unable to store features information (%s).\n", strerror(errno));
+ free(rec);
+ return -1;
+ }
+
+ /* fill the record */
+ p = strchr(fstring, ':');
+ if (!p) {
+ YLMSG_E("Invalid format of the features specification (%s).\n", fstring);
+ return -1;
+ }
+ rec->mod_name = strndup(fstring, p - fstring);
+
+ count = 0;
+ while (p) {
+ size_t len = 0;
+ char *token = p + 1;
+
+ p = strchr(token, ',');
+ if (!p) {
+ /* the last item, if any */
+ len = strlen(token);
+ } else {
+ len = p - token;
+ }
+
+ if (len) {
+ fp = realloc(rec->features, (count + 1) * sizeof *rec->features);
+ if (!fp) {
+ YLMSG_E("Unable to store features list information (%s).\n", strerror(errno));
+ return -1;
+ }
+ rec->features = fp;
+ fp = &rec->features[count++]; /* array item to set */
+ (*fp) = strndup(token, len);
+ }
+ }
+
+ /* terminating NULL */
+ fp = realloc(rec->features, (count + 1) * sizeof *rec->features);
+ if (!fp) {
+ YLMSG_E("Unable to store features list information (%s).\n", strerror(errno));
+ return -1;
+ }
+ rec->features = fp;
+ rec->features[count++] = NULL;
+
+ return 0;
+}
+
+struct cmdline_file *
+fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format)
+{
+ struct cmdline_file *rec;
+
+ rec = malloc(sizeof *rec);
+ if (!rec) {
+ YLMSG_E("Allocating memory for data file information failed.\n");
+ return NULL;
+ }
+ rec->in = in;
+ rec->path = path;
+ rec->format = format;
+
+ if (set && ly_set_add(set, rec, 1, NULL)) {
+ free(rec);
+ YLMSG_E("Storing data file information failed.\n");
+ return NULL;
+ }
+
+ return rec;
+}
+
+int
+collect_features(const struct lys_module *mod, struct ly_set *set)
+{
+ struct lysp_feature *f = NULL;
+ uint32_t idx = 0;
+
+ while ((f = lysp_feature_next(f, mod->parsed, &idx))) {
+ if (ly_set_add(set, (void *)f->name, 1, NULL)) {
+ YLMSG_E("Memory allocation failed.\n");
+ ly_set_erase(set, NULL);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void
+print_features(struct ly_out *out, const struct lys_module *mod, const struct ly_set *set)
+{
+ size_t max_len;
+ uint32_t j;
+ const char *name;
+
+ /* header */
+ ly_print(out, "%s:\n", mod->name);
+
+ /* no features */
+ if (!set->count) {
+ ly_print(out, "\t(none)\n\n");
+ return;
+ }
+
+ /* get max len, so the statuses of all the features will be aligned */
+ max_len = 0;
+ for (j = 0; j < set->count; ++j) {
+ name = set->objs[j];
+ if (strlen(name) > max_len) {
+ max_len = strlen(name);
+ }
+ }
+
+ /* print features */
+ for (j = 0; j < set->count; ++j) {
+ name = set->objs[j];
+ ly_print(out, "\t%-*s (%s)\n", (int)max_len, name, lys_feature_value(mod, name) ? "off" : "on");
+ }
+
+ ly_print(out, "\n");
+}
+
+int
+generate_features_output(const struct lys_module *mod, const struct ly_set *set, char **features_param)
+{
+ uint32_t j;
+ /*
+ * features_len - length of all the features in the current module
+ * added_len - length of a string to be added, = features_len + extra necessary length
+ * param_len - length of the parameter before appending new string
+ */
+ size_t features_len, added_len, param_len;
+ char *tmp;
+
+ features_len = 0;
+ for (j = 0; j < set->count; j++) {
+ features_len += strlen(set->objs[j]);
+ }
+
+ if (j == 0) {
+ /* no features */
+ added_len = strlen("-F ") + strlen(mod->name) + strlen(":");
+ } else {
+ /* j = comma count, -1 because of trailing comma */
+ added_len = strlen("-F ") + strlen(mod->name) + strlen(":") + features_len + j - 1;
+ }
+
+ /* to avoid strlen(NULL) if this is the first call */
+ param_len = 0;
+ if (*features_param) {
+ param_len = strlen(*features_param);
+ }
+
+ /* +1 because of white space at the beginning */
+ tmp = realloc(*features_param, param_len + added_len + 1 + 1);
+ if (!tmp) {
+ goto error;
+ } else {
+ *features_param = tmp;
+ }
+ sprintf(*features_param + param_len, " -F %s:", mod->name);
+
+ for (j = 0; j < set->count; j++) {
+ strcat(*features_param, set->objs[j]);
+ /* no trailing comma */
+ if (j != (set->count - 1)) {
+ strcat(*features_param, ",");
+ }
+ }
+
+ return 0;
+
+error:
+ YLMSG_E("Memory allocation failed (%s:%d, %s).\n", __FILE__, __LINE__, strerror(errno));
+ return 1;
+}
+
+int
+print_all_features(struct ly_out *out, const struct ly_ctx *ctx, ly_bool generate_features, char **features_param)
+{
+ int ret = 0;
+ uint32_t i = 0;
+ struct lys_module *mod;
+ struct ly_set set = {0};
+
+ while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) {
+ /* only care about implemented modules */
+ if (!mod->implemented) {
+ continue;
+ }
+
+ /* always erase the set, so the previous module's features don't carry over to the next module's features */
+ ly_set_erase(&set, NULL);
+
+ if (collect_features(mod, &set)) {
+ ret = 1;
+ goto cleanup;
+ }
+
+ if (generate_features && generate_features_output(mod, &set, features_param)) {
+ ret = 1;
+ goto cleanup;
+ }
+ print_features(out, mod, &set);
+ }
+
+cleanup:
+ ly_set_erase(&set, NULL);
+ return ret;
+}
+
+void
+free_cmdline_file(void *cmdline_file)
+{
+ struct cmdline_file *rec = (struct cmdline_file *)cmdline_file;
+
+ if (rec) {
+ ly_in_free(rec->in, 1);
+ free(rec);
+ }
+}
+
+void
+free_cmdline(char *argv[])
+{
+ if (argv) {
+ free(argv[0]);
+ free(argv);
+ }
+}
+
+int
+parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[])
+{
+ int count;
+ char **vector;
+ char *ptr;
+ char qmark = 0;
+
+ assert(cmdline);
+ assert(argc_p);
+ assert(argv_p);
+
+ /* init */
+ optind = 0; /* reinitialize getopt() */
+ count = 1;
+ vector = malloc((count + 1) * sizeof *vector);
+ vector[0] = strdup(cmdline);
+
+ /* command name */
+ strtok(vector[0], " ");
+
+ /* arguments */
+ while ((ptr = strtok(NULL, " "))) {
+ size_t len;
+ void *r;
+
+ len = strlen(ptr);
+
+ if (qmark) {
+ /* still in quotated text */
+ /* remove NULL termination of the previous token since it is not a token,
+ * but a part of the quotation string */
+ ptr[-1] = ' ';
+
+ if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
+ /* end of quotation */
+ qmark = 0;
+ /* shorten the argument by the terminating quotation mark */
+ ptr[len - 1] = '\0';
+ }
+ continue;
+ }
+
+ /* another token in cmdline */
+ ++count;
+ r = realloc(vector, (count + 1) * sizeof *vector);
+ if (!r) {
+ YLMSG_E("Memory allocation failed (%s:%d, %s).\n", __FILE__, __LINE__, strerror(errno));
+ free(vector);
+ return -1;
+ }
+ vector = r;
+ vector[count - 1] = ptr;
+
+ if ((ptr[0] == '"') || (ptr[0] == '\'')) {
+ /* remember the quotation mark to identify end of quotation */
+ qmark = ptr[0];
+
+ /* move the remembered argument after the quotation mark */
+ ++vector[count - 1];
+
+ /* check if the quotation is terminated within this token */
+ if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
+ /* end of quotation */
+ qmark = 0;
+ /* shorten the argument by the terminating quotation mark */
+ ptr[len - 1] = '\0';
+ }
+ }
+ }
+ vector[count] = NULL;
+
+ *argc_p = count;
+ *argv_p = vector;
+
+ return 0;
+}
+
+int
+get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data)
+{
+ char *ptr;
+ LYS_INFORMAT informat_s;
+ LYD_FORMAT informat_d;
+
+ /* get the file format */
+ if ((ptr = strrchr(filename, '.')) != NULL) {
+ ++ptr;
+ if (!strcmp(ptr, "yang")) {
+ informat_s = LYS_IN_YANG;
+ 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;
+ } else if (!strcmp(ptr, "lyb")) {
+ informat_s = 0;
+ informat_d = LYD_LYB;
+ } else {
+ YLMSG_E("Input file \"%s\" in an unknown format \"%s\".\n", filename, ptr);
+ return 0;
+ }
+ } else {
+ YLMSG_E("Input file \"%s\" without file extension - unknown format.\n", filename);
+ return 1;
+ }
+
+ if (informat_d) {
+ if (!data) {
+ YLMSG_E("Input file \"%s\" not expected to contain data instances (unexpected format).\n", filename);
+ return 2;
+ }
+ (*data) = informat_d;
+ } else if (informat_s) {
+ if (!schema) {
+ YLMSG_E("Input file \"%s\" not expected to contain schema definition (unexpected format).\n", filename);
+ return 3;
+ }
+ (*schema) = informat_s;
+ }
+
+ return 0;
+}
+
+int
+print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat)
+{
+ struct lyd_node *ylib;
+ uint32_t idx = 0, has_modules = 0;
+ const struct lys_module *mod;
+
+ if (outformat != LYD_UNKNOWN) {
+ if (ly_ctx_get_yanglib_data(ctx, &ylib, "%u", ly_ctx_get_change_count(ctx))) {
+ YLMSG_E("Getting context info (ietf-yang-library data) failed. If the YANG module is missing or not implemented, use an option to add it internally.\n");
+ return 1;
+ }
+
+ lyd_print_all(out, ylib, outformat, 0);
+ lyd_free_all(ylib);
+ return 0;
+ }
+
+ /* iterate schemas in context and provide just the basic info */
+ ly_print(out, "List of the loaded models:\n");
+ while ((mod = ly_ctx_get_module_iter(ctx, &idx))) {
+ has_modules++;
+
+ /* conformance print */
+ if (mod->implemented) {
+ ly_print(out, " I");
+ } else {
+ ly_print(out, " i");
+ }
+
+ /* module print */
+ ly_print(out, " %s", mod->name);
+ if (mod->revision) {
+ ly_print(out, "@%s", mod->revision);
+ }
+
+ /* submodules print */
+ if (mod->parsed && mod->parsed->includes) {
+ uint64_t u = 0;
+
+ ly_print(out, " (");
+ LY_ARRAY_FOR(mod->parsed->includes, u) {
+ ly_print(out, "%s%s", !u ? "" : ",", mod->parsed->includes[u].name);
+ if (mod->parsed->includes[u].rev[0]) {
+ ly_print(out, "@%s", mod->parsed->includes[u].rev);
+ }
+ }
+ ly_print(out, ")");
+ }
+
+ /* finish the line */
+ ly_print(out, "\n");
+ }
+
+ if (!has_modules) {
+ ly_print(out, "\t(none)\n");
+ }
+
+ ly_print_flush(out);
+ return 0;
+}
+
+int
+evaluate_xpath(const struct lyd_node *tree, const char *xpath)
+{
+ struct ly_set *set = NULL;
+
+ if (lyd_find_xpath(tree, xpath, &set)) {
+ return -1;
+ }
+
+ /* print result */
+ printf("XPath \"%s\" evaluation result:\n", xpath);
+ if (!set->count) {
+ printf("\tEmpty\n");
+ } else {
+ for (uint32_t u = 0; u < set->count; ++u) {
+ struct lyd_node *node = (struct lyd_node *)set->objs[u];
+
+ printf(" %s \"%s\"", lys_nodetype2str(node->schema->nodetype), node->schema->name);
+ if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
+ printf(" (value: \"%s\")\n", lyd_get_value(node));
+ } else if (node->schema->nodetype == LYS_LIST) {
+ printf(" (");
+ for (struct lyd_node *key = ((struct lyd_node_inner *)node)->child; key && lysc_is_key(key->schema); key = key->next) {
+ printf("%s\"%s\": \"%s\";", (key != ((struct lyd_node_inner *)node)->child) ? " " : "",
+ key->schema->name, lyd_get_value(key));
+ }
+ printf(")\n");
+ }
+ }
+ }
+
+ ly_set_free(set, NULL);
+ return 0;
+}
+
+LY_ERR
+process_data(struct ly_ctx *ctx, enum lyd_type data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
+ uint32_t options_parse, uint32_t options_validate, uint32_t options_print, struct cmdline_file *operational_f,
+ struct cmdline_file *rpc_f, struct ly_set *inputs, struct ly_set *xpaths)
+{
+ LY_ERR ret = LY_SUCCESS;
+ struct lyd_node *tree = NULL, *op = NULL, *envp = NULL, *merged_tree = NULL, *oper_tree = NULL;
+ char *path = NULL;
+ struct ly_set *set = NULL;
+
+ /* additional operational datastore */
+ if (operational_f && operational_f->in) {
+ ret = lyd_parse_data(ctx, NULL, operational_f->in, operational_f->format, LYD_PARSE_ONLY, 0, &oper_tree);
+ if (ret) {
+ YLMSG_E("Failed to parse operational datastore file \"%s\".\n", operational_f->path);
+ goto cleanup;
+ }
+ }
+
+ for (uint32_t u = 0; u < inputs->count; ++u) {
+ struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u];
+
+ switch (data_type) {
+ case LYD_TYPE_DATA_YANG:
+ ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, options_parse, options_validate, &tree);
+ break;
+ case LYD_TYPE_RPC_YANG:
+ case LYD_TYPE_REPLY_YANG:
+ case LYD_TYPE_NOTIF_YANG:
+ ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, data_type, &tree, &op);
+ break;
+ case LYD_TYPE_RPC_NETCONF:
+ case LYD_TYPE_NOTIF_NETCONF:
+ ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, data_type, &envp, &op);
+
+ /* adjust pointers */
+ for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {}
+ break;
+ case LYD_TYPE_REPLY_NETCONF:
+ /* parse source RPC operation */
+ assert(rpc_f && rpc_f->in);
+ ret = lyd_parse_op(ctx, NULL, rpc_f->in, rpc_f->format, LYD_TYPE_RPC_NETCONF, &envp, &op);
+ if (ret) {
+ YLMSG_E("Failed to parse source NETCONF RPC operation file \"%s\".\n", rpc_f->path);
+ goto cleanup;
+ }
+
+ /* adjust pointers */
+ for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {}
+
+ /* free input */
+ lyd_free_siblings(lyd_child(op));
+
+ /* we do not care */
+ lyd_free_all(envp);
+ envp = NULL;
+
+ ret = lyd_parse_op(ctx, op, input_f->in, input_f->format, data_type, &envp, NULL);
+ break;
+ default:
+ YLMSG_E("Internal error (%s:%d).\n", __FILE__, __LINE__);
+ goto cleanup;
+ }
+
+ if (ret) {
+ YLMSG_E("Failed to parse input data file \"%s\".\n", input_f->path);
+ goto cleanup;
+ }
+
+ if (merge) {
+ /* merge the data so far parsed for later validation and print */
+ if (!merged_tree) {
+ merged_tree = tree;
+ } else {
+ ret = lyd_merge_siblings(&merged_tree, tree, LYD_MERGE_DESTRUCT);
+ if (ret) {
+ YLMSG_E("Merging %s with previous data failed.\n", input_f->path);
+ goto cleanup;
+ }
+ }
+ tree = NULL;
+ } else if (format) {
+ /* print */
+ switch (data_type) {
+ case LYD_TYPE_DATA_YANG:
+ lyd_print_all(out, tree, format, options_print);
+ break;
+ case LYD_TYPE_RPC_YANG:
+ case LYD_TYPE_REPLY_YANG:
+ case LYD_TYPE_NOTIF_YANG:
+ case LYD_TYPE_RPC_NETCONF:
+ case LYD_TYPE_NOTIF_NETCONF:
+ lyd_print_tree(out, tree, format, options_print);
+ break;
+ case LYD_TYPE_REPLY_NETCONF:
+ /* just the output */
+ lyd_print_tree(out, lyd_child(tree), format, options_print);
+ break;
+ default:
+ assert(0);
+ }
+ } else {
+ /* validation of the RPC/Action/reply/Notification with the operational datastore, if any */
+ switch (data_type) {
+ case LYD_TYPE_DATA_YANG:
+ /* already validated */
+ break;
+ case LYD_TYPE_RPC_YANG:
+ case LYD_TYPE_RPC_NETCONF:
+ ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_RPC_YANG, NULL);
+ break;
+ case LYD_TYPE_REPLY_YANG:
+ case LYD_TYPE_REPLY_NETCONF:
+ ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_REPLY_YANG, NULL);
+ break;
+ case LYD_TYPE_NOTIF_YANG:
+ case LYD_TYPE_NOTIF_NETCONF:
+ ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_NOTIF_YANG, NULL);
+ break;
+ default:
+ assert(0);
+ }
+ if (ret) {
+ if (operational_f->path) {
+ YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".\n",
+ input_f->path, operational_f->path);
+ } else {
+ YLMSG_E("Failed to validate input data file \"%s\".\n", input_f->path);
+ }
+ goto cleanup;
+ }
+
+ if (op && oper_tree && lyd_parent(op)) {
+ /* check operation parent existence */
+ path = lyd_path(lyd_parent(op), LYD_PATH_STD, NULL, 0);
+ if (!path) {
+ ret = LY_EMEM;
+ goto cleanup;
+ }
+ if ((ret = lyd_find_xpath(oper_tree, path, &set))) {
+ goto cleanup;
+ }
+ if (!set->count) {
+ YLMSG_E("Operation \"%s\" parent \"%s\" not found in the operational data.\n", LYD_NAME(op), path);
+ ret = LY_EVALID;
+ goto cleanup;
+ }
+ }
+ }
+
+ /* next iter */
+ lyd_free_all(tree);
+ tree = NULL;
+ lyd_free_all(envp);
+ envp = NULL;
+ }
+
+ if (merge) {
+ /* validate the merged result */
+ ret = lyd_validate_all(&merged_tree, ctx, LYD_VALIDATE_PRESENT, NULL);
+ if (ret) {
+ YLMSG_E("Merged data are not valid.\n");
+ goto cleanup;
+ }
+
+ if (format) {
+ /* and print it */
+ lyd_print_all(out, merged_tree, format, options_print);
+ }
+
+ for (uint32_t u = 0; xpaths && (u < xpaths->count); ++u) {
+ if (evaluate_xpath(merged_tree, (const char *)xpaths->objs[u])) {
+ goto cleanup;
+ }
+ }
+ }
+
+cleanup:
+ lyd_free_all(tree);
+ lyd_free_all(envp);
+ lyd_free_all(merged_tree);
+ lyd_free_all(oper_tree);
+ free(path);
+ ly_set_free(set, NULL);
+ return ret;
+}
+
+const struct lysc_node *
+find_schema_path(const struct ly_ctx *ctx, const char *schema_path)
+{
+ const char *end, *module_name_end;
+ char *module_name = NULL;
+ const struct lysc_node *node = NULL, *parent_node = NULL, *parent_node_tmp = NULL;
+ const struct lys_module *module;
+ size_t node_name_len;
+ ly_bool found_exact_match = 0;
+
+ /* iterate over each '/' in the path */
+ while (schema_path) {
+ /* example: schema_path = /listen/endpoint
+ * end == NULL for endpoint, end exists for listen */
+ end = strchr(schema_path + 1, '/');
+ if (end) {
+ node_name_len = end - schema_path - 1;
+ } else {
+ node_name_len = strlen(schema_path + 1);
+ }
+
+ /* ex: schema_path = /ietf-interfaces:interfaces/interface/ietf-ip:ipv4 */
+ module_name_end = strchr(schema_path, ':');
+ if (module_name_end && (!end || (module_name_end < end))) {
+ /* only get module's name, if it is in the current scope */
+ free(module_name);
+ /* - 1 because module_name_end points to ':' */
+ module_name = strndup(schema_path + 1, module_name_end - schema_path - 1);
+ if (!module_name) {
+ YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
+ parent_node = NULL;
+ goto cleanup;
+ }
+ /* move the pointer to the beginning of the node's name - 1 */
+ schema_path = module_name_end;
+
+ /* recalculate the length of the node's name, because the module prefix mustn't be compared later */
+ if (module_name_end < end) {
+ node_name_len = end - module_name_end - 1;
+ } else if (!end) {
+ node_name_len = strlen(module_name_end + 1);
+ }
+ }
+
+ module = ly_ctx_get_module_implemented(ctx, module_name);
+ if (!module) {
+ /* unknown module name */
+ parent_node = NULL;
+ goto cleanup;
+ }
+
+ /* iterate over the node's siblings / module's top level containers */
+ while ((node = lys_getnext(node, parent_node, module->compiled, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) {
+ if (end && !strncmp(node->name, schema_path + 1, node_name_len) && (node->name[node_name_len] == '\0')) {
+ /* check if the whole node's name matches and it's not just a common prefix */
+ parent_node = node;
+ break;
+ } else if (!strncmp(node->name, schema_path + 1, node_name_len)) {
+ /* do the same here, however if there is no exact match, use the last node with the same prefix */
+ if (strlen(node->name) == node_name_len) {
+ parent_node = node;
+ found_exact_match = 1;
+ break;
+ } else {
+ parent_node_tmp = node;
+ }
+ }
+ }
+
+ if (!end && !found_exact_match) {
+ /* no exact match */
+ parent_node = parent_node_tmp;
+ }
+ found_exact_match = 0;
+
+ /* next iter */
+ schema_path = strchr(schema_path + 1, '/');
+ }
+
+cleanup:
+ free(module_name);
+ return parent_node;
+}
diff --git a/tools/lint/common.h b/tools/lint/common.h
new file mode 100644
index 0000000..7c6a8ad
--- /dev/null
+++ b/tools/lint/common.h
@@ -0,0 +1,257 @@
+/**
+ * @file common.h
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief libyang's yanglint tool - common functions and definitions for both interactive and non-interactive mode.
+ *
+ * Copyright (c) 2020 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
+ */
+
+#ifndef COMMON_H_
+#define COMMON_H_
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include "libyang.h"
+
+#define PROMPT "> "
+
+/**
+ * @brief Default context creation options.
+ */
+#define YL_DEFAULT_CTX_OPTIONS LY_CTX_NO_YANGLIBRARY
+
+/**
+ * @brief Default data parsing flags.
+ */
+#define YL_DEFAULT_DATA_PARSE_OPTIONS LYD_PARSE_STRICT
+
+/**
+ * @brief log error message
+ */
+#define YLMSG_E(...) \
+ fprintf(stderr, "YANGLINT[E]: " __VA_ARGS__)
+
+/**
+ * @brief log warning message
+ */
+#define YLMSG_W(...) \
+ fprintf(stderr, "YANGLINT[W]: " __VA_ARGS__)
+
+#ifndef _WIN32
+# define PATH_SEPARATOR ":"
+#else
+# define PATH_SEPARATOR ";"
+#endif
+
+/**
+ * @brief Storage for the list of the features (their names) in a specific YANG module.
+ */
+struct schema_features {
+ char *mod_name;
+ char **features;
+ ly_bool applied;
+};
+
+/**
+ * @brief Data connected with a file provided on a command line as a file path.
+ */
+struct cmdline_file {
+ struct ly_in *in;
+ const char *path;
+ LYD_FORMAT format;
+};
+
+/**
+ * @brief Free the schema features list (struct schema_features *)
+ * @param[in,out] flist The (struct schema_features *) to free.
+ */
+void free_features(void *flist);
+
+/**
+ * @brief Get the list of features connected with the specific YANG module.
+ *
+ * @param[in] fset The set of features information (struct schema_features *).
+ * @param[in] module Name of the YANG module which features should be found.
+ * @param[out] features Pointer to the list of features being returned.
+ */
+void get_features(struct ly_set *fset, const char *module, const char ***features);
+
+/**
+ * @brief Parse features being specified for the specific YANG module.
+ *
+ * Format of the input @p fstring is as follows: <module_name>:[<feature>,]*
+ *
+ * @param[in] fstring Input string to be parsed.
+ * @param[in, out] fset Features information set (of struct schema_features *). The set is being filled.
+ */
+int parse_features(const char *fstring, struct ly_set *fset);
+
+/**
+ * @brief Collect all features of a module.
+ *
+ * @param[in] mod Module to be searched for features.
+ * @param[out] set Set in which the features will be stored.
+ * @return 0 on success.
+ * @return 1 on error.
+ */
+int collect_features(const struct lys_module *mod, struct ly_set *set);
+
+/**
+ * @brief Print all features of a single module.
+ *
+ * @param[in] out The output handler for printing.
+ * @param[in] mod Module which contains the features.
+ * @param[in] set Set which holds the features.
+ */
+void print_features(struct ly_out *out, const struct lys_module *mod, const struct ly_set *set);
+
+/**
+ * @brief Generate a string, which will contain features paramater.
+ *
+ * @param[in] mod Module, for which the string will be generated.
+ * @param[in] set Set containing the features.
+ * @param[out] features_param String which will contain the output.
+ * @return 0 on success.
+ * @return 1 on error.
+ */
+int generate_features_output(const struct lys_module *mod, const struct ly_set *set, char **features_param);
+
+/**
+ * @brief Print all features of all implemented modules.
+ *
+ * @param[in] out The output handler for printing.
+ * @param[in] ctx Libyang context.
+ * @param[in] generate_features Flag expressing whether to generate features parameter.
+ * @param[out] features_param String, which will contain the output if the above flag is set.
+ * @return 0 on success.
+ * @return 1 on error.
+ */
+int print_all_features(struct ly_out *out, const struct ly_ctx *ctx, ly_bool generate_features, char **features_param);
+
+/**
+ * @brief Parse path of a schema module file into the directory and module name.
+ *
+ * @param[in] path Schema module file path to be parsed.
+ * @param[out] dir Pointer to the directory path where the file resides. Caller is expected to free the returned string.
+ * @param[out] module Pointer to the name of the module (without file suffixes or revision information) specified by the
+ * @path. Caller is expected to free the returned string.
+ * @return 0 on success
+ * @return -1 on error
+ */
+int parse_schema_path(const char *path, char **dir, char **module);
+
+/**
+ * @brief Get input handler for the specified path.
+ *
+ * Using the @p format_schema and @p format_data the type of the file can be limited (by providing NULL) or it can be
+ * got known if both types are possible.
+ *
+ * @param[in] filepath Path of the file to open.
+ * @param[out] format_schema Format of the schema detected from the file name. If NULL specified, the schema formats are
+ * prohibited and such files are refused.
+ * @param[out] format_data Format of the data detected from the file name. If NULL specified, the data formats are
+ * prohibited and such files are refused.
+ * @param[out] in Created input handler referring the file behind the @p filepath.
+ * @return 0 on success.
+ * @return -1 on failure.
+ */
+int get_input(const char *filepath, LYS_INFORMAT *format_schema, LYD_FORMAT *format_data, struct ly_in **in);
+
+/**
+ * @brief Free the command line file data (struct cmdline_file *)
+ * @param[in,out] cmdline_file The (struct cmdline_file *) to free.
+ */
+void free_cmdline_file(void *cmdline_file);
+
+/**
+ * @brief Create and fill the command line file data (struct cmdline_file *).
+ * @param[in] set Optional parameter in case the record is supposed to be added into a set.
+ * @param[in] in Input file handler.
+ * @param[in] path Filepath of the file.
+ * @param[in] format Format of the data file.
+ * @return The created command line file structure.
+ * @return NULL on failure
+ */
+struct cmdline_file *fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format);
+
+/**
+ * @brief Helper function to prepare argc, argv pair from a command line string.
+ *
+ * @param[in] cmdline Complete command line string.
+ * @param[out] argc_p Pointer to store argc value.
+ * @param[out] argv_p Pointer to store argv vector.
+ * @return 0 on success, non-zero on failure.
+ */
+int parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[]);
+
+/**
+ * @brief Destructor for the argument vector prepared by ::parse_cmdline().
+ *
+ * @param[in,out] argv Argument vector to destroy.
+ */
+void free_cmdline(char *argv[]);
+
+/**
+ * @brief Get expected format of the @p filename's content according to the @p filename's suffix.
+ * @param[in] filename Name of the file to examine.
+ * @param[out] schema Pointer to a variable to store the expected input schema format. Do not provide the pointer in case a
+ * schema format is not expected.
+ * @param[out] data Pointer to a variable to store the expected input data format. Do not provide the pointer in case a data
+ * format is not expected.
+ * @return zero in case a format was successfully detected.
+ * @return nonzero in case it is not possible to get valid format from the @p filename.
+ */
+int get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data);
+
+/**
+ * @brief Print list of schemas in the context.
+ *
+ * @param[in] out Output handler where to print.
+ * @param[in] ctx Context to print.
+ * @param[in] outformat Optional output format. If not specified (:LYD_UNKNOWN), a simple list with single module per line
+ * is printed. Otherwise, the ietf-yang-library data are printed in the specified format.
+ * @return zero in case the data successfully printed.
+ * @return nonzero in case of error.
+ */
+int print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat);
+
+/**
+ * @brief Process the input data files - parse, validate and print according to provided options.
+ *
+ * @param[in] ctx libyang context with schema.
+ * @param[in] data_type The type of data in the input files.
+ * @param[in] merge Flag if the data should be merged before validation.
+ * @param[in] format Data format for printing.
+ * @param[in] out The output handler for printing.
+ * @param[in] options_parse Parser options.
+ * @param[in] options_validate Validation options.
+ * @param[in] options_print Printer options.
+ * @param[in] operational_f Optional operational datastore file information for the case of an extended validation of
+ * operation(s).
+ * @param[in] rpc_f Source RPC operation file information for parsing NETCONF rpc-reply.
+ * @param[in] inputs Set of file informations of input data files.
+ * @param[in] xpath The set of XPaths to be evaluated on the processed data tree, basic information about the resulting set
+ * is printed. Alternative to data printing.
+ * @return LY_ERR value.
+ */
+LY_ERR process_data(struct ly_ctx *ctx, enum lyd_type data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
+ uint32_t options_parse, uint32_t options_validate, uint32_t options_print, struct cmdline_file *operational_f,
+ struct cmdline_file *rpc_f, struct ly_set *inputs, struct ly_set *xpaths);
+
+/**
+ * @brief Get the node specified by the path.
+ *
+ * @param[in] ctx libyang context with schema.
+ * @param[in] schema_path Path to the wanted node.
+ * @return Pointer to the schema node specified by the path on success, NULL otherwise.
+ */
+const struct lysc_node * find_schema_path(const struct ly_ctx *ctx, const char *schema_path);
+
+#endif /* COMMON_H_ */
diff --git a/tools/lint/completion.c b/tools/lint/completion.c
new file mode 100644
index 0000000..9843816
--- /dev/null
+++ b/tools/lint/completion.c
@@ -0,0 +1,379 @@
+/**
+ * @file completion.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief libyang's yanglint tool auto completion
+ *
+ * Copyright (c) 2015 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
+ */
+
+#define _GNU_SOURCE
+#define _POSIX_C_SOURCE 200809L /* strdup */
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libyang.h"
+
+#include "cmd.h"
+#include "common.h"
+#include "compat.h"
+#include "linenoise/linenoise.h"
+
+/* from the main.c */
+extern struct ly_ctx *ctx;
+
+/**
+ * @brief Add a match to the completions array.
+ *
+ * @param[in] match Match to be added.
+ * @param[in,out] matches Matches provided to the user as a completion hint.
+ * @param[in,out] match_count Number of matches.
+ */
+static void
+cmd_completion_add_match(const char *match, char ***matches, unsigned int *match_count)
+{
+ void *p;
+
+ ++(*match_count);
+ p = realloc(*matches, *match_count * sizeof **matches);
+ if (!p) {
+ YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
+ return;
+ }
+ *matches = p;
+ (*matches)[*match_count - 1] = strdup(match);
+}
+
+/**
+ * @brief Provides completion for command names.
+ *
+ * @param[in] hint User input.
+ * @param[out] matches Matches provided to the user as a completion hint.
+ * @param[out] match_count Number of matches.
+ */
+static void
+get_cmd_completion(const char *hint, char ***matches, unsigned int *match_count)
+{
+ int i;
+
+ *match_count = 0;
+ *matches = NULL;
+
+ for (i = 0; commands[i].name; i++) {
+ if (!strncmp(hint, commands[i].name, strlen(hint))) {
+ cmd_completion_add_match(commands[i].name, matches, match_count);
+ }
+ }
+}
+
+/**
+ * @brief Provides completion for module names.
+ *
+ * @param[in] hint User input.
+ * @param[out] matches Matches provided to the user as a completion hint.
+ * @param[out] match_count Number of matches.
+ */
+static void
+get_model_completion(const char *hint, char ***matches, unsigned int *match_count)
+{
+ LY_ARRAY_COUNT_TYPE u;
+ uint32_t idx = 0;
+ const struct lys_module *module;
+
+ *match_count = 0;
+ *matches = NULL;
+
+ while ((module = ly_ctx_get_module_iter(ctx, &idx))) {
+ if (!strncmp(hint, module->name, strlen(hint))) {
+ cmd_completion_add_match(module->name, matches, match_count);
+ }
+
+ LY_ARRAY_FOR(module->parsed->includes, u) {
+ if (!strncmp(hint, module->parsed->includes[u].submodule->name, strlen(hint))) {
+ cmd_completion_add_match(module->parsed->includes[u].submodule->name, matches, match_count);
+ }
+ }
+ }
+}
+
+/**
+ * @brief Add all child nodes of a single node to the completion hint.
+ *
+ * @param[in] last_node Node of which children will be added to the hint.
+ * @param matches[out] Matches provided to the user as a completion hint.
+ * @param match_count[out] Number of matches.
+ */
+static void
+single_hint_add_children(const struct lysc_node *last_node, char ***matches, unsigned int *match_count)
+{
+ const struct lysc_node *node = NULL;
+ char *match;
+
+ if (!last_node) {
+ return;
+ }
+
+ while ((node = lys_getnext(node, last_node, NULL, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) {
+ match = lysc_path(node, LYSC_PATH_LOG, NULL, 0);
+ cmd_completion_add_match(match, matches, match_count);
+ free(match);
+ }
+}
+
+/**
+ * @brief Add module and/or node's children names to the hint.
+ *
+ * @param[in] module Compiled schema module.
+ * @param[in] parent Parent node of which children are potential matches.
+ * @param[in] hint_node_name Node name contained within the hint specified by user.
+ * @param[in,out] matches Matches provided to the user as a completion hint.
+ * @param[in,out] match_count Number of matches.
+ * @param[out] last_node Last processed node.
+ */
+static void
+add_all_children_nodes(const struct lysc_module *module, const struct lysc_node *parent,
+ const char *hint_node_name, char ***matches, unsigned int *match_count, const struct lysc_node **last_node)
+{
+ const struct lysc_node *node;
+ char *match, *node_name = NULL;
+
+ *last_node = NULL;
+
+ if (!parent && !module) {
+ return;
+ }
+
+ node = NULL;
+ while ((node = lys_getnext(node, parent, module, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) {
+ if (parent && (node->module != parent->module)) {
+ /* augmented node */
+ if (asprintf(&node_name, "%s:%s", node->module->name, node->name) == -1) {
+ YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
+ break;
+ }
+ } else {
+ node_name = strdup(node->name);
+ if (!node_name) {
+ YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
+ break;
+ }
+ }
+ if (!hint_node_name || !strncmp(hint_node_name, node_name, strlen(hint_node_name))) {
+ /* adding just module names + their top level node(s) to the hint */
+ *last_node = node;
+ match = lysc_path(node, LYSC_PATH_LOG, NULL, 0);
+ cmd_completion_add_match(match, matches, match_count);
+ free(match);
+ }
+ free(node_name);
+ }
+}
+
+/**
+ * @brief Provides completion for schemas.
+ *
+ * @param[in] hint User input.
+ * @param[out] matches Matches provided to the user as a completion hint.
+ * @param[out] match_count Number of matches.
+ */
+static void
+get_schema_completion(const char *hint, char ***matches, unsigned int *match_count)
+{
+ const struct lys_module *module;
+ uint32_t idx;
+ const char *start;
+ char *end, *module_name = NULL, *path = NULL;
+ const struct lysc_node *parent, *last_node;
+ int rc = 0;
+ size_t len;
+
+ *match_count = 0;
+ *matches = NULL;
+
+ if (strlen(hint)) {
+ if (hint[0] != '/') {
+ return;
+ }
+ start = hint + 1;
+ } else {
+ start = hint;
+ }
+
+ end = strchr(start, ':');
+ if (!end) {
+ /* no module name */
+ len = strlen(start);
+
+ /* go through all the modules */
+ idx = 0;
+ while ((module = ly_ctx_get_module_iter(ctx, &idx))) {
+ if (!module->implemented) {
+ continue;
+ }
+
+ if (!len || !strncmp(start, module->name, len)) {
+ /* add all their (matching) top level nodes */
+ add_all_children_nodes(module->compiled, NULL, NULL, matches, match_count, &last_node);
+ }
+ }
+ } else {
+ /* module name known */
+ module_name = strndup(start, end - start);
+ if (!module_name) {
+ YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
+ rc = 1;
+ goto cleanup;
+ }
+
+ module = ly_ctx_get_module_implemented(ctx, module_name);
+ if (!module) {
+ goto cleanup;
+ }
+
+ /* find the last '/', if it is at the beginning of the hint, only path up to the top level node is known,
+ * else the name of the last node starts after the found '/' */
+ start = strrchr(hint, '/');
+ if (!start) {
+ goto cleanup;
+ }
+
+ if (start == hint) {
+ /* only the (incomplete) top level node path, add all (matching) top level nodes */
+ add_all_children_nodes(module->compiled, NULL, end + 1, matches, match_count, &last_node);
+ goto cleanup;
+ }
+
+ /* get rid of stuff after the last '/' to obtain the parent node */
+ path = strndup(hint, start - hint);
+ if (!path) {
+ YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
+ rc = 1;
+ goto cleanup;
+ }
+
+ /* get the last parent in the hint (it may not exist) */
+ parent = find_schema_path(ctx, path);
+
+ /* add all (matching) child nodes of the parent */
+ add_all_children_nodes(NULL, parent, start + 1, matches, match_count, &last_node);
+ }
+
+cleanup:
+ if (!rc && (*match_count == 1)) {
+ /* to avoid a single hint (space at the end), add all children as hints */
+ single_hint_add_children(last_node, matches, match_count);
+ }
+ free(path);
+ free(module_name);
+}
+
+/**
+ * @brief Get the string before the hint, which autocompletion is for.
+ *
+ * @param[in] buf Complete user input.
+ * @param[in] hint Hint part of the user input.
+ * @return Pointer to the last string.
+ */
+static const char *
+get_last_str(const char *buf, const char *hint)
+{
+ const char *ptr;
+
+ if (buf == hint) {
+ return buf;
+ }
+
+ ptr = hint - 1;
+ while (ptr[0] == ' ') {
+ --ptr;
+ if (buf == ptr) {
+ return buf;
+ }
+ }
+
+ while (ptr[-1] != ' ') {
+ --ptr;
+ if (buf == ptr) {
+ return buf;
+ }
+ }
+
+ return ptr;
+}
+
+/* callback */
+void
+complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc)
+{
+ struct autocomplete {
+ const char *cmd; /**< command */
+ const char *opt; /**< optional option */
+ int last_opt; /**< whether to autocomplete even if an option is last in the hint */
+
+ void (*ln_cb)(const char *, const char *, linenoiseCompletions *); /**< linenoise callback to call */
+ void (*yl_cb)(const char *, char ***, unsigned int *); /**< yanglint callback to call */
+ } ac[] = {
+ {"add", NULL, 1, linenoisePathCompletion, NULL},
+ {"searchpath", NULL, 0, linenoisePathCompletion, NULL},
+ {"data", NULL, 0, linenoisePathCompletion, NULL},
+ {"print", NULL, 0, NULL, get_model_completion},
+ {"feature", NULL, 0, NULL, get_model_completion},
+ {"print", "-P", 1, NULL, get_schema_completion},
+ };
+ size_t cmd_len;
+ const char *last;
+ char **matches = NULL;
+ unsigned int match_count = 0, i;
+
+ if (buf == hint) {
+ /* command autocomplete */
+ get_cmd_completion(hint, &matches, &match_count);
+
+ } else {
+ for (i = 0; i < (sizeof ac / sizeof *ac); ++i) {
+ cmd_len = strlen(ac[i].cmd);
+ if (strncmp(buf, ac[i].cmd, cmd_len) || (buf[cmd_len] != ' ')) {
+ /* not this command */
+ continue;
+ }
+
+ last = get_last_str(buf, hint);
+ if (ac[i].opt && strncmp(ac[i].opt, last, strlen(ac[i].opt))) {
+ /* autocompletion for (another) option */
+ continue;
+ }
+ if (!ac[i].last_opt && (last[0] == '-')) {
+ /* autocompletion for the command, not an option */
+ continue;
+ }
+ if ((last != buf) && (last[0] != '-')) {
+ /* autocompleted */
+ return;
+ }
+
+ /* callback */
+ if (ac[i].ln_cb) {
+ ac[i].ln_cb(buf, hint, lc);
+ } else {
+ ac[i].yl_cb(hint, &matches, &match_count);
+ }
+ break;
+ }
+ }
+
+ /* transform matches into autocompletion, if needed */
+ for (i = 0; i < match_count; ++i) {
+ linenoiseAddCompletion(lc, matches[i]);
+ free(matches[i]);
+ }
+ free(matches);
+}
diff --git a/tools/lint/completion.h b/tools/lint/completion.h
new file mode 100644
index 0000000..20b8d17
--- /dev/null
+++ b/tools/lint/completion.h
@@ -0,0 +1,25 @@
+/**
+ * @file completion.h
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief libyang's yanglint tool auto completion header
+ *
+ * Copyright (c) 2015 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
+ */
+
+#ifndef COMPLETION_H_
+#define COMPLETION_H_
+
+#include "./linenoise/linenoise.h"
+
+/**
+ * @brief Command line completion callback.
+ */
+void complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc);
+
+#endif /* COMPLETION_H_ */
diff --git a/tools/lint/configuration.c b/tools/lint/configuration.c
new file mode 100644
index 0000000..86179fa
--- /dev/null
+++ b/tools/lint/configuration.c
@@ -0,0 +1,125 @@
+/**
+ * @file configuration.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief yanglint configuration
+ *
+ * Copyright (c) 2017 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 "configuration.h"
+
+#include <errno.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "linenoise/linenoise.h"
+
+#include "common.h"
+
+/* Yanglint home (appended to ~/) */
+#define YL_DIR ".yanglint"
+
+char *
+get_yanglint_dir(void)
+{
+ int ret;
+ struct passwd *pw;
+ char *user_home, *yl_dir;
+
+ if (!(pw = getpwuid(getuid()))) {
+ YLMSG_E("Determining home directory failed (%s).\n", strerror(errno));
+ return NULL;
+ }
+ user_home = pw->pw_dir;
+
+ yl_dir = malloc(strlen(user_home) + 1 + strlen(YL_DIR) + 1);
+ if (!yl_dir) {
+ YLMSG_E("Memory allocation failed (%s).\n", strerror(errno));
+ return NULL;
+ }
+ sprintf(yl_dir, "%s/%s", user_home, YL_DIR);
+
+ ret = access(yl_dir, R_OK | X_OK);
+ if (ret == -1) {
+ if (errno == ENOENT) {
+ /* directory does not exist */
+ YLMSG_W("Configuration directory \"%s\" does not exist, creating it.\n", yl_dir);
+ if (mkdir(yl_dir, 00700)) {
+ if (errno != EEXIST) {
+ /* parallel execution, yay */
+ YLMSG_E("Configuration directory \"%s\" cannot be created (%s).\n", yl_dir, strerror(errno));
+ free(yl_dir);
+ return NULL;
+ }
+ }
+ } else {
+ YLMSG_E("Configuration directory \"%s\" exists but cannot be accessed (%s).\n", yl_dir, strerror(errno));
+ free(yl_dir);
+ return NULL;
+ }
+ }
+
+ return yl_dir;
+}
+
+void
+load_config(void)
+{
+ char *yl_dir, *history_file;
+
+ if ((yl_dir = get_yanglint_dir()) == NULL) {
+ return;
+ }
+
+ history_file = malloc(strlen(yl_dir) + 9);
+ if (!history_file) {
+ YLMSG_E("Memory allocation failed (%s).\n", strerror(errno));
+ free(yl_dir);
+ return;
+ }
+
+ sprintf(history_file, "%s/history", yl_dir);
+ if (access(history_file, F_OK) && (errno == ENOENT)) {
+ YLMSG_W("No saved history.\n");
+ } else if (linenoiseHistoryLoad(history_file)) {
+ YLMSG_E("Failed to load history.\n");
+ }
+
+ free(history_file);
+ free(yl_dir);
+}
+
+void
+store_config(void)
+{
+ char *yl_dir, *history_file;
+
+ if ((yl_dir = get_yanglint_dir()) == NULL) {
+ return;
+ }
+
+ history_file = malloc(strlen(yl_dir) + 9);
+ if (!history_file) {
+ YLMSG_E("Memory allocation failed (%s).\n", strerror(errno));
+ free(yl_dir);
+ return;
+ }
+
+ sprintf(history_file, "%s/history", yl_dir);
+ if (linenoiseHistorySave(history_file)) {
+ YLMSG_E("Failed to save history.\n");
+ }
+
+ free(history_file);
+ free(yl_dir);
+}
diff --git a/tools/lint/configuration.h b/tools/lint/configuration.h
new file mode 100644
index 0000000..d677876
--- /dev/null
+++ b/tools/lint/configuration.h
@@ -0,0 +1,36 @@
+/**
+ * @file configuration.h
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief yanglint configuration header
+ *
+ * Copyright (c) 2017 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
+ */
+
+#ifndef CONFIGURATION_H_
+#define CONFIGURATION_H_
+
+/**
+ * @brief Finds the current user's yanglint dir
+ * @return NULL on failure, dynamically allocated yanglint dir path
+ * otherwise
+ */
+char *get_yanglint_dir(void);
+
+/**
+ * @brief Checks all the relevant files and directories creating any
+ * that are missing, sets the saved configuration (currently only history)
+ */
+void load_config(void);
+
+/**
+ * @brief Saves the current configuration (currently only history)
+ */
+void store_config(void);
+
+#endif /* CONFIGURATION_H_ */
diff --git a/tools/lint/examples/README.md b/tools/lint/examples/README.md
new file mode 100644
index 0000000..604591c
--- /dev/null
+++ b/tools/lint/examples/README.md
@@ -0,0 +1,471 @@
+# YANGLINT - Interactive Mode Examples
+
+This text provides several use-case of the `yanglint(1)` interactive
+mode. For basic information about the `yanglint(1)` usage, please see
+the man page.
+
+The examples are supposed to be went through one by one. Some of the examples
+suppose the specific schemas loaded in some of the previous example is still
+loaded. If an addition work is need, the *preparation* part in the example
+provides information what to do.
+
+To show all available command of the `yanglint(1)`, use the `help` command:
+```
+> help
+Available commands:
+ help Display commands description
+ add Add a new module from a specific file
+ load Load a new schema from the searchdirs
+ print Print a module
+ data Load, validate and optionally print instance data
+ list List all the loaded modules
+ feature Print all features of module(s) with their state
+ searchpath Print/set the search path(s) for schemas
+ clear Clear the context - remove all the loaded modules
+ verb Change verbosity
+ debug Display specific debug message groups
+ quit Quit the program
+ ? Display commands description
+ exit Quit the program
+```
+To show the information about the specific command, use the `help` command in
+combination with the command name you are interested in:
+```
+> help searchpath
+Usage: searchpath [--clear] [<modules-dir-path> ...]
+ Set paths of directories where to search for imports and
+ includes of the schema modules. The current working directory
+ and the path of the module being added is used implicitly.
+ The 'load' command uses these paths to search even for the
+ schema modules to be loaded.
+```
+
+The input files referred in this document are available together with this
+document.
+
+## Duplicit Data Model
+
+Let's have two data models [module1.yang](./module1.yang)
+and [module1b.yang](./module1b.yang).
+They differ in the module name but their namespaces are the same.
+
+Preparation:
+
+```
+> clear
+> add module1.yang
+> list
+```
+
+Output:
+
+```
+List of the loaded models:
+ i ietf-yang-metadata@2016-08-05
+ I yang@2022-06-16
+ i ietf-inet-types@2013-07-15
+ i ietf-yang-types@2013-07-15
+ I ietf-yang-schema-mount@2019-01-14
+ I module1
+```
+
+Command and its output:
+
+```
+> add module1b.yang
+libyang[0]: Two different modules ("module1" and "module1b") have the same namespace "urn:yanglint:module".
+libyang[0]: Parsing module "module1b" failed.
+```
+
+## Yang Data Model Validation
+
+**YANG/YIN syntax**
+
+`module2.yin` contains a syntax error.
+There is a bad syntax of the `type` statement in YIN file.
+
+```
+<type value="string"/>
+```
+
+instead of
+
+```
+<type name="string"/>
+```
+
+Preparation:
+
+```
+> clear
+```
+
+Command and its output:
+
+```
+> add module2.yin
+libyang[0]: Unexpected attribute "value" of "type" element. (path: Line number 8.)
+libyang[0]: Parsing module "module2" failed.
+```
+
+Similarly, there is a typo in `module2.yang`.
+
+**XPath errors**
+
+`libyang` and `yanglint(1)` is able to detect also errors in XPath expressions.
+In `module3.yang` the `must` expression refers to the node which does not exists.
+
+Preparation:
+
+```
+> clear
+```
+
+Command and its output:
+
+```
+> add module3.yang
+libyang[1]: Schema node "a" for parent "/module3:c" not found; in expr "../c/a" with context node "/module3:m".
+```
+
+Note that libyang prints only a warning in this case because it is not
+specified that XPath expressions must refer to existing nodes.
+
+## Data Validation
+
+Preparation:
+
+```
+> clear
+> add ietf-netconf-acm.yang
+```
+
+**Unknown data**
+
+By default, yanglint ignores unknown data and no error is printed (you can
+compare real content of the `datastore.xml` file and what yanglint prints
+in the following command if you add `-f xml` option).
+
+Command and its output:
+
+```
+> data -t config datastore.xml
+```
+
+We use option `-t` to specify type of the data in `datastore.xml`. By the
+`config` value we declare that the input file contains all the configuration
+data (with at least all the mandatory nodes as required by the loaded schemas),
+but without the status data. More examples of different data types will follow.
+
+Command and its output:
+
+```
+> data -t config datastore.xml
+libyang[0]: No module with namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces" in the context. (path: Line number 20.)
+YANGLINT[E]: Failed to parse input data file "datastore.xml".
+```
+
+Note that in case of working with complete datastore including the status data
+(no `-t` option is specified), `yanglint(1)` has to add status data from its
+internal `ietf-yang-library` module.
+
+**RPC and RPC-reply**
+
+It is possible to validate RPCs and their replies as well.
+
+Peparation:
+
+```
+> clear
+> add module4.yang
+```
+
+Command and its output:
+
+```
+> data -t rpc rpc.xml
+```
+
+Reply to this RPC can be validated too, but it must be nested in the original
+RPC element.
+
+Command and its output:
+
+```
+> data -t reply ../tools/lint/examples/rpc-reply.xml
+```
+
+**action and action-reply**
+
+Actions are validated the same way as RPCs except you need to be careful
+about the input file structure. No NETCONF-specific envelopes are expected.
+
+Preparation
+
+```
+> clear
+> add module4.yang
+```
+
+Command and its output:
+
+```
+> data -t rpc action.xml
+```
+
+Command and its output:
+
+```
+> data -t rpc action-reply.xml action.xml
+```
+
+**notification**
+
+Both top-level and nested notification can be validated.
+
+Preparation
+
+```
+> clear
+> add module4.yang
+```
+
+Command and its output:
+
+```
+> data -t notif notification.xml
+```
+
+Command and its output:
+
+```
+> data -t notif nested-notification.xml
+```
+
+
+**Multiple top-level elements in a single document**
+
+As a feature and in conflict with the XML definition, `yanglint(1)` (and libyang)
+is able to read XML files with multiple top-level elements. Such documents
+are not well-formed according to the XML spec, but it fits to how the YANG
+interconnects data trees (defined as top-level elements of a single schema
+or by multiple schemas).
+
+Preparation:
+
+```
+> clear
+> add ietf-netconf-acm.yang
+> add ietf-interfaces.yang
+> add ietf-ip.yang
+> add iana-if-type.yang
+```
+
+Command and its output:
+
+```
+> data -t config datastore.xml
+```
+
+**Different data content types**
+
+Since NETCONF requires the data described by YANG to be used in different
+situations (e.g. as <edit-config data>, result of the <get> with status data
+included or as a result of the <get-config> without the status data and
+possibly filtered, so without specified subtrees), it must be possible to
+specify which kind of data is going to be parsed. In `yanglint(1)`, this is done
+via `-t` option. The list of supported modes can be displayed by the `-h`
+option given to the `data` command. In general, the `auto` value lets the
+`yanglint(1)` to recognize the data type automatically by the additional top-level
+elements added to the parsed data. This is the same way as `pyang(1)` uses. Note,
+that the automatic data type recognition is available only for the XML input.
+
+**Malformed XML data**
+
+Command and its output:
+
+```
+> data -t edit config-missing-key.xml
+libyang[0]: Node "nam" not found as a child of "group" node. (path: Schema location "/ietf-netconf-acm:nacm/groups/group", data location "/ietf-netconf-acm:group", line number 19.)
+YANGLINT[E]: Failed to parse input data file "config-missing-key.xml".
+```
+
+**State information in edit-config XML**
+
+Command and its output:
+
+```
+> data -t edit config-unknown-element.xml
+libyang[0]: Unexpected data state node "denied-operations" found. (path: Schema location "/ietf-netconf-acm:nacm/denied-operations", data location "/ietf-netconf-acm:nacm", line number 24.)
+YANGLINT[E]: Failed to parse input data file "config-unknown-element.xml".
+```
+
+**Missing required element in NETCONF data**
+
+Command and its output:
+
+```
+> data data-missing-key.xml
+libyang[0]: List instance is missing its key "name". (path: Schema location "/ietf-netconf-acm:nacm/rule-list/rule", data location "/ietf-netconf-acm:rule", line number 10.)
+YANGLINT[E]: Failed to parse input data file "data-missing-key.xml".
+```
+
+**Malformed XML**
+
+Command and its output:
+
+```
+> data data-malformed-xml.xml
+libyang[0]: Node "nam" not found as a child of "rule" node. (path: Schema location "/ietf-netconf-acm:nacm/rule-list/rule", data location "/ietf-netconf-acm:rule", line number 8.)
+YANGLINT[E]: Failed to parse input data file "data-malformed-xml.xml".
+```
+
+Command and its output:
+
+```
+> data data-malformed-xml2.xml
+libyang[0]: Child element "module-name" inside a terminal node "name" found. (path: Schema location "/ietf-netconf-acm:nacm/rule-list/rule/name", data location "/ietf-netconf-acm:name", line number 7.)
+YANGLINT[E]: Failed to parse input data file "data-malformed-xml2.xml".
+```
+
+**Bad value**
+
+Command and its output:
+
+```
+> data data-out-of-range-value.xml
+libyang[0]: Value "-1" is out of type uint32 min/max bounds. (path: Schema location "/ietf-netconf-acm:nacm/denied-operations", data location "/ietf-netconf-acm:nacm", line number 24.)
+YANGLINT[E]: Failed to parse input data file "data-out-of-range-value.xml".
+```
+
+## Validation of "when" Statement in Data
+
+Preparation:
+
+```
+> clear
+> add ietf-netconf-acm-when.yang
+```
+
+**`When` condition is not satisfied since `denied-operation = 0`**
+
+Command and its output:
+
+```
+> data data-acm.xml
+libyang[0]: When condition "../denied-operations > 0" not satisfied. (path: Schema location "/ietf-netconf-acm-when:nacm/denied-data-writes", data location "/ietf-netconf-acm-when:nacm/denied-data-writes".)
+YANGLINT[E]: Failed to parse input data file "data-acm.xml".
+```
+
+## Printing a Data Model
+
+Preparation:
+
+```
+> clear
+> add ietf-netconf-acm.yang
+```
+
+**Print a `pyang`-style tree**
+
+Command and its output:
+
+```
+> print ietf-netconf-acm
+module: ietf-netconf-acm
+ +--rw nacm
+ +--rw enable-nacm? boolean
+ +--rw read-default? action-type
+ +--rw write-default? action-type
+ +--rw exec-default? action-type
+ +--rw enable-external-groups? boolean
+ +--ro denied-operations yang:zero-based-counter32
+ +--ro denied-data-writes yang:zero-based-counter32
+ +--ro denied-notifications yang:zero-based-counter32
+ +--rw groups
+ | +--rw group* [name]
+ | +--rw name group-name-type
+ | +--rw user-name* user-name-type
+ +--rw rule-list* [name]
+ +--rw name string
+ +--rw group* union
+ +--rw rule* [name]
+ +--rw name string
+ +--rw module-name? union
+ +--rw (rule-type)?
+ | +--:(protocol-operation)
+ | | +--rw rpc-name? union
+ | +--:(notification)
+ | | +--rw notification-name? union
+ | +--:(data-node)
+ | +--rw path node-instance-identifier
+ +--rw access-operations? union
+ +--rw action action-type
+ +--rw comment? string
+```
+
+**Print information about specific model part**
+
+Command and its output:
+
+```
+> print -f info -P /ietf-netconf-acm:nacm/ietf-netconf-acm:enable-nacm ietf-netconf-acm
+leaf enable-nacm {
+ ietf-netconf-acm:default-deny-all;
+ type boolean;
+ default "true";
+ config true;
+ status current;
+ description
+ "Enables or disables all NETCONF access control
+ enforcement. If 'true', then enforcement
+ is enabled. If 'false', then enforcement
+ is disabled.";
+}
+```
+
+## Usage of `feature` in Yang
+
+Preparation:
+
+```
+> clear
+> add ietf-interfaces.yang
+> add ietf-ip.yang -F ietf-ip:*
+> add iana-if-type.yang
+```
+
+Note: This example also shows `JSON` output of the command.
+
+Command and its output:
+```
+> feature ietf-ip
+ietf-ip features:
+ ipv4-non-contiguous-netmasks (on)
+ ipv6-privacy-autoconf (on)
+> data -f json -t config data-ip.xml
+{
+ "ietf-interfaces:interfaces": {
+ "interface": [
+ {
+ "name": "eth0",
+ "description": "Wire Connection",
+ "type": "iana-if-type:ethernetCsmacd",
+ "enabled": true,
+ "ietf-ip:ipv4": {
+ "address": [
+ {
+ "ip": "192.168.1.15",
+ "netmask": "255.255.255.0"
+ },
+ {
+ "ip": "192.168.1.10",
+ "netmask": "255.255.255.0"
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
+```
diff --git a/tools/lint/examples/action-reply.xml b/tools/lint/examples/action-reply.xml
new file mode 100644
index 0000000..e6fc284
--- /dev/null
+++ b/tools/lint/examples/action-reply.xml
@@ -0,0 +1,8 @@
+<cont1 xmlns="urn:module4">
+ <list>
+ <leaf1>key_val</leaf1>
+ <act>
+ <leaf3>some_output</leaf3>
+ </act>
+ </list>
+</cont1>
diff --git a/tools/lint/examples/action.xml b/tools/lint/examples/action.xml
new file mode 100644
index 0000000..661fecf
--- /dev/null
+++ b/tools/lint/examples/action.xml
@@ -0,0 +1,8 @@
+<cont1 xmlns="urn:module4">
+ <list>
+ <leaf1>key_val</leaf1>
+ <act>
+ <leaf2>some_input</leaf2>
+ </act>
+ </list>
+</cont1>
diff --git a/tools/lint/examples/config-acm.xml b/tools/lint/examples/config-acm.xml
new file mode 100644
index 0000000..8c99419
--- /dev/null
+++ b/tools/lint/examples/config-acm.xml
@@ -0,0 +1,24 @@
+<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <rule-list>
+ <name>almighty</name>
+ <group>almighty</group>
+ <group nc:operation="create">test</group>
+ <rule>
+ <name>almighty</name>
+ <module-name>*</module-name>
+ <access-operations>*</access-operations>
+ <action>permit</action>
+ </rule>
+ </rule-list>
+ <groups>
+ <group>
+ <name>test</name>
+ <user-name>smith</user-name>
+ </group>
+ <group>
+ <name>almighty</name>
+ <user-name>smith</user-name>
+ <user-name>doe</user-name>
+ </group>
+ </groups>
+</nacm>
diff --git a/tools/lint/examples/config-missing-key.xml b/tools/lint/examples/config-missing-key.xml
new file mode 100644
index 0000000..c30c2b0
--- /dev/null
+++ b/tools/lint/examples/config-missing-key.xml
@@ -0,0 +1,24 @@
+<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
+ <rule-list>
+ <name>almighty</name>
+ <group>almighty</group>
+ <group>test</group>
+ <rule>
+ <name>almighty</name>
+ <module-name>*</module-name>
+ <access-operations>*</access-operations>
+ <action>permit</action>
+ </rule>
+ </rule-list>
+ <groups>
+ <group>
+ <name>test</name>
+ <user-name>smith</user-name>
+ </group>
+ <group>
+ <nam>almighty</name>
+ <user-name>smith</user-name>
+ <user-name>doe</user-name>
+ </group>
+ </groups>
+</nacm>
diff --git a/tools/lint/examples/config-unknown-element.xml b/tools/lint/examples/config-unknown-element.xml
new file mode 100644
index 0000000..66ae880
--- /dev/null
+++ b/tools/lint/examples/config-unknown-element.xml
@@ -0,0 +1,27 @@
+<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
+ <rule-list>
+ <name>almighty</name>
+ <group>almighty</group>
+ <group>test</group>
+ <rule>
+ <name>almighty</name>
+ <module-name>*</module-name>
+ <access-operations>*</access-operations>
+ <action>permit</action>
+ </rule>
+ </rule-list>
+ <groups>
+ <group>
+ <name>test</name>
+ <user-name>smith</user-name>
+ </group>
+ <group>
+ <name>almighty</name>
+ <user-name>smith</user-name>
+ <user-name>doe</user-name>
+ </group>
+ </groups>
+ <denied-operations>0</denied-operations>
+ <denied-data-writes>0</denied-data-writes>
+ <denied-notifications>0</denied-notifications>
+</nacm>
diff --git a/tools/lint/examples/data-acm.xml b/tools/lint/examples/data-acm.xml
new file mode 100644
index 0000000..66ae880
--- /dev/null
+++ b/tools/lint/examples/data-acm.xml
@@ -0,0 +1,27 @@
+<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
+ <rule-list>
+ <name>almighty</name>
+ <group>almighty</group>
+ <group>test</group>
+ <rule>
+ <name>almighty</name>
+ <module-name>*</module-name>
+ <access-operations>*</access-operations>
+ <action>permit</action>
+ </rule>
+ </rule-list>
+ <groups>
+ <group>
+ <name>test</name>
+ <user-name>smith</user-name>
+ </group>
+ <group>
+ <name>almighty</name>
+ <user-name>smith</user-name>
+ <user-name>doe</user-name>
+ </group>
+ </groups>
+ <denied-operations>0</denied-operations>
+ <denied-data-writes>0</denied-data-writes>
+ <denied-notifications>0</denied-notifications>
+</nacm>
diff --git a/tools/lint/examples/data-ip.xml b/tools/lint/examples/data-ip.xml
new file mode 100644
index 0000000..1894f6d
--- /dev/null
+++ b/tools/lint/examples/data-ip.xml
@@ -0,0 +1,12 @@
+<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
+ <interface>
+ <name>eth0</name>
+ <description>Wire Connection</description>
+ <type xmlns:ift="urn:ietf:params:xml:ns:yang:iana-if-type">ift:ethernetCsmacd</type>
+ <enabled>true</enabled>
+ <ipv4 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip">
+ <address><ip>192.168.1.15</ip><netmask>255.255.255.0</netmask></address>
+ <address><ip>192.168.1.10</ip><netmask>255.255.255.0</netmask></address>
+ </ipv4>
+ </interface>
+</interfaces>
diff --git a/tools/lint/examples/data-malformed-xml.xml b/tools/lint/examples/data-malformed-xml.xml
new file mode 100644
index 0000000..908d79b
--- /dev/null
+++ b/tools/lint/examples/data-malformed-xml.xml
@@ -0,0 +1,27 @@
+<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
+ <rule-list>
+ <name>almighty</name>
+ <group>almighty</group>
+ <group>test</group>
+ <rule>
+ <nam>almighty
+ <module-name>*</module-name>
+ <access-operations>*</access-operations>
+ <action>permit</action>
+ </rule>
+ </rule-list>
+ <groups>
+ <group>
+ <name>test</name>
+ <user-name>smith</user-name>
+ </group>
+ <group>
+ <name>almighty</name>
+ <user-name>smith</user-name>
+ <user-name>doe</user-name>
+ </group>
+ </groups>
+ <denied-operations>0</denied-operations>
+ <denied-data-writes>0</denied-data-writes>
+ <denied-notifications>0</denied-notifications>
+</nacm>
diff --git a/tools/lint/examples/data-malformed-xml2.xml b/tools/lint/examples/data-malformed-xml2.xml
new file mode 100644
index 0000000..8d0e5f4
--- /dev/null
+++ b/tools/lint/examples/data-malformed-xml2.xml
@@ -0,0 +1,26 @@
+<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
+ <rule-list>
+ <name>almighty</name>
+ <group>almighty</group>
+ <group>test</group>
+ <rule>
+ <name>almighty<module-name></name> *</module-name>
+ <access-operations>*</access-operations>
+ <action>permit</action>
+ </rule>
+ </rule-list>
+ <groups>
+ <group>
+ <name>test</name>
+ <user-name>smith</user-name>
+ </group>
+ <group>
+ <name>almighty</name>
+ <user-name>smith</user-name>
+ <user-name>doe</user-name>
+ </group>
+ </groups>
+ <denied-operations>0</denied-operations>
+ <denied-data-writes>0</denied-data-writes>
+ <denied-notifications>0</denied-notifications>
+</nacm>
diff --git a/tools/lint/examples/data-missing-key.xml b/tools/lint/examples/data-missing-key.xml
new file mode 100644
index 0000000..2e9684d
--- /dev/null
+++ b/tools/lint/examples/data-missing-key.xml
@@ -0,0 +1,26 @@
+<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
+ <rule-list>
+ <name>almighty</name>
+ <group>almighty</group>
+ <group>test</group>
+ <rule>
+ <module-name>*</module-name>
+ <access-operations>*</access-operations>
+ <action>permit</action>
+ </rule>
+ </rule-list>
+ <groups>
+ <group>
+ <name>test</name>
+ <user-name>smith</user-name>
+ </group>
+ <group>
+ <name>almighty</name>
+ <user-name>smith</user-name>
+ <user-name>doe</user-name>
+ </group>
+ </groups>
+ <denied-operations>0</denied-operations>
+ <denied-data-writes>0</denied-data-writes>
+ <denied-notifications>0</denied-notifications>
+</nacm>
diff --git a/tools/lint/examples/data-out-of-range-value.xml b/tools/lint/examples/data-out-of-range-value.xml
new file mode 100644
index 0000000..2af5ba9
--- /dev/null
+++ b/tools/lint/examples/data-out-of-range-value.xml
@@ -0,0 +1,27 @@
+<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
+ <rule-list>
+ <name>almighty</name>
+ <group>almighty</group>
+ <group>test</group>
+ <rule>
+ <name>almighty</name>
+ <module-name>*</module-name>
+ <access-operations>*</access-operations>
+ <action>permit</action>
+ </rule>
+ </rule-list>
+ <groups>
+ <group>
+ <name>test</name>
+ <user-name>smith</user-name>
+ </group>
+ <group>
+ <name>almighty</name>
+ <user-name>smith</user-name>
+ <user-name>doe</user-name>
+ </group>
+ </groups>
+ <denied-operations>-1</denied-operations>
+ <denied-data-writes>0</denied-data-writes>
+ <denied-notifications>0</denied-notifications>
+</nacm>
diff --git a/tools/lint/examples/datastore.xml b/tools/lint/examples/datastore.xml
new file mode 100644
index 0000000..c6a6fc9
--- /dev/null
+++ b/tools/lint/examples/datastore.xml
@@ -0,0 +1,29 @@
+<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
+ <rule-list>
+ <name>almighty</name>
+ <group>almighty</group>
+ <rule>
+ <name>almighty</name>
+ <module-name>*</module-name>
+ <access-operations>*</access-operations>
+ <action>permit</action>
+ </rule>
+ </rule-list>
+ <groups>
+ <group>
+ <name>almighty</name>
+ <user-name>smith</user-name>
+ </group>
+ </groups>
+</nacm>
+<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
+ <interface>
+ <name>eth0</name>
+ <description>Wire Connection</description>
+ <type xmlns:ift="urn:ietf:params:xml:ns:yang:iana-if-type">ift:ethernetCsmacd</type>
+ <enabled>true</enabled>
+ <ipv4 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip">
+ <address><ip>192.168.1.15</ip><prefix-length>24</prefix-length></address>
+ </ipv4>
+ </interface>
+</interfaces>
diff --git a/tools/lint/examples/iana-if-type.yang b/tools/lint/examples/iana-if-type.yang
new file mode 100644
index 0000000..5dd8219
--- /dev/null
+++ b/tools/lint/examples/iana-if-type.yang
@@ -0,0 +1,1547 @@
+module iana-if-type {
+ namespace "urn:ietf:params:xml:ns:yang:iana-if-type";
+ prefix ianaift;
+
+ import ietf-interfaces {
+ prefix if;
+ }
+
+ organization "IANA";
+ contact
+ " Internet Assigned Numbers Authority
+
+ Postal: ICANN
+ 4676 Admiralty Way, Suite 330
+ Marina del Rey, CA 90292
+
+ Tel: +1 310 823 9358
+ <mailto:iana@iana.org>";
+ description
+ "This YANG module defines YANG identities for IANA-registered
+ interface types.
+
+ This YANG module is maintained by IANA and reflects the
+ 'ifType definitions' registry.
+
+ The latest revision of this YANG module can be obtained from
+ the IANA web site.
+
+ Requests for new values should be made to IANA via
+ email (iana@iana.org).
+
+ Copyright (c) 2014 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ The initial version of this YANG module is part of RFC 7224;
+ see the RFC itself for full legal notices.";
+ reference
+ "IANA 'ifType definitions' registry.
+ <http://www.iana.org/assignments/smi-numbers>";
+
+ revision 2014-05-08 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 7224: IANA Interface Type YANG Module";
+ }
+
+ identity iana-interface-type {
+ base if:interface-type;
+ description
+ "This identity is used as a base for all interface types
+ defined in the 'ifType definitions' registry.";
+ }
+
+
+
+
+
+
+ identity other {
+ base iana-interface-type;
+ }
+ identity regular1822 {
+ base iana-interface-type;
+ }
+ identity hdh1822 {
+ base iana-interface-type;
+ }
+ identity ddnX25 {
+ base iana-interface-type;
+ }
+ identity rfc877x25 {
+ base iana-interface-type;
+ reference
+ "RFC 1382 - SNMP MIB Extension for the X.25 Packet Layer";
+ }
+ identity ethernetCsmacd {
+ base iana-interface-type;
+ description
+ "For all Ethernet-like interfaces, regardless of speed,
+ as per RFC 3635.";
+ reference
+ "RFC 3635 - Definitions of Managed Objects for the
+ Ethernet-like Interface Types";
+ }
+ identity iso88023Csmacd {
+ base iana-interface-type;
+ status deprecated;
+ description
+ "Deprecated via RFC 3635.
+ Use ethernetCsmacd(6) instead.";
+ reference
+ "RFC 3635 - Definitions of Managed Objects for the
+ Ethernet-like Interface Types";
+ }
+ identity iso88024TokenBus {
+ base iana-interface-type;
+ }
+ identity iso88025TokenRing {
+ base iana-interface-type;
+ }
+ identity iso88026Man {
+ base iana-interface-type;
+ }
+ identity starLan {
+ base iana-interface-type;
+ status deprecated;
+ description
+ "Deprecated via RFC 3635.
+ Use ethernetCsmacd(6) instead.";
+ reference
+ "RFC 3635 - Definitions of Managed Objects for the
+ Ethernet-like Interface Types";
+ }
+ identity proteon10Mbit {
+ base iana-interface-type;
+ }
+ identity proteon80Mbit {
+ base iana-interface-type;
+ }
+ identity hyperchannel {
+ base iana-interface-type;
+ }
+ identity fddi {
+ base iana-interface-type;
+ reference
+ "RFC 1512 - FDDI Management Information Base";
+ }
+ identity lapb {
+ base iana-interface-type;
+ reference
+ "RFC 1381 - SNMP MIB Extension for X.25 LAPB";
+ }
+ identity sdlc {
+ base iana-interface-type;
+ }
+ identity ds1 {
+ base iana-interface-type;
+ description
+ "DS1-MIB.";
+ reference
+ "RFC 4805 - Definitions of Managed Objects for the
+ DS1, J1, E1, DS2, and E2 Interface Types";
+ }
+ identity e1 {
+ base iana-interface-type;
+ status obsolete;
+ description
+ "Obsolete; see DS1-MIB.";
+ reference
+ "RFC 4805 - Definitions of Managed Objects for the
+ DS1, J1, E1, DS2, and E2 Interface Types";
+ }
+
+
+ identity basicISDN {
+ base iana-interface-type;
+ description
+ "No longer used. See also RFC 2127.";
+ }
+ identity primaryISDN {
+ base iana-interface-type;
+ description
+ "No longer used. See also RFC 2127.";
+ }
+ identity propPointToPointSerial {
+ base iana-interface-type;
+ description
+ "Proprietary serial.";
+ }
+ identity ppp {
+ base iana-interface-type;
+ }
+ identity softwareLoopback {
+ base iana-interface-type;
+ }
+ identity eon {
+ base iana-interface-type;
+ description
+ "CLNP over IP.";
+ }
+ identity ethernet3Mbit {
+ base iana-interface-type;
+ }
+ identity nsip {
+ base iana-interface-type;
+ description
+ "XNS over IP.";
+ }
+ identity slip {
+ base iana-interface-type;
+ description
+ "Generic SLIP.";
+ }
+ identity ultra {
+ base iana-interface-type;
+ description
+ "Ultra Technologies.";
+ }
+ identity ds3 {
+ base iana-interface-type;
+ description
+ "DS3-MIB.";
+ reference
+ "RFC 3896 - Definitions of Managed Objects for the
+ DS3/E3 Interface Type";
+ }
+ identity sip {
+ base iana-interface-type;
+ description
+ "SMDS, coffee.";
+ reference
+ "RFC 1694 - Definitions of Managed Objects for SMDS
+ Interfaces using SMIv2";
+ }
+ identity frameRelay {
+ base iana-interface-type;
+ description
+ "DTE only.";
+ reference
+ "RFC 2115 - Management Information Base for Frame Relay
+ DTEs Using SMIv2";
+ }
+ identity rs232 {
+ base iana-interface-type;
+ reference
+ "RFC 1659 - Definitions of Managed Objects for RS-232-like
+ Hardware Devices using SMIv2";
+ }
+ identity para {
+ base iana-interface-type;
+ description
+ "Parallel-port.";
+ reference
+ "RFC 1660 - Definitions of Managed Objects for
+ Parallel-printer-like Hardware Devices using
+ SMIv2";
+ }
+ identity arcnet {
+ base iana-interface-type;
+ description
+ "ARCnet.";
+ }
+ identity arcnetPlus {
+ base iana-interface-type;
+ description
+ "ARCnet Plus.";
+ }
+
+
+
+ identity atm {
+ base iana-interface-type;
+ description
+ "ATM cells.";
+ }
+ identity miox25 {
+ base iana-interface-type;
+ reference
+ "RFC 1461 - SNMP MIB extension for Multiprotocol
+ Interconnect over X.25";
+ }
+ identity sonet {
+ base iana-interface-type;
+ description
+ "SONET or SDH.";
+ }
+ identity x25ple {
+ base iana-interface-type;
+ reference
+ "RFC 2127 - ISDN Management Information Base using SMIv2";
+ }
+ identity iso88022llc {
+ base iana-interface-type;
+ }
+ identity localTalk {
+ base iana-interface-type;
+ }
+ identity smdsDxi {
+ base iana-interface-type;
+ }
+ identity frameRelayService {
+ base iana-interface-type;
+ description
+ "FRNETSERV-MIB.";
+ reference
+ "RFC 2954 - Definitions of Managed Objects for Frame
+ Relay Service";
+ }
+ identity v35 {
+ base iana-interface-type;
+ }
+ identity hssi {
+ base iana-interface-type;
+ }
+ identity hippi {
+ base iana-interface-type;
+ }
+
+ identity modem {
+ base iana-interface-type;
+ description
+ "Generic modem.";
+ }
+ identity aal5 {
+ base iana-interface-type;
+ description
+ "AAL5 over ATM.";
+ }
+ identity sonetPath {
+ base iana-interface-type;
+ }
+ identity sonetVT {
+ base iana-interface-type;
+ }
+ identity smdsIcip {
+ base iana-interface-type;
+ description
+ "SMDS InterCarrier Interface.";
+ }
+ identity propVirtual {
+ base iana-interface-type;
+ description
+ "Proprietary virtual/internal.";
+ reference
+ "RFC 2863 - The Interfaces Group MIB";
+ }
+ identity propMultiplexor {
+ base iana-interface-type;
+ description
+ "Proprietary multiplexing.";
+ reference
+ "RFC 2863 - The Interfaces Group MIB";
+ }
+ identity ieee80212 {
+ base iana-interface-type;
+ description
+ "100BaseVG.";
+ }
+ identity fibreChannel {
+ base iana-interface-type;
+ description
+ "Fibre Channel.";
+ }
+
+
+
+ identity hippiInterface {
+ base iana-interface-type;
+ description
+ "HIPPI interfaces.";
+ }
+ identity frameRelayInterconnect {
+ base iana-interface-type;
+ status obsolete;
+ description
+ "Obsolete; use either
+ frameRelay(32) or frameRelayService(44).";
+ }
+ identity aflane8023 {
+ base iana-interface-type;
+ description
+ "ATM Emulated LAN for 802.3.";
+ }
+ identity aflane8025 {
+ base iana-interface-type;
+ description
+ "ATM Emulated LAN for 802.5.";
+ }
+ identity cctEmul {
+ base iana-interface-type;
+ description
+ "ATM Emulated circuit.";
+ }
+ identity fastEther {
+ base iana-interface-type;
+ status deprecated;
+ description
+ "Obsoleted via RFC 3635.
+ ethernetCsmacd(6) should be used instead.";
+ reference
+ "RFC 3635 - Definitions of Managed Objects for the
+ Ethernet-like Interface Types";
+ }
+ identity isdn {
+ base iana-interface-type;
+ description
+ "ISDN and X.25.";
+ reference
+ "RFC 1356 - Multiprotocol Interconnect on X.25 and ISDN
+ in the Packet Mode";
+ }
+
+
+
+ identity v11 {
+ base iana-interface-type;
+ description
+ "CCITT V.11/X.21.";
+ }
+ identity v36 {
+ base iana-interface-type;
+ description
+ "CCITT V.36.";
+ }
+ identity g703at64k {
+ base iana-interface-type;
+ description
+ "CCITT G703 at 64Kbps.";
+ }
+ identity g703at2mb {
+ base iana-interface-type;
+ status obsolete;
+ description
+ "Obsolete; see DS1-MIB.";
+ }
+ identity qllc {
+ base iana-interface-type;
+ description
+ "SNA QLLC.";
+ }
+ identity fastEtherFX {
+ base iana-interface-type;
+ status deprecated;
+ description
+ "Obsoleted via RFC 3635.
+ ethernetCsmacd(6) should be used instead.";
+ reference
+ "RFC 3635 - Definitions of Managed Objects for the
+ Ethernet-like Interface Types";
+ }
+ identity channel {
+ base iana-interface-type;
+ description
+ "Channel.";
+ }
+ identity ieee80211 {
+ base iana-interface-type;
+ description
+ "Radio spread spectrum.";
+ }
+ identity ibm370parChan {
+ base iana-interface-type;
+ description
+ "IBM System 360/370 OEMI Channel.";
+ }
+ identity escon {
+ base iana-interface-type;
+ description
+ "IBM Enterprise Systems Connection.";
+ }
+ identity dlsw {
+ base iana-interface-type;
+ description
+ "Data Link Switching.";
+ }
+ identity isdns {
+ base iana-interface-type;
+ description
+ "ISDN S/T interface.";
+ }
+ identity isdnu {
+ base iana-interface-type;
+ description
+ "ISDN U interface.";
+ }
+ identity lapd {
+ base iana-interface-type;
+ description
+ "Link Access Protocol D.";
+ }
+ identity ipSwitch {
+ base iana-interface-type;
+ description
+ "IP Switching Objects.";
+ }
+ identity rsrb {
+ base iana-interface-type;
+ description
+ "Remote Source Route Bridging.";
+ }
+ identity atmLogical {
+ base iana-interface-type;
+ description
+ "ATM Logical Port.";
+ reference
+ "RFC 3606 - Definitions of Supplemental Managed Objects
+ for ATM Interface";
+ }
+ identity ds0 {
+ base iana-interface-type;
+ description
+ "Digital Signal Level 0.";
+ reference
+ "RFC 2494 - Definitions of Managed Objects for the DS0
+ and DS0 Bundle Interface Type";
+ }
+ identity ds0Bundle {
+ base iana-interface-type;
+ description
+ "Group of ds0s on the same ds1.";
+ reference
+ "RFC 2494 - Definitions of Managed Objects for the DS0
+ and DS0 Bundle Interface Type";
+ }
+ identity bsc {
+ base iana-interface-type;
+ description
+ "Bisynchronous Protocol.";
+ }
+ identity async {
+ base iana-interface-type;
+ description
+ "Asynchronous Protocol.";
+ }
+ identity cnr {
+ base iana-interface-type;
+ description
+ "Combat Net Radio.";
+ }
+ identity iso88025Dtr {
+ base iana-interface-type;
+ description
+ "ISO 802.5r DTR.";
+ }
+ identity eplrs {
+ base iana-interface-type;
+ description
+ "Ext Pos Loc Report Sys.";
+ }
+ identity arap {
+ base iana-interface-type;
+ description
+ "Appletalk Remote Access Protocol.";
+ }
+ identity propCnls {
+ base iana-interface-type;
+ description
+ "Proprietary Connectionless Protocol.";
+ }
+ identity hostPad {
+ base iana-interface-type;
+ description
+ "CCITT-ITU X.29 PAD Protocol.";
+ }
+ identity termPad {
+ base iana-interface-type;
+ description
+ "CCITT-ITU X.3 PAD Facility.";
+ }
+ identity frameRelayMPI {
+ base iana-interface-type;
+ description
+ "Multiproto Interconnect over FR.";
+ }
+ identity x213 {
+ base iana-interface-type;
+ description
+ "CCITT-ITU X213.";
+ }
+ identity adsl {
+ base iana-interface-type;
+ description
+ "Asymmetric Digital Subscriber Loop.";
+ }
+ identity radsl {
+ base iana-interface-type;
+ description
+ "Rate-Adapt. Digital Subscriber Loop.";
+ }
+ identity sdsl {
+ base iana-interface-type;
+ description
+ "Symmetric Digital Subscriber Loop.";
+ }
+ identity vdsl {
+ base iana-interface-type;
+ description
+ "Very H-Speed Digital Subscrib. Loop.";
+ }
+ identity iso88025CRFPInt {
+ base iana-interface-type;
+ description
+ "ISO 802.5 CRFP.";
+ }
+ identity myrinet {
+ base iana-interface-type;
+ description
+ "Myricom Myrinet.";
+ }
+ identity voiceEM {
+ base iana-interface-type;
+ description
+ "Voice recEive and transMit.";
+ }
+ identity voiceFXO {
+ base iana-interface-type;
+ description
+ "Voice Foreign Exchange Office.";
+ }
+ identity voiceFXS {
+ base iana-interface-type;
+ description
+ "Voice Foreign Exchange Station.";
+ }
+ identity voiceEncap {
+ base iana-interface-type;
+ description
+ "Voice encapsulation.";
+ }
+ identity voiceOverIp {
+ base iana-interface-type;
+ description
+ "Voice over IP encapsulation.";
+ }
+ identity atmDxi {
+ base iana-interface-type;
+ description
+ "ATM DXI.";
+ }
+ identity atmFuni {
+ base iana-interface-type;
+ description
+ "ATM FUNI.";
+ }
+ identity atmIma {
+ base iana-interface-type;
+ description
+ "ATM IMA.";
+ }
+ identity pppMultilinkBundle {
+ base iana-interface-type;
+ description
+ "PPP Multilink Bundle.";
+ }
+ identity ipOverCdlc {
+ base iana-interface-type;
+ description
+ "IBM ipOverCdlc.";
+ }
+ identity ipOverClaw {
+ base iana-interface-type;
+ description
+ "IBM Common Link Access to Workstn.";
+ }
+ identity stackToStack {
+ base iana-interface-type;
+ description
+ "IBM stackToStack.";
+ }
+ identity virtualIpAddress {
+ base iana-interface-type;
+ description
+ "IBM VIPA.";
+ }
+ identity mpc {
+ base iana-interface-type;
+ description
+ "IBM multi-protocol channel support.";
+ }
+ identity ipOverAtm {
+ base iana-interface-type;
+ description
+ "IBM ipOverAtm.";
+ reference
+ "RFC 2320 - Definitions of Managed Objects for Classical IP
+ and ARP Over ATM Using SMIv2 (IPOA-MIB)";
+ }
+ identity iso88025Fiber {
+ base iana-interface-type;
+ description
+ "ISO 802.5j Fiber Token Ring.";
+ }
+ identity tdlc {
+ base iana-interface-type;
+ description
+ "IBM twinaxial data link control.";
+ }
+ identity gigabitEthernet {
+ base iana-interface-type;
+ status deprecated;
+
+
+ description
+ "Obsoleted via RFC 3635.
+ ethernetCsmacd(6) should be used instead.";
+ reference
+ "RFC 3635 - Definitions of Managed Objects for the
+ Ethernet-like Interface Types";
+ }
+ identity hdlc {
+ base iana-interface-type;
+ description
+ "HDLC.";
+ }
+ identity lapf {
+ base iana-interface-type;
+ description
+ "LAP F.";
+ }
+ identity v37 {
+ base iana-interface-type;
+ description
+ "V.37.";
+ }
+ identity x25mlp {
+ base iana-interface-type;
+ description
+ "Multi-Link Protocol.";
+ }
+ identity x25huntGroup {
+ base iana-interface-type;
+ description
+ "X25 Hunt Group.";
+ }
+ identity transpHdlc {
+ base iana-interface-type;
+ description
+ "Transp HDLC.";
+ }
+ identity interleave {
+ base iana-interface-type;
+ description
+ "Interleave channel.";
+ }
+ identity fast {
+ base iana-interface-type;
+ description
+ "Fast channel.";
+ }
+
+ identity ip {
+ base iana-interface-type;
+ description
+ "IP (for APPN HPR in IP networks).";
+ }
+ identity docsCableMaclayer {
+ base iana-interface-type;
+ description
+ "CATV Mac Layer.";
+ }
+ identity docsCableDownstream {
+ base iana-interface-type;
+ description
+ "CATV Downstream interface.";
+ }
+ identity docsCableUpstream {
+ base iana-interface-type;
+ description
+ "CATV Upstream interface.";
+ }
+ identity a12MppSwitch {
+ base iana-interface-type;
+ description
+ "Avalon Parallel Processor.";
+ }
+ identity tunnel {
+ base iana-interface-type;
+ description
+ "Encapsulation interface.";
+ }
+ identity coffee {
+ base iana-interface-type;
+ description
+ "Coffee pot.";
+ reference
+ "RFC 2325 - Coffee MIB";
+ }
+ identity ces {
+ base iana-interface-type;
+ description
+ "Circuit Emulation Service.";
+ }
+ identity atmSubInterface {
+ base iana-interface-type;
+ description
+ "ATM Sub Interface.";
+ }
+
+ identity l2vlan {
+ base iana-interface-type;
+ description
+ "Layer 2 Virtual LAN using 802.1Q.";
+ }
+ identity l3ipvlan {
+ base iana-interface-type;
+ description
+ "Layer 3 Virtual LAN using IP.";
+ }
+ identity l3ipxvlan {
+ base iana-interface-type;
+ description
+ "Layer 3 Virtual LAN using IPX.";
+ }
+ identity digitalPowerline {
+ base iana-interface-type;
+ description
+ "IP over Power Lines.";
+ }
+ identity mediaMailOverIp {
+ base iana-interface-type;
+ description
+ "Multimedia Mail over IP.";
+ }
+ identity dtm {
+ base iana-interface-type;
+ description
+ "Dynamic synchronous Transfer Mode.";
+ }
+ identity dcn {
+ base iana-interface-type;
+ description
+ "Data Communications Network.";
+ }
+ identity ipForward {
+ base iana-interface-type;
+ description
+ "IP Forwarding Interface.";
+ }
+ identity msdsl {
+ base iana-interface-type;
+ description
+ "Multi-rate Symmetric DSL.";
+ }
+ identity ieee1394 {
+ base iana-interface-type;
+
+ description
+ "IEEE1394 High Performance Serial Bus.";
+ }
+ identity if-gsn {
+ base iana-interface-type;
+ description
+ "HIPPI-6400.";
+ }
+ identity dvbRccMacLayer {
+ base iana-interface-type;
+ description
+ "DVB-RCC MAC Layer.";
+ }
+ identity dvbRccDownstream {
+ base iana-interface-type;
+ description
+ "DVB-RCC Downstream Channel.";
+ }
+ identity dvbRccUpstream {
+ base iana-interface-type;
+ description
+ "DVB-RCC Upstream Channel.";
+ }
+ identity atmVirtual {
+ base iana-interface-type;
+ description
+ "ATM Virtual Interface.";
+ }
+ identity mplsTunnel {
+ base iana-interface-type;
+ description
+ "MPLS Tunnel Virtual Interface.";
+ }
+ identity srp {
+ base iana-interface-type;
+ description
+ "Spatial Reuse Protocol.";
+ }
+ identity voiceOverAtm {
+ base iana-interface-type;
+ description
+ "Voice over ATM.";
+ }
+ identity voiceOverFrameRelay {
+ base iana-interface-type;
+ description
+ "Voice Over Frame Relay.";
+ }
+ identity idsl {
+ base iana-interface-type;
+ description
+ "Digital Subscriber Loop over ISDN.";
+ }
+ identity compositeLink {
+ base iana-interface-type;
+ description
+ "Avici Composite Link Interface.";
+ }
+ identity ss7SigLink {
+ base iana-interface-type;
+ description
+ "SS7 Signaling Link.";
+ }
+ identity propWirelessP2P {
+ base iana-interface-type;
+ description
+ "Prop. P2P wireless interface.";
+ }
+ identity frForward {
+ base iana-interface-type;
+ description
+ "Frame Forward Interface.";
+ }
+ identity rfc1483 {
+ base iana-interface-type;
+ description
+ "Multiprotocol over ATM AAL5.";
+ reference
+ "RFC 1483 - Multiprotocol Encapsulation over ATM
+ Adaptation Layer 5";
+ }
+ identity usb {
+ base iana-interface-type;
+ description
+ "USB Interface.";
+ }
+ identity ieee8023adLag {
+ base iana-interface-type;
+ description
+ "IEEE 802.3ad Link Aggregate.";
+ }
+ identity bgppolicyaccounting {
+ base iana-interface-type;
+ description
+ "BGP Policy Accounting.";
+ }
+ identity frf16MfrBundle {
+ base iana-interface-type;
+ description
+ "FRF.16 Multilink Frame Relay.";
+ }
+ identity h323Gatekeeper {
+ base iana-interface-type;
+ description
+ "H323 Gatekeeper.";
+ }
+ identity h323Proxy {
+ base iana-interface-type;
+ description
+ "H323 Voice and Video Proxy.";
+ }
+ identity mpls {
+ base iana-interface-type;
+ description
+ "MPLS.";
+ }
+ identity mfSigLink {
+ base iana-interface-type;
+ description
+ "Multi-frequency signaling link.";
+ }
+ identity hdsl2 {
+ base iana-interface-type;
+ description
+ "High Bit-Rate DSL - 2nd generation.";
+ }
+ identity shdsl {
+ base iana-interface-type;
+ description
+ "Multirate HDSL2.";
+ }
+ identity ds1FDL {
+ base iana-interface-type;
+ description
+ "Facility Data Link (4Kbps) on a DS1.";
+ }
+ identity pos {
+ base iana-interface-type;
+ description
+ "Packet over SONET/SDH Interface.";
+ }
+
+
+
+ identity dvbAsiIn {
+ base iana-interface-type;
+ description
+ "DVB-ASI Input.";
+ }
+ identity dvbAsiOut {
+ base iana-interface-type;
+ description
+ "DVB-ASI Output.";
+ }
+ identity plc {
+ base iana-interface-type;
+ description
+ "Power Line Communications.";
+ }
+ identity nfas {
+ base iana-interface-type;
+ description
+ "Non-Facility Associated Signaling.";
+ }
+ identity tr008 {
+ base iana-interface-type;
+ description
+ "TR008.";
+ }
+ identity gr303RDT {
+ base iana-interface-type;
+ description
+ "Remote Digital Terminal.";
+ }
+ identity gr303IDT {
+ base iana-interface-type;
+ description
+ "Integrated Digital Terminal.";
+ }
+ identity isup {
+ base iana-interface-type;
+ description
+ "ISUP.";
+ }
+ identity propDocsWirelessMaclayer {
+ base iana-interface-type;
+ description
+ "Cisco proprietary Maclayer.";
+ }
+
+
+
+ identity propDocsWirelessDownstream {
+ base iana-interface-type;
+ description
+ "Cisco proprietary Downstream.";
+ }
+ identity propDocsWirelessUpstream {
+ base iana-interface-type;
+ description
+ "Cisco proprietary Upstream.";
+ }
+ identity hiperlan2 {
+ base iana-interface-type;
+ description
+ "HIPERLAN Type 2 Radio Interface.";
+ }
+ identity propBWAp2Mp {
+ base iana-interface-type;
+ description
+ "PropBroadbandWirelessAccesspt2Multipt (use of this value
+ for IEEE 802.16 WMAN interfaces as per IEEE Std 802.16f
+ is deprecated, and ieee80216WMAN(237) should be used
+ instead).";
+ }
+ identity sonetOverheadChannel {
+ base iana-interface-type;
+ description
+ "SONET Overhead Channel.";
+ }
+ identity digitalWrapperOverheadChannel {
+ base iana-interface-type;
+ description
+ "Digital Wrapper.";
+ }
+ identity aal2 {
+ base iana-interface-type;
+ description
+ "ATM adaptation layer 2.";
+ }
+ identity radioMAC {
+ base iana-interface-type;
+ description
+ "MAC layer over radio links.";
+ }
+ identity atmRadio {
+ base iana-interface-type;
+ description
+ "ATM over radio links.";
+ }
+ identity imt {
+ base iana-interface-type;
+ description
+ "Inter-Machine Trunks.";
+ }
+ identity mvl {
+ base iana-interface-type;
+ description
+ "Multiple Virtual Lines DSL.";
+ }
+ identity reachDSL {
+ base iana-interface-type;
+ description
+ "Long Reach DSL.";
+ }
+ identity frDlciEndPt {
+ base iana-interface-type;
+ description
+ "Frame Relay DLCI End Point.";
+ }
+ identity atmVciEndPt {
+ base iana-interface-type;
+ description
+ "ATM VCI End Point.";
+ }
+ identity opticalChannel {
+ base iana-interface-type;
+ description
+ "Optical Channel.";
+ }
+ identity opticalTransport {
+ base iana-interface-type;
+ description
+ "Optical Transport.";
+ }
+ identity propAtm {
+ base iana-interface-type;
+ description
+ "Proprietary ATM.";
+ }
+ identity voiceOverCable {
+ base iana-interface-type;
+ description
+ "Voice Over Cable Interface.";
+ }
+
+
+
+ identity infiniband {
+ base iana-interface-type;
+ description
+ "Infiniband.";
+ }
+ identity teLink {
+ base iana-interface-type;
+ description
+ "TE Link.";
+ }
+ identity q2931 {
+ base iana-interface-type;
+ description
+ "Q.2931.";
+ }
+ identity virtualTg {
+ base iana-interface-type;
+ description
+ "Virtual Trunk Group.";
+ }
+ identity sipTg {
+ base iana-interface-type;
+ description
+ "SIP Trunk Group.";
+ }
+ identity sipSig {
+ base iana-interface-type;
+ description
+ "SIP Signaling.";
+ }
+ identity docsCableUpstreamChannel {
+ base iana-interface-type;
+ description
+ "CATV Upstream Channel.";
+ }
+ identity econet {
+ base iana-interface-type;
+ description
+ "Acorn Econet.";
+ }
+ identity pon155 {
+ base iana-interface-type;
+ description
+ "FSAN 155Mb Symetrical PON interface.";
+ }
+
+
+
+ identity pon622 {
+ base iana-interface-type;
+ description
+ "FSAN 622Mb Symetrical PON interface.";
+ }
+ identity bridge {
+ base iana-interface-type;
+ description
+ "Transparent bridge interface.";
+ }
+ identity linegroup {
+ base iana-interface-type;
+ description
+ "Interface common to multiple lines.";
+ }
+ identity voiceEMFGD {
+ base iana-interface-type;
+ description
+ "Voice E&M Feature Group D.";
+ }
+ identity voiceFGDEANA {
+ base iana-interface-type;
+ description
+ "Voice FGD Exchange Access North American.";
+ }
+ identity voiceDID {
+ base iana-interface-type;
+ description
+ "Voice Direct Inward Dialing.";
+ }
+ identity mpegTransport {
+ base iana-interface-type;
+ description
+ "MPEG transport interface.";
+ }
+ identity sixToFour {
+ base iana-interface-type;
+ status deprecated;
+ description
+ "6to4 interface (DEPRECATED).";
+ reference
+ "RFC 4087 - IP Tunnel MIB";
+ }
+ identity gtp {
+ base iana-interface-type;
+ description
+ "GTP (GPRS Tunneling Protocol).";
+ }
+ identity pdnEtherLoop1 {
+ base iana-interface-type;
+ description
+ "Paradyne EtherLoop 1.";
+ }
+ identity pdnEtherLoop2 {
+ base iana-interface-type;
+ description
+ "Paradyne EtherLoop 2.";
+ }
+ identity opticalChannelGroup {
+ base iana-interface-type;
+ description
+ "Optical Channel Group.";
+ }
+ identity homepna {
+ base iana-interface-type;
+ description
+ "HomePNA ITU-T G.989.";
+ }
+ identity gfp {
+ base iana-interface-type;
+ description
+ "Generic Framing Procedure (GFP).";
+ }
+ identity ciscoISLvlan {
+ base iana-interface-type;
+ description
+ "Layer 2 Virtual LAN using Cisco ISL.";
+ }
+ identity actelisMetaLOOP {
+ base iana-interface-type;
+ description
+ "Acteleis proprietary MetaLOOP High Speed Link.";
+ }
+ identity fcipLink {
+ base iana-interface-type;
+ description
+ "FCIP Link.";
+ }
+ identity rpr {
+ base iana-interface-type;
+ description
+ "Resilient Packet Ring Interface Type.";
+ }
+
+
+
+ identity qam {
+ base iana-interface-type;
+ description
+ "RF Qam Interface.";
+ }
+ identity lmp {
+ base iana-interface-type;
+ description
+ "Link Management Protocol.";
+ reference
+ "RFC 4327 - Link Management Protocol (LMP) Management
+ Information Base (MIB)";
+ }
+ identity cblVectaStar {
+ base iana-interface-type;
+ description
+ "Cambridge Broadband Networks Limited VectaStar.";
+ }
+ identity docsCableMCmtsDownstream {
+ base iana-interface-type;
+ description
+ "CATV Modular CMTS Downstream Interface.";
+ }
+ identity adsl2 {
+ base iana-interface-type;
+ status deprecated;
+ description
+ "Asymmetric Digital Subscriber Loop Version 2
+ (DEPRECATED/OBSOLETED - please use adsl2plus(238)
+ instead).";
+ reference
+ "RFC 4706 - Definitions of Managed Objects for Asymmetric
+ Digital Subscriber Line 2 (ADSL2)";
+ }
+ identity macSecControlledIF {
+ base iana-interface-type;
+ description
+ "MACSecControlled.";
+ }
+ identity macSecUncontrolledIF {
+ base iana-interface-type;
+ description
+ "MACSecUncontrolled.";
+ }
+ identity aviciOpticalEther {
+ base iana-interface-type;
+ description
+ "Avici Optical Ethernet Aggregate.";
+ }
+ identity atmbond {
+ base iana-interface-type;
+ description
+ "atmbond.";
+ }
+ identity voiceFGDOS {
+ base iana-interface-type;
+ description
+ "Voice FGD Operator Services.";
+ }
+ identity mocaVersion1 {
+ base iana-interface-type;
+ description
+ "MultiMedia over Coax Alliance (MoCA) Interface
+ as documented in information provided privately to IANA.";
+ }
+ identity ieee80216WMAN {
+ base iana-interface-type;
+ description
+ "IEEE 802.16 WMAN interface.";
+ }
+ identity adsl2plus {
+ base iana-interface-type;
+ description
+ "Asymmetric Digital Subscriber Loop Version 2 -
+ Version 2 Plus and all variants.";
+ }
+ identity dvbRcsMacLayer {
+ base iana-interface-type;
+ description
+ "DVB-RCS MAC Layer.";
+ reference
+ "RFC 5728 - The SatLabs Group DVB-RCS MIB";
+ }
+ identity dvbTdm {
+ base iana-interface-type;
+ description
+ "DVB Satellite TDM.";
+ reference
+ "RFC 5728 - The SatLabs Group DVB-RCS MIB";
+ }
+ identity dvbRcsTdma {
+ base iana-interface-type;
+ description
+ "DVB-RCS TDMA.";
+ reference
+ "RFC 5728 - The SatLabs Group DVB-RCS MIB";
+ }
+ identity x86Laps {
+ base iana-interface-type;
+ description
+ "LAPS based on ITU-T X.86/Y.1323.";
+ }
+ identity wwanPP {
+ base iana-interface-type;
+ description
+ "3GPP WWAN.";
+ }
+ identity wwanPP2 {
+ base iana-interface-type;
+ description
+ "3GPP2 WWAN.";
+ }
+ identity voiceEBS {
+ base iana-interface-type;
+ description
+ "Voice P-phone EBS physical interface.";
+ }
+ identity ifPwType {
+ base iana-interface-type;
+ description
+ "Pseudowire interface type.";
+ reference
+ "RFC 5601 - Pseudowire (PW) Management Information Base (MIB)";
+ }
+ identity ilan {
+ base iana-interface-type;
+ description
+ "Internal LAN on a bridge per IEEE 802.1ap.";
+ }
+ identity pip {
+ base iana-interface-type;
+ description
+ "Provider Instance Port on a bridge per IEEE 802.1ah PBB.";
+ }
+ identity aluELP {
+ base iana-interface-type;
+ description
+ "Alcatel-Lucent Ethernet Link Protection.";
+ }
+ identity gpon {
+ base iana-interface-type;
+ description
+ "Gigabit-capable passive optical networks (G-PON) as per
+ ITU-T G.948.";
+ }
+ identity vdsl2 {
+ base iana-interface-type;
+ description
+ "Very high speed digital subscriber line Version 2
+ (as per ITU-T Recommendation G.993.2).";
+ reference
+ "RFC 5650 - Definitions of Managed Objects for Very High
+ Speed Digital Subscriber Line 2 (VDSL2)";
+ }
+ identity capwapDot11Profile {
+ base iana-interface-type;
+ description
+ "WLAN Profile Interface.";
+ reference
+ "RFC 5834 - Control and Provisioning of Wireless Access
+ Points (CAPWAP) Protocol Binding MIB for
+ IEEE 802.11";
+ }
+ identity capwapDot11Bss {
+ base iana-interface-type;
+ description
+ "WLAN BSS Interface.";
+ reference
+ "RFC 5834 - Control and Provisioning of Wireless Access
+ Points (CAPWAP) Protocol Binding MIB for
+ IEEE 802.11";
+ }
+ identity capwapWtpVirtualRadio {
+ base iana-interface-type;
+ description
+ "WTP Virtual Radio Interface.";
+ reference
+ "RFC 5833 - Control and Provisioning of Wireless Access
+ Points (CAPWAP) Protocol Base MIB";
+ }
+ identity bits {
+ base iana-interface-type;
+ description
+ "bitsport.";
+ }
+ identity docsCableUpstreamRfPort {
+ base iana-interface-type;
+ description
+ "DOCSIS CATV Upstream RF Port.";
+ }
+
+
+ identity cableDownstreamRfPort {
+ base iana-interface-type;
+ description
+ "CATV downstream RF Port.";
+ }
+ identity vmwareVirtualNic {
+ base iana-interface-type;
+ description
+ "VMware Virtual Network Interface.";
+ }
+ identity ieee802154 {
+ base iana-interface-type;
+ description
+ "IEEE 802.15.4 WPAN interface.";
+ reference
+ "IEEE 802.15.4-2006";
+ }
+ identity otnOdu {
+ base iana-interface-type;
+ description
+ "OTN Optical Data Unit.";
+ }
+ identity otnOtu {
+ base iana-interface-type;
+ description
+ "OTN Optical channel Transport Unit.";
+ }
+ identity ifVfiType {
+ base iana-interface-type;
+ description
+ "VPLS Forwarding Instance Interface Type.";
+ }
+ identity g9981 {
+ base iana-interface-type;
+ description
+ "G.998.1 bonded interface.";
+ }
+ identity g9982 {
+ base iana-interface-type;
+ description
+ "G.998.2 bonded interface.";
+ }
+ identity g9983 {
+ base iana-interface-type;
+ description
+ "G.998.3 bonded interface.";
+ }
+
+ identity aluEpon {
+ base iana-interface-type;
+ description
+ "Ethernet Passive Optical Networks (E-PON).";
+ }
+ identity aluEponOnu {
+ base iana-interface-type;
+ description
+ "EPON Optical Network Unit.";
+ }
+ identity aluEponPhysicalUni {
+ base iana-interface-type;
+ description
+ "EPON physical User to Network interface.";
+ }
+ identity aluEponLogicalLink {
+ base iana-interface-type;
+ description
+ "The emulation of a point-to-point link over the EPON
+ layer.";
+ }
+ identity aluGponOnu {
+ base iana-interface-type;
+ description
+ "GPON Optical Network Unit.";
+ reference
+ "ITU-T G.984.2";
+ }
+ identity aluGponPhysicalUni {
+ base iana-interface-type;
+ description
+ "GPON physical User to Network interface.";
+ reference
+ "ITU-T G.984.2";
+ }
+ identity vmwareNicTeam {
+ base iana-interface-type;
+ description
+ "VMware NIC Team.";
+ }
+}
diff --git a/tools/lint/examples/ietf-interfaces.yang b/tools/lint/examples/ietf-interfaces.yang
new file mode 100644
index 0000000..ad64425
--- /dev/null
+++ b/tools/lint/examples/ietf-interfaces.yang
@@ -0,0 +1,725 @@
+module ietf-interfaces {
+
+ namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces";
+ prefix if;
+
+ import ietf-yang-types {
+ prefix yang;
+ }
+
+ organization
+ "IETF NETMOD (NETCONF Data Modeling Language) Working Group";
+
+ contact
+ "WG Web: <http://tools.ietf.org/wg/netmod/>
+ WG List: <mailto:netmod@ietf.org>
+
+ WG Chair: Thomas Nadeau
+ <mailto:tnadeau@lucidvision.com>
+
+ WG Chair: Juergen Schoenwaelder
+ <mailto:j.schoenwaelder@jacobs-university.de>
+
+ Editor: Martin Bjorklund
+ <mailto:mbj@tail-f.com>";
+
+ description
+ "This module contains a collection of YANG definitions for
+ managing network interfaces.
+
+ Copyright (c) 2014 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 7223; see
+ the RFC itself for full legal notices.";
+
+ revision 2014-05-08 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 7223: A YANG Data Model for Interface Management";
+ }
+
+ /*
+ * Typedefs
+ */
+
+ typedef interface-ref {
+ type leafref {
+ path "/if:interfaces/if:interface/if:name";
+ }
+ description
+ "This type is used by data models that need to reference
+ configured interfaces.";
+ }
+
+ typedef interface-state-ref {
+ type leafref {
+ path "/if:interfaces-state/if:interface/if:name";
+ }
+ description
+ "This type is used by data models that need to reference
+ the operationally present interfaces.";
+ }
+
+ /*
+ * Identities
+ */
+
+ identity interface-type {
+ description
+ "Base identity from which specific interface types are
+ derived.";
+ }
+
+ /*
+ * Features
+ */
+
+ feature arbitrary-names {
+ description
+ "This feature indicates that the device allows user-controlled
+ interfaces to be named arbitrarily.";
+ }
+ feature pre-provisioning {
+ description
+ "This feature indicates that the device supports
+ pre-provisioning of interface configuration, i.e., it is
+ possible to configure an interface whose physical interface
+ hardware is not present on the device.";
+ }
+
+ feature if-mib {
+ description
+ "This feature indicates that the device implements
+ the IF-MIB.";
+ reference
+ "RFC 2863: The Interfaces Group MIB";
+ }
+
+ /*
+ * Configuration data nodes
+ */
+
+ container interfaces {
+ description
+ "Interface configuration parameters.";
+
+ list interface {
+ key "name";
+
+ description
+ "The list of configured interfaces on the device.
+
+ The operational state of an interface is available in the
+ /interfaces-state/interface list. If the configuration of a
+ system-controlled interface cannot be used by the system
+ (e.g., the interface hardware present does not match the
+ interface type), then the configuration is not applied to
+ the system-controlled interface shown in the
+ /interfaces-state/interface list. If the configuration
+ of a user-controlled interface cannot be used by the system,
+ the configured interface is not instantiated in the
+ /interfaces-state/interface list.";
+
+ leaf name {
+ type string;
+ description
+ "The name of the interface.
+
+ A device MAY restrict the allowed values for this leaf,
+ possibly depending on the type of the interface.
+ For system-controlled interfaces, this leaf is the
+ device-specific name of the interface. The 'config false'
+ list /interfaces-state/interface contains the currently
+ existing interfaces on the device.
+
+ If a client tries to create configuration for a
+ system-controlled interface that is not present in the
+ /interfaces-state/interface list, the server MAY reject
+ the request if the implementation does not support
+ pre-provisioning of interfaces or if the name refers to
+ an interface that can never exist in the system. A
+ NETCONF server MUST reply with an rpc-error with the
+ error-tag 'invalid-value' in this case.
+
+ If the device supports pre-provisioning of interface
+ configuration, the 'pre-provisioning' feature is
+ advertised.
+
+ If the device allows arbitrarily named user-controlled
+ interfaces, the 'arbitrary-names' feature is advertised.
+
+ When a configured user-controlled interface is created by
+ the system, it is instantiated with the same name in the
+ /interface-state/interface list.";
+ }
+
+ leaf description {
+ type string;
+ description
+ "A textual description of the interface.
+
+ A server implementation MAY map this leaf to the ifAlias
+ MIB object. Such an implementation needs to use some
+ mechanism to handle the differences in size and characters
+ allowed between this leaf and ifAlias. The definition of
+ such a mechanism is outside the scope of this document.
+
+ Since ifAlias is defined to be stored in non-volatile
+ storage, the MIB implementation MUST map ifAlias to the
+ value of 'description' in the persistently stored
+ datastore.
+
+ Specifically, if the device supports ':startup', when
+ ifAlias is read the device MUST return the value of
+ 'description' in the 'startup' datastore, and when it is
+ written, it MUST be written to the 'running' and 'startup'
+ datastores. Note that it is up to the implementation to
+
+ decide whether to modify this single leaf in 'startup' or
+ perform an implicit copy-config from 'running' to
+ 'startup'.
+
+ If the device does not support ':startup', ifAlias MUST
+ be mapped to the 'description' leaf in the 'running'
+ datastore.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifAlias";
+ }
+
+ leaf type {
+ type identityref {
+ base interface-type;
+ }
+ mandatory true;
+ description
+ "The type of the interface.
+
+ When an interface entry is created, a server MAY
+ initialize the type leaf with a valid value, e.g., if it
+ is possible to derive the type from the name of the
+ interface.
+
+ If a client tries to set the type of an interface to a
+ value that can never be used by the system, e.g., if the
+ type is not supported or if the type does not match the
+ name of the interface, the server MUST reject the request.
+ A NETCONF server MUST reply with an rpc-error with the
+ error-tag 'invalid-value' in this case.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifType";
+ }
+
+ leaf enabled {
+ type boolean;
+ default "true";
+ description
+ "This leaf contains the configured, desired state of the
+ interface.
+
+ Systems that implement the IF-MIB use the value of this
+ leaf in the 'running' datastore to set
+ IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry
+ has been initialized, as described in RFC 2863.
+
+
+
+ Changes in this leaf in the 'running' datastore are
+ reflected in ifAdminStatus, but if ifAdminStatus is
+ changed over SNMP, this leaf is not affected.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifAdminStatus";
+ }
+
+ leaf link-up-down-trap-enable {
+ if-feature if-mib;
+ type enumeration {
+ enum enabled {
+ value 1;
+ }
+ enum disabled {
+ value 2;
+ }
+ }
+ description
+ "Controls whether linkUp/linkDown SNMP notifications
+ should be generated for this interface.
+
+ If this node is not configured, the value 'enabled' is
+ operationally used by the server for interfaces that do
+ not operate on top of any other interface (i.e., there are
+ no 'lower-layer-if' entries), and 'disabled' otherwise.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifLinkUpDownTrapEnable";
+ }
+ }
+ }
+
+ /*
+ * Operational state data nodes
+ */
+
+ container interfaces-state {
+ config false;
+ description
+ "Data nodes for the operational state of interfaces.";
+
+ list interface {
+ key "name";
+
+
+
+
+
+ description
+ "The list of interfaces on the device.
+
+ System-controlled interfaces created by the system are
+ always present in this list, whether they are configured or
+ not.";
+
+ leaf name {
+ type string;
+ description
+ "The name of the interface.
+
+ A server implementation MAY map this leaf to the ifName
+ MIB object. Such an implementation needs to use some
+ mechanism to handle the differences in size and characters
+ allowed between this leaf and ifName. The definition of
+ such a mechanism is outside the scope of this document.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifName";
+ }
+
+ leaf type {
+ type identityref {
+ base interface-type;
+ }
+ mandatory true;
+ description
+ "The type of the interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifType";
+ }
+
+ leaf admin-status {
+ if-feature if-mib;
+ type enumeration {
+ enum up {
+ value 1;
+ description
+ "Ready to pass packets.";
+ }
+ enum down {
+ value 2;
+ description
+ "Not ready to pass packets and not in some test mode.";
+ }
+
+
+
+ enum testing {
+ value 3;
+ description
+ "In some test mode.";
+ }
+ }
+ mandatory true;
+ description
+ "The desired state of the interface.
+
+ This leaf has the same read semantics as ifAdminStatus.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifAdminStatus";
+ }
+
+ leaf oper-status {
+ type enumeration {
+ enum up {
+ value 1;
+ description
+ "Ready to pass packets.";
+ }
+ enum down {
+ value 2;
+ description
+ "The interface does not pass any packets.";
+ }
+ enum testing {
+ value 3;
+ description
+ "In some test mode. No operational packets can
+ be passed.";
+ }
+ enum unknown {
+ value 4;
+ description
+ "Status cannot be determined for some reason.";
+ }
+ enum dormant {
+ value 5;
+ description
+ "Waiting for some external event.";
+ }
+ enum not-present {
+ value 6;
+ description
+ "Some component (typically hardware) is missing.";
+ }
+ enum lower-layer-down {
+ value 7;
+ description
+ "Down due to state of lower-layer interface(s).";
+ }
+ }
+ mandatory true;
+ description
+ "The current operational state of the interface.
+
+ This leaf has the same semantics as ifOperStatus.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOperStatus";
+ }
+
+ leaf last-change {
+ type yang:date-and-time;
+ description
+ "The time the interface entered its current operational
+ state. If the current state was entered prior to the
+ last re-initialization of the local network management
+ subsystem, then this node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifLastChange";
+ }
+
+ leaf if-index {
+ if-feature if-mib;
+ type int32 {
+ range "1..2147483647";
+ }
+ mandatory true;
+ description
+ "The ifIndex value for the ifEntry represented by this
+ interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifIndex";
+ }
+
+ leaf phys-address {
+ type yang:phys-address;
+ description
+ "The interface's address at its protocol sub-layer. For
+ example, for an 802.x interface, this object normally
+ contains a Media Access Control (MAC) address. The
+ interface's media-specific modules must define the bit
+
+
+ and byte ordering and the format of the value of this
+ object. For interfaces that do not have such an address
+ (e.g., a serial line), this node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifPhysAddress";
+ }
+
+ leaf-list higher-layer-if {
+ type interface-state-ref;
+ description
+ "A list of references to interfaces layered on top of this
+ interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifStackTable";
+ }
+
+ leaf-list lower-layer-if {
+ type interface-state-ref;
+ description
+ "A list of references to interfaces layered underneath this
+ interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifStackTable";
+ }
+
+ leaf speed {
+ type yang:gauge64;
+ units "bits/second";
+ description
+ "An estimate of the interface's current bandwidth in bits
+ per second. For interfaces that do not vary in
+ bandwidth or for those where no accurate estimation can
+ be made, this node should contain the nominal bandwidth.
+ For interfaces that have no concept of bandwidth, this
+ node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifSpeed, ifHighSpeed";
+ }
+
+
+
+
+
+
+
+
+
+ container statistics {
+ description
+ "A collection of interface-related statistics objects.";
+
+ leaf discontinuity-time {
+ type yang:date-and-time;
+ mandatory true;
+ description
+ "The time on the most recent occasion at which any one or
+ more of this interface's counters suffered a
+ discontinuity. If no such discontinuities have occurred
+ since the last re-initialization of the local management
+ subsystem, then this node contains the time the local
+ management subsystem re-initialized itself.";
+ }
+
+ leaf in-octets {
+ type yang:counter64;
+ description
+ "The total number of octets received on the interface,
+ including framing characters.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCInOctets";
+ }
+
+ leaf in-unicast-pkts {
+ type yang:counter64;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were not addressed to a
+ multicast or broadcast address at this sub-layer.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts";
+ }
+
+
+
+
+ leaf in-broadcast-pkts {
+ type yang:counter64;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were addressed to a broadcast
+ address at this sub-layer.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCInBroadcastPkts";
+ }
+
+ leaf in-multicast-pkts {
+ type yang:counter64;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were addressed to a multicast
+ address at this sub-layer. For a MAC-layer protocol,
+ this includes both Group and Functional addresses.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCInMulticastPkts";
+ }
+
+ leaf in-discards {
+ type yang:counter32;
+ description
+ "The number of inbound packets that were chosen to be
+ discarded even though no errors had been detected to
+ prevent their being deliverable to a higher-layer
+ protocol. One possible reason for discarding such a
+ packet could be to free up buffer space.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+
+
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInDiscards";
+ }
+
+ leaf in-errors {
+ type yang:counter32;
+ description
+ "For packet-oriented interfaces, the number of inbound
+ packets that contained errors preventing them from being
+ deliverable to a higher-layer protocol. For character-
+ oriented or fixed-length interfaces, the number of
+ inbound transmission units that contained errors
+ preventing them from being deliverable to a higher-layer
+ protocol.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInErrors";
+ }
+
+ leaf in-unknown-protos {
+ type yang:counter32;
+ description
+ "For packet-oriented interfaces, the number of packets
+ received via the interface that were discarded because
+ of an unknown or unsupported protocol. For
+ character-oriented or fixed-length interfaces that
+ support protocol multiplexing, the number of
+ transmission units received via the interface that were
+ discarded because of an unknown or unsupported protocol.
+ For any interface that does not support protocol
+ multiplexing, this counter is not present.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos";
+ }
+
+
+
+
+
+ leaf out-octets {
+ type yang:counter64;
+ description
+ "The total number of octets transmitted out of the
+ interface, including framing characters.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCOutOctets";
+ }
+
+ leaf out-unicast-pkts {
+ type yang:counter64;
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted, and that were not addressed
+ to a multicast or broadcast address at this sub-layer,
+ including those that were discarded or not sent.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts";
+ }
+
+ leaf out-broadcast-pkts {
+ type yang:counter64;
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted, and that were addressed to a
+ broadcast address at this sub-layer, including those
+ that were discarded or not sent.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCOutBroadcastPkts";
+ }
+
+
+ leaf out-multicast-pkts {
+ type yang:counter64;
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted, and that were addressed to a
+ multicast address at this sub-layer, including those
+ that were discarded or not sent. For a MAC-layer
+ protocol, this includes both Group and Functional
+ addresses.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCOutMulticastPkts";
+ }
+
+ leaf out-discards {
+ type yang:counter32;
+ description
+ "The number of outbound packets that were chosen to be
+ discarded even though no errors had been detected to
+ prevent their being transmitted. One possible reason
+ for discarding such a packet could be to free up buffer
+ space.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOutDiscards";
+ }
+
+ leaf out-errors {
+ type yang:counter32;
+ description
+ "For packet-oriented interfaces, the number of outbound
+ packets that could not be transmitted because of errors.
+ For character-oriented or fixed-length interfaces, the
+ number of outbound transmission units that could not be
+ transmitted because of errors.
+
+
+
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system, and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOutErrors";
+ }
+ }
+ }
+ }
+}
diff --git a/tools/lint/examples/ietf-ip.yang b/tools/lint/examples/ietf-ip.yang
new file mode 100644
index 0000000..1499120
--- /dev/null
+++ b/tools/lint/examples/ietf-ip.yang
@@ -0,0 +1,758 @@
+module ietf-ip {
+
+ namespace "urn:ietf:params:xml:ns:yang:ietf-ip";
+ prefix ip;
+
+ import ietf-interfaces {
+ prefix if;
+ }
+ import ietf-inet-types {
+ prefix inet;
+ }
+ import ietf-yang-types {
+ prefix yang;
+ }
+
+ organization
+ "IETF NETMOD (NETCONF Data Modeling Language) Working Group";
+
+ contact
+ "WG Web: <http://tools.ietf.org/wg/netmod/>
+ WG List: <mailto:netmod@ietf.org>
+
+ WG Chair: Thomas Nadeau
+ <mailto:tnadeau@lucidvision.com>
+
+ WG Chair: Juergen Schoenwaelder
+ <mailto:j.schoenwaelder@jacobs-university.de>
+
+ Editor: Martin Bjorklund
+ <mailto:mbj@tail-f.com>";
+
+
+
+
+
+
+
+
+
+
+ description
+ "This module contains a collection of YANG definitions for
+ configuring IP implementations.
+
+ Copyright (c) 2014 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 7277; see
+ the RFC itself for full legal notices.";
+
+ revision 2014-06-16 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 7277: A YANG Data Model for IP Management";
+ }
+
+ /*
+
+ * Features
+ */
+
+ feature ipv4-non-contiguous-netmasks {
+ description
+ "Indicates support for configuring non-contiguous
+ subnet masks.";
+ }
+
+ feature ipv6-privacy-autoconf {
+ description
+ "Indicates support for Privacy Extensions for Stateless Address
+ Autoconfiguration in IPv6.";
+ reference
+ "RFC 4941: Privacy Extensions for Stateless Address
+ Autoconfiguration in IPv6";
+ }
+
+
+
+
+
+ /*
+ * Typedefs
+ */
+
+ typedef ip-address-origin {
+ type enumeration {
+ enum other {
+ description
+ "None of the following.";
+ }
+ enum static {
+ description
+ "Indicates that the address has been statically
+ configured - for example, using NETCONF or a Command Line
+ Interface.";
+ }
+ enum dhcp {
+ description
+ "Indicates an address that has been assigned to this
+ system by a DHCP server.";
+ }
+ enum link-layer {
+ description
+ "Indicates an address created by IPv6 stateless
+ autoconfiguration that embeds a link-layer address in its
+ interface identifier.";
+ }
+ enum random {
+ description
+ "Indicates an address chosen by the system at
+
+ random, e.g., an IPv4 address within 169.254/16, an
+ RFC 4941 temporary address, or an RFC 7217 semantically
+ opaque address.";
+ reference
+ "RFC 4941: Privacy Extensions for Stateless Address
+ Autoconfiguration in IPv6
+ RFC 7217: A Method for Generating Semantically Opaque
+ Interface Identifiers with IPv6 Stateless
+ Address Autoconfiguration (SLAAC)";
+ }
+ }
+ description
+ "The origin of an address.";
+ }
+
+
+
+ typedef neighbor-origin {
+ type enumeration {
+ enum other {
+ description
+ "None of the following.";
+ }
+ enum static {
+ description
+ "Indicates that the mapping has been statically
+ configured - for example, using NETCONF or a Command Line
+ Interface.";
+ }
+ enum dynamic {
+ description
+ "Indicates that the mapping has been dynamically resolved
+ using, e.g., IPv4 ARP or the IPv6 Neighbor Discovery
+ protocol.";
+ }
+ }
+ description
+ "The origin of a neighbor entry.";
+ }
+
+ /*
+ * Configuration data nodes
+ */
+
+ augment "/if:interfaces/if:interface" {
+ description
+ "Parameters for configuring IP on interfaces.
+
+ If an interface is not capable of running IP, the server
+ must not allow the client to configure these parameters.";
+
+ container ipv4 {
+ presence
+ "Enables IPv4 unless the 'enabled' leaf
+ (which defaults to 'true') is set to 'false'";
+ description
+ "Parameters for the IPv4 address family.";
+
+
+
+
+
+
+
+
+ leaf enabled {
+ type boolean;
+ default true;
+ description
+ "Controls whether IPv4 is enabled or disabled on this
+ interface. When IPv4 is enabled, this interface is
+ connected to an IPv4 stack, and the interface can send
+ and receive IPv4 packets.";
+ }
+ leaf forwarding {
+ type boolean;
+ default false;
+ description
+ "Controls IPv4 packet forwarding of datagrams received by,
+ but not addressed to, this interface. IPv4 routers
+ forward datagrams. IPv4 hosts do not (except those
+ source-routed via the host).";
+ }
+ leaf mtu {
+ type uint16 {
+ range "68..max";
+ }
+ units octets;
+ description
+ "The size, in octets, of the largest IPv4 packet that the
+ interface will send and receive.
+
+ The server may restrict the allowed values for this leaf,
+ depending on the interface's type.
+
+ If this leaf is not configured, the operationally used MTU
+ depends on the interface's type.";
+ reference
+ "RFC 791: Internet Protocol";
+ }
+ list address {
+ key "ip";
+ description
+ "The list of configured IPv4 addresses on the interface.";
+
+ leaf ip {
+ type inet:ipv4-address-no-zone;
+ description
+ "The IPv4 address on the interface.";
+ }
+
+
+
+ choice subnet {
+ mandatory true;
+ description
+ "The subnet can be specified as a prefix-length, or,
+ if the server supports non-contiguous netmasks, as
+ a netmask.";
+ leaf prefix-length {
+ type uint8 {
+ range "0..32";
+ }
+ description
+ "The length of the subnet prefix.";
+ }
+ leaf netmask {
+ if-feature ipv4-non-contiguous-netmasks;
+ type yang:dotted-quad;
+ description
+ "The subnet specified as a netmask.";
+ }
+ }
+ }
+ list neighbor {
+ key "ip";
+ description
+ "A list of mappings from IPv4 addresses to
+ link-layer addresses.
+
+ Entries in this list are used as static entries in the
+ ARP Cache.";
+ reference
+ "RFC 826: An Ethernet Address Resolution Protocol";
+
+ leaf ip {
+ type inet:ipv4-address-no-zone;
+ description
+ "The IPv4 address of the neighbor node.";
+ }
+ leaf link-layer-address {
+ type yang:phys-address;
+ mandatory true;
+ description
+ "The link-layer address of the neighbor node.";
+ }
+ }
+
+ }
+
+
+ container ipv6 {
+ presence
+ "Enables IPv6 unless the 'enabled' leaf
+ (which defaults to 'true') is set to 'false'";
+ description
+ "Parameters for the IPv6 address family.";
+
+ leaf enabled {
+ type boolean;
+ default true;
+ description
+ "Controls whether IPv6 is enabled or disabled on this
+ interface. When IPv6 is enabled, this interface is
+ connected to an IPv6 stack, and the interface can send
+ and receive IPv6 packets.";
+ }
+ leaf forwarding {
+ type boolean;
+ default false;
+ description
+ "Controls IPv6 packet forwarding of datagrams received by,
+ but not addressed to, this interface. IPv6 routers
+ forward datagrams. IPv6 hosts do not (except those
+ source-routed via the host).";
+ reference
+ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)
+ Section 6.2.1, IsRouter";
+ }
+ leaf mtu {
+ type uint32 {
+ range "1280..max";
+ }
+ units octets;
+ description
+ "The size, in octets, of the largest IPv6 packet that the
+ interface will send and receive.
+
+ The server may restrict the allowed values for this leaf,
+ depending on the interface's type.
+
+ If this leaf is not configured, the operationally used MTU
+ depends on the interface's type.";
+ reference
+ "RFC 2460: Internet Protocol, Version 6 (IPv6) Specification
+ Section 5";
+ }
+
+
+ list address {
+ key "ip";
+ description
+ "The list of configured IPv6 addresses on the interface.";
+
+ leaf ip {
+ type inet:ipv6-address-no-zone;
+ description
+ "The IPv6 address on the interface.";
+ }
+ leaf prefix-length {
+ type uint8 {
+ range "0..128";
+ }
+ mandatory true;
+ description
+ "The length of the subnet prefix.";
+ }
+ }
+ list neighbor {
+ key "ip";
+ description
+ "A list of mappings from IPv6 addresses to
+ link-layer addresses.
+
+ Entries in this list are used as static entries in the
+ Neighbor Cache.";
+ reference
+ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)";
+
+ leaf ip {
+ type inet:ipv6-address-no-zone;
+ description
+ "The IPv6 address of the neighbor node.";
+ }
+ leaf link-layer-address {
+ type yang:phys-address;
+ mandatory true;
+ description
+ "The link-layer address of the neighbor node.";
+ }
+ }
+
+
+
+
+
+
+ leaf dup-addr-detect-transmits {
+ type uint32;
+ default 1;
+ description
+ "The number of consecutive Neighbor Solicitation messages
+ sent while performing Duplicate Address Detection on a
+ tentative address. A value of zero indicates that
+ Duplicate Address Detection is not performed on
+ tentative addresses. A value of one indicates a single
+ transmission with no follow-up retransmissions.";
+ reference
+ "RFC 4862: IPv6 Stateless Address Autoconfiguration";
+ }
+ container autoconf {
+ description
+ "Parameters to control the autoconfiguration of IPv6
+ addresses, as described in RFC 4862.";
+ reference
+ "RFC 4862: IPv6 Stateless Address Autoconfiguration";
+
+ leaf create-global-addresses {
+ type boolean;
+ default true;
+ description
+ "If enabled, the host creates global addresses as
+ described in RFC 4862.";
+ reference
+ "RFC 4862: IPv6 Stateless Address Autoconfiguration
+ Section 5.5";
+ }
+ leaf create-temporary-addresses {
+ if-feature ipv6-privacy-autoconf;
+ type boolean;
+ default false;
+ description
+ "If enabled, the host creates temporary addresses as
+ described in RFC 4941.";
+ reference
+ "RFC 4941: Privacy Extensions for Stateless Address
+ Autoconfiguration in IPv6";
+ }
+
+
+
+
+
+
+
+ leaf temporary-valid-lifetime {
+ if-feature ipv6-privacy-autoconf;
+ type uint32;
+ units "seconds";
+ default 604800;
+ description
+ "The time period during which the temporary address
+ is valid.";
+ reference
+ "RFC 4941: Privacy Extensions for Stateless Address
+ Autoconfiguration in IPv6
+ - TEMP_VALID_LIFETIME";
+ }
+ leaf temporary-preferred-lifetime {
+ if-feature ipv6-privacy-autoconf;
+ type uint32;
+ units "seconds";
+ default 86400;
+ description
+ "The time period during which the temporary address is
+ preferred.";
+ reference
+ "RFC 4941: Privacy Extensions for Stateless Address
+ Autoconfiguration in IPv6
+ - TEMP_PREFERRED_LIFETIME";
+ }
+ }
+ }
+ }
+
+ /*
+ * Operational state data nodes
+ */
+
+ augment "/if:interfaces-state/if:interface" {
+ description
+ "Data nodes for the operational state of IP on interfaces.";
+
+ container ipv4 {
+ presence "Present if IPv4 is enabled on this interface";
+ config false;
+ description
+ "Interface-specific parameters for the IPv4 address family.";
+
+
+
+
+
+ leaf forwarding {
+ type boolean;
+ description
+ "Indicates whether IPv4 packet forwarding is enabled or
+ disabled on this interface.";
+ }
+ leaf mtu {
+ type uint16 {
+ range "68..max";
+ }
+ units octets;
+ description
+ "The size, in octets, of the largest IPv4 packet that the
+ interface will send and receive.";
+ reference
+ "RFC 791: Internet Protocol";
+ }
+ list address {
+ key "ip";
+ description
+ "The list of IPv4 addresses on the interface.";
+
+ leaf ip {
+ type inet:ipv4-address-no-zone;
+ description
+ "The IPv4 address on the interface.";
+ }
+ choice subnet {
+ description
+ "The subnet can be specified as a prefix-length, or,
+ if the server supports non-contiguous netmasks, as
+ a netmask.";
+ leaf prefix-length {
+ type uint8 {
+ range "0..32";
+ }
+ description
+ "The length of the subnet prefix.";
+ }
+ leaf netmask {
+ if-feature ipv4-non-contiguous-netmasks;
+ type yang:dotted-quad;
+ description
+ "The subnet specified as a netmask.";
+ }
+ }
+
+
+ leaf origin {
+ type ip-address-origin;
+ description
+ "The origin of this address.";
+ }
+ }
+ list neighbor {
+ key "ip";
+ description
+ "A list of mappings from IPv4 addresses to
+ link-layer addresses.
+
+ This list represents the ARP Cache.";
+ reference
+ "RFC 826: An Ethernet Address Resolution Protocol";
+
+ leaf ip {
+ type inet:ipv4-address-no-zone;
+ description
+ "The IPv4 address of the neighbor node.";
+ }
+ leaf link-layer-address {
+ type yang:phys-address;
+ description
+ "The link-layer address of the neighbor node.";
+ }
+ leaf origin {
+ type neighbor-origin;
+ description
+ "The origin of this neighbor entry.";
+ }
+ }
+
+ }
+
+ container ipv6 {
+ presence "Present if IPv6 is enabled on this interface";
+ config false;
+ description
+ "Parameters for the IPv6 address family.";
+
+
+
+
+
+
+
+
+ leaf forwarding {
+ type boolean;
+ default false;
+ description
+ "Indicates whether IPv6 packet forwarding is enabled or
+ disabled on this interface.";
+ reference
+ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)
+ Section 6.2.1, IsRouter";
+ }
+ leaf mtu {
+ type uint32 {
+ range "1280..max";
+ }
+ units octets;
+ description
+ "The size, in octets, of the largest IPv6 packet that the
+ interface will send and receive.";
+ reference
+ "RFC 2460: Internet Protocol, Version 6 (IPv6) Specification
+ Section 5";
+ }
+ list address {
+ key "ip";
+ description
+ "The list of IPv6 addresses on the interface.";
+
+ leaf ip {
+ type inet:ipv6-address-no-zone;
+ description
+ "The IPv6 address on the interface.";
+ }
+ leaf prefix-length {
+ type uint8 {
+ range "0..128";
+ }
+ mandatory true;
+ description
+ "The length of the subnet prefix.";
+ }
+ leaf origin {
+ type ip-address-origin;
+ description
+ "The origin of this address.";
+ }
+
+
+
+ leaf status {
+ type enumeration {
+ enum preferred {
+ description
+ "This is a valid address that can appear as the
+ destination or source address of a packet.";
+ }
+ enum deprecated {
+ description
+ "This is a valid but deprecated address that should
+ no longer be used as a source address in new
+ communications, but packets addressed to such an
+ address are processed as expected.";
+ }
+ enum invalid {
+ description
+ "This isn't a valid address, and it shouldn't appear
+ as the destination or source address of a packet.";
+ }
+ enum inaccessible {
+ description
+ "The address is not accessible because the interface
+ to which this address is assigned is not
+ operational.";
+ }
+ enum unknown {
+ description
+ "The status cannot be determined for some reason.";
+ }
+ enum tentative {
+ description
+ "The uniqueness of the address on the link is being
+ verified. Addresses in this state should not be
+ used for general communication and should only be
+ used to determine the uniqueness of the address.";
+ }
+ enum duplicate {
+ description
+ "The address has been determined to be non-unique on
+ the link and so must not be used.";
+ }
+
+
+
+
+
+
+
+ enum optimistic {
+ description
+ "The address is available for use, subject to
+ restrictions, while its uniqueness on a link is
+ being verified.";
+ }
+ }
+ description
+ "The status of an address. Most of the states correspond
+ to states from the IPv6 Stateless Address
+ Autoconfiguration protocol.";
+ reference
+ "RFC 4293: Management Information Base for the
+ Internet Protocol (IP)
+ - IpAddressStatusTC
+ RFC 4862: IPv6 Stateless Address Autoconfiguration";
+ }
+ }
+ list neighbor {
+ key "ip";
+ description
+ "A list of mappings from IPv6 addresses to
+ link-layer addresses.
+
+ This list represents the Neighbor Cache.";
+ reference
+ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)";
+
+ leaf ip {
+ type inet:ipv6-address-no-zone;
+ description
+ "The IPv6 address of the neighbor node.";
+ }
+ leaf link-layer-address {
+ type yang:phys-address;
+ description
+ "The link-layer address of the neighbor node.";
+ }
+ leaf origin {
+ type neighbor-origin;
+ description
+ "The origin of this neighbor entry.";
+ }
+ leaf is-router {
+ type empty;
+ description
+ "Indicates that the neighbor node acts as a router.";
+ }
+ leaf state {
+ type enumeration {
+ enum incomplete {
+ description
+ "Address resolution is in progress, and the link-layer
+ address of the neighbor has not yet been
+ determined.";
+ }
+ enum reachable {
+ description
+ "Roughly speaking, the neighbor is known to have been
+ reachable recently (within tens of seconds ago).";
+ }
+ enum stale {
+ description
+ "The neighbor is no longer known to be reachable, but
+ until traffic is sent to the neighbor no attempt
+ should be made to verify its reachability.";
+ }
+ enum delay {
+ description
+ "The neighbor is no longer known to be reachable, and
+ traffic has recently been sent to the neighbor.
+ Rather than probe the neighbor immediately, however,
+ delay sending probes for a short while in order to
+ give upper-layer protocols a chance to provide
+ reachability confirmation.";
+ }
+ enum probe {
+ description
+ "The neighbor is no longer known to be reachable, and
+ unicast Neighbor Solicitation probes are being sent
+ to verify reachability.";
+ }
+ }
+ description
+ "The Neighbor Unreachability Detection state of this
+ entry.";
+ reference
+ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)
+ Section 7.3.2";
+ }
+ }
+ }
+ }
+}
diff --git a/tools/lint/examples/ietf-netconf-acm-when.yang b/tools/lint/examples/ietf-netconf-acm-when.yang
new file mode 100644
index 0000000..902fcbf
--- /dev/null
+++ b/tools/lint/examples/ietf-netconf-acm-when.yang
@@ -0,0 +1,412 @@
+module ietf-netconf-acm-when {
+ namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-acm";
+ prefix nacm;
+
+ import ietf-yang-types {
+ prefix yang;
+ }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+ contact
+ "WG Web: <http://tools.ietf.org/wg/netconf/>
+ WG List: <mailto:netconf@ietf.org>
+
+ WG Chair: Mehmet Ersue
+ <mailto:mehmet.ersue@nsn.com>
+
+ WG Chair: Bert Wijnen
+ <mailto:bertietf@bwijnen.net>
+
+ Editor: Andy Bierman
+ <mailto:andy@yumaworks.com>
+
+ Editor: Martin Bjorklund
+ <mailto:mbj@tail-f.com>";
+ description
+ "NETCONF Access Control Model.
+
+ Copyright (c) 2012 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD
+ License set forth in Section 4.c of the IETF Trust's
+ Legal Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 6536; see
+ the RFC itself for full legal notices.";
+
+ revision 2012-02-22 {
+ description
+ "Initial version";
+ reference
+ "RFC 6536: Network Configuration Protocol (NETCONF)
+ Access Control Model";
+ }
+
+ extension default-deny-write {
+ description
+ "Used to indicate that the data model node
+ represents a sensitive security system parameter.
+
+ If present, and the NACM module is enabled (i.e.,
+ /nacm/enable-nacm object equals 'true'), the NETCONF server
+ will only allow the designated 'recovery session' to have
+ write access to the node. An explicit access control rule is
+ required for all other users.
+
+ The 'default-deny-write' extension MAY appear within a data
+ definition statement. It is ignored otherwise.";
+ }
+
+ extension default-deny-all {
+ description
+ "Used to indicate that the data model node
+ controls a very sensitive security system parameter.
+
+ If present, and the NACM module is enabled (i.e.,
+ /nacm/enable-nacm object equals 'true'), the NETCONF server
+ will only allow the designated 'recovery session' to have
+ read, write, or execute access to the node. An explicit
+ access control rule is required for all other users.
+
+ The 'default-deny-all' extension MAY appear within a data
+ definition statement, 'rpc' statement, or 'notification'
+ statement. It is ignored otherwise.";
+ }
+
+ typedef user-name-type {
+ type string {
+ length "1..max";
+ }
+ description
+ "General Purpose Username string.";
+ }
+
+ typedef matchall-string-type {
+ type string {
+ pattern "\\*";
+ }
+ description
+ "The string containing a single asterisk '*' is used
+ to conceptually represent all possible values
+ for the particular leaf using this data type.";
+ }
+
+ typedef access-operations-type {
+ type bits {
+ bit create {
+ description
+ "Any protocol operation that creates a
+ new data node.";
+ }
+ bit read {
+ description
+ "Any protocol operation or notification that
+ returns the value of a data node.";
+ }
+ bit update {
+ description
+ "Any protocol operation that alters an existing
+ data node.";
+ }
+ bit delete {
+ description
+ "Any protocol operation that removes a data node.";
+ }
+ bit exec {
+ description
+ "Execution access to the specified protocol operation.";
+ }
+ }
+ description
+ "NETCONF Access Operation.";
+ }
+
+ typedef group-name-type {
+ type string {
+ length "1..max";
+ pattern "[^\\*].*";
+ }
+ description
+ "Name of administrative group to which
+ users can be assigned.";
+ }
+
+ typedef action-type {
+ type enumeration {
+ enum "permit" {
+ description
+ "Requested action is permitted.";
+ }
+ enum "deny" {
+ description
+ "Requested action is denied.";
+ }
+ }
+ description
+ "Action taken by the server when a particular
+ rule matches.";
+ }
+
+ typedef node-instance-identifier {
+ type yang:xpath1.0;
+ description
+ "Path expression used to represent a special
+ data node instance identifier string.
+
+ A node-instance-identifier value is an
+ unrestricted YANG instance-identifier expression.
+ All the same rules as an instance-identifier apply
+ except predicates for keys are optional. If a key
+ predicate is missing, then the node-instance-identifier
+ represents all possible server instances for that key.
+
+ This XPath expression is evaluated in the following context:
+
+ o The set of namespace declarations are those in scope on
+ the leaf element where this type is used.
+
+ o The set of variable bindings contains one variable,
+ 'USER', which contains the name of the user of the current
+ session.
+
+ o The function library is the core function library, but
+ note that due to the syntax restrictions of an
+ instance-identifier, no functions are allowed.
+
+ o The context node is the root node in the data tree.";
+ }
+
+ container nacm {
+ nacm:default-deny-all;
+ description
+ "Parameters for NETCONF Access Control Model.";
+ leaf enable-nacm {
+ type boolean;
+ default "true";
+ description
+ "Enables or disables all NETCONF access control
+ enforcement. If 'true', then enforcement
+ is enabled. If 'false', then enforcement
+ is disabled.";
+ }
+ leaf read-default {
+ type action-type;
+ default "permit";
+ description
+ "Controls whether read access is granted if
+ no appropriate rule is found for a
+ particular read request.";
+ }
+ leaf write-default {
+ type action-type;
+ default "deny";
+ description
+ "Controls whether create, update, or delete access
+ is granted if no appropriate rule is found for a
+ particular write request.";
+ }
+ leaf exec-default {
+ type action-type;
+ default "permit";
+ description
+ "Controls whether exec access is granted if no appropriate
+ rule is found for a particular protocol operation request.";
+ }
+ leaf enable-external-groups {
+ type boolean;
+ default "true";
+ description
+ "Controls whether the server uses the groups reported by the
+ NETCONF transport layer when it assigns the user to a set of
+ NACM groups. If this leaf has the value 'false', any group
+ names reported by the transport layer are ignored by the
+ server.";
+ }
+ leaf denied-operations {
+ type yang:zero-based-counter32;
+ config false;
+ mandatory true;
+ description
+ "Number of times since the server last restarted that a
+ protocol operation request was denied.";
+ }
+ leaf denied-data-writes {
+ type yang:zero-based-counter32;
+ config false;
+ mandatory true;
+ when "../denied-operations > 0";
+ description
+ "Number of times since the server last restarted that a
+ protocol operation request to alter
+ a configuration datastore was denied.";
+ }
+ leaf denied-notifications {
+ type yang:zero-based-counter32;
+ config false;
+ mandatory true;
+ description
+ "Number of times since the server last restarted that
+ a notification was dropped for a subscription because
+ access to the event type was denied.";
+ }
+ container groups {
+ description
+ "NETCONF Access Control Groups.";
+ list group {
+ key "name";
+ description
+ "One NACM Group Entry. This list will only contain
+ configured entries, not any entries learned from
+ any transport protocols.";
+ leaf name {
+ type group-name-type;
+ description
+ "Group name associated with this entry.";
+ }
+ leaf-list user-name {
+ type user-name-type;
+ description
+ "Each entry identifies the username of
+ a member of the group associated with
+ this entry.";
+ }
+ }
+ }
+ list rule-list {
+ key "name";
+ ordered-by user;
+ description
+ "An ordered collection of access control rules.";
+ leaf name {
+ type string {
+ length "1..max";
+ }
+ description
+ "Arbitrary name assigned to the rule-list.";
+ }
+ leaf-list group {
+ type union {
+ type matchall-string-type;
+ type group-name-type;
+ }
+ description
+ "List of administrative groups that will be
+ assigned the associated access rights
+ defined by the 'rule' list.
+
+ The string '*' indicates that all groups apply to the
+ entry.";
+ }
+ list rule {
+ key "name";
+ ordered-by user;
+ description
+ "One access control rule.
+
+ Rules are processed in user-defined order until a match is
+ found. A rule matches if 'module-name', 'rule-type', and
+ 'access-operations' match the request. If a rule
+ matches, the 'action' leaf determines if access is granted
+ or not.";
+ leaf name {
+ type string {
+ length "1..max";
+ }
+ description
+ "Arbitrary name assigned to the rule.";
+ }
+ leaf module-name {
+ type union {
+ type matchall-string-type;
+ type string;
+ }
+ default "*";
+ description
+ "Name of the module associated with this rule.
+
+ This leaf matches if it has the value '*' or if the
+ object being accessed is defined in the module with the
+ specified module name.";
+ }
+ choice rule-type {
+ description
+ "This choice matches if all leafs present in the rule
+ match the request. If no leafs are present, the
+ choice matches all requests.";
+ case protocol-operation {
+ leaf rpc-name {
+ type union {
+ type matchall-string-type;
+ type string;
+ }
+ description
+ "This leaf matches if it has the value '*' or if
+ its value equals the requested protocol operation
+ name.";
+ }
+ }
+ case notification {
+ leaf notification-name {
+ type union {
+ type matchall-string-type;
+ type string;
+ }
+ description
+ "This leaf matches if it has the value '*' or if its
+ value equals the requested notification name.";
+ }
+ }
+ case data-node {
+ leaf path {
+ type node-instance-identifier;
+ mandatory true;
+ description
+ "Data Node Instance Identifier associated with the
+ data node controlled by this rule.
+
+ Configuration data or state data instance
+ identifiers start with a top-level data node. A
+ complete instance identifier is required for this
+ type of path value.
+
+ The special value '/' refers to all possible
+ datastore contents.";
+ }
+ }
+ }
+ leaf access-operations {
+ type union {
+ type matchall-string-type;
+ type access-operations-type;
+ }
+ default "*";
+ description
+ "Access operations associated with this rule.
+
+ This leaf matches if it has the value '*' or if the
+ bit corresponding to the requested operation is set.";
+ }
+ leaf action {
+ type action-type;
+ mandatory true;
+ description
+ "The access control action associated with the
+ rule. If a rule is determined to match a
+ particular request, then this object is used
+ to determine whether to permit or deny the
+ request.";
+ }
+ leaf comment {
+ type string;
+ description
+ "A textual description of the access rule.";
+ }
+ }
+ }
+ }
+}
diff --git a/tools/lint/examples/ietf-netconf-acm-when.yin b/tools/lint/examples/ietf-netconf-acm-when.yin
new file mode 100644
index 0000000..cbff758
--- /dev/null
+++ b/tools/lint/examples/ietf-netconf-acm-when.yin
@@ -0,0 +1,447 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module xmlns="urn:ietf:params:xml:ns:yang:yin:1" xmlns:nacm="urn:ietf:params:xml:ns:yang:ietf-netconf-acm" xmlns:yang="urn:ietf:params:xml:ns:yang:ietf-yang-types" name="ietf-netconf-acm-when">
+ <namespace uri="urn:ietf:params:xml:ns:yang:ietf-netconf-acm"/>
+ <prefix value="nacm"/>
+ <import module="ietf-yang-types">
+ <prefix value="yang"/>
+ </import>
+ <organization>
+ <text>IETF NETCONF (Network Configuration) Working Group</text>
+ </organization>
+ <contact>
+ <text>WG Web: &lt;http://tools.ietf.org/wg/netconf/&gt;
+WG List: &lt;mailto:netconf@ietf.org&gt;
+
+WG Chair: Mehmet Ersue
+ &lt;mailto:mehmet.ersue@nsn.com&gt;
+
+WG Chair: Bert Wijnen
+ &lt;mailto:bertietf@bwijnen.net&gt;
+
+Editor: Andy Bierman
+ &lt;mailto:andy@yumaworks.com&gt;
+
+Editor: Martin Bjorklund
+ &lt;mailto:mbj@tail-f.com&gt;</text>
+ </contact>
+ <description>
+ <text>NETCONF Access Control Model.
+
+Copyright (c) 2012 IETF Trust and the persons identified as
+authors of the code. All rights reserved.
+
+Redistribution and use in source and binary forms, with or
+without modification, is permitted pursuant to, and subject
+to the license terms contained in, the Simplified BSD
+License set forth in Section 4.c of the IETF Trust's
+Legal Provisions Relating to IETF Documents
+(http://trustee.ietf.org/license-info).
+
+This version of this YANG module is part of RFC 6536; see
+the RFC itself for full legal notices.</text>
+ </description>
+ <revision date="2012-02-22">
+ <description>
+ <text>Initial version</text>
+ </description>
+ <reference>
+ <text>RFC 6536: Network Configuration Protocol (NETCONF)
+ Access Control Model</text>
+ </reference>
+ </revision>
+ <extension name="default-deny-write">
+ <description>
+ <text>Used to indicate that the data model node
+represents a sensitive security system parameter.
+
+If present, and the NACM module is enabled (i.e.,
+/nacm/enable-nacm object equals 'true'), the NETCONF server
+will only allow the designated 'recovery session' to have
+write access to the node. An explicit access control rule is
+required for all other users.
+
+The 'default-deny-write' extension MAY appear within a data
+definition statement. It is ignored otherwise.</text>
+ </description>
+ </extension>
+ <extension name="default-deny-all">
+ <description>
+ <text>Used to indicate that the data model node
+controls a very sensitive security system parameter.
+
+If present, and the NACM module is enabled (i.e.,
+/nacm/enable-nacm object equals 'true'), the NETCONF server
+will only allow the designated 'recovery session' to have
+read, write, or execute access to the node. An explicit
+access control rule is required for all other users.
+
+The 'default-deny-all' extension MAY appear within a data
+definition statement, 'rpc' statement, or 'notification'
+statement. It is ignored otherwise.</text>
+ </description>
+ </extension>
+ <typedef name="user-name-type">
+ <type name="string">
+ <length value="1..max"/>
+ </type>
+ <description>
+ <text>General Purpose Username string.</text>
+ </description>
+ </typedef>
+ <typedef name="matchall-string-type">
+ <type name="string">
+ <pattern value="\*"/>
+ </type>
+ <description>
+ <text>The string containing a single asterisk '*' is used
+to conceptually represent all possible values
+for the particular leaf using this data type.</text>
+ </description>
+ </typedef>
+ <typedef name="access-operations-type">
+ <type name="bits">
+ <bit name="create">
+ <description>
+ <text>Any protocol operation that creates a
+new data node.</text>
+ </description>
+ </bit>
+ <bit name="read">
+ <description>
+ <text>Any protocol operation or notification that
+returns the value of a data node.</text>
+ </description>
+ </bit>
+ <bit name="update">
+ <description>
+ <text>Any protocol operation that alters an existing
+data node.</text>
+ </description>
+ </bit>
+ <bit name="delete">
+ <description>
+ <text>Any protocol operation that removes a data node.</text>
+ </description>
+ </bit>
+ <bit name="exec">
+ <description>
+ <text>Execution access to the specified protocol operation.</text>
+ </description>
+ </bit>
+ </type>
+ <description>
+ <text>NETCONF Access Operation.</text>
+ </description>
+ </typedef>
+ <typedef name="group-name-type">
+ <type name="string">
+ <length value="1..max"/>
+ <pattern value="[^\*].*"/>
+ </type>
+ <description>
+ <text>Name of administrative group to which
+users can be assigned.</text>
+ </description>
+ </typedef>
+ <typedef name="action-type">
+ <type name="enumeration">
+ <enum name="permit">
+ <description>
+ <text>Requested action is permitted.</text>
+ </description>
+ </enum>
+ <enum name="deny">
+ <description>
+ <text>Requested action is denied.</text>
+ </description>
+ </enum>
+ </type>
+ <description>
+ <text>Action taken by the server when a particular
+rule matches.</text>
+ </description>
+ </typedef>
+ <typedef name="node-instance-identifier">
+ <type name="yang:xpath1.0"/>
+ <description>
+ <text>Path expression used to represent a special
+data node instance identifier string.
+
+A node-instance-identifier value is an
+unrestricted YANG instance-identifier expression.
+All the same rules as an instance-identifier apply
+except predicates for keys are optional. If a key
+predicate is missing, then the node-instance-identifier
+represents all possible server instances for that key.
+
+This XPath expression is evaluated in the following context:
+
+ o The set of namespace declarations are those in scope on
+ the leaf element where this type is used.
+
+ o The set of variable bindings contains one variable,
+ 'USER', which contains the name of the user of the current
+ session.
+
+ o The function library is the core function library, but
+ note that due to the syntax restrictions of an
+ instance-identifier, no functions are allowed.
+
+ o The context node is the root node in the data tree.</text>
+ </description>
+ </typedef>
+ <container name="nacm">
+ <nacm:default-deny-all/>
+ <description>
+ <text>Parameters for NETCONF Access Control Model.</text>
+ </description>
+ <leaf name="enable-nacm">
+ <type name="boolean"/>
+ <default value="true"/>
+ <description>
+ <text>Enables or disables all NETCONF access control
+enforcement. If 'true', then enforcement
+is enabled. If 'false', then enforcement
+is disabled.</text>
+ </description>
+ </leaf>
+ <leaf name="read-default">
+ <type name="action-type"/>
+ <default value="permit"/>
+ <description>
+ <text>Controls whether read access is granted if
+no appropriate rule is found for a
+particular read request.</text>
+ </description>
+ </leaf>
+ <leaf name="write-default">
+ <type name="action-type"/>
+ <default value="deny"/>
+ <description>
+ <text>Controls whether create, update, or delete access
+is granted if no appropriate rule is found for a
+particular write request.</text>
+ </description>
+ </leaf>
+ <leaf name="exec-default">
+ <type name="action-type"/>
+ <default value="permit"/>
+ <description>
+ <text>Controls whether exec access is granted if no appropriate
+rule is found for a particular protocol operation request.</text>
+ </description>
+ </leaf>
+ <leaf name="enable-external-groups">
+ <type name="boolean"/>
+ <default value="true"/>
+ <description>
+ <text>Controls whether the server uses the groups reported by the
+NETCONF transport layer when it assigns the user to a set of
+NACM groups. If this leaf has the value 'false', any group
+names reported by the transport layer are ignored by the
+server.</text>
+ </description>
+ </leaf>
+ <leaf name="denied-operations">
+ <type name="yang:zero-based-counter32"/>
+ <config value="false"/>
+ <mandatory value="true"/>
+ <description>
+ <text>Number of times since the server last restarted that a
+protocol operation request was denied.</text>
+ </description>
+ </leaf>
+ <leaf name="denied-data-writes">
+ <type name="yang:zero-based-counter32"/>
+ <config value="false"/>
+ <mandatory value="true"/>
+ <when value="../denied-operations > 0"/>
+ <description>
+ <text>Number of times since the server last restarted that a
+protocol operation request to alter
+a configuration datastore was denied.</text>
+ </description>
+ </leaf>
+ <leaf name="denied-notifications">
+ <type name="yang:zero-based-counter32"/>
+ <config value="false"/>
+ <mandatory value="true"/>
+ <description>
+ <text>Number of times since the server last restarted that
+a notification was dropped for a subscription because
+access to the event type was denied.</text>
+ </description>
+ </leaf>
+ <container name="groups">
+ <description>
+ <text>NETCONF Access Control Groups.</text>
+ </description>
+ <list name="group">
+ <key value="name"/>
+ <description>
+ <text>One NACM Group Entry. This list will only contain
+configured entries, not any entries learned from
+any transport protocols.</text>
+ </description>
+ <leaf name="name">
+ <type name="group-name-type"/>
+ <description>
+ <text>Group name associated with this entry.</text>
+ </description>
+ </leaf>
+ <leaf-list name="user-name">
+ <type name="user-name-type"/>
+ <description>
+ <text>Each entry identifies the username of
+a member of the group associated with
+this entry.</text>
+ </description>
+ </leaf-list>
+ </list>
+ </container>
+ <list name="rule-list">
+ <key value="name"/>
+ <ordered-by value="user"/>
+ <description>
+ <text>An ordered collection of access control rules.</text>
+ </description>
+ <leaf name="name">
+ <type name="string">
+ <length value="1..max"/>
+ </type>
+ <description>
+ <text>Arbitrary name assigned to the rule-list.</text>
+ </description>
+ </leaf>
+ <leaf-list name="group">
+ <type name="union">
+ <type name="matchall-string-type"/>
+ <type name="group-name-type"/>
+ </type>
+ <description>
+ <text>List of administrative groups that will be
+assigned the associated access rights
+defined by the 'rule' list.
+
+The string '*' indicates that all groups apply to the
+entry.</text>
+ </description>
+ </leaf-list>
+ <list name="rule">
+ <key value="name"/>
+ <ordered-by value="user"/>
+ <description>
+ <text>One access control rule.
+
+Rules are processed in user-defined order until a match is
+found. A rule matches if 'module-name', 'rule-type', and
+'access-operations' match the request. If a rule
+matches, the 'action' leaf determines if access is granted
+or not.</text>
+ </description>
+ <leaf name="name">
+ <type name="string">
+ <length value="1..max"/>
+ </type>
+ <description>
+ <text>Arbitrary name assigned to the rule.</text>
+ </description>
+ </leaf>
+ <leaf name="module-name">
+ <type name="union">
+ <type name="matchall-string-type"/>
+ <type name="string"/>
+ </type>
+ <default value="*"/>
+ <description>
+ <text>Name of the module associated with this rule.
+
+This leaf matches if it has the value '*' or if the
+object being accessed is defined in the module with the
+specified module name.</text>
+ </description>
+ </leaf>
+ <choice name="rule-type">
+ <description>
+ <text>This choice matches if all leafs present in the rule
+match the request. If no leafs are present, the
+choice matches all requests.</text>
+ </description>
+ <case name="protocol-operation">
+ <leaf name="rpc-name">
+ <type name="union">
+ <type name="matchall-string-type"/>
+ <type name="string"/>
+ </type>
+ <description>
+ <text>This leaf matches if it has the value '*' or if
+its value equals the requested protocol operation
+name.</text>
+ </description>
+ </leaf>
+ </case>
+ <case name="notification">
+ <leaf name="notification-name">
+ <type name="union">
+ <type name="matchall-string-type"/>
+ <type name="string"/>
+ </type>
+ <description>
+ <text>This leaf matches if it has the value '*' or if its
+value equals the requested notification name.</text>
+ </description>
+ </leaf>
+ </case>
+ <case name="data-node">
+ <leaf name="path">
+ <type name="node-instance-identifier"/>
+ <mandatory value="true"/>
+ <description>
+ <text>Data Node Instance Identifier associated with the
+data node controlled by this rule.
+
+Configuration data or state data instance
+identifiers start with a top-level data node. A
+complete instance identifier is required for this
+type of path value.
+
+The special value '/' refers to all possible
+datastore contents.</text>
+ </description>
+ </leaf>
+ </case>
+ </choice>
+ <leaf name="access-operations">
+ <type name="union">
+ <type name="matchall-string-type"/>
+ <type name="access-operations-type"/>
+ </type>
+ <default value="*"/>
+ <description>
+ <text>Access operations associated with this rule.
+
+This leaf matches if it has the value '*' or if the
+bit corresponding to the requested operation is set.</text>
+ </description>
+ </leaf>
+ <leaf name="action">
+ <type name="action-type"/>
+ <mandatory value="true"/>
+ <description>
+ <text>The access control action associated with the
+rule. If a rule is determined to match a
+particular request, then this object is used
+to determine whether to permit or deny the
+request.</text>
+ </description>
+ </leaf>
+ <leaf name="comment">
+ <type name="string"/>
+ <description>
+ <text>A textual description of the access rule.</text>
+ </description>
+ </leaf>
+ </list>
+ </list>
+ </container>
+</module>
diff --git a/tools/lint/examples/ietf-netconf-acm-when2.yin b/tools/lint/examples/ietf-netconf-acm-when2.yin
new file mode 100644
index 0000000..f8f25a0
--- /dev/null
+++ b/tools/lint/examples/ietf-netconf-acm-when2.yin
@@ -0,0 +1,447 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module xmlns="urn:ietf:params:xml:ns:yang:yin:1" xmlns:nacm="urn:ietf:params:xml:ns:yang:ietf-netconf-acm" xmlns:yang="urn:ietf:params:xml:ns:yang:ietf-yang-types" name="ietf-netconf-acm-when2">
+ <namespace uri="urn:ietf:params:xml:ns:yang:ietf-netconf-acm"/>
+ <prefix value="nacm"/>
+ <import module="ietf-yang-types">
+ <prefix value="yang"/>
+ </import>
+ <organization>
+ <text>IETF NETCONF (Network Configuration) Working Group</text>
+ </organization>
+ <contact>
+ <text>WG Web: &lt;http://tools.ietf.org/wg/netconf/&gt;
+WG List: &lt;mailto:netconf@ietf.org&gt;
+
+WG Chair: Mehmet Ersue
+ &lt;mailto:mehmet.ersue@nsn.com&gt;
+
+WG Chair: Bert Wijnen
+ &lt;mailto:bertietf@bwijnen.net&gt;
+
+Editor: Andy Bierman
+ &lt;mailto:andy@yumaworks.com&gt;
+
+Editor: Martin Bjorklund
+ &lt;mailto:mbj@tail-f.com&gt;</text>
+ </contact>
+ <description>
+ <text>NETCONF Access Control Model.
+
+Copyright (c) 2012 IETF Trust and the persons identified as
+authors of the code. All rights reserved.
+
+Redistribution and use in source and binary forms, with or
+without modification, is permitted pursuant to, and subject
+to the license terms contained in, the Simplified BSD
+License set forth in Section 4.c of the IETF Trust's
+Legal Provisions Relating to IETF Documents
+(http://trustee.ietf.org/license-info).
+
+This version of this YANG module is part of RFC 6536; see
+the RFC itself for full legal notices.</text>
+ </description>
+ <revision date="2012-02-22">
+ <description>
+ <text>Initial version</text>
+ </description>
+ <reference>
+ <text>RFC 6536: Network Configuration Protocol (NETCONF)
+ Access Control Model</text>
+ </reference>
+ </revision>
+ <extension name="default-deny-write">
+ <description>
+ <text>Used to indicate that the data model node
+represents a sensitive security system parameter.
+
+If present, and the NACM module is enabled (i.e.,
+/nacm/enable-nacm object equals 'true'), the NETCONF server
+will only allow the designated 'recovery session' to have
+write access to the node. An explicit access control rule is
+required for all other users.
+
+The 'default-deny-write' extension MAY appear within a data
+definition statement. It is ignored otherwise.</text>
+ </description>
+ </extension>
+ <extension name="default-deny-all">
+ <description>
+ <text>Used to indicate that the data model node
+controls a very sensitive security system parameter.
+
+If present, and the NACM module is enabled (i.e.,
+/nacm/enable-nacm object equals 'true'), the NETCONF server
+will only allow the designated 'recovery session' to have
+read, write, or execute access to the node. An explicit
+access control rule is required for all other users.
+
+The 'default-deny-all' extension MAY appear within a data
+definition statement, 'rpc' statement, or 'notification'
+statement. It is ignored otherwise.</text>
+ </description>
+ </extension>
+ <typedef name="user-name-type">
+ <type name="string">
+ <length value="1..max"/>
+ </type>
+ <description>
+ <text>General Purpose Username string.</text>
+ </description>
+ </typedef>
+ <typedef name="matchall-string-type">
+ <type name="string">
+ <pattern value="\*"/>
+ </type>
+ <description>
+ <text>The string containing a single asterisk '*' is used
+to conceptually represent all possible values
+for the particular leaf using this data type.</text>
+ </description>
+ </typedef>
+ <typedef name="access-operations-type">
+ <type name="bits">
+ <bit name="create">
+ <description>
+ <text>Any protocol operation that creates a
+new data node.</text>
+ </description>
+ </bit>
+ <bit name="read">
+ <description>
+ <text>Any protocol operation or notification that
+returns the value of a data node.</text>
+ </description>
+ </bit>
+ <bit name="update">
+ <description>
+ <text>Any protocol operation that alters an existing
+data node.</text>
+ </description>
+ </bit>
+ <bit name="delete">
+ <description>
+ <text>Any protocol operation that removes a data node.</text>
+ </description>
+ </bit>
+ <bit name="exec">
+ <description>
+ <text>Execution access to the specified protocol operation.</text>
+ </description>
+ </bit>
+ </type>
+ <description>
+ <text>NETCONF Access Operation.</text>
+ </description>
+ </typedef>
+ <typedef name="group-name-type">
+ <type name="string">
+ <length value="1..max"/>
+ <pattern value="[^\*].*"/>
+ </type>
+ <description>
+ <text>Name of administrative group to which
+users can be assigned.</text>
+ </description>
+ </typedef>
+ <typedef name="action-type">
+ <type name="enumeration">
+ <enum name="permit">
+ <description>
+ <text>Requested action is permitted.</text>
+ </description>
+ </enum>
+ <enum name="deny">
+ <description>
+ <text>Requested action is denied.</text>
+ </description>
+ </enum>
+ </type>
+ <description>
+ <text>Action taken by the server when a particular
+rule matches.</text>
+ </description>
+ </typedef>
+ <typedef name="node-instance-identifier">
+ <type name="yang:xpath1.0"/>
+ <description>
+ <text>Path expression used to represent a special
+data node instance identifier string.
+
+A node-instance-identifier value is an
+unrestricted YANG instance-identifier expression.
+All the same rules as an instance-identifier apply
+except predicates for keys are optional. If a key
+predicate is missing, then the node-instance-identifier
+represents all possible server instances for that key.
+
+This XPath expression is evaluated in the following context:
+
+ o The set of namespace declarations are those in scope on
+ the leaf element where this type is used.
+
+ o The set of variable bindings contains one variable,
+ 'USER', which contains the name of the user of the current
+ session.
+
+ o The function library is the core function library, but
+ note that due to the syntax restrictions of an
+ instance-identifier, no functions are allowed.
+
+ o The context node is the root node in the data tree.</text>
+ </description>
+ </typedef>
+ <container name="nacm">
+ <nacm:default-deny-all/>
+ <description>
+ <text>Parameters for NETCONF Access Control Model.</text>
+ </description>
+ <leaf name="enable-nacm">
+ <type name="boolean"/>
+ <default value="true"/>
+ <description>
+ <text>Enables or disables all NETCONF access control
+enforcement. If 'true', then enforcement
+is enabled. If 'false', then enforcement
+is disabled.</text>
+ </description>
+ </leaf>
+ <leaf name="read-default">
+ <type name="action-type"/>
+ <default value="permit"/>
+ <description>
+ <text>Controls whether read access is granted if
+no appropriate rule is found for a
+particular read request.</text>
+ </description>
+ </leaf>
+ <leaf name="write-default">
+ <type name="action-type"/>
+ <default value="deny"/>
+ <description>
+ <text>Controls whether create, update, or delete access
+is granted if no appropriate rule is found for a
+particular write request.</text>
+ </description>
+ </leaf>
+ <leaf name="exec-default">
+ <type name="action-type"/>
+ <default value="permit"/>
+ <description>
+ <text>Controls whether exec access is granted if no appropriate
+rule is found for a particular protocol operation request.</text>
+ </description>
+ </leaf>
+ <leaf name="enable-external-groups">
+ <type name="boolean"/>
+ <default value="true"/>
+ <description>
+ <text>Controls whether the server uses the groups reported by the
+NETCONF transport layer when it assigns the user to a set of
+NACM groups. If this leaf has the value 'false', any group
+names reported by the transport layer are ignored by the
+server.</text>
+ </description>
+ </leaf>
+ <leaf name="denied-operations">
+ <type name="yang:zero-based-counter32"/>
+ <config value="false"/>
+ <mandatory value="true"/>
+ <description>
+ <text>Number of times since the server last restarted that a
+protocol operation request was denied.</text>
+ </description>
+ </leaf>
+ <leaf name="denied-data-writes">
+ <type name="yang:zero-based-counter32"/>
+ <config value="false"/>
+ <mandatory value="true"/>
+ <when condition="../denied-operations > 0"/>
+ <description>
+ <text>Number of times since the server last restarted that a
+protocol operation request to alter
+a configuration datastore was denied.</text>
+ </description>
+ </leaf>
+ <leaf name="denied-notifications">
+ <type name="yang:zero-based-counter32"/>
+ <config value="false"/>
+ <mandatory value="true"/>
+ <description>
+ <text>Number of times since the server last restarted that
+a notification was dropped for a subscription because
+access to the event type was denied.</text>
+ </description>
+ </leaf>
+ <container name="groups">
+ <description>
+ <text>NETCONF Access Control Groups.</text>
+ </description>
+ <list name="group">
+ <key value="name"/>
+ <description>
+ <text>One NACM Group Entry. This list will only contain
+configured entries, not any entries learned from
+any transport protocols.</text>
+ </description>
+ <leaf name="name">
+ <type name="group-name-type"/>
+ <description>
+ <text>Group name associated with this entry.</text>
+ </description>
+ </leaf>
+ <leaf-list name="user-name">
+ <type name="user-name-type"/>
+ <description>
+ <text>Each entry identifies the username of
+a member of the group associated with
+this entry.</text>
+ </description>
+ </leaf-list>
+ </list>
+ </container>
+ <list name="rule-list">
+ <key value="name"/>
+ <ordered-by value="user"/>
+ <description>
+ <text>An ordered collection of access control rules.</text>
+ </description>
+ <leaf name="name">
+ <type name="string">
+ <length value="1..max"/>
+ </type>
+ <description>
+ <text>Arbitrary name assigned to the rule-list.</text>
+ </description>
+ </leaf>
+ <leaf-list name="group">
+ <type name="union">
+ <type name="matchall-string-type"/>
+ <type name="group-name-type"/>
+ </type>
+ <description>
+ <text>List of administrative groups that will be
+assigned the associated access rights
+defined by the 'rule' list.
+
+The string '*' indicates that all groups apply to the
+entry.</text>
+ </description>
+ </leaf-list>
+ <list name="rule">
+ <key value="name"/>
+ <ordered-by value="user"/>
+ <description>
+ <text>One access control rule.
+
+Rules are processed in user-defined order until a match is
+found. A rule matches if 'module-name', 'rule-type', and
+'access-operations' match the request. If a rule
+matches, the 'action' leaf determines if access is granted
+or not.</text>
+ </description>
+ <leaf name="name">
+ <type name="string">
+ <length value="1..max"/>
+ </type>
+ <description>
+ <text>Arbitrary name assigned to the rule.</text>
+ </description>
+ </leaf>
+ <leaf name="module-name">
+ <type name="union">
+ <type name="matchall-string-type"/>
+ <type name="string"/>
+ </type>
+ <default value="*"/>
+ <description>
+ <text>Name of the module associated with this rule.
+
+This leaf matches if it has the value '*' or if the
+object being accessed is defined in the module with the
+specified module name.</text>
+ </description>
+ </leaf>
+ <choice name="rule-type">
+ <description>
+ <text>This choice matches if all leafs present in the rule
+match the request. If no leafs are present, the
+choice matches all requests.</text>
+ </description>
+ <case name="protocol-operation">
+ <leaf name="rpc-name">
+ <type name="union">
+ <type name="matchall-string-type"/>
+ <type name="string"/>
+ </type>
+ <description>
+ <text>This leaf matches if it has the value '*' or if
+its value equals the requested protocol operation
+name.</text>
+ </description>
+ </leaf>
+ </case>
+ <case name="notification">
+ <leaf name="notification-name">
+ <type name="union">
+ <type name="matchall-string-type"/>
+ <type name="string"/>
+ </type>
+ <description>
+ <text>This leaf matches if it has the value '*' or if its
+value equals the requested notification name.</text>
+ </description>
+ </leaf>
+ </case>
+ <case name="data-node">
+ <leaf name="path">
+ <type name="node-instance-identifier"/>
+ <mandatory value="true"/>
+ <description>
+ <text>Data Node Instance Identifier associated with the
+data node controlled by this rule.
+
+Configuration data or state data instance
+identifiers start with a top-level data node. A
+complete instance identifier is required for this
+type of path value.
+
+The special value '/' refers to all possible
+datastore contents.</text>
+ </description>
+ </leaf>
+ </case>
+ </choice>
+ <leaf name="access-operations">
+ <type name="union">
+ <type name="matchall-string-type"/>
+ <type name="access-operations-type"/>
+ </type>
+ <default value="*"/>
+ <description>
+ <text>Access operations associated with this rule.
+
+This leaf matches if it has the value '*' or if the
+bit corresponding to the requested operation is set.</text>
+ </description>
+ </leaf>
+ <leaf name="action">
+ <type name="action-type"/>
+ <mandatory value="true"/>
+ <description>
+ <text>The access control action associated with the
+rule. If a rule is determined to match a
+particular request, then this object is used
+to determine whether to permit or deny the
+request.</text>
+ </description>
+ </leaf>
+ <leaf name="comment">
+ <type name="string"/>
+ <description>
+ <text>A textual description of the access rule.</text>
+ </description>
+ </leaf>
+ </list>
+ </list>
+ </container>
+</module>
diff --git a/tools/lint/examples/ietf-netconf-acm.yang b/tools/lint/examples/ietf-netconf-acm.yang
new file mode 100644
index 0000000..dc3655e
--- /dev/null
+++ b/tools/lint/examples/ietf-netconf-acm.yang
@@ -0,0 +1,411 @@
+module ietf-netconf-acm {
+ namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-acm";
+ prefix nacm;
+
+ import ietf-yang-types {
+ prefix yang;
+ }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+ contact
+ "WG Web: <http://tools.ietf.org/wg/netconf/>
+ WG List: <mailto:netconf@ietf.org>
+
+ WG Chair: Mehmet Ersue
+ <mailto:mehmet.ersue@nsn.com>
+
+ WG Chair: Bert Wijnen
+ <mailto:bertietf@bwijnen.net>
+
+ Editor: Andy Bierman
+ <mailto:andy@yumaworks.com>
+
+ Editor: Martin Bjorklund
+ <mailto:mbj@tail-f.com>";
+ description
+ "NETCONF Access Control Model.
+
+ Copyright (c) 2012 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD
+ License set forth in Section 4.c of the IETF Trust's
+ Legal Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 6536; see
+ the RFC itself for full legal notices.";
+
+ revision 2012-02-22 {
+ description
+ "Initial version";
+ reference
+ "RFC 6536: Network Configuration Protocol (NETCONF)
+ Access Control Model";
+ }
+
+ extension default-deny-write {
+ description
+ "Used to indicate that the data model node
+ represents a sensitive security system parameter.
+
+ If present, and the NACM module is enabled (i.e.,
+ /nacm/enable-nacm object equals 'true'), the NETCONF server
+ will only allow the designated 'recovery session' to have
+ write access to the node. An explicit access control rule is
+ required for all other users.
+
+ The 'default-deny-write' extension MAY appear within a data
+ definition statement. It is ignored otherwise.";
+ }
+
+ extension default-deny-all {
+ description
+ "Used to indicate that the data model node
+ controls a very sensitive security system parameter.
+
+ If present, and the NACM module is enabled (i.e.,
+ /nacm/enable-nacm object equals 'true'), the NETCONF server
+ will only allow the designated 'recovery session' to have
+ read, write, or execute access to the node. An explicit
+ access control rule is required for all other users.
+
+ The 'default-deny-all' extension MAY appear within a data
+ definition statement, 'rpc' statement, or 'notification'
+ statement. It is ignored otherwise.";
+ }
+
+ typedef user-name-type {
+ type string {
+ length "1..max";
+ }
+ description
+ "General Purpose Username string.";
+ }
+
+ typedef matchall-string-type {
+ type string {
+ pattern "\\*";
+ }
+ description
+ "The string containing a single asterisk '*' is used
+ to conceptually represent all possible values
+ for the particular leaf using this data type.";
+ }
+
+ typedef access-operations-type {
+ type bits {
+ bit create {
+ description
+ "Any protocol operation that creates a
+ new data node.";
+ }
+ bit read {
+ description
+ "Any protocol operation or notification that
+ returns the value of a data node.";
+ }
+ bit update {
+ description
+ "Any protocol operation that alters an existing
+ data node.";
+ }
+ bit delete {
+ description
+ "Any protocol operation that removes a data node.";
+ }
+ bit exec {
+ description
+ "Execution access to the specified protocol operation.";
+ }
+ }
+ description
+ "NETCONF Access Operation.";
+ }
+
+ typedef group-name-type {
+ type string {
+ length "1..max";
+ pattern "[^\\*].*";
+ }
+ description
+ "Name of administrative group to which
+ users can be assigned.";
+ }
+
+ typedef action-type {
+ type enumeration {
+ enum "permit" {
+ description
+ "Requested action is permitted.";
+ }
+ enum "deny" {
+ description
+ "Requested action is denied.";
+ }
+ }
+ description
+ "Action taken by the server when a particular
+ rule matches.";
+ }
+
+ typedef node-instance-identifier {
+ type yang:xpath1.0;
+ description
+ "Path expression used to represent a special
+ data node instance identifier string.
+
+ A node-instance-identifier value is an
+ unrestricted YANG instance-identifier expression.
+ All the same rules as an instance-identifier apply
+ except predicates for keys are optional. If a key
+ predicate is missing, then the node-instance-identifier
+ represents all possible server instances for that key.
+
+ This XPath expression is evaluated in the following context:
+
+ o The set of namespace declarations are those in scope on
+ the leaf element where this type is used.
+
+ o The set of variable bindings contains one variable,
+ 'USER', which contains the name of the user of the current
+ session.
+
+ o The function library is the core function library, but
+ note that due to the syntax restrictions of an
+ instance-identifier, no functions are allowed.
+
+ o The context node is the root node in the data tree.";
+ }
+
+ container nacm {
+ nacm:default-deny-all;
+ description
+ "Parameters for NETCONF Access Control Model.";
+ leaf enable-nacm {
+ type boolean;
+ default "true";
+ description
+ "Enables or disables all NETCONF access control
+ enforcement. If 'true', then enforcement
+ is enabled. If 'false', then enforcement
+ is disabled.";
+ }
+ leaf read-default {
+ type action-type;
+ default "permit";
+ description
+ "Controls whether read access is granted if
+ no appropriate rule is found for a
+ particular read request.";
+ }
+ leaf write-default {
+ type action-type;
+ default "deny";
+ description
+ "Controls whether create, update, or delete access
+ is granted if no appropriate rule is found for a
+ particular write request.";
+ }
+ leaf exec-default {
+ type action-type;
+ default "permit";
+ description
+ "Controls whether exec access is granted if no appropriate
+ rule is found for a particular protocol operation request.";
+ }
+ leaf enable-external-groups {
+ type boolean;
+ default "true";
+ description
+ "Controls whether the server uses the groups reported by the
+ NETCONF transport layer when it assigns the user to a set of
+ NACM groups. If this leaf has the value 'false', any group
+ names reported by the transport layer are ignored by the
+ server.";
+ }
+ leaf denied-operations {
+ type yang:zero-based-counter32;
+ config false;
+ mandatory true;
+ description
+ "Number of times since the server last restarted that a
+ protocol operation request was denied.";
+ }
+ leaf denied-data-writes {
+ type yang:zero-based-counter32;
+ config false;
+ mandatory true;
+ description
+ "Number of times since the server last restarted that a
+ protocol operation request to alter
+ a configuration datastore was denied.";
+ }
+ leaf denied-notifications {
+ type yang:zero-based-counter32;
+ config false;
+ mandatory true;
+ description
+ "Number of times since the server last restarted that
+ a notification was dropped for a subscription because
+ access to the event type was denied.";
+ }
+ container groups {
+ description
+ "NETCONF Access Control Groups.";
+ list group {
+ key "name";
+ description
+ "One NACM Group Entry. This list will only contain
+ configured entries, not any entries learned from
+ any transport protocols.";
+ leaf name {
+ type group-name-type;
+ description
+ "Group name associated with this entry.";
+ }
+ leaf-list user-name {
+ type user-name-type;
+ description
+ "Each entry identifies the username of
+ a member of the group associated with
+ this entry.";
+ }
+ }
+ }
+ list rule-list {
+ key "name";
+ ordered-by user;
+ description
+ "An ordered collection of access control rules.";
+ leaf name {
+ type string {
+ length "1..max";
+ }
+ description
+ "Arbitrary name assigned to the rule-list.";
+ }
+ leaf-list group {
+ type union {
+ type matchall-string-type;
+ type group-name-type;
+ }
+ description
+ "List of administrative groups that will be
+ assigned the associated access rights
+ defined by the 'rule' list.
+
+ The string '*' indicates that all groups apply to the
+ entry.";
+ }
+ list rule {
+ key "name";
+ ordered-by user;
+ description
+ "One access control rule.
+
+ Rules are processed in user-defined order until a match is
+ found. A rule matches if 'module-name', 'rule-type', and
+ 'access-operations' match the request. If a rule
+ matches, the 'action' leaf determines if access is granted
+ or not.";
+ leaf name {
+ type string {
+ length "1..max";
+ }
+ description
+ "Arbitrary name assigned to the rule.";
+ }
+ leaf module-name {
+ type union {
+ type matchall-string-type;
+ type string;
+ }
+ default "*";
+ description
+ "Name of the module associated with this rule.
+
+ This leaf matches if it has the value '*' or if the
+ object being accessed is defined in the module with the
+ specified module name.";
+ }
+ choice rule-type {
+ description
+ "This choice matches if all leafs present in the rule
+ match the request. If no leafs are present, the
+ choice matches all requests.";
+ case protocol-operation {
+ leaf rpc-name {
+ type union {
+ type matchall-string-type;
+ type string;
+ }
+ description
+ "This leaf matches if it has the value '*' or if
+ its value equals the requested protocol operation
+ name.";
+ }
+ }
+ case notification {
+ leaf notification-name {
+ type union {
+ type matchall-string-type;
+ type string;
+ }
+ description
+ "This leaf matches if it has the value '*' or if its
+ value equals the requested notification name.";
+ }
+ }
+ case data-node {
+ leaf path {
+ type node-instance-identifier;
+ mandatory true;
+ description
+ "Data Node Instance Identifier associated with the
+ data node controlled by this rule.
+
+ Configuration data or state data instance
+ identifiers start with a top-level data node. A
+ complete instance identifier is required for this
+ type of path value.
+
+ The special value '/' refers to all possible
+ datastore contents.";
+ }
+ }
+ }
+ leaf access-operations {
+ type union {
+ type matchall-string-type;
+ type access-operations-type;
+ }
+ default "*";
+ description
+ "Access operations associated with this rule.
+
+ This leaf matches if it has the value '*' or if the
+ bit corresponding to the requested operation is set.";
+ }
+ leaf action {
+ type action-type;
+ mandatory true;
+ description
+ "The access control action associated with the
+ rule. If a rule is determined to match a
+ particular request, then this object is used
+ to determine whether to permit or deny the
+ request.";
+ }
+ leaf comment {
+ type string;
+ description
+ "A textual description of the access rule.";
+ }
+ }
+ }
+ }
+}
diff --git a/tools/lint/examples/module1.yang b/tools/lint/examples/module1.yang
new file mode 100644
index 0000000..1df7bf1
--- /dev/null
+++ b/tools/lint/examples/module1.yang
@@ -0,0 +1,5 @@
+module module1 {
+ namespace "urn:yanglint:module";
+ prefix m;
+ leaf m { type string; }
+}
diff --git a/tools/lint/examples/module1b.yang b/tools/lint/examples/module1b.yang
new file mode 100644
index 0000000..463c936
--- /dev/null
+++ b/tools/lint/examples/module1b.yang
@@ -0,0 +1,5 @@
+module module1b {
+ namespace "urn:yanglint:module";
+ prefix m;
+ leaf mb { type string; }
+}
diff --git a/tools/lint/examples/module2.yang b/tools/lint/examples/module2.yang
new file mode 100644
index 0000000..c87c764
--- /dev/null
+++ b/tools/lint/examples/module2.yang
@@ -0,0 +1,5 @@
+module module2 {
+ namespace "urn:yanglint:module";
+ prefix m;
+ leaf m { ttype string; }
+}
diff --git a/tools/lint/examples/module2.yin b/tools/lint/examples/module2.yin
new file mode 100644
index 0000000..af6cb50
--- /dev/null
+++ b/tools/lint/examples/module2.yin
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module name="module2"
+ xmlns="urn:ietf:params:xml:ns:yang:yin:1"
+ xmlns:m="urn:yanglint:module">
+ <namespace uri="urn:yanglint:module"/>
+ <prefix value="m"/>
+ <leaf name="m">
+ <type value="string"/>
+ </leaf>
+</module>
diff --git a/tools/lint/examples/module3.yang b/tools/lint/examples/module3.yang
new file mode 100644
index 0000000..63754b1
--- /dev/null
+++ b/tools/lint/examples/module3.yang
@@ -0,0 +1,8 @@
+module module3 {
+ namespace "urn:yanglint:module";
+ prefix m;
+ leaf m { type string; must "../c/a"; }
+ container c {
+ leaf b { type string; }
+ }
+}
diff --git a/tools/lint/examples/module4.yang b/tools/lint/examples/module4.yang
new file mode 100644
index 0000000..23ea289
--- /dev/null
+++ b/tools/lint/examples/module4.yang
@@ -0,0 +1,52 @@
+module module4 {
+ yang-version 1.1;
+ namespace "urn:module4";
+ prefix m4;
+
+ container cont1 {
+ list list {
+ key "leaf1";
+ leaf leaf1 {
+ type string;
+ }
+ action act {
+ input {
+ leaf leaf2 {
+ type string;
+ }
+ }
+ output {
+ leaf leaf3 {
+ type string;
+ }
+ }
+ }
+ notification notif1 {
+ leaf leaf4 {
+ type string;
+ }
+ }
+ }
+ }
+
+ rpc rpc {
+ input {
+ leaf leaf5 {
+ type string;
+ }
+ }
+ output {
+ container cont2 {
+ leaf leaf6 {
+ type empty;
+ }
+ }
+ }
+ }
+
+ notification notif2 {
+ leaf leaf7 {
+ type empty;
+ }
+ }
+}
diff --git a/tools/lint/examples/nested-notification.xml b/tools/lint/examples/nested-notification.xml
new file mode 100644
index 0000000..024b65a
--- /dev/null
+++ b/tools/lint/examples/nested-notification.xml
@@ -0,0 +1,8 @@
+<cont1 xmlns="urn:module4">
+ <list>
+ <leaf1>key_val</leaf1>
+ <notif1>
+ <leaf4>some_value</leaf4>
+ </notif1>
+ </list>
+</cont1>
diff --git a/tools/lint/examples/notification.xml b/tools/lint/examples/notification.xml
new file mode 100644
index 0000000..803ddad
--- /dev/null
+++ b/tools/lint/examples/notification.xml
@@ -0,0 +1,3 @@
+<notif2 xmlns="urn:module4">
+ <leaf7/>
+</notif2>
diff --git a/tools/lint/examples/rpc-reply.xml b/tools/lint/examples/rpc-reply.xml
new file mode 100644
index 0000000..54aab3e
--- /dev/null
+++ b/tools/lint/examples/rpc-reply.xml
@@ -0,0 +1,5 @@
+<rpc xmlns="urn:module4">
+ <cont2>
+ <leaf6/>
+ </cont2>
+</rpc>
diff --git a/tools/lint/examples/rpc.xml b/tools/lint/examples/rpc.xml
new file mode 100644
index 0000000..ea8ca90
--- /dev/null
+++ b/tools/lint/examples/rpc.xml
@@ -0,0 +1,3 @@
+<rpc xmlns="urn:module4">
+ <leaf5>some_input</leaf5>
+</rpc>
diff --git a/tools/lint/examples/sm-context-extension.xml b/tools/lint/examples/sm-context-extension.xml
new file mode 100644
index 0000000..747c60f
--- /dev/null
+++ b/tools/lint/examples/sm-context-extension.xml
@@ -0,0 +1,64 @@
+<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"
+ xmlns:ds="urn:ietf:params:xml:ns:yang:ietf-datastores">
+ <module-set>
+ <name>test-set</name>
+ <module>
+ <name>ietf-datastores</name>
+ <revision>2018-02-14</revision>
+ <namespace>urn:ietf:params:xml:ns:yang:ietf-datastores</namespace>
+ </module>
+ <module>
+ <name>ietf-yang-library</name>
+ <revision>2019-01-04</revision>
+ <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-library</namespace>
+ </module>
+ <module>
+ <name>sm-extension</name>
+ <namespace>urn:sm-ext</namespace>
+ </module>
+ <module>
+ <name>iana-if-type</name>
+ <namespace>urn:ietf:params:xml:ns:yang:iana-if-type</namespace>
+ </module>
+ <import-only-module>
+ <name>ietf-yang-types</name>
+ <revision>2013-07-15</revision>
+ <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-types</namespace>
+ </import-only-module>
+ <import-only-module>
+ <name>sm-mod</name>
+ <revision>2017-01-26</revision>
+ <namespace>urn:yanglint:sm-mod</namespace>
+ </import-only-module>
+ </module-set>
+ <schema>
+ <name>test-schema</name>
+ <module-set>test-set</module-set>
+ </schema>
+ <datastore>
+ <name>ds:running</name>
+ <schema>test-schema</schema>
+ </datastore>
+ <datastore>
+ <name>ds:operational</name>
+ <schema>test-schema</schema>
+ </datastore>
+ <content-id>1</content-id>
+ </yang-library>
+ <modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+ <module-set-id>1</module-set-id>
+ </modules-state>
+ <schema-mounts xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount">
+ <namespace>
+ <prefix>if</prefix>
+ <uri>urn:ietf:params:xml:ns:yang:ietf-interfaces</uri>
+ </namespace>
+ <mount-point>
+ <module>sm-main</module>
+ <label>mnt-root</label>
+ <shared-schema>
+ <parent-reference>/if:interfaces/if:interface/if:name</parent-reference>
+ <parent-reference>/if:interfaces/if:interface/if:type</parent-reference>
+ </shared-schema>
+ </mount-point>
+ </schema-mounts>
diff --git a/tools/lint/examples/sm-context-main.xml b/tools/lint/examples/sm-context-main.xml
new file mode 100644
index 0000000..43558c3
--- /dev/null
+++ b/tools/lint/examples/sm-context-main.xml
@@ -0,0 +1,54 @@
+<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"
+ xmlns:ds="urn:ietf:params:xml:ns:yang:ietf-datastores">
+ <module-set>
+ <name>main-set</name>
+ <module>
+ <name>ietf-datastores</name>
+ <revision>2018-02-14</revision>
+ <namespace>urn:ietf:params:xml:ns:yang:ietf-datastores</namespace>
+ </module>
+ <module>
+ <name>ietf-yang-library</name>
+ <revision>2019-01-04</revision>
+ <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-library</namespace>
+ </module>
+ <module>
+ <name>ietf-yang-schema-mount</name>
+ <revision>2019-01-14</revision>
+ <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount</namespace>
+ </module>
+ <module>
+ <name>sm-main</name>
+ <namespace>urn:sm-main</namespace>
+ </module>
+ <module>
+ <name>iana-if-type</name>
+ <namespace>urn:ietf:params:xml:ns:yang:iana-if-type</namespace>
+ </module>
+ <module>
+ <name>ietf-interfaces</name>
+ <namespace>urn:ietf:params:xml:ns:yang:ietf-interfaces</namespace>
+ </module>
+ <import-only-module>
+ <name>ietf-yang-types</name>
+ <revision>2013-07-15</revision>
+ <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-types</namespace>
+ </import-only-module>
+ </module-set>
+ <schema>
+ <name>main-schema</name>
+ <module-set>main-set</module-set>
+ </schema>
+ <datastore>
+ <name>ds:running</name>
+ <schema>main-schema</schema>
+ </datastore>
+ <datastore>
+ <name>ds:operational</name>
+ <schema>main-schema</schema>
+ </datastore>
+ <content-id>1</content-id>
+ </yang-library>
+ <modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+ <module-set-id>2</module-set-id>
+ </modules-state>
diff --git a/tools/lint/examples/sm-data.xml b/tools/lint/examples/sm-data.xml
new file mode 100644
index 0000000..478d324
--- /dev/null
+++ b/tools/lint/examples/sm-data.xml
@@ -0,0 +1,19 @@
+<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
+ <interface>
+ <name>eth0</name>
+ <type xmlns:ift="urn:ietf:params:xml:ns:yang:iana-if-type">ift:ethernetCsmacd</type>
+ </interface>
+ <interface>
+ <name>eth1</name>
+ <type xmlns:ift="urn:ietf:params:xml:ns:yang:iana-if-type">ift:ethernetCsmacd</type>
+ </interface>
+</interfaces>
+<root3 xmlns="urn:sm-main">
+ <my-list>
+ <name>list item 1</name>
+ <things xmlns="urn:sm-ext">
+ <name>eth0</name>
+ <attribute>1</attribute>
+ </things>
+ </my-list>
+</root3>
diff --git a/tools/lint/examples/sm-extension.yang b/tools/lint/examples/sm-extension.yang
new file mode 100644
index 0000000..2214cf6
--- /dev/null
+++ b/tools/lint/examples/sm-extension.yang
@@ -0,0 +1,39 @@
+module sm-extension {
+ yang-version 1.1;
+ namespace "urn:sm-ext";
+ prefix "sm-ext";
+
+ import ietf-interfaces {
+ prefix if;
+ }
+ import sm-mod {
+ prefix sm-mod;
+ }
+
+ revision 2022-09-15 {
+ description
+ "initial";
+ reference
+ "";
+ }
+
+ list things {
+ key "name";
+ leaf name {
+ type leafref {
+ path "/if:interfaces/if:interface/if:name";
+ }
+ }
+ leaf attribute {
+ type uint32;
+ }
+ }
+
+ augment "/if:interfaces/if:interface" {
+ leaf thing-attribute {
+ type leafref {
+ path "/things/attribute";
+ }
+ }
+ }
+}
diff --git a/tools/lint/examples/sm-main.yang b/tools/lint/examples/sm-main.yang
new file mode 100644
index 0000000..53df6b6
--- /dev/null
+++ b/tools/lint/examples/sm-main.yang
@@ -0,0 +1,32 @@
+module sm-main {
+ yang-version 1.1;
+ namespace "urn:sm-main";
+ prefix "sm-main";
+
+ import ietf-yang-schema-mount {
+ prefix yangmnt;
+ }
+ import ietf-interfaces {
+ prefix if;
+ }
+
+ list root {
+ key "node";
+ leaf node {
+ type string;
+ }
+ yangmnt:mount-point "root";
+ }
+ container root2 {
+ yangmnt:mount-point "root";
+ }
+ container root3 {
+ list my-list {
+ key name;
+ leaf name {
+ type string;
+ }
+ yangmnt:mount-point "mnt-root";
+ }
+ }
+}
diff --git a/tools/lint/examples/sm-mod.yang b/tools/lint/examples/sm-mod.yang
new file mode 100644
index 0000000..79d1a50
--- /dev/null
+++ b/tools/lint/examples/sm-mod.yang
@@ -0,0 +1,21 @@
+module sm-mod {
+ yang-version 1.1;
+ namespace "urn:yanglint:sm-mod";
+ prefix "sm-mod";
+
+ revision 2017-01-26 {
+ description
+ "initial";
+ reference
+ "";
+ }
+
+ container not-compiled {
+ leaf first {
+ type string;
+ }
+ leaf second {
+ type string;
+ }
+ }
+}
diff --git a/tools/lint/linenoise/LICENSE b/tools/lint/linenoise/LICENSE
new file mode 100644
index 0000000..18e8148
--- /dev/null
+++ b/tools/lint/linenoise/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
+Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/tools/lint/linenoise/linenoise.c b/tools/lint/linenoise/linenoise.c
new file mode 100644
index 0000000..fed3d26
--- /dev/null
+++ b/tools/lint/linenoise/linenoise.c
@@ -0,0 +1,1218 @@
+/* linenoise.c -- VERSION 1.0
+ *
+ * Guerrilla line editing library against the idea that a line editing lib
+ * needs to be 20,000 lines of C code.
+ *
+ * You can find the latest source code at:
+ *
+ * http://github.com/antirez/linenoise
+ *
+ * Does a number of crazy assumptions that happen to be true in 99.9999% of
+ * the 2010 UNIX computers around.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * References:
+ * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
+ *
+ * Todo list:
+ * - Filter bogus Ctrl+<char> combinations.
+ * - Win32 support
+ *
+ * Bloat:
+ * - History search like Ctrl+r in readline?
+ *
+ * List of escape sequences used by this program, we do everything just
+ * with three sequences. In order to be so cheap we may have some
+ * flickering effect with some slow terminal, but the lesser sequences
+ * the more compatible.
+ *
+ * EL (Erase Line)
+ * Sequence: ESC [ n K
+ * Effect: if n is 0 or missing, clear from cursor to end of line
+ * Effect: if n is 1, clear from beginning of line to cursor
+ * Effect: if n is 2, clear entire line
+ *
+ * CUF (CUrsor Forward)
+ * Sequence: ESC [ n C
+ * Effect: moves cursor forward n chars
+ *
+ * CUB (CUrsor Backward)
+ * Sequence: ESC [ n D
+ * Effect: moves cursor backward n chars
+ *
+ * The following is used to get the terminal width if getting
+ * the width with the TIOCGWINSZ ioctl fails
+ *
+ * DSR (Device Status Report)
+ * Sequence: ESC [ 6 n
+ * Effect: reports the current cusor position as ESC [ n ; m R
+ * where n is the row and m is the column
+ *
+ * When multi line mode is enabled, we also use an additional escape
+ * sequence. However multi line editing is disabled by default.
+ *
+ * CUU (Cursor Up)
+ * Sequence: ESC [ n A
+ * Effect: moves cursor up of n chars.
+ *
+ * CUD (Cursor Down)
+ * Sequence: ESC [ n B
+ * Effect: moves cursor down of n chars.
+ *
+ * When linenoiseClearScreen() is called, two additional escape sequences
+ * are used in order to clear the screen and position the cursor at home
+ * position.
+ *
+ * CUP (Cursor position)
+ * Sequence: ESC [ H
+ * Effect: moves the cursor to upper left corner
+ *
+ * ED (Erase display)
+ * Sequence: ESC [ 2 J
+ * Effect: clear the whole screen
+ *
+ */
+
+#define _GNU_SOURCE
+#define _POSIX_C_SOURCE 200809L /* strdup */
+
+#include "linenoise.h"
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <unistd.h>
+
+#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
+#define LINENOISE_MAX_LINE 4096
+static char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
+static linenoiseCompletionCallback *completionCallback = NULL;
+
+static struct termios orig_termios; /* In order to restore at exit.*/
+static int mlmode = 0; /* Multi line mode. Default is single line. */
+static int atexit_registered = 0; /* Register atexit just 1 time. */
+static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
+static int history_len = 0;
+static char **history = NULL;
+
+/* The linenoiseState structure represents the state during line editing.
+ * We pass this state to functions implementing specific editing
+ * functionalities. */
+struct linenoiseState lss;
+
+enum KEY_ACTION{
+ KEY_NULL = 0, /* NULL */
+ CTRL_A = 1, /* Ctrl+a */
+ CTRL_B = 2, /* Ctrl-b */
+ CTRL_C = 3, /* Ctrl-c */
+ CTRL_D = 4, /* Ctrl-d */
+ CTRL_E = 5, /* Ctrl-e */
+ CTRL_F = 6, /* Ctrl-f */
+ CTRL_H = 8, /* Ctrl-h */
+ TAB = 9, /* Tab */
+ CTRL_K = 11, /* Ctrl+k */
+ CTRL_L = 12, /* Ctrl+l */
+ ENTER = 13, /* Enter */
+ CTRL_N = 14, /* Ctrl-n */
+ CTRL_P = 16, /* Ctrl-p */
+ CTRL_T = 20, /* Ctrl-t */
+ CTRL_U = 21, /* Ctrl+u */
+ CTRL_W = 23, /* Ctrl+w */
+ ESC = 27, /* Escape */
+ BACKSPACE = 127 /* Backspace */
+};
+
+static void linenoiseAtExit(void);
+int linenoiseHistoryAdd(const char *line);
+
+/* Debugging macro. */
+#if 0
+FILE *lndebug_fp = NULL;
+#define lndebug(...) \
+ do { \
+ if (lndebug_fp == NULL) { \
+ lndebug_fp = fopen("/tmp/lndebug.txt","a"); \
+ fprintf(lndebug_fp, \
+ "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \
+ (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \
+ (int)l->maxrows,old_rows); \
+ } \
+ fprintf(lndebug_fp, ", " __VA_ARGS__); \
+ fflush(lndebug_fp); \
+ } while (0)
+#else
+#define lndebug(...)
+#endif
+
+/* ======================= Low level terminal handling ====================== */
+
+/* Set if to use or not the multi line mode. */
+void linenoiseSetMultiLine(int ml) {
+ mlmode = ml;
+}
+
+/* Return true if the terminal name is in the list of terminals we know are
+ * not able to understand basic escape sequences. */
+static int isUnsupportedTerm(void) {
+ char *term = getenv("TERM");
+ int j;
+
+ if (term == NULL) return 0;
+ for (j = 0; unsupported_term[j]; j++)
+ if (!strcasecmp(term,unsupported_term[j])) return 1;
+ return 0;
+}
+
+/* Raw mode: 1960 magic shit. */
+int linenoiseEnableRawMode(int fd) {
+ struct termios raw;
+
+ if (!isatty(STDIN_FILENO)) goto fatal;
+ if (!atexit_registered) {
+ atexit(linenoiseAtExit);
+ atexit_registered = 1;
+ }
+ if (tcgetattr(fd,&orig_termios) == -1) goto fatal;
+
+ raw = orig_termios; /* modify the original mode */
+ /* input modes: no break, no CR to NL, no parity check, no strip char,
+ * no start/stop output control. */
+ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ /* output modes - disable post processing */
+ raw.c_oflag &= ~(OPOST);
+ /* control modes - set 8 bit chars */
+ raw.c_cflag |= (CS8);
+ /* local modes - choing off, canonical off, no extended functions,
+ * no signal chars (^Z,^C) */
+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+ /* control chars - set return condition: min number of bytes and timer.
+ * We want read to return every single byte, without timeout. */
+ raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
+
+ /* put terminal in raw mode after flushing */
+ if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal;
+ lss.rawmode = 1;
+ return 0;
+
+fatal:
+ errno = ENOTTY;
+ return -1;
+}
+
+void linenoiseDisableRawMode(int fd) {
+ /* Don't even check the return value as it's too late. */
+ if (lss.rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1)
+ lss.rawmode = 0;
+}
+
+/* Use the ESC [6n escape sequence to query the horizontal cursor position
+ * and return it. On error -1 is returned, on success the position of the
+ * cursor. */
+static int getCursorPosition(int ifd, int ofd) {
+ char buf[32];
+ int cols, rows;
+ unsigned int i = 0;
+
+ /* Report cursor location */
+ if (write(ofd, "\x1b[6n", 4) != 4) return -1;
+
+ /* Read the response: ESC [ rows ; cols R */
+ while (i < sizeof(buf)-1) {
+ if (read(ifd,buf+i,1) != 1) break;
+ if (buf[i] == 'R') break;
+ i++;
+ }
+ buf[i] = '\0';
+
+ /* Parse it. */
+ if (buf[0] != ESC || buf[1] != '[') return -1;
+ if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
+ return cols;
+}
+
+/* Try to get the number of columns in the current terminal, or assume 80
+ * if it fails. */
+static int getColumns(int ifd, int ofd) {
+ struct winsize ws;
+
+ if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
+ /* ioctl() failed. Try to query the terminal itself. */
+ int start, cols;
+
+ /* Get the initial position so we can restore it later. */
+ start = getCursorPosition(ifd,ofd);
+ if (start == -1) goto failed;
+
+ /* Go to right margin and get position. */
+ if (write(ofd,"\x1b[999C",6) != 6) goto failed;
+ cols = getCursorPosition(ifd,ofd);
+ if (cols == -1) goto failed;
+
+ /* Restore position. */
+ if (cols > start) {
+ char seq[32];
+ snprintf(seq,32,"\x1b[%dD",cols-start);
+ if (write(ofd,seq,strlen(seq)) == -1) {
+ /* Can't recover... */
+ }
+ }
+ return cols;
+ } else {
+ return ws.ws_col;
+ }
+
+failed:
+ return 80;
+}
+
+/* Clear the screen. Used to handle ctrl+l */
+void linenoiseClearScreen(void) {
+ if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) {
+ /* nothing to do, just to avoid warning. */
+ }
+}
+
+/* Beep, used for completion when there is nothing to complete or when all
+ * the choices were already shown. */
+static void linenoiseBeep(void) {
+ fprintf(stderr, "\x7");
+ fflush(stderr);
+}
+
+/* ============================== Completion ================================ */
+
+/* Free a list of completion option populated by linenoiseAddCompletion(). */
+static void freeCompletions(linenoiseCompletions *lc) {
+ size_t i;
+ for (i = 0; i < lc->len; i++)
+ free(lc->cvec[i]);
+ if (lc->cvec != NULL)
+ free(lc->cvec);
+}
+
+/* This is an helper function for linenoiseEdit() and is called when the
+ * user types the <tab> key in order to complete the string currently in the
+ * input.
+ *
+ * The state of the editing is encapsulated into the pointed linenoiseState
+ * structure as described in the structure definition. */
+static char completeLine(struct linenoiseState *ls) {
+ linenoiseCompletions lc = {0, 0, NULL};
+ int nread, nwritten, hint_len, hint_line_count, char_count;
+ char c = 0, *common, *hint;
+ struct winsize w;
+
+ /* Hint is only the string after the last space */
+ hint = strrchr(ls->buf, ' ');
+ if (!hint) {
+ hint = ls->buf;
+ } else {
+ ++hint;
+ }
+
+ completionCallback(ls->buf, hint, &lc);
+ if (lc.len == 0) {
+ linenoiseBeep();
+ } else {
+ unsigned int i, j;
+
+ /* Learn the longest common part */
+ common = strdup(lc.cvec[0]);
+ for (i = 1; i < lc.len; ++i) {
+ for (j = 0; j < strlen(lc.cvec[i]); ++j) {
+ if (lc.cvec[i][j] != common[j]) {
+ break;
+ }
+ }
+ common[j] = '\0';
+ }
+
+ /* Path completions have a different hint */
+ if (lc.path && strrchr(hint, '/')) {
+ hint = strrchr(hint, '/');
+ ++hint;
+ }
+
+ /* Show completion */
+ if ((lc.len == 1) && (common[strlen(common) - 1] != '/')) {
+ nwritten = snprintf(hint, ls->buflen - (hint - ls->buf), "%s ", common);
+ } else {
+ nwritten = snprintf(hint, ls->buflen - (hint - ls->buf), "%s", common);
+ }
+ free(common);
+ ls->len = ls->pos = (hint - ls->buf) + nwritten;
+ linenoiseRefreshLine();
+
+ /* A single hint */
+ if (lc.len == 1) {
+ freeCompletions(&lc);
+ return 0;
+ }
+
+ /* Read a char */
+ nread = read(ls->ifd,&c,1);
+ if (nread <= 0) {
+ freeCompletions(&lc);
+ return -1;
+ }
+
+ /* Not a tab */
+ if (c != 9) {
+ freeCompletions(&lc);
+ return c;
+ }
+
+ /* Learn terminal window size */
+ ioctl(ls->ifd, TIOCGWINSZ, &w);
+
+ /* Learn the longest hint */
+ hint_len = strlen(lc.cvec[0]);
+ for (i = 1; i < lc.len; ++i) {
+ if (strlen(lc.cvec[i]) > (unsigned)hint_len) {
+ hint_len = strlen(lc.cvec[i]);
+ }
+ }
+
+ /* Learn the number of hints that fit a line */
+ hint_line_count = 0;
+ do {
+ /* Still fits, always at least one hint */
+ ++hint_line_count;
+
+ char_count = 0;
+ if (hint_line_count) {
+ char_count += hint_line_count * (hint_len + 2);
+ }
+ char_count += hint_len;
+
+ /* Too much */
+ } while (char_count <= w.ws_col);
+
+ while (c == 9) {
+ /* Second tab */
+ linenoiseDisableRawMode(ls->ifd);
+ printf("\n");
+ for (i = 0; i < lc.len; ++i) {
+ printf("%-*s", hint_len, lc.cvec[i]);
+ /* Line full or last hint */
+ if (((i + 1) % hint_line_count == 0) || (i == lc.len - 1)) {
+ printf("\n");
+ } else {
+ printf(" ");
+ }
+ }
+ linenoiseEnableRawMode(ls->ifd);
+ linenoiseRefreshLine();
+
+ /* Read a char */
+ nread = read(ls->ifd,&c,1);
+ if (nread <= 0) {
+ freeCompletions(&lc);
+ return -1;
+ }
+ }
+ }
+
+ freeCompletions(&lc);
+ return c; /* Return last read character */
+}
+
+/* Register a callback function to be called for tab-completion. */
+void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
+ completionCallback = fn;
+}
+
+/* This function can be called in user completion callback to fill
+ * path completion for them. hint parameter is actually the whole path
+ * and buf is unused, but included to match the completion callback prototype. */
+void linenoisePathCompletion(const char *buf, const char *hint, linenoiseCompletions *lc) {
+ const char *ptr;
+ char *full_path, *hint_ptr, match[FILENAME_MAX + 2];
+ DIR *dir;
+ struct dirent *ent;
+ struct stat st;
+
+ (void)buf;
+
+ lc->path = 1;
+
+ ptr = strrchr(hint, '/');
+
+ /* new relative path */
+ if (ptr == NULL) {
+ full_path = malloc(2 + FILENAME_MAX + 1);
+ strcpy(full_path, "./");
+
+ ptr = hint;
+ } else {
+ full_path = malloc((int)(ptr - hint) + FILENAME_MAX + 1);
+ ++ptr;
+ sprintf(full_path, "%.*s", (int)(ptr - hint), hint);
+ }
+ hint_ptr = full_path + strlen(full_path);
+
+ dir = opendir(full_path);
+ if (dir == NULL) {
+ free(full_path);
+ return;
+ }
+
+ while ((ent = readdir(dir))) {
+ if (ent->d_name[0] == '.') {
+ continue;
+ }
+
+ if (!strncmp(ptr, ent->d_name, strlen(ptr))) {
+ /* is it a directory? */
+ strcpy(hint_ptr, ent->d_name);
+ if (stat(full_path, &st)) {
+ /* skip this item */
+ continue;
+ }
+
+ strcpy(match, ent->d_name);
+ if (S_ISDIR(st.st_mode)) {
+ strcat(match, "/");
+ }
+
+ linenoiseAddCompletion(lc, match);
+ }
+ }
+
+ free(full_path);
+ closedir(dir);
+}
+
+/* This function is used by the callback function registered by the user
+ * in order to add completion options given the input string when the
+ * user typed <tab>. See the example.c source code for a very easy to
+ * understand example. */
+void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
+ size_t len = strlen(str);
+ char *copy, **cvec;
+
+ copy = malloc(len+1);
+ if (copy == NULL) return;
+ memcpy(copy,str,len+1);
+ cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
+ if (cvec == NULL) {
+ free(copy);
+ return;
+ }
+ lc->cvec = cvec;
+ lc->cvec[lc->len++] = copy;
+}
+
+/* =========================== Line editing ================================= */
+
+/* We define a very simple "append buffer" structure, that is an heap
+ * allocated string where we can append to. This is useful in order to
+ * write all the escape sequences in a buffer and flush them to the standard
+ * output in a single call, to avoid flickering effects. */
+struct abuf {
+ char *b;
+ int len;
+};
+
+static void abInit(struct abuf *ab) {
+ ab->b = NULL;
+ ab->len = 0;
+}
+
+static void abAppend(struct abuf *ab, const char *s, int len) {
+ char *new = realloc(ab->b,ab->len+len);
+
+ if (new == NULL) return;
+ memcpy(new+ab->len,s,len);
+ ab->b = new;
+ ab->len += len;
+}
+
+static void abFree(struct abuf *ab) {
+ free(ab->b);
+}
+
+/* Single line low level line refresh.
+ *
+ * Rewrite the currently edited line accordingly to the buffer content,
+ * cursor position, and number of columns of the terminal. */
+static void refreshSingleLine(struct linenoiseState *l) {
+ char seq[64];
+ size_t plen = strlen(l->prompt);
+ int fd = l->ofd;
+ char *buf = l->buf;
+ size_t len = l->len;
+ size_t pos = l->pos;
+ struct abuf ab;
+
+ while((plen+pos) >= l->cols) {
+ buf++;
+ len--;
+ pos--;
+ }
+ while (plen+len > l->cols) {
+ len--;
+ }
+
+ abInit(&ab);
+ /* Cursor to left edge */
+ snprintf(seq,64,"\r");
+ abAppend(&ab,seq,strlen(seq));
+ /* Write the prompt and the current buffer content */
+ abAppend(&ab,l->prompt,strlen(l->prompt));
+ abAppend(&ab,buf,len);
+ /* Erase to right */
+ snprintf(seq,64,"\x1b[0K");
+ abAppend(&ab,seq,strlen(seq));
+ /* Move cursor to original position. */
+ snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen));
+ abAppend(&ab,seq,strlen(seq));
+ if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
+ abFree(&ab);
+}
+
+/* Multi line low level line refresh.
+ *
+ * Rewrite the currently edited line accordingly to the buffer content,
+ * cursor position, and number of columns of the terminal. */
+static void refreshMultiLine(struct linenoiseState *l) {
+ char seq[64];
+ int plen = strlen(l->prompt);
+ int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */
+ int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */
+ int rpos2; /* rpos after refresh. */
+ int col; /* colum position, zero-based. */
+ int old_rows = l->maxrows;
+ int fd = l->ofd, j;
+ struct abuf ab;
+
+ /* Update maxrows if needed. */
+ if (rows > (int)l->maxrows) l->maxrows = rows;
+
+ /* First step: clear all the lines used before. To do so start by
+ * going to the last row. */
+ abInit(&ab);
+ if (old_rows-rpos > 0) {
+ lndebug("go down %d", old_rows-rpos);
+ snprintf(seq,64,"\x1b[%dB", old_rows-rpos);
+ abAppend(&ab,seq,strlen(seq));
+ }
+
+ /* Now for every row clear it, go up. */
+ for (j = 0; j < old_rows-1; j++) {
+ lndebug("clear+up");
+ snprintf(seq,64,"\r\x1b[0K\x1b[1A");
+ abAppend(&ab,seq,strlen(seq));
+ }
+
+ /* Clean the top line. */
+ lndebug("clear");
+ snprintf(seq,64,"\r\x1b[0K");
+ abAppend(&ab,seq,strlen(seq));
+
+ /* Write the prompt and the current buffer content */
+ abAppend(&ab,l->prompt,strlen(l->prompt));
+ abAppend(&ab,l->buf,l->len);
+
+ /* If we are at the very end of the screen with our prompt, we need to
+ * emit a newline and move the prompt to the first column. */
+ if (l->pos &&
+ l->pos == l->len &&
+ (l->pos+plen) % l->cols == 0)
+ {
+ lndebug("<newline>");
+ abAppend(&ab,"\n",1);
+ snprintf(seq,64,"\r");
+ abAppend(&ab,seq,strlen(seq));
+ rows++;
+ if (rows > (int)l->maxrows) l->maxrows = rows;
+ }
+
+ /* Move cursor to right position. */
+ rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */
+ lndebug("rpos2 %d", rpos2);
+
+ /* Go up till we reach the expected positon. */
+ if (rows-rpos2 > 0) {
+ lndebug("go-up %d", rows-rpos2);
+ snprintf(seq,64,"\x1b[%dA", rows-rpos2);
+ abAppend(&ab,seq,strlen(seq));
+ }
+
+ /* Set column. */
+ col = (plen+(int)l->pos) % (int)l->cols;
+ lndebug("set col %d", 1+col);
+ if (col)
+ snprintf(seq,64,"\r\x1b[%dC", col);
+ else
+ snprintf(seq,64,"\r");
+ abAppend(&ab,seq,strlen(seq));
+
+ lndebug("\n");
+ l->oldpos = l->pos;
+
+ if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
+ abFree(&ab);
+}
+
+/* Calls the two low level functions refreshSingleLine() or
+ * refreshMultiLine() according to the selected mode. */
+void linenoiseRefreshLine(void) {
+ /* Update columns in case the terminal was resized */
+ lss.cols = getColumns(STDIN_FILENO, STDOUT_FILENO);
+
+ if (mlmode)
+ refreshMultiLine(&lss);
+ else
+ refreshSingleLine(&lss);
+}
+
+/* Insert the character 'c' at cursor current position.
+ *
+ * On error writing to the terminal -1 is returned, otherwise 0. */
+int linenoiseEditInsert(struct linenoiseState *l, char c) {
+ if (l->len < l->buflen) {
+ if (l->len == l->pos) {
+ l->buf[l->pos] = c;
+ l->pos++;
+ l->len++;
+ l->buf[l->len] = '\0';
+ if ((!mlmode && l->plen+l->len < l->cols) /* || mlmode */) {
+ /* Avoid a full update of the line in the
+ * trivial case. */
+ if (write(l->ofd,&c,1) == -1) return -1;
+ } else {
+ linenoiseRefreshLine();
+ }
+ } else {
+ memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos);
+ l->buf[l->pos] = c;
+ l->len++;
+ l->pos++;
+ l->buf[l->len] = '\0';
+ linenoiseRefreshLine();
+ }
+ }
+ return 0;
+}
+
+/* Move cursor on the left. */
+void linenoiseEditMoveLeft(struct linenoiseState *l) {
+ if (l->pos > 0) {
+ l->pos--;
+ linenoiseRefreshLine();
+ }
+}
+
+/* Move cursor on the right. */
+void linenoiseEditMoveRight(struct linenoiseState *l) {
+ if (l->pos != l->len) {
+ l->pos++;
+ linenoiseRefreshLine();
+ }
+}
+
+/* Move cursor to the start of the line. */
+void linenoiseEditMoveHome(struct linenoiseState *l) {
+ if (l->pos != 0) {
+ l->pos = 0;
+ linenoiseRefreshLine();
+ }
+}
+
+/* Move cursor to the end of the line. */
+void linenoiseEditMoveEnd(struct linenoiseState *l) {
+ if (l->pos != l->len) {
+ l->pos = l->len;
+ linenoiseRefreshLine();
+ }
+}
+
+/* Substitute the currently edited line with the next or previous history
+ * entry as specified by 'dir'. */
+#define LINENOISE_HISTORY_NEXT 0
+#define LINENOISE_HISTORY_PREV 1
+void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) {
+ if (history_len > 1) {
+ /* Update the current history entry before to
+ * overwrite it with the next one. */
+ free(history[history_len - 1 - l->history_index]);
+ history[history_len - 1 - l->history_index] = strdup(l->buf);
+ /* Show the new entry */
+ l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1;
+ if (l->history_index < 0) {
+ l->history_index = 0;
+ return;
+ } else if (l->history_index >= history_len) {
+ l->history_index = history_len-1;
+ return;
+ }
+ strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen);
+ l->buf[l->buflen-1] = '\0';
+ l->len = l->pos = strlen(l->buf);
+ linenoiseRefreshLine();
+ }
+}
+
+/* Delete the character at the right of the cursor without altering the cursor
+ * position. Basically this is what happens with the "Delete" keyboard key. */
+void linenoiseEditDelete(struct linenoiseState *l) {
+ if (l->len > 0 && l->pos < l->len) {
+ memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1);
+ l->len--;
+ l->buf[l->len] = '\0';
+ linenoiseRefreshLine();
+ }
+}
+
+/* Backspace implementation. */
+void linenoiseEditBackspace(struct linenoiseState *l) {
+ if (l->pos > 0 && l->len > 0) {
+ memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos);
+ l->pos--;
+ l->len--;
+ l->buf[l->len] = '\0';
+ linenoiseRefreshLine();
+ }
+}
+
+/* Delete the previosu word, maintaining the cursor at the start of the
+ * current word. */
+void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
+ size_t old_pos = l->pos;
+ size_t diff;
+
+ while (l->pos > 0 && l->buf[l->pos-1] == ' ')
+ l->pos--;
+ while (l->pos > 0 && l->buf[l->pos-1] != ' ')
+ l->pos--;
+ diff = old_pos - l->pos;
+ memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1);
+ l->len -= diff;
+ linenoiseRefreshLine();
+}
+
+/* This function is the core of the line editing capability of linenoise.
+ * It expects 'fd' to be already in "raw mode" so that every key pressed
+ * will be returned ASAP to read().
+ *
+ * The resulting string is put into 'buf' when the user type enter, or
+ * when ctrl+d is typed.
+ *
+ * The function returns the length of the current buffer. */
+static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt)
+{
+ /* Populate the linenoise state that we pass to functions implementing
+ * specific editing functionalities. */
+ lss.ifd = stdin_fd;
+ lss.ofd = stdout_fd;
+ lss.buf = buf;
+ lss.buflen = buflen;
+ lss.prompt = prompt;
+ lss.plen = strlen(prompt);
+ lss.oldpos = lss.pos = 0;
+ lss.len = 0;
+ lss.cols = getColumns(stdin_fd, stdout_fd);
+ lss.maxrows = 0;
+ lss.history_index = 0;
+
+ /* Buffer starts empty. */
+ lss.buf[0] = '\0';
+ lss.buflen--; /* Make sure there is always space for the nulterm */
+
+ /* The latest history entry is always our current buffer, that
+ * initially is just an empty string. */
+ linenoiseHistoryAdd("");
+
+ if (write(lss.ofd,prompt,lss.plen) == -1) return -1;
+ while(1) {
+ char c = 0;
+ int nread;
+ char seq[3];
+
+ nread = read(lss.ifd,&c,sizeof c);
+ if (nread <= 0) return lss.len;
+
+ /* Only autocomplete when the callback is set. It returns < 0 when
+ * there was an error reading from fd. Otherwise it will return the
+ * character that should be handled next. */
+ if (c == 9 && completionCallback != NULL) {
+ c = completeLine(&lss);
+ /* Return on errors */
+ if (c < 0) return lss.len;
+ /* Read next character when 0 */
+ if (c == 0) continue;
+ }
+
+ switch(c) {
+ case ENTER: /* enter */
+ history_len--;
+ free(history[history_len]);
+ if (mlmode) linenoiseEditMoveEnd(&lss);
+ return (int)lss.len;
+ case CTRL_C: /* ctrl-c */
+ errno = EAGAIN;
+ return -1;
+ case BACKSPACE: /* backspace */
+ case 8: /* ctrl-h */
+ linenoiseEditBackspace(&lss);
+ break;
+ case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the
+ line is empty, act as end-of-file. */
+ if (lss.len > 0) {
+ linenoiseEditDelete(&lss);
+ } else {
+ history_len--;
+ free(history[history_len]);
+ return -1;
+ }
+ break;
+ case CTRL_T: /* ctrl-t, swaps current character with previous. */
+ if (lss.pos > 0 && lss.pos < lss.len) {
+ int aux = buf[lss.pos-1];
+ buf[lss.pos-1] = buf[lss.pos];
+ buf[lss.pos] = aux;
+ if (lss.pos != lss.len-1) lss.pos++;
+ linenoiseRefreshLine();
+ }
+ break;
+ case CTRL_B: /* ctrl-b */
+ linenoiseEditMoveLeft(&lss);
+ break;
+ case CTRL_F: /* ctrl-f */
+ linenoiseEditMoveRight(&lss);
+ break;
+ case CTRL_P: /* ctrl-p */
+ linenoiseEditHistoryNext(&lss, LINENOISE_HISTORY_PREV);
+ break;
+ case CTRL_N: /* ctrl-n */
+ linenoiseEditHistoryNext(&lss, LINENOISE_HISTORY_NEXT);
+ break;
+ case ESC: /* escape sequence */
+ /* Read the next two bytes representing the escape sequence.
+ * Use two calls to handle slow terminals returning the two
+ * chars at different times. */
+ if (read(lss.ifd,seq,1) == -1) break;
+ if (read(lss.ifd,seq+1,1) == -1) break;
+
+ /* ESC [ sequences. */
+ if (seq[0] == '[') {
+ if (seq[1] >= '0' && seq[1] <= '9') {
+ /* Extended escape, read additional byte. */
+ if (read(lss.ifd, seq + 2, 1) == -1) break;
+ if ((seq[1] == '3') && (seq[2] == '~')) {
+ /* Delete key. */
+ linenoiseEditDelete(&lss);
+ }
+ } else {
+ switch(seq[1]) {
+ case 'A': /* Up */
+ linenoiseEditHistoryNext(&lss, LINENOISE_HISTORY_PREV);
+ break;
+ case 'B': /* Down */
+ linenoiseEditHistoryNext(&lss, LINENOISE_HISTORY_NEXT);
+ break;
+ case 'C': /* Right */
+ linenoiseEditMoveRight(&lss);
+ break;
+ case 'D': /* Left */
+ linenoiseEditMoveLeft(&lss);
+ break;
+ case 'H': /* Home */
+ linenoiseEditMoveHome(&lss);
+ break;
+ case 'F': /* End*/
+ linenoiseEditMoveEnd(&lss);
+ break;
+ }
+ }
+ }
+
+ /* ESC O sequences. */
+ else if (seq[0] == 'O') {
+ switch(seq[1]) {
+ case 'H': /* Home */
+ linenoiseEditMoveHome(&lss);
+ break;
+ case 'F': /* End*/
+ linenoiseEditMoveEnd(&lss);
+ break;
+ }
+ }
+ break;
+ default:
+ if (linenoiseEditInsert(&lss,c)) return -1;
+ break;
+ case CTRL_U: /* Ctrl+u, delete the whole line. */
+ buf[0] = '\0';
+ lss.pos = lss.len = 0;
+ linenoiseRefreshLine();
+ break;
+ case CTRL_K: /* Ctrl+k, delete from current to end of line. */
+ buf[lss.pos] = '\0';
+ lss.len = lss.pos;
+ linenoiseRefreshLine();
+ break;
+ case CTRL_A: /* Ctrl+a, go to the start of the line */
+ linenoiseEditMoveHome(&lss);
+ break;
+ case CTRL_E: /* ctrl+e, go to the end of the line */
+ linenoiseEditMoveEnd(&lss);
+ break;
+ case CTRL_L: /* ctrl+l, clear screen */
+ linenoiseClearScreen();
+ linenoiseRefreshLine();
+ break;
+ case CTRL_W: /* ctrl+w, delete previous word */
+ linenoiseEditDeletePrevWord(&lss);
+ break;
+ }
+ }
+ return lss.len;
+}
+
+/* This special mode is used by linenoise in order to print scan codes
+ * on screen for debugging / development purposes. It is implemented
+ * by the linenoise_example program using the --keycodes option. */
+void linenoisePrintKeyCodes(void) {
+ char quit[4];
+
+ printf("Linenoise key codes debugging mode.\n"
+ "Press keys to see scan codes. Type 'quit' at any time to exit.\n");
+ if (linenoiseEnableRawMode(STDIN_FILENO) == -1) return;
+ memset(quit,' ',4);
+ while(1) {
+ char c;
+ int nread;
+
+ nread = read(STDIN_FILENO,&c,1);
+ if (nread <= 0) continue;
+ memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */
+ quit[sizeof(quit)-1] = c; /* Insert current char on the right. */
+ if (memcmp(quit,"quit",sizeof(quit)) == 0) break;
+
+ printf("'%c' %02x (%d) (type quit to exit)\n",
+ isprint(c) ? c : '?', (int)c, (int)c);
+ printf("\r"); /* Go left edge manually, we are in raw mode. */
+ fflush(stdout);
+ }
+ linenoiseDisableRawMode(STDIN_FILENO);
+}
+
+/* This function calls the line editing function linenoiseEdit() using
+ * the STDIN file descriptor set in raw mode. */
+static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
+ int count;
+
+ if (buflen == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!isatty(STDIN_FILENO)) {
+ /* Not a tty: read from file / pipe. */
+ if (fgets(buf, buflen, stdin) == NULL) return -1;
+ count = strlen(buf);
+ if (count && buf[count-1] == '\n') {
+ count--;
+ buf[count] = '\0';
+ }
+ } else {
+ /* Interactive editing. */
+ if (linenoiseEnableRawMode(STDIN_FILENO) == -1) return -1;
+ count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt);
+ linenoiseDisableRawMode(STDIN_FILENO);
+ printf("\n");
+ }
+ return count;
+}
+
+/* The high level function that is the main API of the linenoise library.
+ * This function checks if the terminal has basic capabilities, just checking
+ * for a blacklist of stupid terminals, and later either calls the line
+ * editing function or uses dummy fgets() so that you will be able to type
+ * something even in the most desperate of the conditions. */
+char *linenoise(const char *prompt) {
+ char buf[LINENOISE_MAX_LINE];
+ int count;
+
+ if (isUnsupportedTerm()) {
+ size_t len;
+
+ printf("%s",prompt);
+ fflush(stdout);
+ if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL;
+ len = strlen(buf);
+ while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) {
+ len--;
+ buf[len] = '\0';
+ }
+ return strdup(buf);
+ } else {
+ count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt);
+ if (count == -1) return NULL;
+ return strdup(buf);
+ }
+}
+
+/* ================================ History ================================= */
+
+/* Free the history, but does not reset it. Only used when we have to
+ * exit() to avoid memory leaks are reported by valgrind & co. */
+static void freeHistory(void) {
+ if (history) {
+ int j;
+
+ for (j = 0; j < history_len; j++)
+ free(history[j]);
+ free(history);
+ }
+}
+
+/* At exit we'll try to fix the terminal to the initial conditions. */
+static void linenoiseAtExit(void) {
+ linenoiseDisableRawMode(STDIN_FILENO);
+ freeHistory();
+}
+
+/* This is the API call to add a new entry in the linenoise history.
+ * It uses a fixed array of char pointers that are shifted (memmoved)
+ * when the history max length is reached in order to remove the older
+ * entry and make room for the new one, so it is not exactly suitable for huge
+ * histories, but will work well for a few hundred of entries.
+ *
+ * Using a circular buffer is smarter, but a bit more complex to handle. */
+int linenoiseHistoryAdd(const char *line) {
+ char *linecopy;
+
+ if (history_max_len == 0) return 0;
+
+ /* Initialization on first call. */
+ if (history == NULL) {
+ history = malloc(sizeof(char*)*history_max_len);
+ if (history == NULL) return 0;
+ memset(history,0,(sizeof(char*)*history_max_len));
+ }
+
+ /* Don't add duplicated lines. */
+ if (history_len && !strcmp(history[history_len-1], line)) return 0;
+
+ /* Add an heap allocated copy of the line in the history.
+ * If we reached the max length, remove the older line. */
+ linecopy = strdup(line);
+ if (!linecopy) return 0;
+ if (history_len == history_max_len) {
+ free(history[0]);
+ memmove(history,history+1,sizeof(char*)*(history_max_len-1));
+ history_len--;
+ }
+ history[history_len] = linecopy;
+ history_len++;
+ return 1;
+}
+
+/* Set the maximum length for the history. This function can be called even
+ * if there is already some history, the function will make sure to retain
+ * just the latest 'len' elements if the new history length value is smaller
+ * than the amount of items already inside the history. */
+int linenoiseHistorySetMaxLen(int len) {
+ char **new;
+
+ if (len < 1) return 0;
+ if (history) {
+ int tocopy = history_len;
+
+ new = malloc(sizeof(char*)*len);
+ if (new == NULL) return 0;
+
+ /* If we can't copy everything, free the elements we'll not use. */
+ if (len < tocopy) {
+ int j;
+
+ for (j = 0; j < tocopy-len; j++) free(history[j]);
+ tocopy = len;
+ }
+ memset(new,0,sizeof(char*)*len);
+ memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy);
+ free(history);
+ history = new;
+ }
+ history_max_len = len;
+ if (history_len > history_max_len)
+ history_len = history_max_len;
+ return 1;
+}
+
+/* Save the history in the specified file. On success 0 is returned
+ * otherwise -1 is returned. */
+int linenoiseHistorySave(const char *filename) {
+ FILE *fp = fopen(filename,"w");
+ int j;
+
+ if (fp == NULL) return -1;
+ for (j = 0; j < history_len; j++)
+ fprintf(fp,"%s\n",history[j]);
+ fclose(fp);
+ return 0;
+}
+
+/* Load the history from the specified file. If the file does not exist
+ * zero is returned and no operation is performed.
+ *
+ * If the file exists and the operation succeeded 0 is returned, otherwise
+ * on error -1 is returned. */
+int linenoiseHistoryLoad(const char *filename) {
+ FILE *fp = fopen(filename,"r");
+ char buf[LINENOISE_MAX_LINE];
+
+ if (fp == NULL) return -1;
+
+ while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
+ char *p;
+
+ p = strchr(buf,'\r');
+ if (!p) p = strchr(buf,'\n');
+ if (p) *p = '\0';
+ linenoiseHistoryAdd(buf);
+ }
+ fclose(fp);
+ return 0;
+}
diff --git a/tools/lint/linenoise/linenoise.h b/tools/lint/linenoise/linenoise.h
new file mode 100644
index 0000000..8362b1f
--- /dev/null
+++ b/tools/lint/linenoise/linenoise.h
@@ -0,0 +1,94 @@
+/* linenoise.h -- VERSION 1.0
+ *
+ * Guerrilla line editing library against the idea that a line editing lib
+ * needs to be 20,000 lines of C code.
+ *
+ * See linenoise.c for more information.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __LINENOISE_H
+#define __LINENOISE_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct linenoiseState {
+ int ifd; /* Terminal stdin file descriptor. */
+ int ofd; /* Terminal stdout file descriptor. */
+ char *buf; /* Edited line buffer. */
+ size_t buflen; /* Edited line buffer size. */
+ const char *prompt; /* Prompt to display. */
+ size_t plen; /* Prompt length. */
+ size_t pos; /* Current cursor position. */
+ size_t oldpos; /* Previous refresh cursor position. */
+ size_t len; /* Current edited line length. */
+ size_t cols; /* Number of columns in terminal. */
+ size_t maxrows; /* Maximum num of rows used so far (multiline mode) */
+ int rawmode;
+ int history_index; /* The history index we are currently editing. */
+};
+
+extern struct linenoiseState lss;
+
+typedef struct linenoiseCompletions {
+ int path;
+ size_t len;
+ char **cvec;
+} linenoiseCompletions;
+
+typedef void(linenoiseCompletionCallback)(const char *, const char *, linenoiseCompletions *);
+void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
+void linenoiseAddCompletion(linenoiseCompletions *, const char *);
+
+char *linenoise(const char *prompt);
+int linenoiseHistoryAdd(const char *line);
+int linenoiseHistorySetMaxLen(int len);
+int linenoiseHistorySave(const char *filename);
+int linenoiseHistoryLoad(const char *filename);
+void linenoiseClearScreen(void);
+void linenoiseSetMultiLine(int ml);
+void linenoisePrintKeyCodes(void);
+
+void linenoisePathCompletion(const char *, const char *, linenoiseCompletions *);
+void linenoiseRefreshLine(void);
+int linenoiseEnableRawMode(int fd);
+void linenoiseDisableRawMode(int fd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LINENOISE_H */
diff --git a/tools/lint/main.c b/tools/lint/main.c
new file mode 100644
index 0000000..9f0d027
--- /dev/null
+++ b/tools/lint/main.c
@@ -0,0 +1,102 @@
+/**
+ * @file main.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief libyang's yanglint tool
+ *
+ * Copyright (c) 2015-2020 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
+ */
+
+#define _GNU_SOURCE
+#define _POSIX_C_SOURCE 200809L /* strdup */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libyang.h"
+
+#include "cmd.h"
+#include "common.h"
+#include "completion.h"
+#include "configuration.h"
+#include "linenoise/linenoise.h"
+
+int done;
+struct ly_ctx *ctx = NULL;
+
+/* main_ni.c */
+int main_ni(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ char *cmdline;
+ int cmdlen;
+
+ if (argc > 1) {
+ /* run in non-interactive mode */
+ return main_ni(argc, argv);
+ }
+
+ /* continue in interactive mode */
+ linenoiseSetCompletionCallback(complete_cmd);
+ load_config();
+
+ if (ly_ctx_new(NULL, YL_DEFAULT_CTX_OPTIONS, &ctx)) {
+ YLMSG_E("Failed to create context.\n");
+ return 1;
+ }
+
+ while (!done) {
+ uint8_t executed = 0;
+
+ /* get the command from user */
+ cmdline = linenoise(PROMPT);
+
+ /* EOF -> exit */
+ if (cmdline == NULL) {
+ done = 1;
+ cmdline = strdup("quit");
+ }
+
+ /* empty line -> wait for another command */
+ if (*cmdline == '\0') {
+ free(cmdline);
+ continue;
+ }
+
+ /* isolate the command word. */
+ for (cmdlen = 0; cmdline[cmdlen] && (cmdline[cmdlen] != ' '); cmdlen++) {}
+
+ /* execute the command if any valid specified */
+ for (uint16_t i = 0; commands[i].name; i++) {
+ if (strncmp(cmdline, commands[i].name, (size_t)cmdlen) || (commands[i].name[cmdlen] != '\0')) {
+ continue;
+ }
+
+ commands[i].func(&ctx, cmdline);
+ executed = 1;
+ break;
+ }
+
+ if (!executed) {
+ /* if unknown command specified, tell it to user */
+ YLMSG_E("Unknown command \"%.*s\", type 'help' for more information.\n", cmdlen, cmdline);
+ }
+
+ linenoiseHistoryAdd(cmdline);
+ free(cmdline);
+ }
+
+ store_config();
+ ly_ctx_destroy(ctx);
+
+ return 0;
+}
diff --git a/tools/lint/main_ni.c b/tools/lint/main_ni.c
new file mode 100644
index 0000000..04c2340
--- /dev/null
+++ b/tools/lint/main_ni.c
@@ -0,0 +1,1027 @@
+/**
+ * @file main_ni.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief libyang's yanglint tool - non-interactive code
+ *
+ * Copyright (c) 2020 - 2022 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
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/stat.h>
+
+#include "libyang.h"
+#include "plugins_exts.h"
+
+#include "common.h"
+#include "out.h"
+#include "tools/config.h"
+
+/**
+ * @brief Context structure to hold and pass variables in a structured form.
+ */
+struct context {
+ /* libyang context for the run */
+ const char *yang_lib_file;
+ uint16_t ctx_options;
+ struct ly_ctx *ctx;
+
+ /* prepared output (--output option or stdout by default) */
+ struct ly_out *out;
+
+ char *searchpaths;
+
+ /* options flags */
+ uint8_t list; /* -l option to print list of schemas */
+
+ /* line length for 'tree' format */
+ size_t line_length; /* --tree-line-length */
+
+ /*
+ * schema
+ */
+ /* set schema modules' features via --features option (struct schema_features *) */
+ struct ly_set schema_features;
+
+ /* set of loaded schema modules (struct lys_module *) */
+ struct ly_set schema_modules;
+
+ /* options to parse and print schema modules */
+ uint32_t schema_parse_options;
+ uint32_t schema_print_options;
+
+ /* specification of printing schema node subtree, option --schema-node */
+ const char *schema_node_path;
+ const struct lysc_node *schema_node;
+ const char *submodule;
+
+ /* name of file containing explicit context passed to callback
+ * for schema-mount extension. This also causes a callback to
+ * be registered.
+ */
+ char *schema_context_filename;
+
+ /* value of --format in case of schema format */
+ LYS_OUTFORMAT schema_out_format;
+ ly_bool feature_param_format;
+
+ /*
+ * data
+ */
+ /* various options based on --type option */
+ enum lyd_type data_type;
+ uint32_t data_parse_options;
+ uint32_t data_validate_options;
+ uint32_t data_print_options;
+
+ /* flag for --merge option */
+ uint8_t data_merge;
+
+ /* value of --format in case of data format */
+ LYD_FORMAT data_out_format;
+
+ /* input data files (struct cmdline_file *) */
+ struct ly_set data_inputs;
+
+ /* storage for --operational */
+ struct cmdline_file data_operational;
+
+ /* storage for --reply-rpc */
+ struct cmdline_file reply_rpc;
+};
+
+static void
+erase_context(struct context *c)
+{
+ /* data */
+ ly_set_erase(&c->data_inputs, free_cmdline_file);
+ ly_in_free(c->data_operational.in, 1);
+
+ /* schema */
+ ly_set_erase(&c->schema_features, free_features);
+ ly_set_erase(&c->schema_modules, NULL);
+
+ /* context */
+ free(c->searchpaths);
+ c->searchpaths = NULL;
+
+ ly_out_free(c->out, NULL, 0);
+ ly_ctx_destroy(c->ctx);
+
+ if (c->schema_context_filename) {
+ free(c->schema_context_filename);
+ }
+}
+
+static void
+version(void)
+{
+ printf("yanglint %s\n", PROJECT_VERSION);
+}
+
+static void
+help(int shortout)
+{
+
+ printf("Example usage:\n"
+ " yanglint [-f { yang | yin | info}] <schema>...\n"
+ " Validates the YANG module <schema>(s) and all its dependencies, optionally printing\n"
+ " them in the specified format.\n\n"
+ " yanglint [-f { xml | json }] <schema>... <file>...\n"
+ " Validates the YANG modeled data <file>(s) according to the <schema>(s) optionally\n"
+ " printing them in the specified format.\n\n"
+ " yanglint -t (nc-)rpc/notif [-O <operational-file>] <schema>... <file>\n"
+ " Validates the YANG/NETCONF RPC/notification <file> according to the <schema>(s) using\n"
+ " <operational-file> with possible references to the operational datastore data.\n\n"
+ " yanglint -t nc-reply -R <rpc-file> [-O <operational-file>] <schema>... <file>\n"
+ " Validates the NETCONF rpc-reply <file> of RPC <rpc-file> according to the <schema>(s)\n"
+ " using <operational-file> with possible references to the operational datastore data.\n\n"
+ " yanglint\n"
+ " Starts interactive mode with more features.\n\n");
+
+ if (shortout) {
+ return;
+ }
+ printf("Options:\n"
+ " -h, --help Show this help message and exit.\n"
+ " -v, --version Show version number and exit.\n"
+ " -V, --verbose Increase libyang verbosity and show verbose messages. If specified\n"
+ " a second time, show even debug messages.\n"
+ " -Q, --quiet Decrease libyang verbosity and hide warnings. If specified a second\n"
+ " time, hide errors so no libyang messages are printed.\n");
+
+ printf(" -f FORMAT, --format=FORMAT\n"
+ " Convert input into FORMAT. Supported formats: \n"
+ " yang, yin, tree, info and feature-param for schemas,\n"
+ " xml, json, and lyb for data.\n\n");
+
+ printf(" -p PATH, --path=PATH\n"
+ " Search path for schema (YANG/YIN) modules. The option can be\n"
+ " used multiple times. The current working directory and the\n"
+ " path of the module being added is used implicitly.\n\n");
+
+ printf(" -D, --disable-searchdir\n"
+ " Do not implicitly search in current working directory for\n"
+ " schema modules. If specified a second time, do not even\n"
+ " search in the module directory (all modules must be \n"
+ " explicitly specified).\n\n");
+
+ printf(" -F FEATURES, --features=FEATURES\n"
+ " Specific module features to support in the form <module-name>:(<feature>,)*\n"
+ " Use <feature> '*' to enable all features of a module. This option can be\n"
+ " specified multiple times, to enable features in multiple modules. If this\n"
+ " option is not specified, all the features in all the implemented modules\n"
+ " are enabled.\n\n");
+
+ printf(" -i, --make-implemented\n"
+ " Make the imported modules \"referenced\" from any loaded\n"
+ " module also implemented. If specified a second time, all the\n"
+ " modules are set implemented.\n\n");
+
+ printf(" -P PATH, --schema-node=PATH\n"
+ " Print only the specified subtree of the schema.\n"
+ " The PATH is the XPath subset mentioned in documentation as\n"
+ " the Path format. The option can be combined with --single-node\n"
+ " option to print information only about the specified node.\n"
+ " -q, --single-node\n"
+ " Supplement to the --schema-node option to print information\n"
+ " only about a single node specified as PATH argument.\n\n");
+
+ printf(" -s SUBMODULE, --submodule=SUBMODULE\n"
+ " Print the specific submodule instead of the main module.\n\n");
+
+ printf(" -x FILE, --ext-data=FILE\n"
+ " File containing the specific data required by an extension. Required by\n"
+ " the schema-mount extension, for example, when the operational data are\n"
+ " expected in the file. File format is guessed.\n\n");
+
+ printf(" -n, --not-strict\n"
+ " Do not require strict data parsing (silently skip unknown data),\n"
+ " has no effect for schemas.\n\n");
+
+ printf(" -e, --present Validate only with the schema modules whose data actually\n"
+ " exist in the provided input data files. Takes effect only\n"
+ " with the 'data' or 'config' TYPEs. Used to avoid requiring\n"
+ " mandatory nodes from modules which data are not present in the\n"
+ " provided input data files.\n\n");
+
+ printf(" -t TYPE, --type=TYPE\n"
+ " Specify data tree type in the input data file(s):\n"
+ " data - Complete datastore with status data (default type).\n"
+ " config - Configuration datastore (without status data).\n"
+ " get - Data returned by the NETCONF <get> operation.\n"
+ " getconfig - Data returned by the NETCONF <get-config> operation.\n"
+ " edit - Config content of the NETCONF <edit-config> operation.\n"
+ " rpc - Invocation of a YANG RPC/action, defined as input.\n"
+ " nc-rpc - Similar to 'rpc' but expect and check also the NETCONF\n"
+ " envelopes <rpc> or <action>.\n"
+ " reply - Reply to a YANG RPC/action, defined as output. Note that\n"
+ " the reply data are expected inside a container representing\n"
+ " the original RPC/action invocation.\n"
+ " nc-reply - Similar to 'reply' but expect and check also the NETCONF\n"
+ " envelope <rpc-reply> with output data nodes as direct\n"
+ " descendants. The original RPC/action invocation is expected\n"
+ " in a separate parameter '-R' and is parsed as 'nc-rpc'.\n"
+ " notif - Notification instance of a YANG notification.\n"
+ " nc-notif - Similar to 'notif' but expect and check also the NETCONF\n"
+ " envelope <notification> with element <eventTime> and its\n"
+ " sibling as the actual notification.\n\n");
+
+ printf(" -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");
+
+ printf(" -l, --list Print info about the loaded schemas.\n"
+ " (i - imported module, I - implemented module)\n"
+ " In case the '-f' option with data encoding is specified,\n"
+ " the list is printed as \"ietf-yang-library\" data.\n\n");
+
+ printf(" -L LINE_LENGTH, --tree-line-length=LINE_LENGTH\n"
+ " The limit of the maximum line length on which the 'tree'\n"
+ " format will try to be printed.\n\n");
+
+ printf(" -o OUTFILE, --output=OUTFILE\n"
+ " Write the output to OUTFILE instead of stdout.\n\n");
+
+ printf(" -O FILE, --operational=FILE\n"
+ " Provide optional data to extend validation of the '(nc-)rpc',\n"
+ " '(nc-)reply' or '(nc-)notif' TYPEs. The FILE is supposed to contain\n"
+ " the operational datastore referenced from the operation.\n"
+ " In case of a nested operation, its parent existence is also\n"
+ " checked in these operational data.\n\n");
+
+ printf(" -R FILE, --reply-rpc=FILE\n"
+ " Provide source RPC for parsing of the 'nc-reply' TYPE. The FILE\n"
+ " is supposed to contain the source 'nc-rpc' operation of the reply.\n\n");
+
+ printf(" -m, --merge Merge input data files into a single tree and validate at\n"
+ " once. The option has effect only for 'data' and 'config' TYPEs.\n\n");
+
+ printf(" -y, --yang-library\n"
+ " Load and implement internal \"ietf-yang-library\" YANG module.\n"
+ " Note that this module includes definitions of mandatory state\n"
+ " data that can result in unexpected data validation errors.\n\n");
+
+ printf(" -Y FILE, --yang-library-file=FILE\n"
+ " Parse FILE with \"ietf-yang-library\" data and use them to\n"
+ " create an exact YANG schema context. If specified, the '-F'\n"
+ " parameter (enabled features) is ignored.\n\n");
+
+#ifndef NDEBUG
+ printf(" -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, xpath, dep-sets)\n\n");
+#endif
+}
+
+static void
+libyang_verbclb(LY_LOG_LEVEL level, const char *msg, const char *path)
+{
+ char *levstr;
+
+ 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, "libyang %s %s (%s)\n", levstr, msg, path);
+ } else {
+ fprintf(stderr, "libyang %s %s\n", levstr, msg);
+ }
+}
+
+static struct schema_features *
+get_features_not_applied(const struct ly_set *fset)
+{
+ for (uint32_t u = 0; u < fset->count; ++u) {
+ struct schema_features *sf = fset->objs[u];
+
+ if (!sf->applied) {
+ return sf;
+ }
+ }
+
+ return NULL;
+}
+
+static LY_ERR
+ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free)
+{
+ struct ly_ctx *ctx;
+ struct lyd_node *data = NULL;
+
+ ctx = ext->module->ctx;
+ if (user_data) {
+ lyd_parse_data_path(ctx, user_data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &data);
+ }
+
+ *ext_data = data;
+ *ext_data_free = 1;
+ return LY_SUCCESS;
+}
+
+static LY_ERR
+searchpath_strcat(char **searchpaths, const char *path)
+{
+ uint64_t len;
+ char *new;
+
+ if (!(*searchpaths)) {
+ *searchpaths = strdup(path);
+ return LY_SUCCESS;
+ }
+
+ len = strlen(*searchpaths) + strlen(path) + strlen(PATH_SEPARATOR);
+ new = realloc(*searchpaths, sizeof(char) * len + 1);
+ if (!new) {
+ return LY_EMEM;
+ }
+ strcat(new, PATH_SEPARATOR);
+ strcat(new, path);
+ *searchpaths = new;
+
+ return LY_SUCCESS;
+}
+
+static int
+fill_context_inputs(int argc, char *argv[], struct context *c)
+{
+ struct ly_in *in = NULL;
+ struct schema_features *sf;
+ struct lys_module *mod;
+ const char *all_features[] = {"*", NULL};
+ char *dir = NULL, *module = NULL;
+
+ /* Create libyang context. */
+ if (c->yang_lib_file) {
+ /* ignore features */
+ ly_set_erase(&c->schema_features, free_features);
+
+ if (ly_ctx_new_ylpath(c->searchpaths, c->yang_lib_file, LYD_UNKNOWN, c->ctx_options, &c->ctx)) {
+ YLMSG_E("Unable to modify libyang context with yang-library data.\n");
+ return -1;
+ }
+ } else {
+ /* set imp feature flag if all should be enabled */
+ c->ctx_options |= !c->schema_features.count ? LY_CTX_ENABLE_IMP_FEATURES : 0;
+
+ if (ly_ctx_new(c->searchpaths, c->ctx_options, &c->ctx)) {
+ YLMSG_E("Unable to create libyang context\n");
+ return -1;
+ }
+ }
+
+ /* set callback providing run-time extension instance data */
+ if (c->schema_context_filename) {
+ ly_ctx_set_ext_data_clb(c->ctx, ext_data_clb, c->schema_context_filename);
+ }
+
+ /* process the operational and/or reply RPC content if any */
+ if (c->data_operational.path) {
+ if (get_input(c->data_operational.path, NULL, &c->data_operational.format, &c->data_operational.in)) {
+ return -1;
+ }
+ }
+ if (c->reply_rpc.path) {
+ if (get_input(c->reply_rpc.path, NULL, &c->reply_rpc.format, &c->reply_rpc.in)) {
+ return -1;
+ }
+ }
+
+ for (int i = 0; i < argc - optind; i++) {
+ LYS_INFORMAT format_schema = LYS_IN_UNKNOWN;
+ LYD_FORMAT format_data = LYD_UNKNOWN;
+
+ if (get_input(argv[optind + i], &format_schema, &format_data, &in)) {
+ goto error;
+ }
+
+ if (format_schema) {
+ LY_ERR ret;
+ uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */
+ const char **features;
+
+ /* parse the input */
+ if (parse_schema_path(argv[optind + i], &dir, &module)) {
+ goto error;
+ }
+
+ if (c->yang_lib_file) {
+ /* just get the module, it should already be parsed */
+ mod = ly_ctx_get_module_implemented(c->ctx, module);
+ if (!mod) {
+ YLMSG_E("Schema module \"%s\" not implemented in yang-library data.\n", module);
+ goto error;
+ }
+ } else {
+ /* add temporarily also the path of the module itself */
+ if (ly_ctx_set_searchdir(c->ctx, dir) == LY_EEXIST) {
+ path_unset = 0;
+ }
+
+ /* get features list for this module */
+ if (!c->schema_features.count) {
+ features = all_features;
+ } else {
+ get_features(&c->schema_features, module, &features);
+ }
+
+ /* parse module */
+ ret = lys_parse(c->ctx, in, format_schema, features, &mod);
+ ly_ctx_unset_searchdir_last(c->ctx, path_unset);
+ if (ret) {
+ YLMSG_E("Parsing schema module \"%s\" failed.\n", argv[optind + i]);
+ goto error;
+ }
+ }
+
+ /* temporary cleanup */
+ free(dir);
+ dir = NULL;
+ free(module);
+ module = NULL;
+
+ if (c->schema_out_format || c->feature_param_format) {
+ /* module will be printed */
+ if (ly_set_add(&c->schema_modules, (void *)mod, 1, NULL)) {
+ YLMSG_E("Storing parsed schema module (%s) for print failed.\n", argv[optind + i]);
+ goto error;
+ }
+ }
+ } else if (format_data) {
+ if (!fill_cmdline_file(&c->data_inputs, in, argv[optind + i], format_data)) {
+ goto error;
+ }
+ in = NULL;
+ }
+
+ ly_in_free(in, 1);
+ in = NULL;
+ }
+
+ /* check that all specified features were applied, apply now if possible */
+ while ((sf = get_features_not_applied(&c->schema_features))) {
+ /* try to find implemented or the latest revision of this module */
+ mod = ly_ctx_get_module_implemented(c->ctx, sf->mod_name);
+ if (!mod) {
+ mod = ly_ctx_get_module_latest(c->ctx, sf->mod_name);
+ }
+ if (!mod) {
+ YLMSG_E("Specified features not applied, module \"%s\" not loaded.\n", sf->mod_name);
+ goto error;
+ }
+
+ /* we have the module, implement it if needed and enable the specific features */
+ if (lys_set_implemented(mod, (const char **)sf->features)) {
+ YLMSG_E("Implementing module \"%s\" failed.\n", mod->name);
+ goto error;
+ }
+ sf->applied = 1;
+ }
+
+ return 0;
+
+error:
+ ly_in_free(in, 1);
+ free(dir);
+ free(module);
+ return -1;
+}
+
+/**
+ * @brief Process command line options and store the settings into the context.
+ *
+ * return -1 in case of error;
+ * return 0 in case of success and ready to process
+ * return 1 in case of success, but expect to exit.
+ */
+static int
+fill_context(int argc, char *argv[], struct context *c)
+{
+ int ret;
+
+ int opt, opt_index;
+ struct option options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, 'v'},
+ {"verbose", no_argument, NULL, 'V'},
+ {"quiet", no_argument, NULL, 'Q'},
+ {"format", required_argument, NULL, 'f'},
+ {"path", required_argument, NULL, 'p'},
+ {"disable-searchdir", no_argument, NULL, 'D'},
+ {"features", required_argument, NULL, 'F'},
+ {"make-implemented", no_argument, NULL, 'i'},
+ {"schema-node", required_argument, NULL, 'P'},
+ {"single-node", no_argument, NULL, 'q'},
+ {"submodule", required_argument, NULL, 's'},
+ {"ext-data", required_argument, NULL, 'x'},
+ {"not-strict", no_argument, NULL, 'n'},
+ {"present", no_argument, NULL, 'e'},
+ {"type", required_argument, NULL, 't'},
+ {"default", required_argument, NULL, 'd'},
+ {"list", no_argument, NULL, 'l'},
+ {"tree-line-length", required_argument, NULL, 'L'},
+ {"output", required_argument, NULL, 'o'},
+ {"operational", required_argument, NULL, 'O'},
+ {"reply-rpc", required_argument, NULL, 'R'},
+ {"merge", no_argument, NULL, 'm'},
+ {"yang-library", no_argument, NULL, 'y'},
+ {"yang-library-file", required_argument, NULL, 'Y'},
+#ifndef NDEBUG
+ {"debug", required_argument, NULL, 'G'},
+#endif
+ {NULL, 0, NULL, 0}
+ };
+ uint8_t data_type_set = 0;
+
+ c->ctx_options = YL_DEFAULT_CTX_OPTIONS;
+ c->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS;
+ c->line_length = 0;
+
+ opterr = 0;
+#ifndef NDEBUG
+ while ((opt = getopt_long(argc, argv, "hvVQf:p:DF:iP:qs:net:d:lL:o:O:R:myY:x:G:", options, &opt_index)) != -1)
+#else
+ while ((opt = getopt_long(argc, argv, "hvVQf:p:DF:iP:qs:net:d:lL:o:O:R:myY:x:", options, &opt_index)) != -1)
+#endif
+ {
+ switch (opt) {
+ case 'h': /* --help */
+ help(0);
+ return 1;
+
+ case 'v': /* --version */
+ version();
+ return 1;
+
+ case 'V': { /* --verbose */
+ LY_LOG_LEVEL verbosity = ly_log_level(LY_LLERR);
+
+ if (verbosity < LY_LLDBG) {
+ ++verbosity;
+ }
+ ly_log_level(verbosity);
+ break;
+ } /* case 'V' */
+
+ case 'Q': { /* --quiet */
+ LY_LOG_LEVEL verbosity = ly_log_level(LY_LLERR);
+
+ if (verbosity == LY_LLERR) {
+ /* turn logging off */
+ ly_log_options(LY_LOSTORE_LAST);
+ } else if (verbosity > LY_LLERR) {
+ --verbosity;
+ }
+ ly_log_level(verbosity);
+ break;
+ } /* case 'Q' */
+
+ case 'f': /* --format */
+ if (!strcasecmp(optarg, "yang")) {
+ c->schema_out_format = LYS_OUT_YANG;
+ c->data_out_format = 0;
+ } else if (!strcasecmp(optarg, "yin")) {
+ c->schema_out_format = LYS_OUT_YIN;
+ c->data_out_format = 0;
+ } else if (!strcasecmp(optarg, "info")) {
+ c->schema_out_format = LYS_OUT_YANG_COMPILED;
+ c->data_out_format = 0;
+ } else if (!strcasecmp(optarg, "tree")) {
+ c->schema_out_format = LYS_OUT_TREE;
+ c->data_out_format = 0;
+ } else if (!strcasecmp(optarg, "xml")) {
+ c->schema_out_format = 0;
+ c->data_out_format = LYD_XML;
+ } else if (!strcasecmp(optarg, "json")) {
+ c->schema_out_format = 0;
+ c->data_out_format = LYD_JSON;
+ } else if (!strcasecmp(optarg, "lyb")) {
+ c->schema_out_format = 0;
+ c->data_out_format = LYD_LYB;
+ } else if (!strcasecmp(optarg, "feature-param")) {
+ c->feature_param_format = 1;
+ } else {
+ YLMSG_E("Unknown output format %s\n", optarg);
+ help(1);
+ return -1;
+ }
+ break;
+
+ case 'p': { /* --path */
+ struct stat st;
+
+ if (stat(optarg, &st) == -1) {
+ YLMSG_E("Unable to use search path (%s) - %s.\n", optarg, strerror(errno));
+ return -1;
+ }
+ if (!S_ISDIR(st.st_mode)) {
+ YLMSG_E("Provided search path is not a directory.\n");
+ return -1;
+ }
+
+ if (searchpath_strcat(&c->searchpaths, optarg)) {
+ YLMSG_E("Storing searchpath failed.\n");
+ return -1;
+ }
+
+ break;
+ } /* case 'p' */
+
+ case 'D': /* --disable-search */
+ if (c->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) {
+ YLMSG_W("The -D option specified too many times.\n");
+ }
+ if (c->ctx_options & LY_CTX_DISABLE_SEARCHDIR_CWD) {
+ c->ctx_options &= ~LY_CTX_DISABLE_SEARCHDIR_CWD;
+ c->ctx_options |= LY_CTX_DISABLE_SEARCHDIRS;
+ } else {
+ c->ctx_options |= LY_CTX_DISABLE_SEARCHDIR_CWD;
+ }
+ break;
+
+ case 'F': /* --features */
+ if (parse_features(optarg, &c->schema_features)) {
+ return -1;
+ }
+ break;
+
+ case 'i': /* --make-implemented */
+ if (c->ctx_options & LY_CTX_REF_IMPLEMENTED) {
+ c->ctx_options &= ~LY_CTX_REF_IMPLEMENTED;
+ c->ctx_options |= LY_CTX_ALL_IMPLEMENTED;
+ } else {
+ c->ctx_options |= LY_CTX_REF_IMPLEMENTED;
+ }
+ break;
+
+ case 'P': /* --schema-node */
+ c->schema_node_path = optarg;
+ break;
+
+ case 'q': /* --single-node */
+ c->schema_print_options |= LYS_PRINT_NO_SUBSTMT;
+ break;
+
+ case 's': /* --submodule */
+ c->submodule = optarg;
+ break;
+
+ case 'x': /* --ext-data */
+ c->schema_context_filename = strdup(optarg);
+ break;
+
+ case 'n': /* --not-strict */
+ c->data_parse_options &= ~LYD_PARSE_STRICT;
+ break;
+
+ case 'e': /* --present */
+ c->data_validate_options |= LYD_VALIDATE_PRESENT;
+ break;
+
+ case 't': /* --type */
+ if (data_type_set) {
+ YLMSG_E("The data type (-t) cannot be set multiple times.\n");
+ return -1;
+ }
+
+ if (!strcasecmp(optarg, "config")) {
+ c->data_parse_options |= LYD_PARSE_NO_STATE;
+ c->data_validate_options |= LYD_VALIDATE_NO_STATE;
+ } else if (!strcasecmp(optarg, "get")) {
+ c->data_parse_options |= LYD_PARSE_ONLY;
+ } else if (!strcasecmp(optarg, "getconfig") || !strcasecmp(optarg, "get-config")) {
+ c->data_parse_options |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE;
+ } else if (!strcasecmp(optarg, "edit")) {
+ c->data_parse_options |= LYD_PARSE_ONLY;
+ } else if (!strcasecmp(optarg, "rpc")) {
+ c->data_type = LYD_TYPE_RPC_YANG;
+ } else if (!strcasecmp(optarg, "nc-rpc")) {
+ c->data_type = LYD_TYPE_RPC_NETCONF;
+ } else if (!strcasecmp(optarg, "reply")) {
+ c->data_type = LYD_TYPE_REPLY_YANG;
+ } else if (!strcasecmp(optarg, "nc-reply")) {
+ c->data_type = LYD_TYPE_REPLY_NETCONF;
+ } else if (!strcasecmp(optarg, "notif")) {
+ c->data_type = LYD_TYPE_NOTIF_YANG;
+ } else if (!strcasecmp(optarg, "nc-notif")) {
+ c->data_type = LYD_TYPE_NOTIF_NETCONF;
+ } else if (!strcasecmp(optarg, "data")) {
+ /* default option */
+ } else {
+ YLMSG_E("Unknown data tree type %s\n", optarg);
+ help(1);
+ return -1;
+ }
+
+ data_type_set = 1;
+ break;
+
+ case 'd': /* --default */
+ if (!strcasecmp(optarg, "all")) {
+ c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL;
+ } else if (!strcasecmp(optarg, "all-tagged")) {
+ c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG;
+ } else if (!strcasecmp(optarg, "trim")) {
+ c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM;
+ } else if (!strcasecmp(optarg, "implicit-tagged")) {
+ c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG;
+ } else {
+ YLMSG_E("Unknown default mode %s\n", optarg);
+ help(1);
+ return -1;
+ }
+ break;
+
+ case 'l': /* --list */
+ c->list = 1;
+ break;
+
+ case 'L': /* --tree-line-length */
+ c->line_length = atoi(optarg);
+ break;
+
+ case 'o': /* --output */
+ if (c->out) {
+ YLMSG_E("Only a single output can be specified.\n");
+ return -1;
+ } else {
+ if (ly_out_new_filepath(optarg, &c->out)) {
+ YLMSG_E("Unable open output file %s (%s)\n", optarg, strerror(errno));
+ return -1;
+ }
+ }
+ break;
+
+ case 'O': /* --operational */
+ if (c->data_operational.path) {
+ YLMSG_E("The operational datastore (-O) cannot be set multiple times.\n");
+ return -1;
+ }
+ c->data_operational.path = optarg;
+ break;
+
+ case 'R': /* --reply-rpc */
+ if (c->reply_rpc.path) {
+ YLMSG_E("The PRC of the reply (-R) cannot be set multiple times.\n");
+ return -1;
+ }
+ c->reply_rpc.path = optarg;
+ break;
+
+ case 'm': /* --merge */
+ c->data_merge = 1;
+ break;
+
+ case 'y': /* --yang-library */
+ c->ctx_options &= ~LY_CTX_NO_YANGLIBRARY;
+ break;
+
+ case 'Y': /* --yang-library-file */
+ c->ctx_options &= ~LY_CTX_NO_YANGLIBRARY;
+ c->yang_lib_file = optarg;
+ break;
+
+#ifndef NDEBUG
+ case 'G': { /* --debug */
+ uint32_t dbg_groups = 0;
+ const char *ptr = optarg;
+
+ while (ptr[0]) {
+ if (!strncasecmp(ptr, "dict", sizeof "dict" - 1)) {
+ dbg_groups |= LY_LDGDICT;
+ ptr += sizeof "dict" - 1;
+ } else if (!strncasecmp(ptr, "xpath", sizeof "xpath" - 1)) {
+ dbg_groups |= LY_LDGXPATH;
+ ptr += sizeof "xpath" - 1;
+ } else if (!strncasecmp(ptr, "dep-sets", sizeof "dep-sets" - 1)) {
+ dbg_groups |= LY_LDGDEPSETS;
+ ptr += sizeof "dep-sets" - 1;
+ }
+
+ if (ptr[0]) {
+ if (ptr[0] != ',') {
+ YLMSG_E("Unknown debug group string \"%s\"\n", optarg);
+ return -1;
+ }
+ ++ptr;
+ }
+ }
+ ly_log_dbg_groups(dbg_groups);
+ break;
+ } /* case 'G' */
+#endif
+ default:
+ YLMSG_E("Invalid option or missing argument: -%c\n", optopt);
+ return -1;
+ } /* switch */
+ }
+
+ /* additional checks for the options combinations */
+ if (!c->list && (optind >= argc)) {
+ help(1);
+ YLMSG_E("Missing <schema> to process.\n");
+ return 1;
+ }
+
+ if (c->data_merge) {
+ if (c->data_type || (c->data_parse_options & LYD_PARSE_ONLY)) {
+ /* switch off the option, incompatible input data type */
+ c->data_merge = 0;
+ } else {
+ /* postpone validation after the merge of all the input data */
+ c->data_parse_options |= LYD_PARSE_ONLY;
+ }
+ }
+
+ if (c->data_operational.path && !c->data_type) {
+ YLMSG_E("Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types.\n");
+ c->data_operational.path = NULL;
+ }
+
+ if (c->reply_rpc.path && (c->data_type != LYD_TYPE_REPLY_NETCONF)) {
+ YLMSG_E("Source RPC is needed only for NETCONF Reply input data type.\n");
+ c->data_operational.path = NULL;
+ } else if (!c->reply_rpc.path && (c->data_type == LYD_TYPE_REPLY_NETCONF)) {
+ YLMSG_E("Missing source RPC (-R) for NETCONF Reply input data type.\n");
+ return -1;
+ }
+
+ if ((c->schema_out_format != LYS_OUT_TREE) && c->line_length) {
+ YLMSG_E("--tree-line-length take effect only in case of the tree output format.\n");
+ }
+
+ /* default output stream */
+ if (!c->out) {
+ if (ly_out_new_file(stdout, &c->out)) {
+ YLMSG_E("Unable to set stdout as output.\n");
+ return -1;
+ }
+ }
+
+ if (c->schema_out_format == LYS_OUT_TREE) {
+ /* print tree from lysc_nodes */
+ c->ctx_options |= LY_CTX_SET_PRIV_PARSED;
+ }
+
+ /* process input files provided as standalone command line arguments,
+ * schema modules are parsed and inserted into the context,
+ * data files are just checked and prepared into internal structures for further processing */
+ ret = fill_context_inputs(argc, argv, c);
+ if (ret) {
+ return ret;
+ }
+
+ /* the second batch of checks */
+ if (c->schema_print_options && !c->schema_out_format) {
+ YLMSG_W("Schema printer options specified, but the schema output format is missing.\n");
+ }
+ if (c->schema_parse_options && !c->schema_modules.count) {
+ YLMSG_W("Schema parser options specified, but no schema input file provided.\n");
+ }
+ if (c->data_print_options && !c->data_out_format) {
+ YLMSG_W("data printer options specified, but the data output format is missing.\n");
+ }
+ if (((c->data_parse_options != YL_DEFAULT_DATA_PARSE_OPTIONS) || c->data_type) && !c->data_inputs.count) {
+ YLMSG_W("Data parser options specified, but no data input file provided.\n");
+ }
+
+ if (c->schema_node_path) {
+ c->schema_node = lys_find_path(c->ctx, NULL, c->schema_node_path, 0);
+ if (!c->schema_node) {
+ c->schema_node = lys_find_path(c->ctx, NULL, c->schema_node_path, 1);
+
+ if (!c->schema_node) {
+ YLMSG_E("Invalid schema path.\n");
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int
+main_ni(int argc, char *argv[])
+{
+ int ret = EXIT_SUCCESS, r;
+ struct context c = {0};
+ char *features_output = NULL;
+ struct ly_set set = {0};
+ uint32_t u;
+
+ /* set callback for printing libyang messages */
+ ly_set_log_clb(libyang_verbclb, 1);
+
+ r = fill_context(argc, argv, &c);
+ if (r < 0) {
+ ret = EXIT_FAILURE;
+ }
+ if (r) {
+ goto cleanup;
+ }
+
+ /* do the required job - parse, validate, print */
+
+ if (c.list) {
+ /* print the list of schemas */
+ print_list(c.out, c.ctx, c.data_out_format);
+ } else {
+ if (c.feature_param_format) {
+ for (u = 0; u < c.schema_modules.count; u++) {
+ if (collect_features(c.schema_modules.objs[u], &set)) {
+ YLMSG_E("Unable to read features from a module.\n");
+ goto cleanup;
+ }
+ if (generate_features_output(c.schema_modules.objs[u], &set, &features_output)) {
+ YLMSG_E("Unable to generate feature command output.\n");
+ goto cleanup;
+ }
+ ly_set_erase(&set, NULL);
+ }
+ ly_print(c.out, "%s\n", features_output);
+ } else if (c.schema_out_format) {
+ if (c.schema_node) {
+ ret = lys_print_node(c.out, c.schema_node, c.schema_out_format, 0, c.schema_print_options);
+ if (ret) {
+ YLMSG_E("Unable to print schema node %s.\n", c.schema_node_path);
+ goto cleanup;
+ }
+ } else if (c.submodule) {
+ const struct lysp_submodule *submod = ly_ctx_get_submodule_latest(c.ctx, c.submodule);
+
+ if (!submod) {
+ YLMSG_E("Unable to find submodule %s.\n", c.submodule);
+ goto cleanup;
+ }
+
+ ret = lys_print_submodule(c.out, submod, c.schema_out_format, c.line_length, c.schema_print_options);
+ if (ret) {
+ YLMSG_E("Unable to print submodule %s.\n", submod->name);
+ goto cleanup;
+ }
+ } else {
+ for (u = 0; u < c.schema_modules.count; ++u) {
+ ret = lys_print_module(c.out, (struct lys_module *)c.schema_modules.objs[u], c.schema_out_format,
+ c.line_length, c.schema_print_options);
+ /* for YANG Tree Diagrams printing it's more readable to print a blank line between modules. */
+ if ((c.schema_out_format == LYS_OUT_TREE) && (u + 1 < c.schema_modules.count)) {
+ ly_print(c.out, "\n");
+ }
+ if (ret) {
+ YLMSG_E("Unable to print module %s.\n", ((struct lys_module *)c.schema_modules.objs[u])->name);
+ goto cleanup;
+ }
+ }
+ }
+ }
+
+ /* do the data validation despite the schema was printed */
+ if (c.data_inputs.size) {
+ ret = process_data(c.ctx, c.data_type, c.data_merge, c.data_out_format, c.out, c.data_parse_options,
+ c.data_validate_options, c.data_print_options, &c.data_operational, &c.reply_rpc, &c.data_inputs, NULL);
+ if (ret) {
+ goto cleanup;
+ }
+ }
+ }
+
+cleanup:
+ /* cleanup */
+ erase_context(&c);
+ free(features_output);
+ ly_set_erase(&set, NULL);
+
+ return ret;
+}
diff --git a/tools/lint/main_ni_only.c b/tools/lint/main_ni_only.c
new file mode 100644
index 0000000..d55f2c2
--- /dev/null
+++ b/tools/lint/main_ni_only.c
@@ -0,0 +1,22 @@
+/**
+ * @file main_ni_only.c
+ * @brief non-interactive implementation of main() for those platforms without the linenoise library
+ *
+ * Copyright (c) 2015-2021 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
+ */
+
+int main_ni(int argc, char *argv[]);
+
+int done; /* for cmd.c */
+
+int
+main(int argc, char *argv[])
+{
+ return main_ni(argc, argv);
+}
diff --git a/tools/lint/tests/expect/common.exp b/tools/lint/tests/expect/common.exp
new file mode 100644
index 0000000..0381e6c
--- /dev/null
+++ b/tools/lint/tests/expect/common.exp
@@ -0,0 +1,68 @@
+# detect the path to the yanglint binary
+if { [info exists ::env(YANGLINT)] } {
+ set yanglint "$env(YANGLINT)"
+} else {
+ set yanglint "../../../../build/yanglint"
+}
+
+# set the timeout to 1 second
+set timeout 1
+
+# expect a single line of anchored regex output
+proc expect_output {output} {
+ expect {
+ -re "^${output}$" {}
+ timeout {exit 1}
+ }
+}
+
+# send a command and either expect some anchored regex output if specified or just an empty line
+proc expect_command {command has_output output} {
+ send -- "${command}\r"
+
+ if ($has_output==1) {
+ expect {
+ -re "^${command}\r\n${output}$" {}
+ timeout {exit 1}
+ }
+ } else {
+ # input echoes
+ expect {
+ -re "^${command}\r\n$" {}
+ timeout {exit 1}
+ }
+ expect {
+ -re "^> $" {}
+ timeout {exit 1}
+ }
+ }
+}
+
+# send a completion request and check if the anchored regex output matches
+proc expect_completion {input output} {
+ send -- "${input}\t"
+
+ expect {
+ # expecting echoing input, output and 10 terminal control characters
+ -re "^${input}\r> ${output}.*\r.*$" {}
+ timeout {exit 1}
+ }
+}
+
+# send a completion request and check if the anchored regex hint options match
+proc expect_hint {input prev_input hints} {
+ set output {}
+ foreach i $hints {
+ # each element might have some number of spaces and CRLF around it
+ append output "${i} *(?:\\r\\n)?"
+ }
+
+ send -- "${input}\t"
+
+ expect {
+ # expecting the hints, previous input from which the hints were generated
+ # and some number of terminal control characters
+ -re "^\r\n${output}\r> ${prev_input}.*\r.*$" {}
+ timeout {exit 1}
+ }
+}
diff --git a/tools/lint/tests/expect/completion.exp b/tools/lint/tests/expect/completion.exp
new file mode 100755
index 0000000..ed4f6bd
--- /dev/null
+++ b/tools/lint/tests/expect/completion.exp
@@ -0,0 +1,60 @@
+#!/usr/bin/expect -f
+
+if { [info exists ::env(CURRENT_SOURCE_DIR)] } {
+ source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp"
+ set yang_dir "$env(CURRENT_SOURCE_DIR)/examples"
+} else {
+ source "common.exp"
+ set yang_dir "../../examples"
+}
+
+spawn $yanglint
+
+# skip no dir and/or no history warnings
+expect_output "(YANGLINT.*)*> "
+
+expect_command "clear -ii" 0 ""
+
+expect_command "add ${yang_dir}/ietf-ip.yang" 0 ""
+
+expect_completion "print -f info -P " "print -f info -P /ietf-"
+
+set hints {"/ietf-yang-schema-mount:schema-mounts" "/ietf-interfaces:interfaces" "/ietf-interfaces:interfaces-state"}
+
+expect_hint "" "print -f info -P /ietf-" $hints
+
+expect_completion "i" "print -f info -P /ietf-interfaces:interfaces"
+
+expect_completion "/" "print -f info -P /ietf-interfaces:interfaces/interface"
+
+set hints {"/ietf-interfaces:interfaces/interface"
+"/ietf-interfaces:interfaces/interface/name" "/ietf-interfaces:interfaces/interface/description"
+"/ietf-interfaces:interfaces/interface/type" "/ietf-interfaces:interfaces/interface/enabled"
+"/ietf-interfaces:interfaces/interface/link-up-down-trap-enable"
+"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6"}
+
+expect_hint "" "print -f info -P /ietf-interfaces:interfaces/interface" $hints
+
+expect_completion "/i" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv"
+
+expect_completion "4" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4"
+
+set hints { "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled"
+"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/forwarding" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/mtu"
+"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/address" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/neighbor"
+}
+
+expect_hint "\t" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv" $hints
+
+expect_completion "/e" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled "
+
+send -- "\r"
+
+expect {
+ -re ".*\r\n> " {}
+ timeout {exit 1}
+}
+
+send -- "exit\r"
+
+expect eof
diff --git a/tools/lint/tests/expect/feature.exp b/tools/lint/tests/expect/feature.exp
new file mode 100755
index 0000000..37680b0
--- /dev/null
+++ b/tools/lint/tests/expect/feature.exp
@@ -0,0 +1,20 @@
+#!/usr/bin/expect -f
+
+if { [info exists ::env(CURRENT_SOURCE_DIR)] } {
+ source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp"
+ set yang_dir "$env(CURRENT_SOURCE_DIR)/examples"
+} else {
+ source "common.exp"
+ set yang_dir "../../examples"
+}
+
+spawn $yanglint
+
+# skip no dir and/or no history warnings
+expect_output "(YANGLINT.*)*> "
+
+expect_command "feature -a" 1 "yang:\r\n\t\\(none\\)\r\n\r\nietf-yang-schema-mount:\r\n\t\\(none\\)\r\n\r\n> "
+
+send -- "exit\r"
+
+expect eof
diff --git a/tools/lint/tests/expect/list.exp b/tools/lint/tests/expect/list.exp
new file mode 100755
index 0000000..ec3cdba
--- /dev/null
+++ b/tools/lint/tests/expect/list.exp
@@ -0,0 +1,20 @@
+#!/usr/bin/expect -f
+
+if { [info exists ::env(CURRENT_SOURCE_DIR)] } {
+ source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp"
+ set yang_dir "$env(CURRENT_SOURCE_DIR)/examples"
+} else {
+ source "common.exp"
+ set yang_dir "../../examples"
+}
+
+spawn $yanglint
+
+# skip no dir and/or no history warnings
+expect_output "(YANGLINT.*)*> "
+
+expect_command "list" 1 "List of the loaded models:\r\n *i ietf-yang-metadata@2016-08-05\r\n *I yang@2022-06-16\r\n *i ietf-inet-types@2013-07-15\r\n *i ietf-yang-types@2013-07-15\r\n *I ietf-yang-schema-mount@2019-01-14\r\n *i ietf-yang-structure-ext@2020-06-17\r\n> "
+
+send -- "exit\r"
+
+expect eof
diff --git a/tools/lint/tests/shunit2/feature.sh b/tools/lint/tests/shunit2/feature.sh
new file mode 100755
index 0000000..fb2ee88
--- /dev/null
+++ b/tools/lint/tests/shunit2/feature.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+testFeature() {
+ models=( "iana-if-type@2014-05-08.yang" "ietf-netconf@2011-06-01.yang" "ietf-netconf-with-defaults@2011-06-01.yang"
+ "sm.yang" "ietf-interfaces@2014-05-08.yang" "ietf-netconf-acm@2018-02-14.yang" "ietf-origin@2018-02-14.yang"
+ "ietf-ip@2014-06-16.yang" "ietf-restconf@2017-01-26.yang" )
+ features=( " -F iana-if-type:"
+ " -F ietf-netconf:writable-running,candidate,confirmed-commit,rollback-on-error,validate,startup,url,xpath"
+ " -F ietf-netconf-with-defaults:" " -F sm:" " -F ietf-interfaces:arbitrary-names,pre-provisioning,if-mib"
+ " -F ietf-netconf-acm:" " -F ietf-origin:" " -F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf"
+ " -F ietf-restconf:" )
+
+ for i in ${!models[@]}; do
+ output=`${YANGLINT} -f feature-param ${YANG_MODULES_DIR}/${models[$i]}`
+ assertEquals "Unexpected features of module ${models[$i]}." "${features[$i]}" "${output}"
+ done
+}
+
+. shunit2
diff --git a/tools/lint/tests/shunit2/list.sh b/tools/lint/tests/shunit2/list.sh
new file mode 100755
index 0000000..d64503a
--- /dev/null
+++ b/tools/lint/tests/shunit2/list.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+LIST_BASE="List of the loaded models:
+ i ietf-yang-metadata@2016-08-05
+ I yang@2022-06-16
+ i ietf-inet-types@2013-07-15
+ i ietf-yang-types@2013-07-15
+ I ietf-yang-schema-mount@2019-01-14
+ i ietf-yang-structure-ext@2020-06-17"
+
+testListEmptyContext() {
+ output=`${YANGLINT} -l`
+ assertEquals "Unexpected list of modules in empty context." "${LIST_BASE}" "${output}"
+}
+
+testListAllImplemented() {
+ LIST_BASE_ALLIMPLEMENTED="List of the loaded models:
+ I ietf-yang-metadata@2016-08-05
+ I yang@2022-06-16
+ I ietf-inet-types@2013-07-15
+ I ietf-yang-types@2013-07-15
+ I ietf-yang-schema-mount@2019-01-14
+ I ietf-yang-structure-ext@2020-06-17"
+ output=`${YANGLINT} -lii`
+ assertEquals "Unexpected list of modules in empty context with -ii." "${LIST_BASE_ALLIMPLEMENTED}" "${output}"
+}
+
+. shunit2
diff --git a/tools/lint/yanglint.1 b/tools/lint/yanglint.1
new file mode 100644
index 0000000..4b7060d
--- /dev/null
+++ b/tools/lint/yanglint.1
@@ -0,0 +1,136 @@
+.\" Manpage for yanglint.
+.\" Process this file with
+.\" groff -man -Tascii yanglint.1
+.\"
+
+.TH YANGLINT 1 "2016-10-27" "libyang"
+.SH NAME
+yanglint \- YANG lint tool
+.
+.SH SYNOPSIS
+.B yanglint
+.br
+.B yanglint
+[\fIOPTIONS\fP]
+[\-f { \fByang\fP | \fByin\fP | \fBtree\fP } ]
+.I FILE ...
+.br
+.B yanglint
+[\fIOPTIONS\fP]
+[\-f { \fBxml\fP | \fBjson\fP } ]
+\fISCHEMA\fP...
+\fIFILE\fP...
+.
+.SH DESCRIPTION
+\fByanglint\fP is a command-line tool for validating and converting YANG
+schemas and the YANG modeled data. For a simple use, it validates the provided
+file and if the output format specified, it converts input data into the output
+format. If started with no argument, \fByanglint\fP opens interactive
+environment where the user is allowed to work with schemas and data in a more
+complex way.
+.
+.SH OPTIONS
+.TP
+.BR "\-h\fR,\fP \-\^\-help"
+Outputs usage help and exits.
+.TP
+.BR "\-v\fR,\fP \-\^\-version"
+Outputs the version number and exits.
+.TP
+.BR "\-V\fR,\fP \-\^\-verbose"
+Increases the verbosity level. If not specified, only errors are printed, with
+each appearance it adds: warnings, verbose messages, debug messages (if compiled
+with debug information).
+.TP
+.BR "\-p \fIPATH\fP\fR,\fP \-\^\-path=\fIPATH\fP"
+Specifies search path for getting imported modules or included submodules. The option
+can be used multiple times. The current working directory and path of the module
+being added is used implicitly.
+.TP
+.BR "\-s\fR,\fP \-\^\-strict"
+Changes handling of unknown data nodes - instead of silently ignoring unknown data,
+error is printed and data parsing fails. This option applies only on data parsing.
+.TP
+.BR "\-f \fIFORMAT\fP\fR,\fP \-\^\-format=\fIFORMAT\fP"
+Converts the content of the input \fIFILE\fPs into the specified \fIFORMAT\fP. If no
+\fIOUTFILE\fP is specified, the data are printed on the standard output. Only the
+compatible formats for the input \fIFILE\fPs are allowed, see the section \fBFORMATS\fP.
+.TP
+.BR "\-o \fIOUTFILE\fP\fR,\fP \-\^\-output=\fIOUTFILE\fP"
+Writes the output data into the specified \fIOUTFILE\fP. The option can be used
+only in combination with \fB--format\fR option. In case of converting schema, only
+a single input schema \fIFILE\fP is allowed. In case of data input \fIFILE\fPs,
+input is merged and printed into a single \fIOUTFILE\fP.
+.TP
+.BR "\-F \fIFEATURES\fP\fR,\fP \-\^\-features=\fIFEATURES\fP"
+Specifies the list of enabled features in the format
+\fIMODULE\fP:[\fIFEATURE\fP,...]. In case of processing multiple modules, the
+option can be used repeatedly. To disable all the features, use an empty list
+specified for the particular module.
+.TP
+.BR "\-d \fIMODE\fP\fR,\fP \-\^\-default=\fIMODE\fP"
+Print data with default values, according to the \fIMODE\fP (to print attributes,
+the ietf-netconf-with-defaults model must be loaded). The \fIMODE\fP is one of the following:
+ \[bu] \fBall\fP - add missing default nodes
+ \[bu] \fBall-tagged\fP - add missing default nodes and mark all the default nodes with the attribute
+ \[bu] \fBtrim\fP - remove all nodes with a default value
+ \[bu] \fBimplicit-tagged\fP - add missing nodes and mark them with the attribute
+.TP
+.BR "\-t \fITYPE\fP\fR,\fP \-\^\-type=\fITYPE\fP"
+Specify data tree type in the input data \fIFILE\fPs. The \fITYPE\fP is one of the following:
+ \[bu] \fBauto\fP - Resolve data type (one of the following) automatically (as pyang does). Applicable only on XML input data.
+ \[bu] \fBdata\fP - Complete datastore with status data (default type).
+ \[bu] \fBconfig\fP - Configuration datastore (without status data).
+ \[bu] \fBget\fP - Result of the NETCONF <get> operation.
+ \[bu] \fBgetconfig\fP - Result of the NETCONF <get-config> operation.
+ \[bu] \fBedit\fP - Content of the NETCONF <edit-config> operation.
+ \[bu] \fBrpc\fP - Content of the NETCONF <rpc> message, defined as YANG's rpc input statement.
+ \[bu] \fBrpcreply\fP - Reply to the RPC. This is just a virtual \fITYPE\fP, for parsing replies, '\fBauto\fP' must be used since the data \fIFILE\fPs are expected in pairs.
+.br
+ The first input data \fIFILE\fP is expected as '\fBrpc\fP' \fITYPE\fP, the second \fIFILE\fP is expected as reply to the previous RPC.
+ \[bu] \fBnotif\fP - Notification instance (content of the <notification> element without <eventTime>.
+.TP
+.BR "\-O \fIFILE\fP\fR,\fP \-\^\-operational=\fIFILE\fP]
+Optional parameter for '\fBrpc\fP' and '\fBnotif\fP' \fITYPE\fPs, the \fIFILE\fP contains running configuration datastore and
+state data referenced from the RPC/Notification. The same data apply to all input data \fIFILE\fPs. Note that the file
+is validated as '\fBdata\fP' \fITYPE\fP. Special value '\fB!\fP' can be used as \fIFILE\fP argument to ignore the external references.
+.TP
+.BR "\-y \fIYANGLIB_PATH\fP"
+Specify path to a yang-library data file (XML or JSON) describing the initial context.
+If provided, yanglint loads the modules according to the content of the yang-library data tree.
+Otherwise, an empty content with only the internal libyang modules is used. This does
+not limit user to load another modules explicitly specified as command line parameters.
+.
+.SH FORMATS
+There are two types of formats to use.
+.TP
+.I Schemas
+In case of schemas, the content can be converted into the '\fByang\fP', '\fByin\fP'
+and '\fBtree\fP' formats. As input, only YANG and YIN files are
+accepted. Note, that the corresponding file extension is required.
+.TP
+.I Data\ \ \
+In case of YANG modeled data, the content can be converted between '\fBxml\fP'
+and '\fBjson\fP' formats. Remember that the corresponding file extension of the
+input file is required.
+.
+
+.SH EXAMPLES
+.IP \[bu] 2
+Open interactive environment:
+ yanglint
+.IP \[bu]
+Convert YANG model into YIN and print it to the stdout:
+ yanglint --format=yin ./ietf-system.yang
+.IP \[bu]
+Convert ietf-system configuration data from XML to JSON:
+ yanglint --format=json --type=config --output=data.json ./ietf-system.yang ./data.xml
+
+.SH SEE ALSO
+https://github.com/CESNET/libyang (libyang homepage and Git repository)
+.
+.SH AUTHORS
+Radek Krejci <rkrejci@cesnet.cz>, Michal Vasko <mvasko@cesnet.cz>
+.
+.SH COPYRIGHT
+Copyright \(co 2015-2017 CESNET, a.l.e.
diff --git a/tools/re/CMakeLists.txt b/tools/re/CMakeLists.txt
new file mode 100644
index 0000000..7b6de22
--- /dev/null
+++ b/tools/re/CMakeLists.txt
@@ -0,0 +1,20 @@
+# yangre
+
+set(resrc
+ main.c)
+
+set(format_sources
+ ${format_sources}
+ ${CMAKE_CURRENT_SOURCE_DIR}/*.c
+ PARENT_SCOPE)
+
+add_executable(yangre ${resrc} ${compatsrc})
+target_link_libraries(yangre yang)
+install(TARGETS yangre DESTINATION ${CMAKE_INSTALL_BINDIR})
+install(FILES ${PROJECT_SOURCE_DIR}/tools/re/yangre.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
+target_include_directories(yangre BEFORE PRIVATE ${PROJECT_BINARY_DIR})
+
+if(WIN32)
+ target_include_directories(yangre PRIVATE ${GETOPT_INCLUDE_DIR})
+ target_link_libraries(yangre ${GETOPT_LIBRARY})
+endif()
diff --git a/tools/re/main.c b/tools/re/main.c
new file mode 100644
index 0000000..2292b2a
--- /dev/null
+++ b/tools/re/main.c
@@ -0,0 +1,309 @@
+/**
+ * @file main.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief libyang's YANG Regular Expression tool
+ *
+ * Copyright (c) 2017 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
+ */
+
+#define _GNU_SOURCE /* asprintf, strdup */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "libyang.h"
+
+#include "compat.h"
+#include "tools/config.h"
+
+void
+help(void)
+{
+ fprintf(stdout, "YANG Regular Expressions processor.\n");
+ fprintf(stdout, "Usage:\n");
+ fprintf(stdout, " yangre [-hv]\n");
+ fprintf(stdout, " yangre [-V] -p <regexp1> [-i] [-p <regexp2> [-i] ...] <string>\n");
+ fprintf(stdout, " yangre [-V] -f <file>\n");
+ fprintf(stdout, "Returns 0 if string matches the pattern(s), 1 if not and -1 on error.\n\n");
+ fprintf(stdout, "Options:\n"
+ " -h, --help Show this help message and exit.\n"
+ " -v, --version Show version number and exit.\n"
+ " -V, --verbose Print the processing information.\n"
+ " -i, --invert-match Invert-match modifier for the closest preceding\n"
+ " pattern.\n"
+ " -p, --pattern=\"REGEXP\" Regular expression including the quoting,\n"
+ " which is applied the same way as in a YANG module.\n"
+ " -f, --file=\"FILE\" List of patterns and the <string> (separated by an\n"
+ " empty line) are taken from <file>. Invert-match is\n"
+ " indicated by the single space character at the \n"
+ " beginning of the pattern line. YANG quotation around\n"
+ " patterns is still expected, but that avoids issues with\n"
+ " reading quotation by shell. Avoid newline at the end\n"
+ " of the string line to represent empty <string>.");
+ fprintf(stdout, "Examples:\n"
+ " pattern \"[0-9a-fA-F]*\"; -> yangre -p '\"[0-9a-fA-F]*\"' '1F'\n"
+ " pattern '[a-zA-Z0-9\\-_.]*'; -> yangre -p \"'[a-zA-Z0-9\\-_.]*'\" 'a-b'\n"
+ " pattern [xX][mM][lL].*; -> yangre -p '[xX][mM][lL].*' 'xml-encoding'\n\n");
+ fprintf(stdout, "Note that to pass YANG quoting through your shell, you are supposed to use\n"
+ "the other quotation around. For not-quoted patterns, use single quotes.\n\n");
+}
+
+void
+version(void)
+{
+ fprintf(stdout, "yangre %s\n", PROJECT_VERSION);
+}
+
+void
+pattern_error(LY_LOG_LEVEL level, const char *msg, const char *path)
+{
+ (void) path; /* unused */
+
+ if (level == LY_LLERR) {
+ fprintf(stderr, "yangre error: %s\n", msg);
+ }
+}
+
+static const char *module_start = "module yangre {"
+ "yang-version 1.1;"
+ "namespace urn:cesnet:libyang:yangre;"
+ "prefix re;"
+ "leaf pattern {"
+ " type string {";
+static const char *module_invertmatch = " { modifier invert-match; }";
+static const char *module_match = ";";
+static const char *module_end = "}}}";
+
+static int
+add_pattern(char ***patterns, int **inverts, int *counter, char *pattern)
+{
+ void *reallocated1, *reallocated2;
+
+ (*counter)++;
+ reallocated1 = realloc(*patterns, *counter * sizeof **patterns);
+ reallocated2 = realloc(*inverts, *counter * sizeof **inverts);
+ if (!reallocated1 || !reallocated2) {
+ fprintf(stderr, "yangre error: memory allocation error.\n");
+ free(reallocated1);
+ free(reallocated2);
+ return EXIT_FAILURE;
+ }
+ (*patterns) = reallocated1;
+ (*patterns)[*counter - 1] = strdup(pattern);
+ (*inverts) = reallocated2;
+ (*inverts)[*counter - 1] = 0;
+
+ return EXIT_SUCCESS;
+}
+
+int
+main(int argc, char *argv[])
+{
+ LY_ERR match;
+ int i, opt_index = 0, ret = -1, verbose = 0, blankline = 0;
+ struct option options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"file", required_argument, NULL, 'f'},
+ {"invert-match", no_argument, NULL, 'i'},
+ {"pattern", required_argument, NULL, 'p'},
+ {"version", no_argument, NULL, 'v'},
+ {"verbose", no_argument, NULL, 'V'},
+ {NULL, 0, NULL, 0}
+ };
+ char **patterns = NULL, *str = NULL, *modstr = NULL, *s;
+ int *invert_match = NULL;
+ int patterns_count = 0;
+ struct ly_ctx *ctx = NULL;
+ struct lys_module *mod;
+ FILE *infile = NULL;
+ size_t len = 0;
+ ssize_t l;
+
+ opterr = 0;
+ while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) {
+ switch (i) {
+ case 'h':
+ help();
+ ret = -2; /* continue to allow printing version and help at once */
+ break;
+ case 'f':
+ if (infile) {
+ help();
+ fprintf(stderr, "yangre error: multiple input files are not supported.\n");
+ goto cleanup;
+ } else if (patterns_count) {
+ help();
+ fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
+ goto cleanup;
+ }
+ infile = fopen(optarg, "rb");
+ if (!infile) {
+ fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
+ goto cleanup;
+ }
+
+ while ((l = getline(&str, &len, infile)) != -1) {
+ if (!blankline && (str[0] == '\n')) {
+ /* blank line */
+ blankline = 1;
+ continue;
+ }
+ if ((str[0] != '\n') && (str[l - 1] == '\n')) {
+ /* remove ending newline */
+ str[l - 1] = '\0';
+ }
+ if (blankline) {
+ /* done - str is now the string to check */
+ blankline = 0;
+ break;
+ /* else read the patterns */
+ } else if (add_pattern(&patterns, &invert_match, &patterns_count,
+ (str[0] == ' ') ? &str[1] : str)) {
+ goto cleanup;
+ }
+ if (str[0] == ' ') {
+ /* set invert-match */
+ invert_match[patterns_count - 1] = 1;
+ }
+ }
+ if (blankline) {
+ /* corner case, no input after blankline meaning the pattern to check is empty */
+ if (str != NULL) {
+ free(str);
+ }
+ str = malloc(sizeof(char));
+ str[0] = '\0';
+ }
+ break;
+ case 'i':
+ if (!patterns_count || invert_match[patterns_count - 1]) {
+ help();
+ fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n");
+ goto cleanup;
+ }
+ invert_match[patterns_count - 1] = 1;
+ break;
+ case 'p':
+ if (infile) {
+ help();
+ fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
+ goto cleanup;
+ }
+ if (add_pattern(&patterns, &invert_match, &patterns_count, optarg)) {
+ goto cleanup;
+ }
+ break;
+ case 'v':
+ version();
+ ret = -2; /* continue to allow printing version and help at once */
+ break;
+ case 'V':
+ verbose = 1;
+ break;
+ default:
+ help();
+ if (optopt) {
+ fprintf(stderr, "yangre error: invalid option: -%c\n", optopt);
+ } else {
+ fprintf(stderr, "yangre error: invalid option: %s\n", argv[optind - 1]);
+ }
+ goto cleanup;
+ }
+ }
+
+ if (ret == -2) {
+ goto cleanup;
+ }
+
+ if (!str) {
+ /* check options compatibility */
+ if (optind >= argc) {
+ help();
+ fprintf(stderr, "yangre error: missing <string> parameter to process.\n");
+ goto cleanup;
+ } else if (!patterns_count) {
+ help();
+ fprintf(stderr, "yangre error: missing pattern parameter to use.\n");
+ goto cleanup;
+ }
+ str = argv[optind];
+ }
+
+ for (modstr = (char *)module_start, i = 0; i < patterns_count; i++) {
+ if (asprintf(&s, "%s pattern %s%s", modstr, patterns[i], invert_match[i] ? module_invertmatch : module_match) == -1) {
+ fprintf(stderr, "yangre error: memory allocation failed.\n");
+ goto cleanup;
+ }
+ if (modstr != module_start) {
+ free(modstr);
+ }
+ modstr = s;
+ }
+ if (asprintf(&s, "%s%s", modstr, module_end) == -1) {
+ fprintf(stderr, "yangre error: memory allocation failed.\n");
+ goto cleanup;
+ }
+ if (modstr != module_start) {
+ free(modstr);
+ }
+ modstr = s;
+
+ if (ly_ctx_new(NULL, 0, &ctx)) {
+ goto cleanup;
+ }
+
+ ly_set_log_clb(pattern_error, 0);
+ if (lys_parse_mem(ctx, modstr, LYS_IN_YANG, &mod) || !mod->compiled || !mod->compiled->data) {
+ goto cleanup;
+ }
+
+ /* check the value */
+ match = lyd_value_validate(ctx, mod->compiled->data, str, strlen(str), NULL, NULL, NULL);
+
+ if (verbose) {
+ for (i = 0; i < patterns_count; i++) {
+ fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i]);
+ fprintf(stdout, "matching %d: %s\n", i + 1, invert_match[i] ? "inverted" : "regular");
+ }
+ fprintf(stdout, "string : %s\n", str);
+ if (match == LY_SUCCESS) {
+ fprintf(stdout, "result : matching\n");
+ } else if (match == LY_EVALID) {
+ fprintf(stdout, "result : not matching\n");
+ } else {
+ fprintf(stdout, "result : error (%s)\n", ly_errmsg(ctx));
+ }
+ }
+ if (match == LY_SUCCESS) {
+ ret = 0;
+ } else if (match == LY_EVALID) {
+ ret = 1;
+ } else {
+ ret = -1;
+ }
+
+cleanup:
+ ly_ctx_destroy(ctx);
+ for (i = 0; i < patterns_count; i++) {
+ free(patterns[i]);
+ }
+ free(patterns);
+ free(invert_match);
+ free(modstr);
+ if (infile) {
+ fclose(infile);
+ free(str);
+ }
+
+ return ret;
+}
diff --git a/tools/re/yangre.1 b/tools/re/yangre.1
new file mode 100644
index 0000000..e7b572b
--- /dev/null
+++ b/tools/re/yangre.1
@@ -0,0 +1,118 @@
+.\" Manpage for yanglint.
+.\" Process this file with
+.\" groff -man -Tascii yangre.1
+.\"
+
+.TH YANGRE 1 "2018-11-09" "libyang"
+.SH NAME
+yangre \- YANG regular expression processor
+.
+.SH SYNOPSIS
+.B yangre
+[\-V] \-p \fIREGEXP\fP [\-i] [\-p \fIREGEXP\fP [\-i]...] \fISTRING\fP
+.br
+.B yangre
+[\-V] \-f \fIFILE\fP
+.
+.SH DESCRIPTION
+\fByangre\fP is a command-line tool to test and evaluate regular expressions
+for use in YANG schemas. Supported regular expressions are defined by the
+W3C's XML-Schema standard.
+
+\fByangre\fP can be used either with regular expressions and a target string
+on the command line or with input from a file. The latter is particularly
+useful to avoid dealing with proper shell escaping of regular expression
+patterns, which can be somewhat tricky.
+.
+.SH GENERAL OPTIONS
+.TP
+.BR "\-h\fR,\fP \-\^\-help"
+.br
+Outputs usage help and exits.
+.TP
+.BR "\-v\fR,\fP \-\^\-version"
+.br
+Outputs the version number and exits.
+.TP
+.BR "\-V\fR,\fP \-\^\-verbose"
+Increases the verbosity level. If not specified, only errors are printed, with
+each appearance it adds: warnings, verbose messages, debug messages (if compiled
+with debug information).
+.SH COMMAND LINE INPUT
+.TP
+.BR "\-p \fIREGEXP\fP\fR,\fP \-\^\-pattern=\fIREGEXP\fP"
+.br
+One or more regular expression patterns to be tested against the input
+string. Supplied expressions are tested in the order they appear on the
+command line. Testing is aborted when an expression does not match (or
+does match, if the \fB-i\fP option is used.)
+.TP
+.BR "\-i\fR,\fP \-\^\-invert-match"
+.br
+Reverse match condition for the previous pattern. If the pattern matches,
+an error is printed and evaluation is aborted.
+.TP
+.BR "\fISTRING\fP"
+.br
+Target text input to match the regular expression(s) against. The same
+text is used for all regular expressions. Note that only the first
+argument is used by \fByangre\fP, if it contains spaces or other shell
+metacharacters they must be properly escaped. Additional arguments are
+silently ignored.
+.SH FILE INPUT
+.TP
+.BR "\-f \fIFILE\fP\fR,\fP \-\^\-file=\fIFILE\fP"
+Read both patterns and target text from the specified input file.
+
+\fIFILE\fP must consist of one or more YANG regular expressions, each on
+their own line, followed by a blank line and one line of target text. No
+preprocessing is done on file input, there are no comment lines and
+whitespace is not stripped. A single space character at the beginning of
+a pattern line inverts the match condition for the pattern on that line.
+Patterns must still be properly quoted as mandated by the YANG standard.
+.SH RETURN VALUES
+.TP
+0
+.I Successful match
+.br
+The target text matched for all patterns.
+.TP
+1
+.I Pattern mismatch
+.br
+One or more patterns did not match the target text. An error message is
+printed to stderr describing which pattern was the first not to match.
+.TP
+255
+.I Other error
+.br
+One or more patterns could not be processed or some other error occurred that
+precluded processing.
+.SH EXAMPLES
+.IP \[bu] 2
+Test a single pattern:
+ yangre -p 'te.*xt' text_text
+.IP \[bu]
+Test multiple patterns:
+ yangre -p '.*pat1' -p 'pat2.*' -p 'notpat' -i pat2testpat1
+.IP \[bu]
+Input from a file:
+ cat > /tmp/patterns <<EOF
+ .*pat1
+ pat2.*
+ notpat
+
+ pat2testpat1
+ EOF
+ yangre -f /tmp/patterns
+
+.SH SEE ALSO
+https://github.com/CESNET/libyang (libyang homepage and Git repository)
+.
+.SH AUTHORS
+Radek Krejci <rkrejci@cesnet.cz>, Michal Vasko <mvasko@cesnet.cz>
+.br
+This man page was written by David Lamparter <equinox@diac24.net>
+.
+.SH COPYRIGHT
+Copyright \(co 2015-2018 CESNET, a.l.e.