From 8494d29d6b4474887f8bb3dbba448a8d25a39704 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 25 Sep 2017 22:05:26 +0200 Subject: [PATCH] config file has .xml ending, yang/clixon-config.yang is used as model for an xml-based configuration file --- CHANGELOG.md | 3 +- example/Makefile.in | 4 +- example/routing.xml | 17 +++ include/clixon_custom.h | 6 -- lib/clixon/clixon_xml.h | 7 +- lib/src/clixon_options.c | 160 +++++++++++++++++------------ lib/src/clixon_xml.c | 31 +++--- lib/src/clixon_xml_map.c | 10 -- yang/clixon-config@2017-07-02.yang | 23 +++-- 9 files changed, 154 insertions(+), 107 deletions(-) create mode 100644 example/routing.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b35ff68..e22a609d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Clixon CHANGELOG ## 3.3.3 Upcoming -* Added experimental reading of yang and xml-based config file. +* If clixon config file has .xml ending, yang/clixon-config.yang is used as + model for an xml-based configuration file. Otherwise legacy format is used. * netconf client was limited to 8K byte messages. Now limit is 2^32. * Added event_poll function. * Added experimental xml hash for better performance of large lists. diff --git a/example/Makefile.in b/example/Makefile.in index 6c94b660..5c5c11ea 100644 --- a/example/Makefile.in +++ b/example/Makefile.in @@ -98,9 +98,10 @@ distclean: clean rm -f Makefile *~ .depend (cd docker && $(MAKE) $(MFLAGS) $@) -install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(APPNAME).conf +install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(APPNAME).conf $(APPNAME).xml install -d $(DESTDIR)$(clixon_SYSCONFDIR) install $(APPNAME).conf $(DESTDIR)$(clixon_SYSCONFDIR) + install $(APPNAME).xml $(DESTDIR)$(clixon_SYSCONFDIR) install -d $(DESTDIR)$(clixon_DBSPECDIR)/yang install $(YANGSPECS) $(DESTDIR)$(clixon_DBSPECDIR)/yang install -d $(DESTDIR)$(clixon_LIBDIR)/cli @@ -116,6 +117,7 @@ install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $ uninstall: rm -rf $(DESTDIR)$(clixon_SYSCONFDIR)/$(APPNAME).conf + rm -rf $(DESTDIR)$(clixon_SYSCONFDIR)/$(APPNAME).xml rm -rf $(DESTDIR)$(clixon_DBSPECDIR) rm -rf $(DESTDIR)$(clixon_LOCALSTATEDIR) rm -rf $(DESTDIR)$(clixon_LIBDIR) diff --git a/example/routing.xml b/example/routing.xml new file mode 100644 index 00000000..71625ec8 --- /dev/null +++ b/example/routing.xml @@ -0,0 +1,17 @@ + + /usr/local/etc/routing.conf + /usr/local/share/routing/yang + example + routing + /usr/local/lib/routing/backend> + /usr/local/lib/routing/netconf + /usr/local/lib/routing/restconf + /usr/local/lib/routing/cli + /usr/local/lib/routing/clispec + /usr/local/var/routing/routing.sock + /usr/local/var/routing/routing.pidfile + 1 + /usr/local/var/routing + /usr/local/lib/xmldb/text.so + 0 + diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 6e2dc925..f514230f 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -43,12 +43,6 @@ int strverscmp (__const char *__s1, __const char *__s2); #endif -/* Replace the current clixon.conf.cpp.cpp options with XML file with Yang - * Why not use the config mechanisms that CLixon uses for its own config-file? - * Experimental - */ -#define CONFIGFILE_XML 0 - /* Hash for XML trees list entries * Experimental */ diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index e70abe8e..0ff7903f 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -57,7 +57,12 @@ enum cxobj_type {CX_ERROR=-1, typedef struct xml cxobj; /* struct defined in clicon_xml.c */ -/*! Callback function type for xml_apply */ +/*! Callback function type for xml_apply + * @retval -1 Error, aborted at first error encounter + * @retval 0 OK, continue + * @retval 1 Abort, dont continue with others + * @retval 2 Locally, just abort this subtree, continue with others + */ typedef int (xml_applyfn_t)(cxobj *yn, void *arg); /* diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 035ba187..6f26e61d 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -65,11 +65,10 @@ #include "clixon_yang.h" #include "clixon_plugin.h" #include "clixon_options.h" -#if CONFIGFILE_XML #include "clixon_xml.h" #include "clixon_xsl.h" #include "clixon_xml_map.h" -#endif + /* * clicon_option_dump * Print registry on file. For debugging. @@ -105,23 +104,89 @@ clicon_option_dump(clicon_handle h, int dbglevel) } +/*! Read filename and set values to global options registry. XML variant. + * @see clicon_option_readfile + */ +static int +clicon_option_readfile_xml(clicon_hash_t *copt, + const char *filename, + yang_spec *yspec) +{ + struct stat st; + FILE *f = NULL; + int retval = -1; + int fd; + cxobj *xt = NULL; + cxobj *xc = NULL; + cxobj *x = NULL; + char *name; + char *body; + + if (filename == NULL || !strlen(filename)){ + clicon_err(OE_UNIX, 0, "Not specified"); + goto done; + } + if (stat(filename, &st) < 0){ + clicon_err(OE_UNIX, errno, "%s", filename); + goto done; + } + if (!S_ISREG(st.st_mode)){ + clicon_err(OE_UNIX, 0, "%s is not a regular file", filename); + goto done; + } + if ((f = fopen(filename, "r")) == NULL) { + clicon_err(OE_UNIX, errno, "configure file: %s", filename); + return -1; + } + clicon_debug(2, "Reading config file %s", __FUNCTION__, filename); + fd = fileno(f); + if (clicon_xml_parse_file(fd, &xt, "") < 0) + goto done; + if (xml_child_nr(xt)==1 && xml_child_nr_type(xt, CX_BODY)==1){ + clicon_err(OE_CFG, 0, "Config file %s: Expected XML but is probably old sh style", filename); + goto done; + } + if ((xc = xpath_first(xt, "config")) == NULL) { + clicon_err(OE_CFG, 0, "Config file %s: Lacks top-level \"config\" element", filename); + goto done; + } + if (xml_apply0(xc, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; + if (xml_apply0(xc, CX_ELMNT, xml_default, yspec) < 0) + goto done; + if (xml_apply0(xc, CX_ELMNT, xml_yang_validate_add, NULL) < 0) + goto done; + while ((x = xml_child_each(xc, x, CX_ELMNT)) != NULL) { + name = xml_name(x); + body = xml_body(x); + if (name && body && + (hash_add(copt, + name, + body, + strlen(body)+1)) == NULL) + goto done; + } + retval = 0; + done: + if (f) + fclose(f); + return retval; +} + + /*! Read filename and set values to global options registry + * For legacy configuration file, ie not xml + * @see clicon_option_readfile_xml */ static int clicon_option_readfile(clicon_hash_t *copt, - const char *filename -#if CONFIGFILE_XML - ,yang_spec *yspec -#endif - ) + const char *filename) { struct stat st; -#if !CONFIGFILE_XML char opt[1024]; char val[1024]; char line[1024]; char *cp; -#endif FILE *f = NULL; int retval = -1; @@ -142,39 +207,6 @@ clicon_option_readfile(clicon_hash_t *copt, return -1; } clicon_debug(2, "Reading config file %s", __FUNCTION__, filename); -#if CONFIGFILE_XML - { - int fd = fileno(f); - cxobj *xt = NULL; - cxobj *xc = NULL; - cxobj *x = NULL; - char *name; - char *body; - - if (clicon_xml_parse_file(fd, &xt, "") < 0) - goto done; - if (xml_child_nr(xt)==1 && xml_child_nr_type(xt, CX_BODY)==1){ - clicon_err(OE_CFG, 0, "Config file %s: Expected XML but is probably old sh style", filename); - goto done; - } - if ((xc = xpath_first(xt, "config")) == NULL) { - clicon_err(OE_CFG, 0, "Config file %s: Lacks top-level \"config\" element", filename); - goto done; - } - if (xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) < 0) - goto done; - while ((x = xml_child_each(xc, x, CX_ELMNT)) != NULL) { - name = xml_name(x); - body = xml_body(x); - if (name && body && - (hash_add(copt, - name, - body, - strlen(body)+1)) == NULL) - goto done; - } - } -#else while (fgets(line, sizeof(line), f)) { if ((cp = strchr(line, '\n')) != NULL) /* strip last \n */ *cp = '\0'; @@ -189,7 +221,6 @@ clicon_option_readfile(clicon_hash_t *copt, strlen(val)+1)) == NULL) goto done; } -#endif retval = 0; done: if (f) @@ -197,7 +228,6 @@ clicon_option_readfile(clicon_hash_t *copt, return retval; } -#if !CONFIGFILE_XML /*! Set default values of some options that may not appear in config-file */ static int @@ -256,6 +286,7 @@ clicon_option_default(clicon_hash_t *copt) } /*! Check that options are set + * XXX dont detect extra XML */ static int clicon_option_sanity(clicon_hash_t *copt) @@ -302,7 +333,6 @@ clicon_option_sanity(clicon_hash_t *copt) done: return retval; } -#endif /* !CONFIGFILE_XML */ /*! Initialize option values @@ -316,6 +346,9 @@ clicon_options_main(clicon_handle h) int retval = -1; char *configfile; clicon_hash_t *copt = clicon_options(h); + char *suffix; + char xml = 0; /* Configfile is xml, otherwise legacy */ + yang_spec *yspec; /* * Set configure file if not set by command-line above @@ -326,36 +359,33 @@ clicon_options_main(clicon_handle h) } configfile = hash_value(copt, "CLICON_CONFIGFILE", NULL); clicon_debug(1, "CLICON_CONFIGFILE=%s", configfile); - - -#if CONFIGFILE_XML - /* Read clixon yang file */ - { - yang_spec *yspec; - + /* If file ends with .xml, assume it is new format */ + if ((suffix = rindex(configfile, '.')) != NULL){ + suffix++; + xml = strcmp(suffix,"xml") == 0; + } + if (xml){ /* Read clixon yang file */ if ((yspec = yspec_new()) == NULL) goto done; if (yang_parse(h, CLIXON_DATADIR, "clixon-config", NULL, yspec) < 0) goto done; /* Read configfile */ - if (clicon_option_readfile(copt, configfile, yspec) < 0) + if (clicon_option_readfile_xml(copt, configfile, yspec) < 0) + goto done; + } + else { + /* Set default options */ + if (clicon_option_default(copt) < 0) /* init registry from file */ + goto done; + /* Read configfile */ + if (clicon_option_readfile(copt, configfile) < 0) + goto done; + if (clicon_option_sanity(copt) < 0) goto done; } -#else - /* Set default options */ - if (clicon_option_default(copt) < 0) /* init registry from file */ - goto done; - /* Read configfile */ - if (clicon_option_readfile(copt, configfile) < 0) - goto done; - if (clicon_option_sanity(copt) < 0) - goto done; -#endif - retval = 0; done: return retval; - } /*! Check if a clicon option has a value diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index d733b0d3..585e1934 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -1445,8 +1445,8 @@ cxvec_append(cxobj *x, * @param[in] fn Callback * @param[in] arg Argument * @retval -1 Error, aborted at first error encounter - * @retval 0 OK, all nodes traversed - * @retval n OK, aborted at first encounter of first match + * @retval 0 OK, all nodes traversed (subparts may have been skipped) + * @retval 1 OK, aborted on first fn returned 1 * * @code * int x_fn(cxobj *x, void *arg) @@ -1471,12 +1471,18 @@ xml_apply(cxobj *xn, x = NULL; while ((x = xml_child_each(xn, x, type)) != NULL) { - if (fn(x, arg) < 0) + if ((ret = fn(x, arg)) < 0) goto done; + if (ret == 2) + continue; /* Abort this node, dont recurse */ + else if (ret == 1){ + retval = 1; + goto done; + } if ((ret = xml_apply(x, type, fn, arg)) < 0) goto done; - if (ret > 0){ - retval = ret; + if (ret == 1){ + retval = 1; goto done; } } @@ -1487,10 +1493,9 @@ xml_apply(cxobj *xn, /*! Apply a function call on top object and all xml node children recursively * @retval -1 Error, aborted at first error encounter - * @retval 0 OK, all nodes traversed - * @retval n OK, aborted at first encounter of first match + * @retval 0 OK, all nodes traversed (subparts may have been skipped) + * @retval 1 OK, aborted on first fn returned 1 * @see xml_apply not including top object - */ int xml_apply0(cxobj *xn, @@ -1501,11 +1506,13 @@ xml_apply0(cxobj *xn, int retval = -1; int ret; - if ((ret = fn(xn, arg)) < 0) + if ((ret = fn(xn, arg)) < 0) /* -1, 0, 1, 2 */ goto done; - if (ret > 0) - retval = ret; - else + if (ret == 1) + retval = 1; + else if (ret > 1) + retval = 0; + else /* 0 */ retval = xml_apply(xn, type, fn, arg); done: return retval; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 1c8abcb4..959b6ea8 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1476,16 +1476,6 @@ xml_spec_populate(cxobj *x, y = yang_find_datanode((yang_node*)yp, xml_name(x)); else y = yang_find_topnode(yspec, name, 0); /* still NULL for config */ - //#ifdef XXX_OBSOLETE /* Add validate elsewhere */ -#if 0 - if (y==NULL){ - clicon_err(OE_XML, EBADF, "yang spec not found for xml node '%s' xml parent name: '%s' yangspec:'%s']", - name, - xp?xml_name(xp):"", yp?yp->ys_argument:""); - return -1; // XXX - goto done; - } -#endif if (y) xml_spec_set(x, y); retval = 0; diff --git a/yang/clixon-config@2017-07-02.yang b/yang/clixon-config@2017-07-02.yang index bc5be699..668e37c7 100644 --- a/yang/clixon-config@2017-07-02.yang +++ b/yang/clixon-config@2017-07-02.yang @@ -42,14 +42,14 @@ description "Initial revision"; } + container config { leaf CLICON_CONFIGFILE{ type string; - default "sysconfdir/$APPNAME.conf"; description "Location of configuration-file for default values (this file)"; } leaf CLICON_YANG_DIR { type string; - default "prefix/share/$APPNAME/yang"; + mandatory true; description "Location of YANG module and submodule files. Only if CLICON_DBSPEC_TYPE is YANG"; } leaf CLICON_YANG_MODULE_MAIN { @@ -66,27 +66,27 @@ } leaf CLICON_BACKEND_DIR { type string; - default "libdir/$APPNAME/backend"; + mandatory true; description "Location of backend .so plugins"; } leaf CLICON_NETCONF_DIR { type string; - default "libdir/$APPNAME/netconf"; + mandatory true; description "Location of netconf (frontend) .so plugins"; } leaf CLICON_RESTCONF_DIR { type string; - default "libdir/$APPNAME/restconf"; + mandatory true; description "Location of restconf (frontend) .so plugins"; } leaf CLICON_CLI_DIR { type string; - default "libdir/$APPNAME/cli"; + mandatory true; description "Location of cli frontend .so plugins"; } leaf CLICON_CLISPEC_DIR { type string; - default "libdir/$APPNAME/clispec"; + mandatory true; description "Location of frontend .cli cligen spec files"; } leaf CLICON_USE_STARTUP_CONFIG { @@ -101,7 +101,7 @@ } leaf CLICON_SOCK { type string; - default "localstatedir/$APPNAME/$APPNAME.sock"; + mandatory true; description "If family above is AF_UNIX: Unix socket for communicating with clixon_backend. If family above is AF_INET: IPv4 address"; } @@ -112,7 +112,7 @@ } leaf CLICON_BACKEND_PIDFILE { type string; - default "localstatedir/$APPNAME/$APPNAME.pidfile"; + mandatory true; description "Process-id file"; } leaf CLICON_SOCK_GROUP { @@ -156,12 +156,12 @@ } leaf CLICON_XMLDB_DIR { type string; - default "localstatedir/$APPNAME"; + mandatory true; description "Directory where \"running\", \"candidate\" and \"startup\" are placed"; } leaf CLICON_XMLDB_PLUGIN { type string; - default "libdir/xmldb/text.so"; + mandatory true; description "XMLDB datastore plugin filename (see datastore/ and clixon_xml_db.[ch])"; } leaf CLICON_CLI_VARONLY { @@ -183,3 +183,4 @@ Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock;"; } } +}