diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-07-23 09:41:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-07-23 09:41:28 +0000 |
commit | 76ffd5ce84e4adb412833c8186fc6b26b656947f (patch) | |
tree | 11da7c12c05e9d2a85ec40022d43a970184bb867 /tools | |
parent | Initial commit. (diff) | |
download | libyang3-76ffd5ce84e4adb412833c8186fc6b26b656947f.tar.xz libyang3-76ffd5ce84e4adb412833c8186fc6b26b656947f.zip |
Adding upstream version 3.1.0.upstream/3.1.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools')
73 files changed, 13926 insertions, 0 deletions
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..0e1ee17 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,25 @@ +include(CheckIncludeFile) + +# config file for tools +configure_file(${PROJECT_SOURCE_DIR}/tools/config.h.in ${PROJECT_BINARY_DIR}/tools/config.h @ONLY) + +# find ioctl +check_include_file("sys/ioctl.h" HAVE_IOCTL) +if(NOT HAVE_IOCTL) + message(STATUS "Disabling interactive yanglint, sys/ioctl.h not found...") + set(ENABLE_YANGLINT_INTERACTIVE OFF) +endif() + +# find getopt library on WIN32 +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..059305e --- /dev/null +++ b/tools/lint/CMakeLists.txt @@ -0,0 +1,48 @@ +# yanglint +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 + cmd_extdata.c + cmd_help.c + cmd_verb.c + cmd_debug.c + yl_opt.c + yl_schema_features.c + common.c +) + +if(ENABLE_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() diff --git a/tools/lint/cmd.c b/tools/lint/cmd.c new file mode 100644 index 0000000..8b2ec5d --- /dev/null +++ b/tools/lint/cmd.c @@ -0,0 +1,112 @@ +/** + * @file cmd.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> + * @brief libyang's yanglint tool general commands + * + * Copyright (c) 2015-2023 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; + +void +cmd_free(void) +{ + uint16_t i; + + for (i = 0; commands[i].name; i++) { + if (commands[i].free_func) { + commands[i].free_func(); + } + } +} + +int +cmd_quit_exec(struct ly_ctx **UNUSED(ctx), struct yl_opt *UNUSED(yo), const char *UNUSED(posv)) +{ + done = 1; + return 0; +} + +/* Also keep enum COMMAND_INDEX updated. */ +COMMAND commands[] = { + { + "help", cmd_help_opt, NULL, cmd_help_exec, NULL, cmd_help_help, NULL, + "Display commands description", "h" + }, + { + "add", cmd_add_opt, cmd_add_dep, cmd_add_exec, NULL, cmd_add_help, NULL, + "Add a new module from a specific file", "DF:hiX" + }, + { + "load", cmd_load_opt, cmd_load_dep, cmd_load_exec, NULL, cmd_load_help, NULL, + "Load a new schema from the searchdirs", "F:hiX" + }, + { + "print", cmd_print_opt, cmd_print_dep, cmd_print_exec, NULL, cmd_print_help, NULL, + "Print a module", "f:hL:o:P:q" + }, + { + "data", cmd_data_opt, cmd_data_dep, cmd_data_store, cmd_data_process, cmd_data_help, NULL, + "Load, validate and optionally print instance data", "d:ef:F:hmo:O:R:r:nt:x:" + }, + { + "list", cmd_list_opt, cmd_list_dep, cmd_list_exec, NULL, cmd_list_help, NULL, + "List all the loaded modules", "f:h" + }, + { + "feature", cmd_feature_opt, cmd_feature_dep, cmd_feature_exec, cmd_feature_fin, cmd_feature_help, NULL, + "Print all features of module(s) with their state", "haf" + }, + { + "searchpath", cmd_searchpath_opt, NULL, cmd_searchpath_exec, NULL, cmd_searchpath_help, NULL, + "Print/set the search path(s) for schemas", "ch" + }, + { + "extdata", cmd_extdata_opt, cmd_extdata_dep, cmd_extdata_exec, NULL, cmd_extdata_help, cmd_extdata_free, + "Set the specific data required by an extension", "ch" + }, + { + "clear", cmd_clear_opt, cmd_clear_dep, cmd_clear_exec, NULL, cmd_clear_help, NULL, + "Clear the context - remove all the loaded modules", "iyhY:" + }, + { + "verb", cmd_verb_opt, cmd_verb_dep, cmd_verb_exec, NULL, cmd_verb_help, NULL, + "Change verbosity", "h" + }, + { + "debug", cmd_debug_opt, cmd_debug_dep, cmd_debug_store, cmd_debug_setlog, cmd_debug_help, NULL, +#ifndef NDEBUG + "Display specific debug message groups", "h" +#else + "Unsupported for the Release build", "h" +#endif + }, + {"quit", NULL, NULL, cmd_quit_exec, NULL, NULL, NULL, "Quit the program", "h"}, + /* synonyms for previous commands */ + {"?", NULL, NULL, cmd_help_exec, NULL, NULL, NULL, "Display commands description", "h"}, + {"exit", NULL, NULL, cmd_quit_exec, NULL, NULL, NULL, "Quit the program", "h"}, + {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL} +}; diff --git a/tools/lint/cmd.h b/tools/lint/cmd.h new file mode 100644 index 0000000..e9ee5b4 --- /dev/null +++ b/tools/lint/cmd.h @@ -0,0 +1,394 @@ +/** + * @file cmd.h + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> + * @brief libyang's yanglint tool commands header + * + * Copyright (c) 2015-2023 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" + +struct yl_opt; + +/** + * @brief command information + * + * Callback functions are in the order they should be called. + * First, the 'opt_func' should be called, which parses arguments from the command line and sets flags or pointers in + * the struct yl_opt. This type of function is for interactive mode and is optional. + * Then the 'dep_func' callback can check the struct yl_opt settings. Other items that depend on them can also be + * set. There is also an possibility for controlling the number of positional arguments and its implications. + * The most important callback is 'exec_func' where the command itself is executed. This function can even replace the + * entire libyang context. The function parameters are mainly found in the yl_opt structure. Optionally, the function + * can be called with a positional argument obtained from the command line. Some 'exec_func' are adapted to be called + * from non-interactive yanglint mode. + * The 'fun_func' complements the 'exec_func'. In some cases, the command execution must be divided into two stages. + * For example, the 'exec_func' is used to fill some items in the yl_opt structure from the positional + * arguments and then the 'fin_func' is used to perform the final action. + */ +typedef struct { + /* User printable name of the function. */ + char *name; + + /* Convert command line options to the data struct yl_opt. */ + int (*opt_func)(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + /* Additionally set dependent items and perform error checking. */ + int (*dep_func)(struct yl_opt *yo, int posc); + /* Execute command. */ + int (*exec_func)(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); + /* Finish execution of command. */ + int (*fin_func)(struct ly_ctx *ctx, struct yl_opt *yo); + /* Display command help. */ + void (*help_func)(void); + /* Freeing global variables allocated by the command. */ + void (*free_func)(void); + /* Documentation for this function. */ + char *helpstring; + /* Option characters used in function getopt_long. */ + char *optstring; +} COMMAND; + +/** + * @brief The list of available commands. + */ +extern COMMAND commands[]; + +/** + * @brief Index for global variable ::commands. + */ +enum COMMAND_INDEX { + CMD_HELP = 0, + CMD_ADD, + CMD_LOAD, + CMD_PRINT, + CMD_DATA, + CMD_LIST, + CMD_FEATURE, + CMD_SEARCHPATH, + CMD_EXTDATA, + CMD_CLEAR, + CMD_VERB, + CMD_DEBUG +}; + +/** + * @brief For each cmd, call the COMMAND.free_func in the variable 'commands'. + */ +void cmd_free(void); + +/* cmd_add.c */ + +/** + * @brief Parse the arguments of an interactive command. + * + * @param[out] yo Context for yanglint. + * @param[in] cmdline String containing command line arguments. + * @param[out] posv Pointer to argv to a section of positional arguments. + * @param[out] posc Number of positional arguments. + * @return 0 on success. + */ +int cmd_add_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @brief Check the options and set dependent items in @p yo. + * + * @param[in,out] yo context for yanglint. + * @param[in] posc number of positional arguments. + * @return 0 on success. + */ +int cmd_add_dep(struct yl_opt *yo, int posc); + +/** + * @brief Parse and compile a new module using filepath. + * + * @param[in,out] ctx Context for libyang. + * @param[in,out] yo Context for yanglint. + * @param[in] posv Path to the file where the new module is located. + * @return 0 on success. + */ +int cmd_add_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_add_help(void); + +/* cmd_clear.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_clear_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_clear_dep(struct yl_opt *yo, int posc); + +/** + * @brief Clear libyang context. + * + * @param[in,out] ctx context for libyang that will be replaced with an empty one. + * @param[in,out] yo context for yanglint. + * @param[in] posv not used. + * @return 0 on success. + */ +int cmd_clear_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_clear_help(void); + +/* cmd_data.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_data_dep(struct yl_opt *yo, int posc); + +/** + * @brief Store data file for later processing. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Path to the file where the data is located. + * @return 0 on success. + */ +int cmd_data_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); + +/** + * @brief Parse, validate and optionally print data instances. + * + * @param[in] ctx Context for libyang. + * @param[in] yo Context of yanglint. All necessary parameters should already be set. + * @return 0 on success. + */ +int cmd_data_process(struct ly_ctx *ctx, struct yl_opt *yo); +void cmd_data_help(void); + +/* cmd_list.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_list_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_list_dep(struct yl_opt *yo, int posc); + +/** + * @brief Print the list of modules in the current context. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Not used. + * @return 0 on success. + */ +int cmd_list_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_list_help(void); + +/* cmd_feature.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_feature_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_feature_dep(struct yl_opt *yo, int posc); + +/** + * @brief Print the features the modules. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the module which features are to be printed. + * @return 0 on success. + */ +int cmd_feature_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); + +/** + * @brief Printing of features ends. + * + * @param[in] ctx context for libyang. Not used. + * @param[in] yo context for yanglint. + * @return 0 on success. + */ +int cmd_feature_fin(struct ly_ctx *ctx, struct yl_opt *yo); +void cmd_feature_help(void); + +/* cmd_load.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_load_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_load_dep(struct yl_opt *yo, int posc); + +/** + * @brief Parse and compile a new module using module name. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the module to be loaded into the context. + * @return 0 on success. + */ +int cmd_load_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_load_help(void); + +/* cmd_print.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_print_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_print_dep(struct yl_opt *yo, int posc); + +/** + * @brief Print a schema module. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the module to be printed. Can be NULL in the case of printing a node. + * @return 0 on success. + */ +int cmd_print_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_print_help(void); + +/* cmd_searchpath.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_searchpath_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @brief Set the paths of directories where to search schema modules. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Path to the directory. Can be NULL in the case of printing a current searchdirs. + * @return 0 on success. + */ +int cmd_searchpath_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_searchpath_help(void); + +/* cmd_extdata.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_extdata_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_extdata_dep(struct yl_opt *yo, int posc); + +/** + * @brief Set path to the file required by the extension. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Path to the directory. Can be NULL in the case of printing a current path. + * @return 0 on success. + */ +int cmd_extdata_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_extdata_help(void); +void cmd_extdata_free(void); + +/* cmd_help.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_help_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @brief Print help. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the command which help message is to be printed. Can be NULL. + * @return 0 on success. + */ +int cmd_help_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_help_help(void); + +/* cmd_verb.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_verb_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_verb_dep(struct yl_opt *yo, int posc); + +/** + * @brief Set the verbose level. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the verbose level to be set. + * @return 0 on success. + */ +int cmd_verb_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_verb_help(void); + +/* cmd_debug.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_debug_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_debug_dep(struct yl_opt *yo, int posc); + +/** + * @brief Store the type of debug messages for later processing. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the debug type to be set. + * @return 0 on success. + */ +int cmd_debug_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); + +/** + * @brief Set debug logging. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. All necessary parameters should already be set. + * @return 0 on success. + */ +int cmd_debug_setlog(struct ly_ctx *ctx, struct yl_opt *yo); +void cmd_debug_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..9f10711 --- /dev/null +++ b/tools/lint/cmd_add.c @@ -0,0 +1,210 @@ +/** + * @file cmd_add.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'add' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2023 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 <assert.h> +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "libyang.h" + +#include "common.h" +#include "yl_opt.h" +#include "yl_schema_features.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" + " -X, --extended-leafref\n" + " Allow usage of deref() XPath function within leafref.\n"); +} + +int +cmd_add_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + 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'}, + {"extended-leafref", no_argument, NULL, 'X'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_ADD].optstring, options, &opt_index)) != -1) { + switch (opt) { + case 'D': /* --disable--search */ + if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) { + YLMSG_W("The -D option specified too many times."); + } + yo_opt_update_disable_searchdir(yo); + break; + + case 'F': /* --features */ + if (parse_features(optarg, &yo->schema_features)) { + return 1; + } + break; + + case 'h': + cmd_add_help(); + return 1; + + case 'i': /* --make-implemented */ + yo_opt_update_make_implemented(yo); + break; + + case 'X': /* --extended-leafref */ + yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED; + break; + + default: + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_add_dep(struct yl_opt *yo, int posc) +{ + if (yo->interactive && !posc) { + /* no argument */ + cmd_add_help(); + return 1; + } + if (!yo->schema_features.count) { + /* no features, enable all of them */ + yo->ctx_options |= LY_CTX_ENABLE_IMP_FEATURES; + } + + return 0; +} + +static int +store_parsed_module(const char *filepath, struct lys_module *mod, struct yl_opt *yo) +{ + assert(!yo->interactive); + + if (yo->schema_out_format || yo->feature_param_format) { + if (ly_set_add(&yo->schema_modules, (void *)mod, 1, NULL)) { + YLMSG_E("Storing parsed schema module (%s) for print failed.", filepath); + return 1; + } + } + + return 0; +} + +int +cmd_add_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + const char *all_features[] = {"*", NULL}; + LY_ERR ret; + uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */ + char *dir, *modname = NULL; + const char **features = NULL; + struct ly_in *in = NULL; + struct lys_module *mod; + + assert(posv); + + if (yo->ctx_options) { + ly_ctx_set_options(*ctx, yo->ctx_options); + } + + if (parse_schema_path(posv, &dir, &modname)) { + return 1; + } + + if (!yo->interactive) { + /* The module should already be parsed if yang_lib_file was set. */ + if (yo->yang_lib_file && (mod = ly_ctx_get_module_implemented(*ctx, modname))) { + ret = store_parsed_module(posv, mod, yo); + goto cleanup; + } + /* parse module */ + } + + /* add temporarily also the path of the module itself */ + if (ly_ctx_set_searchdir(*ctx, dir) == LY_EEXIST) { + path_unset = 0; + } + + /* get features list for this module */ + if (!yo->schema_features.count) { + features = all_features; + } else { + get_features(&yo->schema_features, modname, &features); + } + + /* prepare input handler */ + ret = ly_in_new_filepath(posv, 0, &in); + if (ret) { + goto cleanup; + } + + /* parse the file */ + ret = lys_parse(*ctx, in, LYS_IN_UNKNOWN, features, &mod); + ly_in_free(in, 1); + ly_ctx_unset_searchdir_last(*ctx, path_unset); + if (ret) { + goto cleanup; + } + + if (!yo->interactive) { + if ((ret = store_parsed_module(posv, mod, yo))) { + goto cleanup; + } + } + +cleanup: + free(dir); + free(modname); + + return ret; +} diff --git a/tools/lint/cmd_clear.c b/tools/lint/cmd_clear.c new file mode 100644 index 0000000..4a869af --- /dev/null +++ b/tools/lint/cmd_clear.c @@ -0,0 +1,172 @@ +/** + * @file cmd_clear.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'clear' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2023 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" +#include "yl_opt.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" + " When loading a module into the context, the imported 'referenced'\n" + " modules will also be implemented. If specified a second time,\n" + " all the modules will be 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" + " -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. Searchpaths defined so far\n" + " are used, but then deleted.\n"); +} + +int +cmd_clear_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"make-implemented", no_argument, NULL, 'i'}, + {"yang-library", no_argument, NULL, 'y'}, + {"yang-library-file", no_argument, NULL, 'Y'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + yo->ctx_options = LY_CTX_NO_YANGLIBRARY; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_CLEAR].optstring, options, &opt_index)) != -1) { + switch (opt) { + case 'i': + if (yo->ctx_options & LY_CTX_REF_IMPLEMENTED) { + yo->ctx_options &= ~LY_CTX_REF_IMPLEMENTED; + yo->ctx_options |= LY_CTX_ALL_IMPLEMENTED; + } else { + yo->ctx_options |= LY_CTX_REF_IMPLEMENTED; + } + break; + case 'y': + yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; + break; + case 'Y': + yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; + yo->yang_lib_file = optarg; + if (!yo->yang_lib_file) { + YLMSG_E("Memory allocation error."); + return 1; + } + break; + case 'h': + cmd_clear_help(); + return 1; + default: + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return rc; +} + +int +cmd_clear_dep(struct yl_opt *yo, int posc) +{ + (void) yo; + + if (posc) { + YLMSG_E("No positional arguments are allowed."); + return 1; + } + + return 0; +} + +/** + * @brief Convert searchpaths into single string. + * + * @param[in] ctx Context with searchpaths. + * @param[out] searchpaths Collection of paths in the single string. Paths are delimited by colon ":" + * (on Windows, used semicolon ";" instead). + * @return LY_ERR value. + */ +static LY_ERR +searchpaths_to_str(const struct ly_ctx *ctx, char **searchpaths) +{ + uint32_t i; + int rc = 0; + const char * const *dirs = ly_ctx_get_searchdirs(ctx); + + for (i = 0; dirs[i]; ++i) { + rc = searchpath_strcat(searchpaths, dirs[i]); + if (!rc) { + break; + } + } + + return rc; +} + +int +cmd_clear_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) posv; + struct ly_ctx *ctx_new = NULL; + + if (yo->yang_lib_file) { + if (searchpaths_to_str(*ctx, &yo->searchpaths)) { + YLMSG_E("Storing searchpaths failed."); + return 1; + } + if (ly_ctx_new_ylpath(yo->searchpaths, yo->yang_lib_file, LYD_UNKNOWN, yo->ctx_options, &ctx_new)) { + YLMSG_E("Unable to create libyang context with yang-library data."); + return 1; + } + } else { + if (ly_ctx_new(NULL, yo->ctx_options, &ctx_new)) { + YLMSG_W("Failed to create context."); + return 1; + } + } + + /* Global variables in commands are also deleted. */ + cmd_free(); + + ly_ctx_destroy(*ctx); + *ctx = ctx_new; + + return 0; +} diff --git a/tools/lint/cmd_data.c b/tools/lint/cmd_data.c new file mode 100644 index 0000000..44fb237 --- /dev/null +++ b/tools/lint/cmd_data.c @@ -0,0 +1,686 @@ +/** + * @file cmd_data.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'data' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2023 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 <assert.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" +#include "yl_opt.h" + +static void +cmd_data_help_header(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"); +} + +static void +cmd_data_help_type(void) +{ + 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 - 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" + " nc-rpc - Similar to 'rpc' but expect and check also the NETCONF\n" + " envelopes <rpc> or <action>.\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" + " 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 (content of the <notification>\n" + " element without <eventTime>).\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"); +} + +static void +cmd_data_help_format(void) +{ + printf(" -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"); +} + +static void +cmd_data_help_in_format(void) +{ + printf(" -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"); +} + +static void +cmd_data_help_default(void) +{ + 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"); +} + +static void +cmd_data_help_xpath(void) +{ + printf(" -x XPATH, --xpath=XPATH\n" + " Evaluate XPATH expression and print the nodes satisfying 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"); +} + +void +cmd_data_help(void) +{ + cmd_data_help_header(); + printf("\n"); + cmd_data_help_type(); + 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" + " -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" + " -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 operational datastore referenced from the operation.\n" + " In case of a nested notification or action, its parent\n" + " existence is also checked in these operational data.\n" + " -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"); + cmd_data_help_format(); + cmd_data_help_in_format(); + printf(" -o OUTFILE, --output=OUTFILE\n" + " Write the output to OUTFILE instead of stdout.\n"); + cmd_data_help_xpath(); + printf("\n"); +} + +int +cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + 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'}, + {"reply-rpc", required_argument, NULL, 'R'}, + {"not-strict", no_argument, NULL, 'n'}, + {"type", required_argument, NULL, 't'}, + {"xpath", required_argument, NULL, 'x'}, + {NULL, 0, NULL, 0} + }; + + uint8_t data_type_set = 0; + + yo->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS; + yo->data_validate_options = YL_DEFAULT_DATA_VALIDATE_OPTIONS; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_DATA].optstring, options, &opt_index)) != -1) { + switch (opt) { + case 'd': /* --default */ + if (yo_opt_update_data_default(optarg, yo)) { + YLMSG_E("Unknown default mode %s.", optarg); + cmd_data_help_default(); + return 1; + } + break; + case 'f': /* --format */ + if (yl_opt_update_data_out_format(optarg, yo)) { + cmd_data_help_format(); + return 1; + } + break; + case 'F': /* --in-format */ + if (yo_opt_update_data_in_format(optarg, yo)) { + YLMSG_E("Unknown input format %s.", optarg); + cmd_data_help_in_format(); + return 1; + } + break; + case 'o': /* --output */ + if (yo->out) { + YLMSG_E("Only a single output can be specified."); + return 1; + } else { + if (ly_out_new_filepath(optarg, &yo->out)) { + YLMSG_E("Unable open output file %s (%s).", optarg, strerror(errno)); + return 1; + } + } + break; + case 'O': /* --operational */ + if (yo->data_operational.path) { + YLMSG_E("The operational datastore (-O) cannot be set multiple times."); + return 1; + } + yo->data_operational.path = optarg; + break; + case 'R': /* --reply-rpc */ + if (yo->reply_rpc.path) { + YLMSG_E("The PRC of the reply (-R) cannot be set multiple times."); + return 1; + } + yo->reply_rpc.path = optarg; + break; + case 'e': /* --present */ + yo->data_validate_options |= LYD_VALIDATE_PRESENT; + break; + case 'm': /* --merge */ + yo->data_merge = 1; + break; + case 'n': /* --not-strict */ + yo->data_parse_options &= ~LYD_PARSE_STRICT; + break; + case 't': /* --type */ + if (data_type_set) { + YLMSG_E("The data type (-t) cannot be set multiple times."); + return 1; + } + + if (yl_opt_update_data_type(optarg, yo)) { + YLMSG_E("Unknown data tree type %s.", optarg); + cmd_data_help_type(); + return 1; + } + + data_type_set = 1; + break; + + case 'x': /* --xpath */ + if (ly_set_add(&yo->data_xpath, optarg, 0, NULL)) { + YLMSG_E("Storing XPath \"%s\" failed.", optarg); + return 1; + } + break; + + case 'h': /* --help */ + cmd_data_help(); + return 1; + default: + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return rc; +} + +int +cmd_data_dep(struct yl_opt *yo, int posc) +{ + if (yo->interactive && !posc) { + YLMSG_E("Missing the data file to process."); + return 1; + } + + if (yo->data_merge) { + if (yo->data_type || (yo->data_parse_options & LYD_PARSE_ONLY)) { + /* switch off the option, incompatible input data type */ + YLMSG_W("The --merge option has effect only for 'data' and 'config' TYPEs."); + yo->data_merge = 0; + } else { + /* postpone validation after the merge of all the input data */ + yo->data_parse_options |= LYD_PARSE_ONLY; + } + } else if (yo->data_xpath.count) { + yo->data_merge = 1; + } + + if (yo->data_xpath.count && (yo->schema_out_format || yo->data_out_format)) { + YLMSG_E("The --format option cannot be combined with --xpath option."); + if (yo->interactive) { + cmd_data_help_xpath(); + } + return 1; + } + if (yo->data_xpath.count && (yo->data_print_options & LYD_PRINT_WD_MASK)) { + YLMSG_E("The --default option cannot be combined with --xpath option."); + if (yo->interactive) { + cmd_data_help_xpath(); + } + return 1; + } + + if (yo->data_operational.path && !yo->data_type) { + YLMSG_W("Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types."); + yo->data_operational.path = NULL; + } + + if (yo->reply_rpc.path && (yo->data_type != LYD_TYPE_REPLY_NETCONF)) { + YLMSG_W("Source RPC is needed only for NETCONF Reply input data type."); + yo->data_operational.path = NULL; + } else if (!yo->reply_rpc.path && (yo->data_type == LYD_TYPE_REPLY_NETCONF)) { + YLMSG_E("Missing source RPC (-R) for NETCONF Reply input data type."); + return 1; + } + + if (!yo->out && (yo->data_out_format == LYD_LYB)) { + YLMSG_E("The LYB format requires the -o option specified."); + return 1; + } + + /* default output stream */ + if (!yo->out) { + if (ly_out_new_file(stdout, &yo->out)) { + YLMSG_E("Unable to set stdout as output."); + return 1; + } + yo->out_stdout = 1; + } + + /* process the operational and/or reply RPC content if any */ + if (yo->data_operational.path) { + if (get_input(yo->data_operational.path, NULL, &yo->data_operational.format, &yo->data_operational.in)) { + return -1; + } + } + if (yo->reply_rpc.path) { + if (get_input(yo->reply_rpc.path, NULL, &yo->reply_rpc.format, &yo->reply_rpc.in)) { + return -1; + } + } + + return 0; +} + +int +cmd_data_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) ctx; + struct ly_in *in; + LYD_FORMAT format_data; + + assert(posv); + + format_data = yo->data_in_format; + + /* process input data files provided as standalone command line arguments */ + if (get_input(posv, NULL, &format_data, &in)) { + return 1; + } + + if (!fill_cmdline_file(&yo->data_inputs, in, posv, format_data)) { + ly_in_free(in, 1); + return 1; + } + + return 0; +} + +/** + * @brief Evaluate xpath adn print result. + * + * @param[in] tree Data tree. + * @param[in] xpath Xpath to evaluate. + * @return 0 on success. + */ +static 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"); + } else { + printf("\n"); + } + } + } + + ly_set_free(set, NULL); + return 0; +} + +/** + * @brief Checking that a parent data node exists in the datastore for the nested-notification and action. + * + * @param[in] op Operation to check. + * @param[in] oper_tree Data from datastore. + * @param[in] operational_f Operational datastore file information. + * @return LY_ERR value. + */ +static LY_ERR +check_operation_parent(struct lyd_node *op, struct lyd_node *oper_tree, struct cmdline_file *operational_f) +{ + LY_ERR ret; + struct ly_set *set = NULL; + char *path = NULL; + + if (!op || !lyd_parent(op)) { + /* The function is defined only for nested-notification and action. */ + return LY_SUCCESS; + } + + if (!operational_f || (operational_f && !operational_f->in)) { + YLMSG_E("The --operational parameter needed to validate operation \"%s\" is missing.", LYD_NAME(op)); + ret = LY_EVALID; + goto cleanup; + } + + path = lyd_path(lyd_parent(op), LYD_PATH_STD, NULL, 0); + if (!path) { + ret = LY_EMEM; + goto cleanup; + } + + if (!oper_tree) { + YLMSG_W("Operational datastore is empty or contains unknown data."); + YLMSG_E("Operation \"%s\" parent \"%s\" not found in the operational data.", LYD_NAME(op), path); + ret = LY_EVALID; + 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.", LYD_NAME(op), path); + ret = LY_EVALID; + goto cleanup; + } + +cleanup: + ly_set_free(set, NULL); + free(path); + + return ret; +} + +/** + * @brief Process the input data files - parse, validate and print according to provided options. + * + * @param[in] ctx libyang context with schema. + * @param[in] type The type of data in the input files. + * @param[in] merge Flag if the data should be merged before validation. + * @param[in] out_format Data format for printing. + * @param[in] out The output handler for printing. + * @param[in] parse_options Parser options. + * @param[in] validate_options Validation options. + * @param[in] print_options Printer options. + * @param[in] operational Optional operational datastore file information for the case of an extended validation of + * operation(s). + * @param[in] reply_rpc Source RPC operation file information for parsing NETCONF rpc-reply. + * @param[in] inputs Set of file informations of input data files. + * @param[in] xpaths 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. + */ +static LY_ERR +process_data(struct ly_ctx *ctx, enum lyd_type type, uint8_t merge, LYD_FORMAT out_format, + struct ly_out *out, uint32_t parse_options, uint32_t validate_options, uint32_t print_options, + struct cmdline_file *operational, struct cmdline_file *reply_rpc, 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; + const char *xpath; + struct ly_set *set = NULL; + + /* additional operational datastore */ + if (operational && operational->in) { + ret = lyd_parse_data(ctx, NULL, operational->in, operational->format, LYD_PARSE_ONLY, 0, &oper_tree); + if (ret) { + YLMSG_E("Failed to parse operational datastore file \"%s\".", operational->path); + goto cleanup; + } + } + + for (uint32_t u = 0; u < inputs->count; ++u) { + struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u]; + + switch (type) { + case LYD_TYPE_DATA_YANG: + ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, parse_options, validate_options, &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, 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, 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(reply_rpc && reply_rpc->in); + ret = lyd_parse_op(ctx, NULL, reply_rpc->in, reply_rpc->format, LYD_TYPE_RPC_NETCONF, &envp, &op); + if (ret) { + YLMSG_E("Failed to parse source NETCONF RPC operation file \"%s\".", reply_rpc->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, type, &envp, NULL); + break; + default: + YLMSG_E("Internal error (%s:%d).", __FILE__, __LINE__); + goto cleanup; + } + + if (ret) { + YLMSG_E("Failed to parse input data file \"%s\".", 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.", input_f->path); + goto cleanup; + } + } + tree = NULL; + } else if (out_format) { + /* print */ + switch (type) { + case LYD_TYPE_DATA_YANG: + lyd_print_all(out, tree, out_format, print_options); + 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, out_format, print_options); + break; + case LYD_TYPE_REPLY_NETCONF: + /* just the output */ + lyd_print_tree(out, lyd_child(tree), out_format, print_options); + break; + default: + assert(0); + } + } else { + /* validation of the RPC/Action/reply/Notification with the operational datastore, if any */ + switch (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->path) { + YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".", + input_f->path, operational->path); + } else { + YLMSG_E("Failed to validate input data file \"%s\".", input_f->path); + } + goto cleanup; + } + + if ((ret = check_operation_parent(op, oper_tree, operational))) { + 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, validate_options, NULL); + if (ret) { + YLMSG_E("Merged data are not valid."); + goto cleanup; + } + + if (out_format) { + /* and print it */ + lyd_print_all(out, merged_tree, out_format, print_options); + } + + for (uint32_t u = 0; xpaths && (u < xpaths->count); ++u) { + xpath = (const char *)xpaths->objs[u]; + ly_set_free(set, NULL); + ret = lys_find_xpath(ctx, NULL, xpath, LYS_FIND_NO_MATCH_ERROR, &set); + if (ret || !set->count) { + ret = (ret == LY_SUCCESS) ? LY_EINVAL : ret; + YLMSG_E("The requested xpath failed."); + goto cleanup; + } + if (evaluate_xpath(merged_tree, xpath)) { + goto cleanup; + } + } + } + +cleanup: + lyd_free_all(tree); + lyd_free_all(envp); + lyd_free_all(merged_tree); + lyd_free_all(oper_tree); + ly_set_free(set, NULL); + return ret; +} + +int +cmd_data_process(struct ly_ctx *ctx, struct yl_opt *yo) +{ + /* parse, validate and print data */ + if (process_data(ctx, yo->data_type, yo->data_merge, yo->data_out_format, yo->out, yo->data_parse_options, + yo->data_validate_options, yo->data_print_options, &yo->data_operational, &yo->reply_rpc, + &yo->data_inputs, &yo->data_xpath)) { + return 1; + } + + return 0; +} diff --git a/tools/lint/cmd_debug.c b/tools/lint/cmd_debug.c new file mode 100644 index 0000000..a1a8989 --- /dev/null +++ b/tools/lint/cmd_debug.c @@ -0,0 +1,130 @@ +/** + * @file cmd_debug.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'verb' command of the libyang's yanglint tool. + * + * Copyright (c) 2023-2023 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 "cmd.h" + +#include <assert.h> +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <strings.h> + +#include "libyang.h" + +#include "common.h" +#include "yl_opt.h" + +struct debug_groups { + char *name; + uint32_t flag; +} const dg [] = { + {"dict", LY_LDGDICT}, + {"xpath", LY_LDGXPATH}, + {"dep-sets", LY_LDGDEPSETS}, +}; +#define DG_LENGTH (sizeof dg / sizeof *dg) + +void +cmd_debug_help(void) +{ + uint32_t i; + + printf("Usage: debug ("); + for (i = 0; i < DG_LENGTH; i++) { + if ((i + 1) == DG_LENGTH) { + printf("%s", dg[i].name); + } else { + printf("%s | ", dg[i].name); + } + } + printf(")+\n"); +} + +int +cmd_debug_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_DEBUG].optstring, options, &opt_index)) != -1) { + switch (opt) { + case 'h': + cmd_debug_help(); + return 1; + default: + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_debug_dep(struct yl_opt *yo, int posc) +{ + (void) yo; + + if (yo->interactive && !posc) { + /* no argument */ + cmd_debug_help(); + return 1; + } + + return 0; +} + +int +cmd_debug_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) ctx; + uint32_t i; + ly_bool set; + + assert(posv); + + set = 0; + for (i = 0; i < DG_LENGTH; i++) { + if (!strcasecmp(posv, dg[i].name)) { + yo->dbg_groups |= dg[i].flag; + set = 1; + break; + } + } + + if (!set) { + YLMSG_E("Unknown debug group \"%s\".", posv); + return 1; + } + + return 0; +} + +int +cmd_debug_setlog(struct ly_ctx *ctx, struct yl_opt *yo) +{ + (void) ctx; + return ly_log_dbg_groups(yo->dbg_groups); +} diff --git a/tools/lint/cmd_extdata.c b/tools/lint/cmd_extdata.c new file mode 100644 index 0000000..fc7ac7b --- /dev/null +++ b/tools/lint/cmd_extdata.c @@ -0,0 +1,115 @@ +/** + * @file cmd_extdata.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'extdata' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2023 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 "cmd.h" + +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> + +#include "libyang.h" + +#include "common.h" +#include "yl_opt.h" + +char *filename; + +void +cmd_extdata_free(void) +{ + free(filename); + filename = NULL; +} + +void +cmd_extdata_help(void) +{ + printf("Usage: extdata [--clear] [<extdata-file-path>]\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"); +} + +int +cmd_extdata_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"clear", no_argument, NULL, 'c'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_EXTDATA].optstring, options, &opt_index)) != -1) { + switch (opt) { + case 'c': + yo->extdata_unset = 1; + free(filename); + filename = NULL; + break; + case 'h': + cmd_extdata_help(); + return 1; + default: + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_extdata_dep(struct yl_opt *yo, int posc) +{ + if (!yo->extdata_unset && (posc > 1)) { + YLMSG_E("Only one file must be entered."); + return 1; + } + + return 0; +} + +int +cmd_extdata_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + if (yo->extdata_unset) { + ly_ctx_set_ext_data_clb(*ctx, NULL, NULL); + } else if (!yo->extdata_unset && !posv) { + /* no argument - print the current file */ + printf("%s\n", filename ? filename : "No file set."); + } else if (posv) { + /* set callback providing run-time extension instance data */ + free(filename); + filename = strdup(posv); + if (!filename) { + YLMSG_E("Memory allocation error."); + return 1; + } + ly_ctx_set_ext_data_clb(*ctx, ext_data_clb, filename); + } + + return 0; +} diff --git a/tools/lint/cmd_feature.c b/tools/lint/cmd_feature.c new file mode 100644 index 0000000..96d55c1 --- /dev/null +++ b/tools/lint/cmd_feature.c @@ -0,0 +1,131 @@ +/** + * @file cmd_feature.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'feature' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2023 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" +#include "yl_opt.h" +#include "yl_schema_features.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"); +} + +int +cmd_feature_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {"all", no_argument, NULL, 'a'}, + {"feature-param", no_argument, NULL, 'f'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_FEATURE].optstring, options, &opt_index)) != -1) { + switch (opt) { + case 'h': + cmd_feature_help(); + return 1; + case 'a': + yo->feature_print_all = 1; + break; + case 'f': + yo->feature_param_format = 1; + break; + default: + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_feature_dep(struct yl_opt *yo, int posc) +{ + if (ly_out_new_file(stdout, &yo->out)) { + YLMSG_E("Unable to print to the standard output."); + return 1; + } + yo->out_stdout = 1; + + if (yo->interactive && !yo->feature_print_all && !posc) { + YLMSG_E("Missing modules to print."); + return 1; + } + + return 0; +} + +int +cmd_feature_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + const struct lys_module *mod; + + if (yo->feature_print_all) { + print_all_features(yo->out, *ctx, yo->feature_param_format); + return 0; + } + + mod = ly_ctx_get_module_latest(*ctx, posv); + if (!mod) { + YLMSG_E("Module \"%s\" not found.", posv); + return 1; + } + + if (yo->feature_param_format) { + print_feature_param(yo->out, mod); + } else { + print_features(yo->out, mod); + } + + return 0; +} + +int +cmd_feature_fin(struct ly_ctx *ctx, struct yl_opt *yo) +{ + (void) ctx; + + ly_print(yo->out, "\n"); + return 0; +} diff --git a/tools/lint/cmd_help.c b/tools/lint/cmd_help.c new file mode 100644 index 0000000..a1ee3f6 --- /dev/null +++ b/tools/lint/cmd_help.c @@ -0,0 +1,107 @@ +/** + * @file cmd_help.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'help' command of the libyang's yanglint tool. + * + * Copyright (c) 2023-2023 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" +#include "yl_opt.h" + +void +cmd_help_help(void) +{ + printf("Usage: help [cmd ...]\n"); +} + +int +cmd_help_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_HELP].optstring, options, &opt_index)) != -1) { + if (opt == 'h') { + cmd_help_help(); + return 1; + } else { + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return rc; +} + +static void +print_generic_help(void) +{ + 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); + } + } +} + +int +cmd_help_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void)ctx, (void)yo; + + if (!posv) { + print_generic_help(); + } else { + /* print specific help for the selected command(s) */ + + int8_t match = 0; + + /* get the command of the specified name */ + for (uint16_t i = 0; commands[i].name; i++) { + if (strcmp(posv, commands[i].name) == 0) { + match = 1; + if (commands[i].help_func != NULL) { + commands[i].help_func(); + } else { + printf("%s: %s\n", posv, commands[i].helpstring); + } + break; + } + } + if (!match) { + /* if unknown command specified, print the list of commands */ + printf("Unknown command \'%s\'\n", posv); + print_generic_help(); + } + } + + return 0; +} diff --git a/tools/lint/cmd_list.c b/tools/lint/cmd_list.c new file mode 100644 index 0000000..166fbfa --- /dev/null +++ b/tools/lint/cmd_list.c @@ -0,0 +1,186 @@ +/** + * @file cmd_list.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'list' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2023 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" +#include "yl_opt.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"); +} + +int +cmd_list_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"format", required_argument, NULL, 'f'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + yo->data_out_format = LYD_UNKNOWN; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_LIST].optstring, options, &opt_index)) != -1) { + switch (opt) { + case 'f': /* --format */ + if (!strcasecmp(optarg, "xml")) { + yo->data_out_format = LYD_XML; + } else if (!strcasecmp(optarg, "json")) { + yo->data_out_format = LYD_JSON; + } else { + YLMSG_E("Unknown output format %s.", optarg); + cmd_list_help(); + return 1; + } + break; + case 'h': + cmd_list_help(); + return 1; + default: + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_list_dep(struct yl_opt *yo, int posc) +{ + if (posc) { + YLMSG_E("No positional arguments are allowed."); + return 1; + } + if (ly_out_new_file(stdout, &yo->out)) { + YLMSG_E("Unable to print to the standard output."); + return 1; + } + yo->out_stdout = 1; + + return 0; +} + +/** + * @brief Print yang library data. + * + * @param[in] ctx Context for libyang. + * @param[in] data_out_format Output format of printed data. + * @param[in] out Output handler. + * @return 0 on success. + */ +static int +print_yang_lib_data(struct ly_ctx *ctx, LYD_FORMAT data_out_format, struct ly_out *out) +{ + struct lyd_node *ylib; + + 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."); + return 1; + } + + lyd_print_all(out, ylib, data_out_format, 0); + lyd_free_all(ylib); + + return 0; +} + +int +cmd_list_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) posv; + uint32_t idx = 0, has_modules = 0; + const struct lys_module *mod; + + if (yo->data_out_format != LYD_UNKNOWN) { + /* ietf-yang-library data are printed in the specified format */ + if (print_yang_lib_data(*ctx, yo->data_out_format, yo->out)) { + return 1; + } + return 0; + } + + /* iterate schemas in context and provide just the basic info */ + ly_print(yo->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(yo->out, " I"); + } else { + ly_print(yo->out, " i"); + } + + /* module print */ + ly_print(yo->out, " %s", mod->name); + if (mod->revision) { + ly_print(yo->out, "@%s", mod->revision); + } + + /* submodules print */ + if (mod->parsed && mod->parsed->includes) { + uint64_t u = 0; + + ly_print(yo->out, " ("); + LY_ARRAY_FOR(mod->parsed->includes, u) { + ly_print(yo->out, "%s%s", !u ? "" : ",", mod->parsed->includes[u].name); + if (mod->parsed->includes[u].rev[0]) { + ly_print(yo->out, "@%s", mod->parsed->includes[u].rev); + } + } + ly_print(yo->out, ")"); + } + + /* finish the line */ + ly_print(yo->out, "\n"); + } + + if (!has_modules) { + ly_print(yo->out, "\t(none)\n"); + } + + ly_print_flush(yo->out); + + return 0; +} diff --git a/tools/lint/cmd_load.c b/tools/lint/cmd_load.c new file mode 100644 index 0000000..808c125 --- /dev/null +++ b/tools/lint/cmd_load.c @@ -0,0 +1,152 @@ +/** + * @file cmd_load.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'load' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2023 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 <assert.h> +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "libyang.h" + +#include "common.h" +#include "yl_opt.h" +#include "yl_schema_features.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" + " -X, --extended-leafref\n" + " Allow usage of deref() XPath function within leafref.\n"); +} + +int +cmd_load_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc, argc = 0; + int opt, opt_index; + struct option options[] = { + {"features", required_argument, NULL, 'F'}, + {"help", no_argument, NULL, 'h'}, + {"make-implemented", no_argument, NULL, 'i'}, + {"extended-leafref", no_argument, NULL, 'X'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_LOAD].optstring, options, &opt_index)) != -1) { + switch (opt) { + case 'F': /* --features */ + if (parse_features(optarg, &yo->schema_features)) { + return 1; + } + break; + + case 'h': + cmd_load_help(); + return 1; + + case 'i': /* --make-implemented */ + yo_opt_update_make_implemented(yo); + break; + + case 'X': /* --extended-leafref */ + yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED; + break; + + default: + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_load_dep(struct yl_opt *yo, int posc) +{ + if (yo->interactive && !posc) { + /* no argument */ + cmd_load_help(); + return 1; + } + + if (!yo->schema_features.count) { + /* no features, enable all of them */ + yo->ctx_options |= LY_CTX_ENABLE_IMP_FEATURES; + } + + return 0; +} + +int +cmd_load_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + const char *all_features[] = {"*", NULL}; + char *revision; + const char **features = NULL; + + assert(posv); + + if (yo->ctx_options) { + ly_ctx_set_options(*ctx, yo->ctx_options); + yo->ctx_options = 0; + } + + /* get revision */ + revision = strchr(posv, '@'); + if (revision) { + revision[0] = '\0'; + ++revision; + } + + /* get features list for this module */ + if (!yo->schema_features.count) { + features = all_features; + } else { + get_features(&yo->schema_features, posv, &features); + } + + /* load the module */ + if (!ly_ctx_load_module(*ctx, posv, revision, features)) { + /* libyang printed the error messages */ + return 1; + } + + return 0; +} diff --git a/tools/lint/cmd_print.c b/tools/lint/cmd_print.c new file mode 100644 index 0000000..ff5fb90 --- /dev/null +++ b/tools/lint/cmd_print.c @@ -0,0 +1,299 @@ +/** + * @file cmd_print.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'print' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2023 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" +#include "yl_opt.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. For yang, yin and tree\n" + " formats, a submodule can also be printed.\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"); +} + +int +cmd_print_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + 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} + }; + + yo->schema_out_format = LYS_OUT_TREE; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_PRINT].optstring, options, &opt_index)) != -1) { + switch (opt) { + case 'o': /* --output */ + if (yo->out) { + YLMSG_E("Only a single output can be specified."); + return 1; + } else { + if (ly_out_new_filepath(optarg, &yo->out)) { + YLMSG_E("Unable open output file %s (%s).", optarg, strerror(errno)); + return 1; + } + } + break; + + case 'f': /* --format */ + if (yl_opt_update_schema_out_format(optarg, yo)) { + cmd_print_help(); + return 1; + } + break; + + case 'L': /* --tree-line-length */ + yo->line_length = atoi(optarg); + break; + + case 'P': /* --schema-node */ + yo->schema_node_path = optarg; + break; + + case 'q': /* --single-node */ + yo->schema_print_options |= LYS_PRINT_NO_SUBSTMT; + break; + + case 'h': + cmd_print_help(); + return 1; + default: + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_print_dep(struct yl_opt *yo, int posc) +{ + /* file name */ + if (yo->interactive && !posc && !yo->schema_node_path) { + YLMSG_E("Missing the name of the module to print."); + return 1; + } + + if ((yo->schema_out_format != LYS_OUT_TREE) && yo->line_length) { + YLMSG_W("--tree-line-length take effect only in case of the tree output format."); + } + + if (!yo->out) { + if (ly_out_new_file(stdout, &yo->out)) { + YLMSG_E("Could not use stdout to print output."); + } + yo->out_stdout = 1; + } + + if (yo->schema_out_format == LYS_OUT_TREE) { + /* print tree from lysc_nodes */ + yo->ctx_options |= LY_CTX_SET_PRIV_PARSED; + } + + return 0; +} + +static LY_ERR +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; + + if (!erc) { + return 0; + } else if ((erc == LY_ENOTFOUND) && revision) { + YLMSG_E("No submodule \"%s\" found.", name); + } else { + YLMSG_E("Unable to print submodule %s.", name); + } + + return erc; +} + +static LY_ERR +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; + + if (!erc) { + return 0; + } else if ((erc == LY_ENOTFOUND) && revision) { + YLMSG_E("No module \"%s\" found.", name); + } else { + YLMSG_E("Unable to print module %s.", name); + } + + return erc; +} + +static int +cmd_print_module(const char *posv, struct ly_out *out, struct ly_ctx **ctx, LYS_OUTFORMAT format, + size_t line_length, uint32_t options) +{ + LY_ERR erc; + char *name = NULL, *revision; + + name = strdup(posv); + /* get revision */ + revision = strchr(name, '@'); + if (revision) { + revision[0] = '\0'; + ++revision; + } + + erc = print_module(out, ctx, name, revision, format, line_length, options); + + if (erc == LY_ENOTFOUND) { + erc = print_submodule(out, ctx, name, revision, format, line_length, options); + } + + free(name); + return erc; +} + +/** + * @brief Print schema node path. + * + * @param[in] ctx Context for libyang. + * @param[in] yo Context for yanglint. + * @return 0 on success. + */ +static int +print_node(struct ly_ctx *ctx, struct yl_opt *yo) +{ + const struct lysc_node *node; + uint32_t temp_lo = 0; + + if (yo->interactive) { + /* Use the same approach as for completion. */ + node = find_schema_path(ctx, yo->schema_node_path); + if (!node) { + YLMSG_E("The requested schema node \"%s\" does not exists.", yo->schema_node_path); + return 1; + } + } else { + /* turn off logging so that the message is not repeated */ + ly_temp_log_options(&temp_lo); + /* search operation input */ + node = lys_find_path(ctx, NULL, yo->schema_node_path, 0); + if (!node) { + /* restore logging so an error may be displayed */ + ly_temp_log_options(NULL); + /* search operation output */ + node = lys_find_path(ctx, NULL, yo->schema_node_path, 1); + if (!node) { + YLMSG_E("Invalid schema path."); + return 1; + } + } + } + + if (lys_print_node(yo->out, node, yo->schema_out_format, yo->line_length, yo->schema_print_options)) { + YLMSG_E("Unable to print schema node %s.", yo->schema_node_path); + return 1; + } + + return 0; +} + +int +cmd_print_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + int rc = 0; + + if (yo->ctx_options & LY_CTX_SET_PRIV_PARSED) { + /* print tree from lysc_nodes */ + ly_ctx_set_options(*ctx, LY_CTX_SET_PRIV_PARSED); + } + + if (yo->schema_node_path) { + rc = print_node(*ctx, yo); + } else if (!yo->interactive && yo->submodule) { + rc = print_submodule(yo->out, ctx, yo->submodule, NULL, yo->schema_out_format, yo->line_length, + yo->schema_print_options); + } else { + rc = cmd_print_module(posv, yo->out, ctx, yo->schema_out_format, yo->line_length, yo->schema_print_options); + if (!yo->last_one && (yo->schema_out_format == LYS_OUT_TREE)) { + ly_print(yo->out, "\n"); + } + } + + return rc; +} diff --git a/tools/lint/cmd_searchpath.c b/tools/lint/cmd_searchpath.c new file mode 100644 index 0000000..a6aeacf --- /dev/null +++ b/tools/lint/cmd_searchpath.c @@ -0,0 +1,96 @@ +/** + * @file cmd_searchpath.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'searchpath' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2023 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" +#include "yl_opt.h" + +void +cmd_searchpath_help(void) +{ + printf("Usage: searchpath [--clear] [<modules-dir-path> ...]\n" + " Set paths of directories where to search for imports and includes\n" + " of the schema modules. Subdirectories are also searched. The current\n" + " working directory and the path of the module being added is used implicitly.\n" + " The 'load' command uses these paths to search even for the schema modules\n" + " to be loaded.\n"); +} + +int +cmd_searchpath_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"clear", no_argument, NULL, 'c'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_SEARCHPATH].optstring, options, &opt_index)) != -1) { + switch (opt) { + case 'c': + yo->searchdir_unset = 1; + break; + case 'h': + cmd_searchpath_help(); + return 1; + default: + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_searchpath_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + int rc = 0; + + if (yo->searchdir_unset) { + ly_ctx_unset_searchdir(*ctx, NULL); + } else if (!yo->searchdir_unset && !posv) { + /* 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]); + } + } else { + rc = ly_ctx_set_searchdir(*ctx, posv); + } + + return rc; +} diff --git a/tools/lint/cmd_verb.c b/tools/lint/cmd_verb.c new file mode 100644 index 0000000..33c8d1e --- /dev/null +++ b/tools/lint/cmd_verb.c @@ -0,0 +1,114 @@ +/** + * @file cmd_verb.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'verb' command of the libyang's yanglint tool. + * + * Copyright (c) 2023-2023 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 "cmd.h" + +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <strings.h> + +#include "libyang.h" + +#include "common.h" +#include "yl_opt.h" + +void +cmd_verb_help(void) +{ + printf("Usage: verb (error | warning | verbose | debug)\n"); +} + +int +cmd_verb_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_VERB].optstring, options, &opt_index)) != -1) { + if (opt == 'h') { + cmd_verb_help(); + return 1; + } else { + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_verb_dep(struct yl_opt *yo, int posc) +{ + (void) yo; + + if (posc > 1) { + YLMSG_E("Only a single verbosity level can be set."); + cmd_verb_help(); + return 1; + } + + return 0; +} + +int +cmd_verb_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) ctx, (void) yo; + + if (!posv) { + /* 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"); + } + return 0; + } else { + if (!strcasecmp("error", posv) || !strcmp("0", posv)) { + ly_log_level(LY_LLERR); + } else if (!strcasecmp("warning", posv) || !strcmp("1", posv)) { + ly_log_level(LY_LLWRN); + } else if (!strcasecmp("verbose", posv) || !strcmp("2", posv)) { + ly_log_level(LY_LLVRB); + } else if (!strcasecmp("debug", posv) || !strcmp("3", posv)) { + ly_log_level(LY_LLDBG); + } else { + YLMSG_E("Unknown verbosity \"%s\".", posv); + return 1; + } + } + + return 0; +} diff --git a/tools/lint/common.c b/tools/lint/common.c new file mode 100644 index 0000000..d86c54f --- /dev/null +++ b/tools/lint/common.c @@ -0,0 +1,301 @@ +/** + * @file common.c + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> + * @brief libyang's yanglint tool - common functions for both interactive and non-interactive mode. + * + * Copyright (c) 2023 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 <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "compat.h" +#include "libyang.h" +#include "plugins_exts.h" +#include "yl_opt.h" + +void +yl_log(ly_bool err, const char *format, ...) +{ + char msg[256]; + va_list ap; + + va_start(ap, format); + vsnprintf(msg, 256, format, ap); + va_end(ap); + + fprintf(stderr, "YANGLINT[%c]: %s\n", err ? 'E' : 'W', msg); +} + +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); + /* FIXME: this is broken on Windows */ + *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.", filepath, strerror(errno)); + return -1; + } + if (!S_ISREG(st.st_mode)) { + YLMSG_E("Provided input file (%s) is not a regular file.", filepath); + return -1; + } + + if (get_format(filepath, format_schema, format_data)) { + return -1; + } + + if (in && ly_in_new_filepath(filepath, 0, in)) { + YLMSG_E("Unable to process input file."); + return -1; + } + + return 0; +} + +LYS_INFORMAT +get_schema_format(const char *filename) +{ + char *ptr; + + if ((ptr = strrchr(filename, '.')) != NULL) { + ++ptr; + if (!strcmp(ptr, "yang")) { + return LYS_IN_YANG; + } else if (!strcmp(ptr, "yin")) { + return LYS_IN_YIN; + } else { + return LYS_IN_UNKNOWN; + } + } else { + return LYS_IN_UNKNOWN; + } +} + +LYD_FORMAT +get_data_format(const char *filename) +{ + char *ptr; + + if ((ptr = strrchr(filename, '.')) != NULL) { + ++ptr; + if (!strcmp(ptr, "xml")) { + return LYD_XML; + } else if (!strcmp(ptr, "json")) { + return LYD_JSON; + } else if (!strcmp(ptr, "lyb")) { + return LYD_LYB; + } else { + return LYD_UNKNOWN; + } + } else { + return LYD_UNKNOWN; + } +} + +int +get_format(const char *filepath, LYS_INFORMAT *schema_form, LYD_FORMAT *data_form) +{ + LYS_INFORMAT schema; + LYD_FORMAT data; + + schema = !schema_form || !*schema_form ? LYS_IN_UNKNOWN : *schema_form; + data = !data_form || !*data_form ? LYD_UNKNOWN : *data_form; + + if (!schema) { + schema = get_schema_format(filepath); + } + if (!data) { + data = get_data_format(filepath); + } + + if (!schema && !data) { + YLMSG_E("Input schema format for %s file not recognized.", filepath); + return -1; + } else if (!data && !schema) { + YLMSG_E("Input data format for %s file not recognized.", filepath); + return -1; + } + assert(schema || data); + + if (schema_form) { + *schema_form = schema; + } + if (data_form) { + *data_form = data; + } + + return 0; +} + +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; +} + +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; +} + +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; +} diff --git a/tools/lint/common.h b/tools/lint/common.h new file mode 100644 index 0000000..7c50e72 --- /dev/null +++ b/tools/lint/common.h @@ -0,0 +1,158 @@ +/** + * @file common.h + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> + * @brief libyang's yanglint tool - common functions and definitions for both interactive and non-interactive mode. + * + * Copyright (c) 2023 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 Default data validation flags. + */ +#define YL_DEFAULT_DATA_VALIDATE_OPTIONS LYD_VALIDATE_MULTI_ERROR + +/** + * @brief log error message + */ +#define YLMSG_E(...) \ + yl_log(1, __VA_ARGS__); + +/** + * @brief log warning message + */ +#define YLMSG_W(...) \ + yl_log(0, __VA_ARGS__); + +#ifndef _WIN32 +# define PATH_SEPARATOR ":" +#else +# define PATH_SEPARATOR ";" +#endif + +struct cmdline_file; + +/** + * @brief Log a yanglint message. + * + * @param[in] err Whether the message is an error or a warning. + * @param[in] format Message format. + * @param[in] ... Format arguments. + */ +void yl_log(ly_bool err, const char *format, ...); + +/** + * @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 + * @p 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. Can be NULL. + * @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 Get schema format of the @p filename's content according to the @p filename's suffix. + * + * @param[in] filename Name of the file to examine. + * @return Detected schema input format. + */ +LYS_INFORMAT get_schema_format(const char *filename); + +/** + * @brief Get data format of the @p filename's content according to the @p filename's suffix. + * + * @param[in] filename Name of the file to examine. + * @return Detected data input format. + */ +LYD_FORMAT get_data_format(const char *filename); + +/** + * @brief Get format of the @p filename's content according to the @p filename's suffix. + * + * Either the @p schema or @p data parameter is set. + * + * @param[in] filepath Name of the file to examine. + * @param[out] schema_form Pointer to a variable to store the input schema format. + * @param[out] data_form Pointer to a variable to store the expected input data format. + * @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 *filepath, LYS_INFORMAT *schema_form, LYD_FORMAT *data_form); + +/** + * @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); + +/** + * @brief General callback providing run-time extension instance data. + * + * @param[in] ext Compiled extension instance. + * @param[in] user_data User-supplied callback data. + * @param[out] ext_data Provided extension instance data. + * @param[out] ext_data_free Whether the extension instance should free @p ext_data or not. + * @return LY_ERR value. + */ +LY_ERR ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free); + +/** + * @brief Concatenation of paths into one string. + * + * @param[in,out] searchpaths Collection of paths in the single string. Paths are delimited by colon ":" + * (on Windows, used semicolon ";" instead). + * @param[in] path Path to add. + * @return LY_ERR value. + */ +LY_ERR searchpath_strcat(char **searchpaths, const char *path); + +#endif /* COMMON_H_ */ diff --git a/tools/lint/completion.c b/tools/lint/completion.c new file mode 100644 index 0000000..59207ca --- /dev/null +++ b/tools/lint/completion.c @@ -0,0 +1,508 @@ +/** + * @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; + + p = realloc(*matches, (*match_count + 1) * sizeof **matches); + if (!p) { + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); + return; + } + *matches = p; + (*matches)[*match_count] = strdup(match); + if (!((*matches)[*match_count])) { + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); + return; + } + ++(*match_count); +} + +/** + * @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 arguments. + * + * @param[in] hint User input. + * @param[in] args Array of all possible arguments. The last element must be NULL. + * @param[out] matches Matches provided to the user as a completion hint. + * @param[out] match_count Number of matches. + */ +static void +get_arg_completion(const char *hint, const char **args, char ***matches, unsigned int *match_count) +{ + int i; + + *match_count = 0; + *matches = NULL; + + for (i = 0; args[i]; i++) { + if (!strncmp(hint, args[i], strlen(hint))) { + cmd_completion_add_match(args[i], matches, match_count); + } + } + if (*match_count == 0) { + for (i = 0; args[i]; i++) { + cmd_completion_add_match(args[i], 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] parent Node of which children will be added to the hint. + * @param[out] matches Matches provided to the user as a completion hint. + * @param[out] match_count Number of matches. + */ +static void +single_hint_add_children(const struct lysc_node *parent, char ***matches, unsigned int *match_count) +{ + const struct lysc_node *node = NULL; + char *match; + + if (!parent) { + return; + } + + while ((node = lys_getnext(node, parent, 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 all possible argument hints for option. + * + * @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_print_format_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"yang", "yin", "tree", "info", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_data_type_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"data", "config", "get", "getconfig", "edit", "rpc", "reply", "notif", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_data_in_format_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"xml", "json", "lyb", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_data_default_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"all", "all-tagged", "trim", "implicit-tagged", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_list_format_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"xml", "json", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_verb_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"error", "warning", "verbose", "debug", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_debug_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"dict", "xpath", "dep-sets", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @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 { + enum COMMAND_INDEX ci; /**< command index to global variable 'commands' */ + const char *opt; /**< optional option */ + 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[] = { + {CMD_ADD, NULL, linenoisePathCompletion, NULL}, + {CMD_PRINT, "-f", NULL, get_print_format_arg}, + {CMD_PRINT, "-P", NULL, get_schema_completion}, + {CMD_PRINT, "-o", linenoisePathCompletion, NULL}, + {CMD_PRINT, NULL, NULL, get_model_completion}, + {CMD_SEARCHPATH, NULL, linenoisePathCompletion, NULL}, + {CMD_EXTDATA, NULL, linenoisePathCompletion, NULL}, + {CMD_CLEAR, "-Y", linenoisePathCompletion, NULL}, + {CMD_DATA, "-t", NULL, get_data_type_arg}, + {CMD_DATA, "-O", linenoisePathCompletion, NULL}, + {CMD_DATA, "-R", linenoisePathCompletion, NULL}, + {CMD_DATA, "-f", NULL, get_data_in_format_arg}, + {CMD_DATA, "-F", NULL, get_data_in_format_arg}, + {CMD_DATA, "-d", NULL, get_data_default_arg}, + {CMD_DATA, "-o", linenoisePathCompletion, NULL}, + {CMD_DATA, NULL, linenoisePathCompletion, NULL}, + {CMD_LIST, NULL, NULL, get_list_format_arg}, + {CMD_FEATURE, NULL, NULL, get_model_completion}, + {CMD_VERB, NULL, NULL, get_verb_arg}, + {CMD_DEBUG, NULL, NULL, get_debug_arg}, + }; + size_t name_len; + const char *last, *name, *getoptstr; + char opt[3] = {'\0', ':', '\0'}; + 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) { + /* Find the right command. */ + name = commands[ac[i].ci].name; + name_len = strlen(name); + if (strncmp(buf, name, name_len) || (buf[name_len] != ' ')) { + /* not this command */ + continue; + } + + /* Select based on the right option. */ + last = get_last_str(buf, hint); + opt[0] = (last[0] == '-') && last[1] ? last[1] : '\0'; + getoptstr = commands[ac[i].ci].optstring; + if (!ac[i].opt && opt[0] && strstr(getoptstr, opt)) { + /* completion for the argument must be defined */ + continue; + } else if (ac[i].opt && opt[0] && strncmp(ac[i].opt, last, strlen(ac[i].opt))) { + /* completion for (another) option */ + continue; + } else if (ac[i].opt && !opt[0]) { + /* completion is defined for option */ + continue; + } + + /* 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..e3db668 --- /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).", 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).", 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.", yl_dir); + if (mkdir(yl_dir, 00700)) { + if (errno != EEXIST) { + /* parallel execution, yay */ + YLMSG_E("Configuration directory \"%s\" cannot be created (%s).", yl_dir, strerror(errno)); + free(yl_dir); + return NULL; + } + } + } else { + YLMSG_E("Configuration directory \"%s\" exists but cannot be accessed (%s).", 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).", 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."); + } else if (linenoiseHistoryLoad(history_file)) { + YLMSG_E("Failed to load history."); + } + + 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).", strerror(errno)); + free(yl_dir); + return; + } + + sprintf(history_file, "%s/history", yl_dir); + if (linenoiseHistorySave(history_file)) { + YLMSG_E("Failed to save history."); + } + + 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..93d3c2a --- /dev/null +++ b/tools/lint/examples/README.md @@ -0,0 +1,536 @@ +# 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. Subdirectories are also searched. 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" + } + ] + } + } + ] + } +} +``` + +## YANG modules with the Schema Mount extension + +In these examples the non-interactive `yanglint` is used to simplify creating the context, a `yang-library` data file is +used. The working directory is `libyang/tools/lint/examples` and *libyang* must be installed. + +**Print tree output of a model with Schema Mount** + +Command and its output: + +``` +$ yanglint -f tree -p . -Y sm-context-main.xml -x sm-context-extension.xml sm-main.yang +module: sm-main + +--mp root* [node] + | +--rw node string + +--mp root2 + +--rw root3 + +--mp my-list* [name] + +--rw things/* [name] + | +--rw name -> /if:interfaces/if:interface/if:name + | +--rw attribute? uint32 + +--rw not-compiled/ + | +--rw first? string + | +--rw second? string + +--rw interfaces@ + | +--rw interface* [name] + | +--rw name string + | +--rw type identityref + +--rw name string +``` + +**Validating and printing mounted data** + +Command and its output: + +``` +$ yanglint -f json -t config -p . -Y sm-context-main.xml -x sm-context-extension.xml sm-data.xml +{ + "ietf-interfaces:interfaces": { + "interface": [ + { + "name": "eth0", + "type": "iana-if-type:ethernetCsmacd" + }, + { + "name": "eth1", + "type": "iana-if-type:ethernetCsmacd" + } + ] + }, + "sm-main:root3": { + "my-list": [ + { + "name": "list item 1", + "sm-extension:things": [ + { + "name": "eth0", + "attribute": 1 + } + ] + } + ] + } +} +``` 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: <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></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: <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></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..43b90c8 --- /dev/null +++ b/tools/lint/main.c @@ -0,0 +1,134 @@ +/** + * @file main.c + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> + * @brief libyang's yanglint tool + * + * Copyright (c) 2015-2023 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" +#include "yl_opt.h" + +int done; +struct ly_ctx *ctx = NULL; + +/* main_ni.c */ +int main_ni(int argc, char *argv[]); + +int +main(int argc, char *argv[]) +{ + int cmdlen, posc, i, j; + struct yl_opt yo = {0}; + char *empty = NULL, *cmdline; + char **posv; + uint8_t cmd_found; + + if (argc > 1) { + /* run in non-interactive mode */ + return main_ni(argc, argv); + } + yo.interactive = 1; + + /* 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."); + return 1; + } + + while (!done) { + cmd_found = 0; + + posv = ∅ + posc = 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 (i = 0; commands[i].name; i++) { + if (strncmp(cmdline, commands[i].name, (size_t)cmdlen) || (commands[i].name[cmdlen] != '\0')) { + continue; + } + + cmd_found = 1; + if (commands[i].opt_func && commands[i].opt_func(&yo, cmdline, &posv, &posc)) { + break; + } + if (commands[i].dep_func && commands[i].dep_func(&yo, posc)) { + break; + } + if (posc) { + for (j = 0; j < posc; j++) { + yo.last_one = (j + 1) == posc; + if (commands[i].exec_func(&ctx, &yo, posv[j])) { + break; + } + } + } else { + commands[i].exec_func(&ctx, &yo, NULL); + } + if (commands[i].fin_func) { + commands[i].fin_func(ctx, &yo); + } + + break; + } + + if (!cmd_found) { + /* if unknown command specified, tell it to user */ + YLMSG_E("Unknown command \"%.*s\", type 'help' for more information.", cmdlen, cmdline); + } + + linenoiseHistoryAdd(cmdline); + free(cmdline); + yl_opt_erase(&yo); + } + + /* Global variables in commands are freed. */ + cmd_free(); + + 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..9bdb8d9 --- /dev/null +++ b/tools/lint/main_ni.c @@ -0,0 +1,790 @@ +/** + * @file main_ni.c + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> + * @brief libyang's yanglint tool - non-interactive code + * + * Copyright (c) 2020 - 2023 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 "cmd.h" +#include "common.h" +#include "out.h" +#include "tools/config.h" +#include "yl_opt.h" +#include "yl_schema_features.h" + +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" + " To validate nested-notification or action, the <operational-file> is required.\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(" -I 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\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. Subdirectories\n" + " are also searched\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(" -E XPATH, --data-xpath=XPATH\n" + " Evaluate XPATH expression over the data and print the nodes satisfying\n" + " the 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"); + + 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 notification or action, its parent existence\n" + " is also 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"); + + printf(" -X, --extended-leafref\n" + " Allow usage of deref() XPath function within leafref\n\n"); + + printf(" -J, --json-null\n" + " Allow usage of JSON empty values ('null') within input data\n\n"); + + printf(" -G GROUPS, --debug=GROUPS\n" +#ifndef NDEBUG + " 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" +#else + " Unsupported for the Release build\n\n" +#endif + ); +} + +static void +libyang_verbclb(LY_LOG_LEVEL level, const char *msg, const char *data_path, const char *schema_path, uint64_t line) +{ + 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 (data_path) { + fprintf(stderr, "libyang %s %s (%s)\n", levstr, msg, data_path); + } else if (schema_path) { + fprintf(stderr, "libyang %s %s (%s)\n", levstr, msg, schema_path); + } else if (line) { + fprintf(stderr, "libyang %s %s (line %" PRIu64 ")\n", levstr, msg, line); + } else { + fprintf(stderr, "libyang %s %s\n", levstr, msg); + } +} + +static struct yl_schema_features * +get_features_not_applied(const struct ly_set *fset) +{ + for (uint32_t u = 0; u < fset->count; ++u) { + struct yl_schema_features *sf = fset->objs[u]; + + if (!sf->applied) { + return sf; + } + } + + return NULL; +} + +/** + * @brief Create the libyang context. + * + * @param[in] yang_lib_file Context can be defined in yang library file. + * @param[in] searchpaths Directories in which modules are searched. + * @param[in,out] schema_features Set of features. + * @param[in,out] ctx_options Options for libyang context. + * @param[out] ctx Context for libyang. + * @return 0 on success. + */ +static int +create_ly_context(const char *yang_lib_file, const char *searchpaths, struct ly_set *schema_features, + uint16_t *ctx_options, struct ly_ctx **ctx) +{ + if (yang_lib_file) { + /* ignore features */ + ly_set_erase(schema_features, yl_schema_features_free); + + if (ly_ctx_new_ylpath(searchpaths, yang_lib_file, LYD_UNKNOWN, *ctx_options, ctx)) { + YLMSG_E("Unable to modify libyang context with yang-library data."); + return -1; + } + } else { + /* set imp feature flag if all should be enabled */ + (*ctx_options) |= !schema_features->count ? LY_CTX_ENABLE_IMP_FEATURES : 0; + + if (ly_ctx_new(searchpaths, *ctx_options, ctx)) { + YLMSG_E("Unable to create libyang context."); + return -1; + } + } + + return 0; +} + +/** + * @brief Implement module if some feature has not been applied. + * + * @param[in] schema_features Set of features. + * @param[in,out] ctx Context for libyang. + * @return 0 on success. + */ +static int +apply_features(struct ly_set *schema_features, struct ly_ctx *ctx) +{ + struct yl_schema_features *sf; + struct lys_module *mod; + + /* check that all specified features were applied, apply now if possible */ + while ((sf = get_features_not_applied(schema_features))) { + /* try to find implemented or the latest revision of this module */ + mod = ly_ctx_get_module_implemented(ctx, sf->mod_name); + if (!mod) { + mod = ly_ctx_get_module_latest(ctx, sf->mod_name); + } + if (!mod) { + YLMSG_E("Specified features not applied, module \"%s\" not loaded.", sf->mod_name); + return 1; + } + + /* 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.", mod->name); + return 1; + } + sf->applied = 1; + } + + return 0; +} + +/** + * @brief Parse and compile modules, data are only stored for later processing. + * + * @param[in] argc Number of strings in @p argv. + * @param[in] argv Strings from command line. + * @param[in] optind Index to the first input file in @p argv. + * @param[in] data_in_format Specified input data format. + * @param[in,out] ctx Context for libyang. + * @param[in,out] yo Options for yanglint. + * @return 0 on success. + */ +static int +fill_context_inputs(int argc, char *argv[], int optind, LYD_FORMAT data_in_format, struct ly_ctx *ctx, + struct yl_opt *yo) +{ + char *filepath = NULL; + LYS_INFORMAT format_schema; + LYD_FORMAT format_data; + + for (int i = 0; i < argc - optind; i++) { + format_schema = LYS_IN_UNKNOWN; + format_data = data_in_format; + + filepath = argv[optind + i]; + + if (!filepath) { + return -1; + } + if (get_format(filepath, &format_schema, &format_data)) { + return -1; + } + + if (format_schema) { + if (cmd_add_exec(&ctx, yo, filepath)) { + return -1; + } + } else { + if (cmd_data_store(&ctx, yo, filepath)) { + return -1; + } + } + } + + /* Check that all specified features were applied, apply now if possible. */ + if (apply_features(&yo->schema_features, ctx)) { + return -1; + } + + return 0; +} + +/** + * @brief Enable specific debugging messages. + * + * @param[in] groups String in the form "<group>[,group>]*". + * @param[in,out] yo Options for yanglint. + * return 0 on success. + */ +static int +set_debug_groups(char *groups, struct yl_opt *yo) +{ + int rc; + char *str, *end; + + /* Process all debug arguments except the last one. */ + for (str = groups; (end = strchr(str, ',')); str = end + 1) { + /* Temporary modify input string. */ + *end = '\0'; + rc = cmd_debug_store(NULL, yo, str); + *end = ','; + if (rc) { + return -1; + } + } + /* Process single/last debug argument. */ + if (cmd_debug_store(NULL, yo, str)) { + return -1; + } + /* All debug arguments are valid, so they can apply. */ + if (cmd_debug_setlog(NULL, yo)) { + return -1; + } + + return 0; +} + +/** + * @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 yl_opt *yo, struct ly_ctx **ctx) +{ + 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'}, + {"in-format", required_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'}, + {"data-xpath", required_argument, NULL, 'E'}, + {"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'}, + {"extended-leafref", no_argument, NULL, 'X'}, + {"json-null", no_argument, NULL, 'J'}, + {"debug", required_argument, NULL, 'G'}, + {NULL, 0, NULL, 0} + }; + uint8_t data_type_set = 0; + + yo->ctx_options = YL_DEFAULT_CTX_OPTIONS; + yo->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS; + yo->data_validate_options = YL_DEFAULT_DATA_VALIDATE_OPTIONS; + yo->line_length = 0; + + opterr = 0; + while ((opt = getopt_long(argc, argv, "hvVQf:I:p:DF:iP:qs:neE:t:d:lL:o:O:R:myY:XJx:G:", options, &opt_index)) != -1) { + 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 (yl_opt_update_out_format(optarg, yo)) { + help(1); + return -1; + } + break; + + case 'I': /* --in-format */ + if (yo_opt_update_data_in_format(optarg, yo)) { + YLMSG_E("Unknown input format %s.", optarg); + help(1); + return -1; + } + break; + + case 'p': /* --path */ + if (searchpath_strcat(&yo->searchpaths, optarg)) { + YLMSG_E("Storing searchpath failed."); + return -1; + } + break; + /* case 'p' */ + + case 'D': /* --disable-searchdir */ + if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) { + YLMSG_W("The -D option specified too many times."); + } + yo_opt_update_disable_searchdir(yo); + break; + + case 'F': /* --features */ + if (parse_features(optarg, &yo->schema_features)) { + return -1; + } + break; + + case 'i': /* --make-implemented */ + yo_opt_update_make_implemented(yo); + break; + + case 'P': /* --schema-node */ + yo->schema_node_path = optarg; + break; + + case 'q': /* --single-node */ + yo->schema_print_options |= LYS_PRINT_NO_SUBSTMT; + break; + + case 's': /* --submodule */ + yo->submodule = optarg; + break; + + case 'x': /* --ext-data */ + yo->schema_context_filename = optarg; + break; + + case 'n': /* --not-strict */ + yo->data_parse_options &= ~LYD_PARSE_STRICT; + break; + + case 'e': /* --present */ + yo->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."); + return -1; + } + + if (yl_opt_update_data_type(optarg, yo)) { + YLMSG_E("Unknown data tree type %s.", optarg); + help(1); + return -1; + } + + data_type_set = 1; + break; + + case 'd': /* --default */ + if (yo_opt_update_data_default(optarg, yo)) { + YLMSG_E("Unknown default mode %s.", optarg); + help(1); + return -1; + } + break; + + case 'E': /* --data-xpath */ + if (ly_set_add(&yo->data_xpath, optarg, 0, NULL)) { + YLMSG_E("Storing XPath \"%s\" failed.", optarg); + return -1; + } + break; + + case 'l': /* --list */ + yo->list = 1; + break; + + case 'L': /* --tree-line-length */ + yo->line_length = atoi(optarg); + break; + + case 'o': /* --output */ + if (yo->out) { + YLMSG_E("Only a single output can be specified."); + return -1; + } else { + if (ly_out_new_filepath(optarg, &yo->out)) { + YLMSG_E("Unable open output file %s (%s).", optarg, strerror(errno)); + return -1; + } + } + break; + + case 'O': /* --operational */ + if (yo->data_operational.path) { + YLMSG_E("The operational datastore (-O) cannot be set multiple times."); + return -1; + } + yo->data_operational.path = optarg; + break; + + case 'R': /* --reply-rpc */ + if (yo->reply_rpc.path) { + YLMSG_E("The PRC of the reply (-R) cannot be set multiple times."); + return -1; + } + yo->reply_rpc.path = optarg; + break; + + case 'm': /* --merge */ + yo->data_merge = 1; + break; + + case 'y': /* --yang-library */ + yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; + break; + + case 'Y': /* --yang-library-file */ + yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; + yo->yang_lib_file = optarg; + break; + + case 'X': /* --extended-leafref */ + yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED; + break; + + case 'J': /* --json-null */ + yo->data_parse_options |= LYD_PARSE_JSON_NULL; + break; + + case 'G': /* --debug */ + if (set_debug_groups(optarg, yo)) { + return -1; + } + break; + + default: + YLMSG_E("Invalid option or missing argument: -%c.", optopt); + return -1; + } /* switch */ + } + + /* additional checks for the options combinations */ + if (!yo->list && (optind >= argc)) { + help(1); + YLMSG_E("Missing <schema> to process."); + return 1; + } + + if (cmd_data_dep(yo, 0)) { + return -1; + } + if (cmd_print_dep(yo, 0)) { + return -1; + } + + /* Create the libyang context. */ + if (create_ly_context(yo->yang_lib_file, yo->searchpaths, &yo->schema_features, &yo->ctx_options, ctx)) { + return -1; + } + + /* Set callback providing run-time extension instance data. */ + if (yo->schema_context_filename) { + ly_ctx_set_ext_data_clb(*ctx, ext_data_clb, yo->schema_context_filename); + } + + /* Schema modules and data files are just checked and prepared into internal structures for further processing. */ + if (fill_context_inputs(argc, argv, optind, yo->data_in_format, *ctx, yo)) { + return -1; + } + + /* the second batch of checks */ + if (yo->schema_print_options && !yo->schema_out_format) { + YLMSG_W("Schema printer options specified, but the schema output format is missing."); + } + if (yo->schema_parse_options && !yo->schema_modules.count) { + YLMSG_W("Schema parser options specified, but no schema input file provided."); + } + if (yo->data_print_options && !yo->data_out_format) { + YLMSG_W("data printer options specified, but the data output format is missing."); + } + if (((yo->data_parse_options != YL_DEFAULT_DATA_PARSE_OPTIONS) || yo->data_type) && !yo->data_inputs.count) { + YLMSG_W("Data parser options specified, but no data input file provided."); + } + + return 0; +} + +int +main_ni(int argc, char *argv[]) +{ + int ret = EXIT_SUCCESS, r; + struct yl_opt yo = {0}; + struct ly_ctx *ctx = NULL; + char *features_output = NULL; + struct ly_set set = {0}; + uint32_t u; + + /* set callback for printing libyang messages */ + ly_set_log_clb(libyang_verbclb); + + r = fill_context(argc, argv, &yo, &ctx); + if (r < 0) { + ret = EXIT_FAILURE; + } + if (r) { + goto cleanup; + } + + /* do the required job - parse, validate, print */ + + if (yo.list) { + /* print the list of schemas */ + ret = cmd_list_exec(&ctx, &yo, NULL); + goto cleanup; + } + if (yo.feature_param_format) { + for (u = 0; u < yo.schema_modules.count; u++) { + if ((ret = cmd_feature_exec(&ctx, &yo, ((struct lys_module *)yo.schema_modules.objs[u])->name))) { + goto cleanup; + } + } + cmd_feature_fin(ctx, &yo); + } else if (yo.schema_out_format && yo.schema_node_path) { + if ((ret = cmd_print_exec(&ctx, &yo, NULL))) { + goto cleanup; + } + } else if (yo.schema_out_format && yo.submodule) { + if ((ret = cmd_print_exec(&ctx, &yo, yo.submodule))) { + goto cleanup; + } + } else if (yo.schema_out_format) { + for (u = 0; u < yo.schema_modules.count; ++u) { + yo.last_one = (u + 1) == yo.schema_modules.count; + if ((ret = cmd_print_exec(&ctx, &yo, ((struct lys_module *)yo.schema_modules.objs[u])->name))) { + goto cleanup; + } + } + } + + /* do the data validation despite the schema was printed */ + if (yo.data_inputs.size) { + if ((ret = cmd_data_process(ctx, &yo))) { + goto cleanup; + } + } + +cleanup: + /* cleanup */ + yl_opt_erase(&yo); + ly_ctx_destroy(ctx); + 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/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/lint/yl_opt.c b/tools/lint/yl_opt.c new file mode 100644 index 0000000..7cd855f --- /dev/null +++ b/tools/lint/yl_opt.c @@ -0,0 +1,344 @@ +/** + * @file yl_opt.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief Settings options for the libyang context. + * + * Copyright (c) 2020 - 2023 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 <assert.h> +#include <errno.h> +#include <getopt.h> +#include <strings.h> + +#include "in.h" /* ly_in_free */ + +#include "common.h" +#include "yl_opt.h" +#include "yl_schema_features.h" + +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."); + 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."); + return NULL; + } + + return rec; +} + +void +free_cmdline_file_items(struct cmdline_file *rec) +{ + if (rec && rec->in) { + ly_in_free(rec->in, 1); + } +} + +void +free_cmdline_file(void *cmdline_file) +{ + struct cmdline_file *rec = (struct cmdline_file *)cmdline_file; + + if (rec) { + free_cmdline_file_items(rec); + free(rec); + } +} + +void +yl_opt_erase(struct yl_opt *yo) +{ + ly_bool interactive; + + interactive = yo->interactive; + + /* data */ + ly_set_erase(&yo->data_inputs, free_cmdline_file); + ly_in_free(yo->data_operational.in, 1); + ly_set_erase(&yo->data_xpath, NULL); + + /* schema */ + ly_set_erase(&yo->schema_features, yl_schema_features_free); + ly_set_erase(&yo->schema_modules, NULL); + + /* context */ + free(yo->searchpaths); + + /* --reply-rpc */ + ly_in_free(yo->reply_rpc.in, 1); + + ly_out_free(yo->out, NULL, yo->out_stdout ? 0 : 1); + + free_cmdline(yo->argv); + + *yo = (const struct yl_opt) { + 0 + }; + yo->interactive = interactive; +} + +int +yl_opt_update_schema_out_format(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "yang")) { + yo->schema_out_format = LYS_OUT_YANG; + yo->data_out_format = 0; + } else if (!strcasecmp(arg, "yin")) { + yo->schema_out_format = LYS_OUT_YIN; + yo->data_out_format = 0; + } else if (!strcasecmp(arg, "info")) { + yo->schema_out_format = LYS_OUT_YANG_COMPILED; + yo->data_out_format = 0; + } else if (!strcasecmp(arg, "tree")) { + yo->schema_out_format = LYS_OUT_TREE; + yo->data_out_format = 0; + } else { + return 1; + } + + return 0; +} + +int +yl_opt_update_data_out_format(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "xml")) { + yo->schema_out_format = 0; + yo->data_out_format = LYD_XML; + } else if (!strcasecmp(arg, "json")) { + yo->schema_out_format = 0; + yo->data_out_format = LYD_JSON; + } else if (!strcasecmp(arg, "lyb")) { + yo->schema_out_format = 0; + yo->data_out_format = LYD_LYB; + } else { + return 1; + } + + return 0; +} + +static int +yl_opt_update_other_out_format(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "feature-param")) { + yo->feature_param_format = 1; + } else { + return 1; + } + + return 0; +} + +int +yl_opt_update_out_format(const char *arg, struct yl_opt *yo) +{ + if (!yl_opt_update_schema_out_format(arg, yo)) { + return 0; + } + if (!yl_opt_update_data_out_format(arg, yo)) { + return 0; + } + if (!yl_opt_update_other_out_format(arg, yo)) { + return 0; + } + + YLMSG_E("Unknown output format %s.", arg); + return 1; +} + +int +yl_opt_update_data_type(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "config")) { + yo->data_parse_options |= LYD_PARSE_NO_STATE; + yo->data_validate_options |= LYD_VALIDATE_NO_STATE; + } else if (!strcasecmp(arg, "get")) { + yo->data_parse_options |= LYD_PARSE_ONLY; + } else if (!strcasecmp(arg, "getconfig") || !strcasecmp(arg, "get-config") || !strcasecmp(arg, "edit")) { + yo->data_parse_options |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE; + } else if (!strcasecmp(arg, "rpc") || !strcasecmp(arg, "action")) { + yo->data_type = LYD_TYPE_RPC_YANG; + } else if (!strcasecmp(arg, "nc-rpc")) { + yo->data_type = LYD_TYPE_RPC_NETCONF; + } else if (!strcasecmp(arg, "reply") || !strcasecmp(arg, "rpcreply")) { + yo->data_type = LYD_TYPE_REPLY_YANG; + } else if (!strcasecmp(arg, "nc-reply")) { + yo->data_type = LYD_TYPE_REPLY_NETCONF; + } else if (!strcasecmp(arg, "notif") || !strcasecmp(arg, "notification")) { + yo->data_type = LYD_TYPE_NOTIF_YANG; + } else if (!strcasecmp(arg, "nc-notif")) { + yo->data_type = LYD_TYPE_NOTIF_NETCONF; + } else if (!strcasecmp(arg, "data")) { + /* default option */ + } else { + return 1; + } + + return 0; +} + +int +yo_opt_update_data_default(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "all")) { + yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL; + } else if (!strcasecmp(arg, "all-tagged")) { + yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG; + } else if (!strcasecmp(arg, "trim")) { + yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM; + } else if (!strcasecmp(arg, "implicit-tagged")) { + yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG; + } else { + return 1; + } + + return 0; +} + +int +yo_opt_update_data_in_format(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "xml")) { + yo->data_in_format = LYD_XML; + } else if (!strcasecmp(arg, "json")) { + yo->data_in_format = LYD_JSON; + } else if (!strcasecmp(arg, "lyb")) { + yo->data_in_format = LYD_LYB; + } else { + return 1; + } + + return 0; +} + +void +yo_opt_update_make_implemented(struct yl_opt *yo) +{ + if (yo->ctx_options & LY_CTX_REF_IMPLEMENTED) { + yo->ctx_options &= ~LY_CTX_REF_IMPLEMENTED; + yo->ctx_options |= LY_CTX_ALL_IMPLEMENTED; + } else { + yo->ctx_options |= LY_CTX_REF_IMPLEMENTED; + } +} + +void +yo_opt_update_disable_searchdir(struct yl_opt *yo) +{ + if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIR_CWD) { + yo->ctx_options &= ~LY_CTX_DISABLE_SEARCHDIR_CWD; + yo->ctx_options |= LY_CTX_DISABLE_SEARCHDIRS; + } else { + yo->ctx_options |= LY_CTX_DISABLE_SEARCHDIR_CWD; + } +} + +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).", __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; +} diff --git a/tools/lint/yl_opt.h b/tools/lint/yl_opt.h new file mode 100644 index 0000000..d66ae4d --- /dev/null +++ b/tools/lint/yl_opt.h @@ -0,0 +1,237 @@ +/** + * @file yl_opt.h + * @author Adam Piecek <piecek@cesnet.cz> + * @brief Settings options for the libyang context. + * + * Copyright (c) 2020 - 2023 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 YL_OPT_H_ +#define YL_OPT_H_ + +#include "parser_data.h" /* enum lyd_type */ +#include "printer_schema.h" /* LYS_OUTFORMAT */ +#include "set.h" /* ly_set */ + +/** + * @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 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 Free the command line file data items. + * @param[in,out] rec record to free. + */ +void free_cmdline_file_items(struct cmdline_file *rec); + +/** + * @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 Context structure to hold and pass variables in a structured form. + */ +struct yl_opt { + /* Set to 1 if yanglint running in the interactive mode */ + ly_bool interactive; + ly_bool last_one; + + /* libyang context for the run */ + char *yang_lib_file; + uint16_t ctx_options; + + /* prepared output (--output option or stdout by default) */ + ly_bool out_stdout; + struct ly_out *out; + + char *searchpaths; + ly_bool searchdir_unset; + + /* options flags */ + uint8_t list; /* -l option to print list of schemas */ + + /* line length for 'tree' format */ + size_t line_length; /* --tree-line-length */ + + uint32_t dbg_groups; + + /* + * 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 */ + char *schema_node_path; + 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; + ly_bool extdata_unset; + + /* value of --format in case of schema format */ + LYS_OUTFORMAT schema_out_format; + ly_bool feature_param_format; + ly_bool feature_print_all; + + /* + * 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; + + /* value of --in-format in case of data format */ + LYD_FORMAT data_in_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; + + /* storage for --data-xpath */ + struct ly_set data_xpath; + + char **argv; +}; + +/** + * @brief Erase all values in @p opt. + * + * The yl_opt.interactive item is not deleted. + * + * @param[in,out] yo Option context to erase. + */ +void yl_opt_erase(struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the schema --format parameter. + * + * @param[in] arg Format parameter argument (for example yang, yin, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yl_opt_update_schema_out_format(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the data --format parameter. + * + * @param[in] arg Format parameter argument (for example xml, json, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yl_opt_update_data_out_format(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the general --format parameter. + * + * @param[in] arg Format parameter argument (for example yang, xml, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yl_opt_update_out_format(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the data --type parameter. + * + * @param[in] arg Format parameter argument (for example config, rpc, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yl_opt_update_data_type(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the data --default parameter. + * + * @param[in] arg Format parameter argument (for example all, trim, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yo_opt_update_data_default(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the data --in-format parameter. + * + * @param[in] arg Format parameter argument (for example xml, json, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yo_opt_update_data_in_format(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the --make-implemented parameter. + * + * @param[in,out] yo yanglint options used to update. + */ +void yo_opt_update_make_implemented(struct yl_opt *yo); + +/** + * @brief Update @p yo according to the --disable-searchdir parameter. + * + * @param[in,out] yo yanglint options used to update. + */ +void yo_opt_update_disable_searchdir(struct yl_opt *yo); + +/** + * @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[]); + +#endif /* YL_OPT_H_ */ diff --git a/tools/lint/yl_schema_features.c b/tools/lint/yl_schema_features.c new file mode 100644 index 0000000..74f88b9 --- /dev/null +++ b/tools/lint/yl_schema_features.c @@ -0,0 +1,212 @@ +/** + * @file yl_schema_features.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief Control features for the schema. + * + * Copyright (c) 2023 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 <stdlib.h> /* calloc */ +#include <string.h> /* strcmp */ + +#include "compat.h" /* strndup */ +#include "set.h" /* ly_set */ + +#include "common.h" +#include "yl_schema_features.h" + +void +yl_schema_features_free(void *flist) +{ + struct yl_schema_features *rec = (struct yl_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(const 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 yl_schema_features *sf = (struct yl_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 yl_schema_features *rec = NULL; + uint32_t count; + char *p, **fp; + + rec = calloc(1, sizeof *rec); + if (!rec) { + YLMSG_E("Unable to allocate features information record (%s).", strerror(errno)); + goto error; + } + + /* fill the record */ + p = strchr(fstring, ':'); + if (!p) { + YLMSG_E("Invalid format of the features specification (%s).", fstring); + goto error; + } + 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).", strerror(errno)); + goto error; + } + 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).", strerror(errno)); + goto error; + } + rec->features = fp; + rec->features[count++] = NULL; + + /* Store record to the output set. */ + if (ly_set_add(fset, rec, 1, NULL)) { + YLMSG_E("Unable to store features information (%s).", strerror(errno)); + goto error; + } + rec = NULL; + + return 0; + +error: + yl_schema_features_free(rec); + return -1; +} + +void +print_features(struct ly_out *out, const struct lys_module *mod) +{ + struct lysp_feature *f; + uint32_t idx; + size_t max_len, len; + + ly_print(out, "%s:\n", mod->name); + + /* get max len, so the statuses of all the features will be aligned */ + max_len = 0, idx = 0, f = NULL; + while ((f = lysp_feature_next(f, mod->parsed, &idx))) { + len = strlen(f->name); + max_len = (max_len > len) ? max_len : len; + } + if (!max_len) { + ly_print(out, "\t(none)\n"); + return; + } + + /* print features */ + idx = 0, f = NULL; + while ((f = lysp_feature_next(f, mod->parsed, &idx))) { + ly_print(out, "\t%-*s (%s)\n", (int)max_len, f->name, lys_feature_value(mod, f->name) ? "off" : "on"); + } +} + +void +print_feature_param(struct ly_out *out, const struct lys_module *mod) +{ + struct lysp_feature *f = NULL; + uint32_t idx = 0; + uint8_t first = 1; + + ly_print(out, " -F %s:", mod->name); + while ((f = lysp_feature_next(f, mod->parsed, &idx))) { + if (first) { + ly_print(out, "%s", f->name); + first = 0; + } else { + ly_print(out, ",%s", f->name); + } + } +} + +void +print_all_features(struct ly_out *out, const struct ly_ctx *ctx, uint8_t feature_param) +{ + uint32_t i; + struct lys_module *mod; + uint8_t first; + + /* Print features for all implemented modules. */ + first = 1; + i = 0; + while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) { + if (!mod->implemented) { + continue; + } + if (first) { + print_features(out, mod); + first = 0; + } else { + ly_print(out, "\n"); + print_features(out, mod); + } + } + + if (!feature_param) { + return; + } + ly_print(out, "\n"); + + /* Print features for all implemented modules in 'feature-param' format. */ + i = 0; + while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) { + if (mod->implemented) { + print_feature_param(out, mod); + } + } +} diff --git a/tools/lint/yl_schema_features.h b/tools/lint/yl_schema_features.h new file mode 100644 index 0000000..7bfe9fd --- /dev/null +++ b/tools/lint/yl_schema_features.h @@ -0,0 +1,84 @@ +/** + * @file yl_schema_features.h + * @author Adam Piecek <piecek@cesnet.cz> + * @brief Control features for the schema. + * + * Copyright (c) 2023 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 YL_SCHEMA_FEATURES_H_ +#define YL_SCHEMA_FEATURES_H_ + +#include <stdint.h> + +struct ly_set; +struct lys_module; +struct ly_out; +struct ly_ctx; + +/** + * @brief Storage for the list of the features (their names) in a specific YANG module. + */ +struct yl_schema_features { + char *mod_name; + char **features; + uint8_t applied; +}; + +/** + * @brief Free the schema features list (struct schema_features *) + * @param[in,out] flist The (struct schema_features *) to free. + */ +void yl_schema_features_free(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(const 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 Print all features of a single module. + * + * @param[in] out The output handler for printing. + * @param[in] mod Module which can contains the features. + */ +void print_features(struct ly_out *out, const struct lys_module *mod); + +/** + * @brief Print all features in the 'feature-param' format. + * + * @param[in] out The output handler for printing. + * @param[in] mod Module which can contains the features. + */ +void print_feature_param(struct ly_out *out, const struct lys_module *mod); + +/** + * @brief Print all features of all implemented modules. + * + * @param[in] out The output handler for printing. + * @param[in] ctx Libyang context. + * @param[in] feature_param Flag expressing whether to print features parameter. + */ +void print_all_features(struct ly_out *out, const struct ly_ctx *ctx, uint8_t feature_param); + +#endif /* YL_SCHEMA_FEATURES_H_ */ 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..5d1edd5 --- /dev/null +++ b/tools/re/main.c @@ -0,0 +1,461 @@ +/** + * @file main.c + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@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" + +struct yr_pattern { + char *expr; + ly_bool invert; +}; + +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) or if otherwise successful.\n"); + fprintf(stdout, "Returns 1 on error.\n"); + fprintf(stdout, "Returns 2 if string does not match the pattern(s).\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 *UNUSED(data_path), const char *UNUSED(schema_path), + uint64_t UNUSED(line)) +{ + if (level == LY_LLERR) { + fprintf(stderr, "yangre error: %s\n", msg); + } +} + +static int +add_pattern(struct yr_pattern **patterns, int *counter, char *pattern) +{ + void *reallocated; + int orig_counter; + + /* Store the original number of items. */ + orig_counter = *counter; + + /* Reallocate 'patterns' memory with additional space. */ + reallocated = realloc(*patterns, (orig_counter + 1) * sizeof **patterns); + if (!reallocated) { + goto error; + } + (*patterns) = reallocated; + /* Allocated memory is now larger. */ + (*counter)++; + /* Copy the pattern and store it to the additonal space. */ + (*patterns)[orig_counter].expr = strdup(pattern); + if (!(*patterns)[orig_counter].expr) { + goto error; + } + (*patterns)[orig_counter].invert = 0; + + return 0; + +error: + fprintf(stderr, "yangre error: memory allocation error.\n"); + return 1; +} + +static int +create_empty_string(char **str) +{ + free(*str); + *str = malloc(sizeof(char)); + if (!(*str)) { + fprintf(stderr, "yangre error: memory allocation failed.\n"); + return 1; + } + (*str)[0] = '\0'; + + return 0; +} + +static ly_bool +file_is_empty(FILE *fp) +{ + int c; + + c = fgetc(fp); + if (c == EOF) { + return 1; + } else { + ungetc(c, fp); + return 0; + } +} + +/** + * @brief Open the @p filepath, parse patterns and given string-argument. + * + * @param[in] filepath File to parse. Contains patterns and string. + * @param[out] infile The file descriptor of @p filepath. + * @param[out] patterns Storage of patterns. + * @param[out] patterns_count Number of items in @p patterns. + * @param[out] strarg The string-argument to check. + * @return 0 on success. + */ +static int +parse_patterns_file(const char *filepath, FILE **infile, struct yr_pattern **patterns, int *patterns_count, char **strarg) +{ + int blankline = 0; + char *str = NULL; + size_t len = 0; + ssize_t l; + + *infile = fopen(filepath, "rb"); + if (!(*infile)) { + fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno)); + goto error; + } + if (file_is_empty(*infile)) { + if (create_empty_string(strarg)) { + goto error; + } + return 0; + } + + while ((l = getline(&str, &len, *infile)) != -1) { + if (!blankline && ((str[0] == '\n') || ((str[0] == '\r') && (str[1] == '\n')))) { + /* blank line */ + blankline = 1; + continue; + } + if ((str[0] != '\n') && (str[0] != '\r') && (str[l - 1] == '\n')) { + /* remove ending newline */ + if ((l > 1) && (str[l - 2] == '\r') && (str[l - 1] == '\n')) { + str[l - 2] = '\0'; + } else { + str[l - 1] = '\0'; + } + } + if (blankline) { + /* done - str is now the string to check */ + blankline = 0; + *strarg = str; + break; + /* else read the patterns */ + } else if (add_pattern(patterns, patterns_count, (str[0] == ' ') ? &str[1] : str)) { + goto error; + } + if (str[0] == ' ') { + /* set invert-match */ + (*patterns)[*patterns_count - 1].invert = 1; + } + } + if (!str || (blankline && (str[0] != '\0'))) { + /* corner case, no input after blankline meaning the pattern to check is empty */ + if (create_empty_string(&str)) { + goto error; + } + } + *strarg = str; + + return 0; + +error: + free(str); + if (*infile) { + fclose(*infile); + *infile = NULL; + } + *strarg = NULL; + + return 1; +} + +static char * +modstr_init(void) +{ + const char *module_start = "module yangre {" + "yang-version 1.1;" + "namespace urn:cesnet:libyang:yangre;" + "prefix re;" + "leaf pattern {" + " type string {"; + + return strdup(module_start); +} + +static char * +modstr_add_pattern(char **modstr, const struct yr_pattern *pattern) +{ + char *new; + const char *module_invertmatch = " { modifier invert-match; }"; + const char *module_match = ";"; + + if (asprintf(&new, "%s pattern %s%s", *modstr, pattern->expr, + pattern->invert ? module_invertmatch : module_match) == -1) { + fprintf(stderr, "yangre error: memory allocation failed.\n"); + return NULL; + } + free(*modstr); + *modstr = NULL; + + return new; +} + +static char * +modstr_add_ending(char **modstr) +{ + char *new; + static const char *module_end = "}}}"; + + if (asprintf(&new, "%s%s", *modstr, module_end) == -1) { + fprintf(stderr, "yangre error: memory allocation failed.\n"); + return NULL; + } + free(*modstr); + *modstr = NULL; + + return new; +} + +static int +create_module(struct yr_pattern *patterns, int patterns_count, char **mod) +{ + int i; + char *new = NULL, *modstr; + + if (!(modstr = modstr_init())) { + goto error; + } + + for (i = 0; i < patterns_count; i++) { + if (!(new = modstr_add_pattern(&modstr, &patterns[i]))) { + goto error; + } + modstr = new; + } + + if (!(new = modstr_add_ending(&modstr))) { + goto error; + } + + *mod = new; + + return 0; + +error: + *mod = NULL; + free(new); + free(modstr); + + return 1; +} + +static void +print_verbose(struct yr_pattern *patterns, int patterns_count, char *str, LY_ERR match) +{ + int i; + + for (i = 0; i < patterns_count; i++) { + fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i].expr); + fprintf(stdout, "matching %d: %s\n", i + 1, patterns[i].invert ? "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_last_logmsg()); + } +} + +int +main(int argc, char *argv[]) +{ + LY_ERR match; + int i, opt_index = 0, ret = 1, verbose = 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} + }; + struct yr_pattern *patterns = NULL; + char *str = NULL, *modstr = NULL; + int patterns_count = 0; + struct ly_ctx *ctx = NULL; + struct lys_module *mod; + FILE *infile = NULL; + ly_bool info_printed = 0; + + opterr = 0; + while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) { + switch (i) { + case 'h': + help(); + info_printed = 1; + 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; + } + if (parse_patterns_file(optarg, &infile, &patterns, &patterns_count, &str)) { + goto cleanup; + } + break; + case 'i': + if (!patterns_count || patterns[patterns_count - 1].invert) { + help(); + fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n"); + goto cleanup; + } + patterns[patterns_count - 1].invert = 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, &patterns_count, optarg)) { + goto cleanup; + } + break; + case 'v': + version(); + info_printed = 1; + 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 (info_printed) { + ret = 0; + 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]; + } + + if (create_module(patterns, patterns_count, &modstr)) { + goto cleanup; + } + + if (ly_ctx_new(NULL, 0, &ctx)) { + goto cleanup; + } + + ly_set_log_clb(pattern_error); + 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) { + print_verbose(patterns, patterns_count, str, match); + } + if (match == LY_SUCCESS) { + ret = 0; + } else if (match == LY_EVALID) { + ret = 2; + } else { + ret = 1; + } + +cleanup: + ly_ctx_destroy(ctx); + for (i = 0; i < patterns_count; i++) { + free(patterns[i].expr); + } + if (patterns_count) { + free(patterns); + } + 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. |