diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 04:23:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 04:23:18 +0000 |
commit | 16527d165de55f2727f55b752b30d44a7e61e927 (patch) | |
tree | 0840ee6e0e109a9e85f0d80ff36f92587c6a5538 /tools/lint | |
parent | Releasing progress-linux version 2.1.30-2.1~progress7.99u1. (diff) | |
download | libyang2-16527d165de55f2727f55b752b30d44a7e61e927.tar.xz libyang2-16527d165de55f2727f55b752b30d44a7e61e927.zip |
Merging upstream version 2.1.148.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/lint')
32 files changed, 3693 insertions, 2482 deletions
diff --git a/tools/lint/CMakeLists.txt b/tools/lint/CMakeLists.txt index 32cdcbf..14f8b76 100644 --- a/tools/lint/CMakeLists.txt +++ b/tools/lint/CMakeLists.txt @@ -17,6 +17,12 @@ set(lintsrc cmd_load.c cmd_print.c cmd_searchpath.c + cmd_extdata.c + cmd_help.c + cmd_verb.c + cmd_debug.c + yl_opt.c + yl_schema_features.c common.c ) if(YANGLINT_INTERACTIVE) @@ -46,45 +52,3 @@ 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 index 10e7446..344900d 100644 --- a/tools/lint/cmd.c +++ b/tools/lint/cmd.c @@ -2,9 +2,10 @@ * @file cmd.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief libyang's yanglint tool general commands * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -30,230 +31,80 @@ 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) +cmd_free(void) { - 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; - } + uint16_t i; - 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; + for (i = 0; commands[i].name; i++) { + if (commands[i].free_func) { + commands[i].free_func(); } } - 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)) +int +cmd_quit_exec(struct ly_ctx **UNUSED(ctx), struct yl_opt *UNUSED(yo), const char *UNUSED(posv)) { 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); + return 0; } +/* Also keep enum COMMAND_INDEX updated. */ 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"}, + { + "help", cmd_help_opt, NULL, cmd_help_exec, NULL, cmd_help_help, NULL, + "Display commands description", "h" + }, + { + "add", cmd_add_opt, cmd_add_dep, cmd_add_exec, NULL, cmd_add_help, NULL, + "Add a new module from a specific file", "DF:hiX" + }, + { + "load", cmd_load_opt, cmd_load_dep, cmd_load_exec, NULL, cmd_load_help, NULL, + "Load a new schema from the searchdirs", "F:hiX" + }, + { + "print", cmd_print_opt, cmd_print_dep, cmd_print_exec, NULL, cmd_print_help, NULL, + "Print a module", "f:hL:o:P:q" + }, + { + "data", cmd_data_opt, cmd_data_dep, cmd_data_store, cmd_data_process, cmd_data_help, NULL, + "Load, validate and optionally print instance data", "d:ef:F:hmo:O:R:r:nt:x:" + }, + { + "list", cmd_list_opt, cmd_list_dep, cmd_list_exec, NULL, cmd_list_help, NULL, + "List all the loaded modules", "f:h" + }, + { + "feature", cmd_feature_opt, cmd_feature_dep, cmd_feature_exec, cmd_feature_fin, cmd_feature_help, NULL, + "Print all features of module(s) with their state", "haf" + }, + { + "searchpath", cmd_searchpath_opt, NULL, cmd_searchpath_exec, NULL, cmd_searchpath_help, NULL, + "Print/set the search path(s) for schemas", "ch" + }, + { + "extdata", cmd_extdata_opt, cmd_extdata_dep, cmd_extdata_exec, NULL, cmd_extdata_help, cmd_extdata_free, + "Set the specific data required by an extension", "ch" + }, + { + "clear", cmd_clear_opt, cmd_clear_dep, cmd_clear_exec, NULL, cmd_clear_help, NULL, + "Clear the context - remove all the loaded modules", "iyhY:" + }, + { + "verb", cmd_verb_opt, cmd_verb_dep, cmd_verb_exec, NULL, cmd_verb_help, NULL, + "Change verbosity", "h" + }, #ifndef NDEBUG - {"debug", cmd_debug, cmd_debug_help, "Display specific debug message groups"}, + { + "debug", cmd_debug_opt, cmd_debug_dep, cmd_debug_store, cmd_debug_setlog, cmd_debug_help, NULL, + "Display specific debug message groups", "h" + }, #endif - {"quit", cmd_quit, NULL, "Quit the program"}, + {"quit", NULL, NULL, cmd_quit_exec, NULL, NULL, NULL, "Quit the program", "h"}, /* synonyms for previous commands */ - {"?", cmd_help, NULL, "Display commands description"}, - {"exit", cmd_quit, NULL, "Quit the program"}, - {NULL, NULL, NULL, NULL} + {"?", NULL, NULL, cmd_help_exec, NULL, NULL, NULL, "Display commands description", "h"}, + {"exit", NULL, NULL, cmd_quit_exec, NULL, NULL, NULL, "Quit the program", "h"}, + {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL} }; diff --git a/tools/lint/cmd.h b/tools/lint/cmd.h index 9f6f88d..bd2f2f2 100644 --- a/tools/lint/cmd.h +++ b/tools/lint/cmd.h @@ -2,9 +2,10 @@ * @file cmd.h * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief libyang's yanglint tool commands header * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -18,15 +19,44 @@ #include "libyang.h" +struct yl_opt; + /** * @brief command information + * + * Callback functions are in the order they should be called. + * First, the 'opt_func' should be called, which parses arguments from the command line and sets flags or pointers in + * the struct yl_opt. This type of function is for interactive mode and is optional. + * Then the 'dep_func' callback can check the struct yl_opt settings. Other items that depend on them can also be + * set. There is also an possibility for controlling the number of positional arguments and its implications. + * The most important callback is 'exec_func' where the command itself is executed. This function can even replace the + * entire libyang context. The function parameters are mainly found in the yl_opt structure. Optionally, the function + * can be called with a positional argument obtained from the command line. Some 'exec_func' are adapted to be called + * from non-interactive yanglint mode. + * The 'fun_func' complements the 'exec_func'. In some cases, the command execution must be divided into two stages. + * For example, the 'exec_func' is used to fill some items in the yl_opt structure from the positional + * arguments and then the 'fin_func' is used to perform the final action. */ typedef struct { - char *name; /* User printable name of the function. */ + /* User printable name of the function. */ + char *name; - 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. */ + /* Convert command line options to the data struct yl_opt. */ + int (*opt_func)(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + /* Additionally set dependent items and perform error checking. */ + int (*dep_func)(struct yl_opt *yo, int posc); + /* Execute command. */ + int (*exec_func)(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); + /* Finish execution of command. */ + int (*fin_func)(struct ly_ctx *ctx, struct yl_opt *yo); + /* Display command help. */ + void (*help_func)(void); + /* Freeing global variables allocated by the command. */ + void (*free_func)(void); + /* Documentation for this function. */ + char *helpstring; + /* Option characters used in function getopt_long. */ + char *optstring; } COMMAND; /** @@ -34,36 +64,333 @@ typedef struct { */ extern COMMAND commands[]; +/** + * @brief Index for global variable ::commands. + */ +enum COMMAND_INDEX { + CMD_HELP = 0, + CMD_ADD, + CMD_LOAD, + CMD_PRINT, + CMD_DATA, + CMD_LIST, + CMD_FEATURE, + CMD_SEARCHPATH, + CMD_EXTDATA, + CMD_CLEAR, + CMD_VERB, +#ifndef NDEBUG + CMD_DEBUG, +#endif +}; + +/** + * @brief For each cmd, call the COMMAND.free_func in the variable 'commands'. + */ +void cmd_free(void); + /* cmd_add.c */ -void cmd_add(struct ly_ctx **ctx, const char *cmdline); + +/** + * @brief Parse the arguments of an interactive command. + * + * @param[out] yo Context for yanglint. + * @param[in] cmdline String containing command line arguments. + * @param[out] posv Pointer to argv to a section of positional arguments. + * @param[out] posc Number of positional arguments. + * @return 0 on success. + */ +int cmd_add_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @brief Check the options and set dependent items in @p yo. + * + * @param[in,out] yo context for yanglint. + * @param[in] posc number of positional arguments. + * @return 0 on success. + */ +int cmd_add_dep(struct yl_opt *yo, int posc); + +/** + * @brief Parse and compile a new module using filepath. + * + * @param[in,out] ctx Context for libyang. + * @param[in,out] yo Context for yanglint. + * @param[in] posv Path to the file where the new module is located. + * @return 0 on success. + */ +int cmd_add_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); void cmd_add_help(void); /* cmd_clear.c */ -void cmd_clear(struct ly_ctx **ctx, const char *cmdline); + +/** + * @copydoc cmd_add_opt + */ +int cmd_clear_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_clear_dep(struct yl_opt *yo, int posc); + +/** + * @brief Clear libyang context. + * + * @param[in,out] ctx context for libyang that will be replaced with an empty one. + * @param[in,out] yo context for yanglint. + * @param[in] posv not used. + * @return 0 on success. + */ +int cmd_clear_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); void cmd_clear_help(void); /* cmd_data.c */ -void cmd_data(struct ly_ctx **ctx, const char *cmdline); + +/** + * @copydoc cmd_add_opt + */ +int cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_data_dep(struct yl_opt *yo, int posc); + +/** + * @brief Store data file for later processing. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Path to the file where the data is located. + * @return 0 on success. + */ +int cmd_data_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); + +/** + * @brief Parse, validate and optionally print data instances. + * + * @param[in] ctx Context for libyang. + * @param[in] yo Context of yanglint. All necessary parameters should already be set. + * @return 0 on success. + */ +int cmd_data_process(struct ly_ctx *ctx, struct yl_opt *yo); void cmd_data_help(void); /* cmd_list.c */ -void cmd_list(struct ly_ctx **ctx, const char *cmdline); + +/** + * @copydoc cmd_add_opt + */ +int cmd_list_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_list_dep(struct yl_opt *yo, int posc); + +/** + * @brief Print the list of modules in the current context. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Not used. + * @return 0 on success. + */ +int cmd_list_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); void cmd_list_help(void); /* cmd_feature.c */ -void cmd_feature(struct ly_ctx **ctx, const char *cmdline); + +/** + * @copydoc cmd_add_opt + */ +int cmd_feature_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_feature_dep(struct yl_opt *yo, int posc); + +/** + * @brief Print the features the modules. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the module which features are to be printed. + * @return 0 on success. + */ +int cmd_feature_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); + +/** + * @brief Printing of features ends. + * + * @param[in] ctx context for libyang. Not used. + * @param[in] yo context for yanglint. + * @return 0 on success. + */ +int cmd_feature_fin(struct ly_ctx *ctx, struct yl_opt *yo); void cmd_feature_help(void); /* cmd_load.c */ -void cmd_load(struct ly_ctx **ctx, const char *cmdline); + +/** + * @copydoc cmd_add_opt + */ +int cmd_load_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_load_dep(struct yl_opt *yo, int posc); + +/** + * @brief Parse and compile a new module using module name. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the module to be loaded into the context. + * @return 0 on success. + */ +int cmd_load_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); void cmd_load_help(void); /* cmd_print.c */ -void cmd_print(struct ly_ctx **ctx, const char *cmdline); + +/** + * @copydoc cmd_add_opt + */ +int cmd_print_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_print_dep(struct yl_opt *yo, int posc); + +/** + * @brief Print a schema module. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the module to be printed. Can be NULL in the case of printing a node. + * @return 0 on success. + */ +int cmd_print_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); void cmd_print_help(void); /* cmd_searchpath.c */ -void cmd_searchpath(struct ly_ctx **ctx, const char *cmdline); + +/** + * @copydoc cmd_add_opt + */ +int cmd_searchpath_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @brief Set the paths of directories where to search schema modules. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Path to the directory. Can be NULL in the case of printing a current searchdirs. + * @return 0 on success. + */ +int cmd_searchpath_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); void cmd_searchpath_help(void); +/* cmd_extdata.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_extdata_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_extdata_dep(struct yl_opt *yo, int posc); + +/** + * @brief Set path to the file required by the extension. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Path to the directory. Can be NULL in the case of printing a current path. + * @return 0 on success. + */ +int cmd_extdata_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_extdata_help(void); +void cmd_extdata_free(void); + +/* cmd_help.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_help_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @brief Print help. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the command which help message is to be printed. Can be NULL. + * @return 0 on success. + */ +int cmd_help_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_help_help(void); + +/* cmd_verb.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_verb_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_verb_dep(struct yl_opt *yo, int posc); + +/** + * @brief Set the verbose level. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the verbose level to be set. + * @return 0 on success. + */ +int cmd_verb_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_verb_help(void); + +/* cmd_debug.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_debug_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_debug_dep(struct yl_opt *yo, int posc); + +/** + * @brief Store the type of debug messages for later processing. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the debug type to be set. + * @return 0 on success. + */ +int cmd_debug_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); + +/** + * @brief Set debug logging. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. All necessary parameters should already be set. + * @return 0 on success. + */ +int cmd_debug_setlog(struct ly_ctx *ctx, struct yl_opt *yo); +void cmd_debug_help(void); + #endif /* COMMANDS_H_ */ diff --git a/tools/lint/cmd_add.c b/tools/lint/cmd_add.c index bbfdfd6..9f10711 100644 --- a/tools/lint/cmd_add.c +++ b/tools/lint/cmd_add.c @@ -2,9 +2,10 @@ * @file cmd_add.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'add' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -17,8 +18,8 @@ #include "cmd.h" +#include <assert.h> #include <getopt.h> -#include <libgen.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -26,6 +27,8 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" +#include "yl_schema_features.h" void cmd_add_help(void) @@ -44,133 +47,164 @@ cmd_add_help(void) " -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"); + " all the modules are set implemented.\n" + " -X, --extended-leafref\n" + " Allow usage of deref() XPath function within leafref.\n"); } -void -cmd_add(struct ly_ctx **ctx, const char *cmdline) +int +cmd_add_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; + int rc = 0, argc = 0; int opt, opt_index; struct option options[] = { {"disable-searchdir", no_argument, NULL, 'D'}, {"features", required_argument, NULL, 'F'}, {"help", no_argument, NULL, 'h'}, {"make-implemented", no_argument, NULL, 'i'}, + {"extended-leafref", no_argument, NULL, 'X'}, {NULL, 0, NULL, 0} }; - uint16_t options_ctx = 0; - const char *all_features[] = {"*", NULL}; - struct ly_set fset = {0}; - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "D:F:hi", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_ADD].optstring, 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; + if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) { + YLMSG_W("The -D option specified too many times."); } + yo_opt_update_disable_searchdir(yo); break; case 'F': /* --features */ - if (parse_features(optarg, &fset)) { - goto cleanup; + if (parse_features(optarg, &yo->schema_features)) { + return 1; } break; case 'h': cmd_add_help(); - goto cleanup; + return 1; 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; - } + yo_opt_update_make_implemented(yo); + break; + + case 'X': /* --extended-leafref */ + yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED; break; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; } } - if (argc == optind) { + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_add_dep(struct yl_opt *yo, int posc) +{ + if (yo->interactive && !posc) { /* no argument */ cmd_add_help(); - goto cleanup; + return 1; } - - if (!fset.count) { + if (!yo->schema_features.count) { /* no features, enable all of them */ - options_ctx |= LY_CTX_ENABLE_IMP_FEATURES; + yo->ctx_options |= LY_CTX_ENABLE_IMP_FEATURES; } - if (options_ctx) { - ly_ctx_set_options(*ctx, options_ctx); - } + return 0; +} - 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; +static int +store_parsed_module(const char *filepath, struct lys_module *mod, struct yl_opt *yo) +{ + assert(!yo->interactive); - if (parse_schema_path(argv[optind + i], &dir, &module)) { - goto cleanup; + if (yo->schema_out_format || yo->feature_param_format) { + if (ly_set_add(&yo->schema_modules, (void *)mod, 1, NULL)) { + YLMSG_E("Storing parsed schema module (%s) for print failed.", filepath); + return 1; } + } - /* add temporarily also the path of the module itself */ - if (ly_ctx_set_searchdir(*ctx, dirname(dir)) == LY_EEXIST) { - path_unset = 0; - } + return 0; +} - /* get features list for this module */ - if (!fset.count) { - features = all_features; - } else { - get_features(&fset, module, &features); - } +int +cmd_add_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + const char *all_features[] = {"*", NULL}; + LY_ERR ret; + uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */ + char *dir, *modname = NULL; + const char **features = NULL; + struct ly_in *in = NULL; + struct lys_module *mod; + + assert(posv); - /* temporary cleanup */ - free(dir); - free(module); + if (yo->ctx_options) { + ly_ctx_set_options(*ctx, yo->ctx_options); + } + + if (parse_schema_path(posv, &dir, &modname)) { + return 1; + } - /* prepare input handler */ - ret = ly_in_new_filepath(argv[optind + i], 0, &in); - if (ret) { + if (!yo->interactive) { + /* The module should already be parsed if yang_lib_file was set. */ + if (yo->yang_lib_file && (mod = ly_ctx_get_module_implemented(*ctx, modname))) { + ret = store_parsed_module(posv, mod, yo); goto cleanup; } + /* parse module */ + } + + /* add temporarily also the path of the module itself */ + if (ly_ctx_set_searchdir(*ctx, dir) == LY_EEXIST) { + path_unset = 0; + } + + /* get features list for this module */ + if (!yo->schema_features.count) { + features = all_features; + } else { + get_features(&yo->schema_features, modname, &features); + } + + /* prepare input handler */ + ret = ly_in_new_filepath(posv, 0, &in); + if (ret) { + goto cleanup; + } - /* parse the file */ - ret = lys_parse(*ctx, in, LYS_IN_UNKNOWN, features, NULL); - ly_in_free(in, 1); - ly_ctx_unset_searchdir_last(*ctx, path_unset); + /* parse the file */ + ret = lys_parse(*ctx, in, LYS_IN_UNKNOWN, features, &mod); + ly_in_free(in, 1); + ly_ctx_unset_searchdir_last(*ctx, path_unset); + if (ret) { + goto cleanup; + } - if (ret) { - /* libyang printed the error messages */ + if (!yo->interactive) { + if ((ret = store_parsed_module(posv, mod, yo))) { goto cleanup; } } cleanup: - if (options_ctx) { - ly_ctx_unset_options(*ctx, options_ctx); - } - ly_set_erase(&fset, free_features); - free_cmdline(argv); + free(dir); + free(modname); + + return ret; } diff --git a/tools/lint/cmd_clear.c b/tools/lint/cmd_clear.c index 5eed6ff..4a869af 100644 --- a/tools/lint/cmd_clear.c +++ b/tools/lint/cmd_clear.c @@ -2,9 +2,10 @@ * @file cmd_clear.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'clear' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -24,6 +25,7 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" void cmd_clear_help(void) @@ -32,68 +34,139 @@ cmd_clear_help(void) " 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" + " When loading a module into the context, the imported 'referenced'\n" + " modules will also be implemented. If specified a second time,\n" + " all the modules will be implemented.\n" " -y, --yang-library\n" " Load and implement internal \"ietf-yang-library\" YANG module.\n" " Note that this module includes definitions of mandatory state\n" - " data that can result in unexpected data validation errors.\n"); -#if 0 - " If <yang-library-data> path specified, load the modules\n" - " according to the provided yang library data.\n" -#endif + " data that can result in unexpected data validation errors.\n" + " -Y FILE, --yang-library-file=FILE\n" + " Parse FILE with \"ietf-yang-library\" data and use them to\n" + " create an exact YANG schema context. Searchpaths defined so far\n" + " are used, but then deleted.\n"); } -void -cmd_clear(struct ly_ctx **ctx, const char *cmdline) +int +cmd_clear_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; + int rc = 0, argc = 0; int opt, opt_index; struct option options[] = { - {"make-implemented", no_argument, NULL, 'i'}, - {"yang-library", no_argument, NULL, 'y'}, + {"make-implemented", no_argument, NULL, 'i'}, + {"yang-library", no_argument, NULL, 'y'}, + {"yang-library-file", no_argument, NULL, 'Y'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; - uint16_t options_ctx = LY_CTX_NO_YANGLIBRARY; - struct ly_ctx *ctx_new; - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; + yo->ctx_options = LY_CTX_NO_YANGLIBRARY; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "iyh", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_CLEAR].optstring, 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; + if (yo->ctx_options & LY_CTX_REF_IMPLEMENTED) { + yo->ctx_options &= ~LY_CTX_REF_IMPLEMENTED; + yo->ctx_options |= LY_CTX_ALL_IMPLEMENTED; } else { - options_ctx |= LY_CTX_REF_IMPLEMENTED; + yo->ctx_options |= LY_CTX_REF_IMPLEMENTED; } break; case 'y': - options_ctx &= ~LY_CTX_NO_YANGLIBRARY; + yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; + break; + case 'Y': + yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; + yo->yang_lib_file = optarg; + if (!yo->yang_lib_file) { + YLMSG_E("Memory allocation error."); + return 1; + } break; case 'h': cmd_clear_help(); - goto cleanup; + return 1; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return rc; +} + +int +cmd_clear_dep(struct yl_opt *yo, int posc) +{ + (void) yo; + + if (posc) { + YLMSG_E("No positional arguments are allowed."); + return 1; + } + + return 0; +} + +/** + * @brief Convert searchpaths into single string. + * + * @param[in] ctx Context with searchpaths. + * @param[out] searchpaths Collection of paths in the single string. Paths are delimited by colon ":" + * (on Windows, used semicolon ";" instead). + * @return LY_ERR value. + */ +static LY_ERR +searchpaths_to_str(const struct ly_ctx *ctx, char **searchpaths) +{ + uint32_t i; + int rc = 0; + const char * const *dirs = ly_ctx_get_searchdirs(ctx); + + for (i = 0; dirs[i]; ++i) { + rc = searchpath_strcat(searchpaths, dirs[i]); + if (!rc) { + break; } } - if (ly_ctx_new(NULL, options_ctx, &ctx_new)) { - YLMSG_W("Failed to create context.\n"); - goto cleanup; + return rc; +} + +int +cmd_clear_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) posv; + struct ly_ctx *ctx_new = NULL; + + if (yo->yang_lib_file) { + if (searchpaths_to_str(*ctx, &yo->searchpaths)) { + YLMSG_E("Storing searchpaths failed."); + return 1; + } + if (ly_ctx_new_ylpath(yo->searchpaths, yo->yang_lib_file, LYD_UNKNOWN, yo->ctx_options, &ctx_new)) { + YLMSG_E("Unable to create libyang context with yang-library data."); + return 1; + } + } else { + if (ly_ctx_new(NULL, yo->ctx_options, &ctx_new)) { + YLMSG_W("Failed to create context."); + return 1; + } } + /* Global variables in commands are also deleted. */ + cmd_free(); + ly_ctx_destroy(*ctx); *ctx = ctx_new; -cleanup: - free_cmdline(argv); + return 0; } diff --git a/tools/lint/cmd_data.c b/tools/lint/cmd_data.c index 25449f5..44fb237 100644 --- a/tools/lint/cmd_data.c +++ b/tools/lint/cmd_data.c @@ -2,9 +2,10 @@ * @file cmd_data.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'data' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -17,6 +18,7 @@ #include "cmd.h" +#include <assert.h> #include <errno.h> #include <getopt.h> #include <stdint.h> @@ -27,18 +29,23 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" -void -cmd_data_help(void) +static void +cmd_data_help_header(void) { printf("Usage: data [-emn] [-t TYPE]\n" " [-F FORMAT] [-f FORMAT] [-d DEFAULTS] [-o OUTFILE] <data1> ...\n" " data [-n] -t (rpc | notif | reply) [-O FILE]\n" " [-F FORMAT] [-f FORMAT] [-d DEFAULTS] [-o OUTFILE] <data1> ...\n" " data [-en] [-t TYPE] [-F FORMAT] -x XPATH [-o OUTFILE] <data1> ...\n" - " Parse, validate and optionally print data instances\n\n" + " Parse, validate and optionally print data instances\n"); +} - " -t TYPE, --type=TYPE\n" +static void +cmd_data_help_type(void) +{ + printf(" -t TYPE, --type=TYPE\n" " Specify data tree type in the input data file(s):\n" " data - Complete datastore with status data (default type).\n" " config - Configuration datastore (without status data).\n" @@ -47,39 +54,45 @@ cmd_data_help(void) " edit - Content of the NETCONF <edit-config> operation.\n" " rpc - Content of the NETCONF <rpc> message, defined as YANG's\n" " RPC/Action input statement.\n" + " nc-rpc - Similar to 'rpc' but expect and check also the NETCONF\n" + " envelopes <rpc> or <action>.\n" " reply - Reply to the RPC/Action. Note that the reply data are\n" " expected inside a container representing the original\n" " RPC/Action. This is necessary to identify appropriate\n" " data definitions in the schema module.\n" + " nc-reply - Similar to 'reply' but expect and check also the NETCONF\n" + " envelope <rpc-reply> with output data nodes as direct\n" + " descendants. The original RPC/action invocation is expected\n" + " in a separate parameter '-R' and is parsed as 'nc-rpc'.\n" " notif - Notification instance (content of the <notification>\n" - " element without <eventTime>).\n\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" + " element without <eventTime>).\n" + " nc-notif - Similar to 'notif' but expect and check also the NETCONF\n" + " envelope <notification> with element <eventTime> and its\n" + " sibling as the actual notification.\n"); +} - " -f FORMAT, --format=FORMAT\n" +static void +cmd_data_help_format(void) +{ + printf(" -f FORMAT, --format=FORMAT\n" " Print the data in one of the following formats:\n" " xml, json, lyb\n" - " Note that the LYB format requires the -o option specified.\n" - " -F FORMAT, --in-format=FORMAT\n" + " Note that the LYB format requires the -o option specified.\n"); +} + +static void +cmd_data_help_in_format(void) +{ + printf(" -F FORMAT, --in-format=FORMAT\n" " Load the data in one of the following formats:\n" " xml, json, lyb\n" - " If input format not specified, it is detected from the file extension.\n" - " -d MODE, --default=MODE\n" + " If input format not specified, it is detected from the file extension.\n"); +} + +static void +cmd_data_help_default(void) +{ + printf(" -d MODE, --default=MODE\n" " Print data with default values, according to the MODE\n" " (to print attributes, ietf-netconf-with-defaults model\n" " must be loaded):\n" @@ -87,24 +100,58 @@ cmd_data_help(void) " 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" + " implicit-tagged - Add missing nodes and mark them with the attribute.\n"); +} - " -x XPATH, --xpath=XPATH\n" - " Evaluate XPATH expression and print the nodes satysfying the.\n" +static void +cmd_data_help_xpath(void) +{ + printf(" -x XPATH, --xpath=XPATH\n" + " Evaluate XPATH expression and print the nodes satisfying the\n" " expression. The output format is specific and the option cannot\n" " be combined with the -f and -d options. Also all the data\n" " inputs are merged into a single data tree where the expression\n" - " is evaluated, so the -m option is always set implicitly.\n\n"); - + " is evaluated, so the -m option is always set implicitly.\n"); } void -cmd_data(struct ly_ctx **ctx, const char *cmdline) +cmd_data_help(void) +{ + cmd_data_help_header(); + printf("\n"); + cmd_data_help_type(); + printf(" -e, --present Validate only with the schema modules whose data actually\n" + " exist in the provided input data files. Takes effect only\n" + " with the 'data' or 'config' TYPEs. Used to avoid requiring\n" + " mandatory nodes from modules which data are not present in the\n" + " provided input data files.\n" + " -m, --merge Merge input data files into a single tree and validate at\n" + " once.The option has effect only for 'data' and 'config' TYPEs.\n" + " In case of using -x option, the data are always merged.\n" + " -n, --not-strict\n" + " Do not require strict data parsing (silently skip unknown data),\n" + " has no effect for schemas.\n" + " -O FILE, --operational=FILE\n" + " Provide optional data to extend validation of the 'rpc',\n" + " 'reply' or 'notif' TYPEs. The FILE is supposed to contain\n" + " the operational datastore referenced from the operation.\n" + " In case of a nested notification or action, its parent\n" + " existence is also checked in these operational data.\n" + " -R FILE, --reply-rpc=FILE\n" + " Provide source RPC for parsing of the 'nc-reply' TYPE. The FILE\n" + " is supposed to contain the source 'nc-rpc' operation of the reply.\n"); + cmd_data_help_format(); + cmd_data_help_in_format(); + printf(" -o OUTFILE, --output=OUTFILE\n" + " Write the output to OUTFILE instead of stdout.\n"); + cmd_data_help_xpath(); + printf("\n"); +} + +int +cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; + int rc = 0, argc = 0; int opt, opt_index; struct option options[] = { {"defaults", required_argument, NULL, 'd'}, @@ -115,214 +162,525 @@ cmd_data(struct ly_ctx **ctx, const char *cmdline) {"merge", no_argument, NULL, 'm'}, {"output", required_argument, NULL, 'o'}, {"operational", required_argument, NULL, 'O'}, + {"reply-rpc", required_argument, NULL, 'R'}, {"not-strict", no_argument, NULL, 'n'}, {"type", required_argument, NULL, 't'}, {"xpath", required_argument, NULL, 'x'}, {NULL, 0, NULL, 0} }; - uint8_t data_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; + + yo->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS; + yo->data_validate_options = YL_DEFAULT_DATA_VALIDATE_OPTIONS; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "d:ef:F:hmo:O:r:nt:x:", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_DATA].optstring, 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; + if (yo_opt_update_data_default(optarg, yo)) { + YLMSG_E("Unknown default mode %s.", optarg); + cmd_data_help_default(); + return 1; } break; case 'f': /* --format */ - if (!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; + if (yl_opt_update_data_out_format(optarg, yo)) { + cmd_data_help_format(); + return 1; } 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; + if (yo_opt_update_data_in_format(optarg, yo)) { + YLMSG_E("Unknown input format %s.", optarg); + cmd_data_help_in_format(); + return 1; } break; case 'o': /* --output */ - if (out) { - YLMSG_E("Only a single output can be specified.\n"); - goto cleanup; + if (yo->out) { + YLMSG_E("Only a single output can be specified."); + return 1; } else { - if (ly_out_new_filepath(optarg, &out)) { - YLMSG_E("Unable open output file %s (%s)\n", optarg, strerror(errno)); - goto cleanup; + if (ly_out_new_filepath(optarg, &yo->out)) { + YLMSG_E("Unable open output file %s (%s).", optarg, strerror(errno)); + return 1; } } break; - case 'O': { /* --operational */ - struct ly_in *in; - LYD_FORMAT f; - - if (operational) { - YLMSG_E("The operational datastore (-O) cannot be set multiple times.\n"); - goto cleanup; + case 'O': /* --operational */ + if (yo->data_operational.path) { + YLMSG_E("The operational datastore (-O) cannot be set multiple times."); + return 1; } - if (get_input(optarg, NULL, &f, &in)) { - goto cleanup; + yo->data_operational.path = optarg; + break; + case 'R': /* --reply-rpc */ + if (yo->reply_rpc.path) { + YLMSG_E("The PRC of the reply (-R) cannot be set multiple times."); + return 1; } - operational = fill_cmdline_file(NULL, in, optarg, f); + yo->reply_rpc.path = optarg; break; - } /* case 'O' */ - case 'e': /* --present */ - options_validate |= LYD_VALIDATE_PRESENT; + yo->data_validate_options |= LYD_VALIDATE_PRESENT; break; case 'm': /* --merge */ - data_merge = 1; + yo->data_merge = 1; break; case 'n': /* --not-strict */ - options_parse &= ~LYD_PARSE_STRICT; + yo->data_parse_options &= ~LYD_PARSE_STRICT; break; case 't': /* --type */ if (data_type_set) { - YLMSG_E("The data type (-t) cannot be set multiple times.\n"); - goto cleanup; + YLMSG_E("The data type (-t) cannot be set multiple times."); + return 1; } - 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; + if (yl_opt_update_data_type(optarg, yo)) { + YLMSG_E("Unknown data tree type %s.", optarg); + cmd_data_help_type(); + return 1; } data_type_set = 1; break; case 'x': /* --xpath */ - if (ly_set_add(&xpaths, optarg, 0, NULL)) { - YLMSG_E("Storing XPath \"%s\" failed.\n", optarg); - goto cleanup; + if (ly_set_add(&yo->data_xpath, optarg, 0, NULL)) { + YLMSG_E("Storing XPath \"%s\" failed.", optarg); + return 1; } break; case 'h': /* --help */ cmd_data_help(); - goto cleanup; + return 1; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; } } - if (optind == argc) { - YLMSG_E("Missing the data file to process.\n"); - goto cleanup; + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return rc; +} + +int +cmd_data_dep(struct yl_opt *yo, int posc) +{ + if (yo->interactive && !posc) { + YLMSG_E("Missing the data file to process."); + return 1; } - if (data_merge) { - if (data_type || (options_parse & LYD_PARSE_ONLY)) { + if (yo->data_merge) { + if (yo->data_type || (yo->data_parse_options & LYD_PARSE_ONLY)) { /* switch off the option, incompatible input data type */ - data_merge = 0; + YLMSG_W("The --merge option has effect only for 'data' and 'config' TYPEs."); + yo->data_merge = 0; } else { /* postpone validation after the merge of all the input data */ - options_parse |= LYD_PARSE_ONLY; + yo->data_parse_options |= LYD_PARSE_ONLY; } - } else if (xpaths.count) { - data_merge = 1; + } else if (yo->data_xpath.count) { + yo->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 (yo->data_xpath.count && (yo->schema_out_format || yo->data_out_format)) { + YLMSG_E("The --format option cannot be combined with --xpath option."); + if (yo->interactive) { + cmd_data_help_xpath(); + } + return 1; + } + if (yo->data_xpath.count && (yo->data_print_options & LYD_PRINT_WD_MASK)) { + YLMSG_E("The --default option cannot be combined with --xpath option."); + if (yo->interactive) { + cmd_data_help_xpath(); + } + return 1; + } + + if (yo->data_operational.path && !yo->data_type) { + YLMSG_W("Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types."); + yo->data_operational.path = NULL; + } + + if (yo->reply_rpc.path && (yo->data_type != LYD_TYPE_REPLY_NETCONF)) { + YLMSG_W("Source RPC is needed only for NETCONF Reply input data type."); + yo->data_operational.path = NULL; + } else if (!yo->reply_rpc.path && (yo->data_type == LYD_TYPE_REPLY_NETCONF)) { + YLMSG_E("Missing source RPC (-R) for NETCONF Reply input data type."); + return 1; + } + + if (!yo->out && (yo->data_out_format == LYD_LYB)) { + YLMSG_E("The LYB format requires the -o option specified."); + return 1; + } + + /* default output stream */ + if (!yo->out) { + if (ly_out_new_file(stdout, &yo->out)) { + YLMSG_E("Unable to set stdout as output."); + return 1; + } + yo->out_stdout = 1; } - 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 the operational and/or reply RPC content if any */ + if (yo->data_operational.path) { + if (get_input(yo->data_operational.path, NULL, &yo->data_operational.format, &yo->data_operational.in)) { + return -1; + } + } + if (yo->reply_rpc.path) { + if (get_input(yo->reply_rpc.path, NULL, &yo->reply_rpc.format, &yo->reply_rpc.in)) { + return -1; + } } + return 0; +} + +int +cmd_data_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) ctx; + struct ly_in *in; + LYD_FORMAT format_data; + + assert(posv); + + format_data = yo->data_in_format; + /* process input data files provided as standalone command line arguments */ - for (int i = 0; i < argc - optind; i++) { - struct ly_in *in; + if (get_input(posv, NULL, &format_data, &in)) { + return 1; + } + + if (!fill_cmdline_file(&yo->data_inputs, in, posv, format_data)) { + ly_in_free(in, 1); + return 1; + } + + return 0; +} + +/** + * @brief Evaluate xpath adn print result. + * + * @param[in] tree Data tree. + * @param[in] xpath Xpath to evaluate. + * @return 0 on success. + */ +static int +evaluate_xpath(const struct lyd_node *tree, const char *xpath) +{ + struct ly_set *set = NULL; + + if (lyd_find_xpath(tree, xpath, &set)) { + return -1; + } + + /* print result */ + printf("XPath \"%s\" evaluation result:\n", xpath); + if (!set->count) { + printf("\tEmpty\n"); + } else { + for (uint32_t u = 0; u < set->count; ++u) { + struct lyd_node *node = (struct lyd_node *)set->objs[u]; + + printf(" %s \"%s\"", lys_nodetype2str(node->schema->nodetype), node->schema->name); + if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { + printf(" (value: \"%s\")\n", lyd_get_value(node)); + } else if (node->schema->nodetype == LYS_LIST) { + printf(" ("); + for (struct lyd_node *key = ((struct lyd_node_inner *)node)->child; key && lysc_is_key(key->schema); key = key->next) { + printf("%s\"%s\": \"%s\";", (key != ((struct lyd_node_inner *)node)->child) ? " " : "", + key->schema->name, lyd_get_value(key)); + } + printf(")\n"); + } else { + printf("\n"); + } + } + } + + ly_set_free(set, NULL); + return 0; +} + +/** + * @brief Checking that a parent data node exists in the datastore for the nested-notification and action. + * + * @param[in] op Operation to check. + * @param[in] oper_tree Data from datastore. + * @param[in] operational_f Operational datastore file information. + * @return LY_ERR value. + */ +static LY_ERR +check_operation_parent(struct lyd_node *op, struct lyd_node *oper_tree, struct cmdline_file *operational_f) +{ + LY_ERR ret; + struct ly_set *set = NULL; + char *path = NULL; + + if (!op || !lyd_parent(op)) { + /* The function is defined only for nested-notification and action. */ + return LY_SUCCESS; + } + + if (!operational_f || (operational_f && !operational_f->in)) { + YLMSG_E("The --operational parameter needed to validate operation \"%s\" is missing.", LYD_NAME(op)); + ret = LY_EVALID; + goto cleanup; + } + + path = lyd_path(lyd_parent(op), LYD_PATH_STD, NULL, 0); + if (!path) { + ret = LY_EMEM; + goto cleanup; + } + + if (!oper_tree) { + YLMSG_W("Operational datastore is empty or contains unknown data."); + YLMSG_E("Operation \"%s\" parent \"%s\" not found in the operational data.", LYD_NAME(op), path); + ret = LY_EVALID; + goto cleanup; + } + if ((ret = lyd_find_xpath(oper_tree, path, &set))) { + goto cleanup; + } + if (!set->count) { + YLMSG_E("Operation \"%s\" parent \"%s\" not found in the operational data.", LYD_NAME(op), path); + ret = LY_EVALID; + goto cleanup; + } + +cleanup: + ly_set_free(set, NULL); + free(path); + + return ret; +} + +/** + * @brief Process the input data files - parse, validate and print according to provided options. + * + * @param[in] ctx libyang context with schema. + * @param[in] type The type of data in the input files. + * @param[in] merge Flag if the data should be merged before validation. + * @param[in] out_format Data format for printing. + * @param[in] out The output handler for printing. + * @param[in] parse_options Parser options. + * @param[in] validate_options Validation options. + * @param[in] print_options Printer options. + * @param[in] operational Optional operational datastore file information for the case of an extended validation of + * operation(s). + * @param[in] reply_rpc Source RPC operation file information for parsing NETCONF rpc-reply. + * @param[in] inputs Set of file informations of input data files. + * @param[in] xpaths The set of XPaths to be evaluated on the processed data tree, basic information about the resulting set + * is printed. Alternative to data printing. + * @return LY_ERR value. + */ +static LY_ERR +process_data(struct ly_ctx *ctx, enum lyd_type type, uint8_t merge, LYD_FORMAT out_format, + struct ly_out *out, uint32_t parse_options, uint32_t validate_options, uint32_t print_options, + struct cmdline_file *operational, struct cmdline_file *reply_rpc, struct ly_set *inputs, + struct ly_set *xpaths) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *tree = NULL, *op = NULL, *envp = NULL, *merged_tree = NULL, *oper_tree = NULL; + const char *xpath; + struct ly_set *set = NULL; - if (get_input(argv[optind + i], NULL, &informat, &in)) { + /* additional operational datastore */ + if (operational && operational->in) { + ret = lyd_parse_data(ctx, NULL, operational->in, operational->format, LYD_PARSE_ONLY, 0, &oper_tree); + if (ret) { + YLMSG_E("Failed to parse operational datastore file \"%s\".", operational->path); goto cleanup; } + } + + for (uint32_t u = 0; u < inputs->count; ++u) { + struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u]; + + switch (type) { + case LYD_TYPE_DATA_YANG: + ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, parse_options, validate_options, &tree); + break; + case LYD_TYPE_RPC_YANG: + case LYD_TYPE_REPLY_YANG: + case LYD_TYPE_NOTIF_YANG: + ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, type, &tree, &op); + break; + case LYD_TYPE_RPC_NETCONF: + case LYD_TYPE_NOTIF_NETCONF: + ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, type, &envp, &op); + + /* adjust pointers */ + for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {} + break; + case LYD_TYPE_REPLY_NETCONF: + /* parse source RPC operation */ + assert(reply_rpc && reply_rpc->in); + ret = lyd_parse_op(ctx, NULL, reply_rpc->in, reply_rpc->format, LYD_TYPE_RPC_NETCONF, &envp, &op); + if (ret) { + YLMSG_E("Failed to parse source NETCONF RPC operation file \"%s\".", reply_rpc->path); + goto cleanup; + } + + /* adjust pointers */ + for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {} + + /* free input */ + lyd_free_siblings(lyd_child(op)); + + /* we do not care */ + lyd_free_all(envp); + envp = NULL; - if (!fill_cmdline_file(&inputs, in, argv[optind + i], informat)) { - ly_in_free(in, 1); + ret = lyd_parse_op(ctx, op, input_f->in, input_f->format, type, &envp, NULL); + break; + default: + YLMSG_E("Internal error (%s:%d).", __FILE__, __LINE__); + goto cleanup; + } + + if (ret) { + YLMSG_E("Failed to parse input data file \"%s\".", input_f->path); goto cleanup; } + + if (merge) { + /* merge the data so far parsed for later validation and print */ + if (!merged_tree) { + merged_tree = tree; + } else { + ret = lyd_merge_siblings(&merged_tree, tree, LYD_MERGE_DESTRUCT); + if (ret) { + YLMSG_E("Merging %s with previous data failed.", input_f->path); + goto cleanup; + } + } + tree = NULL; + } else if (out_format) { + /* print */ + switch (type) { + case LYD_TYPE_DATA_YANG: + lyd_print_all(out, tree, out_format, print_options); + break; + case LYD_TYPE_RPC_YANG: + case LYD_TYPE_REPLY_YANG: + case LYD_TYPE_NOTIF_YANG: + case LYD_TYPE_RPC_NETCONF: + case LYD_TYPE_NOTIF_NETCONF: + lyd_print_tree(out, tree, out_format, print_options); + break; + case LYD_TYPE_REPLY_NETCONF: + /* just the output */ + lyd_print_tree(out, lyd_child(tree), out_format, print_options); + break; + default: + assert(0); + } + } else { + /* validation of the RPC/Action/reply/Notification with the operational datastore, if any */ + switch (type) { + case LYD_TYPE_DATA_YANG: + /* already validated */ + break; + case LYD_TYPE_RPC_YANG: + case LYD_TYPE_RPC_NETCONF: + ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_RPC_YANG, NULL); + break; + case LYD_TYPE_REPLY_YANG: + case LYD_TYPE_REPLY_NETCONF: + ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_REPLY_YANG, NULL); + break; + case LYD_TYPE_NOTIF_YANG: + case LYD_TYPE_NOTIF_NETCONF: + ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_NOTIF_YANG, NULL); + break; + default: + assert(0); + } + if (ret) { + if (operational->path) { + YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".", + input_f->path, operational->path); + } else { + YLMSG_E("Failed to validate input data file \"%s\".", input_f->path); + } + goto cleanup; + } + + if ((ret = check_operation_parent(op, oper_tree, operational))) { + goto cleanup; + } + } + + /* next iter */ + lyd_free_all(tree); + tree = NULL; + lyd_free_all(envp); + envp = NULL; } - /* default output stream */ - if (!out) { - if (ly_out_new_file(stdout, &out)) { - YLMSG_E("Unable to set stdout as output.\n"); + if (merge) { + /* validate the merged result */ + ret = lyd_validate_all(&merged_tree, ctx, validate_options, NULL); + if (ret) { + YLMSG_E("Merged data are not valid."); goto cleanup; } + + if (out_format) { + /* and print it */ + lyd_print_all(out, merged_tree, out_format, print_options); + } + + for (uint32_t u = 0; xpaths && (u < xpaths->count); ++u) { + xpath = (const char *)xpaths->objs[u]; + ly_set_free(set, NULL); + ret = lys_find_xpath(ctx, NULL, xpath, LYS_FIND_NO_MATCH_ERROR, &set); + if (ret || !set->count) { + ret = (ret == LY_SUCCESS) ? LY_EINVAL : ret; + YLMSG_E("The requested xpath failed."); + goto cleanup; + } + if (evaluate_xpath(merged_tree, xpath)) { + goto cleanup; + } + } } +cleanup: + lyd_free_all(tree); + lyd_free_all(envp); + lyd_free_all(merged_tree); + lyd_free_all(oper_tree); + ly_set_free(set, NULL); + return ret; +} + +int +cmd_data_process(struct ly_ctx *ctx, struct yl_opt *yo) +{ /* parse, validate and print data */ - if (process_data(*ctx, data_type, data_merge, outformat, out, options_parse, options_validate, options_print, - operational, NULL, &inputs, &xpaths)) { - goto cleanup; + if (process_data(ctx, yo->data_type, yo->data_merge, yo->data_out_format, yo->out, yo->data_parse_options, + yo->data_validate_options, yo->data_print_options, &yo->data_operational, &yo->reply_rpc, + &yo->data_inputs, &yo->data_xpath)) { + return 1; } -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); + return 0; } diff --git a/tools/lint/cmd_debug.c b/tools/lint/cmd_debug.c new file mode 100644 index 0000000..3661bfa --- /dev/null +++ b/tools/lint/cmd_debug.c @@ -0,0 +1,134 @@ +/** + * @file cmd_debug.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'verb' command of the libyang's yanglint tool. + * + * Copyright (c) 2023-2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef NDEBUG + +#include "cmd.h" + +#include <assert.h> +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <strings.h> + +#include "libyang.h" + +#include "common.h" +#include "yl_opt.h" + +struct debug_groups { + char *name; + uint32_t flag; +} const dg [] = { + {"dict", LY_LDGDICT}, + {"xpath", LY_LDGXPATH}, + {"dep-sets", LY_LDGDEPSETS}, +}; +#define DG_LENGTH (sizeof dg / sizeof *dg) + +void +cmd_debug_help(void) +{ + uint32_t i; + + printf("Usage: debug ("); + for (i = 0; i < DG_LENGTH; i++) { + if ((i + 1) == DG_LENGTH) { + printf("%s", dg[i].name); + } else { + printf("%s | ", dg[i].name); + } + } + printf(")+\n"); +} + +int +cmd_debug_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_DEBUG].optstring, options, &opt_index)) != -1) { + switch (opt) { + case 'h': + cmd_debug_help(); + return 1; + default: + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_debug_dep(struct yl_opt *yo, int posc) +{ + (void) yo; + + if (yo->interactive && !posc) { + /* no argument */ + cmd_debug_help(); + return 1; + } + + return 0; +} + +int +cmd_debug_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) ctx; + uint32_t i; + ly_bool set; + + assert(posv); + + set = 0; + for (i = 0; i < DG_LENGTH; i++) { + if (!strcasecmp(posv, dg[i].name)) { + yo->dbg_groups |= dg[i].flag; + set = 1; + break; + } + } + + if (!set) { + YLMSG_E("Unknown debug group \"%s\".", posv); + return 1; + } + + return 0; +} + +int +cmd_debug_setlog(struct ly_ctx *ctx, struct yl_opt *yo) +{ + (void) ctx; + return ly_log_dbg_groups(yo->dbg_groups); +} + +#endif diff --git a/tools/lint/cmd_extdata.c b/tools/lint/cmd_extdata.c new file mode 100644 index 0000000..fc7ac7b --- /dev/null +++ b/tools/lint/cmd_extdata.c @@ -0,0 +1,115 @@ +/** + * @file cmd_extdata.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'extdata' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200809L /* strdup */ + +#include "cmd.h" + +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> + +#include "libyang.h" + +#include "common.h" +#include "yl_opt.h" + +char *filename; + +void +cmd_extdata_free(void) +{ + free(filename); + filename = NULL; +} + +void +cmd_extdata_help(void) +{ + printf("Usage: extdata [--clear] [<extdata-file-path>]\n" + " File containing the specific data required by an extension. Required by\n" + " the schema-mount extension, for example, when the operational data are\n" + " expected in the file. File format is guessed.\n"); +} + +int +cmd_extdata_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"clear", no_argument, NULL, 'c'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_EXTDATA].optstring, options, &opt_index)) != -1) { + switch (opt) { + case 'c': + yo->extdata_unset = 1; + free(filename); + filename = NULL; + break; + case 'h': + cmd_extdata_help(); + return 1; + default: + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_extdata_dep(struct yl_opt *yo, int posc) +{ + if (!yo->extdata_unset && (posc > 1)) { + YLMSG_E("Only one file must be entered."); + return 1; + } + + return 0; +} + +int +cmd_extdata_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + if (yo->extdata_unset) { + ly_ctx_set_ext_data_clb(*ctx, NULL, NULL); + } else if (!yo->extdata_unset && !posv) { + /* no argument - print the current file */ + printf("%s\n", filename ? filename : "No file set."); + } else if (posv) { + /* set callback providing run-time extension instance data */ + free(filename); + filename = strdup(posv); + if (!filename) { + YLMSG_E("Memory allocation error."); + return 1; + } + ly_ctx_set_ext_data_clb(*ctx, ext_data_clb, filename); + } + + return 0; +} diff --git a/tools/lint/cmd_feature.c b/tools/lint/cmd_feature.c index 6b332ab..96d55c1 100644 --- a/tools/lint/cmd_feature.c +++ b/tools/lint/cmd_feature.c @@ -1,9 +1,10 @@ /** * @file cmd_feature.c * @author Michal Vasko <mvasko@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'feature' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2021 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -23,6 +24,8 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" +#include "yl_schema_features.h" void cmd_feature_help(void) @@ -37,17 +40,11 @@ cmd_feature_help(void) " Print features of all implemented modules.\n"); } -void -cmd_feature(struct ly_ctx **ctx, const char *cmdline) +int +cmd_feature_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - 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; + int rc = 0, argc = 0; + int opt, opt_index; struct option options[] = { {"help", no_argument, NULL, 'h'}, {"all", no_argument, NULL, 'a'}, @@ -55,81 +52,80 @@ cmd_feature(struct ly_ctx **ctx, const char *cmdline) {NULL, 0, NULL, 0} }; - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "haf", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_FEATURE].optstring, options, &opt_index)) != -1) { switch (opt) { case 'h': cmd_feature_help(); - goto cleanup; + return 1; case 'a': - print_all = 1; + yo->feature_print_all = 1; break; case 'f': - generate_features = 1; + yo->feature_param_format = 1; break; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; } } - if (ly_out_new_file(stdout, &out)) { - YLMSG_E("Unable to print to the standard output.\n"); - goto cleanup; - } + *posv = &yo->argv[optind]; + *posc = argc - optind; - 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; - } + return 0; +} - if (argc == optind) { - YLMSG_E("Missing modules to print.\n"); - goto cleanup; +int +cmd_feature_dep(struct yl_opt *yo, int posc) +{ + if (ly_out_new_file(stdout, &yo->out)) { + YLMSG_E("Unable to print to the standard output."); + return 1; } + yo->out_stdout = 1; - 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); + if (yo->interactive && !yo->feature_print_all && !posc) { + YLMSG_E("Missing modules to print."); + return 1; + } - mod = ly_ctx_get_module_latest(*ctx, argv[optind + i]); - if (!mod) { - YLMSG_E("Module \"%s\" not found.\n", argv[optind + i]); - goto cleanup; - } + return 0; +} - /* collect features of the module */ - if (collect_features(mod, &set)) { - goto cleanup; - } +int +cmd_feature_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + const struct lys_module *mod; - 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; - } + if (yo->feature_print_all) { + print_all_features(yo->out, *ctx, yo->feature_param_format); + return 0; + } - print_features(out, mod, &set); + mod = ly_ctx_get_module_latest(*ctx, posv); + if (!mod) { + YLMSG_E("Module \"%s\" not found.", posv); + return 1; } - if (generate_features) { - printf("%s\n", features_output); + if (yo->feature_param_format) { + print_feature_param(yo->out, mod); + } else { + print_features(yo->out, mod); } -cleanup: - free_cmdline(argv); - ly_out_free(out, NULL, 0); - ly_set_erase(&set, NULL); - free(features_output); + return 0; +} + +int +cmd_feature_fin(struct ly_ctx *ctx, struct yl_opt *yo) +{ + (void) ctx; + + ly_print(yo->out, "\n"); + return 0; } diff --git a/tools/lint/cmd_help.c b/tools/lint/cmd_help.c new file mode 100644 index 0000000..a1ee3f6 --- /dev/null +++ b/tools/lint/cmd_help.c @@ -0,0 +1,107 @@ +/** + * @file cmd_help.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'help' command of the libyang's yanglint tool. + * + * Copyright (c) 2023-2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include "cmd.h" + +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> + +#include "libyang.h" + +#include "common.h" +#include "yl_opt.h" + +void +cmd_help_help(void) +{ + printf("Usage: help [cmd ...]\n"); +} + +int +cmd_help_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_HELP].optstring, options, &opt_index)) != -1) { + if (opt == 'h') { + cmd_help_help(); + return 1; + } else { + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return rc; +} + +static void +print_generic_help(void) +{ + printf("Available commands:\n"); + for (uint16_t i = 0; commands[i].name; i++) { + if (commands[i].helpstring != NULL) { + printf(" %-15s %s\n", commands[i].name, commands[i].helpstring); + } + } +} + +int +cmd_help_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void)ctx, (void)yo; + + if (!posv) { + print_generic_help(); + } else { + /* print specific help for the selected command(s) */ + + int8_t match = 0; + + /* get the command of the specified name */ + for (uint16_t i = 0; commands[i].name; i++) { + if (strcmp(posv, commands[i].name) == 0) { + match = 1; + if (commands[i].help_func != NULL) { + commands[i].help_func(); + } else { + printf("%s: %s\n", posv, commands[i].helpstring); + } + break; + } + } + if (!match) { + /* if unknown command specified, print the list of commands */ + printf("Unknown command \'%s\'\n", posv); + print_generic_help(); + } + } + + return 0; +} diff --git a/tools/lint/cmd_list.c b/tools/lint/cmd_list.c index ec7a021..166fbfa 100644 --- a/tools/lint/cmd_list.c +++ b/tools/lint/cmd_list.c @@ -2,9 +2,10 @@ * @file cmd_list.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'list' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -24,6 +25,7 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" void cmd_list_help(void) @@ -37,53 +39,148 @@ cmd_list_help(void) " modules.\n"); } -void -cmd_list(struct ly_ctx **ctx, const char *cmdline) +int +cmd_list_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; + int rc = 0, argc = 0; int opt, opt_index; struct option options[] = { {"format", required_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; - LYD_FORMAT format = LYD_UNKNOWN; - struct ly_out *out = NULL; - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; + yo->data_out_format = LYD_UNKNOWN; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "f:h", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_LIST].optstring, options, &opt_index)) != -1) { switch (opt) { case 'f': /* --format */ if (!strcasecmp(optarg, "xml")) { - format = LYD_XML; + yo->data_out_format = LYD_XML; } else if (!strcasecmp(optarg, "json")) { - format = LYD_JSON; + yo->data_out_format = LYD_JSON; } else { - YLMSG_E("Unknown output format %s\n", optarg); + YLMSG_E("Unknown output format %s.", optarg); cmd_list_help(); - goto cleanup; + return 1; } break; case 'h': cmd_list_help(); - goto cleanup; + return 1; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_list_dep(struct yl_opt *yo, int posc) +{ + if (posc) { + YLMSG_E("No positional arguments are allowed."); + return 1; + } + if (ly_out_new_file(stdout, &yo->out)) { + YLMSG_E("Unable to print to the standard output."); + return 1; + } + yo->out_stdout = 1; + + return 0; +} + +/** + * @brief Print yang library data. + * + * @param[in] ctx Context for libyang. + * @param[in] data_out_format Output format of printed data. + * @param[in] out Output handler. + * @return 0 on success. + */ +static int +print_yang_lib_data(struct ly_ctx *ctx, LYD_FORMAT data_out_format, struct ly_out *out) +{ + struct lyd_node *ylib; + + if (ly_ctx_get_yanglib_data(ctx, &ylib, "%u", ly_ctx_get_change_count(ctx))) { + YLMSG_E("Getting context info (ietf-yang-library data) failed. If the YANG module is missing or not implemented, " + "use an option to add it internally."); + return 1; + } + + lyd_print_all(out, ylib, data_out_format, 0); + lyd_free_all(ylib); + + return 0; +} + +int +cmd_list_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) posv; + uint32_t idx = 0, has_modules = 0; + const struct lys_module *mod; + + if (yo->data_out_format != LYD_UNKNOWN) { + /* ietf-yang-library data are printed in the specified format */ + if (print_yang_lib_data(*ctx, yo->data_out_format, yo->out)) { + return 1; } + return 0; } - 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"); + /* iterate schemas in context and provide just the basic info */ + ly_print(yo->out, "List of the loaded models:\n"); + while ((mod = ly_ctx_get_module_iter(*ctx, &idx))) { + has_modules++; + + /* conformance print */ + if (mod->implemented) { + ly_print(yo->out, " I"); + } else { + ly_print(yo->out, " i"); + } + + /* module print */ + ly_print(yo->out, " %s", mod->name); + if (mod->revision) { + ly_print(yo->out, "@%s", mod->revision); + } + + /* submodules print */ + if (mod->parsed && mod->parsed->includes) { + uint64_t u = 0; + + ly_print(yo->out, " ("); + LY_ARRAY_FOR(mod->parsed->includes, u) { + ly_print(yo->out, "%s%s", !u ? "" : ",", mod->parsed->includes[u].name); + if (mod->parsed->includes[u].rev[0]) { + ly_print(yo->out, "@%s", mod->parsed->includes[u].rev); + } + } + ly_print(yo->out, ")"); + } + + /* finish the line */ + ly_print(yo->out, "\n"); + } + + if (!has_modules) { + ly_print(yo->out, "\t(none)\n"); } -cleanup: - free_cmdline(argv); + ly_print_flush(yo->out); + + return 0; } diff --git a/tools/lint/cmd_load.c b/tools/lint/cmd_load.c index f5883e9..808c125 100644 --- a/tools/lint/cmd_load.c +++ b/tools/lint/cmd_load.c @@ -2,9 +2,10 @@ * @file cmd_load.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'load' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -17,6 +18,7 @@ #include "cmd.h" +#include <assert.h> #include <getopt.h> #include <stdint.h> #include <stdio.h> @@ -25,13 +27,15 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" +#include "yl_schema_features.h" void cmd_load_help(void) { printf("Usage: load [-i] <module-name1>[@<revision>] [<module-name2>[@revision] ...]\n" " Add a new module of the specified name, yanglint will find\n" - " them in searchpaths. if the <revision> of the module not\n" + " 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" @@ -39,101 +43,110 @@ cmd_load_help(void) " -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"); + " all the modules are set implemented.\n" + " -X, --extended-leafref\n" + " Allow usage of deref() XPath function within leafref.\n"); } -void -cmd_load(struct ly_ctx **ctx, const char *cmdline) +int +cmd_load_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; + int rc, argc = 0; int opt, opt_index; struct option options[] = { {"features", required_argument, NULL, 'F'}, {"help", no_argument, NULL, 'h'}, {"make-implemented", no_argument, NULL, 'i'}, + {"extended-leafref", no_argument, NULL, 'X'}, {NULL, 0, NULL, 0} }; - uint16_t options_ctx = 0; - const char *all_features[] = {"*", NULL}; - struct ly_set fset = {0}; - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "F:hi", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_LOAD].optstring, options, &opt_index)) != -1) { switch (opt) { case 'F': /* --features */ - if (parse_features(optarg, &fset)) { - goto cleanup; + if (parse_features(optarg, &yo->schema_features)) { + return 1; } break; case 'h': cmd_load_help(); - goto cleanup; + return 1; 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; - } + yo_opt_update_make_implemented(yo); + break; + + case 'X': /* --extended-leafref */ + yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED; break; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; } } - if (argc == optind) { + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_load_dep(struct yl_opt *yo, int posc) +{ + if (yo->interactive && !posc) { /* no argument */ - cmd_add_help(); - goto cleanup; + cmd_load_help(); + return 1; } - if (!fset.count) { + if (!yo->schema_features.count) { /* no features, enable all of them */ - options_ctx |= LY_CTX_ENABLE_IMP_FEATURES; + yo->ctx_options |= LY_CTX_ENABLE_IMP_FEATURES; } - if (options_ctx) { - ly_ctx_set_options(*ctx, options_ctx); - } + return 0; +} - for (int i = 0; i < argc - optind; i++) { - /* process the schema module files */ - char *revision; - const char **features = NULL; +int +cmd_load_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + const char *all_features[] = {"*", NULL}; + char *revision; + const char **features = NULL; - /* get revision */ - revision = strchr(argv[optind + i], '@'); - if (revision) { - revision[0] = '\0'; - ++revision; - } + assert(posv); - /* get features list for this module */ - if (!fset.count) { - features = all_features; - } else { - get_features(&fset, argv[optind + i], &features); - } + if (yo->ctx_options) { + ly_ctx_set_options(*ctx, yo->ctx_options); + yo->ctx_options = 0; + } - /* load the module */ - if (!ly_ctx_load_module(*ctx, argv[optind + i], revision, features)) { - /* libyang printed the error messages */ - goto cleanup; - } + /* get revision */ + revision = strchr(posv, '@'); + if (revision) { + revision[0] = '\0'; + ++revision; } -cleanup: - if (options_ctx) { - ly_ctx_unset_options(*ctx, options_ctx); + /* get features list for this module */ + if (!yo->schema_features.count) { + features = all_features; + } else { + get_features(&yo->schema_features, posv, &features); } - ly_set_erase(&fset, free_features); - free_cmdline(argv); + + /* load the module */ + if (!ly_ctx_load_module(*ctx, posv, revision, features)) { + /* libyang printed the error messages */ + return 1; + } + + return 0; } diff --git a/tools/lint/cmd_print.c b/tools/lint/cmd_print.c index c1a5359..ff5fb90 100644 --- a/tools/lint/cmd_print.c +++ b/tools/lint/cmd_print.c @@ -2,9 +2,10 @@ * @file cmd_print.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'print' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -27,6 +28,7 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" void cmd_print_help(void) @@ -34,7 +36,8 @@ 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" + " only in case the -P option is specified. For yang, yin and tree\n" + " formats, a submodule can also be printed.\n\n" " -f FORMAT, --format=FORMAT\n" " Print the module in the specified FORMAT. If format not\n" " specified, the 'tree' format is used.\n" @@ -53,95 +56,10 @@ cmd_print_help(void) " 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 +cmd_print_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; + int rc = 0, argc = 0; int opt, opt_index; struct option options[] = { {"format", required_argument, NULL, 'f'}, @@ -152,113 +70,230 @@ cmd_print(struct ly_ctx **ctx, const char *cmdline) {"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; + + yo->schema_out_format = LYS_OUT_TREE; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "f:hL:o:P:q", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_PRINT].optstring, 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; - } + if (yo->out) { + YLMSG_E("Only a single output can be specified."); + return 1; } else { - if (ly_out_new_filepath(optarg, &out)) { - YLMSG_E("Unable to use output file %s for printing output.\n", optarg); - goto cleanup; + if (ly_out_new_filepath(optarg, &yo->out)) { + YLMSG_E("Unable open output file %s (%s).", optarg, strerror(errno)); + return 1; } } break; case 'f': /* --format */ - if (!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); + if (yl_opt_update_schema_out_format(optarg, yo)) { cmd_print_help(); - goto cleanup; + return 1; } break; case 'L': /* --tree-line-length */ - line_length = atoi(optarg); + yo->line_length = atoi(optarg); break; case 'P': /* --schema-node */ - node_path = optarg; + yo->schema_node_path = optarg; break; case 'q': /* --single-node */ - options_print |= LYS_PRINT_NO_SUBSTMT; + yo->schema_print_options |= LYS_PRINT_NO_SUBSTMT; break; case 'h': cmd_print_help(); - goto cleanup; + return 1; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; } } + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_print_dep(struct yl_opt *yo, int posc) +{ /* file name */ - if ((argc == optind) && !node_path) { - YLMSG_E("Missing the name of the module to print.\n"); - goto cleanup; + if (yo->interactive && !posc && !yo->schema_node_path) { + YLMSG_E("Missing the name of the module to print."); + return 1; } - 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 ((yo->schema_out_format != LYS_OUT_TREE) && yo->line_length) { + YLMSG_W("--tree-line-length take effect only in case of the tree output format."); } - if (!out) { - if (ly_out_new_file(stdout, &out)) { - YLMSG_E("Could not use stdout to print output.\n"); - goto cleanup; + if (!yo->out) { + if (ly_out_new_file(stdout, &yo->out)) { + YLMSG_E("Could not use stdout to print output."); } - out_stdout = 1; + yo->out_stdout = 1; } - if (format == LYS_OUT_TREE) { + if (yo->schema_out_format == LYS_OUT_TREE) { /* print tree from lysc_nodes */ - ly_ctx_set_options(*ctx, LY_CTX_SET_PRIV_PARSED); + yo->ctx_options |= LY_CTX_SET_PRIV_PARSED; } - if (node_path) { - const struct lysc_node *node; + return 0; +} + +static LY_ERR +print_submodule(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options) +{ + LY_ERR erc; + const struct lysp_submodule *submodule; - node = find_schema_path(*ctx, node_path); + submodule = revision ? + ly_ctx_get_submodule(*ctx, name, revision) : + ly_ctx_get_submodule_latest(*ctx, name); + + erc = submodule ? + lys_print_submodule(out, submodule, format, line_length, options) : + LY_ENOTFOUND; + + if (!erc) { + return 0; + } else if ((erc == LY_ENOTFOUND) && revision) { + YLMSG_E("No submodule \"%s\" found.", name); + } else { + YLMSG_E("Unable to print submodule %s.", name); + } + + return erc; +} + +static LY_ERR +print_module(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options) +{ + LY_ERR erc; + struct lys_module *module; + + module = revision ? + ly_ctx_get_module(*ctx, name, revision) : + ly_ctx_get_module_latest(*ctx, name); + + erc = module ? + lys_print_module(out, module, format, line_length, options) : + LY_ENOTFOUND; + + if (!erc) { + return 0; + } else if ((erc == LY_ENOTFOUND) && revision) { + YLMSG_E("No module \"%s\" found.", name); + } else { + YLMSG_E("Unable to print module %s.", name); + } + + return erc; +} + +static int +cmd_print_module(const char *posv, struct ly_out *out, struct ly_ctx **ctx, LYS_OUTFORMAT format, + size_t line_length, uint32_t options) +{ + LY_ERR erc; + char *name = NULL, *revision; + + name = strdup(posv); + /* get revision */ + revision = strchr(name, '@'); + if (revision) { + revision[0] = '\0'; + ++revision; + } + + erc = print_module(out, ctx, name, revision, format, line_length, options); + + if (erc == LY_ENOTFOUND) { + erc = print_submodule(out, ctx, name, revision, format, line_length, options); + } + + free(name); + return erc; +} + +/** + * @brief Print schema node path. + * + * @param[in] ctx Context for libyang. + * @param[in] yo Context for yanglint. + * @return 0 on success. + */ +static int +print_node(struct ly_ctx *ctx, struct yl_opt *yo) +{ + const struct lysc_node *node; + uint32_t temp_lo = 0; + + if (yo->interactive) { + /* Use the same approach as for completion. */ + node = find_schema_path(ctx, yo->schema_node_path); if (!node) { - YLMSG_E("The requested schema node \"%s\" does not exists.\n", node_path); - goto cleanup; + YLMSG_E("The requested schema node \"%s\" does not exists.", yo->schema_node_path); + return 1; } - - if (lys_print_node(out, node, format, 0, options_print)) { - YLMSG_E("Unable to print schema node %s.\n", node_path); - goto cleanup; + } else { + /* turn off logging so that the message is not repeated */ + ly_temp_log_options(&temp_lo); + /* search operation input */ + node = lys_find_path(ctx, NULL, yo->schema_node_path, 0); + if (!node) { + /* restore logging so an error may be displayed */ + ly_temp_log_options(NULL); + /* search operation output */ + node = lys_find_path(ctx, NULL, yo->schema_node_path, 1); + if (!node) { + YLMSG_E("Invalid schema path."); + return 1; + } } + } + + if (lys_print_node(yo->out, node, yo->schema_out_format, yo->line_length, yo->schema_print_options)) { + YLMSG_E("Unable to print schema node %s.", yo->schema_node_path); + return 1; + } + + return 0; +} + +int +cmd_print_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + int rc = 0; + + if (yo->ctx_options & LY_CTX_SET_PRIV_PARSED) { + /* print tree from lysc_nodes */ + ly_ctx_set_options(*ctx, LY_CTX_SET_PRIV_PARSED); + } + + if (yo->schema_node_path) { + rc = print_node(*ctx, yo); + } else if (!yo->interactive && yo->submodule) { + rc = print_submodule(yo->out, ctx, yo->submodule, NULL, yo->schema_out_format, yo->line_length, + yo->schema_print_options); } else { - cmd_print_modules(argc, argv, out, ctx, format, line_length, options_print); - goto cleanup; + rc = cmd_print_module(posv, yo->out, ctx, yo->schema_out_format, yo->line_length, yo->schema_print_options); + if (!yo->last_one && (yo->schema_out_format == LYS_OUT_TREE)) { + ly_print(yo->out, "\n"); + } } -cleanup: - free_cmdline(argv); - ly_out_free(out, NULL, out_stdout ? 0 : 1); + return rc; } diff --git a/tools/lint/cmd_searchpath.c b/tools/lint/cmd_searchpath.c index 529e05d..a6aeacf 100644 --- a/tools/lint/cmd_searchpath.c +++ b/tools/lint/cmd_searchpath.c @@ -2,9 +2,10 @@ * @file cmd_searchpath.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'searchpath' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -24,51 +25,62 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" void cmd_searchpath_help(void) { printf("Usage: searchpath [--clear] [<modules-dir-path> ...]\n" - " Set paths of directories where to search for imports and\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"); + " Set paths of directories where to search for imports and includes\n" + " of the schema modules. Subdirectories are also searched. The current\n" + " working directory and the path of the module being added is used implicitly.\n" + " The 'load' command uses these paths to search even for the schema modules\n" + " to be loaded.\n"); } -void -cmd_searchpath(struct ly_ctx **ctx, const char *cmdline) +int +cmd_searchpath_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; + int rc = 0, argc = 0; int opt, opt_index; struct option options[] = { {"clear", no_argument, NULL, 'c'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; - int8_t cleared = 0; - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "ch", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_SEARCHPATH].optstring, options, &opt_index)) != -1) { switch (opt) { case 'c': - ly_ctx_unset_searchdir(*ctx, NULL); - cleared = 1; + yo->searchdir_unset = 1; break; case 'h': cmd_searchpath_help(); - goto cleanup; + return 1; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; } } - if (!cleared && (argc == optind)) { + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_searchpath_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + int rc = 0; + + if (yo->searchdir_unset) { + ly_ctx_unset_searchdir(*ctx, NULL); + } else if (!yo->searchdir_unset && !posv) { /* no argument - print the paths */ const char * const *dirs = ly_ctx_get_searchdirs(*ctx); @@ -76,15 +88,9 @@ cmd_searchpath(struct ly_ctx **ctx, const char *cmdline) 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; - } + } else { + rc = ly_ctx_set_searchdir(*ctx, posv); } -cleanup: - free_cmdline(argv); + return rc; } diff --git a/tools/lint/cmd_verb.c b/tools/lint/cmd_verb.c new file mode 100644 index 0000000..33c8d1e --- /dev/null +++ b/tools/lint/cmd_verb.c @@ -0,0 +1,114 @@ +/** + * @file cmd_verb.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'verb' command of the libyang's yanglint tool. + * + * Copyright (c) 2023-2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "cmd.h" + +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <strings.h> + +#include "libyang.h" + +#include "common.h" +#include "yl_opt.h" + +void +cmd_verb_help(void) +{ + printf("Usage: verb (error | warning | verbose | debug)\n"); +} + +int +cmd_verb_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_VERB].optstring, options, &opt_index)) != -1) { + if (opt == 'h') { + cmd_verb_help(); + return 1; + } else { + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_verb_dep(struct yl_opt *yo, int posc) +{ + (void) yo; + + if (posc > 1) { + YLMSG_E("Only a single verbosity level can be set."); + cmd_verb_help(); + return 1; + } + + return 0; +} + +int +cmd_verb_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) ctx, (void) yo; + + if (!posv) { + /* no argument - print current value */ + LY_LOG_LEVEL level = ly_log_level(LY_LLERR); + + ly_log_level(level); + printf("Current verbosity level: "); + if (level == LY_LLERR) { + printf("error\n"); + } else if (level == LY_LLWRN) { + printf("warning\n"); + } else if (level == LY_LLVRB) { + printf("verbose\n"); + } else if (level == LY_LLDBG) { + printf("debug\n"); + } + return 0; + } else { + if (!strcasecmp("error", posv) || !strcmp("0", posv)) { + ly_log_level(LY_LLERR); + } else if (!strcasecmp("warning", posv) || !strcmp("1", posv)) { + ly_log_level(LY_LLWRN); + } else if (!strcasecmp("verbose", posv) || !strcmp("2", posv)) { + ly_log_level(LY_LLVRB); + } else if (!strcasecmp("debug", posv) || !strcmp("3", posv)) { + ly_log_level(LY_LLDBG); + } else { + YLMSG_E("Unknown verbosity \"%s\".", posv); + return 1; + } + } + + return 0; +} diff --git a/tools/lint/common.c b/tools/lint/common.c index fc9b1cd..d86c54f 100644 --- a/tools/lint/common.c +++ b/tools/lint/common.c @@ -1,9 +1,10 @@ /** * @file common.c * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief libyang's yanglint tool - common functions for both interactive and non-interactive mode. * - * Copyright (c) 2020 CESNET, z.s.p.o. + * Copyright (c) 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -19,7 +20,7 @@ #include <assert.h> #include <errno.h> -#include <getopt.h> +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -27,6 +28,21 @@ #include "compat.h" #include "libyang.h" +#include "plugins_exts.h" +#include "yl_opt.h" + +void +yl_log(ly_bool err, const char *format, ...) +{ + char msg[256]; + va_list ap; + + va_start(ap, format); + vsnprintf(msg, 256, format, ap); + va_end(ap); + + fprintf(stderr, "YANGLINT[%c]: %s\n", err ? 'E' : 'W', msg); +} int parse_schema_path(const char *path, char **dir, char **module) @@ -38,6 +54,7 @@ parse_schema_path(const char *path, char **dir, char **module) /* split the path to dirname and basename for further work */ *dir = strdup(path); + /* FIXME: this is broken on Windows */ *module = strrchr(*dir, '/'); if (!(*module)) { *module = *dir; @@ -65,722 +82,99 @@ get_input(const char *filepath, LYS_INFORMAT *format_schema, LYD_FORMAT *format_ /* 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)); + YLMSG_E("Unable to use input filepath (%s) - %s.", filepath, strerror(errno)); return -1; } if (!S_ISREG(st.st_mode)) { - YLMSG_E("Provided input file (%s) is not a regular file.\n", filepath); + YLMSG_E("Provided input file (%s) is not a regular file.", 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"); + if (get_format(filepath, format_schema, format_data)) { 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)); + if (in && ly_in_new_filepath(filepath, 0, in)) { + YLMSG_E("Unable to process input file."); 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) +LYS_INFORMAT +get_schema_format(const char *filename) { - 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'; - } + if ((ptr = strrchr(filename, '.')) != NULL) { + ++ptr; + if (!strcmp(ptr, "yang")) { + return LYS_IN_YANG; + } else if (!strcmp(ptr, "yin")) { + return LYS_IN_YIN; + } else { + return LYS_IN_UNKNOWN; } + } else { + return LYS_IN_UNKNOWN; } - vector[count] = NULL; - - *argc_p = count; - *argv_p = vector; - - return 0; } -int -get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data) +LYD_FORMAT +get_data_format(const char *filename) { 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; + if (!strcmp(ptr, "xml")) { + return LYD_XML; } else if (!strcmp(ptr, "json")) { - informat_s = 0; - informat_d = LYD_JSON; + return LYD_JSON; } else if (!strcmp(ptr, "lyb")) { - informat_s = 0; - informat_d = LYD_LYB; + return LYD_LYB; } else { - YLMSG_E("Input file \"%s\" in an unknown format \"%s\".\n", filename, ptr); - return 0; + return LYD_UNKNOWN; } } else { - YLMSG_E("Input file \"%s\" without file extension - unknown format.\n", filename); - return 1; + return LYD_UNKNOWN; } - - 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) +get_format(const char *filepath, LYS_INFORMAT *schema_form, LYD_FORMAT *data_form) { - 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); - } + LYS_INFORMAT schema; + LYD_FORMAT data; - /* submodules print */ - if (mod->parsed && mod->parsed->includes) { - uint64_t u = 0; + schema = !schema_form || !*schema_form ? LYS_IN_UNKNOWN : *schema_form; + data = !data_form || !*data_form ? LYD_UNKNOWN : *data_form; - 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 (!schema) { + schema = get_schema_format(filepath); } - - if (!has_modules) { - ly_print(out, "\t(none)\n"); + if (!data) { + data = get_data_format(filepath); } - 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)) { + if (!schema && !data) { + YLMSG_E("Input schema format for %s file not recognized.", filepath); + return -1; + } else if (!data && !schema) { + YLMSG_E("Input data format for %s file not recognized.", filepath); return -1; } + assert(schema || data); - /* 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 (schema_form) { + *schema_form = schema; } - - 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; - } - } + if (data_form) { + *data_form = data; } -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; + return 0; } const struct lysc_node * @@ -812,7 +206,7 @@ find_schema_path(const struct ly_ctx *ctx, const char *schema_path) /* - 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)); + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); parent_node = NULL; goto cleanup; } @@ -866,3 +260,42 @@ cleanup: free(module_name); return parent_node; } + +LY_ERR +ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free) +{ + struct ly_ctx *ctx; + struct lyd_node *data = NULL; + + ctx = ext->module->ctx; + if (user_data) { + lyd_parse_data_path(ctx, user_data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &data); + } + + *ext_data = data; + *ext_data_free = 1; + return LY_SUCCESS; +} + +LY_ERR +searchpath_strcat(char **searchpaths, const char *path) +{ + uint64_t len; + char *new; + + if (!(*searchpaths)) { + *searchpaths = strdup(path); + return LY_SUCCESS; + } + + len = strlen(*searchpaths) + strlen(path) + strlen(PATH_SEPARATOR); + new = realloc(*searchpaths, sizeof(char) * len + 1); + if (!new) { + return LY_EMEM; + } + strcat(new, PATH_SEPARATOR); + strcat(new, path); + *searchpaths = new; + + return LY_SUCCESS; +} diff --git a/tools/lint/common.h b/tools/lint/common.h index 7c6a8ad..7c50e72 100644 --- a/tools/lint/common.h +++ b/tools/lint/common.h @@ -1,9 +1,10 @@ /** * @file common.h * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief libyang's yanglint tool - common functions and definitions for both interactive and non-interactive mode. * - * Copyright (c) 2020 CESNET, z.s.p.o. + * Copyright (c) 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -33,16 +34,21 @@ #define YL_DEFAULT_DATA_PARSE_OPTIONS LYD_PARSE_STRICT /** + * @brief Default data validation flags. + */ +#define YL_DEFAULT_DATA_VALIDATE_OPTIONS LYD_VALIDATE_MULTI_ERROR + +/** * @brief log error message */ #define YLMSG_E(...) \ - fprintf(stderr, "YANGLINT[E]: " __VA_ARGS__) + yl_log(1, __VA_ARGS__); /** * @brief log warning message */ #define YLMSG_W(...) \ - fprintf(stderr, "YANGLINT[W]: " __VA_ARGS__) + yl_log(0, __VA_ARGS__); #ifndef _WIN32 # define PATH_SEPARATOR ":" @@ -50,90 +56,16 @@ # 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); +struct cmdline_file; /** - * @brief Print all features of all implemented modules. + * @brief Log a yanglint message. * - * @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. + * @param[in] err Whether the message is an error or a warning. + * @param[in] format Message format. + * @param[in] ... Format arguments. */ -int print_all_features(struct ly_out *out, const struct ly_ctx *ctx, ly_bool generate_features, char **features_param); +void yl_log(ly_bool err, const char *format, ...); /** * @brief Parse path of a schema module file into the directory and module name. @@ -141,7 +73,7 @@ int print_all_features(struct ly_out *out, const struct ly_ctx *ctx, ly_bool gen * @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. + * @p path. Caller is expected to free the returned string. * @return 0 on success * @return -1 on error */ @@ -158,100 +90,69 @@ int parse_schema_path(const char *path, char **dir, char **module); * 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. + * @param[out] in Created input handler referring the file behind the @p filepath. Can be NULL. * @return 0 on success. * @return -1 on failure. */ int get_input(const char *filepath, LYS_INFORMAT *format_schema, LYD_FORMAT *format_data, struct ly_in **in); /** - * @brief 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. + * @brief Get schema format of the @p filename's content according to the @p filename's suffix. * - * @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. + * @param[in] filename Name of the file to examine. + * @return Detected schema input format. */ -int parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[]); +LYS_INFORMAT get_schema_format(const char *filename); /** - * @brief Destructor for the argument vector prepared by ::parse_cmdline(). + * @brief Get data format of the @p filename's content according to the @p filename's suffix. * - * @param[in,out] argv Argument vector to destroy. + * @param[in] filename Name of the file to examine. + * @return Detected data input format. */ -void free_cmdline(char *argv[]); +LYD_FORMAT get_data_format(const char *filename); /** - * @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. + * @brief Get format of the @p filename's content according to the @p filename's suffix. + * + * Either the @p schema or @p data parameter is set. + * + * @param[in] filepath Name of the file to examine. + * @param[out] schema_form Pointer to a variable to store the input schema format. + * @param[out] data_form Pointer to a variable to store the expected input data format. * @return zero in case a format was successfully detected. * @return nonzero in case it is not possible to get valid format from the @p filename. */ -int get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data); +int get_format(const char *filepath, LYS_INFORMAT *schema_form, LYD_FORMAT *data_form); /** - * @brief Print list of schemas in the context. + * @brief Get the node specified by the path. * - * @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. + * @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. */ -int print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat); +const struct lysc_node *find_schema_path(const struct ly_ctx *ctx, const char *schema_path); /** - * @brief Process the input data files - parse, validate and print according to provided options. + * @brief General callback providing run-time extension instance data. * - * @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. + * @param[in] ext Compiled extension instance. + * @param[in] user_data User-supplied callback data. + * @param[out] ext_data Provided extension instance data. + * @param[out] ext_data_free Whether the extension instance should free @p ext_data or not. * @return LY_ERR value. */ -LY_ERR 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 ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free); /** - * @brief Get the node specified by the path. + * @brief Concatenation of paths into one string. * - * @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. + * @param[in,out] searchpaths Collection of paths in the single string. Paths are delimited by colon ":" + * (on Windows, used semicolon ";" instead). + * @param[in] path Path to add. + * @return LY_ERR value. */ -const struct lysc_node * find_schema_path(const struct ly_ctx *ctx, const char *schema_path); +LY_ERR searchpath_strcat(char **searchpaths, const char *path); #endif /* COMMON_H_ */ diff --git a/tools/lint/completion.c b/tools/lint/completion.c index 9843816..67c6b68 100644 --- a/tools/lint/completion.c +++ b/tools/lint/completion.c @@ -43,14 +43,18 @@ cmd_completion_add_match(const char *match, char ***matches, unsigned int *match { void *p; - ++(*match_count); - p = realloc(*matches, *match_count * sizeof **matches); + p = realloc(*matches, (*match_count + 1) * sizeof **matches); if (!p) { - YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno)); + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); return; } *matches = p; - (*matches)[*match_count - 1] = strdup(match); + (*matches)[*match_count] = strdup(match); + if (!((*matches)[*match_count])) { + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); + return; + } + ++(*match_count); } /** @@ -76,6 +80,34 @@ get_cmd_completion(const char *hint, char ***matches, unsigned int *match_count) } /** + * @brief Provides completion for arguments. + * + * @param[in] hint User input. + * @param[in] args Array of all possible arguments. The last element must be NULL. + * @param[out] matches Matches provided to the user as a completion hint. + * @param[out] match_count Number of matches. + */ +static void +get_arg_completion(const char *hint, const char **args, char ***matches, unsigned int *match_count) +{ + int i; + + *match_count = 0; + *matches = NULL; + + for (i = 0; args[i]; i++) { + if (!strncmp(hint, args[i], strlen(hint))) { + cmd_completion_add_match(args[i], matches, match_count); + } + } + if (*match_count == 0) { + for (i = 0; args[i]; i++) { + cmd_completion_add_match(args[i], matches, match_count); + } + } +} + +/** * @brief Provides completion for module names. * * @param[in] hint User input. @@ -108,21 +140,21 @@ get_model_completion(const char *hint, char ***matches, unsigned int *match_coun /** * @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. + * @param[in] parent Node of which children will be added to the hint. + * @param[out] matches Matches provided to the user as a completion hint. + * @param[out] match_count Number of matches. */ static void -single_hint_add_children(const struct lysc_node *last_node, char ***matches, unsigned int *match_count) +single_hint_add_children(const struct lysc_node *parent, char ***matches, unsigned int *match_count) { const struct lysc_node *node = NULL; char *match; - if (!last_node) { + if (!parent) { return; } - while ((node = lys_getnext(node, last_node, NULL, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) { + while ((node = lys_getnext(node, parent, NULL, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) { match = lysc_path(node, LYSC_PATH_LOG, NULL, 0); cmd_completion_add_match(match, matches, match_count); free(match); @@ -157,13 +189,13 @@ add_all_children_nodes(const struct lysc_module *module, const struct lysc_node 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)); + 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)); + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); break; } } @@ -229,7 +261,7 @@ get_schema_completion(const char *hint, char ***matches, unsigned int *match_cou /* module name known */ module_name = strndup(start, end - start); if (!module_name) { - YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno)); + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); rc = 1; goto cleanup; } @@ -255,7 +287,7 @@ get_schema_completion(const char *hint, char ***matches, unsigned int *match_cou /* 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)); + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); rc = 1; goto cleanup; } @@ -277,6 +309,90 @@ cleanup: } /** + * @brief Get all possible argument hints for option. + * + * @param[in] hint User input. + * @param[out] matches Matches provided to the user as a completion hint. + * @param[out] match_count Number of matches. + */ +static void +get_print_format_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"yang", "yin", "tree", "info", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_data_type_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"data", "config", "get", "getconfig", "edit", "rpc", "reply", "notif", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_data_in_format_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"xml", "json", "lyb", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_data_default_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"all", "all-tagged", "trim", "implicit-tagged", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_list_format_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"xml", "json", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_verb_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"error", "warning", "verbose", "debug", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +#ifndef NDEBUG +/** + * @copydoc get_print_format_arg + */ +static void +get_debug_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"dict", "xpath", "dep-sets", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +#endif + +/** * @brief Get the string before the hint, which autocompletion is for. * * @param[in] buf Complete user input. @@ -315,22 +431,37 @@ 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 */ - + enum COMMAND_INDEX ci; /**< command index to global variable 'commands' */ + const char *opt; /**< optional option */ void (*ln_cb)(const char *, const char *, linenoiseCompletions *); /**< linenoise callback to call */ void (*yl_cb)(const char *, char ***, unsigned int *); /**< yanglint callback to call */ } ac[] = { - {"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}, + {CMD_ADD, NULL, linenoisePathCompletion, NULL}, + {CMD_PRINT, "-f", NULL, get_print_format_arg}, + {CMD_PRINT, "-P", NULL, get_schema_completion}, + {CMD_PRINT, "-o", linenoisePathCompletion, NULL}, + {CMD_PRINT, NULL, NULL, get_model_completion}, + {CMD_SEARCHPATH, NULL, linenoisePathCompletion, NULL}, + {CMD_EXTDATA, NULL, linenoisePathCompletion, NULL}, + {CMD_CLEAR, "-Y", linenoisePathCompletion, NULL}, + {CMD_DATA, "-t", NULL, get_data_type_arg}, + {CMD_DATA, "-O", linenoisePathCompletion, NULL}, + {CMD_DATA, "-R", linenoisePathCompletion, NULL}, + {CMD_DATA, "-f", NULL, get_data_in_format_arg}, + {CMD_DATA, "-F", NULL, get_data_in_format_arg}, + {CMD_DATA, "-d", NULL, get_data_default_arg}, + {CMD_DATA, "-o", linenoisePathCompletion, NULL}, + {CMD_DATA, NULL, linenoisePathCompletion, NULL}, + {CMD_LIST, NULL, NULL, get_list_format_arg}, + {CMD_FEATURE, NULL, NULL, get_model_completion}, + {CMD_VERB, NULL, NULL, get_verb_arg}, +#ifndef NDEBUG + {CMD_DEBUG, NULL, NULL, get_debug_arg}, +#endif }; - size_t cmd_len; - const char *last; + size_t name_len; + const char *last, *name, *getoptstr; + char opt[3] = {'\0', ':', '\0'}; char **matches = NULL; unsigned int match_count = 0, i; @@ -340,24 +471,27 @@ complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc) } 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] != ' ')) { + /* Find the right command. */ + name = commands[ac[i].ci].name; + name_len = strlen(name); + if (strncmp(buf, name, name_len) || (buf[name_len] != ' ')) { /* not this command */ continue; } + /* Select based on the right option. */ last = get_last_str(buf, hint); - if (ac[i].opt && strncmp(ac[i].opt, last, strlen(ac[i].opt))) { - /* autocompletion for (another) option */ + opt[0] = (last[0] == '-') && last[1] ? last[1] : '\0'; + getoptstr = commands[ac[i].ci].optstring; + if (!ac[i].opt && opt[0] && strstr(getoptstr, opt)) { + /* completion for the argument must be defined */ continue; - } - if (!ac[i].last_opt && (last[0] == '-')) { - /* autocompletion for the command, not an option */ + } else if (ac[i].opt && opt[0] && strncmp(ac[i].opt, last, strlen(ac[i].opt))) { + /* completion for (another) option */ + continue; + } else if (ac[i].opt && !opt[0]) { + /* completion is defined for option */ continue; - } - if ((last != buf) && (last[0] != '-')) { - /* autocompleted */ - return; } /* callback */ diff --git a/tools/lint/configuration.c b/tools/lint/configuration.c index 86179fa..e3db668 100644 --- a/tools/lint/configuration.c +++ b/tools/lint/configuration.c @@ -37,14 +37,14 @@ get_yanglint_dir(void) char *user_home, *yl_dir; if (!(pw = getpwuid(getuid()))) { - YLMSG_E("Determining home directory failed (%s).\n", strerror(errno)); + YLMSG_E("Determining home directory failed (%s).", strerror(errno)); return NULL; } user_home = pw->pw_dir; yl_dir = malloc(strlen(user_home) + 1 + strlen(YL_DIR) + 1); if (!yl_dir) { - YLMSG_E("Memory allocation failed (%s).\n", strerror(errno)); + YLMSG_E("Memory allocation failed (%s).", strerror(errno)); return NULL; } sprintf(yl_dir, "%s/%s", user_home, YL_DIR); @@ -53,17 +53,17 @@ get_yanglint_dir(void) if (ret == -1) { if (errno == ENOENT) { /* directory does not exist */ - YLMSG_W("Configuration directory \"%s\" does not exist, creating it.\n", yl_dir); + YLMSG_W("Configuration directory \"%s\" does not exist, creating it.", yl_dir); if (mkdir(yl_dir, 00700)) { if (errno != EEXIST) { /* parallel execution, yay */ - YLMSG_E("Configuration directory \"%s\" cannot be created (%s).\n", yl_dir, strerror(errno)); + YLMSG_E("Configuration directory \"%s\" cannot be created (%s).", yl_dir, strerror(errno)); free(yl_dir); return NULL; } } } else { - YLMSG_E("Configuration directory \"%s\" exists but cannot be accessed (%s).\n", yl_dir, strerror(errno)); + YLMSG_E("Configuration directory \"%s\" exists but cannot be accessed (%s).", yl_dir, strerror(errno)); free(yl_dir); return NULL; } @@ -83,16 +83,16 @@ load_config(void) history_file = malloc(strlen(yl_dir) + 9); if (!history_file) { - YLMSG_E("Memory allocation failed (%s).\n", strerror(errno)); + YLMSG_E("Memory allocation failed (%s).", strerror(errno)); free(yl_dir); return; } sprintf(history_file, "%s/history", yl_dir); if (access(history_file, F_OK) && (errno == ENOENT)) { - YLMSG_W("No saved history.\n"); + YLMSG_W("No saved history."); } else if (linenoiseHistoryLoad(history_file)) { - YLMSG_E("Failed to load history.\n"); + YLMSG_E("Failed to load history."); } free(history_file); @@ -110,14 +110,14 @@ store_config(void) history_file = malloc(strlen(yl_dir) + 9); if (!history_file) { - YLMSG_E("Memory allocation failed (%s).\n", strerror(errno)); + YLMSG_E("Memory allocation failed (%s).", strerror(errno)); free(yl_dir); return; } sprintf(history_file, "%s/history", yl_dir); if (linenoiseHistorySave(history_file)) { - YLMSG_E("Failed to save history.\n"); + YLMSG_E("Failed to save history."); } free(history_file); diff --git a/tools/lint/examples/README.md b/tools/lint/examples/README.md index 604591c..93d3c2a 100644 --- a/tools/lint/examples/README.md +++ b/tools/lint/examples/README.md @@ -33,11 +33,11 @@ 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. + Set paths of directories where to search for imports and includes + of the schema modules. Subdirectories are also searched. The current + working directory and the path of the module being added is used implicitly. + The 'load' command uses these paths to search even for the schema modules + to be loaded. ``` The input files referred in this document are available together with this @@ -469,3 +469,68 @@ ietf-ip features: } } ``` + +## YANG modules with the Schema Mount extension + +In these examples the non-interactive `yanglint` is used to simplify creating the context, a `yang-library` data file is +used. The working directory is `libyang/tools/lint/examples` and *libyang* must be installed. + +**Print tree output of a model with Schema Mount** + +Command and its output: + +``` +$ yanglint -f tree -p . -Y sm-context-main.xml -x sm-context-extension.xml sm-main.yang +module: sm-main + +--mp root* [node] + | +--rw node string + +--mp root2 + +--rw root3 + +--mp my-list* [name] + +--rw things/* [name] + | +--rw name -> /if:interfaces/if:interface/if:name + | +--rw attribute? uint32 + +--rw not-compiled/ + | +--rw first? string + | +--rw second? string + +--rw interfaces@ + | +--rw interface* [name] + | +--rw name string + | +--rw type identityref + +--rw name string +``` + +**Validating and printing mounted data** + +Command and its output: + +``` +$ yanglint -f json -t config -p . -Y sm-context-main.xml -x sm-context-extension.xml sm-data.xml +{ + "ietf-interfaces:interfaces": { + "interface": [ + { + "name": "eth0", + "type": "iana-if-type:ethernetCsmacd" + }, + { + "name": "eth1", + "type": "iana-if-type:ethernetCsmacd" + } + ] + }, + "sm-main:root3": { + "my-list": [ + { + "name": "list item 1", + "sm-extension:things": [ + { + "name": "eth0", + "attribute": 1 + } + ] + } + ] + } +} +``` diff --git a/tools/lint/main.c b/tools/lint/main.c index 9f0d027..43b90c8 100644 --- a/tools/lint/main.c +++ b/tools/lint/main.c @@ -1,9 +1,10 @@ /** * @file main.c * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief libyang's yanglint tool * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -27,6 +28,7 @@ #include "completion.h" #include "configuration.h" #include "linenoise/linenoise.h" +#include "yl_opt.h" int done; struct ly_ctx *ctx = NULL; @@ -37,25 +39,32 @@ int main_ni(int argc, char *argv[]); int main(int argc, char *argv[]) { - char *cmdline; - int cmdlen; + int cmdlen, posc, i, j; + struct yl_opt yo = {0}; + char *empty = NULL, *cmdline; + char **posv; + uint8_t cmd_found; if (argc > 1) { /* run in non-interactive mode */ return main_ni(argc, argv); } + yo.interactive = 1; /* continue in interactive mode */ linenoiseSetCompletionCallback(complete_cmd); load_config(); if (ly_ctx_new(NULL, YL_DEFAULT_CTX_OPTIONS, &ctx)) { - YLMSG_E("Failed to create context.\n"); + YLMSG_E("Failed to create context."); return 1; } while (!done) { - uint8_t executed = 0; + cmd_found = 0; + + posv = ∅ + posc = 0; /* get the command from user */ cmdline = linenoise(PROMPT); @@ -76,25 +85,48 @@ main(int argc, char *argv[]) for (cmdlen = 0; cmdline[cmdlen] && (cmdline[cmdlen] != ' '); cmdlen++) {} /* execute the command if any valid specified */ - for (uint16_t i = 0; commands[i].name; i++) { + for (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; + cmd_found = 1; + if (commands[i].opt_func && commands[i].opt_func(&yo, cmdline, &posv, &posc)) { + break; + } + if (commands[i].dep_func && commands[i].dep_func(&yo, posc)) { + break; + } + if (posc) { + for (j = 0; j < posc; j++) { + yo.last_one = (j + 1) == posc; + if (commands[i].exec_func(&ctx, &yo, posv[j])) { + break; + } + } + } else { + commands[i].exec_func(&ctx, &yo, NULL); + } + if (commands[i].fin_func) { + commands[i].fin_func(ctx, &yo); + } + break; } - if (!executed) { + if (!cmd_found) { /* if unknown command specified, tell it to user */ - YLMSG_E("Unknown command \"%.*s\", type 'help' for more information.\n", cmdlen, cmdline); + YLMSG_E("Unknown command \"%.*s\", type 'help' for more information.", cmdlen, cmdline); } linenoiseHistoryAdd(cmdline); free(cmdline); + yl_opt_erase(&yo); } + /* Global variables in commands are freed. */ + cmd_free(); + store_config(); ly_ctx_destroy(ctx); diff --git a/tools/lint/main_ni.c b/tools/lint/main_ni.c index 04c2340..c08acc3 100644 --- a/tools/lint/main_ni.c +++ b/tools/lint/main_ni.c @@ -2,9 +2,10 @@ * @file main_ni.c * @author Radek Krejci <rkrejci@cesnet.cz> * @author Michal Vasko <mvasko@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief libyang's yanglint tool - non-interactive code * - * Copyright (c) 2020 - 2022 CESNET, z.s.p.o. + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -25,107 +26,13 @@ #include <sys/stat.h> #include "libyang.h" -#include "plugins_exts.h" +#include "cmd.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); - } -} +#include "yl_opt.h" +#include "yl_schema_features.h" static void version(void) @@ -146,7 +53,8 @@ help(int shortout) " 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" + " <operational-file> with possible references to the operational datastore data.\n" + " To validate nested-notification or action, the <operational-file> is required.\n\n" " yanglint -t nc-reply -R <rpc-file> [-O <operational-file>] <schema>... <file>\n" " Validates the NETCONF rpc-reply <file> of RPC <rpc-file> according to the <schema>(s)\n" " using <operational-file> with possible references to the operational datastore data.\n\n" @@ -169,10 +77,16 @@ help(int shortout) " yang, yin, tree, info and feature-param for schemas,\n" " xml, json, and lyb for data.\n\n"); + printf(" -I FORMAT, --in-format=FORMAT\n" + " Load the data in one of the following formats:\n" + " xml, json, lyb\n" + " If input format not specified, it is detected from the file extension.\n\n"); + printf(" -p PATH, --path=PATH\n" " Search path for schema (YANG/YIN) modules. The option can be\n" " used multiple times. The current working directory and the\n" - " path of the module being added is used implicitly.\n\n"); + " path of the module being added is used implicitly. Subdirectories\n" + " are also searched\n\n"); printf(" -D, --disable-searchdir\n" " Do not implicitly search in current working directory for\n" @@ -251,6 +165,13 @@ help(int shortout) " trim - Remove all nodes with a default value.\n" " implicit-tagged - Add missing nodes and mark them with the attribute.\n\n"); + printf(" -E XPATH, --data-xpath=XPATH\n" + " Evaluate XPATH expression over the data and print the nodes satisfying\n" + " the expression. The output format is specific and the option cannot\n" + " be combined with the -f and -d options. Also all the data\n" + " inputs are merged into a single data tree where the expression\n" + " is evaluated, so the -m option is always set implicitly.\n\n"); + printf(" -l, --list Print info about the loaded schemas.\n" " (i - imported module, I - implemented module)\n" " In case the '-f' option with data encoding is specified,\n" @@ -267,8 +188,8 @@ help(int shortout) " 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"); + " In case of a nested notification or action, its parent existence\n" + " is also checked in these operational data.\n\n"); printf(" -R FILE, --reply-rpc=FILE\n" " Provide source RPC for parsing of the 'nc-reply' TYPE. The FILE\n" @@ -287,6 +208,9 @@ help(int shortout) " create an exact YANG schema context. If specified, the '-F'\n" " parameter (enabled features) is ignored.\n\n"); + printf(" -X, --extended-leafref\n" + " Allow usage of deref() XPath function within leafref\n\n"); + #ifndef NDEBUG printf(" -G GROUPS, --debug=GROUPS\n" " Enable printing of specific debugging message group\n" @@ -321,11 +245,11 @@ libyang_verbclb(LY_LOG_LEVEL level, const char *msg, const char *path) } } -static struct schema_features * +static struct yl_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]; + struct yl_schema_features *sf = fset->objs[u]; if (!sf->applied) { return sf; @@ -335,190 +259,166 @@ get_features_not_applied(const struct ly_set *fset) 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; -} - +/** + * @brief Create the libyang context. + * + * @param[in] yang_lib_file Context can be defined in yang library file. + * @param[in] searchpaths Directories in which modules are searched. + * @param[in,out] schema_features Set of features. + * @param[in,out] ctx_options Options for libyang context. + * @param[out] ctx Context for libyang. + * @return 0 on success. + */ static int -fill_context_inputs(int argc, char *argv[], struct context *c) +create_ly_context(const char *yang_lib_file, const char *searchpaths, struct ly_set *schema_features, + uint16_t *ctx_options, struct ly_ctx **ctx) { - 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) { + if (yang_lib_file) { /* ignore features */ - ly_set_erase(&c->schema_features, free_features); + ly_set_erase(schema_features, yl_schema_features_free); - 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"); + if (ly_ctx_new_ylpath(searchpaths, yang_lib_file, LYD_UNKNOWN, *ctx_options, ctx)) { + YLMSG_E("Unable to modify libyang context with yang-library data."); return -1; } } else { /* set imp feature flag if all should be enabled */ - c->ctx_options |= !c->schema_features.count ? LY_CTX_ENABLE_IMP_FEATURES : 0; + (*ctx_options) |= !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"); + if (ly_ctx_new(searchpaths, *ctx_options, ctx)) { + YLMSG_E("Unable to create libyang context."); 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); - } + return 0; +} - /* 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; +/** + * @brief Implement module if some feature has not been applied. + * + * @param[in] schema_features Set of features. + * @param[in,out] ctx Context for libyang. + * @return 0 on success. + */ +static int +apply_features(struct ly_set *schema_features, struct ly_ctx *ctx) +{ + struct yl_schema_features *sf; + struct lys_module *mod; + + /* check that all specified features were applied, apply now if possible */ + while ((sf = get_features_not_applied(schema_features))) { + /* try to find implemented or the latest revision of this module */ + mod = ly_ctx_get_module_implemented(ctx, sf->mod_name); + if (!mod) { + mod = ly_ctx_get_module_latest(ctx, sf->mod_name); } - } - if (c->reply_rpc.path) { - if (get_input(c->reply_rpc.path, NULL, &c->reply_rpc.format, &c->reply_rpc.in)) { - return -1; + if (!mod) { + YLMSG_E("Specified features not applied, module \"%s\" not loaded.", sf->mod_name); + 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; + /* we have the module, implement it if needed and enable the specific features */ + if (lys_set_implemented(mod, (const char **)sf->features)) { + YLMSG_E("Implementing module \"%s\" failed.", mod->name); + return 1; } + sf->applied = 1; + } - 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; + return 0; +} - /* parse the input */ - if (parse_schema_path(argv[optind + i], &dir, &module)) { - goto error; - } +/** + * @brief Parse and compile modules, data are only stored for later processing. + * + * @param[in] argc Number of strings in @p argv. + * @param[in] argv Strings from command line. + * @param[in] optind Index to the first input file in @p argv. + * @param[in] data_in_format Specified input data format. + * @param[in,out] ctx Context for libyang. + * @param[in,out] yo Options for yanglint. + * @return 0 on success. + */ +static int +fill_context_inputs(int argc, char *argv[], int optind, LYD_FORMAT data_in_format, struct ly_ctx *ctx, + struct yl_opt *yo) +{ + char *filepath = NULL; + LYS_INFORMAT format_schema; + LYD_FORMAT format_data; - 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; - } + for (int i = 0; i < argc - optind; i++) { + format_schema = LYS_IN_UNKNOWN; + format_data = data_in_format; - /* get features list for this module */ - if (!c->schema_features.count) { - features = all_features; - } else { - get_features(&c->schema_features, module, &features); - } + filepath = argv[optind + i]; - /* 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; - } - } + if (!filepath) { + return -1; + } + if (get_format(filepath, &format_schema, &format_data)) { + return -1; + } - /* 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; - } + if (format_schema) { + if (cmd_add_exec(&ctx, yo, filepath)) { + return -1; } - } else if (format_data) { - if (!fill_cmdline_file(&c->data_inputs, in, argv[optind + i], format_data)) { - goto error; + } else { + if (cmd_data_store(&ctx, yo, filepath)) { + return -1; } - in = NULL; } + } - ly_in_free(in, 1); - in = NULL; + /* Check that all specified features were applied, apply now if possible. */ + if (apply_features(&yo->schema_features, ctx)) { + return -1; } - /* 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; - } + return 0; +} - /* 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; +#ifndef NDEBUG +/** + * @brief Enable specific debugging messages. + * + * @param[in] groups String in the form "<group>[,group>]*". + * @param[in,out] yo Options for yanglint. + * return 0 on success. + */ +static int +set_debug_groups(char *groups, struct yl_opt *yo) +{ + int rc; + char *str, *end; + + /* Process all debug arguments except the last one. */ + for (str = groups; (end = strchr(str, ',')); str = end + 1) { + /* Temporary modify input string. */ + *end = '\0'; + rc = cmd_debug_store(NULL, yo, str); + *end = ','; + if (rc) { + return -1; } - sf->applied = 1; + } + /* Process single/last debug argument. */ + if (cmd_debug_store(NULL, yo, str)) { + return -1; + } + /* All debug arguments are valid, so they can apply. */ + if (cmd_debug_setlog(NULL, yo)) { + return -1; } return 0; - -error: - ly_in_free(in, 1); - free(dir); - free(module); - return -1; } +#endif + /** * @brief Process command line options and store the settings into the context. * @@ -527,10 +427,8 @@ error: * return 1 in case of success, but expect to exit. */ static int -fill_context(int argc, char *argv[], struct context *c) +fill_context(int argc, char *argv[], struct yl_opt *yo, struct ly_ctx **ctx) { - int ret; - int opt, opt_index; struct option options[] = { {"help", no_argument, NULL, 'h'}, @@ -542,6 +440,7 @@ fill_context(int argc, char *argv[], struct context *c) {"disable-searchdir", no_argument, NULL, 'D'}, {"features", required_argument, NULL, 'F'}, {"make-implemented", no_argument, NULL, 'i'}, + {"in-format", required_argument, NULL, 'I'}, {"schema-node", required_argument, NULL, 'P'}, {"single-node", no_argument, NULL, 'q'}, {"submodule", required_argument, NULL, 's'}, @@ -550,6 +449,7 @@ fill_context(int argc, char *argv[], struct context *c) {"present", no_argument, NULL, 'e'}, {"type", required_argument, NULL, 't'}, {"default", required_argument, NULL, 'd'}, + {"data-xpath", required_argument, NULL, 'E'}, {"list", no_argument, NULL, 'l'}, {"tree-line-length", required_argument, NULL, 'L'}, {"output", required_argument, NULL, 'o'}, @@ -558,6 +458,7 @@ fill_context(int argc, char *argv[], struct context *c) {"merge", no_argument, NULL, 'm'}, {"yang-library", no_argument, NULL, 'y'}, {"yang-library-file", required_argument, NULL, 'Y'}, + {"extended-leafref", no_argument, NULL, 'X'}, #ifndef NDEBUG {"debug", required_argument, NULL, 'G'}, #endif @@ -565,15 +466,16 @@ fill_context(int argc, char *argv[], struct context *c) }; 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; + yo->ctx_options = YL_DEFAULT_CTX_OPTIONS; + yo->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS; + yo->data_validate_options = YL_DEFAULT_DATA_VALIDATE_OPTIONS; + yo->line_length = 0; opterr = 0; #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) + while ((opt = getopt_long(argc, argv, "hvVQf:I:p:DF:iP:qs:neE:t:d:lL:o:O:R:myY:Xx: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) + while ((opt = getopt_long(argc, argv, "hvVQf:I:p:DF:iP:qs:neE:t:d:lL:o:O:R:myY:Xx:", options, &opt_index)) != -1) #endif { switch (opt) { @@ -609,138 +511,77 @@ fill_context(int argc, char *argv[], struct context *c) } /* 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); + if (yl_opt_update_out_format(optarg, yo)) { 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"); + case 'I': /* --in-format */ + if (yo_opt_update_data_in_format(optarg, yo)) { + YLMSG_E("Unknown input format %s.", optarg); + help(1); return -1; } + break; - if (searchpath_strcat(&c->searchpaths, optarg)) { - YLMSG_E("Storing searchpath failed.\n"); + case 'p': /* --path */ + if (searchpath_strcat(&yo->searchpaths, optarg)) { + YLMSG_E("Storing searchpath failed."); return -1; } - break; - } /* case 'p' */ + /* 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; + case 'D': /* --disable-searchdir */ + if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) { + YLMSG_W("The -D option specified too many times."); } + yo_opt_update_disable_searchdir(yo); break; case 'F': /* --features */ - if (parse_features(optarg, &c->schema_features)) { + if (parse_features(optarg, &yo->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; - } + yo_opt_update_make_implemented(yo); break; case 'P': /* --schema-node */ - c->schema_node_path = optarg; + yo->schema_node_path = optarg; break; case 'q': /* --single-node */ - c->schema_print_options |= LYS_PRINT_NO_SUBSTMT; + yo->schema_print_options |= LYS_PRINT_NO_SUBSTMT; break; case 's': /* --submodule */ - c->submodule = optarg; + yo->submodule = optarg; break; case 'x': /* --ext-data */ - c->schema_context_filename = strdup(optarg); + yo->schema_context_filename = optarg; break; case 'n': /* --not-strict */ - c->data_parse_options &= ~LYD_PARSE_STRICT; + yo->data_parse_options &= ~LYD_PARSE_STRICT; break; case 'e': /* --present */ - c->data_validate_options |= LYD_VALIDATE_PRESENT; + yo->data_validate_options |= LYD_VALIDATE_PRESENT; break; case 't': /* --type */ if (data_type_set) { - YLMSG_E("The data type (-t) cannot be set multiple times.\n"); + YLMSG_E("The data type (-t) cannot be set multiple times."); 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); + if (yl_opt_update_data_type(optarg, yo)) { + YLMSG_E("Unknown data tree type %s.", optarg); help(1); return -1; } @@ -749,184 +590,128 @@ fill_context(int argc, char *argv[], struct context *c) 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); + if (yo_opt_update_data_default(optarg, yo)) { + YLMSG_E("Unknown default mode %s.", optarg); help(1); return -1; } break; + case 'E': /* --data-xpath */ + if (ly_set_add(&yo->data_xpath, optarg, 0, NULL)) { + YLMSG_E("Storing XPath \"%s\" failed.", optarg); + return -1; + } + break; + case 'l': /* --list */ - c->list = 1; + yo->list = 1; break; case 'L': /* --tree-line-length */ - c->line_length = atoi(optarg); + yo->line_length = atoi(optarg); break; case 'o': /* --output */ - if (c->out) { - YLMSG_E("Only a single output can be specified.\n"); + if (yo->out) { + YLMSG_E("Only a single output can be specified."); return -1; } else { - if (ly_out_new_filepath(optarg, &c->out)) { - YLMSG_E("Unable open output file %s (%s)\n", optarg, strerror(errno)); + if (ly_out_new_filepath(optarg, &yo->out)) { + YLMSG_E("Unable open output file %s (%s).", optarg, strerror(errno)); return -1; } } break; case 'O': /* --operational */ - if (c->data_operational.path) { - YLMSG_E("The operational datastore (-O) cannot be set multiple times.\n"); + if (yo->data_operational.path) { + YLMSG_E("The operational datastore (-O) cannot be set multiple times."); return -1; } - c->data_operational.path = optarg; + yo->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"); + if (yo->reply_rpc.path) { + YLMSG_E("The PRC of the reply (-R) cannot be set multiple times."); return -1; } - c->reply_rpc.path = optarg; + yo->reply_rpc.path = optarg; break; case 'm': /* --merge */ - c->data_merge = 1; + yo->data_merge = 1; break; case 'y': /* --yang-library */ - c->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; + yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; break; case 'Y': /* --yang-library-file */ - c->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; - c->yang_lib_file = optarg; + yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; + yo->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; - } + case 'X': /* --extended-leafref */ + yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED; + break; - if (ptr[0]) { - if (ptr[0] != ',') { - YLMSG_E("Unknown debug group string \"%s\"\n", optarg); - return -1; - } - ++ptr; - } +#ifndef NDEBUG + case 'G': /* --debug */ + if (set_debug_groups(optarg, yo)) { + return -1; } - ly_log_dbg_groups(dbg_groups); break; - } /* case 'G' */ + /* case 'G' */ #endif default: - YLMSG_E("Invalid option or missing argument: -%c\n", optopt); + YLMSG_E("Invalid option or missing argument: -%c.", optopt); return -1; } /* switch */ } /* additional checks for the options combinations */ - if (!c->list && (optind >= argc)) { + if (!yo->list && (optind >= argc)) { help(1); - YLMSG_E("Missing <schema> to process.\n"); + YLMSG_E("Missing <schema> to process."); 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"); + if (cmd_data_dep(yo, 0)) { 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"); + if (cmd_print_dep(yo, 0)) { + return -1; } - /* 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; - } + /* Create the libyang context. */ + if (create_ly_context(yo->yang_lib_file, yo->searchpaths, &yo->schema_features, &yo->ctx_options, ctx)) { + return -1; } - if (c->schema_out_format == LYS_OUT_TREE) { - /* print tree from lysc_nodes */ - c->ctx_options |= LY_CTX_SET_PRIV_PARSED; + /* Set callback providing run-time extension instance data. */ + if (yo->schema_context_filename) { + ly_ctx_set_ext_data_clb(*ctx, ext_data_clb, yo->schema_context_filename); } - /* 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; + /* Schema modules and data files are just checked and prepared into internal structures for further processing. */ + if (fill_context_inputs(argc, argv, optind, yo->data_in_format, *ctx, yo)) { + return -1; } /* the second batch of checks */ - if (c->schema_print_options && !c->schema_out_format) { - YLMSG_W("Schema printer options specified, but the schema output format is missing.\n"); + if (yo->schema_print_options && !yo->schema_out_format) { + YLMSG_W("Schema printer options specified, but the schema output format is missing."); } - if (c->schema_parse_options && !c->schema_modules.count) { - YLMSG_W("Schema parser options specified, but no schema input file provided.\n"); + if (yo->schema_parse_options && !yo->schema_modules.count) { + YLMSG_W("Schema parser options specified, but no schema input file provided."); } - if (c->data_print_options && !c->data_out_format) { - YLMSG_W("data printer options specified, but the data output format is missing.\n"); + if (yo->data_print_options && !yo->data_out_format) { + YLMSG_W("data printer options specified, but the data output format is missing."); } - 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; - } - } + if (((yo->data_parse_options != YL_DEFAULT_DATA_PARSE_OPTIONS) || yo->data_type) && !yo->data_inputs.count) { + YLMSG_W("Data parser options specified, but no data input file provided."); } return 0; @@ -936,7 +721,8 @@ int main_ni(int argc, char *argv[]) { int ret = EXIT_SUCCESS, r; - struct context c = {0}; + struct yl_opt yo = {0}; + struct ly_ctx *ctx = NULL; char *features_output = NULL; struct ly_set set = {0}; uint32_t u; @@ -944,7 +730,7 @@ main_ni(int argc, char *argv[]) /* set callback for printing libyang messages */ ly_set_log_clb(libyang_verbclb, 1); - r = fill_context(argc, argv, &c); + r = fill_context(argc, argv, &yo, &ctx); if (r < 0) { ret = EXIT_FAILURE; } @@ -954,72 +740,46 @@ main_ni(int argc, char *argv[]) /* do the required job - parse, validate, print */ - if (c.list) { + if (yo.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; - } - } + ret = cmd_list_exec(&ctx, &yo, NULL); + goto cleanup; + } + if (yo.feature_param_format) { + for (u = 0; u < yo.schema_modules.count; u++) { + if ((ret = cmd_feature_exec(&ctx, &yo, ((struct lys_module *)yo.schema_modules.objs[u])->name))) { + goto cleanup; } } - - /* 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) { + cmd_feature_fin(ctx, &yo); + } else if (yo.schema_out_format && yo.schema_node_path) { + if ((ret = cmd_print_exec(&ctx, &yo, NULL))) { + goto cleanup; + } + } else if (yo.schema_out_format && yo.submodule) { + if ((ret = cmd_print_exec(&ctx, &yo, yo.submodule))) { + goto cleanup; + } + } else if (yo.schema_out_format) { + for (u = 0; u < yo.schema_modules.count; ++u) { + yo.last_one = (u + 1) == yo.schema_modules.count; + if ((ret = cmd_print_exec(&ctx, &yo, ((struct lys_module *)yo.schema_modules.objs[u])->name))) { goto cleanup; } } } + /* do the data validation despite the schema was printed */ + if (yo.data_inputs.size) { + if ((ret = cmd_data_process(ctx, &yo))) { + goto cleanup; + } + } + cleanup: /* cleanup */ - erase_context(&c); + yl_opt_erase(&yo); + ly_ctx_destroy(ctx); free(features_output); ly_set_erase(&set, NULL); diff --git a/tools/lint/tests/expect/common.exp b/tools/lint/tests/expect/common.exp deleted file mode 100644 index 0381e6c..0000000 --- a/tools/lint/tests/expect/common.exp +++ /dev/null @@ -1,68 +0,0 @@ -# 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 deleted file mode 100755 index ed4f6bd..0000000 --- a/tools/lint/tests/expect/completion.exp +++ /dev/null @@ -1,60 +0,0 @@ -#!/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 deleted file mode 100755 index 37680b0..0000000 --- a/tools/lint/tests/expect/feature.exp +++ /dev/null @@ -1,20 +0,0 @@ -#!/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 deleted file mode 100755 index ec3cdba..0000000 --- a/tools/lint/tests/expect/list.exp +++ /dev/null @@ -1,20 +0,0 @@ -#!/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 deleted file mode 100755 index fb2ee88..0000000 --- a/tools/lint/tests/shunit2/feature.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/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 deleted file mode 100755 index d64503a..0000000 --- a/tools/lint/tests/shunit2/list.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/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/yl_opt.c b/tools/lint/yl_opt.c new file mode 100644 index 0000000..7cd855f --- /dev/null +++ b/tools/lint/yl_opt.c @@ -0,0 +1,344 @@ +/** + * @file yl_opt.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief Settings options for the libyang context. + * + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include <assert.h> +#include <errno.h> +#include <getopt.h> +#include <strings.h> + +#include "in.h" /* ly_in_free */ + +#include "common.h" +#include "yl_opt.h" +#include "yl_schema_features.h" + +struct cmdline_file * +fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format) +{ + struct cmdline_file *rec; + + rec = malloc(sizeof *rec); + if (!rec) { + YLMSG_E("Allocating memory for data file information failed."); + return NULL; + } + rec->in = in; + rec->path = path; + rec->format = format; + + if (set && ly_set_add(set, rec, 1, NULL)) { + free(rec); + YLMSG_E("Storing data file information failed."); + return NULL; + } + + return rec; +} + +void +free_cmdline_file_items(struct cmdline_file *rec) +{ + if (rec && rec->in) { + ly_in_free(rec->in, 1); + } +} + +void +free_cmdline_file(void *cmdline_file) +{ + struct cmdline_file *rec = (struct cmdline_file *)cmdline_file; + + if (rec) { + free_cmdline_file_items(rec); + free(rec); + } +} + +void +yl_opt_erase(struct yl_opt *yo) +{ + ly_bool interactive; + + interactive = yo->interactive; + + /* data */ + ly_set_erase(&yo->data_inputs, free_cmdline_file); + ly_in_free(yo->data_operational.in, 1); + ly_set_erase(&yo->data_xpath, NULL); + + /* schema */ + ly_set_erase(&yo->schema_features, yl_schema_features_free); + ly_set_erase(&yo->schema_modules, NULL); + + /* context */ + free(yo->searchpaths); + + /* --reply-rpc */ + ly_in_free(yo->reply_rpc.in, 1); + + ly_out_free(yo->out, NULL, yo->out_stdout ? 0 : 1); + + free_cmdline(yo->argv); + + *yo = (const struct yl_opt) { + 0 + }; + yo->interactive = interactive; +} + +int +yl_opt_update_schema_out_format(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "yang")) { + yo->schema_out_format = LYS_OUT_YANG; + yo->data_out_format = 0; + } else if (!strcasecmp(arg, "yin")) { + yo->schema_out_format = LYS_OUT_YIN; + yo->data_out_format = 0; + } else if (!strcasecmp(arg, "info")) { + yo->schema_out_format = LYS_OUT_YANG_COMPILED; + yo->data_out_format = 0; + } else if (!strcasecmp(arg, "tree")) { + yo->schema_out_format = LYS_OUT_TREE; + yo->data_out_format = 0; + } else { + return 1; + } + + return 0; +} + +int +yl_opt_update_data_out_format(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "xml")) { + yo->schema_out_format = 0; + yo->data_out_format = LYD_XML; + } else if (!strcasecmp(arg, "json")) { + yo->schema_out_format = 0; + yo->data_out_format = LYD_JSON; + } else if (!strcasecmp(arg, "lyb")) { + yo->schema_out_format = 0; + yo->data_out_format = LYD_LYB; + } else { + return 1; + } + + return 0; +} + +static int +yl_opt_update_other_out_format(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "feature-param")) { + yo->feature_param_format = 1; + } else { + return 1; + } + + return 0; +} + +int +yl_opt_update_out_format(const char *arg, struct yl_opt *yo) +{ + if (!yl_opt_update_schema_out_format(arg, yo)) { + return 0; + } + if (!yl_opt_update_data_out_format(arg, yo)) { + return 0; + } + if (!yl_opt_update_other_out_format(arg, yo)) { + return 0; + } + + YLMSG_E("Unknown output format %s.", arg); + return 1; +} + +int +yl_opt_update_data_type(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "config")) { + yo->data_parse_options |= LYD_PARSE_NO_STATE; + yo->data_validate_options |= LYD_VALIDATE_NO_STATE; + } else if (!strcasecmp(arg, "get")) { + yo->data_parse_options |= LYD_PARSE_ONLY; + } else if (!strcasecmp(arg, "getconfig") || !strcasecmp(arg, "get-config") || !strcasecmp(arg, "edit")) { + yo->data_parse_options |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE; + } else if (!strcasecmp(arg, "rpc") || !strcasecmp(arg, "action")) { + yo->data_type = LYD_TYPE_RPC_YANG; + } else if (!strcasecmp(arg, "nc-rpc")) { + yo->data_type = LYD_TYPE_RPC_NETCONF; + } else if (!strcasecmp(arg, "reply") || !strcasecmp(arg, "rpcreply")) { + yo->data_type = LYD_TYPE_REPLY_YANG; + } else if (!strcasecmp(arg, "nc-reply")) { + yo->data_type = LYD_TYPE_REPLY_NETCONF; + } else if (!strcasecmp(arg, "notif") || !strcasecmp(arg, "notification")) { + yo->data_type = LYD_TYPE_NOTIF_YANG; + } else if (!strcasecmp(arg, "nc-notif")) { + yo->data_type = LYD_TYPE_NOTIF_NETCONF; + } else if (!strcasecmp(arg, "data")) { + /* default option */ + } else { + return 1; + } + + return 0; +} + +int +yo_opt_update_data_default(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "all")) { + yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL; + } else if (!strcasecmp(arg, "all-tagged")) { + yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG; + } else if (!strcasecmp(arg, "trim")) { + yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM; + } else if (!strcasecmp(arg, "implicit-tagged")) { + yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG; + } else { + return 1; + } + + return 0; +} + +int +yo_opt_update_data_in_format(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "xml")) { + yo->data_in_format = LYD_XML; + } else if (!strcasecmp(arg, "json")) { + yo->data_in_format = LYD_JSON; + } else if (!strcasecmp(arg, "lyb")) { + yo->data_in_format = LYD_LYB; + } else { + return 1; + } + + return 0; +} + +void +yo_opt_update_make_implemented(struct yl_opt *yo) +{ + if (yo->ctx_options & LY_CTX_REF_IMPLEMENTED) { + yo->ctx_options &= ~LY_CTX_REF_IMPLEMENTED; + yo->ctx_options |= LY_CTX_ALL_IMPLEMENTED; + } else { + yo->ctx_options |= LY_CTX_REF_IMPLEMENTED; + } +} + +void +yo_opt_update_disable_searchdir(struct yl_opt *yo) +{ + if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIR_CWD) { + yo->ctx_options &= ~LY_CTX_DISABLE_SEARCHDIR_CWD; + yo->ctx_options |= LY_CTX_DISABLE_SEARCHDIRS; + } else { + yo->ctx_options |= LY_CTX_DISABLE_SEARCHDIR_CWD; + } +} + +void +free_cmdline(char *argv[]) +{ + if (argv) { + free(argv[0]); + free(argv); + } +} + +int +parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[]) +{ + int count; + char **vector; + char *ptr; + char qmark = 0; + + assert(cmdline); + assert(argc_p); + assert(argv_p); + + /* init */ + optind = 0; /* reinitialize getopt() */ + count = 1; + vector = malloc((count + 1) * sizeof *vector); + vector[0] = strdup(cmdline); + + /* command name */ + strtok(vector[0], " "); + + /* arguments */ + while ((ptr = strtok(NULL, " "))) { + size_t len; + void *r; + + len = strlen(ptr); + + if (qmark) { + /* still in quotated text */ + /* remove NULL termination of the previous token since it is not a token, + * but a part of the quotation string */ + ptr[-1] = ' '; + + if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) { + /* end of quotation */ + qmark = 0; + /* shorten the argument by the terminating quotation mark */ + ptr[len - 1] = '\0'; + } + continue; + } + + /* another token in cmdline */ + ++count; + r = realloc(vector, (count + 1) * sizeof *vector); + if (!r) { + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); + free(vector); + return -1; + } + vector = r; + vector[count - 1] = ptr; + + if ((ptr[0] == '"') || (ptr[0] == '\'')) { + /* remember the quotation mark to identify end of quotation */ + qmark = ptr[0]; + + /* move the remembered argument after the quotation mark */ + ++vector[count - 1]; + + /* check if the quotation is terminated within this token */ + if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) { + /* end of quotation */ + qmark = 0; + /* shorten the argument by the terminating quotation mark */ + ptr[len - 1] = '\0'; + } + } + } + vector[count] = NULL; + + *argc_p = count; + *argv_p = vector; + + return 0; +} diff --git a/tools/lint/yl_opt.h b/tools/lint/yl_opt.h new file mode 100644 index 0000000..d66ae4d --- /dev/null +++ b/tools/lint/yl_opt.h @@ -0,0 +1,237 @@ +/** + * @file yl_opt.h + * @author Adam Piecek <piecek@cesnet.cz> + * @brief Settings options for the libyang context. + * + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef YL_OPT_H_ +#define YL_OPT_H_ + +#include "parser_data.h" /* enum lyd_type */ +#include "printer_schema.h" /* LYS_OUTFORMAT */ +#include "set.h" /* ly_set */ + +/** + * @brief Data connected with a file provided on a command line as a file path. + */ +struct cmdline_file { + struct ly_in *in; + const char *path; + LYD_FORMAT format; +}; + +/** + * @brief Create and fill the command line file data (struct cmdline_file *). + * @param[in] set Optional parameter in case the record is supposed to be added into a set. + * @param[in] in Input file handler. + * @param[in] path Filepath of the file. + * @param[in] format Format of the data file. + * @return The created command line file structure. + * @return NULL on failure + */ +struct cmdline_file *fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format); + +/** + * @brief Free the command line file data items. + * @param[in,out] rec record to free. + */ +void free_cmdline_file_items(struct cmdline_file *rec); + +/** + * @brief Free the command line file data (struct cmdline_file *). + * @param[in,out] cmdline_file The (struct cmdline_file *) to free. + */ +void free_cmdline_file(void *cmdline_file); + +/** + * @brief Context structure to hold and pass variables in a structured form. + */ +struct yl_opt { + /* Set to 1 if yanglint running in the interactive mode */ + ly_bool interactive; + ly_bool last_one; + + /* libyang context for the run */ + char *yang_lib_file; + uint16_t ctx_options; + + /* prepared output (--output option or stdout by default) */ + ly_bool out_stdout; + struct ly_out *out; + + char *searchpaths; + ly_bool searchdir_unset; + + /* options flags */ + uint8_t list; /* -l option to print list of schemas */ + + /* line length for 'tree' format */ + size_t line_length; /* --tree-line-length */ + + uint32_t dbg_groups; + + /* + * schema + */ + /* set schema modules' features via --features option (struct schema_features *) */ + struct ly_set schema_features; + + /* set of loaded schema modules (struct lys_module *) */ + struct ly_set schema_modules; + + /* options to parse and print schema modules */ + uint32_t schema_parse_options; + uint32_t schema_print_options; + + /* specification of printing schema node subtree, option --schema-node */ + char *schema_node_path; + char *submodule; + + /* name of file containing explicit context passed to callback + * for schema-mount extension. This also causes a callback to + * be registered. + */ + char *schema_context_filename; + ly_bool extdata_unset; + + /* value of --format in case of schema format */ + LYS_OUTFORMAT schema_out_format; + ly_bool feature_param_format; + ly_bool feature_print_all; + + /* + * data + */ + /* various options based on --type option */ + enum lyd_type data_type; + uint32_t data_parse_options; + uint32_t data_validate_options; + uint32_t data_print_options; + + /* flag for --merge option */ + uint8_t data_merge; + + /* value of --format in case of data format */ + LYD_FORMAT data_out_format; + + /* value of --in-format in case of data format */ + LYD_FORMAT data_in_format; + + /* input data files (struct cmdline_file *) */ + struct ly_set data_inputs; + + /* storage for --operational */ + struct cmdline_file data_operational; + + /* storage for --reply-rpc */ + struct cmdline_file reply_rpc; + + /* storage for --data-xpath */ + struct ly_set data_xpath; + + char **argv; +}; + +/** + * @brief Erase all values in @p opt. + * + * The yl_opt.interactive item is not deleted. + * + * @param[in,out] yo Option context to erase. + */ +void yl_opt_erase(struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the schema --format parameter. + * + * @param[in] arg Format parameter argument (for example yang, yin, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yl_opt_update_schema_out_format(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the data --format parameter. + * + * @param[in] arg Format parameter argument (for example xml, json, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yl_opt_update_data_out_format(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the general --format parameter. + * + * @param[in] arg Format parameter argument (for example yang, xml, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yl_opt_update_out_format(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the data --type parameter. + * + * @param[in] arg Format parameter argument (for example config, rpc, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yl_opt_update_data_type(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the data --default parameter. + * + * @param[in] arg Format parameter argument (for example all, trim, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yo_opt_update_data_default(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the data --in-format parameter. + * + * @param[in] arg Format parameter argument (for example xml, json, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yo_opt_update_data_in_format(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the --make-implemented parameter. + * + * @param[in,out] yo yanglint options used to update. + */ +void yo_opt_update_make_implemented(struct yl_opt *yo); + +/** + * @brief Update @p yo according to the --disable-searchdir parameter. + * + * @param[in,out] yo yanglint options used to update. + */ +void yo_opt_update_disable_searchdir(struct yl_opt *yo); + +/** + * @brief Helper function to prepare argc, argv pair from a command line string. + * + * @param[in] cmdline Complete command line string. + * @param[out] argc_p Pointer to store argc value. + * @param[out] argv_p Pointer to store argv vector. + * @return 0 on success, non-zero on failure. + */ +int parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[]); + +/** + * @brief Destructor for the argument vector prepared by ::parse_cmdline(). + * + * @param[in,out] argv Argument vector to destroy. + */ +void free_cmdline(char *argv[]); + +#endif /* YL_OPT_H_ */ diff --git a/tools/lint/yl_schema_features.c b/tools/lint/yl_schema_features.c new file mode 100644 index 0000000..74f88b9 --- /dev/null +++ b/tools/lint/yl_schema_features.c @@ -0,0 +1,212 @@ +/** + * @file yl_schema_features.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief Control features for the schema. + * + * Copyright (c) 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include <errno.h> +#include <stdlib.h> /* calloc */ +#include <string.h> /* strcmp */ + +#include "compat.h" /* strndup */ +#include "set.h" /* ly_set */ + +#include "common.h" +#include "yl_schema_features.h" + +void +yl_schema_features_free(void *flist) +{ + struct yl_schema_features *rec = (struct yl_schema_features *)flist; + + if (rec) { + free(rec->mod_name); + if (rec->features) { + for (uint32_t u = 0; rec->features[u]; ++u) { + free(rec->features[u]); + } + free(rec->features); + } + free(rec); + } +} + +void +get_features(const struct ly_set *fset, const char *module, const char ***features) +{ + /* get features list for this module */ + for (uint32_t u = 0; u < fset->count; ++u) { + struct yl_schema_features *sf = (struct yl_schema_features *)fset->objs[u]; + + if (!strcmp(module, sf->mod_name)) { + /* matched module - explicitly set features */ + *features = (const char **)sf->features; + sf->applied = 1; + return; + } + } + + /* features not set so disable all */ + *features = NULL; +} + +int +parse_features(const char *fstring, struct ly_set *fset) +{ + struct yl_schema_features *rec = NULL; + uint32_t count; + char *p, **fp; + + rec = calloc(1, sizeof *rec); + if (!rec) { + YLMSG_E("Unable to allocate features information record (%s).", strerror(errno)); + goto error; + } + + /* fill the record */ + p = strchr(fstring, ':'); + if (!p) { + YLMSG_E("Invalid format of the features specification (%s).", fstring); + goto error; + } + rec->mod_name = strndup(fstring, p - fstring); + + count = 0; + while (p) { + size_t len = 0; + char *token = p + 1; + + p = strchr(token, ','); + if (!p) { + /* the last item, if any */ + len = strlen(token); + } else { + len = p - token; + } + + if (len) { + fp = realloc(rec->features, (count + 1) * sizeof *rec->features); + if (!fp) { + YLMSG_E("Unable to store features list information (%s).", strerror(errno)); + goto error; + } + rec->features = fp; + fp = &rec->features[count++]; /* array item to set */ + (*fp) = strndup(token, len); + } + } + + /* terminating NULL */ + fp = realloc(rec->features, (count + 1) * sizeof *rec->features); + if (!fp) { + YLMSG_E("Unable to store features list information (%s).", strerror(errno)); + goto error; + } + rec->features = fp; + rec->features[count++] = NULL; + + /* Store record to the output set. */ + if (ly_set_add(fset, rec, 1, NULL)) { + YLMSG_E("Unable to store features information (%s).", strerror(errno)); + goto error; + } + rec = NULL; + + return 0; + +error: + yl_schema_features_free(rec); + return -1; +} + +void +print_features(struct ly_out *out, const struct lys_module *mod) +{ + struct lysp_feature *f; + uint32_t idx; + size_t max_len, len; + + ly_print(out, "%s:\n", mod->name); + + /* get max len, so the statuses of all the features will be aligned */ + max_len = 0, idx = 0, f = NULL; + while ((f = lysp_feature_next(f, mod->parsed, &idx))) { + len = strlen(f->name); + max_len = (max_len > len) ? max_len : len; + } + if (!max_len) { + ly_print(out, "\t(none)\n"); + return; + } + + /* print features */ + idx = 0, f = NULL; + while ((f = lysp_feature_next(f, mod->parsed, &idx))) { + ly_print(out, "\t%-*s (%s)\n", (int)max_len, f->name, lys_feature_value(mod, f->name) ? "off" : "on"); + } +} + +void +print_feature_param(struct ly_out *out, const struct lys_module *mod) +{ + struct lysp_feature *f = NULL; + uint32_t idx = 0; + uint8_t first = 1; + + ly_print(out, " -F %s:", mod->name); + while ((f = lysp_feature_next(f, mod->parsed, &idx))) { + if (first) { + ly_print(out, "%s", f->name); + first = 0; + } else { + ly_print(out, ",%s", f->name); + } + } +} + +void +print_all_features(struct ly_out *out, const struct ly_ctx *ctx, uint8_t feature_param) +{ + uint32_t i; + struct lys_module *mod; + uint8_t first; + + /* Print features for all implemented modules. */ + first = 1; + i = 0; + while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) { + if (!mod->implemented) { + continue; + } + if (first) { + print_features(out, mod); + first = 0; + } else { + ly_print(out, "\n"); + print_features(out, mod); + } + } + + if (!feature_param) { + return; + } + ly_print(out, "\n"); + + /* Print features for all implemented modules in 'feature-param' format. */ + i = 0; + while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) { + if (mod->implemented) { + print_feature_param(out, mod); + } + } +} diff --git a/tools/lint/yl_schema_features.h b/tools/lint/yl_schema_features.h new file mode 100644 index 0000000..7bfe9fd --- /dev/null +++ b/tools/lint/yl_schema_features.h @@ -0,0 +1,84 @@ +/** + * @file yl_schema_features.h + * @author Adam Piecek <piecek@cesnet.cz> + * @brief Control features for the schema. + * + * Copyright (c) 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef YL_SCHEMA_FEATURES_H_ +#define YL_SCHEMA_FEATURES_H_ + +#include <stdint.h> + +struct ly_set; +struct lys_module; +struct ly_out; +struct ly_ctx; + +/** + * @brief Storage for the list of the features (their names) in a specific YANG module. + */ +struct yl_schema_features { + char *mod_name; + char **features; + uint8_t applied; +}; + +/** + * @brief Free the schema features list (struct schema_features *) + * @param[in,out] flist The (struct schema_features *) to free. + */ +void yl_schema_features_free(void *flist); + +/** + * @brief Get the list of features connected with the specific YANG module. + * + * @param[in] fset The set of features information (struct schema_features *). + * @param[in] module Name of the YANG module which features should be found. + * @param[out] features Pointer to the list of features being returned. + */ +void get_features(const struct ly_set *fset, const char *module, const char ***features); + +/** + * @brief Parse features being specified for the specific YANG module. + * + * Format of the input @p fstring is as follows: "<module_name>:[<feature>,]*" + * + * @param[in] fstring Input string to be parsed. + * @param[in, out] fset Features information set (of struct schema_features *). The set is being filled. + */ +int parse_features(const char *fstring, struct ly_set *fset); + +/** + * @brief Print all features of a single module. + * + * @param[in] out The output handler for printing. + * @param[in] mod Module which can contains the features. + */ +void print_features(struct ly_out *out, const struct lys_module *mod); + +/** + * @brief Print all features in the 'feature-param' format. + * + * @param[in] out The output handler for printing. + * @param[in] mod Module which can contains the features. + */ +void print_feature_param(struct ly_out *out, const struct lys_module *mod); + +/** + * @brief Print all features of all implemented modules. + * + * @param[in] out The output handler for printing. + * @param[in] ctx Libyang context. + * @param[in] feature_param Flag expressing whether to print features parameter. + */ +void print_all_features(struct ly_out *out, const struct ly_ctx *ctx, uint8_t feature_param); + +#endif /* YL_SCHEMA_FEATURES_H_ */ |