diff options
Diffstat (limited to 'tools')
71 files changed, 12565 insertions, 0 deletions
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..b92f80c --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,15 @@ +# config file for tools +configure_file(${PROJECT_SOURCE_DIR}/tools/config.h.in ${PROJECT_BINARY_DIR}/tools/config.h @ONLY) + +if(WIN32) + find_library(GETOPT_LIBRARY NAMES getopt REQUIRED) + find_path(GETOPT_INCLUDE_DIR NAMES getopt.h REQUIRED) + message(STATUS "Found <getopt.h> at ${GETOPT_INCLUDE_DIR}, library at ${GETOPT_LIBRARY}") +endif() + +add_subdirectory(lint) +add_subdirectory(re) + +set(format_sources + ${format_sources} + PARENT_SCOPE) diff --git a/tools/config.h.in b/tools/config.h.in new file mode 100644 index 0000000..603a42c --- /dev/null +++ b/tools/config.h.in @@ -0,0 +1,20 @@ +/** + * @file config.h + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief various variables detected by cmake + * + * Copyright (c) 2019 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef YANGLINT_CONFIG_H_ +#define YANGLINT_CONFIG_H_ + +#define PROJECT_VERSION "@LIBYANG_VERSION@" /**< libyang project version string */ + +#endif /* YANGLINT_CONFIG_H_ */ diff --git a/tools/lint/CMakeLists.txt b/tools/lint/CMakeLists.txt new file mode 100644 index 0000000..32cdcbf --- /dev/null +++ b/tools/lint/CMakeLists.txt @@ -0,0 +1,90 @@ +# yanglint + +if(WIN32) + set(YANGLINT_INTERACTIVE OFF) +else() + set(YANGLINT_INTERACTIVE ON) +endif() + +set(lintsrc + main_ni.c + cmd.c + cmd_add.c + cmd_clear.c + cmd_data.c + cmd_list.c + cmd_feature.c + cmd_load.c + cmd_print.c + cmd_searchpath.c + common.c +) +if(YANGLINT_INTERACTIVE) + set(lintsrc ${lintsrc} + main.c + completion.c + configuration.c + linenoise/linenoise.c) +else() + set(lintsrc ${lintsrc} + main_ni_only.c) +endif() + +set(format_sources + ${format_sources} + ${CMAKE_CURRENT_SOURCE_DIR}/*.c + ${CMAKE_CURRENT_SOURCE_DIR}/*.h + PARENT_SCOPE) + +add_executable(yanglint ${lintsrc} ${compatsrc}) +target_link_libraries(yanglint yang) +install(TARGETS yanglint DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(FILES ${PROJECT_SOURCE_DIR}/tools/lint/yanglint.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) +target_include_directories(yanglint BEFORE PRIVATE ${PROJECT_BINARY_DIR}) + +if(WIN32) + target_include_directories(yanglint PRIVATE ${GETOPT_INCLUDE_DIR}) + target_link_libraries(yanglint ${GETOPT_LIBRARY}) +endif() + +# +# tests +# +function(add_yanglint_test) + cmake_parse_arguments(ADDTEST "" "NAME;VIA;SCRIPT" "" ${ARGN}) + set(TEST_NAME yanglint_${ADDTEST_NAME}) + + if(${ADDTEST_VIA} STREQUAL "bash") + set(WRAPPER /usr/bin/env bash) + elseif(${ADDTEST_VIA} STREQUAL "expect") + set(WRAPPER ${PATH_EXPECT}) + else() + message(FATAL_ERROR "build: unexpected wrapper '${ADDTEST_VIA}'") + endif() + + add_test(NAME ${TEST_NAME} COMMAND ${WRAPPER} ${CMAKE_CURRENT_SOURCE_DIR}/tests/${ADDTEST_SCRIPT}) + set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANGLINT=${PROJECT_BINARY_DIR}/yanglint") + set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANG_MODULES_DIR=${PROJECT_SOURCE_DIR}/tests/modules/yang") + set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}") +endfunction(add_yanglint_test) + +if(ENABLE_TESTS) + # tests of non-interactive mode using shunit2 + find_program(PATH_SHUNIT NAMES shunit2) + if(NOT PATH_SHUNIT) + message(WARNING "'shunit2' not found! The yanglint(1) non-interactive tests will not be available.") + else() + add_yanglint_test(NAME ni_list VIA bash SCRIPT shunit2/list.sh) + add_yanglint_test(NAME ni_feature VIA bash SCRIPT shunit2/feature.sh) + endif() + + # tests of interactive mode using expect + find_program(PATH_EXPECT NAMES expect) + if(NOT PATH_EXPECT) + message(WARNING "'expect' not found! The yanglint(1) interactive tests will not be available.") + elseif(YANGLINT_INTERACTIVE) + add_yanglint_test(NAME in_list VIA expect SCRIPT expect/list.exp) + add_yanglint_test(NAME in_feature VIA expect SCRIPT expect/feature.exp) + add_yanglint_test(NAME in_completion VIA expect SCRIPT expect/completion.exp) + endif() +endif() diff --git a/tools/lint/cmd.c b/tools/lint/cmd.c new file mode 100644 index 0000000..10e7446 --- /dev/null +++ b/tools/lint/cmd.c @@ -0,0 +1,259 @@ +/** + * @file cmd.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief libyang's yanglint tool general commands + * + * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include "cmd.h" + +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> + +#include "common.h" +#include "compat.h" +#include "libyang.h" + +COMMAND commands[]; +extern int done; + +#ifndef NDEBUG + +void +cmd_debug_help(void) +{ + printf("Usage: debug (dict | xpath)+\n"); +} + +void +cmd_debug(struct ly_ctx **UNUSED(ctx), const char *cmdline) +{ + int argc = 0; + char **argv = NULL; + int opt, opt_index; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + uint32_t dbg_groups = 0; + + if (parse_cmdline(cmdline, &argc, &argv)) { + goto cleanup; + } + + while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) { + switch (opt) { + case 'h': + cmd_debug_help(); + goto cleanup; + default: + YLMSG_E("Unknown option.\n"); + goto cleanup; + } + } + if (argc == optind) { + /* no argument */ + cmd_debug_help(); + goto cleanup; + } + + for (int i = 0; i < argc - optind; i++) { + if (!strcasecmp("dict", argv[optind + i])) { + dbg_groups |= LY_LDGDICT; + } else if (!strcasecmp("xpath", argv[optind + i])) { + dbg_groups |= LY_LDGXPATH; + } else { + YLMSG_E("Unknown debug group \"%s\"\n", argv[optind + 1]); + goto cleanup; + } + } + + ly_log_dbg_groups(dbg_groups); + +cleanup: + free_cmdline(argv); +} + +#endif + +void +cmd_verb_help(void) +{ + printf("Usage: verb (error | warning | verbose | debug)\n"); +} + +void +cmd_verb(struct ly_ctx **UNUSED(ctx), const char *cmdline) +{ + int argc = 0; + char **argv = NULL; + int opt, opt_index; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if (parse_cmdline(cmdline, &argc, &argv)) { + goto cleanup; + } + + while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) { + switch (opt) { + case 'h': + cmd_verb_help(); + goto cleanup; + default: + YLMSG_E("Unknown option.\n"); + goto cleanup; + } + } + + if (argc - optind > 1) { + YLMSG_E("Only a single verbosity level can be set.\n"); + cmd_verb_help(); + goto cleanup; + } else if (argc == optind) { + /* no argument - print current value */ + LY_LOG_LEVEL level = ly_log_level(LY_LLERR); + + ly_log_level(level); + printf("Current verbosity level: "); + if (level == LY_LLERR) { + printf("error\n"); + } else if (level == LY_LLWRN) { + printf("warning\n"); + } else if (level == LY_LLVRB) { + printf("verbose\n"); + } else if (level == LY_LLDBG) { + printf("debug\n"); + } + goto cleanup; + } + + if (!strcasecmp("error", argv[optind]) || !strcmp("0", argv[optind])) { + ly_log_level(LY_LLERR); + } else if (!strcasecmp("warning", argv[optind]) || !strcmp("1", argv[optind])) { + ly_log_level(LY_LLWRN); + } else if (!strcasecmp("verbose", argv[optind]) || !strcmp("2", argv[optind])) { + ly_log_level(LY_LLVRB); + } else if (!strcasecmp("debug", argv[optind]) || !strcmp("3", argv[optind])) { + ly_log_level(LY_LLDBG); + } else { + YLMSG_E("Unknown verbosity \"%s\"\n", argv[optind]); + goto cleanup; + } + +cleanup: + free_cmdline(argv); +} + +void +cmd_quit(struct ly_ctx **UNUSED(ctx), const char *UNUSED(cmdline)) +{ + done = 1; + return; +} + +void +cmd_help_help(void) +{ + printf("Usage: help [cmd ...]\n"); +} + +void +cmd_help(struct ly_ctx **UNUSED(ctx), const char *cmdline) +{ + int argc = 0; + char **argv = NULL; + int opt, opt_index; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if (parse_cmdline(cmdline, &argc, &argv)) { + goto cleanup; + } + + while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) { + switch (opt) { + case 'h': + cmd_help_help(); + goto cleanup; + default: + YLMSG_E("Unknown option.\n"); + goto cleanup; + } + } + + if (argc == optind) { +generic_help: + printf("Available commands:\n"); + for (uint16_t i = 0; commands[i].name; i++) { + if (commands[i].helpstring != NULL) { + printf(" %-15s %s\n", commands[i].name, commands[i].helpstring); + } + } + } else { + /* print specific help for the selected command(s) */ + + for (int c = 0; c < argc - optind; ++c) { + int8_t match = 0; + + /* get the command of the specified name */ + for (uint16_t i = 0; commands[i].name; i++) { + if (strcmp(argv[optind + c], commands[i].name) == 0) { + match = 1; + if (commands[i].help_func != NULL) { + commands[i].help_func(); + } else { + printf("%s: %s\n", argv[optind + c], commands[i].helpstring); + } + break; + } + } + if (!match) { + /* if unknown command specified, print the list of commands */ + printf("Unknown command \'%s\'\n", argv[optind + c]); + goto generic_help; + } + } + } + +cleanup: + free_cmdline(argv); +} + +COMMAND commands[] = { + {"help", cmd_help, cmd_help_help, "Display commands description"}, + {"add", cmd_add, cmd_add_help, "Add a new module from a specific file"}, + {"load", cmd_load, cmd_load_help, "Load a new schema from the searchdirs"}, + {"print", cmd_print, cmd_print_help, "Print a module"}, + {"data", cmd_data, cmd_data_help, "Load, validate and optionally print instance data"}, + {"list", cmd_list, cmd_list_help, "List all the loaded modules"}, + {"feature", cmd_feature, cmd_feature_help, "Print all features of module(s) with their state"}, + {"searchpath", cmd_searchpath, cmd_searchpath_help, "Print/set the search path(s) for schemas"}, + {"clear", cmd_clear, cmd_clear_help, "Clear the context - remove all the loaded modules"}, + {"verb", cmd_verb, cmd_verb_help, "Change verbosity"}, +#ifndef NDEBUG + {"debug", cmd_debug, cmd_debug_help, "Display specific debug message groups"}, +#endif + {"quit", cmd_quit, NULL, "Quit the program"}, + /* synonyms for previous commands */ + {"?", cmd_help, NULL, "Display commands description"}, + {"exit", cmd_quit, NULL, "Quit the program"}, + {NULL, NULL, NULL, NULL} +}; diff --git a/tools/lint/cmd.h b/tools/lint/cmd.h new file mode 100644 index 0000000..9f6f88d --- /dev/null +++ b/tools/lint/cmd.h @@ -0,0 +1,69 @@ +/** + * @file cmd.h + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief libyang's yanglint tool commands header + * + * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef COMMANDS_H_ +#define COMMANDS_H_ + +#include "libyang.h" + +/** + * @brief command information + */ +typedef struct { + char *name; /* User printable name of the function. */ + + void (*func)(struct ly_ctx **ctx, const char *); /* Function to call to do the command. */ + void (*help_func)(void); /* Display command help. */ + char *helpstring; /* Documentation for this function. */ +} COMMAND; + +/** + * @brief The list of available commands. + */ +extern COMMAND commands[]; + +/* cmd_add.c */ +void cmd_add(struct ly_ctx **ctx, const char *cmdline); +void cmd_add_help(void); + +/* cmd_clear.c */ +void cmd_clear(struct ly_ctx **ctx, const char *cmdline); +void cmd_clear_help(void); + +/* cmd_data.c */ +void cmd_data(struct ly_ctx **ctx, const char *cmdline); +void cmd_data_help(void); + +/* cmd_list.c */ +void cmd_list(struct ly_ctx **ctx, const char *cmdline); +void cmd_list_help(void); + +/* cmd_feature.c */ +void cmd_feature(struct ly_ctx **ctx, const char *cmdline); +void cmd_feature_help(void); + +/* cmd_load.c */ +void cmd_load(struct ly_ctx **ctx, const char *cmdline); +void cmd_load_help(void); + +/* cmd_print.c */ +void cmd_print(struct ly_ctx **ctx, const char *cmdline); +void cmd_print_help(void); + +/* cmd_searchpath.c */ +void cmd_searchpath(struct ly_ctx **ctx, const char *cmdline); +void cmd_searchpath_help(void); + +#endif /* COMMANDS_H_ */ diff --git a/tools/lint/cmd_add.c b/tools/lint/cmd_add.c new file mode 100644 index 0000000..bbfdfd6 --- /dev/null +++ b/tools/lint/cmd_add.c @@ -0,0 +1,176 @@ +/** + * @file cmd_add.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief 'add' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include "cmd.h" + +#include <getopt.h> +#include <libgen.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "libyang.h" + +#include "common.h" + +void +cmd_add_help(void) +{ + printf("Usage: add [-iD] <schema1> [<schema2> ...]\n" + " Add a new module from a specific file.\n\n" + " -D, --disable-searchdir\n" + " Do not implicitly search in current working directory for\n" + " the import schema modules. If specified a second time, do not\n" + " even search in the module directory (all modules must be \n" + " explicitly specified).\n" + " -F FEATURES, --features=FEATURES\n" + " Features to support, default all in all implemented modules.\n" + " Specify separately for each module.\n" + " <modname>:[<feature>,]*\n" + " -i, --make-implemented\n" + " Make the imported modules \"referenced\" from any loaded\n" + " <schema> module also implemented. If specified a second time,\n" + " all the modules are set implemented.\n"); +} + +void +cmd_add(struct ly_ctx **ctx, const char *cmdline) +{ + int argc = 0; + char **argv = NULL; + int opt, opt_index; + struct option options[] = { + {"disable-searchdir", no_argument, NULL, 'D'}, + {"features", required_argument, NULL, 'F'}, + {"help", no_argument, NULL, 'h'}, + {"make-implemented", no_argument, NULL, 'i'}, + {NULL, 0, NULL, 0} + }; + uint16_t options_ctx = 0; + const char *all_features[] = {"*", NULL}; + struct ly_set fset = {0}; + + if (parse_cmdline(cmdline, &argc, &argv)) { + goto cleanup; + } + + while ((opt = getopt_long(argc, argv, "D:F:hi", options, &opt_index)) != -1) { + switch (opt) { + case 'D': /* --disable--search */ + if (options_ctx & LY_CTX_DISABLE_SEARCHDIRS) { + YLMSG_W("The -D option specified too many times.\n"); + } + if (options_ctx & LY_CTX_DISABLE_SEARCHDIR_CWD) { + options_ctx &= ~LY_CTX_DISABLE_SEARCHDIR_CWD; + options_ctx |= LY_CTX_DISABLE_SEARCHDIRS; + } else { + options_ctx |= LY_CTX_DISABLE_SEARCHDIR_CWD; + } + break; + + case 'F': /* --features */ + if (parse_features(optarg, &fset)) { + goto cleanup; + } + break; + + case 'h': + cmd_add_help(); + goto cleanup; + + case 'i': /* --make-implemented */ + if (options_ctx & LY_CTX_REF_IMPLEMENTED) { + options_ctx &= ~LY_CTX_REF_IMPLEMENTED; + options_ctx |= LY_CTX_ALL_IMPLEMENTED; + } else { + options_ctx |= LY_CTX_REF_IMPLEMENTED; + } + break; + + default: + YLMSG_E("Unknown option.\n"); + goto cleanup; + } + } + + if (argc == optind) { + /* no argument */ + cmd_add_help(); + goto cleanup; + } + + if (!fset.count) { + /* no features, enable all of them */ + options_ctx |= LY_CTX_ENABLE_IMP_FEATURES; + } + + if (options_ctx) { + ly_ctx_set_options(*ctx, options_ctx); + } + + for (int i = 0; i < argc - optind; i++) { + /* process the schema module files */ + LY_ERR ret; + uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */ + char *dir, *module; + const char **features = NULL; + struct ly_in *in = NULL; + + if (parse_schema_path(argv[optind + i], &dir, &module)) { + goto cleanup; + } + + /* add temporarily also the path of the module itself */ + if (ly_ctx_set_searchdir(*ctx, dirname(dir)) == LY_EEXIST) { + path_unset = 0; + } + + /* get features list for this module */ + if (!fset.count) { + features = all_features; + } else { + get_features(&fset, module, &features); + } + + /* temporary cleanup */ + free(dir); + free(module); + + /* prepare input handler */ + ret = ly_in_new_filepath(argv[optind + i], 0, &in); + if (ret) { + goto cleanup; + } + + /* parse the file */ + ret = lys_parse(*ctx, in, LYS_IN_UNKNOWN, features, NULL); + ly_in_free(in, 1); + ly_ctx_unset_searchdir_last(*ctx, path_unset); + + if (ret) { + /* libyang printed the error messages */ + goto cleanup; + } + } + +cleanup: + if (options_ctx) { + ly_ctx_unset_options(*ctx, options_ctx); + } + ly_set_erase(&fset, free_features); + free_cmdline(argv); +} diff --git a/tools/lint/cmd_clear.c b/tools/lint/cmd_clear.c new file mode 100644 index 0000000..5eed6ff --- /dev/null +++ b/tools/lint/cmd_clear.c @@ -0,0 +1,99 @@ +/** + * @file cmd_clear.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief 'clear' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include "cmd.h" + +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> + +#include "libyang.h" + +#include "common.h" + +void +cmd_clear_help(void) +{ + printf("Usage: clear [-i] [-y]\n" + " Replace the current context with an empty one, searchpaths\n" + " are not kept.\n\n" + " -i, --make-implemented\n" + " Make the imported modules \"referenced\" from any loaded\n" + " module also implemented. If specified a second time, all the\n" + " modules are set implemented.\n" + " -y, --yang-library\n" + " Load and implement internal \"ietf-yang-library\" YANG module.\n" + " Note that this module includes definitions of mandatory state\n" + " data that can result in unexpected data validation errors.\n"); +#if 0 + " If <yang-library-data> path specified, load the modules\n" + " according to the provided yang library data.\n" +#endif +} + +void +cmd_clear(struct ly_ctx **ctx, const char *cmdline) +{ + int argc = 0; + char **argv = NULL; + int opt, opt_index; + struct option options[] = { + {"make-implemented", no_argument, NULL, 'i'}, + {"yang-library", no_argument, NULL, 'y'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + uint16_t options_ctx = LY_CTX_NO_YANGLIBRARY; + struct ly_ctx *ctx_new; + + if (parse_cmdline(cmdline, &argc, &argv)) { + goto cleanup; + } + + while ((opt = getopt_long(argc, argv, "iyh", options, &opt_index)) != -1) { + switch (opt) { + case 'i': + if (options_ctx & LY_CTX_REF_IMPLEMENTED) { + options_ctx &= ~LY_CTX_REF_IMPLEMENTED; + options_ctx |= LY_CTX_ALL_IMPLEMENTED; + } else { + options_ctx |= LY_CTX_REF_IMPLEMENTED; + } + break; + case 'y': + options_ctx &= ~LY_CTX_NO_YANGLIBRARY; + break; + case 'h': + cmd_clear_help(); + goto cleanup; + default: + YLMSG_E("Unknown option.\n"); + goto cleanup; + } + } + + if (ly_ctx_new(NULL, options_ctx, &ctx_new)) { + YLMSG_W("Failed to create context.\n"); + goto cleanup; + } + + ly_ctx_destroy(*ctx); + *ctx = ctx_new; + +cleanup: + free_cmdline(argv); +} diff --git a/tools/lint/cmd_data.c b/tools/lint/cmd_data.c new file mode 100644 index 0000000..25449f5 --- /dev/null +++ b/tools/lint/cmd_data.c @@ -0,0 +1,328 @@ +/** + * @file cmd_data.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief 'data' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include "cmd.h" + +#include <errno.h> +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> + +#include "libyang.h" + +#include "common.h" + +void +cmd_data_help(void) +{ + printf("Usage: data [-emn] [-t TYPE]\n" + " [-F FORMAT] [-f FORMAT] [-d DEFAULTS] [-o OUTFILE] <data1> ...\n" + " data [-n] -t (rpc | notif | reply) [-O FILE]\n" + " [-F FORMAT] [-f FORMAT] [-d DEFAULTS] [-o OUTFILE] <data1> ...\n" + " data [-en] [-t TYPE] [-F FORMAT] -x XPATH [-o OUTFILE] <data1> ...\n" + " Parse, validate and optionally print data instances\n\n" + + " -t TYPE, --type=TYPE\n" + " Specify data tree type in the input data file(s):\n" + " data - Complete datastore with status data (default type).\n" + " config - Configuration datastore (without status data).\n" + " get - Result of the NETCONF <get> operation.\n" + " getconfig - Result of the NETCONF <get-config> operation.\n" + " edit - Content of the NETCONF <edit-config> operation.\n" + " rpc - Content of the NETCONF <rpc> message, defined as YANG's\n" + " RPC/Action input statement.\n" + " reply - Reply to the RPC/Action. Note that the reply data are\n" + " expected inside a container representing the original\n" + " RPC/Action. This is necessary to identify appropriate\n" + " data definitions in the schema module.\n" + " notif - Notification instance (content of the <notification>\n" + " element without <eventTime>).\n\n" + + " -e, --present Validate only with the schema modules whose data actually\n" + " exist in the provided input data files. Takes effect only\n" + " with the 'data' or 'config' TYPEs. Used to avoid requiring\n" + " mandatory nodes from modules which data are not present in the\n" + " provided input data files.\n" + " -m, --merge Merge input data files into a single tree and validate at\n" + " once.The option has effect only for 'data' and 'config' TYPEs.\n" + " In case of using -x option, the data are always merged.\n" + " -n, --not-strict\n" + " Do not require strict data parsing (silently skip unknown data),\n" + " has no effect for schemas.\n\n" + " -O FILE, --operational=FILE\n" + " Provide optional data to extend validation of the 'rpc',\n" + " 'reply' or 'notif' TYPEs. The FILE is supposed to contain\n" + " the :running configuration datastore and state data\n" + " (operational datastore) referenced from the RPC/Notification.\n\n" + + " -f FORMAT, --format=FORMAT\n" + " Print the data in one of the following formats:\n" + " xml, json, lyb\n" + " Note that the LYB format requires the -o option specified.\n" + " -F FORMAT, --in-format=FORMAT\n" + " Load the data in one of the following formats:\n" + " xml, json, lyb\n" + " If input format not specified, it is detected from the file extension.\n" + " -d MODE, --default=MODE\n" + " Print data with default values, according to the MODE\n" + " (to print attributes, ietf-netconf-with-defaults model\n" + " must be loaded):\n" + " all - Add missing default nodes.\n" + " all-tagged - Add missing default nodes and mark all the default\n" + " nodes with the attribute.\n" + " trim - Remove all nodes with a default value.\n" + " implicit-tagged - Add missing nodes and mark them with the attribute.\n" + " -o OUTFILE, --output=OUTFILE\n" + " Write the output to OUTFILE instead of stdout.\n\n" + + " -x XPATH, --xpath=XPATH\n" + " Evaluate XPATH expression and print the nodes satysfying the.\n" + " expression. The output format is specific and the option cannot\n" + " be combined with the -f and -d options. Also all the data\n" + " inputs are merged into a single data tree where the expression\n" + " is evaluated, so the -m option is always set implicitly.\n\n"); + +} + +void +cmd_data(struct ly_ctx **ctx, const char *cmdline) +{ + int argc = 0; + char **argv = NULL; + int opt, opt_index; + struct option options[] = { + {"defaults", required_argument, NULL, 'd'}, + {"present", no_argument, NULL, 'e'}, + {"format", required_argument, NULL, 'f'}, + {"in-format", required_argument, NULL, 'F'}, + {"help", no_argument, NULL, 'h'}, + {"merge", no_argument, NULL, 'm'}, + {"output", required_argument, NULL, 'o'}, + {"operational", required_argument, NULL, 'O'}, + {"not-strict", no_argument, NULL, 'n'}, + {"type", required_argument, NULL, 't'}, + {"xpath", required_argument, NULL, 'x'}, + {NULL, 0, NULL, 0} + }; + + uint8_t data_merge = 0; + uint32_t options_print = 0; + uint32_t options_parse = YL_DEFAULT_DATA_PARSE_OPTIONS; + uint32_t options_validate = 0; + enum lyd_type data_type = 0; + uint8_t data_type_set = 0; + LYD_FORMAT outformat = LYD_UNKNOWN; + LYD_FORMAT informat = LYD_UNKNOWN; + struct ly_out *out = NULL; + struct cmdline_file *operational = NULL; + struct ly_set inputs = {0}; + struct ly_set xpaths = {0}; + + if (parse_cmdline(cmdline, &argc, &argv)) { + goto cleanup; + } + + while ((opt = getopt_long(argc, argv, "d:ef:F:hmo:O:r:nt:x:", options, &opt_index)) != -1) { + switch (opt) { + case 'd': /* --default */ + if (!strcasecmp(optarg, "all")) { + options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL; + } else if (!strcasecmp(optarg, "all-tagged")) { + options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG; + } else if (!strcasecmp(optarg, "trim")) { + options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM; + } else if (!strcasecmp(optarg, "implicit-tagged")) { + options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG; + } else { + YLMSG_E("Unknown default mode %s\n", optarg); + cmd_data_help(); + goto cleanup; + } + break; + case 'f': /* --format */ + if (!strcasecmp(optarg, "xml")) { + outformat = LYD_XML; + } else if (!strcasecmp(optarg, "json")) { + outformat = LYD_JSON; + } else if (!strcasecmp(optarg, "lyb")) { + outformat = LYD_LYB; + } else { + YLMSG_E("Unknown output format %s\n", optarg); + cmd_data_help(); + goto cleanup; + } + break; + case 'F': /* --in-format */ + if (!strcasecmp(optarg, "xml")) { + informat = LYD_XML; + } else if (!strcasecmp(optarg, "json")) { + informat = LYD_JSON; + } else if (!strcasecmp(optarg, "lyb")) { + informat = LYD_LYB; + } else { + YLMSG_E("Unknown input format %s\n", optarg); + cmd_data_help(); + goto cleanup; + } + break; + case 'o': /* --output */ + if (out) { + YLMSG_E("Only a single output can be specified.\n"); + goto cleanup; + } else { + if (ly_out_new_filepath(optarg, &out)) { + YLMSG_E("Unable open output file %s (%s)\n", optarg, strerror(errno)); + goto cleanup; + } + } + break; + case 'O': { /* --operational */ + struct ly_in *in; + LYD_FORMAT f; + + if (operational) { + YLMSG_E("The operational datastore (-O) cannot be set multiple times.\n"); + goto cleanup; + } + if (get_input(optarg, NULL, &f, &in)) { + goto cleanup; + } + operational = fill_cmdline_file(NULL, in, optarg, f); + break; + } /* case 'O' */ + + case 'e': /* --present */ + options_validate |= LYD_VALIDATE_PRESENT; + break; + case 'm': /* --merge */ + data_merge = 1; + break; + case 'n': /* --not-strict */ + options_parse &= ~LYD_PARSE_STRICT; + break; + case 't': /* --type */ + if (data_type_set) { + YLMSG_E("The data type (-t) cannot be set multiple times.\n"); + goto cleanup; + } + + if (!strcasecmp(optarg, "config")) { + options_parse |= LYD_PARSE_NO_STATE; + options_validate |= LYD_VALIDATE_NO_STATE; + } else if (!strcasecmp(optarg, "get")) { + options_parse |= LYD_PARSE_ONLY; + } else if (!strcasecmp(optarg, "getconfig") || !strcasecmp(optarg, "get-config") || !strcasecmp(optarg, "edit")) { + options_parse |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE; + } else if (!strcasecmp(optarg, "rpc") || !strcasecmp(optarg, "action")) { + data_type = LYD_TYPE_RPC_YANG; + } else if (!strcasecmp(optarg, "reply") || !strcasecmp(optarg, "rpcreply")) { + data_type = LYD_TYPE_REPLY_YANG; + } else if (!strcasecmp(optarg, "notif") || !strcasecmp(optarg, "notification")) { + data_type = LYD_TYPE_NOTIF_YANG; + } else if (!strcasecmp(optarg, "data")) { + /* default option */ + } else { + YLMSG_E("Unknown data tree type %s.\n", optarg); + cmd_data_help(); + goto cleanup; + } + + data_type_set = 1; + break; + + case 'x': /* --xpath */ + if (ly_set_add(&xpaths, optarg, 0, NULL)) { + YLMSG_E("Storing XPath \"%s\" failed.\n", optarg); + goto cleanup; + } + break; + + case 'h': /* --help */ + cmd_data_help(); + goto cleanup; + default: + YLMSG_E("Unknown option.\n"); + goto cleanup; + } + } + + if (optind == argc) { + YLMSG_E("Missing the data file to process.\n"); + goto cleanup; + } + + if (data_merge) { + if (data_type || (options_parse & LYD_PARSE_ONLY)) { + /* switch off the option, incompatible input data type */ + data_merge = 0; + } else { + /* postpone validation after the merge of all the input data */ + options_parse |= LYD_PARSE_ONLY; + } + } else if (xpaths.count) { + data_merge = 1; + } + + if (xpaths.count && outformat) { + YLMSG_E("The --format option cannot be combined with --xpath option.\n"); + cmd_data_help(); + goto cleanup; + } + + if (operational && !data_type) { + YLMSG_W("Operational datastore takes effect only with RPCs/Actions/Replies/Notifications input data types.\n"); + free_cmdline_file(operational); + operational = NULL; + } + + /* process input data files provided as standalone command line arguments */ + for (int i = 0; i < argc - optind; i++) { + struct ly_in *in; + + if (get_input(argv[optind + i], NULL, &informat, &in)) { + goto cleanup; + } + + if (!fill_cmdline_file(&inputs, in, argv[optind + i], informat)) { + ly_in_free(in, 1); + goto cleanup; + } + } + + /* default output stream */ + if (!out) { + if (ly_out_new_file(stdout, &out)) { + YLMSG_E("Unable to set stdout as output.\n"); + goto cleanup; + } + } + + /* parse, validate and print data */ + if (process_data(*ctx, data_type, data_merge, outformat, out, options_parse, options_validate, options_print, + operational, NULL, &inputs, &xpaths)) { + goto cleanup; + } + +cleanup: + ly_out_free(out, NULL, 0); + ly_set_erase(&inputs, free_cmdline_file); + ly_set_erase(&xpaths, NULL); + free_cmdline_file(operational); + free_cmdline(argv); +} diff --git a/tools/lint/cmd_feature.c b/tools/lint/cmd_feature.c new file mode 100644 index 0000000..6b332ab --- /dev/null +++ b/tools/lint/cmd_feature.c @@ -0,0 +1,135 @@ +/** + * @file cmd_feature.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @brief 'feature' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2021 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include "cmd.h" + +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> + +#include "libyang.h" + +#include "common.h" + +void +cmd_feature_help(void) +{ + printf("Usage: feature [-f] <module> [<module>]*\n" + " feature -a [-f]\n" + " Print features of all the modules with state of each one.\n\n" + " -f <module1, module2, ...>, --feature-param <module1, module2, ...>\n" + " Generate features parameter for the command \"add\" \n" + " in the form of -F <module-name>:<features>\n" + " -a, --all \n" + " Print features of all implemented modules.\n"); +} + +void +cmd_feature(struct ly_ctx **ctx, const char *cmdline) +{ + int argc = 0; + char **argv = NULL; + char *features_output = NULL; + int opt, opt_index, i; + ly_bool generate_features = 0, print_all = 0; + struct ly_set set = {0}; + const struct lys_module *mod; + struct ly_out *out = NULL; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {"all", no_argument, NULL, 'a'}, + {"feature-param", no_argument, NULL, 'f'}, + {NULL, 0, NULL, 0} + }; + + if (parse_cmdline(cmdline, &argc, &argv)) { + goto cleanup; + } + + while ((opt = getopt_long(argc, argv, "haf", options, &opt_index)) != -1) { + switch (opt) { + case 'h': + cmd_feature_help(); + goto cleanup; + case 'a': + print_all = 1; + break; + case 'f': + generate_features = 1; + break; + default: + YLMSG_E("Unknown option.\n"); + goto cleanup; + } + } + + if (ly_out_new_file(stdout, &out)) { + YLMSG_E("Unable to print to the standard output.\n"); + goto cleanup; + } + + if (print_all) { + if (print_all_features(out, *ctx, generate_features, &features_output)) { + YLMSG_E("Printing all features failed.\n"); + goto cleanup; + } + if (generate_features) { + printf("%s\n", features_output); + } + goto cleanup; + } + + if (argc == optind) { + YLMSG_E("Missing modules to print.\n"); + goto cleanup; + } + + for (i = 0; i < argc - optind; i++) { + /* always erase the set, so the previous module's features don't carry over to the next module's features */ + ly_set_erase(&set, NULL); + + mod = ly_ctx_get_module_latest(*ctx, argv[optind + i]); + if (!mod) { + YLMSG_E("Module \"%s\" not found.\n", argv[optind + i]); + goto cleanup; + } + + /* collect features of the module */ + if (collect_features(mod, &set)) { + goto cleanup; + } + + if (generate_features) { + if (generate_features_output(mod, &set, &features_output)) { + goto cleanup; + } + /* don't print features and their state of each module if generating features parameter */ + continue; + } + + print_features(out, mod, &set); + } + + if (generate_features) { + printf("%s\n", features_output); + } + +cleanup: + free_cmdline(argv); + ly_out_free(out, NULL, 0); + ly_set_erase(&set, NULL); + free(features_output); +} diff --git a/tools/lint/cmd_list.c b/tools/lint/cmd_list.c new file mode 100644 index 0000000..ec7a021 --- /dev/null +++ b/tools/lint/cmd_list.c @@ -0,0 +1,89 @@ +/** + * @file cmd_list.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief 'list' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include "cmd.h" + +#include <getopt.h> +#include <stdio.h> +#include <strings.h> + +#include "libyang.h" + +#include "common.h" + +void +cmd_list_help(void) +{ + printf("Usage: list [-f (xml | json)]\n" + " Print the list of modules in the current context\n\n" + " -f FORMAT, --format=FORMAT\n" + " Print the list as ietf-yang-library data in the specified\n" + " data FORMAT. If format not specified, a simple list is\n" + " printed with an indication of imported (i) / implemented (I)\n" + " modules.\n"); +} + +void +cmd_list(struct ly_ctx **ctx, const char *cmdline) +{ + int argc = 0; + char **argv = NULL; + int opt, opt_index; + struct option options[] = { + {"format", required_argument, NULL, 'f'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + LYD_FORMAT format = LYD_UNKNOWN; + struct ly_out *out = NULL; + + if (parse_cmdline(cmdline, &argc, &argv)) { + goto cleanup; + } + + while ((opt = getopt_long(argc, argv, "f:h", options, &opt_index)) != -1) { + switch (opt) { + case 'f': /* --format */ + if (!strcasecmp(optarg, "xml")) { + format = LYD_XML; + } else if (!strcasecmp(optarg, "json")) { + format = LYD_JSON; + } else { + YLMSG_E("Unknown output format %s\n", optarg); + cmd_list_help(); + goto cleanup; + } + break; + case 'h': + cmd_list_help(); + goto cleanup; + default: + YLMSG_E("Unknown option.\n"); + goto cleanup; + } + } + + if (!ly_out_new_file(stdout, &out)) { + print_list(out, *ctx, format); + ly_out_free(out, NULL, 0); + } else { + YLMSG_E("Unable to print to the standard output.\n"); + } + +cleanup: + free_cmdline(argv); +} diff --git a/tools/lint/cmd_load.c b/tools/lint/cmd_load.c new file mode 100644 index 0000000..f5883e9 --- /dev/null +++ b/tools/lint/cmd_load.c @@ -0,0 +1,139 @@ +/** + * @file cmd_load.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief 'load' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include "cmd.h" + +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "libyang.h" + +#include "common.h" + +void +cmd_load_help(void) +{ + printf("Usage: load [-i] <module-name1>[@<revision>] [<module-name2>[@revision] ...]\n" + " Add a new module of the specified name, yanglint will find\n" + " them in searchpaths. if the <revision> of the module not\n" + " specified, the latest revision available is loaded.\n\n" + " -F FEATURES, --features=FEATURES\n" + " Features to support, default all in all implemented modules.\n" + " <modname>:[<feature>,]*\n" + " -i, --make-implemented\n" + " Make the imported modules \"referenced\" from any loaded\n" + " <schema> module also implemented. If specified a second time,\n" + " all the modules are set implemented.\n"); +} + +void +cmd_load(struct ly_ctx **ctx, const char *cmdline) +{ + int argc = 0; + char **argv = NULL; + int opt, opt_index; + struct option options[] = { + {"features", required_argument, NULL, 'F'}, + {"help", no_argument, NULL, 'h'}, + {"make-implemented", no_argument, NULL, 'i'}, + {NULL, 0, NULL, 0} + }; + uint16_t options_ctx = 0; + const char *all_features[] = {"*", NULL}; + struct ly_set fset = {0}; + + if (parse_cmdline(cmdline, &argc, &argv)) { + goto cleanup; + } + + while ((opt = getopt_long(argc, argv, "F:hi", options, &opt_index)) != -1) { + switch (opt) { + case 'F': /* --features */ + if (parse_features(optarg, &fset)) { + goto cleanup; + } + break; + + case 'h': + cmd_load_help(); + goto cleanup; + + case 'i': /* --make-implemented */ + if (options_ctx & LY_CTX_REF_IMPLEMENTED) { + options_ctx &= ~LY_CTX_REF_IMPLEMENTED; + options_ctx |= LY_CTX_ALL_IMPLEMENTED; + } else { + options_ctx |= LY_CTX_REF_IMPLEMENTED; + } + break; + + default: + YLMSG_E("Unknown option.\n"); + goto cleanup; + } + } + + if (argc == optind) { + /* no argument */ + cmd_add_help(); + goto cleanup; + } + + if (!fset.count) { + /* no features, enable all of them */ + options_ctx |= LY_CTX_ENABLE_IMP_FEATURES; + } + + if (options_ctx) { + ly_ctx_set_options(*ctx, options_ctx); + } + + for (int i = 0; i < argc - optind; i++) { + /* process the schema module files */ + char *revision; + const char **features = NULL; + + /* get revision */ + revision = strchr(argv[optind + i], '@'); + if (revision) { + revision[0] = '\0'; + ++revision; + } + + /* get features list for this module */ + if (!fset.count) { + features = all_features; + } else { + get_features(&fset, argv[optind + i], &features); + } + + /* load the module */ + if (!ly_ctx_load_module(*ctx, argv[optind + i], revision, features)) { + /* libyang printed the error messages */ + goto cleanup; + } + } + +cleanup: + if (options_ctx) { + ly_ctx_unset_options(*ctx, options_ctx); + } + ly_set_erase(&fset, free_features); + free_cmdline(argv); +} diff --git a/tools/lint/cmd_print.c b/tools/lint/cmd_print.c new file mode 100644 index 0000000..c1a5359 --- /dev/null +++ b/tools/lint/cmd_print.c @@ -0,0 +1,264 @@ +/** + * @file cmd_print.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief 'print' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include "cmd.h" + +#include <errno.h> +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> + +#include "libyang.h" + +#include "common.h" + +void +cmd_print_help(void) +{ + printf("Usage: print [-f (yang | yin | tree [-q -P PATH -L LINE_LENGTH ] | info [-q -P PATH])]\n" + " [-o OUTFILE] [<module-name1>[@revision]] ...\n" + " Print a schema module. The <module-name> is not required\n" + " only in case the -P option is specified.\n\n" + " -f FORMAT, --format=FORMAT\n" + " Print the module in the specified FORMAT. If format not\n" + " specified, the 'tree' format is used.\n" + " -L LINE_LENGTH, --tree-line-length=LINE_LENGTH\n" + " The limit of the maximum line length on which the 'tree'\n" + " format will try to be printed.\n" + " -P PATH, --schema-node=PATH\n" + " Print only the specified subtree of the schema.\n" + " The PATH is the XPath subset mentioned in documentation as\n" + " the Path format. The option can be combined with --single-node\n" + " option to print information only about the specified node.\n" + " -q, --single-node\n" + " Supplement to the --schema-node option to print information\n" + " only about a single node specified as PATH argument.\n" + " -o OUTFILE, --output=OUTFILE\n" + " Write the output to OUTFILE instead of stdout.\n"); +} + +static LY_ERR +cmd_print_submodule(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options) +{ + LY_ERR erc; + const struct lysp_submodule *submodule; + + submodule = revision ? + ly_ctx_get_submodule(*ctx, name, revision) : + ly_ctx_get_submodule_latest(*ctx, name); + + erc = submodule ? + lys_print_submodule(out, submodule, format, line_length, options) : + LY_ENOTFOUND; + + return erc; +} + +static LY_ERR +cmd_print_module(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options) +{ + LY_ERR erc; + struct lys_module *module; + + module = revision ? + ly_ctx_get_module(*ctx, name, revision) : + ly_ctx_get_module_latest(*ctx, name); + + erc = module ? + lys_print_module(out, module, format, line_length, options) : + LY_ENOTFOUND; + + return erc; +} + +static void +cmd_print_modules(int argc, char **argv, struct ly_out *out, struct ly_ctx **ctx, LYS_OUTFORMAT format, size_t line_length, uint32_t options) +{ + LY_ERR erc; + char *name, *revision; + ly_bool search_submodul; + const int stop = argc - optind; + + for (int i = 0; i < stop; i++) { + name = argv[optind + i]; + /* get revision */ + revision = strchr(name, '@'); + if (revision) { + revision[0] = '\0'; + ++revision; + } + + erc = cmd_print_module(out, ctx, name, revision, format, line_length, options); + + if (erc == LY_ENOTFOUND) { + search_submodul = 1; + erc = cmd_print_submodule(out, ctx, name, revision, format, line_length, options); + } else { + search_submodul = 0; + } + + if (erc == LY_SUCCESS) { + /* for YANG Tree Diagrams printing it's more readable to print a blank line between modules. */ + if ((format == LYS_OUT_TREE) && (i + 1 < stop)) { + ly_print(out, "\n"); + } + continue; + } else if (erc == LY_ENOTFOUND) { + if (revision) { + YLMSG_E("No (sub)module \"%s\" in revision %s found.\n", name, revision); + } else { + YLMSG_E("No (sub)module \"%s\" found.\n", name); + } + break; + } else { + if (search_submodul) { + YLMSG_E("Unable to print submodule %s.\n", name); + } else { + YLMSG_E("Unable to print module %s.\n", name); + } + break; + } + } +} + +void +cmd_print(struct ly_ctx **ctx, const char *cmdline) +{ + int argc = 0; + char **argv = NULL; + int opt, opt_index; + struct option options[] = { + {"format", required_argument, NULL, 'f'}, + {"help", no_argument, NULL, 'h'}, + {"tree-line-length", required_argument, NULL, 'L'}, + {"output", required_argument, NULL, 'o'}, + {"schema-node", required_argument, NULL, 'P'}, + {"single-node", no_argument, NULL, 'q'}, + {NULL, 0, NULL, 0} + }; + uint16_t options_print = 0; + const char *node_path = NULL; + LYS_OUTFORMAT format = LYS_OUT_TREE; + struct ly_out *out = NULL; + ly_bool out_stdout = 0; + size_t line_length = 0; + + if (parse_cmdline(cmdline, &argc, &argv)) { + goto cleanup; + } + + while ((opt = getopt_long(argc, argv, "f:hL:o:P:q", options, &opt_index)) != -1) { + switch (opt) { + case 'o': /* --output */ + if (out) { + if (ly_out_filepath(out, optarg) != NULL) { + YLMSG_E("Unable to use output file %s for printing output.\n", optarg); + goto cleanup; + } + } else { + if (ly_out_new_filepath(optarg, &out)) { + YLMSG_E("Unable to use output file %s for printing output.\n", optarg); + goto cleanup; + } + } + break; + + case 'f': /* --format */ + if (!strcasecmp(optarg, "yang")) { + format = LYS_OUT_YANG; + } else if (!strcasecmp(optarg, "yin")) { + format = LYS_OUT_YIN; + } else if (!strcasecmp(optarg, "info")) { + format = LYS_OUT_YANG_COMPILED; + } else if (!strcasecmp(optarg, "tree")) { + format = LYS_OUT_TREE; + } else { + YLMSG_E("Unknown output format %s\n", optarg); + cmd_print_help(); + goto cleanup; + } + break; + + case 'L': /* --tree-line-length */ + line_length = atoi(optarg); + break; + + case 'P': /* --schema-node */ + node_path = optarg; + break; + + case 'q': /* --single-node */ + options_print |= LYS_PRINT_NO_SUBSTMT; + break; + + case 'h': + cmd_print_help(); + goto cleanup; + default: + YLMSG_E("Unknown option.\n"); + goto cleanup; + } + } + + /* file name */ + if ((argc == optind) && !node_path) { + YLMSG_E("Missing the name of the module to print.\n"); + goto cleanup; + } + + if ((format != LYS_OUT_TREE) && line_length) { + YLMSG_E("--tree-line-length take effect only in case of the tree output format.\n"); + goto cleanup; + } + + if (!out) { + if (ly_out_new_file(stdout, &out)) { + YLMSG_E("Could not use stdout to print output.\n"); + goto cleanup; + } + out_stdout = 1; + } + + if (format == LYS_OUT_TREE) { + /* print tree from lysc_nodes */ + ly_ctx_set_options(*ctx, LY_CTX_SET_PRIV_PARSED); + } + + if (node_path) { + const struct lysc_node *node; + + node = find_schema_path(*ctx, node_path); + if (!node) { + YLMSG_E("The requested schema node \"%s\" does not exists.\n", node_path); + goto cleanup; + } + + if (lys_print_node(out, node, format, 0, options_print)) { + YLMSG_E("Unable to print schema node %s.\n", node_path); + goto cleanup; + } + } else { + cmd_print_modules(argc, argv, out, ctx, format, line_length, options_print); + goto cleanup; + } + +cleanup: + free_cmdline(argv); + ly_out_free(out, NULL, out_stdout ? 0 : 1); +} diff --git a/tools/lint/cmd_searchpath.c b/tools/lint/cmd_searchpath.c new file mode 100644 index 0000000..529e05d --- /dev/null +++ b/tools/lint/cmd_searchpath.c @@ -0,0 +1,90 @@ +/** + * @file cmd_searchpath.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief 'searchpath' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include "cmd.h" + +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> + +#include "libyang.h" + +#include "common.h" + +void +cmd_searchpath_help(void) +{ + printf("Usage: searchpath [--clear] [<modules-dir-path> ...]\n" + " Set paths of directories where to search for imports and\n" + " includes of the schema modules. The current working directory\n" + " and the path of the module being added is used implicitly.\n" + " The 'load' command uses these paths to search even for the\n" + " schema modules to be loaded.\n"); +} + +void +cmd_searchpath(struct ly_ctx **ctx, const char *cmdline) +{ + int argc = 0; + char **argv = NULL; + int opt, opt_index; + struct option options[] = { + {"clear", no_argument, NULL, 'c'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + int8_t cleared = 0; + + if (parse_cmdline(cmdline, &argc, &argv)) { + goto cleanup; + } + + while ((opt = getopt_long(argc, argv, "ch", options, &opt_index)) != -1) { + switch (opt) { + case 'c': + ly_ctx_unset_searchdir(*ctx, NULL); + cleared = 1; + break; + case 'h': + cmd_searchpath_help(); + goto cleanup; + default: + YLMSG_E("Unknown option.\n"); + goto cleanup; + } + } + + if (!cleared && (argc == optind)) { + /* no argument - print the paths */ + const char * const *dirs = ly_ctx_get_searchdirs(*ctx); + + printf("List of the searchpaths:\n"); + for (uint32_t i = 0; dirs[i]; ++i) { + printf(" %s\n", dirs[i]); + } + goto cleanup; + } + + for (int i = 0; i < argc - optind; i++) { + if (ly_ctx_set_searchdir(*ctx, argv[optind + i])) { + goto cleanup; + } + } + +cleanup: + free_cmdline(argv); +} diff --git a/tools/lint/common.c b/tools/lint/common.c new file mode 100644 index 0000000..fc9b1cd --- /dev/null +++ b/tools/lint/common.c @@ -0,0 +1,868 @@ +/** + * @file common.c + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief libyang's yanglint tool - common functions for both interactive and non-interactive mode. + * + * Copyright (c) 2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200809L /* strdup, strndup */ + +#include "common.h" + +#include <assert.h> +#include <errno.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "compat.h" +#include "libyang.h" + +int +parse_schema_path(const char *path, char **dir, char **module) +{ + char *p; + + assert(dir); + assert(module); + + /* split the path to dirname and basename for further work */ + *dir = strdup(path); + *module = strrchr(*dir, '/'); + if (!(*module)) { + *module = *dir; + *dir = strdup("./"); + } else { + *module[0] = '\0'; /* break the dir */ + *module = strdup((*module) + 1); + } + /* get the pure module name without suffix or revision part of the filename */ + if ((p = strchr(*module, '@'))) { + /* revision */ + *p = '\0'; + } else if ((p = strrchr(*module, '.'))) { + /* fileformat suffix */ + *p = '\0'; + } + + return 0; +} + +int +get_input(const char *filepath, LYS_INFORMAT *format_schema, LYD_FORMAT *format_data, struct ly_in **in) +{ + struct stat st; + + /* check that the filepath exists and is a regular file */ + if (stat(filepath, &st) == -1) { + YLMSG_E("Unable to use input filepath (%s) - %s.\n", filepath, strerror(errno)); + return -1; + } + if (!S_ISREG(st.st_mode)) { + YLMSG_E("Provided input file (%s) is not a regular file.\n", filepath); + return -1; + } + + if ((format_schema && !*format_schema) || (format_data && !*format_data)) { + /* get the file format */ + if (get_format(filepath, format_schema, format_data)) { + return -1; + } + } + + if (ly_in_new_filepath(filepath, 0, in)) { + YLMSG_E("Unable to process input file.\n"); + return -1; + } + + return 0; +} + +void +free_features(void *flist) +{ + struct schema_features *rec = (struct schema_features *)flist; + + if (rec) { + free(rec->mod_name); + if (rec->features) { + for (uint32_t u = 0; rec->features[u]; ++u) { + free(rec->features[u]); + } + free(rec->features); + } + free(rec); + } +} + +void +get_features(struct ly_set *fset, const char *module, const char ***features) +{ + /* get features list for this module */ + for (uint32_t u = 0; u < fset->count; ++u) { + struct schema_features *sf = (struct schema_features *)fset->objs[u]; + + if (!strcmp(module, sf->mod_name)) { + /* matched module - explicitly set features */ + *features = (const char **)sf->features; + sf->applied = 1; + return; + } + } + + /* features not set so disable all */ + *features = NULL; +} + +int +parse_features(const char *fstring, struct ly_set *fset) +{ + struct schema_features *rec; + uint32_t count; + char *p, **fp; + + rec = calloc(1, sizeof *rec); + if (!rec) { + YLMSG_E("Unable to allocate features information record (%s).\n", strerror(errno)); + return -1; + } + if (ly_set_add(fset, rec, 1, NULL)) { + YLMSG_E("Unable to store features information (%s).\n", strerror(errno)); + free(rec); + return -1; + } + + /* fill the record */ + p = strchr(fstring, ':'); + if (!p) { + YLMSG_E("Invalid format of the features specification (%s).\n", fstring); + return -1; + } + rec->mod_name = strndup(fstring, p - fstring); + + count = 0; + while (p) { + size_t len = 0; + char *token = p + 1; + + p = strchr(token, ','); + if (!p) { + /* the last item, if any */ + len = strlen(token); + } else { + len = p - token; + } + + if (len) { + fp = realloc(rec->features, (count + 1) * sizeof *rec->features); + if (!fp) { + YLMSG_E("Unable to store features list information (%s).\n", strerror(errno)); + return -1; + } + rec->features = fp; + fp = &rec->features[count++]; /* array item to set */ + (*fp) = strndup(token, len); + } + } + + /* terminating NULL */ + fp = realloc(rec->features, (count + 1) * sizeof *rec->features); + if (!fp) { + YLMSG_E("Unable to store features list information (%s).\n", strerror(errno)); + return -1; + } + rec->features = fp; + rec->features[count++] = NULL; + + return 0; +} + +struct cmdline_file * +fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format) +{ + struct cmdline_file *rec; + + rec = malloc(sizeof *rec); + if (!rec) { + YLMSG_E("Allocating memory for data file information failed.\n"); + return NULL; + } + rec->in = in; + rec->path = path; + rec->format = format; + + if (set && ly_set_add(set, rec, 1, NULL)) { + free(rec); + YLMSG_E("Storing data file information failed.\n"); + return NULL; + } + + return rec; +} + +int +collect_features(const struct lys_module *mod, struct ly_set *set) +{ + struct lysp_feature *f = NULL; + uint32_t idx = 0; + + while ((f = lysp_feature_next(f, mod->parsed, &idx))) { + if (ly_set_add(set, (void *)f->name, 1, NULL)) { + YLMSG_E("Memory allocation failed.\n"); + ly_set_erase(set, NULL); + return 1; + } + } + + return 0; +} + +void +print_features(struct ly_out *out, const struct lys_module *mod, const struct ly_set *set) +{ + size_t max_len; + uint32_t j; + const char *name; + + /* header */ + ly_print(out, "%s:\n", mod->name); + + /* no features */ + if (!set->count) { + ly_print(out, "\t(none)\n\n"); + return; + } + + /* get max len, so the statuses of all the features will be aligned */ + max_len = 0; + for (j = 0; j < set->count; ++j) { + name = set->objs[j]; + if (strlen(name) > max_len) { + max_len = strlen(name); + } + } + + /* print features */ + for (j = 0; j < set->count; ++j) { + name = set->objs[j]; + ly_print(out, "\t%-*s (%s)\n", (int)max_len, name, lys_feature_value(mod, name) ? "off" : "on"); + } + + ly_print(out, "\n"); +} + +int +generate_features_output(const struct lys_module *mod, const struct ly_set *set, char **features_param) +{ + uint32_t j; + /* + * features_len - length of all the features in the current module + * added_len - length of a string to be added, = features_len + extra necessary length + * param_len - length of the parameter before appending new string + */ + size_t features_len, added_len, param_len; + char *tmp; + + features_len = 0; + for (j = 0; j < set->count; j++) { + features_len += strlen(set->objs[j]); + } + + if (j == 0) { + /* no features */ + added_len = strlen("-F ") + strlen(mod->name) + strlen(":"); + } else { + /* j = comma count, -1 because of trailing comma */ + added_len = strlen("-F ") + strlen(mod->name) + strlen(":") + features_len + j - 1; + } + + /* to avoid strlen(NULL) if this is the first call */ + param_len = 0; + if (*features_param) { + param_len = strlen(*features_param); + } + + /* +1 because of white space at the beginning */ + tmp = realloc(*features_param, param_len + added_len + 1 + 1); + if (!tmp) { + goto error; + } else { + *features_param = tmp; + } + sprintf(*features_param + param_len, " -F %s:", mod->name); + + for (j = 0; j < set->count; j++) { + strcat(*features_param, set->objs[j]); + /* no trailing comma */ + if (j != (set->count - 1)) { + strcat(*features_param, ","); + } + } + + return 0; + +error: + YLMSG_E("Memory allocation failed (%s:%d, %s).\n", __FILE__, __LINE__, strerror(errno)); + return 1; +} + +int +print_all_features(struct ly_out *out, const struct ly_ctx *ctx, ly_bool generate_features, char **features_param) +{ + int ret = 0; + uint32_t i = 0; + struct lys_module *mod; + struct ly_set set = {0}; + + while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) { + /* only care about implemented modules */ + if (!mod->implemented) { + continue; + } + + /* always erase the set, so the previous module's features don't carry over to the next module's features */ + ly_set_erase(&set, NULL); + + if (collect_features(mod, &set)) { + ret = 1; + goto cleanup; + } + + if (generate_features && generate_features_output(mod, &set, features_param)) { + ret = 1; + goto cleanup; + } + print_features(out, mod, &set); + } + +cleanup: + ly_set_erase(&set, NULL); + return ret; +} + +void +free_cmdline_file(void *cmdline_file) +{ + struct cmdline_file *rec = (struct cmdline_file *)cmdline_file; + + if (rec) { + ly_in_free(rec->in, 1); + free(rec); + } +} + +void +free_cmdline(char *argv[]) +{ + if (argv) { + free(argv[0]); + free(argv); + } +} + +int +parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[]) +{ + int count; + char **vector; + char *ptr; + char qmark = 0; + + assert(cmdline); + assert(argc_p); + assert(argv_p); + + /* init */ + optind = 0; /* reinitialize getopt() */ + count = 1; + vector = malloc((count + 1) * sizeof *vector); + vector[0] = strdup(cmdline); + + /* command name */ + strtok(vector[0], " "); + + /* arguments */ + while ((ptr = strtok(NULL, " "))) { + size_t len; + void *r; + + len = strlen(ptr); + + if (qmark) { + /* still in quotated text */ + /* remove NULL termination of the previous token since it is not a token, + * but a part of the quotation string */ + ptr[-1] = ' '; + + if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) { + /* end of quotation */ + qmark = 0; + /* shorten the argument by the terminating quotation mark */ + ptr[len - 1] = '\0'; + } + continue; + } + + /* another token in cmdline */ + ++count; + r = realloc(vector, (count + 1) * sizeof *vector); + if (!r) { + YLMSG_E("Memory allocation failed (%s:%d, %s).\n", __FILE__, __LINE__, strerror(errno)); + free(vector); + return -1; + } + vector = r; + vector[count - 1] = ptr; + + if ((ptr[0] == '"') || (ptr[0] == '\'')) { + /* remember the quotation mark to identify end of quotation */ + qmark = ptr[0]; + + /* move the remembered argument after the quotation mark */ + ++vector[count - 1]; + + /* check if the quotation is terminated within this token */ + if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) { + /* end of quotation */ + qmark = 0; + /* shorten the argument by the terminating quotation mark */ + ptr[len - 1] = '\0'; + } + } + } + vector[count] = NULL; + + *argc_p = count; + *argv_p = vector; + + return 0; +} + +int +get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data) +{ + char *ptr; + LYS_INFORMAT informat_s; + LYD_FORMAT informat_d; + + /* get the file format */ + if ((ptr = strrchr(filename, '.')) != NULL) { + ++ptr; + if (!strcmp(ptr, "yang")) { + informat_s = LYS_IN_YANG; + informat_d = 0; + } else if (!strcmp(ptr, "yin")) { + informat_s = LYS_IN_YIN; + informat_d = 0; + } else if (!strcmp(ptr, "xml")) { + informat_s = 0; + informat_d = LYD_XML; + } else if (!strcmp(ptr, "json")) { + informat_s = 0; + informat_d = LYD_JSON; + } else if (!strcmp(ptr, "lyb")) { + informat_s = 0; + informat_d = LYD_LYB; + } else { + YLMSG_E("Input file \"%s\" in an unknown format \"%s\".\n", filename, ptr); + return 0; + } + } else { + YLMSG_E("Input file \"%s\" without file extension - unknown format.\n", filename); + return 1; + } + + if (informat_d) { + if (!data) { + YLMSG_E("Input file \"%s\" not expected to contain data instances (unexpected format).\n", filename); + return 2; + } + (*data) = informat_d; + } else if (informat_s) { + if (!schema) { + YLMSG_E("Input file \"%s\" not expected to contain schema definition (unexpected format).\n", filename); + return 3; + } + (*schema) = informat_s; + } + + return 0; +} + +int +print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat) +{ + struct lyd_node *ylib; + uint32_t idx = 0, has_modules = 0; + const struct lys_module *mod; + + if (outformat != LYD_UNKNOWN) { + if (ly_ctx_get_yanglib_data(ctx, &ylib, "%u", ly_ctx_get_change_count(ctx))) { + YLMSG_E("Getting context info (ietf-yang-library data) failed. If the YANG module is missing or not implemented, use an option to add it internally.\n"); + return 1; + } + + lyd_print_all(out, ylib, outformat, 0); + lyd_free_all(ylib); + return 0; + } + + /* iterate schemas in context and provide just the basic info */ + ly_print(out, "List of the loaded models:\n"); + while ((mod = ly_ctx_get_module_iter(ctx, &idx))) { + has_modules++; + + /* conformance print */ + if (mod->implemented) { + ly_print(out, " I"); + } else { + ly_print(out, " i"); + } + + /* module print */ + ly_print(out, " %s", mod->name); + if (mod->revision) { + ly_print(out, "@%s", mod->revision); + } + + /* submodules print */ + if (mod->parsed && mod->parsed->includes) { + uint64_t u = 0; + + ly_print(out, " ("); + LY_ARRAY_FOR(mod->parsed->includes, u) { + ly_print(out, "%s%s", !u ? "" : ",", mod->parsed->includes[u].name); + if (mod->parsed->includes[u].rev[0]) { + ly_print(out, "@%s", mod->parsed->includes[u].rev); + } + } + ly_print(out, ")"); + } + + /* finish the line */ + ly_print(out, "\n"); + } + + if (!has_modules) { + ly_print(out, "\t(none)\n"); + } + + ly_print_flush(out); + return 0; +} + +int +evaluate_xpath(const struct lyd_node *tree, const char *xpath) +{ + struct ly_set *set = NULL; + + if (lyd_find_xpath(tree, xpath, &set)) { + return -1; + } + + /* print result */ + printf("XPath \"%s\" evaluation result:\n", xpath); + if (!set->count) { + printf("\tEmpty\n"); + } else { + for (uint32_t u = 0; u < set->count; ++u) { + struct lyd_node *node = (struct lyd_node *)set->objs[u]; + + printf(" %s \"%s\"", lys_nodetype2str(node->schema->nodetype), node->schema->name); + if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { + printf(" (value: \"%s\")\n", lyd_get_value(node)); + } else if (node->schema->nodetype == LYS_LIST) { + printf(" ("); + for (struct lyd_node *key = ((struct lyd_node_inner *)node)->child; key && lysc_is_key(key->schema); key = key->next) { + printf("%s\"%s\": \"%s\";", (key != ((struct lyd_node_inner *)node)->child) ? " " : "", + key->schema->name, lyd_get_value(key)); + } + printf(")\n"); + } + } + } + + ly_set_free(set, NULL); + return 0; +} + +LY_ERR +process_data(struct ly_ctx *ctx, enum lyd_type data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out, + uint32_t options_parse, uint32_t options_validate, uint32_t options_print, struct cmdline_file *operational_f, + struct cmdline_file *rpc_f, struct ly_set *inputs, struct ly_set *xpaths) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *tree = NULL, *op = NULL, *envp = NULL, *merged_tree = NULL, *oper_tree = NULL; + char *path = NULL; + struct ly_set *set = NULL; + + /* additional operational datastore */ + if (operational_f && operational_f->in) { + ret = lyd_parse_data(ctx, NULL, operational_f->in, operational_f->format, LYD_PARSE_ONLY, 0, &oper_tree); + if (ret) { + YLMSG_E("Failed to parse operational datastore file \"%s\".\n", operational_f->path); + goto cleanup; + } + } + + for (uint32_t u = 0; u < inputs->count; ++u) { + struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u]; + + switch (data_type) { + case LYD_TYPE_DATA_YANG: + ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, options_parse, options_validate, &tree); + break; + case LYD_TYPE_RPC_YANG: + case LYD_TYPE_REPLY_YANG: + case LYD_TYPE_NOTIF_YANG: + ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, data_type, &tree, &op); + break; + case LYD_TYPE_RPC_NETCONF: + case LYD_TYPE_NOTIF_NETCONF: + ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, data_type, &envp, &op); + + /* adjust pointers */ + for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {} + break; + case LYD_TYPE_REPLY_NETCONF: + /* parse source RPC operation */ + assert(rpc_f && rpc_f->in); + ret = lyd_parse_op(ctx, NULL, rpc_f->in, rpc_f->format, LYD_TYPE_RPC_NETCONF, &envp, &op); + if (ret) { + YLMSG_E("Failed to parse source NETCONF RPC operation file \"%s\".\n", rpc_f->path); + goto cleanup; + } + + /* adjust pointers */ + for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {} + + /* free input */ + lyd_free_siblings(lyd_child(op)); + + /* we do not care */ + lyd_free_all(envp); + envp = NULL; + + ret = lyd_parse_op(ctx, op, input_f->in, input_f->format, data_type, &envp, NULL); + break; + default: + YLMSG_E("Internal error (%s:%d).\n", __FILE__, __LINE__); + goto cleanup; + } + + if (ret) { + YLMSG_E("Failed to parse input data file \"%s\".\n", input_f->path); + goto cleanup; + } + + if (merge) { + /* merge the data so far parsed for later validation and print */ + if (!merged_tree) { + merged_tree = tree; + } else { + ret = lyd_merge_siblings(&merged_tree, tree, LYD_MERGE_DESTRUCT); + if (ret) { + YLMSG_E("Merging %s with previous data failed.\n", input_f->path); + goto cleanup; + } + } + tree = NULL; + } else if (format) { + /* print */ + switch (data_type) { + case LYD_TYPE_DATA_YANG: + lyd_print_all(out, tree, format, options_print); + break; + case LYD_TYPE_RPC_YANG: + case LYD_TYPE_REPLY_YANG: + case LYD_TYPE_NOTIF_YANG: + case LYD_TYPE_RPC_NETCONF: + case LYD_TYPE_NOTIF_NETCONF: + lyd_print_tree(out, tree, format, options_print); + break; + case LYD_TYPE_REPLY_NETCONF: + /* just the output */ + lyd_print_tree(out, lyd_child(tree), format, options_print); + break; + default: + assert(0); + } + } else { + /* validation of the RPC/Action/reply/Notification with the operational datastore, if any */ + switch (data_type) { + case LYD_TYPE_DATA_YANG: + /* already validated */ + break; + case LYD_TYPE_RPC_YANG: + case LYD_TYPE_RPC_NETCONF: + ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_RPC_YANG, NULL); + break; + case LYD_TYPE_REPLY_YANG: + case LYD_TYPE_REPLY_NETCONF: + ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_REPLY_YANG, NULL); + break; + case LYD_TYPE_NOTIF_YANG: + case LYD_TYPE_NOTIF_NETCONF: + ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_NOTIF_YANG, NULL); + break; + default: + assert(0); + } + if (ret) { + if (operational_f->path) { + YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".\n", + input_f->path, operational_f->path); + } else { + YLMSG_E("Failed to validate input data file \"%s\".\n", input_f->path); + } + goto cleanup; + } + + if (op && oper_tree && lyd_parent(op)) { + /* check operation parent existence */ + path = lyd_path(lyd_parent(op), LYD_PATH_STD, NULL, 0); + if (!path) { + ret = LY_EMEM; + goto cleanup; + } + if ((ret = lyd_find_xpath(oper_tree, path, &set))) { + goto cleanup; + } + if (!set->count) { + YLMSG_E("Operation \"%s\" parent \"%s\" not found in the operational data.\n", LYD_NAME(op), path); + ret = LY_EVALID; + goto cleanup; + } + } + } + + /* next iter */ + lyd_free_all(tree); + tree = NULL; + lyd_free_all(envp); + envp = NULL; + } + + if (merge) { + /* validate the merged result */ + ret = lyd_validate_all(&merged_tree, ctx, LYD_VALIDATE_PRESENT, NULL); + if (ret) { + YLMSG_E("Merged data are not valid.\n"); + goto cleanup; + } + + if (format) { + /* and print it */ + lyd_print_all(out, merged_tree, format, options_print); + } + + for (uint32_t u = 0; xpaths && (u < xpaths->count); ++u) { + if (evaluate_xpath(merged_tree, (const char *)xpaths->objs[u])) { + goto cleanup; + } + } + } + +cleanup: + lyd_free_all(tree); + lyd_free_all(envp); + lyd_free_all(merged_tree); + lyd_free_all(oper_tree); + free(path); + ly_set_free(set, NULL); + return ret; +} + +const struct lysc_node * +find_schema_path(const struct ly_ctx *ctx, const char *schema_path) +{ + const char *end, *module_name_end; + char *module_name = NULL; + const struct lysc_node *node = NULL, *parent_node = NULL, *parent_node_tmp = NULL; + const struct lys_module *module; + size_t node_name_len; + ly_bool found_exact_match = 0; + + /* iterate over each '/' in the path */ + while (schema_path) { + /* example: schema_path = /listen/endpoint + * end == NULL for endpoint, end exists for listen */ + end = strchr(schema_path + 1, '/'); + if (end) { + node_name_len = end - schema_path - 1; + } else { + node_name_len = strlen(schema_path + 1); + } + + /* ex: schema_path = /ietf-interfaces:interfaces/interface/ietf-ip:ipv4 */ + module_name_end = strchr(schema_path, ':'); + if (module_name_end && (!end || (module_name_end < end))) { + /* only get module's name, if it is in the current scope */ + free(module_name); + /* - 1 because module_name_end points to ':' */ + module_name = strndup(schema_path + 1, module_name_end - schema_path - 1); + if (!module_name) { + YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno)); + parent_node = NULL; + goto cleanup; + } + /* move the pointer to the beginning of the node's name - 1 */ + schema_path = module_name_end; + + /* recalculate the length of the node's name, because the module prefix mustn't be compared later */ + if (module_name_end < end) { + node_name_len = end - module_name_end - 1; + } else if (!end) { + node_name_len = strlen(module_name_end + 1); + } + } + + module = ly_ctx_get_module_implemented(ctx, module_name); + if (!module) { + /* unknown module name */ + parent_node = NULL; + goto cleanup; + } + + /* iterate over the node's siblings / module's top level containers */ + while ((node = lys_getnext(node, parent_node, module->compiled, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) { + if (end && !strncmp(node->name, schema_path + 1, node_name_len) && (node->name[node_name_len] == '\0')) { + /* check if the whole node's name matches and it's not just a common prefix */ + parent_node = node; + break; + } else if (!strncmp(node->name, schema_path + 1, node_name_len)) { + /* do the same here, however if there is no exact match, use the last node with the same prefix */ + if (strlen(node->name) == node_name_len) { + parent_node = node; + found_exact_match = 1; + break; + } else { + parent_node_tmp = node; + } + } + } + + if (!end && !found_exact_match) { + /* no exact match */ + parent_node = parent_node_tmp; + } + found_exact_match = 0; + + /* next iter */ + schema_path = strchr(schema_path + 1, '/'); + } + +cleanup: + free(module_name); + return parent_node; +} diff --git a/tools/lint/common.h b/tools/lint/common.h new file mode 100644 index 0000000..7c6a8ad --- /dev/null +++ b/tools/lint/common.h @@ -0,0 +1,257 @@ +/** + * @file common.h + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief libyang's yanglint tool - common functions and definitions for both interactive and non-interactive mode. + * + * Copyright (c) 2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef COMMON_H_ +#define COMMON_H_ + +#include <stdint.h> +#include <stdio.h> + +#include "libyang.h" + +#define PROMPT "> " + +/** + * @brief Default context creation options. + */ +#define YL_DEFAULT_CTX_OPTIONS LY_CTX_NO_YANGLIBRARY + +/** + * @brief Default data parsing flags. + */ +#define YL_DEFAULT_DATA_PARSE_OPTIONS LYD_PARSE_STRICT + +/** + * @brief log error message + */ +#define YLMSG_E(...) \ + fprintf(stderr, "YANGLINT[E]: " __VA_ARGS__) + +/** + * @brief log warning message + */ +#define YLMSG_W(...) \ + fprintf(stderr, "YANGLINT[W]: " __VA_ARGS__) + +#ifndef _WIN32 +# define PATH_SEPARATOR ":" +#else +# define PATH_SEPARATOR ";" +#endif + +/** + * @brief Storage for the list of the features (their names) in a specific YANG module. + */ +struct schema_features { + char *mod_name; + char **features; + ly_bool applied; +}; + +/** + * @brief Data connected with a file provided on a command line as a file path. + */ +struct cmdline_file { + struct ly_in *in; + const char *path; + LYD_FORMAT format; +}; + +/** + * @brief Free the schema features list (struct schema_features *) + * @param[in,out] flist The (struct schema_features *) to free. + */ +void free_features(void *flist); + +/** + * @brief Get the list of features connected with the specific YANG module. + * + * @param[in] fset The set of features information (struct schema_features *). + * @param[in] module Name of the YANG module which features should be found. + * @param[out] features Pointer to the list of features being returned. + */ +void get_features(struct ly_set *fset, const char *module, const char ***features); + +/** + * @brief Parse features being specified for the specific YANG module. + * + * Format of the input @p fstring is as follows: <module_name>:[<feature>,]* + * + * @param[in] fstring Input string to be parsed. + * @param[in, out] fset Features information set (of struct schema_features *). The set is being filled. + */ +int parse_features(const char *fstring, struct ly_set *fset); + +/** + * @brief Collect all features of a module. + * + * @param[in] mod Module to be searched for features. + * @param[out] set Set in which the features will be stored. + * @return 0 on success. + * @return 1 on error. + */ +int collect_features(const struct lys_module *mod, struct ly_set *set); + +/** + * @brief Print all features of a single module. + * + * @param[in] out The output handler for printing. + * @param[in] mod Module which contains the features. + * @param[in] set Set which holds the features. + */ +void print_features(struct ly_out *out, const struct lys_module *mod, const struct ly_set *set); + +/** + * @brief Generate a string, which will contain features paramater. + * + * @param[in] mod Module, for which the string will be generated. + * @param[in] set Set containing the features. + * @param[out] features_param String which will contain the output. + * @return 0 on success. + * @return 1 on error. + */ +int generate_features_output(const struct lys_module *mod, const struct ly_set *set, char **features_param); + +/** + * @brief Print all features of all implemented modules. + * + * @param[in] out The output handler for printing. + * @param[in] ctx Libyang context. + * @param[in] generate_features Flag expressing whether to generate features parameter. + * @param[out] features_param String, which will contain the output if the above flag is set. + * @return 0 on success. + * @return 1 on error. + */ +int print_all_features(struct ly_out *out, const struct ly_ctx *ctx, ly_bool generate_features, char **features_param); + +/** + * @brief Parse path of a schema module file into the directory and module name. + * + * @param[in] path Schema module file path to be parsed. + * @param[out] dir Pointer to the directory path where the file resides. Caller is expected to free the returned string. + * @param[out] module Pointer to the name of the module (without file suffixes or revision information) specified by the + * @path. Caller is expected to free the returned string. + * @return 0 on success + * @return -1 on error + */ +int parse_schema_path(const char *path, char **dir, char **module); + +/** + * @brief Get input handler for the specified path. + * + * Using the @p format_schema and @p format_data the type of the file can be limited (by providing NULL) or it can be + * got known if both types are possible. + * + * @param[in] filepath Path of the file to open. + * @param[out] format_schema Format of the schema detected from the file name. If NULL specified, the schema formats are + * prohibited and such files are refused. + * @param[out] format_data Format of the data detected from the file name. If NULL specified, the data formats are + * prohibited and such files are refused. + * @param[out] in Created input handler referring the file behind the @p filepath. + * @return 0 on success. + * @return -1 on failure. + */ +int get_input(const char *filepath, LYS_INFORMAT *format_schema, LYD_FORMAT *format_data, struct ly_in **in); + +/** + * @brief Free the command line file data (struct cmdline_file *) + * @param[in,out] cmdline_file The (struct cmdline_file *) to free. + */ +void free_cmdline_file(void *cmdline_file); + +/** + * @brief Create and fill the command line file data (struct cmdline_file *). + * @param[in] set Optional parameter in case the record is supposed to be added into a set. + * @param[in] in Input file handler. + * @param[in] path Filepath of the file. + * @param[in] format Format of the data file. + * @return The created command line file structure. + * @return NULL on failure + */ +struct cmdline_file *fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format); + +/** + * @brief Helper function to prepare argc, argv pair from a command line string. + * + * @param[in] cmdline Complete command line string. + * @param[out] argc_p Pointer to store argc value. + * @param[out] argv_p Pointer to store argv vector. + * @return 0 on success, non-zero on failure. + */ +int parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[]); + +/** + * @brief Destructor for the argument vector prepared by ::parse_cmdline(). + * + * @param[in,out] argv Argument vector to destroy. + */ +void free_cmdline(char *argv[]); + +/** + * @brief Get expected format of the @p filename's content according to the @p filename's suffix. + * @param[in] filename Name of the file to examine. + * @param[out] schema Pointer to a variable to store the expected input schema format. Do not provide the pointer in case a + * schema format is not expected. + * @param[out] data Pointer to a variable to store the expected input data format. Do not provide the pointer in case a data + * format is not expected. + * @return zero in case a format was successfully detected. + * @return nonzero in case it is not possible to get valid format from the @p filename. + */ +int get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data); + +/** + * @brief Print list of schemas in the context. + * + * @param[in] out Output handler where to print. + * @param[in] ctx Context to print. + * @param[in] outformat Optional output format. If not specified (:LYD_UNKNOWN), a simple list with single module per line + * is printed. Otherwise, the ietf-yang-library data are printed in the specified format. + * @return zero in case the data successfully printed. + * @return nonzero in case of error. + */ +int print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat); + +/** + * @brief Process the input data files - parse, validate and print according to provided options. + * + * @param[in] ctx libyang context with schema. + * @param[in] data_type The type of data in the input files. + * @param[in] merge Flag if the data should be merged before validation. + * @param[in] format Data format for printing. + * @param[in] out The output handler for printing. + * @param[in] options_parse Parser options. + * @param[in] options_validate Validation options. + * @param[in] options_print Printer options. + * @param[in] operational_f Optional operational datastore file information for the case of an extended validation of + * operation(s). + * @param[in] rpc_f Source RPC operation file information for parsing NETCONF rpc-reply. + * @param[in] inputs Set of file informations of input data files. + * @param[in] xpath The set of XPaths to be evaluated on the processed data tree, basic information about the resulting set + * is printed. Alternative to data printing. + * @return LY_ERR value. + */ +LY_ERR process_data(struct ly_ctx *ctx, enum lyd_type data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out, + uint32_t options_parse, uint32_t options_validate, uint32_t options_print, struct cmdline_file *operational_f, + struct cmdline_file *rpc_f, struct ly_set *inputs, struct ly_set *xpaths); + +/** + * @brief Get the node specified by the path. + * + * @param[in] ctx libyang context with schema. + * @param[in] schema_path Path to the wanted node. + * @return Pointer to the schema node specified by the path on success, NULL otherwise. + */ +const struct lysc_node * find_schema_path(const struct ly_ctx *ctx, const char *schema_path); + +#endif /* COMMON_H_ */ diff --git a/tools/lint/completion.c b/tools/lint/completion.c new file mode 100644 index 0000000..9843816 --- /dev/null +++ b/tools/lint/completion.c @@ -0,0 +1,379 @@ +/** + * @file completion.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @brief libyang's yanglint tool auto completion + * + * Copyright (c) 2015 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200809L /* strdup */ + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "libyang.h" + +#include "cmd.h" +#include "common.h" +#include "compat.h" +#include "linenoise/linenoise.h" + +/* from the main.c */ +extern struct ly_ctx *ctx; + +/** + * @brief Add a match to the completions array. + * + * @param[in] match Match to be added. + * @param[in,out] matches Matches provided to the user as a completion hint. + * @param[in,out] match_count Number of matches. + */ +static void +cmd_completion_add_match(const char *match, char ***matches, unsigned int *match_count) +{ + void *p; + + ++(*match_count); + p = realloc(*matches, *match_count * sizeof **matches); + if (!p) { + YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno)); + return; + } + *matches = p; + (*matches)[*match_count - 1] = strdup(match); +} + +/** + * @brief Provides completion for command names. + * + * @param[in] hint User input. + * @param[out] matches Matches provided to the user as a completion hint. + * @param[out] match_count Number of matches. + */ +static void +get_cmd_completion(const char *hint, char ***matches, unsigned int *match_count) +{ + int i; + + *match_count = 0; + *matches = NULL; + + for (i = 0; commands[i].name; i++) { + if (!strncmp(hint, commands[i].name, strlen(hint))) { + cmd_completion_add_match(commands[i].name, matches, match_count); + } + } +} + +/** + * @brief Provides completion for module names. + * + * @param[in] hint User input. + * @param[out] matches Matches provided to the user as a completion hint. + * @param[out] match_count Number of matches. + */ +static void +get_model_completion(const char *hint, char ***matches, unsigned int *match_count) +{ + LY_ARRAY_COUNT_TYPE u; + uint32_t idx = 0; + const struct lys_module *module; + + *match_count = 0; + *matches = NULL; + + while ((module = ly_ctx_get_module_iter(ctx, &idx))) { + if (!strncmp(hint, module->name, strlen(hint))) { + cmd_completion_add_match(module->name, matches, match_count); + } + + LY_ARRAY_FOR(module->parsed->includes, u) { + if (!strncmp(hint, module->parsed->includes[u].submodule->name, strlen(hint))) { + cmd_completion_add_match(module->parsed->includes[u].submodule->name, matches, match_count); + } + } + } +} + +/** + * @brief Add all child nodes of a single node to the completion hint. + * + * @param[in] last_node Node of which children will be added to the hint. + * @param matches[out] Matches provided to the user as a completion hint. + * @param match_count[out] Number of matches. + */ +static void +single_hint_add_children(const struct lysc_node *last_node, char ***matches, unsigned int *match_count) +{ + const struct lysc_node *node = NULL; + char *match; + + if (!last_node) { + return; + } + + while ((node = lys_getnext(node, last_node, NULL, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) { + match = lysc_path(node, LYSC_PATH_LOG, NULL, 0); + cmd_completion_add_match(match, matches, match_count); + free(match); + } +} + +/** + * @brief Add module and/or node's children names to the hint. + * + * @param[in] module Compiled schema module. + * @param[in] parent Parent node of which children are potential matches. + * @param[in] hint_node_name Node name contained within the hint specified by user. + * @param[in,out] matches Matches provided to the user as a completion hint. + * @param[in,out] match_count Number of matches. + * @param[out] last_node Last processed node. + */ +static void +add_all_children_nodes(const struct lysc_module *module, const struct lysc_node *parent, + const char *hint_node_name, char ***matches, unsigned int *match_count, const struct lysc_node **last_node) +{ + const struct lysc_node *node; + char *match, *node_name = NULL; + + *last_node = NULL; + + if (!parent && !module) { + return; + } + + node = NULL; + while ((node = lys_getnext(node, parent, module, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) { + if (parent && (node->module != parent->module)) { + /* augmented node */ + if (asprintf(&node_name, "%s:%s", node->module->name, node->name) == -1) { + YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno)); + break; + } + } else { + node_name = strdup(node->name); + if (!node_name) { + YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno)); + break; + } + } + if (!hint_node_name || !strncmp(hint_node_name, node_name, strlen(hint_node_name))) { + /* adding just module names + their top level node(s) to the hint */ + *last_node = node; + match = lysc_path(node, LYSC_PATH_LOG, NULL, 0); + cmd_completion_add_match(match, matches, match_count); + free(match); + } + free(node_name); + } +} + +/** + * @brief Provides completion for schemas. + * + * @param[in] hint User input. + * @param[out] matches Matches provided to the user as a completion hint. + * @param[out] match_count Number of matches. + */ +static void +get_schema_completion(const char *hint, char ***matches, unsigned int *match_count) +{ + const struct lys_module *module; + uint32_t idx; + const char *start; + char *end, *module_name = NULL, *path = NULL; + const struct lysc_node *parent, *last_node; + int rc = 0; + size_t len; + + *match_count = 0; + *matches = NULL; + + if (strlen(hint)) { + if (hint[0] != '/') { + return; + } + start = hint + 1; + } else { + start = hint; + } + + end = strchr(start, ':'); + if (!end) { + /* no module name */ + len = strlen(start); + + /* go through all the modules */ + idx = 0; + while ((module = ly_ctx_get_module_iter(ctx, &idx))) { + if (!module->implemented) { + continue; + } + + if (!len || !strncmp(start, module->name, len)) { + /* add all their (matching) top level nodes */ + add_all_children_nodes(module->compiled, NULL, NULL, matches, match_count, &last_node); + } + } + } else { + /* module name known */ + module_name = strndup(start, end - start); + if (!module_name) { + YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno)); + rc = 1; + goto cleanup; + } + + module = ly_ctx_get_module_implemented(ctx, module_name); + if (!module) { + goto cleanup; + } + + /* find the last '/', if it is at the beginning of the hint, only path up to the top level node is known, + * else the name of the last node starts after the found '/' */ + start = strrchr(hint, '/'); + if (!start) { + goto cleanup; + } + + if (start == hint) { + /* only the (incomplete) top level node path, add all (matching) top level nodes */ + add_all_children_nodes(module->compiled, NULL, end + 1, matches, match_count, &last_node); + goto cleanup; + } + + /* get rid of stuff after the last '/' to obtain the parent node */ + path = strndup(hint, start - hint); + if (!path) { + YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno)); + rc = 1; + goto cleanup; + } + + /* get the last parent in the hint (it may not exist) */ + parent = find_schema_path(ctx, path); + + /* add all (matching) child nodes of the parent */ + add_all_children_nodes(NULL, parent, start + 1, matches, match_count, &last_node); + } + +cleanup: + if (!rc && (*match_count == 1)) { + /* to avoid a single hint (space at the end), add all children as hints */ + single_hint_add_children(last_node, matches, match_count); + } + free(path); + free(module_name); +} + +/** + * @brief Get the string before the hint, which autocompletion is for. + * + * @param[in] buf Complete user input. + * @param[in] hint Hint part of the user input. + * @return Pointer to the last string. + */ +static const char * +get_last_str(const char *buf, const char *hint) +{ + const char *ptr; + + if (buf == hint) { + return buf; + } + + ptr = hint - 1; + while (ptr[0] == ' ') { + --ptr; + if (buf == ptr) { + return buf; + } + } + + while (ptr[-1] != ' ') { + --ptr; + if (buf == ptr) { + return buf; + } + } + + return ptr; +} + +/* callback */ +void +complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc) +{ + struct autocomplete { + const char *cmd; /**< command */ + const char *opt; /**< optional option */ + int last_opt; /**< whether to autocomplete even if an option is last in the hint */ + + void (*ln_cb)(const char *, const char *, linenoiseCompletions *); /**< linenoise callback to call */ + void (*yl_cb)(const char *, char ***, unsigned int *); /**< yanglint callback to call */ + } ac[] = { + {"add", NULL, 1, linenoisePathCompletion, NULL}, + {"searchpath", NULL, 0, linenoisePathCompletion, NULL}, + {"data", NULL, 0, linenoisePathCompletion, NULL}, + {"print", NULL, 0, NULL, get_model_completion}, + {"feature", NULL, 0, NULL, get_model_completion}, + {"print", "-P", 1, NULL, get_schema_completion}, + }; + size_t cmd_len; + const char *last; + char **matches = NULL; + unsigned int match_count = 0, i; + + if (buf == hint) { + /* command autocomplete */ + get_cmd_completion(hint, &matches, &match_count); + + } else { + for (i = 0; i < (sizeof ac / sizeof *ac); ++i) { + cmd_len = strlen(ac[i].cmd); + if (strncmp(buf, ac[i].cmd, cmd_len) || (buf[cmd_len] != ' ')) { + /* not this command */ + continue; + } + + last = get_last_str(buf, hint); + if (ac[i].opt && strncmp(ac[i].opt, last, strlen(ac[i].opt))) { + /* autocompletion for (another) option */ + continue; + } + if (!ac[i].last_opt && (last[0] == '-')) { + /* autocompletion for the command, not an option */ + continue; + } + if ((last != buf) && (last[0] != '-')) { + /* autocompleted */ + return; + } + + /* callback */ + if (ac[i].ln_cb) { + ac[i].ln_cb(buf, hint, lc); + } else { + ac[i].yl_cb(hint, &matches, &match_count); + } + break; + } + } + + /* transform matches into autocompletion, if needed */ + for (i = 0; i < match_count; ++i) { + linenoiseAddCompletion(lc, matches[i]); + free(matches[i]); + } + free(matches); +} diff --git a/tools/lint/completion.h b/tools/lint/completion.h new file mode 100644 index 0000000..20b8d17 --- /dev/null +++ b/tools/lint/completion.h @@ -0,0 +1,25 @@ +/** + * @file completion.h + * @author Michal Vasko <mvasko@cesnet.cz> + * @brief libyang's yanglint tool auto completion header + * + * Copyright (c) 2015 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef COMPLETION_H_ +#define COMPLETION_H_ + +#include "./linenoise/linenoise.h" + +/** + * @brief Command line completion callback. + */ +void complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc); + +#endif /* COMPLETION_H_ */ diff --git a/tools/lint/configuration.c b/tools/lint/configuration.c new file mode 100644 index 0000000..86179fa --- /dev/null +++ b/tools/lint/configuration.c @@ -0,0 +1,125 @@ +/** + * @file configuration.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @brief yanglint configuration + * + * Copyright (c) 2017 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "configuration.h" + +#include <errno.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "linenoise/linenoise.h" + +#include "common.h" + +/* Yanglint home (appended to ~/) */ +#define YL_DIR ".yanglint" + +char * +get_yanglint_dir(void) +{ + int ret; + struct passwd *pw; + char *user_home, *yl_dir; + + if (!(pw = getpwuid(getuid()))) { + YLMSG_E("Determining home directory failed (%s).\n", strerror(errno)); + return NULL; + } + user_home = pw->pw_dir; + + yl_dir = malloc(strlen(user_home) + 1 + strlen(YL_DIR) + 1); + if (!yl_dir) { + YLMSG_E("Memory allocation failed (%s).\n", strerror(errno)); + return NULL; + } + sprintf(yl_dir, "%s/%s", user_home, YL_DIR); + + ret = access(yl_dir, R_OK | X_OK); + if (ret == -1) { + if (errno == ENOENT) { + /* directory does not exist */ + YLMSG_W("Configuration directory \"%s\" does not exist, creating it.\n", yl_dir); + if (mkdir(yl_dir, 00700)) { + if (errno != EEXIST) { + /* parallel execution, yay */ + YLMSG_E("Configuration directory \"%s\" cannot be created (%s).\n", yl_dir, strerror(errno)); + free(yl_dir); + return NULL; + } + } + } else { + YLMSG_E("Configuration directory \"%s\" exists but cannot be accessed (%s).\n", yl_dir, strerror(errno)); + free(yl_dir); + return NULL; + } + } + + return yl_dir; +} + +void +load_config(void) +{ + char *yl_dir, *history_file; + + if ((yl_dir = get_yanglint_dir()) == NULL) { + return; + } + + history_file = malloc(strlen(yl_dir) + 9); + if (!history_file) { + YLMSG_E("Memory allocation failed (%s).\n", strerror(errno)); + free(yl_dir); + return; + } + + sprintf(history_file, "%s/history", yl_dir); + if (access(history_file, F_OK) && (errno == ENOENT)) { + YLMSG_W("No saved history.\n"); + } else if (linenoiseHistoryLoad(history_file)) { + YLMSG_E("Failed to load history.\n"); + } + + free(history_file); + free(yl_dir); +} + +void +store_config(void) +{ + char *yl_dir, *history_file; + + if ((yl_dir = get_yanglint_dir()) == NULL) { + return; + } + + history_file = malloc(strlen(yl_dir) + 9); + if (!history_file) { + YLMSG_E("Memory allocation failed (%s).\n", strerror(errno)); + free(yl_dir); + return; + } + + sprintf(history_file, "%s/history", yl_dir); + if (linenoiseHistorySave(history_file)) { + YLMSG_E("Failed to save history.\n"); + } + + free(history_file); + free(yl_dir); +} diff --git a/tools/lint/configuration.h b/tools/lint/configuration.h new file mode 100644 index 0000000..d677876 --- /dev/null +++ b/tools/lint/configuration.h @@ -0,0 +1,36 @@ +/** + * @file configuration.h + * @author Michal Vasko <mvasko@cesnet.cz> + * @brief yanglint configuration header + * + * Copyright (c) 2017 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef CONFIGURATION_H_ +#define CONFIGURATION_H_ + +/** + * @brief Finds the current user's yanglint dir + * @return NULL on failure, dynamically allocated yanglint dir path + * otherwise + */ +char *get_yanglint_dir(void); + +/** + * @brief Checks all the relevant files and directories creating any + * that are missing, sets the saved configuration (currently only history) + */ +void load_config(void); + +/** + * @brief Saves the current configuration (currently only history) + */ +void store_config(void); + +#endif /* CONFIGURATION_H_ */ diff --git a/tools/lint/examples/README.md b/tools/lint/examples/README.md new file mode 100644 index 0000000..604591c --- /dev/null +++ b/tools/lint/examples/README.md @@ -0,0 +1,471 @@ +# YANGLINT - Interactive Mode Examples + +This text provides several use-case of the `yanglint(1)` interactive +mode. For basic information about the `yanglint(1)` usage, please see +the man page. + +The examples are supposed to be went through one by one. Some of the examples +suppose the specific schemas loaded in some of the previous example is still +loaded. If an addition work is need, the *preparation* part in the example +provides information what to do. + +To show all available command of the `yanglint(1)`, use the `help` command: +``` +> help +Available commands: + help Display commands description + add Add a new module from a specific file + load Load a new schema from the searchdirs + print Print a module + data Load, validate and optionally print instance data + list List all the loaded modules + feature Print all features of module(s) with their state + searchpath Print/set the search path(s) for schemas + clear Clear the context - remove all the loaded modules + verb Change verbosity + debug Display specific debug message groups + quit Quit the program + ? Display commands description + exit Quit the program +``` +To show the information about the specific command, use the `help` command in +combination with the command name you are interested in: +``` +> help searchpath +Usage: searchpath [--clear] [<modules-dir-path> ...] + Set paths of directories where to search for imports and + includes of the schema modules. The current working directory + and the path of the module being added is used implicitly. + The 'load' command uses these paths to search even for the + schema modules to be loaded. +``` + +The input files referred in this document are available together with this +document. + +## Duplicit Data Model + +Let's have two data models [module1.yang](./module1.yang) +and [module1b.yang](./module1b.yang). +They differ in the module name but their namespaces are the same. + +Preparation: + +``` +> clear +> add module1.yang +> list +``` + +Output: + +``` +List of the loaded models: + i ietf-yang-metadata@2016-08-05 + I yang@2022-06-16 + i ietf-inet-types@2013-07-15 + i ietf-yang-types@2013-07-15 + I ietf-yang-schema-mount@2019-01-14 + I module1 +``` + +Command and its output: + +``` +> add module1b.yang +libyang[0]: Two different modules ("module1" and "module1b") have the same namespace "urn:yanglint:module". +libyang[0]: Parsing module "module1b" failed. +``` + +## Yang Data Model Validation + +**YANG/YIN syntax** + +`module2.yin` contains a syntax error. +There is a bad syntax of the `type` statement in YIN file. + +``` +<type value="string"/> +``` + +instead of + +``` +<type name="string"/> +``` + +Preparation: + +``` +> clear +``` + +Command and its output: + +``` +> add module2.yin +libyang[0]: Unexpected attribute "value" of "type" element. (path: Line number 8.) +libyang[0]: Parsing module "module2" failed. +``` + +Similarly, there is a typo in `module2.yang`. + +**XPath errors** + +`libyang` and `yanglint(1)` is able to detect also errors in XPath expressions. +In `module3.yang` the `must` expression refers to the node which does not exists. + +Preparation: + +``` +> clear +``` + +Command and its output: + +``` +> add module3.yang +libyang[1]: Schema node "a" for parent "/module3:c" not found; in expr "../c/a" with context node "/module3:m". +``` + +Note that libyang prints only a warning in this case because it is not +specified that XPath expressions must refer to existing nodes. + +## Data Validation + +Preparation: + +``` +> clear +> add ietf-netconf-acm.yang +``` + +**Unknown data** + +By default, yanglint ignores unknown data and no error is printed (you can +compare real content of the `datastore.xml` file and what yanglint prints +in the following command if you add `-f xml` option). + +Command and its output: + +``` +> data -t config datastore.xml +``` + +We use option `-t` to specify type of the data in `datastore.xml`. By the +`config` value we declare that the input file contains all the configuration +data (with at least all the mandatory nodes as required by the loaded schemas), +but without the status data. More examples of different data types will follow. + +Command and its output: + +``` +> data -t config datastore.xml +libyang[0]: No module with namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces" in the context. (path: Line number 20.) +YANGLINT[E]: Failed to parse input data file "datastore.xml". +``` + +Note that in case of working with complete datastore including the status data +(no `-t` option is specified), `yanglint(1)` has to add status data from its +internal `ietf-yang-library` module. + +**RPC and RPC-reply** + +It is possible to validate RPCs and their replies as well. + +Peparation: + +``` +> clear +> add module4.yang +``` + +Command and its output: + +``` +> data -t rpc rpc.xml +``` + +Reply to this RPC can be validated too, but it must be nested in the original +RPC element. + +Command and its output: + +``` +> data -t reply ../tools/lint/examples/rpc-reply.xml +``` + +**action and action-reply** + +Actions are validated the same way as RPCs except you need to be careful +about the input file structure. No NETCONF-specific envelopes are expected. + +Preparation + +``` +> clear +> add module4.yang +``` + +Command and its output: + +``` +> data -t rpc action.xml +``` + +Command and its output: + +``` +> data -t rpc action-reply.xml action.xml +``` + +**notification** + +Both top-level and nested notification can be validated. + +Preparation + +``` +> clear +> add module4.yang +``` + +Command and its output: + +``` +> data -t notif notification.xml +``` + +Command and its output: + +``` +> data -t notif nested-notification.xml +``` + + +**Multiple top-level elements in a single document** + +As a feature and in conflict with the XML definition, `yanglint(1)` (and libyang) +is able to read XML files with multiple top-level elements. Such documents +are not well-formed according to the XML spec, but it fits to how the YANG +interconnects data trees (defined as top-level elements of a single schema +or by multiple schemas). + +Preparation: + +``` +> clear +> add ietf-netconf-acm.yang +> add ietf-interfaces.yang +> add ietf-ip.yang +> add iana-if-type.yang +``` + +Command and its output: + +``` +> data -t config datastore.xml +``` + +**Different data content types** + +Since NETCONF requires the data described by YANG to be used in different +situations (e.g. as <edit-config data>, result of the <get> with status data +included or as a result of the <get-config> without the status data and +possibly filtered, so without specified subtrees), it must be possible to +specify which kind of data is going to be parsed. In `yanglint(1)`, this is done +via `-t` option. The list of supported modes can be displayed by the `-h` +option given to the `data` command. In general, the `auto` value lets the +`yanglint(1)` to recognize the data type automatically by the additional top-level +elements added to the parsed data. This is the same way as `pyang(1)` uses. Note, +that the automatic data type recognition is available only for the XML input. + +**Malformed XML data** + +Command and its output: + +``` +> data -t edit config-missing-key.xml +libyang[0]: Node "nam" not found as a child of "group" node. (path: Schema location "/ietf-netconf-acm:nacm/groups/group", data location "/ietf-netconf-acm:group", line number 19.) +YANGLINT[E]: Failed to parse input data file "config-missing-key.xml". +``` + +**State information in edit-config XML** + +Command and its output: + +``` +> data -t edit config-unknown-element.xml +libyang[0]: Unexpected data state node "denied-operations" found. (path: Schema location "/ietf-netconf-acm:nacm/denied-operations", data location "/ietf-netconf-acm:nacm", line number 24.) +YANGLINT[E]: Failed to parse input data file "config-unknown-element.xml". +``` + +**Missing required element in NETCONF data** + +Command and its output: + +``` +> data data-missing-key.xml +libyang[0]: List instance is missing its key "name". (path: Schema location "/ietf-netconf-acm:nacm/rule-list/rule", data location "/ietf-netconf-acm:rule", line number 10.) +YANGLINT[E]: Failed to parse input data file "data-missing-key.xml". +``` + +**Malformed XML** + +Command and its output: + +``` +> data data-malformed-xml.xml +libyang[0]: Node "nam" not found as a child of "rule" node. (path: Schema location "/ietf-netconf-acm:nacm/rule-list/rule", data location "/ietf-netconf-acm:rule", line number 8.) +YANGLINT[E]: Failed to parse input data file "data-malformed-xml.xml". +``` + +Command and its output: + +``` +> data data-malformed-xml2.xml +libyang[0]: Child element "module-name" inside a terminal node "name" found. (path: Schema location "/ietf-netconf-acm:nacm/rule-list/rule/name", data location "/ietf-netconf-acm:name", line number 7.) +YANGLINT[E]: Failed to parse input data file "data-malformed-xml2.xml". +``` + +**Bad value** + +Command and its output: + +``` +> data data-out-of-range-value.xml +libyang[0]: Value "-1" is out of type uint32 min/max bounds. (path: Schema location "/ietf-netconf-acm:nacm/denied-operations", data location "/ietf-netconf-acm:nacm", line number 24.) +YANGLINT[E]: Failed to parse input data file "data-out-of-range-value.xml". +``` + +## Validation of "when" Statement in Data + +Preparation: + +``` +> clear +> add ietf-netconf-acm-when.yang +``` + +**`When` condition is not satisfied since `denied-operation = 0`** + +Command and its output: + +``` +> data data-acm.xml +libyang[0]: When condition "../denied-operations > 0" not satisfied. (path: Schema location "/ietf-netconf-acm-when:nacm/denied-data-writes", data location "/ietf-netconf-acm-when:nacm/denied-data-writes".) +YANGLINT[E]: Failed to parse input data file "data-acm.xml". +``` + +## Printing a Data Model + +Preparation: + +``` +> clear +> add ietf-netconf-acm.yang +``` + +**Print a `pyang`-style tree** + +Command and its output: + +``` +> print ietf-netconf-acm +module: ietf-netconf-acm + +--rw nacm + +--rw enable-nacm? boolean + +--rw read-default? action-type + +--rw write-default? action-type + +--rw exec-default? action-type + +--rw enable-external-groups? boolean + +--ro denied-operations yang:zero-based-counter32 + +--ro denied-data-writes yang:zero-based-counter32 + +--ro denied-notifications yang:zero-based-counter32 + +--rw groups + | +--rw group* [name] + | +--rw name group-name-type + | +--rw user-name* user-name-type + +--rw rule-list* [name] + +--rw name string + +--rw group* union + +--rw rule* [name] + +--rw name string + +--rw module-name? union + +--rw (rule-type)? + | +--:(protocol-operation) + | | +--rw rpc-name? union + | +--:(notification) + | | +--rw notification-name? union + | +--:(data-node) + | +--rw path node-instance-identifier + +--rw access-operations? union + +--rw action action-type + +--rw comment? string +``` + +**Print information about specific model part** + +Command and its output: + +``` +> print -f info -P /ietf-netconf-acm:nacm/ietf-netconf-acm:enable-nacm ietf-netconf-acm +leaf enable-nacm { + ietf-netconf-acm:default-deny-all; + type boolean; + default "true"; + config true; + status current; + description + "Enables or disables all NETCONF access control + enforcement. If 'true', then enforcement + is enabled. If 'false', then enforcement + is disabled."; +} +``` + +## Usage of `feature` in Yang + +Preparation: + +``` +> clear +> add ietf-interfaces.yang +> add ietf-ip.yang -F ietf-ip:* +> add iana-if-type.yang +``` + +Note: This example also shows `JSON` output of the command. + +Command and its output: +``` +> feature ietf-ip +ietf-ip features: + ipv4-non-contiguous-netmasks (on) + ipv6-privacy-autoconf (on) +> data -f json -t config data-ip.xml +{ + "ietf-interfaces:interfaces": { + "interface": [ + { + "name": "eth0", + "description": "Wire Connection", + "type": "iana-if-type:ethernetCsmacd", + "enabled": true, + "ietf-ip:ipv4": { + "address": [ + { + "ip": "192.168.1.15", + "netmask": "255.255.255.0" + }, + { + "ip": "192.168.1.10", + "netmask": "255.255.255.0" + } + ] + } + } + ] + } +} +``` diff --git a/tools/lint/examples/action-reply.xml b/tools/lint/examples/action-reply.xml new file mode 100644 index 0000000..e6fc284 --- /dev/null +++ b/tools/lint/examples/action-reply.xml @@ -0,0 +1,8 @@ +<cont1 xmlns="urn:module4"> + <list> + <leaf1>key_val</leaf1> + <act> + <leaf3>some_output</leaf3> + </act> + </list> +</cont1> diff --git a/tools/lint/examples/action.xml b/tools/lint/examples/action.xml new file mode 100644 index 0000000..661fecf --- /dev/null +++ b/tools/lint/examples/action.xml @@ -0,0 +1,8 @@ +<cont1 xmlns="urn:module4"> + <list> + <leaf1>key_val</leaf1> + <act> + <leaf2>some_input</leaf2> + </act> + </list> +</cont1> diff --git a/tools/lint/examples/config-acm.xml b/tools/lint/examples/config-acm.xml new file mode 100644 index 0000000..8c99419 --- /dev/null +++ b/tools/lint/examples/config-acm.xml @@ -0,0 +1,24 @@ +<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"> + <rule-list> + <name>almighty</name> + <group>almighty</group> + <group nc:operation="create">test</group> + <rule> + <name>almighty</name> + <module-name>*</module-name> + <access-operations>*</access-operations> + <action>permit</action> + </rule> + </rule-list> + <groups> + <group> + <name>test</name> + <user-name>smith</user-name> + </group> + <group> + <name>almighty</name> + <user-name>smith</user-name> + <user-name>doe</user-name> + </group> + </groups> +</nacm> diff --git a/tools/lint/examples/config-missing-key.xml b/tools/lint/examples/config-missing-key.xml new file mode 100644 index 0000000..c30c2b0 --- /dev/null +++ b/tools/lint/examples/config-missing-key.xml @@ -0,0 +1,24 @@ +<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm"> + <rule-list> + <name>almighty</name> + <group>almighty</group> + <group>test</group> + <rule> + <name>almighty</name> + <module-name>*</module-name> + <access-operations>*</access-operations> + <action>permit</action> + </rule> + </rule-list> + <groups> + <group> + <name>test</name> + <user-name>smith</user-name> + </group> + <group> + <nam>almighty</name> + <user-name>smith</user-name> + <user-name>doe</user-name> + </group> + </groups> +</nacm> diff --git a/tools/lint/examples/config-unknown-element.xml b/tools/lint/examples/config-unknown-element.xml new file mode 100644 index 0000000..66ae880 --- /dev/null +++ b/tools/lint/examples/config-unknown-element.xml @@ -0,0 +1,27 @@ +<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm"> + <rule-list> + <name>almighty</name> + <group>almighty</group> + <group>test</group> + <rule> + <name>almighty</name> + <module-name>*</module-name> + <access-operations>*</access-operations> + <action>permit</action> + </rule> + </rule-list> + <groups> + <group> + <name>test</name> + <user-name>smith</user-name> + </group> + <group> + <name>almighty</name> + <user-name>smith</user-name> + <user-name>doe</user-name> + </group> + </groups> + <denied-operations>0</denied-operations> + <denied-data-writes>0</denied-data-writes> + <denied-notifications>0</denied-notifications> +</nacm> diff --git a/tools/lint/examples/data-acm.xml b/tools/lint/examples/data-acm.xml new file mode 100644 index 0000000..66ae880 --- /dev/null +++ b/tools/lint/examples/data-acm.xml @@ -0,0 +1,27 @@ +<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm"> + <rule-list> + <name>almighty</name> + <group>almighty</group> + <group>test</group> + <rule> + <name>almighty</name> + <module-name>*</module-name> + <access-operations>*</access-operations> + <action>permit</action> + </rule> + </rule-list> + <groups> + <group> + <name>test</name> + <user-name>smith</user-name> + </group> + <group> + <name>almighty</name> + <user-name>smith</user-name> + <user-name>doe</user-name> + </group> + </groups> + <denied-operations>0</denied-operations> + <denied-data-writes>0</denied-data-writes> + <denied-notifications>0</denied-notifications> +</nacm> diff --git a/tools/lint/examples/data-ip.xml b/tools/lint/examples/data-ip.xml new file mode 100644 index 0000000..1894f6d --- /dev/null +++ b/tools/lint/examples/data-ip.xml @@ -0,0 +1,12 @@ +<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"> + <interface> + <name>eth0</name> + <description>Wire Connection</description> + <type xmlns:ift="urn:ietf:params:xml:ns:yang:iana-if-type">ift:ethernetCsmacd</type> + <enabled>true</enabled> + <ipv4 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip"> + <address><ip>192.168.1.15</ip><netmask>255.255.255.0</netmask></address> + <address><ip>192.168.1.10</ip><netmask>255.255.255.0</netmask></address> + </ipv4> + </interface> +</interfaces> diff --git a/tools/lint/examples/data-malformed-xml.xml b/tools/lint/examples/data-malformed-xml.xml new file mode 100644 index 0000000..908d79b --- /dev/null +++ b/tools/lint/examples/data-malformed-xml.xml @@ -0,0 +1,27 @@ +<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm"> + <rule-list> + <name>almighty</name> + <group>almighty</group> + <group>test</group> + <rule> + <nam>almighty + <module-name>*</module-name> + <access-operations>*</access-operations> + <action>permit</action> + </rule> + </rule-list> + <groups> + <group> + <name>test</name> + <user-name>smith</user-name> + </group> + <group> + <name>almighty</name> + <user-name>smith</user-name> + <user-name>doe</user-name> + </group> + </groups> + <denied-operations>0</denied-operations> + <denied-data-writes>0</denied-data-writes> + <denied-notifications>0</denied-notifications> +</nacm> diff --git a/tools/lint/examples/data-malformed-xml2.xml b/tools/lint/examples/data-malformed-xml2.xml new file mode 100644 index 0000000..8d0e5f4 --- /dev/null +++ b/tools/lint/examples/data-malformed-xml2.xml @@ -0,0 +1,26 @@ +<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm"> + <rule-list> + <name>almighty</name> + <group>almighty</group> + <group>test</group> + <rule> + <name>almighty<module-name></name> *</module-name> + <access-operations>*</access-operations> + <action>permit</action> + </rule> + </rule-list> + <groups> + <group> + <name>test</name> + <user-name>smith</user-name> + </group> + <group> + <name>almighty</name> + <user-name>smith</user-name> + <user-name>doe</user-name> + </group> + </groups> + <denied-operations>0</denied-operations> + <denied-data-writes>0</denied-data-writes> + <denied-notifications>0</denied-notifications> +</nacm> diff --git a/tools/lint/examples/data-missing-key.xml b/tools/lint/examples/data-missing-key.xml new file mode 100644 index 0000000..2e9684d --- /dev/null +++ b/tools/lint/examples/data-missing-key.xml @@ -0,0 +1,26 @@ +<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm"> + <rule-list> + <name>almighty</name> + <group>almighty</group> + <group>test</group> + <rule> + <module-name>*</module-name> + <access-operations>*</access-operations> + <action>permit</action> + </rule> + </rule-list> + <groups> + <group> + <name>test</name> + <user-name>smith</user-name> + </group> + <group> + <name>almighty</name> + <user-name>smith</user-name> + <user-name>doe</user-name> + </group> + </groups> + <denied-operations>0</denied-operations> + <denied-data-writes>0</denied-data-writes> + <denied-notifications>0</denied-notifications> +</nacm> diff --git a/tools/lint/examples/data-out-of-range-value.xml b/tools/lint/examples/data-out-of-range-value.xml new file mode 100644 index 0000000..2af5ba9 --- /dev/null +++ b/tools/lint/examples/data-out-of-range-value.xml @@ -0,0 +1,27 @@ +<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm"> + <rule-list> + <name>almighty</name> + <group>almighty</group> + <group>test</group> + <rule> + <name>almighty</name> + <module-name>*</module-name> + <access-operations>*</access-operations> + <action>permit</action> + </rule> + </rule-list> + <groups> + <group> + <name>test</name> + <user-name>smith</user-name> + </group> + <group> + <name>almighty</name> + <user-name>smith</user-name> + <user-name>doe</user-name> + </group> + </groups> + <denied-operations>-1</denied-operations> + <denied-data-writes>0</denied-data-writes> + <denied-notifications>0</denied-notifications> +</nacm> diff --git a/tools/lint/examples/datastore.xml b/tools/lint/examples/datastore.xml new file mode 100644 index 0000000..c6a6fc9 --- /dev/null +++ b/tools/lint/examples/datastore.xml @@ -0,0 +1,29 @@ +<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm"> + <rule-list> + <name>almighty</name> + <group>almighty</group> + <rule> + <name>almighty</name> + <module-name>*</module-name> + <access-operations>*</access-operations> + <action>permit</action> + </rule> + </rule-list> + <groups> + <group> + <name>almighty</name> + <user-name>smith</user-name> + </group> + </groups> +</nacm> +<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"> + <interface> + <name>eth0</name> + <description>Wire Connection</description> + <type xmlns:ift="urn:ietf:params:xml:ns:yang:iana-if-type">ift:ethernetCsmacd</type> + <enabled>true</enabled> + <ipv4 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip"> + <address><ip>192.168.1.15</ip><prefix-length>24</prefix-length></address> + </ipv4> + </interface> +</interfaces> diff --git a/tools/lint/examples/iana-if-type.yang b/tools/lint/examples/iana-if-type.yang new file mode 100644 index 0000000..5dd8219 --- /dev/null +++ b/tools/lint/examples/iana-if-type.yang @@ -0,0 +1,1547 @@ +module iana-if-type { + namespace "urn:ietf:params:xml:ns:yang:iana-if-type"; + prefix ianaift; + + import ietf-interfaces { + prefix if; + } + + organization "IANA"; + contact + " Internet Assigned Numbers Authority + + Postal: ICANN + 4676 Admiralty Way, Suite 330 + Marina del Rey, CA 90292 + + Tel: +1 310 823 9358 + <mailto:iana@iana.org>"; + description + "This YANG module defines YANG identities for IANA-registered + interface types. + + This YANG module is maintained by IANA and reflects the + 'ifType definitions' registry. + + The latest revision of this YANG module can be obtained from + the IANA web site. + + Requests for new values should be made to IANA via + email (iana@iana.org). + + Copyright (c) 2014 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + The initial version of this YANG module is part of RFC 7224; + see the RFC itself for full legal notices."; + reference + "IANA 'ifType definitions' registry. + <http://www.iana.org/assignments/smi-numbers>"; + + revision 2014-05-08 { + description + "Initial revision."; + reference + "RFC 7224: IANA Interface Type YANG Module"; + } + + identity iana-interface-type { + base if:interface-type; + description + "This identity is used as a base for all interface types + defined in the 'ifType definitions' registry."; + } + + + + + + + identity other { + base iana-interface-type; + } + identity regular1822 { + base iana-interface-type; + } + identity hdh1822 { + base iana-interface-type; + } + identity ddnX25 { + base iana-interface-type; + } + identity rfc877x25 { + base iana-interface-type; + reference + "RFC 1382 - SNMP MIB Extension for the X.25 Packet Layer"; + } + identity ethernetCsmacd { + base iana-interface-type; + description + "For all Ethernet-like interfaces, regardless of speed, + as per RFC 3635."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity iso88023Csmacd { + base iana-interface-type; + status deprecated; + description + "Deprecated via RFC 3635. + Use ethernetCsmacd(6) instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity iso88024TokenBus { + base iana-interface-type; + } + identity iso88025TokenRing { + base iana-interface-type; + } + identity iso88026Man { + base iana-interface-type; + } + identity starLan { + base iana-interface-type; + status deprecated; + description + "Deprecated via RFC 3635. + Use ethernetCsmacd(6) instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity proteon10Mbit { + base iana-interface-type; + } + identity proteon80Mbit { + base iana-interface-type; + } + identity hyperchannel { + base iana-interface-type; + } + identity fddi { + base iana-interface-type; + reference + "RFC 1512 - FDDI Management Information Base"; + } + identity lapb { + base iana-interface-type; + reference + "RFC 1381 - SNMP MIB Extension for X.25 LAPB"; + } + identity sdlc { + base iana-interface-type; + } + identity ds1 { + base iana-interface-type; + description + "DS1-MIB."; + reference + "RFC 4805 - Definitions of Managed Objects for the + DS1, J1, E1, DS2, and E2 Interface Types"; + } + identity e1 { + base iana-interface-type; + status obsolete; + description + "Obsolete; see DS1-MIB."; + reference + "RFC 4805 - Definitions of Managed Objects for the + DS1, J1, E1, DS2, and E2 Interface Types"; + } + + + identity basicISDN { + base iana-interface-type; + description + "No longer used. See also RFC 2127."; + } + identity primaryISDN { + base iana-interface-type; + description + "No longer used. See also RFC 2127."; + } + identity propPointToPointSerial { + base iana-interface-type; + description + "Proprietary serial."; + } + identity ppp { + base iana-interface-type; + } + identity softwareLoopback { + base iana-interface-type; + } + identity eon { + base iana-interface-type; + description + "CLNP over IP."; + } + identity ethernet3Mbit { + base iana-interface-type; + } + identity nsip { + base iana-interface-type; + description + "XNS over IP."; + } + identity slip { + base iana-interface-type; + description + "Generic SLIP."; + } + identity ultra { + base iana-interface-type; + description + "Ultra Technologies."; + } + identity ds3 { + base iana-interface-type; + description + "DS3-MIB."; + reference + "RFC 3896 - Definitions of Managed Objects for the + DS3/E3 Interface Type"; + } + identity sip { + base iana-interface-type; + description + "SMDS, coffee."; + reference + "RFC 1694 - Definitions of Managed Objects for SMDS + Interfaces using SMIv2"; + } + identity frameRelay { + base iana-interface-type; + description + "DTE only."; + reference + "RFC 2115 - Management Information Base for Frame Relay + DTEs Using SMIv2"; + } + identity rs232 { + base iana-interface-type; + reference + "RFC 1659 - Definitions of Managed Objects for RS-232-like + Hardware Devices using SMIv2"; + } + identity para { + base iana-interface-type; + description + "Parallel-port."; + reference + "RFC 1660 - Definitions of Managed Objects for + Parallel-printer-like Hardware Devices using + SMIv2"; + } + identity arcnet { + base iana-interface-type; + description + "ARCnet."; + } + identity arcnetPlus { + base iana-interface-type; + description + "ARCnet Plus."; + } + + + + identity atm { + base iana-interface-type; + description + "ATM cells."; + } + identity miox25 { + base iana-interface-type; + reference + "RFC 1461 - SNMP MIB extension for Multiprotocol + Interconnect over X.25"; + } + identity sonet { + base iana-interface-type; + description + "SONET or SDH."; + } + identity x25ple { + base iana-interface-type; + reference + "RFC 2127 - ISDN Management Information Base using SMIv2"; + } + identity iso88022llc { + base iana-interface-type; + } + identity localTalk { + base iana-interface-type; + } + identity smdsDxi { + base iana-interface-type; + } + identity frameRelayService { + base iana-interface-type; + description + "FRNETSERV-MIB."; + reference + "RFC 2954 - Definitions of Managed Objects for Frame + Relay Service"; + } + identity v35 { + base iana-interface-type; + } + identity hssi { + base iana-interface-type; + } + identity hippi { + base iana-interface-type; + } + + identity modem { + base iana-interface-type; + description + "Generic modem."; + } + identity aal5 { + base iana-interface-type; + description + "AAL5 over ATM."; + } + identity sonetPath { + base iana-interface-type; + } + identity sonetVT { + base iana-interface-type; + } + identity smdsIcip { + base iana-interface-type; + description + "SMDS InterCarrier Interface."; + } + identity propVirtual { + base iana-interface-type; + description + "Proprietary virtual/internal."; + reference + "RFC 2863 - The Interfaces Group MIB"; + } + identity propMultiplexor { + base iana-interface-type; + description + "Proprietary multiplexing."; + reference + "RFC 2863 - The Interfaces Group MIB"; + } + identity ieee80212 { + base iana-interface-type; + description + "100BaseVG."; + } + identity fibreChannel { + base iana-interface-type; + description + "Fibre Channel."; + } + + + + identity hippiInterface { + base iana-interface-type; + description + "HIPPI interfaces."; + } + identity frameRelayInterconnect { + base iana-interface-type; + status obsolete; + description + "Obsolete; use either + frameRelay(32) or frameRelayService(44)."; + } + identity aflane8023 { + base iana-interface-type; + description + "ATM Emulated LAN for 802.3."; + } + identity aflane8025 { + base iana-interface-type; + description + "ATM Emulated LAN for 802.5."; + } + identity cctEmul { + base iana-interface-type; + description + "ATM Emulated circuit."; + } + identity fastEther { + base iana-interface-type; + status deprecated; + description + "Obsoleted via RFC 3635. + ethernetCsmacd(6) should be used instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity isdn { + base iana-interface-type; + description + "ISDN and X.25."; + reference + "RFC 1356 - Multiprotocol Interconnect on X.25 and ISDN + in the Packet Mode"; + } + + + + identity v11 { + base iana-interface-type; + description + "CCITT V.11/X.21."; + } + identity v36 { + base iana-interface-type; + description + "CCITT V.36."; + } + identity g703at64k { + base iana-interface-type; + description + "CCITT G703 at 64Kbps."; + } + identity g703at2mb { + base iana-interface-type; + status obsolete; + description + "Obsolete; see DS1-MIB."; + } + identity qllc { + base iana-interface-type; + description + "SNA QLLC."; + } + identity fastEtherFX { + base iana-interface-type; + status deprecated; + description + "Obsoleted via RFC 3635. + ethernetCsmacd(6) should be used instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity channel { + base iana-interface-type; + description + "Channel."; + } + identity ieee80211 { + base iana-interface-type; + description + "Radio spread spectrum."; + } + identity ibm370parChan { + base iana-interface-type; + description + "IBM System 360/370 OEMI Channel."; + } + identity escon { + base iana-interface-type; + description + "IBM Enterprise Systems Connection."; + } + identity dlsw { + base iana-interface-type; + description + "Data Link Switching."; + } + identity isdns { + base iana-interface-type; + description + "ISDN S/T interface."; + } + identity isdnu { + base iana-interface-type; + description + "ISDN U interface."; + } + identity lapd { + base iana-interface-type; + description + "Link Access Protocol D."; + } + identity ipSwitch { + base iana-interface-type; + description + "IP Switching Objects."; + } + identity rsrb { + base iana-interface-type; + description + "Remote Source Route Bridging."; + } + identity atmLogical { + base iana-interface-type; + description + "ATM Logical Port."; + reference + "RFC 3606 - Definitions of Supplemental Managed Objects + for ATM Interface"; + } + identity ds0 { + base iana-interface-type; + description + "Digital Signal Level 0."; + reference + "RFC 2494 - Definitions of Managed Objects for the DS0 + and DS0 Bundle Interface Type"; + } + identity ds0Bundle { + base iana-interface-type; + description + "Group of ds0s on the same ds1."; + reference + "RFC 2494 - Definitions of Managed Objects for the DS0 + and DS0 Bundle Interface Type"; + } + identity bsc { + base iana-interface-type; + description + "Bisynchronous Protocol."; + } + identity async { + base iana-interface-type; + description + "Asynchronous Protocol."; + } + identity cnr { + base iana-interface-type; + description + "Combat Net Radio."; + } + identity iso88025Dtr { + base iana-interface-type; + description + "ISO 802.5r DTR."; + } + identity eplrs { + base iana-interface-type; + description + "Ext Pos Loc Report Sys."; + } + identity arap { + base iana-interface-type; + description + "Appletalk Remote Access Protocol."; + } + identity propCnls { + base iana-interface-type; + description + "Proprietary Connectionless Protocol."; + } + identity hostPad { + base iana-interface-type; + description + "CCITT-ITU X.29 PAD Protocol."; + } + identity termPad { + base iana-interface-type; + description + "CCITT-ITU X.3 PAD Facility."; + } + identity frameRelayMPI { + base iana-interface-type; + description + "Multiproto Interconnect over FR."; + } + identity x213 { + base iana-interface-type; + description + "CCITT-ITU X213."; + } + identity adsl { + base iana-interface-type; + description + "Asymmetric Digital Subscriber Loop."; + } + identity radsl { + base iana-interface-type; + description + "Rate-Adapt. Digital Subscriber Loop."; + } + identity sdsl { + base iana-interface-type; + description + "Symmetric Digital Subscriber Loop."; + } + identity vdsl { + base iana-interface-type; + description + "Very H-Speed Digital Subscrib. Loop."; + } + identity iso88025CRFPInt { + base iana-interface-type; + description + "ISO 802.5 CRFP."; + } + identity myrinet { + base iana-interface-type; + description + "Myricom Myrinet."; + } + identity voiceEM { + base iana-interface-type; + description + "Voice recEive and transMit."; + } + identity voiceFXO { + base iana-interface-type; + description + "Voice Foreign Exchange Office."; + } + identity voiceFXS { + base iana-interface-type; + description + "Voice Foreign Exchange Station."; + } + identity voiceEncap { + base iana-interface-type; + description + "Voice encapsulation."; + } + identity voiceOverIp { + base iana-interface-type; + description + "Voice over IP encapsulation."; + } + identity atmDxi { + base iana-interface-type; + description + "ATM DXI."; + } + identity atmFuni { + base iana-interface-type; + description + "ATM FUNI."; + } + identity atmIma { + base iana-interface-type; + description + "ATM IMA."; + } + identity pppMultilinkBundle { + base iana-interface-type; + description + "PPP Multilink Bundle."; + } + identity ipOverCdlc { + base iana-interface-type; + description + "IBM ipOverCdlc."; + } + identity ipOverClaw { + base iana-interface-type; + description + "IBM Common Link Access to Workstn."; + } + identity stackToStack { + base iana-interface-type; + description + "IBM stackToStack."; + } + identity virtualIpAddress { + base iana-interface-type; + description + "IBM VIPA."; + } + identity mpc { + base iana-interface-type; + description + "IBM multi-protocol channel support."; + } + identity ipOverAtm { + base iana-interface-type; + description + "IBM ipOverAtm."; + reference + "RFC 2320 - Definitions of Managed Objects for Classical IP + and ARP Over ATM Using SMIv2 (IPOA-MIB)"; + } + identity iso88025Fiber { + base iana-interface-type; + description + "ISO 802.5j Fiber Token Ring."; + } + identity tdlc { + base iana-interface-type; + description + "IBM twinaxial data link control."; + } + identity gigabitEthernet { + base iana-interface-type; + status deprecated; + + + description + "Obsoleted via RFC 3635. + ethernetCsmacd(6) should be used instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity hdlc { + base iana-interface-type; + description + "HDLC."; + } + identity lapf { + base iana-interface-type; + description + "LAP F."; + } + identity v37 { + base iana-interface-type; + description + "V.37."; + } + identity x25mlp { + base iana-interface-type; + description + "Multi-Link Protocol."; + } + identity x25huntGroup { + base iana-interface-type; + description + "X25 Hunt Group."; + } + identity transpHdlc { + base iana-interface-type; + description + "Transp HDLC."; + } + identity interleave { + base iana-interface-type; + description + "Interleave channel."; + } + identity fast { + base iana-interface-type; + description + "Fast channel."; + } + + identity ip { + base iana-interface-type; + description + "IP (for APPN HPR in IP networks)."; + } + identity docsCableMaclayer { + base iana-interface-type; + description + "CATV Mac Layer."; + } + identity docsCableDownstream { + base iana-interface-type; + description + "CATV Downstream interface."; + } + identity docsCableUpstream { + base iana-interface-type; + description + "CATV Upstream interface."; + } + identity a12MppSwitch { + base iana-interface-type; + description + "Avalon Parallel Processor."; + } + identity tunnel { + base iana-interface-type; + description + "Encapsulation interface."; + } + identity coffee { + base iana-interface-type; + description + "Coffee pot."; + reference + "RFC 2325 - Coffee MIB"; + } + identity ces { + base iana-interface-type; + description + "Circuit Emulation Service."; + } + identity atmSubInterface { + base iana-interface-type; + description + "ATM Sub Interface."; + } + + identity l2vlan { + base iana-interface-type; + description + "Layer 2 Virtual LAN using 802.1Q."; + } + identity l3ipvlan { + base iana-interface-type; + description + "Layer 3 Virtual LAN using IP."; + } + identity l3ipxvlan { + base iana-interface-type; + description + "Layer 3 Virtual LAN using IPX."; + } + identity digitalPowerline { + base iana-interface-type; + description + "IP over Power Lines."; + } + identity mediaMailOverIp { + base iana-interface-type; + description + "Multimedia Mail over IP."; + } + identity dtm { + base iana-interface-type; + description + "Dynamic synchronous Transfer Mode."; + } + identity dcn { + base iana-interface-type; + description + "Data Communications Network."; + } + identity ipForward { + base iana-interface-type; + description + "IP Forwarding Interface."; + } + identity msdsl { + base iana-interface-type; + description + "Multi-rate Symmetric DSL."; + } + identity ieee1394 { + base iana-interface-type; + + description + "IEEE1394 High Performance Serial Bus."; + } + identity if-gsn { + base iana-interface-type; + description + "HIPPI-6400."; + } + identity dvbRccMacLayer { + base iana-interface-type; + description + "DVB-RCC MAC Layer."; + } + identity dvbRccDownstream { + base iana-interface-type; + description + "DVB-RCC Downstream Channel."; + } + identity dvbRccUpstream { + base iana-interface-type; + description + "DVB-RCC Upstream Channel."; + } + identity atmVirtual { + base iana-interface-type; + description + "ATM Virtual Interface."; + } + identity mplsTunnel { + base iana-interface-type; + description + "MPLS Tunnel Virtual Interface."; + } + identity srp { + base iana-interface-type; + description + "Spatial Reuse Protocol."; + } + identity voiceOverAtm { + base iana-interface-type; + description + "Voice over ATM."; + } + identity voiceOverFrameRelay { + base iana-interface-type; + description + "Voice Over Frame Relay."; + } + identity idsl { + base iana-interface-type; + description + "Digital Subscriber Loop over ISDN."; + } + identity compositeLink { + base iana-interface-type; + description + "Avici Composite Link Interface."; + } + identity ss7SigLink { + base iana-interface-type; + description + "SS7 Signaling Link."; + } + identity propWirelessP2P { + base iana-interface-type; + description + "Prop. P2P wireless interface."; + } + identity frForward { + base iana-interface-type; + description + "Frame Forward Interface."; + } + identity rfc1483 { + base iana-interface-type; + description + "Multiprotocol over ATM AAL5."; + reference + "RFC 1483 - Multiprotocol Encapsulation over ATM + Adaptation Layer 5"; + } + identity usb { + base iana-interface-type; + description + "USB Interface."; + } + identity ieee8023adLag { + base iana-interface-type; + description + "IEEE 802.3ad Link Aggregate."; + } + identity bgppolicyaccounting { + base iana-interface-type; + description + "BGP Policy Accounting."; + } + identity frf16MfrBundle { + base iana-interface-type; + description + "FRF.16 Multilink Frame Relay."; + } + identity h323Gatekeeper { + base iana-interface-type; + description + "H323 Gatekeeper."; + } + identity h323Proxy { + base iana-interface-type; + description + "H323 Voice and Video Proxy."; + } + identity mpls { + base iana-interface-type; + description + "MPLS."; + } + identity mfSigLink { + base iana-interface-type; + description + "Multi-frequency signaling link."; + } + identity hdsl2 { + base iana-interface-type; + description + "High Bit-Rate DSL - 2nd generation."; + } + identity shdsl { + base iana-interface-type; + description + "Multirate HDSL2."; + } + identity ds1FDL { + base iana-interface-type; + description + "Facility Data Link (4Kbps) on a DS1."; + } + identity pos { + base iana-interface-type; + description + "Packet over SONET/SDH Interface."; + } + + + + identity dvbAsiIn { + base iana-interface-type; + description + "DVB-ASI Input."; + } + identity dvbAsiOut { + base iana-interface-type; + description + "DVB-ASI Output."; + } + identity plc { + base iana-interface-type; + description + "Power Line Communications."; + } + identity nfas { + base iana-interface-type; + description + "Non-Facility Associated Signaling."; + } + identity tr008 { + base iana-interface-type; + description + "TR008."; + } + identity gr303RDT { + base iana-interface-type; + description + "Remote Digital Terminal."; + } + identity gr303IDT { + base iana-interface-type; + description + "Integrated Digital Terminal."; + } + identity isup { + base iana-interface-type; + description + "ISUP."; + } + identity propDocsWirelessMaclayer { + base iana-interface-type; + description + "Cisco proprietary Maclayer."; + } + + + + identity propDocsWirelessDownstream { + base iana-interface-type; + description + "Cisco proprietary Downstream."; + } + identity propDocsWirelessUpstream { + base iana-interface-type; + description + "Cisco proprietary Upstream."; + } + identity hiperlan2 { + base iana-interface-type; + description + "HIPERLAN Type 2 Radio Interface."; + } + identity propBWAp2Mp { + base iana-interface-type; + description + "PropBroadbandWirelessAccesspt2Multipt (use of this value + for IEEE 802.16 WMAN interfaces as per IEEE Std 802.16f + is deprecated, and ieee80216WMAN(237) should be used + instead)."; + } + identity sonetOverheadChannel { + base iana-interface-type; + description + "SONET Overhead Channel."; + } + identity digitalWrapperOverheadChannel { + base iana-interface-type; + description + "Digital Wrapper."; + } + identity aal2 { + base iana-interface-type; + description + "ATM adaptation layer 2."; + } + identity radioMAC { + base iana-interface-type; + description + "MAC layer over radio links."; + } + identity atmRadio { + base iana-interface-type; + description + "ATM over radio links."; + } + identity imt { + base iana-interface-type; + description + "Inter-Machine Trunks."; + } + identity mvl { + base iana-interface-type; + description + "Multiple Virtual Lines DSL."; + } + identity reachDSL { + base iana-interface-type; + description + "Long Reach DSL."; + } + identity frDlciEndPt { + base iana-interface-type; + description + "Frame Relay DLCI End Point."; + } + identity atmVciEndPt { + base iana-interface-type; + description + "ATM VCI End Point."; + } + identity opticalChannel { + base iana-interface-type; + description + "Optical Channel."; + } + identity opticalTransport { + base iana-interface-type; + description + "Optical Transport."; + } + identity propAtm { + base iana-interface-type; + description + "Proprietary ATM."; + } + identity voiceOverCable { + base iana-interface-type; + description + "Voice Over Cable Interface."; + } + + + + identity infiniband { + base iana-interface-type; + description + "Infiniband."; + } + identity teLink { + base iana-interface-type; + description + "TE Link."; + } + identity q2931 { + base iana-interface-type; + description + "Q.2931."; + } + identity virtualTg { + base iana-interface-type; + description + "Virtual Trunk Group."; + } + identity sipTg { + base iana-interface-type; + description + "SIP Trunk Group."; + } + identity sipSig { + base iana-interface-type; + description + "SIP Signaling."; + } + identity docsCableUpstreamChannel { + base iana-interface-type; + description + "CATV Upstream Channel."; + } + identity econet { + base iana-interface-type; + description + "Acorn Econet."; + } + identity pon155 { + base iana-interface-type; + description + "FSAN 155Mb Symetrical PON interface."; + } + + + + identity pon622 { + base iana-interface-type; + description + "FSAN 622Mb Symetrical PON interface."; + } + identity bridge { + base iana-interface-type; + description + "Transparent bridge interface."; + } + identity linegroup { + base iana-interface-type; + description + "Interface common to multiple lines."; + } + identity voiceEMFGD { + base iana-interface-type; + description + "Voice E&M Feature Group D."; + } + identity voiceFGDEANA { + base iana-interface-type; + description + "Voice FGD Exchange Access North American."; + } + identity voiceDID { + base iana-interface-type; + description + "Voice Direct Inward Dialing."; + } + identity mpegTransport { + base iana-interface-type; + description + "MPEG transport interface."; + } + identity sixToFour { + base iana-interface-type; + status deprecated; + description + "6to4 interface (DEPRECATED)."; + reference + "RFC 4087 - IP Tunnel MIB"; + } + identity gtp { + base iana-interface-type; + description + "GTP (GPRS Tunneling Protocol)."; + } + identity pdnEtherLoop1 { + base iana-interface-type; + description + "Paradyne EtherLoop 1."; + } + identity pdnEtherLoop2 { + base iana-interface-type; + description + "Paradyne EtherLoop 2."; + } + identity opticalChannelGroup { + base iana-interface-type; + description + "Optical Channel Group."; + } + identity homepna { + base iana-interface-type; + description + "HomePNA ITU-T G.989."; + } + identity gfp { + base iana-interface-type; + description + "Generic Framing Procedure (GFP)."; + } + identity ciscoISLvlan { + base iana-interface-type; + description + "Layer 2 Virtual LAN using Cisco ISL."; + } + identity actelisMetaLOOP { + base iana-interface-type; + description + "Acteleis proprietary MetaLOOP High Speed Link."; + } + identity fcipLink { + base iana-interface-type; + description + "FCIP Link."; + } + identity rpr { + base iana-interface-type; + description + "Resilient Packet Ring Interface Type."; + } + + + + identity qam { + base iana-interface-type; + description + "RF Qam Interface."; + } + identity lmp { + base iana-interface-type; + description + "Link Management Protocol."; + reference + "RFC 4327 - Link Management Protocol (LMP) Management + Information Base (MIB)"; + } + identity cblVectaStar { + base iana-interface-type; + description + "Cambridge Broadband Networks Limited VectaStar."; + } + identity docsCableMCmtsDownstream { + base iana-interface-type; + description + "CATV Modular CMTS Downstream Interface."; + } + identity adsl2 { + base iana-interface-type; + status deprecated; + description + "Asymmetric Digital Subscriber Loop Version 2 + (DEPRECATED/OBSOLETED - please use adsl2plus(238) + instead)."; + reference + "RFC 4706 - Definitions of Managed Objects for Asymmetric + Digital Subscriber Line 2 (ADSL2)"; + } + identity macSecControlledIF { + base iana-interface-type; + description + "MACSecControlled."; + } + identity macSecUncontrolledIF { + base iana-interface-type; + description + "MACSecUncontrolled."; + } + identity aviciOpticalEther { + base iana-interface-type; + description + "Avici Optical Ethernet Aggregate."; + } + identity atmbond { + base iana-interface-type; + description + "atmbond."; + } + identity voiceFGDOS { + base iana-interface-type; + description + "Voice FGD Operator Services."; + } + identity mocaVersion1 { + base iana-interface-type; + description + "MultiMedia over Coax Alliance (MoCA) Interface + as documented in information provided privately to IANA."; + } + identity ieee80216WMAN { + base iana-interface-type; + description + "IEEE 802.16 WMAN interface."; + } + identity adsl2plus { + base iana-interface-type; + description + "Asymmetric Digital Subscriber Loop Version 2 - + Version 2 Plus and all variants."; + } + identity dvbRcsMacLayer { + base iana-interface-type; + description + "DVB-RCS MAC Layer."; + reference + "RFC 5728 - The SatLabs Group DVB-RCS MIB"; + } + identity dvbTdm { + base iana-interface-type; + description + "DVB Satellite TDM."; + reference + "RFC 5728 - The SatLabs Group DVB-RCS MIB"; + } + identity dvbRcsTdma { + base iana-interface-type; + description + "DVB-RCS TDMA."; + reference + "RFC 5728 - The SatLabs Group DVB-RCS MIB"; + } + identity x86Laps { + base iana-interface-type; + description + "LAPS based on ITU-T X.86/Y.1323."; + } + identity wwanPP { + base iana-interface-type; + description + "3GPP WWAN."; + } + identity wwanPP2 { + base iana-interface-type; + description + "3GPP2 WWAN."; + } + identity voiceEBS { + base iana-interface-type; + description + "Voice P-phone EBS physical interface."; + } + identity ifPwType { + base iana-interface-type; + description + "Pseudowire interface type."; + reference + "RFC 5601 - Pseudowire (PW) Management Information Base (MIB)"; + } + identity ilan { + base iana-interface-type; + description + "Internal LAN on a bridge per IEEE 802.1ap."; + } + identity pip { + base iana-interface-type; + description + "Provider Instance Port on a bridge per IEEE 802.1ah PBB."; + } + identity aluELP { + base iana-interface-type; + description + "Alcatel-Lucent Ethernet Link Protection."; + } + identity gpon { + base iana-interface-type; + description + "Gigabit-capable passive optical networks (G-PON) as per + ITU-T G.948."; + } + identity vdsl2 { + base iana-interface-type; + description + "Very high speed digital subscriber line Version 2 + (as per ITU-T Recommendation G.993.2)."; + reference + "RFC 5650 - Definitions of Managed Objects for Very High + Speed Digital Subscriber Line 2 (VDSL2)"; + } + identity capwapDot11Profile { + base iana-interface-type; + description + "WLAN Profile Interface."; + reference + "RFC 5834 - Control and Provisioning of Wireless Access + Points (CAPWAP) Protocol Binding MIB for + IEEE 802.11"; + } + identity capwapDot11Bss { + base iana-interface-type; + description + "WLAN BSS Interface."; + reference + "RFC 5834 - Control and Provisioning of Wireless Access + Points (CAPWAP) Protocol Binding MIB for + IEEE 802.11"; + } + identity capwapWtpVirtualRadio { + base iana-interface-type; + description + "WTP Virtual Radio Interface."; + reference + "RFC 5833 - Control and Provisioning of Wireless Access + Points (CAPWAP) Protocol Base MIB"; + } + identity bits { + base iana-interface-type; + description + "bitsport."; + } + identity docsCableUpstreamRfPort { + base iana-interface-type; + description + "DOCSIS CATV Upstream RF Port."; + } + + + identity cableDownstreamRfPort { + base iana-interface-type; + description + "CATV downstream RF Port."; + } + identity vmwareVirtualNic { + base iana-interface-type; + description + "VMware Virtual Network Interface."; + } + identity ieee802154 { + base iana-interface-type; + description + "IEEE 802.15.4 WPAN interface."; + reference + "IEEE 802.15.4-2006"; + } + identity otnOdu { + base iana-interface-type; + description + "OTN Optical Data Unit."; + } + identity otnOtu { + base iana-interface-type; + description + "OTN Optical channel Transport Unit."; + } + identity ifVfiType { + base iana-interface-type; + description + "VPLS Forwarding Instance Interface Type."; + } + identity g9981 { + base iana-interface-type; + description + "G.998.1 bonded interface."; + } + identity g9982 { + base iana-interface-type; + description + "G.998.2 bonded interface."; + } + identity g9983 { + base iana-interface-type; + description + "G.998.3 bonded interface."; + } + + identity aluEpon { + base iana-interface-type; + description + "Ethernet Passive Optical Networks (E-PON)."; + } + identity aluEponOnu { + base iana-interface-type; + description + "EPON Optical Network Unit."; + } + identity aluEponPhysicalUni { + base iana-interface-type; + description + "EPON physical User to Network interface."; + } + identity aluEponLogicalLink { + base iana-interface-type; + description + "The emulation of a point-to-point link over the EPON + layer."; + } + identity aluGponOnu { + base iana-interface-type; + description + "GPON Optical Network Unit."; + reference + "ITU-T G.984.2"; + } + identity aluGponPhysicalUni { + base iana-interface-type; + description + "GPON physical User to Network interface."; + reference + "ITU-T G.984.2"; + } + identity vmwareNicTeam { + base iana-interface-type; + description + "VMware NIC Team."; + } +} diff --git a/tools/lint/examples/ietf-interfaces.yang b/tools/lint/examples/ietf-interfaces.yang new file mode 100644 index 0000000..ad64425 --- /dev/null +++ b/tools/lint/examples/ietf-interfaces.yang @@ -0,0 +1,725 @@ +module ietf-interfaces { + + namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces"; + prefix if; + + import ietf-yang-types { + prefix yang; + } + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: <http://tools.ietf.org/wg/netmod/> + WG List: <mailto:netmod@ietf.org> + + WG Chair: Thomas Nadeau + <mailto:tnadeau@lucidvision.com> + + WG Chair: Juergen Schoenwaelder + <mailto:j.schoenwaelder@jacobs-university.de> + + Editor: Martin Bjorklund + <mailto:mbj@tail-f.com>"; + + description + "This module contains a collection of YANG definitions for + managing network interfaces. + + Copyright (c) 2014 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 7223; see + the RFC itself for full legal notices."; + + revision 2014-05-08 { + description + "Initial revision."; + reference + "RFC 7223: A YANG Data Model for Interface Management"; + } + + /* + * Typedefs + */ + + typedef interface-ref { + type leafref { + path "/if:interfaces/if:interface/if:name"; + } + description + "This type is used by data models that need to reference + configured interfaces."; + } + + typedef interface-state-ref { + type leafref { + path "/if:interfaces-state/if:interface/if:name"; + } + description + "This type is used by data models that need to reference + the operationally present interfaces."; + } + + /* + * Identities + */ + + identity interface-type { + description + "Base identity from which specific interface types are + derived."; + } + + /* + * Features + */ + + feature arbitrary-names { + description + "This feature indicates that the device allows user-controlled + interfaces to be named arbitrarily."; + } + feature pre-provisioning { + description + "This feature indicates that the device supports + pre-provisioning of interface configuration, i.e., it is + possible to configure an interface whose physical interface + hardware is not present on the device."; + } + + feature if-mib { + description + "This feature indicates that the device implements + the IF-MIB."; + reference + "RFC 2863: The Interfaces Group MIB"; + } + + /* + * Configuration data nodes + */ + + container interfaces { + description + "Interface configuration parameters."; + + list interface { + key "name"; + + description + "The list of configured interfaces on the device. + + The operational state of an interface is available in the + /interfaces-state/interface list. If the configuration of a + system-controlled interface cannot be used by the system + (e.g., the interface hardware present does not match the + interface type), then the configuration is not applied to + the system-controlled interface shown in the + /interfaces-state/interface list. If the configuration + of a user-controlled interface cannot be used by the system, + the configured interface is not instantiated in the + /interfaces-state/interface list."; + + leaf name { + type string; + description + "The name of the interface. + + A device MAY restrict the allowed values for this leaf, + possibly depending on the type of the interface. + For system-controlled interfaces, this leaf is the + device-specific name of the interface. The 'config false' + list /interfaces-state/interface contains the currently + existing interfaces on the device. + + If a client tries to create configuration for a + system-controlled interface that is not present in the + /interfaces-state/interface list, the server MAY reject + the request if the implementation does not support + pre-provisioning of interfaces or if the name refers to + an interface that can never exist in the system. A + NETCONF server MUST reply with an rpc-error with the + error-tag 'invalid-value' in this case. + + If the device supports pre-provisioning of interface + configuration, the 'pre-provisioning' feature is + advertised. + + If the device allows arbitrarily named user-controlled + interfaces, the 'arbitrary-names' feature is advertised. + + When a configured user-controlled interface is created by + the system, it is instantiated with the same name in the + /interface-state/interface list."; + } + + leaf description { + type string; + description + "A textual description of the interface. + + A server implementation MAY map this leaf to the ifAlias + MIB object. Such an implementation needs to use some + mechanism to handle the differences in size and characters + allowed between this leaf and ifAlias. The definition of + such a mechanism is outside the scope of this document. + + Since ifAlias is defined to be stored in non-volatile + storage, the MIB implementation MUST map ifAlias to the + value of 'description' in the persistently stored + datastore. + + Specifically, if the device supports ':startup', when + ifAlias is read the device MUST return the value of + 'description' in the 'startup' datastore, and when it is + written, it MUST be written to the 'running' and 'startup' + datastores. Note that it is up to the implementation to + + decide whether to modify this single leaf in 'startup' or + perform an implicit copy-config from 'running' to + 'startup'. + + If the device does not support ':startup', ifAlias MUST + be mapped to the 'description' leaf in the 'running' + datastore."; + reference + "RFC 2863: The Interfaces Group MIB - ifAlias"; + } + + leaf type { + type identityref { + base interface-type; + } + mandatory true; + description + "The type of the interface. + + When an interface entry is created, a server MAY + initialize the type leaf with a valid value, e.g., if it + is possible to derive the type from the name of the + interface. + + If a client tries to set the type of an interface to a + value that can never be used by the system, e.g., if the + type is not supported or if the type does not match the + name of the interface, the server MUST reject the request. + A NETCONF server MUST reply with an rpc-error with the + error-tag 'invalid-value' in this case."; + reference + "RFC 2863: The Interfaces Group MIB - ifType"; + } + + leaf enabled { + type boolean; + default "true"; + description + "This leaf contains the configured, desired state of the + interface. + + Systems that implement the IF-MIB use the value of this + leaf in the 'running' datastore to set + IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry + has been initialized, as described in RFC 2863. + + + + Changes in this leaf in the 'running' datastore are + reflected in ifAdminStatus, but if ifAdminStatus is + changed over SNMP, this leaf is not affected."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + } + + leaf link-up-down-trap-enable { + if-feature if-mib; + type enumeration { + enum enabled { + value 1; + } + enum disabled { + value 2; + } + } + description + "Controls whether linkUp/linkDown SNMP notifications + should be generated for this interface. + + If this node is not configured, the value 'enabled' is + operationally used by the server for interfaces that do + not operate on top of any other interface (i.e., there are + no 'lower-layer-if' entries), and 'disabled' otherwise."; + reference + "RFC 2863: The Interfaces Group MIB - + ifLinkUpDownTrapEnable"; + } + } + } + + /* + * Operational state data nodes + */ + + container interfaces-state { + config false; + description + "Data nodes for the operational state of interfaces."; + + list interface { + key "name"; + + + + + + description + "The list of interfaces on the device. + + System-controlled interfaces created by the system are + always present in this list, whether they are configured or + not."; + + leaf name { + type string; + description + "The name of the interface. + + A server implementation MAY map this leaf to the ifName + MIB object. Such an implementation needs to use some + mechanism to handle the differences in size and characters + allowed between this leaf and ifName. The definition of + such a mechanism is outside the scope of this document."; + reference + "RFC 2863: The Interfaces Group MIB - ifName"; + } + + leaf type { + type identityref { + base interface-type; + } + mandatory true; + description + "The type of the interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifType"; + } + + leaf admin-status { + if-feature if-mib; + type enumeration { + enum up { + value 1; + description + "Ready to pass packets."; + } + enum down { + value 2; + description + "Not ready to pass packets and not in some test mode."; + } + + + + enum testing { + value 3; + description + "In some test mode."; + } + } + mandatory true; + description + "The desired state of the interface. + + This leaf has the same read semantics as ifAdminStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + } + + leaf oper-status { + type enumeration { + enum up { + value 1; + description + "Ready to pass packets."; + } + enum down { + value 2; + description + "The interface does not pass any packets."; + } + enum testing { + value 3; + description + "In some test mode. No operational packets can + be passed."; + } + enum unknown { + value 4; + description + "Status cannot be determined for some reason."; + } + enum dormant { + value 5; + description + "Waiting for some external event."; + } + enum not-present { + value 6; + description + "Some component (typically hardware) is missing."; + } + enum lower-layer-down { + value 7; + description + "Down due to state of lower-layer interface(s)."; + } + } + mandatory true; + description + "The current operational state of the interface. + + This leaf has the same semantics as ifOperStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifOperStatus"; + } + + leaf last-change { + type yang:date-and-time; + description + "The time the interface entered its current operational + state. If the current state was entered prior to the + last re-initialization of the local network management + subsystem, then this node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - ifLastChange"; + } + + leaf if-index { + if-feature if-mib; + type int32 { + range "1..2147483647"; + } + mandatory true; + description + "The ifIndex value for the ifEntry represented by this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifIndex"; + } + + leaf phys-address { + type yang:phys-address; + description + "The interface's address at its protocol sub-layer. For + example, for an 802.x interface, this object normally + contains a Media Access Control (MAC) address. The + interface's media-specific modules must define the bit + + + and byte ordering and the format of the value of this + object. For interfaces that do not have such an address + (e.g., a serial line), this node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - ifPhysAddress"; + } + + leaf-list higher-layer-if { + type interface-state-ref; + description + "A list of references to interfaces layered on top of this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifStackTable"; + } + + leaf-list lower-layer-if { + type interface-state-ref; + description + "A list of references to interfaces layered underneath this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifStackTable"; + } + + leaf speed { + type yang:gauge64; + units "bits/second"; + description + "An estimate of the interface's current bandwidth in bits + per second. For interfaces that do not vary in + bandwidth or for those where no accurate estimation can + be made, this node should contain the nominal bandwidth. + For interfaces that have no concept of bandwidth, this + node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - + ifSpeed, ifHighSpeed"; + } + + + + + + + + + + container statistics { + description + "A collection of interface-related statistics objects."; + + leaf discontinuity-time { + type yang:date-and-time; + mandatory true; + description + "The time on the most recent occasion at which any one or + more of this interface's counters suffered a + discontinuity. If no such discontinuities have occurred + since the last re-initialization of the local management + subsystem, then this node contains the time the local + management subsystem re-initialized itself."; + } + + leaf in-octets { + type yang:counter64; + description + "The total number of octets received on the interface, + including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInOctets"; + } + + leaf in-unicast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were not addressed to a + multicast or broadcast address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts"; + } + + + + + leaf in-broadcast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a broadcast + address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInBroadcastPkts"; + } + + leaf in-multicast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a multicast + address at this sub-layer. For a MAC-layer protocol, + this includes both Group and Functional addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInMulticastPkts"; + } + + leaf in-discards { + type yang:counter32; + description + "The number of inbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being deliverable to a higher-layer + protocol. One possible reason for discarding such a + packet could be to free up buffer space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + + + reference + "RFC 2863: The Interfaces Group MIB - ifInDiscards"; + } + + leaf in-errors { + type yang:counter32; + description + "For packet-oriented interfaces, the number of inbound + packets that contained errors preventing them from being + deliverable to a higher-layer protocol. For character- + oriented or fixed-length interfaces, the number of + inbound transmission units that contained errors + preventing them from being deliverable to a higher-layer + protocol. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInErrors"; + } + + leaf in-unknown-protos { + type yang:counter32; + description + "For packet-oriented interfaces, the number of packets + received via the interface that were discarded because + of an unknown or unsupported protocol. For + character-oriented or fixed-length interfaces that + support protocol multiplexing, the number of + transmission units received via the interface that were + discarded because of an unknown or unsupported protocol. + For any interface that does not support protocol + multiplexing, this counter is not present. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; + } + + + + + + leaf out-octets { + type yang:counter64; + description + "The total number of octets transmitted out of the + interface, including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutOctets"; + } + + leaf out-unicast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were not addressed + to a multicast or broadcast address at this sub-layer, + including those that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts"; + } + + leaf out-broadcast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were addressed to a + broadcast address at this sub-layer, including those + that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutBroadcastPkts"; + } + + + leaf out-multicast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were addressed to a + multicast address at this sub-layer, including those + that were discarded or not sent. For a MAC-layer + protocol, this includes both Group and Functional + addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutMulticastPkts"; + } + + leaf out-discards { + type yang:counter32; + description + "The number of outbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being transmitted. One possible reason + for discarding such a packet could be to free up buffer + space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutDiscards"; + } + + leaf out-errors { + type yang:counter32; + description + "For packet-oriented interfaces, the number of outbound + packets that could not be transmitted because of errors. + For character-oriented or fixed-length interfaces, the + number of outbound transmission units that could not be + transmitted because of errors. + + + + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutErrors"; + } + } + } + } +} diff --git a/tools/lint/examples/ietf-ip.yang b/tools/lint/examples/ietf-ip.yang new file mode 100644 index 0000000..1499120 --- /dev/null +++ b/tools/lint/examples/ietf-ip.yang @@ -0,0 +1,758 @@ +module ietf-ip { + + namespace "urn:ietf:params:xml:ns:yang:ietf-ip"; + prefix ip; + + import ietf-interfaces { + prefix if; + } + import ietf-inet-types { + prefix inet; + } + import ietf-yang-types { + prefix yang; + } + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: <http://tools.ietf.org/wg/netmod/> + WG List: <mailto:netmod@ietf.org> + + WG Chair: Thomas Nadeau + <mailto:tnadeau@lucidvision.com> + + WG Chair: Juergen Schoenwaelder + <mailto:j.schoenwaelder@jacobs-university.de> + + Editor: Martin Bjorklund + <mailto:mbj@tail-f.com>"; + + + + + + + + + + + description + "This module contains a collection of YANG definitions for + configuring IP implementations. + + Copyright (c) 2014 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 7277; see + the RFC itself for full legal notices."; + + revision 2014-06-16 { + description + "Initial revision."; + reference + "RFC 7277: A YANG Data Model for IP Management"; + } + + /* + + * Features + */ + + feature ipv4-non-contiguous-netmasks { + description + "Indicates support for configuring non-contiguous + subnet masks."; + } + + feature ipv6-privacy-autoconf { + description + "Indicates support for Privacy Extensions for Stateless Address + Autoconfiguration in IPv6."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6"; + } + + + + + + /* + * Typedefs + */ + + typedef ip-address-origin { + type enumeration { + enum other { + description + "None of the following."; + } + enum static { + description + "Indicates that the address has been statically + configured - for example, using NETCONF or a Command Line + Interface."; + } + enum dhcp { + description + "Indicates an address that has been assigned to this + system by a DHCP server."; + } + enum link-layer { + description + "Indicates an address created by IPv6 stateless + autoconfiguration that embeds a link-layer address in its + interface identifier."; + } + enum random { + description + "Indicates an address chosen by the system at + + random, e.g., an IPv4 address within 169.254/16, an + RFC 4941 temporary address, or an RFC 7217 semantically + opaque address."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + RFC 7217: A Method for Generating Semantically Opaque + Interface Identifiers with IPv6 Stateless + Address Autoconfiguration (SLAAC)"; + } + } + description + "The origin of an address."; + } + + + + typedef neighbor-origin { + type enumeration { + enum other { + description + "None of the following."; + } + enum static { + description + "Indicates that the mapping has been statically + configured - for example, using NETCONF or a Command Line + Interface."; + } + enum dynamic { + description + "Indicates that the mapping has been dynamically resolved + using, e.g., IPv4 ARP or the IPv6 Neighbor Discovery + protocol."; + } + } + description + "The origin of a neighbor entry."; + } + + /* + * Configuration data nodes + */ + + augment "/if:interfaces/if:interface" { + description + "Parameters for configuring IP on interfaces. + + If an interface is not capable of running IP, the server + must not allow the client to configure these parameters."; + + container ipv4 { + presence + "Enables IPv4 unless the 'enabled' leaf + (which defaults to 'true') is set to 'false'"; + description + "Parameters for the IPv4 address family."; + + + + + + + + + leaf enabled { + type boolean; + default true; + description + "Controls whether IPv4 is enabled or disabled on this + interface. When IPv4 is enabled, this interface is + connected to an IPv4 stack, and the interface can send + and receive IPv4 packets."; + } + leaf forwarding { + type boolean; + default false; + description + "Controls IPv4 packet forwarding of datagrams received by, + but not addressed to, this interface. IPv4 routers + forward datagrams. IPv4 hosts do not (except those + source-routed via the host)."; + } + leaf mtu { + type uint16 { + range "68..max"; + } + units octets; + description + "The size, in octets, of the largest IPv4 packet that the + interface will send and receive. + + The server may restrict the allowed values for this leaf, + depending on the interface's type. + + If this leaf is not configured, the operationally used MTU + depends on the interface's type."; + reference + "RFC 791: Internet Protocol"; + } + list address { + key "ip"; + description + "The list of configured IPv4 addresses on the interface."; + + leaf ip { + type inet:ipv4-address-no-zone; + description + "The IPv4 address on the interface."; + } + + + + choice subnet { + mandatory true; + description + "The subnet can be specified as a prefix-length, or, + if the server supports non-contiguous netmasks, as + a netmask."; + leaf prefix-length { + type uint8 { + range "0..32"; + } + description + "The length of the subnet prefix."; + } + leaf netmask { + if-feature ipv4-non-contiguous-netmasks; + type yang:dotted-quad; + description + "The subnet specified as a netmask."; + } + } + } + list neighbor { + key "ip"; + description + "A list of mappings from IPv4 addresses to + link-layer addresses. + + Entries in this list are used as static entries in the + ARP Cache."; + reference + "RFC 826: An Ethernet Address Resolution Protocol"; + + leaf ip { + type inet:ipv4-address-no-zone; + description + "The IPv4 address of the neighbor node."; + } + leaf link-layer-address { + type yang:phys-address; + mandatory true; + description + "The link-layer address of the neighbor node."; + } + } + + } + + + container ipv6 { + presence + "Enables IPv6 unless the 'enabled' leaf + (which defaults to 'true') is set to 'false'"; + description + "Parameters for the IPv6 address family."; + + leaf enabled { + type boolean; + default true; + description + "Controls whether IPv6 is enabled or disabled on this + interface. When IPv6 is enabled, this interface is + connected to an IPv6 stack, and the interface can send + and receive IPv6 packets."; + } + leaf forwarding { + type boolean; + default false; + description + "Controls IPv6 packet forwarding of datagrams received by, + but not addressed to, this interface. IPv6 routers + forward datagrams. IPv6 hosts do not (except those + source-routed via the host)."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) + Section 6.2.1, IsRouter"; + } + leaf mtu { + type uint32 { + range "1280..max"; + } + units octets; + description + "The size, in octets, of the largest IPv6 packet that the + interface will send and receive. + + The server may restrict the allowed values for this leaf, + depending on the interface's type. + + If this leaf is not configured, the operationally used MTU + depends on the interface's type."; + reference + "RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + Section 5"; + } + + + list address { + key "ip"; + description + "The list of configured IPv6 addresses on the interface."; + + leaf ip { + type inet:ipv6-address-no-zone; + description + "The IPv6 address on the interface."; + } + leaf prefix-length { + type uint8 { + range "0..128"; + } + mandatory true; + description + "The length of the subnet prefix."; + } + } + list neighbor { + key "ip"; + description + "A list of mappings from IPv6 addresses to + link-layer addresses. + + Entries in this list are used as static entries in the + Neighbor Cache."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)"; + + leaf ip { + type inet:ipv6-address-no-zone; + description + "The IPv6 address of the neighbor node."; + } + leaf link-layer-address { + type yang:phys-address; + mandatory true; + description + "The link-layer address of the neighbor node."; + } + } + + + + + + + leaf dup-addr-detect-transmits { + type uint32; + default 1; + description + "The number of consecutive Neighbor Solicitation messages + sent while performing Duplicate Address Detection on a + tentative address. A value of zero indicates that + Duplicate Address Detection is not performed on + tentative addresses. A value of one indicates a single + transmission with no follow-up retransmissions."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration"; + } + container autoconf { + description + "Parameters to control the autoconfiguration of IPv6 + addresses, as described in RFC 4862."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration"; + + leaf create-global-addresses { + type boolean; + default true; + description + "If enabled, the host creates global addresses as + described in RFC 4862."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration + Section 5.5"; + } + leaf create-temporary-addresses { + if-feature ipv6-privacy-autoconf; + type boolean; + default false; + description + "If enabled, the host creates temporary addresses as + described in RFC 4941."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6"; + } + + + + + + + + leaf temporary-valid-lifetime { + if-feature ipv6-privacy-autoconf; + type uint32; + units "seconds"; + default 604800; + description + "The time period during which the temporary address + is valid."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + - TEMP_VALID_LIFETIME"; + } + leaf temporary-preferred-lifetime { + if-feature ipv6-privacy-autoconf; + type uint32; + units "seconds"; + default 86400; + description + "The time period during which the temporary address is + preferred."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + - TEMP_PREFERRED_LIFETIME"; + } + } + } + } + + /* + * Operational state data nodes + */ + + augment "/if:interfaces-state/if:interface" { + description + "Data nodes for the operational state of IP on interfaces."; + + container ipv4 { + presence "Present if IPv4 is enabled on this interface"; + config false; + description + "Interface-specific parameters for the IPv4 address family."; + + + + + + leaf forwarding { + type boolean; + description + "Indicates whether IPv4 packet forwarding is enabled or + disabled on this interface."; + } + leaf mtu { + type uint16 { + range "68..max"; + } + units octets; + description + "The size, in octets, of the largest IPv4 packet that the + interface will send and receive."; + reference + "RFC 791: Internet Protocol"; + } + list address { + key "ip"; + description + "The list of IPv4 addresses on the interface."; + + leaf ip { + type inet:ipv4-address-no-zone; + description + "The IPv4 address on the interface."; + } + choice subnet { + description + "The subnet can be specified as a prefix-length, or, + if the server supports non-contiguous netmasks, as + a netmask."; + leaf prefix-length { + type uint8 { + range "0..32"; + } + description + "The length of the subnet prefix."; + } + leaf netmask { + if-feature ipv4-non-contiguous-netmasks; + type yang:dotted-quad; + description + "The subnet specified as a netmask."; + } + } + + + leaf origin { + type ip-address-origin; + description + "The origin of this address."; + } + } + list neighbor { + key "ip"; + description + "A list of mappings from IPv4 addresses to + link-layer addresses. + + This list represents the ARP Cache."; + reference + "RFC 826: An Ethernet Address Resolution Protocol"; + + leaf ip { + type inet:ipv4-address-no-zone; + description + "The IPv4 address of the neighbor node."; + } + leaf link-layer-address { + type yang:phys-address; + description + "The link-layer address of the neighbor node."; + } + leaf origin { + type neighbor-origin; + description + "The origin of this neighbor entry."; + } + } + + } + + container ipv6 { + presence "Present if IPv6 is enabled on this interface"; + config false; + description + "Parameters for the IPv6 address family."; + + + + + + + + + leaf forwarding { + type boolean; + default false; + description + "Indicates whether IPv6 packet forwarding is enabled or + disabled on this interface."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) + Section 6.2.1, IsRouter"; + } + leaf mtu { + type uint32 { + range "1280..max"; + } + units octets; + description + "The size, in octets, of the largest IPv6 packet that the + interface will send and receive."; + reference + "RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + Section 5"; + } + list address { + key "ip"; + description + "The list of IPv6 addresses on the interface."; + + leaf ip { + type inet:ipv6-address-no-zone; + description + "The IPv6 address on the interface."; + } + leaf prefix-length { + type uint8 { + range "0..128"; + } + mandatory true; + description + "The length of the subnet prefix."; + } + leaf origin { + type ip-address-origin; + description + "The origin of this address."; + } + + + + leaf status { + type enumeration { + enum preferred { + description + "This is a valid address that can appear as the + destination or source address of a packet."; + } + enum deprecated { + description + "This is a valid but deprecated address that should + no longer be used as a source address in new + communications, but packets addressed to such an + address are processed as expected."; + } + enum invalid { + description + "This isn't a valid address, and it shouldn't appear + as the destination or source address of a packet."; + } + enum inaccessible { + description + "The address is not accessible because the interface + to which this address is assigned is not + operational."; + } + enum unknown { + description + "The status cannot be determined for some reason."; + } + enum tentative { + description + "The uniqueness of the address on the link is being + verified. Addresses in this state should not be + used for general communication and should only be + used to determine the uniqueness of the address."; + } + enum duplicate { + description + "The address has been determined to be non-unique on + the link and so must not be used."; + } + + + + + + + + enum optimistic { + description + "The address is available for use, subject to + restrictions, while its uniqueness on a link is + being verified."; + } + } + description + "The status of an address. Most of the states correspond + to states from the IPv6 Stateless Address + Autoconfiguration protocol."; + reference + "RFC 4293: Management Information Base for the + Internet Protocol (IP) + - IpAddressStatusTC + RFC 4862: IPv6 Stateless Address Autoconfiguration"; + } + } + list neighbor { + key "ip"; + description + "A list of mappings from IPv6 addresses to + link-layer addresses. + + This list represents the Neighbor Cache."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)"; + + leaf ip { + type inet:ipv6-address-no-zone; + description + "The IPv6 address of the neighbor node."; + } + leaf link-layer-address { + type yang:phys-address; + description + "The link-layer address of the neighbor node."; + } + leaf origin { + type neighbor-origin; + description + "The origin of this neighbor entry."; + } + leaf is-router { + type empty; + description + "Indicates that the neighbor node acts as a router."; + } + leaf state { + type enumeration { + enum incomplete { + description + "Address resolution is in progress, and the link-layer + address of the neighbor has not yet been + determined."; + } + enum reachable { + description + "Roughly speaking, the neighbor is known to have been + reachable recently (within tens of seconds ago)."; + } + enum stale { + description + "The neighbor is no longer known to be reachable, but + until traffic is sent to the neighbor no attempt + should be made to verify its reachability."; + } + enum delay { + description + "The neighbor is no longer known to be reachable, and + traffic has recently been sent to the neighbor. + Rather than probe the neighbor immediately, however, + delay sending probes for a short while in order to + give upper-layer protocols a chance to provide + reachability confirmation."; + } + enum probe { + description + "The neighbor is no longer known to be reachable, and + unicast Neighbor Solicitation probes are being sent + to verify reachability."; + } + } + description + "The Neighbor Unreachability Detection state of this + entry."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) + Section 7.3.2"; + } + } + } + } +} diff --git a/tools/lint/examples/ietf-netconf-acm-when.yang b/tools/lint/examples/ietf-netconf-acm-when.yang new file mode 100644 index 0000000..902fcbf --- /dev/null +++ b/tools/lint/examples/ietf-netconf-acm-when.yang @@ -0,0 +1,412 @@ +module ietf-netconf-acm-when { + namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-acm"; + prefix nacm; + + import ietf-yang-types { + prefix yang; + } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + contact + "WG Web: <http://tools.ietf.org/wg/netconf/> + WG List: <mailto:netconf@ietf.org> + + WG Chair: Mehmet Ersue + <mailto:mehmet.ersue@nsn.com> + + WG Chair: Bert Wijnen + <mailto:bertietf@bwijnen.net> + + Editor: Andy Bierman + <mailto:andy@yumaworks.com> + + Editor: Martin Bjorklund + <mailto:mbj@tail-f.com>"; + description + "NETCONF Access Control Model. + + Copyright (c) 2012 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD + License set forth in Section 4.c of the IETF Trust's + Legal Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6536; see + the RFC itself for full legal notices."; + + revision 2012-02-22 { + description + "Initial version"; + reference + "RFC 6536: Network Configuration Protocol (NETCONF) + Access Control Model"; + } + + extension default-deny-write { + description + "Used to indicate that the data model node + represents a sensitive security system parameter. + + If present, and the NACM module is enabled (i.e., + /nacm/enable-nacm object equals 'true'), the NETCONF server + will only allow the designated 'recovery session' to have + write access to the node. An explicit access control rule is + required for all other users. + + The 'default-deny-write' extension MAY appear within a data + definition statement. It is ignored otherwise."; + } + + extension default-deny-all { + description + "Used to indicate that the data model node + controls a very sensitive security system parameter. + + If present, and the NACM module is enabled (i.e., + /nacm/enable-nacm object equals 'true'), the NETCONF server + will only allow the designated 'recovery session' to have + read, write, or execute access to the node. An explicit + access control rule is required for all other users. + + The 'default-deny-all' extension MAY appear within a data + definition statement, 'rpc' statement, or 'notification' + statement. It is ignored otherwise."; + } + + typedef user-name-type { + type string { + length "1..max"; + } + description + "General Purpose Username string."; + } + + typedef matchall-string-type { + type string { + pattern "\\*"; + } + description + "The string containing a single asterisk '*' is used + to conceptually represent all possible values + for the particular leaf using this data type."; + } + + typedef access-operations-type { + type bits { + bit create { + description + "Any protocol operation that creates a + new data node."; + } + bit read { + description + "Any protocol operation or notification that + returns the value of a data node."; + } + bit update { + description + "Any protocol operation that alters an existing + data node."; + } + bit delete { + description + "Any protocol operation that removes a data node."; + } + bit exec { + description + "Execution access to the specified protocol operation."; + } + } + description + "NETCONF Access Operation."; + } + + typedef group-name-type { + type string { + length "1..max"; + pattern "[^\\*].*"; + } + description + "Name of administrative group to which + users can be assigned."; + } + + typedef action-type { + type enumeration { + enum "permit" { + description + "Requested action is permitted."; + } + enum "deny" { + description + "Requested action is denied."; + } + } + description + "Action taken by the server when a particular + rule matches."; + } + + typedef node-instance-identifier { + type yang:xpath1.0; + description + "Path expression used to represent a special + data node instance identifier string. + + A node-instance-identifier value is an + unrestricted YANG instance-identifier expression. + All the same rules as an instance-identifier apply + except predicates for keys are optional. If a key + predicate is missing, then the node-instance-identifier + represents all possible server instances for that key. + + This XPath expression is evaluated in the following context: + + o The set of namespace declarations are those in scope on + the leaf element where this type is used. + + o The set of variable bindings contains one variable, + 'USER', which contains the name of the user of the current + session. + + o The function library is the core function library, but + note that due to the syntax restrictions of an + instance-identifier, no functions are allowed. + + o The context node is the root node in the data tree."; + } + + container nacm { + nacm:default-deny-all; + description + "Parameters for NETCONF Access Control Model."; + leaf enable-nacm { + type boolean; + default "true"; + description + "Enables or disables all NETCONF access control + enforcement. If 'true', then enforcement + is enabled. If 'false', then enforcement + is disabled."; + } + leaf read-default { + type action-type; + default "permit"; + description + "Controls whether read access is granted if + no appropriate rule is found for a + particular read request."; + } + leaf write-default { + type action-type; + default "deny"; + description + "Controls whether create, update, or delete access + is granted if no appropriate rule is found for a + particular write request."; + } + leaf exec-default { + type action-type; + default "permit"; + description + "Controls whether exec access is granted if no appropriate + rule is found for a particular protocol operation request."; + } + leaf enable-external-groups { + type boolean; + default "true"; + description + "Controls whether the server uses the groups reported by the + NETCONF transport layer when it assigns the user to a set of + NACM groups. If this leaf has the value 'false', any group + names reported by the transport layer are ignored by the + server."; + } + leaf denied-operations { + type yang:zero-based-counter32; + config false; + mandatory true; + description + "Number of times since the server last restarted that a + protocol operation request was denied."; + } + leaf denied-data-writes { + type yang:zero-based-counter32; + config false; + mandatory true; + when "../denied-operations > 0"; + description + "Number of times since the server last restarted that a + protocol operation request to alter + a configuration datastore was denied."; + } + leaf denied-notifications { + type yang:zero-based-counter32; + config false; + mandatory true; + description + "Number of times since the server last restarted that + a notification was dropped for a subscription because + access to the event type was denied."; + } + container groups { + description + "NETCONF Access Control Groups."; + list group { + key "name"; + description + "One NACM Group Entry. This list will only contain + configured entries, not any entries learned from + any transport protocols."; + leaf name { + type group-name-type; + description + "Group name associated with this entry."; + } + leaf-list user-name { + type user-name-type; + description + "Each entry identifies the username of + a member of the group associated with + this entry."; + } + } + } + list rule-list { + key "name"; + ordered-by user; + description + "An ordered collection of access control rules."; + leaf name { + type string { + length "1..max"; + } + description + "Arbitrary name assigned to the rule-list."; + } + leaf-list group { + type union { + type matchall-string-type; + type group-name-type; + } + description + "List of administrative groups that will be + assigned the associated access rights + defined by the 'rule' list. + + The string '*' indicates that all groups apply to the + entry."; + } + list rule { + key "name"; + ordered-by user; + description + "One access control rule. + + Rules are processed in user-defined order until a match is + found. A rule matches if 'module-name', 'rule-type', and + 'access-operations' match the request. If a rule + matches, the 'action' leaf determines if access is granted + or not."; + leaf name { + type string { + length "1..max"; + } + description + "Arbitrary name assigned to the rule."; + } + leaf module-name { + type union { + type matchall-string-type; + type string; + } + default "*"; + description + "Name of the module associated with this rule. + + This leaf matches if it has the value '*' or if the + object being accessed is defined in the module with the + specified module name."; + } + choice rule-type { + description + "This choice matches if all leafs present in the rule + match the request. If no leafs are present, the + choice matches all requests."; + case protocol-operation { + leaf rpc-name { + type union { + type matchall-string-type; + type string; + } + description + "This leaf matches if it has the value '*' or if + its value equals the requested protocol operation + name."; + } + } + case notification { + leaf notification-name { + type union { + type matchall-string-type; + type string; + } + description + "This leaf matches if it has the value '*' or if its + value equals the requested notification name."; + } + } + case data-node { + leaf path { + type node-instance-identifier; + mandatory true; + description + "Data Node Instance Identifier associated with the + data node controlled by this rule. + + Configuration data or state data instance + identifiers start with a top-level data node. A + complete instance identifier is required for this + type of path value. + + The special value '/' refers to all possible + datastore contents."; + } + } + } + leaf access-operations { + type union { + type matchall-string-type; + type access-operations-type; + } + default "*"; + description + "Access operations associated with this rule. + + This leaf matches if it has the value '*' or if the + bit corresponding to the requested operation is set."; + } + leaf action { + type action-type; + mandatory true; + description + "The access control action associated with the + rule. If a rule is determined to match a + particular request, then this object is used + to determine whether to permit or deny the + request."; + } + leaf comment { + type string; + description + "A textual description of the access rule."; + } + } + } + } +} diff --git a/tools/lint/examples/ietf-netconf-acm-when.yin b/tools/lint/examples/ietf-netconf-acm-when.yin new file mode 100644 index 0000000..cbff758 --- /dev/null +++ b/tools/lint/examples/ietf-netconf-acm-when.yin @@ -0,0 +1,447 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module xmlns="urn:ietf:params:xml:ns:yang:yin:1" xmlns:nacm="urn:ietf:params:xml:ns:yang:ietf-netconf-acm" xmlns:yang="urn:ietf:params:xml:ns:yang:ietf-yang-types" name="ietf-netconf-acm-when"> + <namespace uri="urn:ietf:params:xml:ns:yang:ietf-netconf-acm"/> + <prefix value="nacm"/> + <import module="ietf-yang-types"> + <prefix value="yang"/> + </import> + <organization> + <text>IETF NETCONF (Network Configuration) Working Group</text> + </organization> + <contact> + <text>WG Web: <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..9f0d027 --- /dev/null +++ b/tools/lint/main.c @@ -0,0 +1,102 @@ +/** + * @file main.c + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief libyang's yanglint tool + * + * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200809L /* strdup */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "libyang.h" + +#include "cmd.h" +#include "common.h" +#include "completion.h" +#include "configuration.h" +#include "linenoise/linenoise.h" + +int done; +struct ly_ctx *ctx = NULL; + +/* main_ni.c */ +int main_ni(int argc, char *argv[]); + +int +main(int argc, char *argv[]) +{ + char *cmdline; + int cmdlen; + + if (argc > 1) { + /* run in non-interactive mode */ + return main_ni(argc, argv); + } + + /* continue in interactive mode */ + linenoiseSetCompletionCallback(complete_cmd); + load_config(); + + if (ly_ctx_new(NULL, YL_DEFAULT_CTX_OPTIONS, &ctx)) { + YLMSG_E("Failed to create context.\n"); + return 1; + } + + while (!done) { + uint8_t executed = 0; + + /* get the command from user */ + cmdline = linenoise(PROMPT); + + /* EOF -> exit */ + if (cmdline == NULL) { + done = 1; + cmdline = strdup("quit"); + } + + /* empty line -> wait for another command */ + if (*cmdline == '\0') { + free(cmdline); + continue; + } + + /* isolate the command word. */ + for (cmdlen = 0; cmdline[cmdlen] && (cmdline[cmdlen] != ' '); cmdlen++) {} + + /* execute the command if any valid specified */ + for (uint16_t i = 0; commands[i].name; i++) { + if (strncmp(cmdline, commands[i].name, (size_t)cmdlen) || (commands[i].name[cmdlen] != '\0')) { + continue; + } + + commands[i].func(&ctx, cmdline); + executed = 1; + break; + } + + if (!executed) { + /* if unknown command specified, tell it to user */ + YLMSG_E("Unknown command \"%.*s\", type 'help' for more information.\n", cmdlen, cmdline); + } + + linenoiseHistoryAdd(cmdline); + free(cmdline); + } + + store_config(); + ly_ctx_destroy(ctx); + + return 0; +} diff --git a/tools/lint/main_ni.c b/tools/lint/main_ni.c new file mode 100644 index 0000000..04c2340 --- /dev/null +++ b/tools/lint/main_ni.c @@ -0,0 +1,1027 @@ +/** + * @file main_ni.c + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> + * @brief libyang's yanglint tool - non-interactive code + * + * Copyright (c) 2020 - 2022 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include <errno.h> +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/stat.h> + +#include "libyang.h" +#include "plugins_exts.h" + +#include "common.h" +#include "out.h" +#include "tools/config.h" + +/** + * @brief Context structure to hold and pass variables in a structured form. + */ +struct context { + /* libyang context for the run */ + const char *yang_lib_file; + uint16_t ctx_options; + struct ly_ctx *ctx; + + /* prepared output (--output option or stdout by default) */ + struct ly_out *out; + + char *searchpaths; + + /* options flags */ + uint8_t list; /* -l option to print list of schemas */ + + /* line length for 'tree' format */ + size_t line_length; /* --tree-line-length */ + + /* + * schema + */ + /* set schema modules' features via --features option (struct schema_features *) */ + struct ly_set schema_features; + + /* set of loaded schema modules (struct lys_module *) */ + struct ly_set schema_modules; + + /* options to parse and print schema modules */ + uint32_t schema_parse_options; + uint32_t schema_print_options; + + /* specification of printing schema node subtree, option --schema-node */ + const char *schema_node_path; + const struct lysc_node *schema_node; + const char *submodule; + + /* name of file containing explicit context passed to callback + * for schema-mount extension. This also causes a callback to + * be registered. + */ + char *schema_context_filename; + + /* value of --format in case of schema format */ + LYS_OUTFORMAT schema_out_format; + ly_bool feature_param_format; + + /* + * data + */ + /* various options based on --type option */ + enum lyd_type data_type; + uint32_t data_parse_options; + uint32_t data_validate_options; + uint32_t data_print_options; + + /* flag for --merge option */ + uint8_t data_merge; + + /* value of --format in case of data format */ + LYD_FORMAT data_out_format; + + /* input data files (struct cmdline_file *) */ + struct ly_set data_inputs; + + /* storage for --operational */ + struct cmdline_file data_operational; + + /* storage for --reply-rpc */ + struct cmdline_file reply_rpc; +}; + +static void +erase_context(struct context *c) +{ + /* data */ + ly_set_erase(&c->data_inputs, free_cmdline_file); + ly_in_free(c->data_operational.in, 1); + + /* schema */ + ly_set_erase(&c->schema_features, free_features); + ly_set_erase(&c->schema_modules, NULL); + + /* context */ + free(c->searchpaths); + c->searchpaths = NULL; + + ly_out_free(c->out, NULL, 0); + ly_ctx_destroy(c->ctx); + + if (c->schema_context_filename) { + free(c->schema_context_filename); + } +} + +static void +version(void) +{ + printf("yanglint %s\n", PROJECT_VERSION); +} + +static void +help(int shortout) +{ + + printf("Example usage:\n" + " yanglint [-f { yang | yin | info}] <schema>...\n" + " Validates the YANG module <schema>(s) and all its dependencies, optionally printing\n" + " them in the specified format.\n\n" + " yanglint [-f { xml | json }] <schema>... <file>...\n" + " Validates the YANG modeled data <file>(s) according to the <schema>(s) optionally\n" + " printing them in the specified format.\n\n" + " yanglint -t (nc-)rpc/notif [-O <operational-file>] <schema>... <file>\n" + " Validates the YANG/NETCONF RPC/notification <file> according to the <schema>(s) using\n" + " <operational-file> with possible references to the operational datastore data.\n\n" + " yanglint -t nc-reply -R <rpc-file> [-O <operational-file>] <schema>... <file>\n" + " Validates the NETCONF rpc-reply <file> of RPC <rpc-file> according to the <schema>(s)\n" + " using <operational-file> with possible references to the operational datastore data.\n\n" + " yanglint\n" + " Starts interactive mode with more features.\n\n"); + + if (shortout) { + return; + } + printf("Options:\n" + " -h, --help Show this help message and exit.\n" + " -v, --version Show version number and exit.\n" + " -V, --verbose Increase libyang verbosity and show verbose messages. If specified\n" + " a second time, show even debug messages.\n" + " -Q, --quiet Decrease libyang verbosity and hide warnings. If specified a second\n" + " time, hide errors so no libyang messages are printed.\n"); + + printf(" -f FORMAT, --format=FORMAT\n" + " Convert input into FORMAT. Supported formats: \n" + " yang, yin, tree, info and feature-param for schemas,\n" + " xml, json, and lyb for data.\n\n"); + + printf(" -p PATH, --path=PATH\n" + " Search path for schema (YANG/YIN) modules. The option can be\n" + " used multiple times. The current working directory and the\n" + " path of the module being added is used implicitly.\n\n"); + + printf(" -D, --disable-searchdir\n" + " Do not implicitly search in current working directory for\n" + " schema modules. If specified a second time, do not even\n" + " search in the module directory (all modules must be \n" + " explicitly specified).\n\n"); + + printf(" -F FEATURES, --features=FEATURES\n" + " Specific module features to support in the form <module-name>:(<feature>,)*\n" + " Use <feature> '*' to enable all features of a module. This option can be\n" + " specified multiple times, to enable features in multiple modules. If this\n" + " option is not specified, all the features in all the implemented modules\n" + " are enabled.\n\n"); + + printf(" -i, --make-implemented\n" + " Make the imported modules \"referenced\" from any loaded\n" + " module also implemented. If specified a second time, all the\n" + " modules are set implemented.\n\n"); + + printf(" -P PATH, --schema-node=PATH\n" + " Print only the specified subtree of the schema.\n" + " The PATH is the XPath subset mentioned in documentation as\n" + " the Path format. The option can be combined with --single-node\n" + " option to print information only about the specified node.\n" + " -q, --single-node\n" + " Supplement to the --schema-node option to print information\n" + " only about a single node specified as PATH argument.\n\n"); + + printf(" -s SUBMODULE, --submodule=SUBMODULE\n" + " Print the specific submodule instead of the main module.\n\n"); + + printf(" -x FILE, --ext-data=FILE\n" + " File containing the specific data required by an extension. Required by\n" + " the schema-mount extension, for example, when the operational data are\n" + " expected in the file. File format is guessed.\n\n"); + + printf(" -n, --not-strict\n" + " Do not require strict data parsing (silently skip unknown data),\n" + " has no effect for schemas.\n\n"); + + printf(" -e, --present Validate only with the schema modules whose data actually\n" + " exist in the provided input data files. Takes effect only\n" + " with the 'data' or 'config' TYPEs. Used to avoid requiring\n" + " mandatory nodes from modules which data are not present in the\n" + " provided input data files.\n\n"); + + printf(" -t TYPE, --type=TYPE\n" + " Specify data tree type in the input data file(s):\n" + " data - Complete datastore with status data (default type).\n" + " config - Configuration datastore (without status data).\n" + " get - Data returned by the NETCONF <get> operation.\n" + " getconfig - Data returned by the NETCONF <get-config> operation.\n" + " edit - Config content of the NETCONF <edit-config> operation.\n" + " rpc - Invocation of a YANG RPC/action, defined as input.\n" + " nc-rpc - Similar to 'rpc' but expect and check also the NETCONF\n" + " envelopes <rpc> or <action>.\n" + " reply - Reply to a YANG RPC/action, defined as output. Note that\n" + " the reply data are expected inside a container representing\n" + " the original RPC/action invocation.\n" + " nc-reply - Similar to 'reply' but expect and check also the NETCONF\n" + " envelope <rpc-reply> with output data nodes as direct\n" + " descendants. The original RPC/action invocation is expected\n" + " in a separate parameter '-R' and is parsed as 'nc-rpc'.\n" + " notif - Notification instance of a YANG notification.\n" + " nc-notif - Similar to 'notif' but expect and check also the NETCONF\n" + " envelope <notification> with element <eventTime> and its\n" + " sibling as the actual notification.\n\n"); + + printf(" -d MODE, --default=MODE\n" + " Print data with default values, according to the MODE\n" + " (to print attributes, ietf-netconf-with-defaults model\n" + " must be loaded):\n" + " all - Add missing default nodes.\n" + " all-tagged - Add missing default nodes and mark all the default\n" + " nodes with the attribute.\n" + " trim - Remove all nodes with a default value.\n" + " implicit-tagged - Add missing nodes and mark them with the attribute.\n\n"); + + printf(" -l, --list Print info about the loaded schemas.\n" + " (i - imported module, I - implemented module)\n" + " In case the '-f' option with data encoding is specified,\n" + " the list is printed as \"ietf-yang-library\" data.\n\n"); + + printf(" -L LINE_LENGTH, --tree-line-length=LINE_LENGTH\n" + " The limit of the maximum line length on which the 'tree'\n" + " format will try to be printed.\n\n"); + + printf(" -o OUTFILE, --output=OUTFILE\n" + " Write the output to OUTFILE instead of stdout.\n\n"); + + printf(" -O FILE, --operational=FILE\n" + " Provide optional data to extend validation of the '(nc-)rpc',\n" + " '(nc-)reply' or '(nc-)notif' TYPEs. The FILE is supposed to contain\n" + " the operational datastore referenced from the operation.\n" + " In case of a nested operation, its parent existence is also\n" + " checked in these operational data.\n\n"); + + printf(" -R FILE, --reply-rpc=FILE\n" + " Provide source RPC for parsing of the 'nc-reply' TYPE. The FILE\n" + " is supposed to contain the source 'nc-rpc' operation of the reply.\n\n"); + + printf(" -m, --merge Merge input data files into a single tree and validate at\n" + " once. The option has effect only for 'data' and 'config' TYPEs.\n\n"); + + printf(" -y, --yang-library\n" + " Load and implement internal \"ietf-yang-library\" YANG module.\n" + " Note that this module includes definitions of mandatory state\n" + " data that can result in unexpected data validation errors.\n\n"); + + printf(" -Y FILE, --yang-library-file=FILE\n" + " Parse FILE with \"ietf-yang-library\" data and use them to\n" + " create an exact YANG schema context. If specified, the '-F'\n" + " parameter (enabled features) is ignored.\n\n"); + +#ifndef NDEBUG + printf(" -G GROUPS, --debug=GROUPS\n" + " Enable printing of specific debugging message group\n" + " (nothing will be printed unless verbosity is set to debug):\n" + " <group>[,<group>]* (dict, xpath, dep-sets)\n\n"); +#endif +} + +static void +libyang_verbclb(LY_LOG_LEVEL level, const char *msg, const char *path) +{ + char *levstr; + + switch (level) { + case LY_LLERR: + levstr = "err :"; + break; + case LY_LLWRN: + levstr = "warn:"; + break; + case LY_LLVRB: + levstr = "verb:"; + break; + default: + levstr = "dbg :"; + break; + } + if (path) { + fprintf(stderr, "libyang %s %s (%s)\n", levstr, msg, path); + } else { + fprintf(stderr, "libyang %s %s\n", levstr, msg); + } +} + +static struct schema_features * +get_features_not_applied(const struct ly_set *fset) +{ + for (uint32_t u = 0; u < fset->count; ++u) { + struct schema_features *sf = fset->objs[u]; + + if (!sf->applied) { + return sf; + } + } + + return NULL; +} + +static LY_ERR +ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free) +{ + struct ly_ctx *ctx; + struct lyd_node *data = NULL; + + ctx = ext->module->ctx; + if (user_data) { + lyd_parse_data_path(ctx, user_data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &data); + } + + *ext_data = data; + *ext_data_free = 1; + return LY_SUCCESS; +} + +static LY_ERR +searchpath_strcat(char **searchpaths, const char *path) +{ + uint64_t len; + char *new; + + if (!(*searchpaths)) { + *searchpaths = strdup(path); + return LY_SUCCESS; + } + + len = strlen(*searchpaths) + strlen(path) + strlen(PATH_SEPARATOR); + new = realloc(*searchpaths, sizeof(char) * len + 1); + if (!new) { + return LY_EMEM; + } + strcat(new, PATH_SEPARATOR); + strcat(new, path); + *searchpaths = new; + + return LY_SUCCESS; +} + +static int +fill_context_inputs(int argc, char *argv[], struct context *c) +{ + struct ly_in *in = NULL; + struct schema_features *sf; + struct lys_module *mod; + const char *all_features[] = {"*", NULL}; + char *dir = NULL, *module = NULL; + + /* Create libyang context. */ + if (c->yang_lib_file) { + /* ignore features */ + ly_set_erase(&c->schema_features, free_features); + + if (ly_ctx_new_ylpath(c->searchpaths, c->yang_lib_file, LYD_UNKNOWN, c->ctx_options, &c->ctx)) { + YLMSG_E("Unable to modify libyang context with yang-library data.\n"); + return -1; + } + } else { + /* set imp feature flag if all should be enabled */ + c->ctx_options |= !c->schema_features.count ? LY_CTX_ENABLE_IMP_FEATURES : 0; + + if (ly_ctx_new(c->searchpaths, c->ctx_options, &c->ctx)) { + YLMSG_E("Unable to create libyang context\n"); + return -1; + } + } + + /* set callback providing run-time extension instance data */ + if (c->schema_context_filename) { + ly_ctx_set_ext_data_clb(c->ctx, ext_data_clb, c->schema_context_filename); + } + + /* process the operational and/or reply RPC content if any */ + if (c->data_operational.path) { + if (get_input(c->data_operational.path, NULL, &c->data_operational.format, &c->data_operational.in)) { + return -1; + } + } + if (c->reply_rpc.path) { + if (get_input(c->reply_rpc.path, NULL, &c->reply_rpc.format, &c->reply_rpc.in)) { + return -1; + } + } + + for (int i = 0; i < argc - optind; i++) { + LYS_INFORMAT format_schema = LYS_IN_UNKNOWN; + LYD_FORMAT format_data = LYD_UNKNOWN; + + if (get_input(argv[optind + i], &format_schema, &format_data, &in)) { + goto error; + } + + if (format_schema) { + LY_ERR ret; + uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */ + const char **features; + + /* parse the input */ + if (parse_schema_path(argv[optind + i], &dir, &module)) { + goto error; + } + + if (c->yang_lib_file) { + /* just get the module, it should already be parsed */ + mod = ly_ctx_get_module_implemented(c->ctx, module); + if (!mod) { + YLMSG_E("Schema module \"%s\" not implemented in yang-library data.\n", module); + goto error; + } + } else { + /* add temporarily also the path of the module itself */ + if (ly_ctx_set_searchdir(c->ctx, dir) == LY_EEXIST) { + path_unset = 0; + } + + /* get features list for this module */ + if (!c->schema_features.count) { + features = all_features; + } else { + get_features(&c->schema_features, module, &features); + } + + /* parse module */ + ret = lys_parse(c->ctx, in, format_schema, features, &mod); + ly_ctx_unset_searchdir_last(c->ctx, path_unset); + if (ret) { + YLMSG_E("Parsing schema module \"%s\" failed.\n", argv[optind + i]); + goto error; + } + } + + /* temporary cleanup */ + free(dir); + dir = NULL; + free(module); + module = NULL; + + if (c->schema_out_format || c->feature_param_format) { + /* module will be printed */ + if (ly_set_add(&c->schema_modules, (void *)mod, 1, NULL)) { + YLMSG_E("Storing parsed schema module (%s) for print failed.\n", argv[optind + i]); + goto error; + } + } + } else if (format_data) { + if (!fill_cmdline_file(&c->data_inputs, in, argv[optind + i], format_data)) { + goto error; + } + in = NULL; + } + + ly_in_free(in, 1); + in = NULL; + } + + /* check that all specified features were applied, apply now if possible */ + while ((sf = get_features_not_applied(&c->schema_features))) { + /* try to find implemented or the latest revision of this module */ + mod = ly_ctx_get_module_implemented(c->ctx, sf->mod_name); + if (!mod) { + mod = ly_ctx_get_module_latest(c->ctx, sf->mod_name); + } + if (!mod) { + YLMSG_E("Specified features not applied, module \"%s\" not loaded.\n", sf->mod_name); + goto error; + } + + /* we have the module, implement it if needed and enable the specific features */ + if (lys_set_implemented(mod, (const char **)sf->features)) { + YLMSG_E("Implementing module \"%s\" failed.\n", mod->name); + goto error; + } + sf->applied = 1; + } + + return 0; + +error: + ly_in_free(in, 1); + free(dir); + free(module); + return -1; +} + +/** + * @brief Process command line options and store the settings into the context. + * + * return -1 in case of error; + * return 0 in case of success and ready to process + * return 1 in case of success, but expect to exit. + */ +static int +fill_context(int argc, char *argv[], struct context *c) +{ + int ret; + + int opt, opt_index; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, + {"verbose", no_argument, NULL, 'V'}, + {"quiet", no_argument, NULL, 'Q'}, + {"format", required_argument, NULL, 'f'}, + {"path", required_argument, NULL, 'p'}, + {"disable-searchdir", no_argument, NULL, 'D'}, + {"features", required_argument, NULL, 'F'}, + {"make-implemented", no_argument, NULL, 'i'}, + {"schema-node", required_argument, NULL, 'P'}, + {"single-node", no_argument, NULL, 'q'}, + {"submodule", required_argument, NULL, 's'}, + {"ext-data", required_argument, NULL, 'x'}, + {"not-strict", no_argument, NULL, 'n'}, + {"present", no_argument, NULL, 'e'}, + {"type", required_argument, NULL, 't'}, + {"default", required_argument, NULL, 'd'}, + {"list", no_argument, NULL, 'l'}, + {"tree-line-length", required_argument, NULL, 'L'}, + {"output", required_argument, NULL, 'o'}, + {"operational", required_argument, NULL, 'O'}, + {"reply-rpc", required_argument, NULL, 'R'}, + {"merge", no_argument, NULL, 'm'}, + {"yang-library", no_argument, NULL, 'y'}, + {"yang-library-file", required_argument, NULL, 'Y'}, +#ifndef NDEBUG + {"debug", required_argument, NULL, 'G'}, +#endif + {NULL, 0, NULL, 0} + }; + uint8_t data_type_set = 0; + + c->ctx_options = YL_DEFAULT_CTX_OPTIONS; + c->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS; + c->line_length = 0; + + opterr = 0; +#ifndef NDEBUG + while ((opt = getopt_long(argc, argv, "hvVQf:p:DF:iP:qs:net:d:lL:o:O:R:myY:x:G:", options, &opt_index)) != -1) +#else + while ((opt = getopt_long(argc, argv, "hvVQf:p:DF:iP:qs:net:d:lL:o:O:R:myY:x:", options, &opt_index)) != -1) +#endif + { + switch (opt) { + case 'h': /* --help */ + help(0); + return 1; + + case 'v': /* --version */ + version(); + return 1; + + case 'V': { /* --verbose */ + LY_LOG_LEVEL verbosity = ly_log_level(LY_LLERR); + + if (verbosity < LY_LLDBG) { + ++verbosity; + } + ly_log_level(verbosity); + break; + } /* case 'V' */ + + case 'Q': { /* --quiet */ + LY_LOG_LEVEL verbosity = ly_log_level(LY_LLERR); + + if (verbosity == LY_LLERR) { + /* turn logging off */ + ly_log_options(LY_LOSTORE_LAST); + } else if (verbosity > LY_LLERR) { + --verbosity; + } + ly_log_level(verbosity); + break; + } /* case 'Q' */ + + case 'f': /* --format */ + if (!strcasecmp(optarg, "yang")) { + c->schema_out_format = LYS_OUT_YANG; + c->data_out_format = 0; + } else if (!strcasecmp(optarg, "yin")) { + c->schema_out_format = LYS_OUT_YIN; + c->data_out_format = 0; + } else if (!strcasecmp(optarg, "info")) { + c->schema_out_format = LYS_OUT_YANG_COMPILED; + c->data_out_format = 0; + } else if (!strcasecmp(optarg, "tree")) { + c->schema_out_format = LYS_OUT_TREE; + c->data_out_format = 0; + } else if (!strcasecmp(optarg, "xml")) { + c->schema_out_format = 0; + c->data_out_format = LYD_XML; + } else if (!strcasecmp(optarg, "json")) { + c->schema_out_format = 0; + c->data_out_format = LYD_JSON; + } else if (!strcasecmp(optarg, "lyb")) { + c->schema_out_format = 0; + c->data_out_format = LYD_LYB; + } else if (!strcasecmp(optarg, "feature-param")) { + c->feature_param_format = 1; + } else { + YLMSG_E("Unknown output format %s\n", optarg); + help(1); + return -1; + } + break; + + case 'p': { /* --path */ + struct stat st; + + if (stat(optarg, &st) == -1) { + YLMSG_E("Unable to use search path (%s) - %s.\n", optarg, strerror(errno)); + return -1; + } + if (!S_ISDIR(st.st_mode)) { + YLMSG_E("Provided search path is not a directory.\n"); + return -1; + } + + if (searchpath_strcat(&c->searchpaths, optarg)) { + YLMSG_E("Storing searchpath failed.\n"); + return -1; + } + + break; + } /* case 'p' */ + + case 'D': /* --disable-search */ + if (c->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) { + YLMSG_W("The -D option specified too many times.\n"); + } + if (c->ctx_options & LY_CTX_DISABLE_SEARCHDIR_CWD) { + c->ctx_options &= ~LY_CTX_DISABLE_SEARCHDIR_CWD; + c->ctx_options |= LY_CTX_DISABLE_SEARCHDIRS; + } else { + c->ctx_options |= LY_CTX_DISABLE_SEARCHDIR_CWD; + } + break; + + case 'F': /* --features */ + if (parse_features(optarg, &c->schema_features)) { + return -1; + } + break; + + case 'i': /* --make-implemented */ + if (c->ctx_options & LY_CTX_REF_IMPLEMENTED) { + c->ctx_options &= ~LY_CTX_REF_IMPLEMENTED; + c->ctx_options |= LY_CTX_ALL_IMPLEMENTED; + } else { + c->ctx_options |= LY_CTX_REF_IMPLEMENTED; + } + break; + + case 'P': /* --schema-node */ + c->schema_node_path = optarg; + break; + + case 'q': /* --single-node */ + c->schema_print_options |= LYS_PRINT_NO_SUBSTMT; + break; + + case 's': /* --submodule */ + c->submodule = optarg; + break; + + case 'x': /* --ext-data */ + c->schema_context_filename = strdup(optarg); + break; + + case 'n': /* --not-strict */ + c->data_parse_options &= ~LYD_PARSE_STRICT; + break; + + case 'e': /* --present */ + c->data_validate_options |= LYD_VALIDATE_PRESENT; + break; + + case 't': /* --type */ + if (data_type_set) { + YLMSG_E("The data type (-t) cannot be set multiple times.\n"); + return -1; + } + + if (!strcasecmp(optarg, "config")) { + c->data_parse_options |= LYD_PARSE_NO_STATE; + c->data_validate_options |= LYD_VALIDATE_NO_STATE; + } else if (!strcasecmp(optarg, "get")) { + c->data_parse_options |= LYD_PARSE_ONLY; + } else if (!strcasecmp(optarg, "getconfig") || !strcasecmp(optarg, "get-config")) { + c->data_parse_options |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE; + } else if (!strcasecmp(optarg, "edit")) { + c->data_parse_options |= LYD_PARSE_ONLY; + } else if (!strcasecmp(optarg, "rpc")) { + c->data_type = LYD_TYPE_RPC_YANG; + } else if (!strcasecmp(optarg, "nc-rpc")) { + c->data_type = LYD_TYPE_RPC_NETCONF; + } else if (!strcasecmp(optarg, "reply")) { + c->data_type = LYD_TYPE_REPLY_YANG; + } else if (!strcasecmp(optarg, "nc-reply")) { + c->data_type = LYD_TYPE_REPLY_NETCONF; + } else if (!strcasecmp(optarg, "notif")) { + c->data_type = LYD_TYPE_NOTIF_YANG; + } else if (!strcasecmp(optarg, "nc-notif")) { + c->data_type = LYD_TYPE_NOTIF_NETCONF; + } else if (!strcasecmp(optarg, "data")) { + /* default option */ + } else { + YLMSG_E("Unknown data tree type %s\n", optarg); + help(1); + return -1; + } + + data_type_set = 1; + break; + + case 'd': /* --default */ + if (!strcasecmp(optarg, "all")) { + c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL; + } else if (!strcasecmp(optarg, "all-tagged")) { + c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG; + } else if (!strcasecmp(optarg, "trim")) { + c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM; + } else if (!strcasecmp(optarg, "implicit-tagged")) { + c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG; + } else { + YLMSG_E("Unknown default mode %s\n", optarg); + help(1); + return -1; + } + break; + + case 'l': /* --list */ + c->list = 1; + break; + + case 'L': /* --tree-line-length */ + c->line_length = atoi(optarg); + break; + + case 'o': /* --output */ + if (c->out) { + YLMSG_E("Only a single output can be specified.\n"); + return -1; + } else { + if (ly_out_new_filepath(optarg, &c->out)) { + YLMSG_E("Unable open output file %s (%s)\n", optarg, strerror(errno)); + return -1; + } + } + break; + + case 'O': /* --operational */ + if (c->data_operational.path) { + YLMSG_E("The operational datastore (-O) cannot be set multiple times.\n"); + return -1; + } + c->data_operational.path = optarg; + break; + + case 'R': /* --reply-rpc */ + if (c->reply_rpc.path) { + YLMSG_E("The PRC of the reply (-R) cannot be set multiple times.\n"); + return -1; + } + c->reply_rpc.path = optarg; + break; + + case 'm': /* --merge */ + c->data_merge = 1; + break; + + case 'y': /* --yang-library */ + c->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; + break; + + case 'Y': /* --yang-library-file */ + c->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; + c->yang_lib_file = optarg; + break; + +#ifndef NDEBUG + case 'G': { /* --debug */ + uint32_t dbg_groups = 0; + const char *ptr = optarg; + + while (ptr[0]) { + if (!strncasecmp(ptr, "dict", sizeof "dict" - 1)) { + dbg_groups |= LY_LDGDICT; + ptr += sizeof "dict" - 1; + } else if (!strncasecmp(ptr, "xpath", sizeof "xpath" - 1)) { + dbg_groups |= LY_LDGXPATH; + ptr += sizeof "xpath" - 1; + } else if (!strncasecmp(ptr, "dep-sets", sizeof "dep-sets" - 1)) { + dbg_groups |= LY_LDGDEPSETS; + ptr += sizeof "dep-sets" - 1; + } + + if (ptr[0]) { + if (ptr[0] != ',') { + YLMSG_E("Unknown debug group string \"%s\"\n", optarg); + return -1; + } + ++ptr; + } + } + ly_log_dbg_groups(dbg_groups); + break; + } /* case 'G' */ +#endif + default: + YLMSG_E("Invalid option or missing argument: -%c\n", optopt); + return -1; + } /* switch */ + } + + /* additional checks for the options combinations */ + if (!c->list && (optind >= argc)) { + help(1); + YLMSG_E("Missing <schema> to process.\n"); + return 1; + } + + if (c->data_merge) { + if (c->data_type || (c->data_parse_options & LYD_PARSE_ONLY)) { + /* switch off the option, incompatible input data type */ + c->data_merge = 0; + } else { + /* postpone validation after the merge of all the input data */ + c->data_parse_options |= LYD_PARSE_ONLY; + } + } + + if (c->data_operational.path && !c->data_type) { + YLMSG_E("Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types.\n"); + c->data_operational.path = NULL; + } + + if (c->reply_rpc.path && (c->data_type != LYD_TYPE_REPLY_NETCONF)) { + YLMSG_E("Source RPC is needed only for NETCONF Reply input data type.\n"); + c->data_operational.path = NULL; + } else if (!c->reply_rpc.path && (c->data_type == LYD_TYPE_REPLY_NETCONF)) { + YLMSG_E("Missing source RPC (-R) for NETCONF Reply input data type.\n"); + return -1; + } + + if ((c->schema_out_format != LYS_OUT_TREE) && c->line_length) { + YLMSG_E("--tree-line-length take effect only in case of the tree output format.\n"); + } + + /* default output stream */ + if (!c->out) { + if (ly_out_new_file(stdout, &c->out)) { + YLMSG_E("Unable to set stdout as output.\n"); + return -1; + } + } + + if (c->schema_out_format == LYS_OUT_TREE) { + /* print tree from lysc_nodes */ + c->ctx_options |= LY_CTX_SET_PRIV_PARSED; + } + + /* process input files provided as standalone command line arguments, + * schema modules are parsed and inserted into the context, + * data files are just checked and prepared into internal structures for further processing */ + ret = fill_context_inputs(argc, argv, c); + if (ret) { + return ret; + } + + /* the second batch of checks */ + if (c->schema_print_options && !c->schema_out_format) { + YLMSG_W("Schema printer options specified, but the schema output format is missing.\n"); + } + if (c->schema_parse_options && !c->schema_modules.count) { + YLMSG_W("Schema parser options specified, but no schema input file provided.\n"); + } + if (c->data_print_options && !c->data_out_format) { + YLMSG_W("data printer options specified, but the data output format is missing.\n"); + } + if (((c->data_parse_options != YL_DEFAULT_DATA_PARSE_OPTIONS) || c->data_type) && !c->data_inputs.count) { + YLMSG_W("Data parser options specified, but no data input file provided.\n"); + } + + if (c->schema_node_path) { + c->schema_node = lys_find_path(c->ctx, NULL, c->schema_node_path, 0); + if (!c->schema_node) { + c->schema_node = lys_find_path(c->ctx, NULL, c->schema_node_path, 1); + + if (!c->schema_node) { + YLMSG_E("Invalid schema path.\n"); + return -1; + } + } + } + + return 0; +} + +int +main_ni(int argc, char *argv[]) +{ + int ret = EXIT_SUCCESS, r; + struct context c = {0}; + char *features_output = NULL; + struct ly_set set = {0}; + uint32_t u; + + /* set callback for printing libyang messages */ + ly_set_log_clb(libyang_verbclb, 1); + + r = fill_context(argc, argv, &c); + if (r < 0) { + ret = EXIT_FAILURE; + } + if (r) { + goto cleanup; + } + + /* do the required job - parse, validate, print */ + + if (c.list) { + /* print the list of schemas */ + print_list(c.out, c.ctx, c.data_out_format); + } else { + if (c.feature_param_format) { + for (u = 0; u < c.schema_modules.count; u++) { + if (collect_features(c.schema_modules.objs[u], &set)) { + YLMSG_E("Unable to read features from a module.\n"); + goto cleanup; + } + if (generate_features_output(c.schema_modules.objs[u], &set, &features_output)) { + YLMSG_E("Unable to generate feature command output.\n"); + goto cleanup; + } + ly_set_erase(&set, NULL); + } + ly_print(c.out, "%s\n", features_output); + } else if (c.schema_out_format) { + if (c.schema_node) { + ret = lys_print_node(c.out, c.schema_node, c.schema_out_format, 0, c.schema_print_options); + if (ret) { + YLMSG_E("Unable to print schema node %s.\n", c.schema_node_path); + goto cleanup; + } + } else if (c.submodule) { + const struct lysp_submodule *submod = ly_ctx_get_submodule_latest(c.ctx, c.submodule); + + if (!submod) { + YLMSG_E("Unable to find submodule %s.\n", c.submodule); + goto cleanup; + } + + ret = lys_print_submodule(c.out, submod, c.schema_out_format, c.line_length, c.schema_print_options); + if (ret) { + YLMSG_E("Unable to print submodule %s.\n", submod->name); + goto cleanup; + } + } else { + for (u = 0; u < c.schema_modules.count; ++u) { + ret = lys_print_module(c.out, (struct lys_module *)c.schema_modules.objs[u], c.schema_out_format, + c.line_length, c.schema_print_options); + /* for YANG Tree Diagrams printing it's more readable to print a blank line between modules. */ + if ((c.schema_out_format == LYS_OUT_TREE) && (u + 1 < c.schema_modules.count)) { + ly_print(c.out, "\n"); + } + if (ret) { + YLMSG_E("Unable to print module %s.\n", ((struct lys_module *)c.schema_modules.objs[u])->name); + goto cleanup; + } + } + } + } + + /* do the data validation despite the schema was printed */ + if (c.data_inputs.size) { + ret = process_data(c.ctx, c.data_type, c.data_merge, c.data_out_format, c.out, c.data_parse_options, + c.data_validate_options, c.data_print_options, &c.data_operational, &c.reply_rpc, &c.data_inputs, NULL); + if (ret) { + goto cleanup; + } + } + } + +cleanup: + /* cleanup */ + erase_context(&c); + free(features_output); + ly_set_erase(&set, NULL); + + return ret; +} diff --git a/tools/lint/main_ni_only.c b/tools/lint/main_ni_only.c new file mode 100644 index 0000000..d55f2c2 --- /dev/null +++ b/tools/lint/main_ni_only.c @@ -0,0 +1,22 @@ +/** + * @file main_ni_only.c + * @brief non-interactive implementation of main() for those platforms without the linenoise library + * + * Copyright (c) 2015-2021 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +int main_ni(int argc, char *argv[]); + +int done; /* for cmd.c */ + +int +main(int argc, char *argv[]) +{ + return main_ni(argc, argv); +} diff --git a/tools/lint/tests/expect/common.exp b/tools/lint/tests/expect/common.exp new file mode 100644 index 0000000..0381e6c --- /dev/null +++ b/tools/lint/tests/expect/common.exp @@ -0,0 +1,68 @@ +# detect the path to the yanglint binary +if { [info exists ::env(YANGLINT)] } { + set yanglint "$env(YANGLINT)" +} else { + set yanglint "../../../../build/yanglint" +} + +# set the timeout to 1 second +set timeout 1 + +# expect a single line of anchored regex output +proc expect_output {output} { + expect { + -re "^${output}$" {} + timeout {exit 1} + } +} + +# send a command and either expect some anchored regex output if specified or just an empty line +proc expect_command {command has_output output} { + send -- "${command}\r" + + if ($has_output==1) { + expect { + -re "^${command}\r\n${output}$" {} + timeout {exit 1} + } + } else { + # input echoes + expect { + -re "^${command}\r\n$" {} + timeout {exit 1} + } + expect { + -re "^> $" {} + timeout {exit 1} + } + } +} + +# send a completion request and check if the anchored regex output matches +proc expect_completion {input output} { + send -- "${input}\t" + + expect { + # expecting echoing input, output and 10 terminal control characters + -re "^${input}\r> ${output}.*\r.*$" {} + timeout {exit 1} + } +} + +# send a completion request and check if the anchored regex hint options match +proc expect_hint {input prev_input hints} { + set output {} + foreach i $hints { + # each element might have some number of spaces and CRLF around it + append output "${i} *(?:\\r\\n)?" + } + + send -- "${input}\t" + + expect { + # expecting the hints, previous input from which the hints were generated + # and some number of terminal control characters + -re "^\r\n${output}\r> ${prev_input}.*\r.*$" {} + timeout {exit 1} + } +} diff --git a/tools/lint/tests/expect/completion.exp b/tools/lint/tests/expect/completion.exp new file mode 100755 index 0000000..ed4f6bd --- /dev/null +++ b/tools/lint/tests/expect/completion.exp @@ -0,0 +1,60 @@ +#!/usr/bin/expect -f + +if { [info exists ::env(CURRENT_SOURCE_DIR)] } { + source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp" + set yang_dir "$env(CURRENT_SOURCE_DIR)/examples" +} else { + source "common.exp" + set yang_dir "../../examples" +} + +spawn $yanglint + +# skip no dir and/or no history warnings +expect_output "(YANGLINT.*)*> " + +expect_command "clear -ii" 0 "" + +expect_command "add ${yang_dir}/ietf-ip.yang" 0 "" + +expect_completion "print -f info -P " "print -f info -P /ietf-" + +set hints {"/ietf-yang-schema-mount:schema-mounts" "/ietf-interfaces:interfaces" "/ietf-interfaces:interfaces-state"} + +expect_hint "" "print -f info -P /ietf-" $hints + +expect_completion "i" "print -f info -P /ietf-interfaces:interfaces" + +expect_completion "/" "print -f info -P /ietf-interfaces:interfaces/interface" + +set hints {"/ietf-interfaces:interfaces/interface" +"/ietf-interfaces:interfaces/interface/name" "/ietf-interfaces:interfaces/interface/description" +"/ietf-interfaces:interfaces/interface/type" "/ietf-interfaces:interfaces/interface/enabled" +"/ietf-interfaces:interfaces/interface/link-up-down-trap-enable" +"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6"} + +expect_hint "" "print -f info -P /ietf-interfaces:interfaces/interface" $hints + +expect_completion "/i" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv" + +expect_completion "4" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4" + +set hints { "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled" +"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/forwarding" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/mtu" +"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/address" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/neighbor" +} + +expect_hint "\t" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv" $hints + +expect_completion "/e" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled " + +send -- "\r" + +expect { + -re ".*\r\n> " {} + timeout {exit 1} +} + +send -- "exit\r" + +expect eof diff --git a/tools/lint/tests/expect/feature.exp b/tools/lint/tests/expect/feature.exp new file mode 100755 index 0000000..37680b0 --- /dev/null +++ b/tools/lint/tests/expect/feature.exp @@ -0,0 +1,20 @@ +#!/usr/bin/expect -f + +if { [info exists ::env(CURRENT_SOURCE_DIR)] } { + source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp" + set yang_dir "$env(CURRENT_SOURCE_DIR)/examples" +} else { + source "common.exp" + set yang_dir "../../examples" +} + +spawn $yanglint + +# skip no dir and/or no history warnings +expect_output "(YANGLINT.*)*> " + +expect_command "feature -a" 1 "yang:\r\n\t\\(none\\)\r\n\r\nietf-yang-schema-mount:\r\n\t\\(none\\)\r\n\r\n> " + +send -- "exit\r" + +expect eof diff --git a/tools/lint/tests/expect/list.exp b/tools/lint/tests/expect/list.exp new file mode 100755 index 0000000..ec3cdba --- /dev/null +++ b/tools/lint/tests/expect/list.exp @@ -0,0 +1,20 @@ +#!/usr/bin/expect -f + +if { [info exists ::env(CURRENT_SOURCE_DIR)] } { + source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp" + set yang_dir "$env(CURRENT_SOURCE_DIR)/examples" +} else { + source "common.exp" + set yang_dir "../../examples" +} + +spawn $yanglint + +# skip no dir and/or no history warnings +expect_output "(YANGLINT.*)*> " + +expect_command "list" 1 "List of the loaded models:\r\n *i ietf-yang-metadata@2016-08-05\r\n *I yang@2022-06-16\r\n *i ietf-inet-types@2013-07-15\r\n *i ietf-yang-types@2013-07-15\r\n *I ietf-yang-schema-mount@2019-01-14\r\n *i ietf-yang-structure-ext@2020-06-17\r\n> " + +send -- "exit\r" + +expect eof diff --git a/tools/lint/tests/shunit2/feature.sh b/tools/lint/tests/shunit2/feature.sh new file mode 100755 index 0000000..fb2ee88 --- /dev/null +++ b/tools/lint/tests/shunit2/feature.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +testFeature() { + models=( "iana-if-type@2014-05-08.yang" "ietf-netconf@2011-06-01.yang" "ietf-netconf-with-defaults@2011-06-01.yang" + "sm.yang" "ietf-interfaces@2014-05-08.yang" "ietf-netconf-acm@2018-02-14.yang" "ietf-origin@2018-02-14.yang" + "ietf-ip@2014-06-16.yang" "ietf-restconf@2017-01-26.yang" ) + features=( " -F iana-if-type:" + " -F ietf-netconf:writable-running,candidate,confirmed-commit,rollback-on-error,validate,startup,url,xpath" + " -F ietf-netconf-with-defaults:" " -F sm:" " -F ietf-interfaces:arbitrary-names,pre-provisioning,if-mib" + " -F ietf-netconf-acm:" " -F ietf-origin:" " -F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf" + " -F ietf-restconf:" ) + + for i in ${!models[@]}; do + output=`${YANGLINT} -f feature-param ${YANG_MODULES_DIR}/${models[$i]}` + assertEquals "Unexpected features of module ${models[$i]}." "${features[$i]}" "${output}" + done +} + +. shunit2 diff --git a/tools/lint/tests/shunit2/list.sh b/tools/lint/tests/shunit2/list.sh new file mode 100755 index 0000000..d64503a --- /dev/null +++ b/tools/lint/tests/shunit2/list.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +LIST_BASE="List of the loaded models: + i ietf-yang-metadata@2016-08-05 + I yang@2022-06-16 + i ietf-inet-types@2013-07-15 + i ietf-yang-types@2013-07-15 + I ietf-yang-schema-mount@2019-01-14 + i ietf-yang-structure-ext@2020-06-17" + +testListEmptyContext() { + output=`${YANGLINT} -l` + assertEquals "Unexpected list of modules in empty context." "${LIST_BASE}" "${output}" +} + +testListAllImplemented() { + LIST_BASE_ALLIMPLEMENTED="List of the loaded models: + I ietf-yang-metadata@2016-08-05 + I yang@2022-06-16 + I ietf-inet-types@2013-07-15 + I ietf-yang-types@2013-07-15 + I ietf-yang-schema-mount@2019-01-14 + I ietf-yang-structure-ext@2020-06-17" + output=`${YANGLINT} -lii` + assertEquals "Unexpected list of modules in empty context with -ii." "${LIST_BASE_ALLIMPLEMENTED}" "${output}" +} + +. shunit2 diff --git a/tools/lint/yanglint.1 b/tools/lint/yanglint.1 new file mode 100644 index 0000000..4b7060d --- /dev/null +++ b/tools/lint/yanglint.1 @@ -0,0 +1,136 @@ +.\" Manpage for yanglint. +.\" Process this file with +.\" groff -man -Tascii yanglint.1 +.\" + +.TH YANGLINT 1 "2016-10-27" "libyang" +.SH NAME +yanglint \- YANG lint tool +. +.SH SYNOPSIS +.B yanglint +.br +.B yanglint +[\fIOPTIONS\fP] +[\-f { \fByang\fP | \fByin\fP | \fBtree\fP } ] +.I FILE ... +.br +.B yanglint +[\fIOPTIONS\fP] +[\-f { \fBxml\fP | \fBjson\fP } ] +\fISCHEMA\fP... +\fIFILE\fP... +. +.SH DESCRIPTION +\fByanglint\fP is a command-line tool for validating and converting YANG +schemas and the YANG modeled data. For a simple use, it validates the provided +file and if the output format specified, it converts input data into the output +format. If started with no argument, \fByanglint\fP opens interactive +environment where the user is allowed to work with schemas and data in a more +complex way. +. +.SH OPTIONS +.TP +.BR "\-h\fR,\fP \-\^\-help" +Outputs usage help and exits. +.TP +.BR "\-v\fR,\fP \-\^\-version" +Outputs the version number and exits. +.TP +.BR "\-V\fR,\fP \-\^\-verbose" +Increases the verbosity level. If not specified, only errors are printed, with +each appearance it adds: warnings, verbose messages, debug messages (if compiled +with debug information). +.TP +.BR "\-p \fIPATH\fP\fR,\fP \-\^\-path=\fIPATH\fP" +Specifies search path for getting imported modules or included submodules. The option +can be used multiple times. The current working directory and path of the module +being added is used implicitly. +.TP +.BR "\-s\fR,\fP \-\^\-strict" +Changes handling of unknown data nodes - instead of silently ignoring unknown data, +error is printed and data parsing fails. This option applies only on data parsing. +.TP +.BR "\-f \fIFORMAT\fP\fR,\fP \-\^\-format=\fIFORMAT\fP" +Converts the content of the input \fIFILE\fPs into the specified \fIFORMAT\fP. If no +\fIOUTFILE\fP is specified, the data are printed on the standard output. Only the +compatible formats for the input \fIFILE\fPs are allowed, see the section \fBFORMATS\fP. +.TP +.BR "\-o \fIOUTFILE\fP\fR,\fP \-\^\-output=\fIOUTFILE\fP" +Writes the output data into the specified \fIOUTFILE\fP. The option can be used +only in combination with \fB--format\fR option. In case of converting schema, only +a single input schema \fIFILE\fP is allowed. In case of data input \fIFILE\fPs, +input is merged and printed into a single \fIOUTFILE\fP. +.TP +.BR "\-F \fIFEATURES\fP\fR,\fP \-\^\-features=\fIFEATURES\fP" +Specifies the list of enabled features in the format +\fIMODULE\fP:[\fIFEATURE\fP,...]. In case of processing multiple modules, the +option can be used repeatedly. To disable all the features, use an empty list +specified for the particular module. +.TP +.BR "\-d \fIMODE\fP\fR,\fP \-\^\-default=\fIMODE\fP" +Print data with default values, according to the \fIMODE\fP (to print attributes, +the ietf-netconf-with-defaults model must be loaded). The \fIMODE\fP is one of the following: + \[bu] \fBall\fP - add missing default nodes + \[bu] \fBall-tagged\fP - add missing default nodes and mark all the default nodes with the attribute + \[bu] \fBtrim\fP - remove all nodes with a default value + \[bu] \fBimplicit-tagged\fP - add missing nodes and mark them with the attribute +.TP +.BR "\-t \fITYPE\fP\fR,\fP \-\^\-type=\fITYPE\fP" +Specify data tree type in the input data \fIFILE\fPs. The \fITYPE\fP is one of the following: + \[bu] \fBauto\fP - Resolve data type (one of the following) automatically (as pyang does). Applicable only on XML input data. + \[bu] \fBdata\fP - Complete datastore with status data (default type). + \[bu] \fBconfig\fP - Configuration datastore (without status data). + \[bu] \fBget\fP - Result of the NETCONF <get> operation. + \[bu] \fBgetconfig\fP - Result of the NETCONF <get-config> operation. + \[bu] \fBedit\fP - Content of the NETCONF <edit-config> operation. + \[bu] \fBrpc\fP - Content of the NETCONF <rpc> message, defined as YANG's rpc input statement. + \[bu] \fBrpcreply\fP - Reply to the RPC. This is just a virtual \fITYPE\fP, for parsing replies, '\fBauto\fP' must be used since the data \fIFILE\fPs are expected in pairs. +.br + The first input data \fIFILE\fP is expected as '\fBrpc\fP' \fITYPE\fP, the second \fIFILE\fP is expected as reply to the previous RPC. + \[bu] \fBnotif\fP - Notification instance (content of the <notification> element without <eventTime>. +.TP +.BR "\-O \fIFILE\fP\fR,\fP \-\^\-operational=\fIFILE\fP] +Optional parameter for '\fBrpc\fP' and '\fBnotif\fP' \fITYPE\fPs, the \fIFILE\fP contains running configuration datastore and +state data referenced from the RPC/Notification. The same data apply to all input data \fIFILE\fPs. Note that the file +is validated as '\fBdata\fP' \fITYPE\fP. Special value '\fB!\fP' can be used as \fIFILE\fP argument to ignore the external references. +.TP +.BR "\-y \fIYANGLIB_PATH\fP" +Specify path to a yang-library data file (XML or JSON) describing the initial context. +If provided, yanglint loads the modules according to the content of the yang-library data tree. +Otherwise, an empty content with only the internal libyang modules is used. This does +not limit user to load another modules explicitly specified as command line parameters. +. +.SH FORMATS +There are two types of formats to use. +.TP +.I Schemas +In case of schemas, the content can be converted into the '\fByang\fP', '\fByin\fP' +and '\fBtree\fP' formats. As input, only YANG and YIN files are +accepted. Note, that the corresponding file extension is required. +.TP +.I Data\ \ \ +In case of YANG modeled data, the content can be converted between '\fBxml\fP' +and '\fBjson\fP' formats. Remember that the corresponding file extension of the +input file is required. +. + +.SH EXAMPLES +.IP \[bu] 2 +Open interactive environment: + yanglint +.IP \[bu] +Convert YANG model into YIN and print it to the stdout: + yanglint --format=yin ./ietf-system.yang +.IP \[bu] +Convert ietf-system configuration data from XML to JSON: + yanglint --format=json --type=config --output=data.json ./ietf-system.yang ./data.xml + +.SH SEE ALSO +https://github.com/CESNET/libyang (libyang homepage and Git repository) +. +.SH AUTHORS +Radek Krejci <rkrejci@cesnet.cz>, Michal Vasko <mvasko@cesnet.cz> +. +.SH COPYRIGHT +Copyright \(co 2015-2017 CESNET, a.l.e. diff --git a/tools/re/CMakeLists.txt b/tools/re/CMakeLists.txt new file mode 100644 index 0000000..7b6de22 --- /dev/null +++ b/tools/re/CMakeLists.txt @@ -0,0 +1,20 @@ +# yangre + +set(resrc + main.c) + +set(format_sources + ${format_sources} + ${CMAKE_CURRENT_SOURCE_DIR}/*.c + PARENT_SCOPE) + +add_executable(yangre ${resrc} ${compatsrc}) +target_link_libraries(yangre yang) +install(TARGETS yangre DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(FILES ${PROJECT_SOURCE_DIR}/tools/re/yangre.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) +target_include_directories(yangre BEFORE PRIVATE ${PROJECT_BINARY_DIR}) + +if(WIN32) + target_include_directories(yangre PRIVATE ${GETOPT_INCLUDE_DIR}) + target_link_libraries(yangre ${GETOPT_LIBRARY}) +endif() diff --git a/tools/re/main.c b/tools/re/main.c new file mode 100644 index 0000000..2292b2a --- /dev/null +++ b/tools/re/main.c @@ -0,0 +1,309 @@ +/** + * @file main.c + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief libyang's YANG Regular Expression tool + * + * Copyright (c) 2017 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE /* asprintf, strdup */ + +#include <errno.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "libyang.h" + +#include "compat.h" +#include "tools/config.h" + +void +help(void) +{ + fprintf(stdout, "YANG Regular Expressions processor.\n"); + fprintf(stdout, "Usage:\n"); + fprintf(stdout, " yangre [-hv]\n"); + fprintf(stdout, " yangre [-V] -p <regexp1> [-i] [-p <regexp2> [-i] ...] <string>\n"); + fprintf(stdout, " yangre [-V] -f <file>\n"); + fprintf(stdout, "Returns 0 if string matches the pattern(s), 1 if not and -1 on error.\n\n"); + fprintf(stdout, "Options:\n" + " -h, --help Show this help message and exit.\n" + " -v, --version Show version number and exit.\n" + " -V, --verbose Print the processing information.\n" + " -i, --invert-match Invert-match modifier for the closest preceding\n" + " pattern.\n" + " -p, --pattern=\"REGEXP\" Regular expression including the quoting,\n" + " which is applied the same way as in a YANG module.\n" + " -f, --file=\"FILE\" List of patterns and the <string> (separated by an\n" + " empty line) are taken from <file>. Invert-match is\n" + " indicated by the single space character at the \n" + " beginning of the pattern line. YANG quotation around\n" + " patterns is still expected, but that avoids issues with\n" + " reading quotation by shell. Avoid newline at the end\n" + " of the string line to represent empty <string>."); + fprintf(stdout, "Examples:\n" + " pattern \"[0-9a-fA-F]*\"; -> yangre -p '\"[0-9a-fA-F]*\"' '1F'\n" + " pattern '[a-zA-Z0-9\\-_.]*'; -> yangre -p \"'[a-zA-Z0-9\\-_.]*'\" 'a-b'\n" + " pattern [xX][mM][lL].*; -> yangre -p '[xX][mM][lL].*' 'xml-encoding'\n\n"); + fprintf(stdout, "Note that to pass YANG quoting through your shell, you are supposed to use\n" + "the other quotation around. For not-quoted patterns, use single quotes.\n\n"); +} + +void +version(void) +{ + fprintf(stdout, "yangre %s\n", PROJECT_VERSION); +} + +void +pattern_error(LY_LOG_LEVEL level, const char *msg, const char *path) +{ + (void) path; /* unused */ + + if (level == LY_LLERR) { + fprintf(stderr, "yangre error: %s\n", msg); + } +} + +static const char *module_start = "module yangre {" + "yang-version 1.1;" + "namespace urn:cesnet:libyang:yangre;" + "prefix re;" + "leaf pattern {" + " type string {"; +static const char *module_invertmatch = " { modifier invert-match; }"; +static const char *module_match = ";"; +static const char *module_end = "}}}"; + +static int +add_pattern(char ***patterns, int **inverts, int *counter, char *pattern) +{ + void *reallocated1, *reallocated2; + + (*counter)++; + reallocated1 = realloc(*patterns, *counter * sizeof **patterns); + reallocated2 = realloc(*inverts, *counter * sizeof **inverts); + if (!reallocated1 || !reallocated2) { + fprintf(stderr, "yangre error: memory allocation error.\n"); + free(reallocated1); + free(reallocated2); + return EXIT_FAILURE; + } + (*patterns) = reallocated1; + (*patterns)[*counter - 1] = strdup(pattern); + (*inverts) = reallocated2; + (*inverts)[*counter - 1] = 0; + + return EXIT_SUCCESS; +} + +int +main(int argc, char *argv[]) +{ + LY_ERR match; + int i, opt_index = 0, ret = -1, verbose = 0, blankline = 0; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {"file", required_argument, NULL, 'f'}, + {"invert-match", no_argument, NULL, 'i'}, + {"pattern", required_argument, NULL, 'p'}, + {"version", no_argument, NULL, 'v'}, + {"verbose", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} + }; + char **patterns = NULL, *str = NULL, *modstr = NULL, *s; + int *invert_match = NULL; + int patterns_count = 0; + struct ly_ctx *ctx = NULL; + struct lys_module *mod; + FILE *infile = NULL; + size_t len = 0; + ssize_t l; + + opterr = 0; + while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) { + switch (i) { + case 'h': + help(); + ret = -2; /* continue to allow printing version and help at once */ + break; + case 'f': + if (infile) { + help(); + fprintf(stderr, "yangre error: multiple input files are not supported.\n"); + goto cleanup; + } else if (patterns_count) { + help(); + fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n"); + goto cleanup; + } + infile = fopen(optarg, "rb"); + if (!infile) { + fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno)); + goto cleanup; + } + + while ((l = getline(&str, &len, infile)) != -1) { + if (!blankline && (str[0] == '\n')) { + /* blank line */ + blankline = 1; + continue; + } + if ((str[0] != '\n') && (str[l - 1] == '\n')) { + /* remove ending newline */ + str[l - 1] = '\0'; + } + if (blankline) { + /* done - str is now the string to check */ + blankline = 0; + break; + /* else read the patterns */ + } else if (add_pattern(&patterns, &invert_match, &patterns_count, + (str[0] == ' ') ? &str[1] : str)) { + goto cleanup; + } + if (str[0] == ' ') { + /* set invert-match */ + invert_match[patterns_count - 1] = 1; + } + } + if (blankline) { + /* corner case, no input after blankline meaning the pattern to check is empty */ + if (str != NULL) { + free(str); + } + str = malloc(sizeof(char)); + str[0] = '\0'; + } + break; + case 'i': + if (!patterns_count || invert_match[patterns_count - 1]) { + help(); + fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n"); + goto cleanup; + } + invert_match[patterns_count - 1] = 1; + break; + case 'p': + if (infile) { + help(); + fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n"); + goto cleanup; + } + if (add_pattern(&patterns, &invert_match, &patterns_count, optarg)) { + goto cleanup; + } + break; + case 'v': + version(); + ret = -2; /* continue to allow printing version and help at once */ + break; + case 'V': + verbose = 1; + break; + default: + help(); + if (optopt) { + fprintf(stderr, "yangre error: invalid option: -%c\n", optopt); + } else { + fprintf(stderr, "yangre error: invalid option: %s\n", argv[optind - 1]); + } + goto cleanup; + } + } + + if (ret == -2) { + goto cleanup; + } + + if (!str) { + /* check options compatibility */ + if (optind >= argc) { + help(); + fprintf(stderr, "yangre error: missing <string> parameter to process.\n"); + goto cleanup; + } else if (!patterns_count) { + help(); + fprintf(stderr, "yangre error: missing pattern parameter to use.\n"); + goto cleanup; + } + str = argv[optind]; + } + + for (modstr = (char *)module_start, i = 0; i < patterns_count; i++) { + if (asprintf(&s, "%s pattern %s%s", modstr, patterns[i], invert_match[i] ? module_invertmatch : module_match) == -1) { + fprintf(stderr, "yangre error: memory allocation failed.\n"); + goto cleanup; + } + if (modstr != module_start) { + free(modstr); + } + modstr = s; + } + if (asprintf(&s, "%s%s", modstr, module_end) == -1) { + fprintf(stderr, "yangre error: memory allocation failed.\n"); + goto cleanup; + } + if (modstr != module_start) { + free(modstr); + } + modstr = s; + + if (ly_ctx_new(NULL, 0, &ctx)) { + goto cleanup; + } + + ly_set_log_clb(pattern_error, 0); + if (lys_parse_mem(ctx, modstr, LYS_IN_YANG, &mod) || !mod->compiled || !mod->compiled->data) { + goto cleanup; + } + + /* check the value */ + match = lyd_value_validate(ctx, mod->compiled->data, str, strlen(str), NULL, NULL, NULL); + + if (verbose) { + for (i = 0; i < patterns_count; i++) { + fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i]); + fprintf(stdout, "matching %d: %s\n", i + 1, invert_match[i] ? "inverted" : "regular"); + } + fprintf(stdout, "string : %s\n", str); + if (match == LY_SUCCESS) { + fprintf(stdout, "result : matching\n"); + } else if (match == LY_EVALID) { + fprintf(stdout, "result : not matching\n"); + } else { + fprintf(stdout, "result : error (%s)\n", ly_errmsg(ctx)); + } + } + if (match == LY_SUCCESS) { + ret = 0; + } else if (match == LY_EVALID) { + ret = 1; + } else { + ret = -1; + } + +cleanup: + ly_ctx_destroy(ctx); + for (i = 0; i < patterns_count; i++) { + free(patterns[i]); + } + free(patterns); + free(invert_match); + free(modstr); + if (infile) { + fclose(infile); + free(str); + } + + return ret; +} diff --git a/tools/re/yangre.1 b/tools/re/yangre.1 new file mode 100644 index 0000000..e7b572b --- /dev/null +++ b/tools/re/yangre.1 @@ -0,0 +1,118 @@ +.\" Manpage for yanglint. +.\" Process this file with +.\" groff -man -Tascii yangre.1 +.\" + +.TH YANGRE 1 "2018-11-09" "libyang" +.SH NAME +yangre \- YANG regular expression processor +. +.SH SYNOPSIS +.B yangre +[\-V] \-p \fIREGEXP\fP [\-i] [\-p \fIREGEXP\fP [\-i]...] \fISTRING\fP +.br +.B yangre +[\-V] \-f \fIFILE\fP +. +.SH DESCRIPTION +\fByangre\fP is a command-line tool to test and evaluate regular expressions +for use in YANG schemas. Supported regular expressions are defined by the +W3C's XML-Schema standard. + +\fByangre\fP can be used either with regular expressions and a target string +on the command line or with input from a file. The latter is particularly +useful to avoid dealing with proper shell escaping of regular expression +patterns, which can be somewhat tricky. +. +.SH GENERAL OPTIONS +.TP +.BR "\-h\fR,\fP \-\^\-help" +.br +Outputs usage help and exits. +.TP +.BR "\-v\fR,\fP \-\^\-version" +.br +Outputs the version number and exits. +.TP +.BR "\-V\fR,\fP \-\^\-verbose" +Increases the verbosity level. If not specified, only errors are printed, with +each appearance it adds: warnings, verbose messages, debug messages (if compiled +with debug information). +.SH COMMAND LINE INPUT +.TP +.BR "\-p \fIREGEXP\fP\fR,\fP \-\^\-pattern=\fIREGEXP\fP" +.br +One or more regular expression patterns to be tested against the input +string. Supplied expressions are tested in the order they appear on the +command line. Testing is aborted when an expression does not match (or +does match, if the \fB-i\fP option is used.) +.TP +.BR "\-i\fR,\fP \-\^\-invert-match" +.br +Reverse match condition for the previous pattern. If the pattern matches, +an error is printed and evaluation is aborted. +.TP +.BR "\fISTRING\fP" +.br +Target text input to match the regular expression(s) against. The same +text is used for all regular expressions. Note that only the first +argument is used by \fByangre\fP, if it contains spaces or other shell +metacharacters they must be properly escaped. Additional arguments are +silently ignored. +.SH FILE INPUT +.TP +.BR "\-f \fIFILE\fP\fR,\fP \-\^\-file=\fIFILE\fP" +Read both patterns and target text from the specified input file. + +\fIFILE\fP must consist of one or more YANG regular expressions, each on +their own line, followed by a blank line and one line of target text. No +preprocessing is done on file input, there are no comment lines and +whitespace is not stripped. A single space character at the beginning of +a pattern line inverts the match condition for the pattern on that line. +Patterns must still be properly quoted as mandated by the YANG standard. +.SH RETURN VALUES +.TP +0 +.I Successful match +.br +The target text matched for all patterns. +.TP +1 +.I Pattern mismatch +.br +One or more patterns did not match the target text. An error message is +printed to stderr describing which pattern was the first not to match. +.TP +255 +.I Other error +.br +One or more patterns could not be processed or some other error occurred that +precluded processing. +.SH EXAMPLES +.IP \[bu] 2 +Test a single pattern: + yangre -p 'te.*xt' text_text +.IP \[bu] +Test multiple patterns: + yangre -p '.*pat1' -p 'pat2.*' -p 'notpat' -i pat2testpat1 +.IP \[bu] +Input from a file: + cat > /tmp/patterns <<EOF + .*pat1 + pat2.* + notpat + + pat2testpat1 + EOF + yangre -f /tmp/patterns + +.SH SEE ALSO +https://github.com/CESNET/libyang (libyang homepage and Git repository) +. +.SH AUTHORS +Radek Krejci <rkrejci@cesnet.cz>, Michal Vasko <mvasko@cesnet.cz> +.br +This man page was written by David Lamparter <equinox@diac24.net> +. +.SH COPYRIGHT +Copyright \(co 2015-2018 CESNET, a.l.e. |