From ac1aa44fc4b853d05662fc51b2a6e1a10714db52 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 3 Dec 2018 21:16:35 +0100 Subject: [PATCH] * Yang Configure options changed * `CLICON_YANG_DIR` is changed from a single directory to a path of directories * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list * CLICON_YANG_MAIN_FILE Provides a filename with a single module filename. * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. * Change all @datamodel:tree to @datamodel in all CLI specification files * If you generate CLI code from the model (CLIXON_CLI_GENMODEL). * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h * Removed return value ymodp from yang parse functions (eg yang_parse()). * New config option: CLICON_CLI_MODEL_TREENAME defining name of generated syntax tree if CLIXON_CLI_GENMODEL is set. --- CHANGELOG.md | 12 ++- README.md | 3 +- apps/backend/backend_main.c | 35 ++++--- apps/cli/cli_main.c | 54 ++++++----- apps/cli/cli_plugin.c | 29 +++++- apps/netconf/netconf_main.c | 27 ++++-- apps/restconf/restconf_main.c | 32 +++--- datastore/datastore_client.c | 2 +- doc/FAQ.md | 15 ++- example/example_cli.cli | 18 ++-- include/clixon_custom.h | 12 +++ lib/clixon/clixon_options.h | 9 ++ lib/clixon/clixon_yang.h | 9 +- lib/src/clixon_netconf_lib.c | 4 +- lib/src/clixon_options.c | 2 +- lib/src/clixon_plugin.c | 3 +- lib/src/clixon_xml_parse.y | 2 +- lib/src/clixon_yang.c | 150 ++++++++++++++++++++--------- lib/src/clixon_yang_module.c | 2 +- test/test_yang.sh | 2 +- yang/clixon-config@2018-10-21.yang | 27 +++++- 21 files changed, 309 insertions(+), 140 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a70162a9..d71d1ec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,15 +12,23 @@ * Support of submodule, include and belongs-to. * Openconfig yang specs parsed: https://github.com/openconfig/public * Improved unknown handling - * Configure option `CLICON_YANG_DIR` is changed from a single directory to a path of directories - * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list + * Yang Configure options changed + * `CLICON_YANG_DIR` is changed from a single directory to a path of directories + * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list + * CLICON_YANG_MAIN_FILE Provides a filename with a single module filename. + * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. ### API changes on existing features (you may need to change your code) * Yang parser is stricter (see above) which may break parsing of existing yang specs. * Yang parser functions have changed signatures. Please check the source if you call these functions. * Add `/usr/local/share/clixon` to your configuration file, or corresponding CLICON_DATADIR directory for Clixon system yang files. +* Change all @datamodel:tree to @datamodel in all CLI specification files + * If you generate CLI code from the model (CLIXON_CLI_GENMODEL). + * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes +* Removed return value ymodp from yang parse functions (eg yang_parse()). +* New config option: CLICON_CLI_MODEL_TREENAME defining name of generated syntax tree if CLIXON_CLI_GENMODEL is set. * XML parser conformance to W3 spec * Names lexically correct (NCName) * Syntactically Correct handling of ' cd doc diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index db66647e..1359e984 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -264,7 +264,7 @@ nacm_load_external(clicon_handle h) } if ((yspec = yspec_new()) == NULL) goto done; - if (yang_parse(h, NULL, "ietf-netconf-acm", NULL, yspec, NULL) < 0) + if (yang_parse(h, NULL, "ietf-netconf-acm", NULL, yspec) < 0) goto done; fd = fileno(f); /* Read configfile */ @@ -527,7 +527,7 @@ main(int argc, int logdst = CLICON_LOG_SYSLOG|CLICON_LOG_STDERR; yang_spec *yspec = NULL; yang_spec *yspecfg = NULL; /* For config XXX clixon bug */ - char *yang_filename = NULL; + char *str; /* In the startup, logs to stderr & syslog and debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, logdst); @@ -652,8 +652,8 @@ main(int argc, case 'g': /* config socket group */ clicon_option_str_set(h, "CLICON_SOCK_GROUP", optarg); break; - case 'y' :{ /* Load yang spec file (override yang main module) */ - yang_filename = optarg; + case 'y' :{ /* Load yang absolute filename */ + clicon_option_str_set(h, "CLICON_YANG_MAIN_FILE", optarg); break; } case 'x' :{ /* xmldb plugin */ @@ -750,17 +750,20 @@ main(int argc, if ((yspec = yspec_new()) == NULL) goto done; clicon_dbspec_yang_set(h, yspec); - /* Load main application yang specification either module or specific file - * If -y is given, it overrides main module */ - if (yang_filename){ - if (yang_spec_parse_file(h, yang_filename, yspec, NULL) < 0) + /* Load Yang modules + * 1. Load a yang module as a specific absolute filename */ + if ((str = clicon_yang_main_file(h)) != NULL) + if (yang_spec_parse_file(h, str, yspec) < 0) + goto done; + /* 2. Load a (single) main module */ + if ((str = clicon_yang_module_main(h)) != NULL) + if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h), + yspec) < 0) + goto done; + /* 3. Load all modules in a directory */ + if ((str = clicon_yang_main_dir(h)) != NULL) + if (yang_spec_load_dir(h, str, yspec) < 0) goto done; - } - else if (yang_spec_parse_module(h, clicon_yang_module_main(h), - clicon_yang_module_revision(h), - yspec, NULL) < 0) - goto done; - /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) goto done; @@ -769,11 +772,11 @@ main(int argc, goto done; /* Load yang Restconf stream discovery */ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") && - yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec, NULL)< 0) + yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec)< 0) goto done; /* Load yang Netconf stream discovery */ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && - yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec, NULL)< 0) + yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0) goto done; /* Set options: database dir and yangspec (could be hidden in connect?)*/ if (xmldb_setopt(h, "dbdir", clicon_xmldb_dir(h)) < 0) diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index feb5f158..b22e1c9f 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -250,15 +250,14 @@ main(int argc, char **argv) int logclisyntax = 0; int help = 0; char *treename = NULL; - int len; + // int len; int logdst = CLICON_LOG_STDERR; char *restarg = NULL; /* what remains after options */ int dump_configfile_xml = 0; yang_spec *yspec; yang_spec *yspecfg = NULL; /* For config XXX clixon bug */ struct passwd *pw; - char *yang_filename = NULL; - yang_stmt *ymod = NULL; /* Main module */ + char *str; /* Defaults */ once = 0; @@ -390,8 +389,8 @@ main(int argc, char **argv) case 'L' : /* Debug print dynamic CLI syntax */ logclisyntax++; break; - case 'y' :{ /* Load yang spec file (override yang main module) */ - yang_filename = optarg; + case 'y' :{ /* Load yang absolute filename */ + clicon_option_str_set(h, "CLICON_YANG_MAIN_FILE", optarg); break; } case 'c' :{ /* Overwrite clispec with absolute filename */ @@ -430,16 +429,24 @@ main(int argc, char **argv) goto done; clicon_dbspec_yang_set(h, yspec); - /* Load main application yang specification either module or specific file - * If -y is given, it overrides main module */ - if (yang_filename){ - if (yang_spec_parse_file(h, yang_filename, yspec, &ymod) < 0) + /* Load Yang modules + * 1. Load a yang module as a specific absolute filename */ + if ((str = clicon_yang_main_file(h)) != NULL){ + if (yang_spec_parse_file(h, str, yspec) < 0) goto done; } - else if (yang_spec_parse_module(h, clicon_yang_module_main(h), - clicon_yang_module_revision(h), - yspec, &ymod) < 0) - goto done; + /* 2. Load a (single) main module */ + if ((str = clicon_yang_module_main(h)) != NULL){ + if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h), + yspec) < 0) + goto done; + } + /* 3. Load all modules in a directory */ + if ((str = clicon_yang_main_dir(h)) != NULL){ + if (yang_spec_load_dir(h, str, yspec) < 0) + goto done; + } + /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) goto done; @@ -448,25 +455,22 @@ main(int argc, char **argv) /* Create tree generated from dataspec. If no other trees exists, this is * the only one. + * The following code creates the tree @datamodel + * This tree is referenced from the main CLI spec (CLICON_CLISPEC_DIR) + * using the "tree reference" + * syntax, ie @datamodel + * But note that yang2cli generates syntax for ALL modules, not just for + * . */ if (clicon_cli_genmodel(h)){ parse_tree pt = {0,}; /* cli parse tree */ - char *name; /* main module name */ + char *treeref; + treeref = clicon_cli_model_treename(h); /* Create cli command tree from dbspec */ if (yang2cli(h, yspec, &pt, clicon_cli_genmodel_type(h)) < 0) goto done; - - /* name of main module */ - name = ymod->ys_argument; - - len = strlen("datamodel:") + strlen(name) + 1; - if ((treename = malloc(len)) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - snprintf(treename, len, "datamodel:%s", name); - cligen_tree_add(cli_cligen(h), treename, pt); + cligen_tree_add(cli_cligen(h), treeref, pt); if (printgen) cligen_print(stdout, pt, 1); diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index 0fb58736..633c46ae 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -209,6 +209,30 @@ clixon_str2fn(char *name, return NULL; } +#ifdef CLICON_CLI_MODEL_TREENAME_PATCH +/*! Patch all CLI spec calls to @datamodel:tree to @datamodel. + * This is a backward compatible fix for 3.9 for CLIgen specification files + * using model generation (CLIXON_CLI_GENMODEL). + * All new references should use @datamodel (or CLICON_CLI_MODEL_TREENAME). + * whereas older code used @datamodel:tree. + */ +static int +mask_datamodel_fn(cg_obj *co, + void *arg) +{ + char *str = "datamodel:"; + int len = strlen(str); + if (co->co_type == CO_REFERENCE){ + + if (strlen(co->co_command) > len && + strncmp(co->co_command, "datamodel:", len)==0){ + co->co_command[len-1] = '\0'; + } + } + return 0; +} +#endif /* CLICON_CLI_MODEL_TREENAME_PATCH */ + /*! Append to syntax mode from file * @param[in] h Clixon handle * @param[in] filename Name of file where syntax is specified (in syntax-group dir) @@ -253,7 +277,10 @@ cli_load_syntax(clicon_handle h, goto done; } fclose(f); - +#ifdef CLICON_CLI_MODEL_TREENAME_PATCH + if (pt_apply(pt, mask_datamodel_fn, h) < 0) + goto done; +#endif /* Get CLICON specific global variables */ prompt = cvec_find_str(cvv, "CLICON_PROMPT"); plgnam = cvec_find_str(cvv, "CLICON_PLUGIN"); diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index aee1cf80..cc51d966 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -341,7 +341,7 @@ main(int argc, struct timeval tv = {0,}; /* timeout */ yang_spec *yspec = NULL; yang_spec *yspecfg = NULL; /* For config XXX clixon bug */ - char *yang_filename = NULL; + char *str; /* Create handle */ if ((h = clicon_handle_init()) == NULL) @@ -420,7 +420,7 @@ main(int argc, clicon_option_str_set(h, "CLICON_NETCONF_DIR", optarg); break; case 'y' :{ /* Load yang spec file (override yang main module) */ - yang_filename = optarg; + clicon_option_str_set(h, "CLICON_YANG_MAIN_FILE", optarg); break; } case 'U': /* Clixon 'pseudo' user */ @@ -444,16 +444,23 @@ main(int argc, if ((yspec = yspec_new()) == NULL) goto done; clicon_dbspec_yang_set(h, yspec); - /* Load main application yang specification either module or specific file - * If -y is given, it overrides main module */ - if (yang_filename){ - if (yang_spec_parse_file(h, yang_filename, yspec, NULL) < 0) + /* Load Yang modules + * 1. Load a yang module as a specific absolute filename */ + if ((str = clicon_yang_main_file(h)) != NULL){ + if (yang_spec_parse_file(h, str, yspec) < 0) + goto done; + } + /* 2. Load a (single) main module */ + if ((str = clicon_yang_module_main(h)) != NULL){ + if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h), + yspec) < 0) + goto done; + } + /* 3. Load all modules in a directory */ + if ((str = clicon_yang_main_dir(h)) != NULL){ + if (yang_spec_load_dir(h, str, yspec) < 0) goto done; } - else if (yang_spec_parse_module(h, clicon_yang_module_main(h), - clicon_yang_module_revision(h), - yspec, NULL) < 0) - goto done; /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 76fe7aab..d5067c4f 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -522,9 +522,9 @@ main(int argc, int logdst = CLICON_LOG_SYSLOG; yang_spec *yspec = NULL; yang_spec *yspecfg = NULL; /* For config XXX clixon bug */ - char *yang_filename = NULL; char *stream_path; int finish; + char *str; /* In the startup, logs to stderr & debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, logdst); @@ -601,7 +601,7 @@ main(int argc, clicon_option_str_set(h, "CLICON_RESTCONF_DIR", optarg); break; case 'y' : /* Load yang spec file (override yang main module) */ - yang_filename = optarg; + clicon_option_str_set(h, "CLICON_YANG_MAIN_FILE", optarg); break; case 'a': /* internal backend socket address family */ clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg); @@ -627,26 +627,34 @@ main(int argc, if ((yspec = yspec_new()) == NULL) goto done; clicon_dbspec_yang_set(h, yspec); - /* Load main application yang specification either module or specific file - * If -y is given, it overrides main module */ - if (yang_filename){ - if (yang_spec_parse_file(h, yang_filename, yspec, NULL) < 0) + + /* Load Yang modules + * 1. Load a yang module as a specific absolute filename */ + if ((str = clicon_yang_main_file(h)) != NULL){ + if (yang_spec_parse_file(h, str, yspec) < 0) + goto done; + } + /* 2. Load a (single) main module */ + if ((str = clicon_yang_module_main(h)) != NULL){ + if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h), + yspec) < 0) + goto done; + } + /* 3. Load all modules in a directory */ + if ((str = clicon_yang_main_dir(h)) != NULL){ + if (yang_spec_load_dir(h, str, yspec) < 0) goto done; } - else if (yang_spec_parse_module(h, clicon_yang_module_main(h), - clicon_yang_module_revision(h), - yspec, NULL) < 0) - goto done; /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) goto done; /* Add system modules */ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") && - yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec, NULL)< 0) + yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec)< 0) goto done; if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && - yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec, NULL)< 0) + yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0) goto done; /* Call start function in all plugins before we go interactive Pass all args after the standard options to plugin_start diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c index 165b4238..5722d497 100644 --- a/datastore/datastore_client.c +++ b/datastore/datastore_client.c @@ -195,7 +195,7 @@ main(int argc, char **argv) if ((yspec = yspec_new()) == NULL) goto done; /* Parse yang spec from given file */ - if (yang_parse(h, yangmodule, NULL, NULL, yspec, NULL) < 0) + if (yang_parse(h, yangmodule, NULL, NULL, yspec) < 0) goto done; /* Set database directory option */ if (xmldb_setopt(h, "dbdir", dbdir) < 0) diff --git a/doc/FAQ.md b/doc/FAQ.md index a5539c5a..9e54e304 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -73,8 +73,14 @@ clicon:x:1001:,www-data ``` ## What about reference documentation? -Clixon uses Doxygen for reference documentation. -Build using 'make doc' and aim your browser at doc/html/index.html. +Clixon uses [Doxygen](http://www.doxygen.nl/index.html) for reference documentation. +You need to install doxygen and graphviz on your system. +Build it in the doc directory and point the browser to `.../clixon/doc/html/index.html` as follows: +``` +> cd doc +> make doc +> make graphs # detailed callgraphs +``` ## How is configuration data stored? Configuration data is stored in an XML datastore. In the example the @@ -115,12 +121,15 @@ are included. The following configuration file options control the loading of Yang files: - `CLICON_YANG_DIR` - A list of directories (yang dir path) where Clixon searches for module and submodules. +- `CLICON_YANG_MAIN_DIR` - Load all yang modules in this directory. +- `CLICON_YANG_MAIN_FILE` - Load a specific Yang module fiven by a file. - `CLICON_YANG_MODULE_MAIN` - Specifies a single module to load. The module is searched for in the yang dir path. - `CLICON_YANG_MODULE_REVISION` : Specifies a revision to the main module. Note that the special `CLIXON_DATADIR`, by default `/usr/local/share/clixon` should be included in the yang dir path for Clixon system files to be found. -Application also has a command-line option `-y` to include a single Yang using absolute file path. This is mainly for debugging. +You can combine the options, however, more specific options override +less specific. For example, `CLICON_YANG_MAIN_FILE` overrides `CLICON_YANG_MODULE_MAIN`. ## How do I enable Yang features? diff --git a/example/example_cli.cli b/example/example_cli.cli index 1856c8c7..302a6cf3 100644 --- a/example/example_cli.cli +++ b/example/example_cli.cli @@ -7,10 +7,10 @@ CLICON_PLUGIN="example_cli"; translate value (),cli_set("/translate/value"); # Note, when switching to PT, change datamodel to only @datamodel -set @datamodel:example, cli_set(); -merge @datamodel:example, cli_merge(); -create @datamodel:example, cli_create(); -delete("Delete a configuration item") @datamodel:example, cli_del(); +set @datamodel, cli_set(); +merge @datamodel, cli_merge(); +create @datamodel, cli_create(); +delete("Delete a configuration item") @datamodel, cli_del(); validate("Validate changes"), cli_validate(); commit("Commit the changes"), cli_commit(); @@ -40,19 +40,19 @@ show("Show a particular state of the system"){ } configuration("Show configuration"), cli_show_config("candidate", "text", "/");{ xml("Show configuration as XML"), cli_show_config("candidate", "xml", "/");{ - @datamodel:example, cli_show_auto("candidate", "text"); + @datamodel, cli_show_auto("candidate", "text"); } cli("Show configuration as CLI commands"), cli_show_config("candidate", "cli", "/");{ - @datamodel:example, cli_show_auto("candidate", "cli"); + @datamodel, cli_show_auto("candidate", "cli"); } netconf("Show configuration as netconf edit-config operation"), cli_show_config("candidate", "netconf", "/");{ - @datamodel:example, cli_show_auto("candidate", "netconf"); + @datamodel, cli_show_auto("candidate", "netconf"); } text("Show configuration as text"), cli_show_config("candidate","text","/");{ - @datamodel:example, cli_show_auto("candidate", "text"); + @datamodel, cli_show_auto("candidate", "text"); } json("Show configuration as JSON"), cli_show_config("candidate", "json", "/");{ - @datamodel:example, cli_show_auto("candidate", "json"); + @datamodel, cli_show_auto("candidate", "json"); } } } diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 8bad0f92..cbb3f1df 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -51,3 +51,15 @@ int strverscmp (__const char *__s1, __const char *__s2); */ #define XMLNS_YANG_ONLY 1 +/* Set for full XML namespace code in XML, NETCONF and YANG + * Experimental + */ +#undef ENABLE_XMLNS + +/* If set, patch all CLI spec calls to @datamodel:tree to @datamodel. + * This is a backward compatible fix for 3.9 for CLIgen specification files + * using model generation (CLIXON_CLI_GENMODEL). + * All new references should use @datamodel (or CLICON_CLI_MODEL_TREENAME). + * whereas older code used @datamodel:tree. + */ +#define CLICON_CLI_MODEL_TREENAME_PATCH diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index d774fe29..1f4069fc 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -105,6 +105,12 @@ int clicon_option_del(clicon_handle h, const char *name); static inline char *clicon_configfile(clicon_handle h){ return clicon_option_str(h, "CLICON_CONFIGFILE"); } +static inline char *clicon_yang_main_file(clicon_handle h){ + return clicon_option_str(h, "CLICON_YANG_MAIN_FILE"); +} +static inline char *clicon_yang_main_dir(clicon_handle h){ + return clicon_option_str(h, "CLICON_YANG_MAIN_DIR"); +} static inline char *clicon_yang_module_main(clicon_handle h){ return clicon_option_str(h, "CLICON_YANG_MODULE_MAIN"); } @@ -129,6 +135,9 @@ static inline char *clicon_clispec_dir(clicon_handle h){ static inline char *clicon_cli_mode(clicon_handle h){ return clicon_option_str(h, "CLICON_CLI_MODE"); } +static inline char *clicon_cli_model_treename(clicon_handle h){ + return clicon_option_str(h, "CLICON_CLI_MODEL_TREENAME"); +} static inline char *clicon_sock(clicon_handle h){ return clicon_option_str(h, "CLICON_SOCK"); } diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 222f3f91..a49d1e94 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -256,7 +256,7 @@ int yang_nodeid_split(char *nodeid, char **prefix, char **id); yang_stmt *ys_module(yang_stmt *ys); yang_spec *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); -yang_stmt *yang_find(yang_node *yn, int keyword, char *argument); +yang_stmt *yang_find(yang_node *yn, int keyword, const char *argument); int yang_match(yang_node *yn, int keyword, char *argument); yang_stmt *yang_find_datanode(yang_node *yn, char *argument); yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); @@ -269,7 +269,7 @@ int ys_populate(yang_stmt *ys, void *arg); yang_stmt *yang_parse_file(int fd, const char *name, yang_spec *ysp); int yang_parse(clicon_handle h, const char *filename, const char *module, - const char *revision, yang_spec *ysp, yang_stmt **ymodp); + const char *revision, yang_spec *ysp); int yang_apply(yang_node *yn, enum rfc_6020 key, yang_applyfn_t fn, void *arg); int yang_abs_schema_nodeid(yang_spec *yspec, yang_stmt *ys, @@ -281,8 +281,9 @@ cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype); int ys_parse_sub(yang_stmt *ys, char *extra); int yang_mandatory(yang_stmt *ys); int yang_config(yang_stmt *ys); -int yang_spec_parse_module(clicon_handle h, char *module, char *revision, yang_spec *yspec, yang_stmt **ymodp); -int yang_spec_parse_file(clicon_handle h, char *filename, yang_spec *yspec, yang_stmt **ymodp); +int yang_spec_parse_module(clicon_handle h, char *module, char *revision, yang_spec *yspec); +int yang_spec_parse_file(clicon_handle h, char *filename, yang_spec *yspec); +int yang_spec_load_dir(clicon_handle h, char *dir, yang_spec *yspec); cvec *yang_arg2cvec(yang_stmt *ys, char *delimi); int yang_key_match(yang_node *yn, char *name); diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index c2a25f25..27c0acc0 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -988,9 +988,9 @@ netconf_module_load(clicon_handle h) yspec = clicon_dbspec_yang(h); /* Load yang spec */ - if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec, NULL)< 0) + if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0) goto done; - if (yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec, NULL)< 0) + if (yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0) goto done; if ((xc = clicon_conf_xml(h)) == NULL){ clicon_err(OE_CFG, ENOENT, "Clicon configuration not loaded"); diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 5804f182..9c737b18 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -252,7 +252,7 @@ clicon_options_main(clicon_handle h, /* Set clixon_conf pointer to handle */ clicon_conf_xml_set(h, xconfig); /* Parse clixon yang spec */ - if (yang_parse(h, NULL, "clixon-config", NULL, yspec, NULL) < 0) + if (yang_parse(h, NULL, "clixon-config", NULL, yspec) < 0) goto done; clicon_conf_xml_set(h, NULL); if (xconfig) diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 7710ae80..03ed2b29 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -269,8 +269,7 @@ clixon_plugins_load(clicon_handle h, clicon_debug(1, "%s", __FUNCTION__); /* Get plugin objects names from plugin directory */ - if((ndp = clicon_file_dirent(dir, &dp, - regexp?regexp:"(.so)$", S_IFREG))<0) + if((ndp = clicon_file_dirent(dir, &dp, regexp?regexp:"(.so)$", S_IFREG)) < 0) goto done; /* Load all plugins */ for (i = 0; i < ndp; i++) { diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index a86c7276..7072ed01 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -315,7 +315,7 @@ xml_parse_attr(struct xml_parse_yacc_arg *ya, int retval = -1; cxobj *xa; -#ifdef notyet +#ifdef ENABLE_XMLNS if (prefix && strcmp(prefix,"xmlns")==0) fprintf(stderr, "PrefixedAttName NCNAME:%s = %s\n", name, attval); if (prefix==NULL && strcmp(name,"xmlns")==0) diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index a7033ce2..2a6b1572 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -46,7 +46,7 @@ #include #include #include -#define __USE_GNU /* strverscmp */ +#define __USE_GNU /* strverscmp */ #include #include #include @@ -56,6 +56,7 @@ #include #include #include +#include #include /* cligen */ @@ -416,7 +417,7 @@ yn_each(yang_node *yn, yang_stmt * yang_find(yang_node *yn, int keyword, - char *argument) + const char *argument) { yang_stmt *ys = NULL; int i; @@ -1939,7 +1940,6 @@ yang_parse_find_match(clicon_handle h, S_IFREG)) < 0) goto done; /* Entries are sorted, last entry should be most recent date - * Found */ if (ndp != 0){ cprintf(fbuf, "%s/%s", dir, dp[ndp-1].d_name); @@ -2278,7 +2278,6 @@ yang_merge_submodules(clicon_handle h, * @param[in] module Name of main YANG module. Or absolute file name. * @param[in] revision Main module revision date string or NULL * @param[in,out] ysp Yang specification. Should have been created by caller using yspec_new - * @param[out] ymodp Yang module of first, topmost Yang module, if given. * @retval 0 Everything OK * @retval -1 Error encountered * The database symbols are inserted in alphabetical order. @@ -2298,72 +2297,89 @@ yang_parse(clicon_handle h, const char *filename, const char *module, const char *revision, - yang_spec *ysp, - yang_stmt **ymodp) + yang_spec *yspec) { int retval = -1; yang_stmt *ymod = NULL; /* Top-level yang (sub)module */ int i; int modnr; /* Existing number of modules */ + char *base = NULL;; /* Apply steps 2.. on new modules, ie ones after modnr. */ - modnr = ysp->yp_len; + modnr = yspec->yp_len; if (filename){ - if ((ymod = yang_parse_filename(filename, ysp)) == NULL) + /* Find module, and do not load file if module already exists */ + if (basename(filename) == NULL){ + clicon_err(OE_YANG, errno, "No basename"); + goto done; + } + if ((base = strdup(basename(filename))) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + if (index(base, '@') != NULL) + *index(base, '@') = '\0'; + if (yang_find((yang_node*)yspec, Y_MODULE, base) != NULL) + goto ok; + if ((ymod = yang_parse_filename(filename, yspec)) == NULL) goto done; } - else - if ((ymod = yang_parse_module(h, module, revision, ysp)) == NULL) + else { + /* Do not load module if it already exists */ + if (yang_find((yang_node*)yspec, Y_MODULE, module) != NULL) + goto ok; + if ((ymod = yang_parse_module(h, module, revision, yspec)) == NULL) goto done; + } /* 1: Parse from text to yang parse-tree. */ /* Iterate through modules */ - if (yang_parse_recurse(h, ymod, ysp) < 0) + if (yang_parse_recurse(h, ymod, yspec) < 0) goto done; /* 2. Check cardinality maybe this should be done after grouping/augment */ - for (i=modnr; iyp_len; i++) /* XXX */ - if (yang_cardinality(h, ysp->yp_stmt[i], ysp->yp_stmt[i]->ys_argument) < 0) + for (i=modnr; iyp_len; i++) /* XXX */ + if (yang_cardinality(h, yspec->yp_stmt[i], yspec->yp_stmt[i]->ys_argument) < 0) goto done; /* 3: Merge sub-modules with modules - after this step, no submodules exist * In the merge, remove submodule headers */ - for (i=modnr; iyp_len; i++){ - if (ysp->yp_stmt[i]->ys_keyword != Y_SUBMODULE) + for (i=modnr; iyp_len; i++){ + if (yspec->yp_stmt[i]->ys_keyword != Y_SUBMODULE) continue; } i = 0; - while (iyp_len){ + while (iyp_len){ int j; - if (ysp->yp_stmt[i]->ys_keyword != Y_SUBMODULE){ + if (yspec->yp_stmt[i]->ys_keyword != Y_SUBMODULE){ i++; continue; } - if (yang_merge_submodules(h, ysp, ysp->yp_stmt[i]) < 0) + if (yang_merge_submodules(h, yspec, yspec->yp_stmt[i]) < 0) goto done; /* shift down one step */ - for (j=i; jyp_len-1; j++) - ysp->yp_stmt[j] = ysp->yp_stmt[j+1]; - ysp->yp_len--; + for (j=i; jyp_len-1; j++) + yspec->yp_stmt[j] = yspec->yp_stmt[j+1]; + yspec->yp_len--; } /* 4: Check features: check if enabled and remove disabled features */ - for (i=modnr; iyp_len; i++) /* XXX */ - if (yang_features(h, ysp->yp_stmt[i]) < 0) + for (i=modnr; iyp_len; i++) /* XXX */ + if (yang_features(h, yspec->yp_stmt[i]) < 0) goto done; /* 5: Go through parse tree and populate it with cv types */ - for (i=modnr; iyp_len; i++) - if (yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_populate, (void*)h) < 0) + for (i=modnr; iyp_len; i++) + if (yang_apply((yang_node*)yspec->yp_stmt[i], -1, ys_populate, (void*)h) < 0) goto done; /* 6: Resolve all types: populate type caches. Requires eg length/range cvecs * from ys_populate step. * Must be done using static binding. */ - for (i=modnr; iyp_len; i++) - if (yang_apply((yang_node*)ysp->yp_stmt[i], Y_TYPE, ys_resolve_type, NULL) < 0) + for (i=modnr; iyp_len; i++) + if (yang_apply((yang_node*)yspec->yp_stmt[i], Y_TYPE, ys_resolve_type, NULL) < 0) goto done; /* Up to here resolving is made in the context they are defined, rather @@ -2374,25 +2390,26 @@ yang_parse(clicon_handle h, */ /* 7: Macro expansion of all grouping/uses pairs. Expansion needs marking */ - for (i=modnr; iyp_len; i++){ - if (yang_expand((yang_node*)ysp->yp_stmt[i]) < 0) + for (i=modnr; iyp_len; i++){ + if (yang_expand((yang_node*)yspec->yp_stmt[i]) < 0) goto done; - yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_flag_reset, (void*)YANG_FLAG_MARK); + yang_apply((yang_node*)yspec->yp_stmt[i], -1, ys_flag_reset, (void*)YANG_FLAG_MARK); } /* 8: Top-level augmentation of all modules XXX: only new modules? */ - if (yang_augment_spec(ysp) < 0) + if (yang_augment_spec(yspec) < 0) goto done; /* 9: sanity check of schemanode references, need more here */ - for (i=modnr; iyp_len; i++) - if (yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_schemanode_check, NULL) < 0) + for (i=modnr; iyp_len; i++) + if (yang_apply((yang_node*)yspec->yp_stmt[i], -1, ys_schemanode_check, NULL) < 0) goto done; /* Return main module parsed in step 1 */ - if (ymodp) - *ymodp = ymod; + ok: retval = 0; - done: + done: + if (base) + free(base); return retval; } @@ -2792,7 +2809,6 @@ yang_config(yang_stmt *ys) * @param[in] dir Directory where to look for modules and sub-modules * @param[in] revision Revision, or NULL * @param[in,out] yspec Modules parse are added to this yangspec - * @param[out] ymodp Yang module of first, topmost Yang module, if given. * @retval 0 OK * @retval -1 Error * @see yang_spec_parse_file @@ -2801,8 +2817,7 @@ int yang_spec_parse_module(clicon_handle h, char *module, char *revision, - yang_spec *yspec, - yang_stmt **ymodp) + yang_spec *yspec) { int retval = -1; @@ -2819,7 +2834,7 @@ yang_spec_parse_module(clicon_handle h, clicon_err(OE_YANG, EINVAL, "yang module illegal format"); goto done; } - if (yang_parse(h, NULL, module, revision, yspec, ymodp) < 0) + if (yang_parse(h, NULL, module, revision, yspec) < 0) goto done; retval = 0; done: @@ -2831,7 +2846,6 @@ yang_spec_parse_module(clicon_handle h, * @param[in] filename Actual filename (including dir and revision) * @param[in] dir Directory for sub-modules * @param[in,out] yspec Modules parse are added to this yangspec - * @param[out] ymodp Yang module of first, topmost Yang module, if given. * @retval 0 OK * @retval -1 Error * @see yang_spec_parse_module for yang dir,module,revision instead of actual filename @@ -2839,8 +2853,7 @@ yang_spec_parse_module(clicon_handle h, int yang_spec_parse_file(clicon_handle h, char *filename, - yang_spec *yspec, - yang_stmt **ymodp) + yang_spec *yspec) { int retval = -1; @@ -2848,13 +2861,62 @@ yang_spec_parse_file(clicon_handle h, clicon_err(OE_YANG, EINVAL, "yang spec is NULL"); goto done; } - if (yang_parse(h, filename, NULL, NULL, yspec, ymodp) < 0) + if (yang_parse(h, filename, NULL, NULL, yspec) < 0) goto done; retval = 0; done: return retval; } +/*! Load all yang modules in directory + * @param[in] h Clicon handle + * @param[in] dir Load all yang modules in this directory + * @param[in,out] yspec Modules parse are added to this yangspec + * @retval 0 OK + * @retval -1 Error + */ +int +yang_spec_load_dir(clicon_handle h, + char *dir, + yang_spec *yspec) +{ + int retval = -1; + int ndp; + struct dirent *dp = NULL; + int i; + char filename[MAXPATHLEN]; + char *base; + char *b; + int j; + int len; + + /* Get plugin objects names from plugin directory */ + if((ndp = clicon_file_dirent(dir, &dp, "(.yang)$", S_IFREG)) < 0) + goto done; + /* Load all yang files */ + for (i = 0; i < ndp; i++) { + base = dp[i].d_name; + /* Entries are sorted, see if later entry exists (include @), if so skip + * this one and take last. + */ + if ((b = index(base, '@')) != NULL) + len = b-base; + else + len = strlen(base); + for (j = (i+1); j < ndp; j++) + if (strncmp(base, dp[j].d_name, len) == 0) + break; + if (j[@]"; @@ -224,9 +234,18 @@ module clixon-config { type int32; default 1; description - "Generate code for CLI completion of existing db symbols. - Example: Add name=\"myspec\" in datamodel spec and reference - as @myspec"; + "If set, generate CLI specification for CLI completion of + loaded Yang modules. This CLI tree can be accessed in CLI + spec files using the tree reference syntax (eg @datamodel). + See also CLICON_CLI_MODEL_TREENAME."; + } + leaf CLICON_CLI_MODEL_TREENAME { + type string; + default "datamodel"; + description + "If CLICON_CLI_GENMODEL is set, CLI specs can reference the + model syntax using this reference. + Example: set @datamodel, cli_set();"; } leaf CLICON_CLI_GENMODEL_COMPLETION { type int32;