diff --git a/CHANGELOG.md b/CHANGELOG.md index 69f0ce26..ff9c8650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,104 @@ # Clixon Changelog +## 3.7.0 (22 July 2018) + +### Major New features + +* YANG "must" and "when" Xpath-basedconstraints according to RFC 7950 Sec 7.5.3 and 7.21.5. + * Must and when Xpath constrained checked at validation/commit. +* YANG "identity" and "identityref", according to RFC 7950 Sec 7.18 and 9.10. + * Identities checked at validation/commit. + * CLI completion support of identity values. + * Example extended with iana-if-type RFC 7224 interface identities. +* Improved support for XPATH 1.0 according to https://www.w3.org/TR/xpath-10 using yacc/lex, + * Full suport of boolean constraints for "when"/"must", not only nodesets. + * See also API changes below. +* CDATA XML support (patch by David Cornejo, Netgate) + * Encode and decode (parsing) support +* Added cligen variable translation. Useful for processing input such as hashing, encryption. + * More info in example and FAQ. + * Example: +``` +cli> translate value HAL +cli> show configuration +translate { + value IBM; +} +``` +### API changes on existing features (you may need to change your code) + +* YANG identity, identityref, must, and when validation support may cause applications that had not strictly enforced identities and constraints before. +* Restconf operations changed from prefix to module name. + * Proper specification for an operation is POST /restconf/operations/: HTTP/1.1 + * See https://github.com/clicon/clixon/issues/31, https://github.com/clicon/clixon/pull/32 and https://github.com/clicon/clixon/issues/30 + * Thanks David Cornejo and Dmitry Vakhrushev of Netgate for pointing this out. +* New XPATH 1.0 leads to some API changes and corrections + * Due to an error in the previous implementation, all XPATH calls on the form `x[a=str]` where `str` is a string (not a number or XML symbol), must be changed to: `x[a='str'] or x[a="str"]` + * This includes all calls to `xpath_vec, xpath_first`, etc. + * In CLI specs, calls to cli_copy_config() must change 2nd argument from `x[%s=%s]` to `x[%s='%s']` + * In CLI specs, calls to cli_show_config() may need to change third argument, eg + * `cli_show_config("running","text","/profile[name=%s]","name")` to `cli_show_config("running","text","/profile[name='%s']","name")` + * xpath_each() is removed + * The old API can be enabled by setting COMPAT_XSL in include/clixon_custom.h and recompile. + +* Makefile change: Removed the make include file: clixon.mk and clixon.mk.in + * These generated the Makefile variables: clixon_DBSPECDIR, clixon_SYSCONFDIR, clixon_LOCALSTATEDIR, clixon_LIBDIR, clixon_DATADIR which have been replaced by generic autoconf variables instead. + +* Removed cli callback vector functions. Set COMPAT_CLIV if you need to keep these functions in include/clixon_custom.h. + * Replace functions as follows in CLI SPEC files: + * cli_setv --> cli_set + * cli_mergev --> cli_merge + * cli_delv --> cli_del + * cli_debug_cliv --> cli_debug_cli + * cli_debug_backendv --> cli_debug_backend + * cli_set_modev --> cli_set_mode + * cli_start_shellv --> cli_start_shell + * cli_quitv --> cli_quit + * cli_commitv --> cli_commit + * cli_validatev --> cli_validate + * compare_dbsv --> compare_dbs + * load_config_filev --> load_config_file + * save_config_filev --> save_config_file + * delete_allv --> delete_all + * discard_changesv --> discard_changes + * cli_notifyv --> cli_notify + * show_yangv --> show_yang + * show_confv_xpath --> show_conf_xpath + +* Changed `plugin_init()` backend return semantics: If returns NULL, _without_ calling clicon_err(), the module is disabled. + +### Minor changes + +* Clixon docker upgrade + * Updated the docker image build and example to a single clixon docker image. + * Example pushed to https://hub.docker.com/r/olofhagsand/clixon_example/ +* Added systemd example files under example/systemd +* Added util subdir, with dedicated standalone xml, json, yang and xpath parser utility test programs. +* Validation of yang bits type space-separated list value +* Added -U command line to clixon_cli and clixon_netconf for changing user. + * This is primarily for NACM pseudo-user tests +* Added a generated CLI show command that works on the generated parse tree with auto completion. + * A typical call is: show @datamodel:example, cli_show_auto("candidate", "json"); + * The example contains a more elaborate example. + * Thanks ngashok for request, see https://github.com/clicon/clixon/issues/24 +* Added XML namespace (xmlns) validation + * for eg +* ./configure extended with --enable-debug flag + * CFLAGS=-g ./configure deprecated + +### Corrected Bugs +* Prefix of rpc was ignored (thanks Dmitri at netgate) + * https://github.com/clicon/clixon/issues/30 +* Added cli return value also for single commands (eg -1) +* Fixed JSON unbalanced braces resulting in assert. + +### Known issues +* Namespace name relabeling is not supported. + * Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlfns is not supported, such as: +``` + x:des3 +``` + ## 3.6.1 (29 May 2018) ### Corrected Bugs @@ -452,7 +551,7 @@ If you submit "nopresence" without a leaf, it will automatically be removed: Instead use the rpc calls in clixon_proto_client.[ch] In clients (eg cli/netconf) replace xmldb_get() in client code with clicon_rpc_get_config(). - pIf you use the vector arguments of xmldb_get(), replace as follows: + If you use the vector arguments of xmldb_get(), replace as follows: xmldb_get(h, db, api_path, &xt, &xvec, &xlen); with clicon_rpc_get_config(h, dbstr, api_path, &xt); diff --git a/Makefile.in b/Makefile.in index 04c72ab5..84368869 100644 --- a/Makefile.in +++ b/Makefile.in @@ -52,11 +52,11 @@ INSTALL = @INSTALL@ INCLUDES = -I. -I@srcdir@ @INCLUDES@ SHELL = /bin/sh -SUBDIRS = lib apps include etc datastore yang +SUBDIRS = lib apps include etc datastore util yang .PHONY: doc all clean depend $(SUBDIRS) install loc TAGS .config.status docker -all: $(SUBDIRS) clixon.mk +all: $(SUBDIRS) $(SUBDIRS): (cd $@ && $(MAKE) $(MFLAGS) all) @@ -65,12 +65,7 @@ depend: for i in $(SUBDIRS) doc example docker; \ do (cd $$i && $(MAKE) $(MFLAGS) depend); done -clixon.mk: clixon.mk.cpp - $(CPP) -P -traditional-cpp -x assembler-with-cpp -Dprefix=$(prefix) -Dlocalstatedir=$(localstatedir) -Dsysconfdir=$(sysconfdir) -Ddatadir=$(datadir) -Dlibdir=$(libdir) $< > $@ - -install: clixon.mk - install -d -m 0755 $(DESTDIR)$(datadir)/clixon - install -m 0644 clixon.mk $(DESTDIR)$(datadir)/clixon +install: for i in $(SUBDIRS) doc; \ do (cd $$i; $(MAKE) $(MFLAGS) $@)||exit 1; done; \ echo "Install for compilation by: make install-include" @@ -83,7 +78,6 @@ install-include: uninstall: for i in $(SUBDIRS) doc example docker; \ do (cd $$i && $(MAKE) $(MFLAGS) $@)||exit 1; done; - rm -f $(DESTDIR)$(datadir)/clixon/clixon.mk doc: cd $@; $(MAKE) $(MFLAGS) $@ @@ -100,7 +94,7 @@ clean: distclean: rm -f Makefile TAGS config.status config.log *~ .depend - rm -rf autom4te.cache clixon.mk build-root/rpmbuild + rm -rf autom4te.cache build-root/rpmbuild rm -f build-root/*.tar.xz build-root/*.rpm extras/rpm/Makefile for i in $(SUBDIRS) doc example docker; \ do (cd $$i && $(MAKE) $(MFLAGS) $@); done diff --git a/README.md b/README.md index 44198776..e422bb35 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ transaction support from a YANG specification. * [Support](#support) * [Dependencies](#dependencies) * [Extending](#extending) + * [XML and XPATH](#xml) * [Yang](#yang) * [Netconf](#netconf) * [Restconf](#restconf) @@ -21,13 +22,14 @@ transaction support from a YANG specification. * [Runtime](#runtime) * [Clixon project page](http://www.clicon.org) * [Tests](test/) + * [Docker](docker/) * [Reference manual](http://www.clicon.org/doxygen/index.html) (Note: the link may not be up-to-date. It is better to build your own: `cd doc; make doc`) Background ========== Clixon was implemented to provide an open-source generic configuration -tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, whilke clixon is a system with config-db, xml and rest interfaces. Most of the projects using clixon are for embedded network and measuring devices. But Clixon is more generic than that. +tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, while clixon is a system with configuration database, xml and rest interfaces. Most of the projects using clixon are for embedded network and measuring devices. But Clixon is more generic than that. Users of clixon currently include: * [Netgate](https://www.netgate.com) @@ -89,9 +91,17 @@ are also available. Plugins are written in C and easiest is to look at [example](example/README.md) or consulting the [FAQ](doc/FAQ.md). +XML +=== +Clixon has its own implementation of XML and XPATH implementation. + +The standards covered include: +- [XML](https://www.w3.org/TR/2008/REC-xml-20081126) +- [Namespaces](https://www.w3.org/TR/2009/REC-xml-names-20091208) +- [XPATH](https://www.w3.org/TR/xpath-10) + Yang ==== - YANG and XML is at the heart of Clixon. Yang modules are used as a specification for handling XML configuration data. The YANG spec is used to generate an interactive CLI, netconf and restconf clients. It @@ -99,8 +109,9 @@ also manages an XML datastore. Clixon mainly follows [YANG 1.0 RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) with some exceptions: - conformance: feature, if-feature, deviation -- identity, base, identityref - list features: min/max-elements, unique +- action statements +- notifications The aim is also to cover new features in YANG 1.1 [YANG RFC 7950](https://www.rfc-editor.org/rfc/rfc7950.txt) diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index fc619640..7ffdbf2b 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -531,6 +531,8 @@ from_client_unlock(clicon_handle h, ok: retval = 0; done: + if (cbx) + cbuf_free(cbx); return retval; } @@ -803,12 +805,13 @@ from_client_debug(clicon_handle h, return retval; } -/*! Match nacm access operations according to RFC8321 3.4.4. +/*! Match nacm access operations according to RFC8341 3.4.4. * Incoming RPC Message Validation Step 7 (c) * The rule's "access-operations" leaf has the "exec" bit set or * has the special value "*". * @retval 0 No match * @retval 1 Match + * XXX access_operations is bit-fields */ static int nacm_match_access(char *access_operations, @@ -818,7 +821,7 @@ nacm_match_access(char *access_operations, return 0; if (strcmp(access_operations,"*")==0) return 1; - if (strstr(mode, access_operations)!=NULL) + if (strstr(access_operations, mode)!=NULL) return 1; return 0; } @@ -832,7 +835,7 @@ nacm_match_access(char *access_operations, * @retval 0 Matching rule AND Not access and cbret set * @retval 1 Matchung rule AND Access * @retval 2 No matching rule Goto step 10 - * From RFC8321 3.4.4. Incoming RPC Message Validation + * From RFC8341 3.4.4. Incoming RPC Message Validation +---------+-----------------+---------------------+-----------------+ | Method | Resource class | NETCONF operation | Access | | | | | operation | @@ -874,6 +877,7 @@ nacm_match_rule(clicon_handle h, module_name = xml_find_body(xrule, "module-name"); rpc_name = xml_find_body(xrule, "rpc-name"); + /* XXX access_operations can be a set of bits */ access_operations = xml_find_body(xrule, "access-operations"); action = xml_find_body(xrule, "action"); clicon_debug(1, "%s: %s %s %s %s", __FUNCTION__, @@ -911,7 +915,7 @@ nacm_match_rule(clicon_handle h, * @retval -1 Error * @retval 0 Not access and cbret set * @retval 1 Access - * From RFC8321 3.4.4. Incoming RPC Message Validation + * From RFC8341 3.4.4. Incoming RPC Message Validation */ static int nacm_access(clicon_handle h, @@ -983,7 +987,13 @@ nacm_access(clicon_handle h, if (username == NULL) goto step10; /* User's group */ - if (xpath_vec(xacm, "groups/group[user-name=%s]", &gvec, &glen, username) < 0) + if (xpath_vec(xacm, +#ifdef COMPAT_XSL + "groups/group[user-name=%s]", +#else + "groups/group[user-name='%s']", +#endif + &gvec, &glen, username) < 0) goto done; /* 5. If no groups are found, continue with step 10. */ if (glen == 0) @@ -1000,7 +1010,13 @@ nacm_access(clicon_handle h, for (j=0; jcp_name); if (netconf_operation_failed_xml(xtop, "rpc", reason)< 0) goto done; goto ok; @@ -167,7 +169,7 @@ clixon_plugin_statedata(clicon_handle h, } } /* Code complex to filter out anything that is outside of xpath */ - if (xpath_vec(*xtop, xpath?xpath:"/", &xvec, &xlen) < 0) + if (xpath_vec(*xtop, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) goto done; /* If vectors are specified then mark the nodes found and diff --git a/apps/backend/backend_socket.c b/apps/backend/backend_socket.c index 7472fa06..a2587044 100644 --- a/apps/backend/backend_socket.c +++ b/apps/backend/backend_socket.c @@ -96,12 +96,12 @@ config_socket_init_ipv4(clicon_handle h, char *dst) goto err; /* Could check getaddrinfo */ } if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0){ - clicon_err(OE_UNIX, errno, "%s: bind", __FUNCTION__); + clicon_err(OE_UNIX, errno, "bind"); goto err; } clicon_debug(1, "Listen on server socket at %s:%hu", dst, port); if (listen(s, 5) < 0){ - clicon_err(OE_UNIX, errno, "%s: listen", __FUNCTION__); + clicon_err(OE_UNIX, errno, "listen"); goto err; } return s; @@ -126,7 +126,7 @@ config_socket_init_unix(clicon_handle h, char *sock) struct stat st; if (lstat(sock, &st) == 0 && unlink(sock) < 0){ - clicon_err(OE_UNIX, errno, "%s: unlink(%s)", __FUNCTION__, sock); + clicon_err(OE_UNIX, errno, "unlink(%s)", sock); return -1; } /* then find configuration group (for clients) and find its groupid */ @@ -142,7 +142,7 @@ config_socket_init_unix(clicon_handle h, char *sock) #endif /* create unix socket */ if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { - clicon_err(OE_UNIX, errno, "%s: socket", __FUNCTION__); + clicon_err(OE_UNIX, errno, "socket"); return -1; } // setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&one, sizeof(one)); @@ -151,20 +151,19 @@ config_socket_init_unix(clicon_handle h, char *sock) strncpy(addr.sun_path, sock, sizeof(addr.sun_path)-1); old_mask = umask(S_IRWXO | S_IXGRP | S_IXUSR); if (bind(s, (struct sockaddr *)&addr, SUN_LEN(&addr)) < 0){ - clicon_err(OE_UNIX, errno, "%s: bind", __FUNCTION__); + clicon_err(OE_UNIX, errno, "bind"); umask(old_mask); goto err; } umask(old_mask); /* change socket path file group */ if (lchown(sock, -1, gid) < 0){ - clicon_err(OE_UNIX, errno, "%s: lchown(%s, %s)", __FUNCTION__, - sock, config_group); + clicon_err(OE_UNIX, errno, "lchown(%s, %s)", sock, config_group); goto err; } clicon_debug(1, "Listen on server socket at %s", addr.sun_path); if (listen(s, 5) < 0){ - clicon_err(OE_UNIX, errno, "%s: listen", __FUNCTION__); + clicon_err(OE_UNIX, errno, "listen"); goto err; } return s; @@ -221,14 +220,14 @@ backend_accept_client(int fd, clicon_debug(2, "%s", __FUNCTION__); len = sizeof(from); if ((s = accept(fd, (struct sockaddr*)&from, &len)) < 0){ - clicon_err(OE_UNIX, errno, "%s: accept", __FUNCTION__); + clicon_err(OE_UNIX, errno, "accept"); goto done; } #if defined(SO_PEERCRED) /* fill in the user data structure */ clen = sizeof(credentials); if(getsockopt(s, SOL_SOCKET, SO_PEERCRED/* XXX finns ej i freebsd*/, &credentials, &clen)){ - clicon_err(OE_UNIX, errno, "%s: getsockopt", __FUNCTION__); + clicon_err(OE_UNIX, errno, "getsockopt"); goto done; } #endif diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c index dfb9461a..1fc397d8 100644 --- a/apps/backend/clixon_backend_handle.c +++ b/apps/backend/clixon_backend_handle.c @@ -220,7 +220,7 @@ backend_notify_xml(clicon_handle h, ce_next = ce->ce_next; for (su = ce->ce_subscription; su; su = su->su_next) if (strcmp(su->su_stream, stream) == 0){ - if (strlen(su->su_filter)==0 || xpath_first(x, su->su_filter) != NULL){ + if (strlen(su->su_filter)==0 || xpath_first(x, "%s", su->su_filter) != NULL){ if (cb==NULL){ if ((cb = cbuf_new()) == NULL){ clicon_err(OE_PLUGIN, errno, "cbuf_new"); @@ -255,7 +255,7 @@ backend_notify_xml(clicon_handle h, continue; if (strcmp(hs->hs_stream, stream)) continue; - if (strlen(hs->hs_filter)==0 || xpath_first(x, hs->hs_filter) != NULL){ + if (strlen(hs->hs_filter)==0 || xpath_first(x, "%s", hs->hs_filter) != NULL){ if ((*hs->hs_fn)(h, x, hs->hs_arg) < 0) goto done; } diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 4979f412..03ba734c 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -78,7 +78,7 @@ /*! Register log notification stream * @param[in] h Clicon handle * @param[in] stream Event stream. CLICON is predefined, others are application-defined - * @param[in] filter Filter. For xml notification ie xpath: .[name=kalle] + * @param[in] filter Filter. For xml notification ie xpath: .[name="kalle"] * @param[in] status 0 for stop, 1 to start * @param[in] fn Callback function called when notification occurs * @param[in] arg Argument to function note @@ -184,7 +184,7 @@ cli_signal_flush(clicon_handle h) cli_signal_block (h); } -/*! Modify xml database from a callback using xml key format strings +/*! Modify xml datastore from a callback using xml key format strings * @param[in] h Clicon handle * @param[in] cvv Vector of cli string and instantiated variables * @param[in] argv Vector. First element xml key format string, eg "/aaa/%s" @@ -221,7 +221,7 @@ cli_dbxml(clicon_handle h, cxobj *xb; /* body */ if (cvec_len(argv) != 1){ - clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be xml key format string", __FUNCTION__); + clicon_err(OE_PLUGIN, 0, "Requires one element to be xml key format string"); goto done; } if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -283,8 +283,15 @@ cli_dbxml(clicon_handle h, return retval; } +/*! Set datastore xml entry + * @param[in] h Clicon handle + * @param[in] cvv Vector of cli string and instantiated variables + * @param[in] argv Vector. First element xml key format string, eg "/aaa/%s" + */ int -cli_set(clicon_handle h, cvec *cvv, cvec *argv) +cli_set(clicon_handle h, + cvec *cvv, + cvec *argv) { int retval = 1; @@ -294,13 +301,16 @@ cli_set(clicon_handle h, cvec *cvv, cvec *argv) done: return retval; } -int cli_setv(clicon_handle h, cvec *vars, cvec *argv) -{ - return cli_set(h, vars, argv); -} +/*! Merge datastore xml entry + * @param[in] h Clicon handle + * @param[in] cvv Vector of cli string and instantiated variables + * @param[in] argv Vector. First element xml key format string, eg "/aaa/%s" + */ int -cli_merge(clicon_handle h, cvec *cvv, cvec *argv) +cli_merge(clicon_handle h, + cvec *cvv, + cvec *argv) { int retval = -1; @@ -310,13 +320,16 @@ cli_merge(clicon_handle h, cvec *cvv, cvec *argv) done: return retval; } -int cli_mergev(clicon_handle h, cvec *vars, cvec *argv) -{ - return cli_merge(h, vars, argv); -} +/*! Create datastore xml entry + * @param[in] h Clicon handle + * @param[in] cvv Vector of cli string and instantiated variables + * @param[in] argv Vector. First element xml key format string, eg "/aaa/%s" + */ int -cli_create(clicon_handle h, cvec *cvv, cvec *argv) +cli_create(clicon_handle h, + cvec *cvv, + cvec *argv) { int retval = -1; @@ -326,11 +339,16 @@ cli_create(clicon_handle h, cvec *cvv, cvec *argv) done: return retval; } -/*! +/*! Remove datastore xml entry + * @param[in] h Clicon handle + * @param[in] cvv Vector of cli string and instantiated variables + * @param[in] argv Vector. First element xml key format string, eg "/aaa/%s" * @see cli_del */ int -cli_remove(clicon_handle h, cvec *cvv, cvec *argv) +cli_remove(clicon_handle h, + cvec *cvv, + cvec *argv) { int retval = -1; @@ -341,8 +359,15 @@ cli_remove(clicon_handle h, cvec *cvv, cvec *argv) return retval; } +/*! Delete datastore xml + * @param[in] h Clicon handle + * @param[in] cvv Vector of cli string and instantiated variables + * @param[in] argv Vector. First element xml key format string, eg "/aaa/%s" + */ int -cli_del(clicon_handle h, cvec *cvv, cvec *argv) +cli_del(clicon_handle h, + cvec *cvv, + cvec *argv) { int retval = -1; @@ -352,11 +377,6 @@ cli_del(clicon_handle h, cvec *cvv, cvec *argv) done: return retval; } -int cli_delv(clicon_handle h, cvec *vars, cvec *argv) -{ - return cli_del(h, vars, argv); -} - /*! Set debug level on CLI client (not backend daemon) * @param[in] h Clicon handle @@ -374,9 +394,9 @@ cli_debug_cli(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find_var(vars, "level")) == NULL){ + if ((cv = cvec_find(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ - clicon_err(OE_PLUGIN, 0, "%s: Requires either label var or single arg: 0|1", __FUNCTION__); + clicon_err(OE_PLUGIN, 0, "Requires either label var or single arg: 0|1"); goto done; } cv = cvec_i(argv, 0); @@ -388,10 +408,6 @@ cli_debug_cli(clicon_handle h, done: return retval; } -int cli_debug_cliv(clicon_handle h, cvec *vars, cvec *argv) -{ - return cli_debug_cli(h, vars, argv); -} /*! Set debug level on backend daemon (not CLI) * @param[in] h Clicon handle @@ -409,9 +425,9 @@ cli_debug_backend(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find_var(vars, "level")) == NULL){ + if ((cv = cvec_find(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ - clicon_err(OE_PLUGIN, 0, "%s: Requires either label var or single arg: 0|1", __FUNCTION__); + clicon_err(OE_PLUGIN, 0, "Requires either label var or single arg: 0|1"); goto done; } cv = cvec_i(argv, 0); @@ -422,11 +438,39 @@ cli_debug_backend(clicon_handle h, done: return retval; } -int cli_debug_backendv(clicon_handle h, cvec *vars, cvec *argv) + +/*! Set debug level on restconf daemon + * @param[in] h Clicon handle + * @param[in] vars If variable "level" exists, its integer value is used + * @param[in] arg Else use the integer value of argument + * @note The level is either what is specified in arg as int argument. + * _or_ if a 'level' variable is present in vars use that value instead. + */ +int +cli_debug_restconf(clicon_handle h, + cvec *vars, + cvec *argv) { - return cli_debug_backend(h, vars, argv); + int retval = -1; + cg_var *cv; + int level; + + if ((cv = cvec_find(vars, "level")) == NULL){ + if (cvec_len(argv) != 1){ + clicon_err(OE_PLUGIN, 0, "Requires either label var or single arg: 0|1"); + goto done; + } + cv = cvec_i(argv, 0); + } + level = cv_int32_get(cv); + /* restconf daemon */ + if (0) /* XXX notyet */ + retval = clicon_rpc_debug(h, level); + done: + return retval; } + /*! Set syntax mode */ int @@ -438,7 +482,7 @@ cli_set_mode(clicon_handle h, char *str = NULL; if (cvec_len(argv) != 1){ - clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be cli mode", __FUNCTION__); + clicon_err(OE_PLUGIN, 0, "Requires one element to be cli mode"); goto done; } str = cv_string_get(cvec_i(argv, 0)); @@ -447,13 +491,10 @@ cli_set_mode(clicon_handle h, done: return retval; } -int cli_set_modev(clicon_handle h, cvec *vars, cvec *argv) -{ - return cli_set_mode(h, vars, argv); -} /*! Start bash from cli callback * XXX Application specific?? + * XXX replace fprintf with clicon_err? */ int cli_start_shell(clicon_handle h, @@ -470,10 +511,10 @@ cli_start_shell(clicon_handle h, if ((pw = getpwuid(getuid())) == NULL){ fprintf(stderr, "%s: getpwuid: %s\n", - __FUNCTION__, strerror(errno)); + __FUNCTION__, strerror(errno)); return -1; } - if (chdir(pw->pw_dir) < 0){ + if (chdir(pw->pw_dir) < 0){ fprintf(stderr, "%s: chdir(%s): %s\n", __FUNCTION__, pw->pw_dir, strerror(errno)); endpwent(); @@ -508,10 +549,6 @@ cli_start_shell(clicon_handle h, return 0; } -int cli_start_shellv(clicon_handle h, cvec *vars, cvec *argv) -{ - return cli_start_shell(h, vars, argv); -} /*! Generic quit callback */ @@ -523,10 +560,6 @@ cli_quit(clicon_handle h, cligen_exiting_set(cli_cligen(h), 1); return 0; } -int cli_quitv(clicon_handle h, cvec *vars, cvec *argv) -{ - return cli_quit(h, vars, argv); -} /*! Generic commit callback * @param[in] argv No arguments expected @@ -544,10 +577,6 @@ cli_commit(clicon_handle h, done: return retval; } -int cli_commitv(clicon_handle h, cvec *vars, cvec *argv) -{ - return cli_commit(h, vars, argv); -} /*! Generic validate callback */ @@ -564,10 +593,6 @@ cli_validate(clicon_handle h, done: return retval; } -int cli_validatev(clicon_handle h, cvec *vars, cvec *argv) -{ - return cli_validate(h, vars, argv); -} /*! Compare two dbs using XML. Write to file and run diff */ @@ -647,7 +672,7 @@ compare_dbs(clicon_handle h, int astext; if (cvec_len(argv) > 1){ - clicon_err(OE_PLUGIN, 0, "%s: Requires 0 or 1 element. If given: astext flag 0|1", __FUNCTION__); + clicon_err(OE_PLUGIN, 0, "Requires 0 or 1 element. If given: astext flag 0|1"); goto done; } if (cvec_len(argv)) @@ -677,10 +702,6 @@ compare_dbs(clicon_handle h, return retval; } -int compare_dbsv(clicon_handle h, cvec *vars, cvec *argv) -{ - return compare_dbs(h, vars, argv); -} /*! Load a configuration file to candidate database * Utility function used by cligen spec file @@ -692,7 +713,7 @@ int compare_dbsv(clicon_handle h, cvec *vars, cvec *argv) * @note file is assumed to have a dummy top-tag, eg * @code * # cligen spec - * load file , load_config_filev("name2","merge"); + * load file , load_config_file("name2","merge"); * @endcode * @see save_config_file */ @@ -730,7 +751,7 @@ load_config_file(clicon_handle h, clicon_err(OE_PLUGIN, 0, "No such op: %s, expected merge or replace", opstr); goto done; } - if ((cv = cvec_find_var(cvv, varstr)) == NULL){ + if ((cv = cvec_find(cvv, varstr)) == NULL){ clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr); goto done; } @@ -742,7 +763,7 @@ load_config_file(clicon_handle h, } /* Open and parse local file into xml */ if ((fd = open(filename, O_RDONLY)) < 0){ - clicon_err(OE_UNIX, errno, "%s: open(%s)", __FUNCTION__, filename); + clicon_err(OE_UNIX, errno, "open(%s)", filename); goto done; } if (xml_parse_file(fd, "", NULL, &xt) < 0) @@ -772,10 +793,6 @@ load_config_file(clicon_handle h, close(fd); return ret; } -int load_config_filev(clicon_handle h, cvec *vars, cvec *argv) -{ - return load_config_file(h, vars, argv); -} /*! Copy database to local file * Utility function used by cligen spec file @@ -808,9 +825,11 @@ save_config_file(clicon_handle h, if (cvec_len(argv) != 2){ if (cvec_len(argv)==1) - clicon_err(OE_PLUGIN, 0, "%s: Got single argument:\"%s\". Expected \",\"", cv_string_get(cvec_i(argv,0))); + clicon_err(OE_PLUGIN, 0, "Got single argument:\"%s\". Expected \",\"", + cv_string_get(cvec_i(argv,0))); else - clicon_err(OE_PLUGIN, 0, "%s: Got %d arguments. Expected: ,", cvec_len(argv)); + clicon_err(OE_PLUGIN, 0, " Got %d arguments. Expected: ,", + cvec_len(argv)); goto done; } @@ -822,7 +841,7 @@ save_config_file(clicon_handle h, clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr); goto done; } - if ((cv = cvec_find_var(cvv, varstr)) == NULL){ + if ((cv = cvec_find(cvv, varstr)) == NULL){ clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr); goto done; } @@ -857,10 +876,6 @@ save_config_file(clicon_handle h, fclose(f); return retval; } -int save_config_filev(clicon_handle h, cvec *vars, cvec *argv) -{ - return save_config_file(h, vars, argv); -} /*! Delete all elements in a database * Utility function used by cligen spec file @@ -874,7 +889,7 @@ delete_all(clicon_handle h, int retval = -1; if (cvec_len(argv) != 1){ - clicon_err(OE_PLUGIN, 0, "%s: Requires one element: dbname", __FUNCTION__); + clicon_err(OE_PLUGIN, 0, "Requires one element: dbname"); goto done; } dbstr = cv_string_get(cvec_i(argv, 0)); @@ -890,10 +905,6 @@ delete_all(clicon_handle h, done: return retval; } -int delete_allv(clicon_handle h, cvec *vars, cvec *argv) -{ - return delete_all(h, vars, argv); -} /*! Discard all changes in candidate and replace with running */ @@ -903,12 +914,8 @@ discard_changes(clicon_handle h, cvec *argv) { return clicon_rpc_discard_changes(h); -} -int discard_changesv(clicon_handle h, cvec *vars, cvec *argv) -{ - return discard_changes(h, vars, argv); -} +} /*! Copy from one database to another, eg running->startup * @param[in] argv a string: " " Copy from db1 to db2 */ @@ -945,7 +952,7 @@ cli_notification_cb(int s, if (clicon_msg_rcv(s, &reply, &eof) < 0) goto done; if (eof){ - clicon_err(OE_PROTO, ESHUTDOWN, "%s: Socket unexpected close", __FUNCTION__); + clicon_err(OE_PROTO, ESHUTDOWN, "Socket unexpected close"); close(s); errno = ESHUTDOWN; event_unreg_fd(s, cli_notification_cb); @@ -992,7 +999,7 @@ cli_notification_cb(int s, * and is XXX * Example code: Start logging of mystream and show logs as xml * @code - * cmd("comment"), cli_notifyv("mystream","1","xml"); + * cmd("comment"), cli_notify("mystream","1","xml"); * @endcode * XXX: format is a memory leak */ @@ -1008,7 +1015,7 @@ cli_notify(clicon_handle h, enum format_enum format = FORMAT_TEXT; if (cvec_len(argv) != 2 && cvec_len(argv) != 3){ - clicon_err(OE_PLUGIN, 0, "%s Requires arguments: []", __FUNCTION__); + clicon_err(OE_PLUGIN, 0, "Requires arguments: []"); goto done; } stream = cv_string_get(cvec_i(argv, 0)); @@ -1030,10 +1037,79 @@ cli_notify(clicon_handle h, done: return retval; } + +/* Backward compatible Set if you want to enable "v" cli callback functions, + * such as cli_setv() + * This was obsoleted in 3.7. + * @see include/clixon_custom.h + */ +#ifdef COMPAT_CLIV +int cli_setv(clicon_handle h, cvec *vars, cvec *argv) +{ + return cli_set(h, vars, argv); +} +int cli_mergev(clicon_handle h, cvec *vars, cvec *argv) +{ + return cli_merge(h, vars, argv); +} +int cli_delv(clicon_handle h, cvec *vars, cvec *argv) +{ + return cli_del(h, vars, argv); +} +int cli_debug_cliv(clicon_handle h, cvec *vars, cvec *argv) +{ + return cli_debug_cli(h, vars, argv); +} +int cli_debug_backendv(clicon_handle h, cvec *vars, cvec *argv) +{ + return cli_debug_backend(h, vars, argv); +} +int cli_set_modev(clicon_handle h, cvec *vars, cvec *argv) +{ + return cli_set_mode(h, vars, argv); +} +int cli_start_shellv(clicon_handle h, cvec *vars, cvec *argv) +{ + return cli_start_shell(h, vars, argv); +} +int cli_quitv(clicon_handle h, cvec *vars, cvec *argv) +{ + return cli_quit(h, vars, argv); +} +int cli_commitv(clicon_handle h, cvec *vars, cvec *argv) +{ + return cli_commit(h, vars, argv); +} +int cli_validatev(clicon_handle h, cvec *vars, cvec *argv) +{ + return cli_validate(h, vars, argv); + +} +int compare_dbsv(clicon_handle h, cvec *vars, cvec *argv) +{ + return compare_dbs(h, vars, argv); +} +int load_config_filev(clicon_handle h, cvec *vars, cvec *argv) +{ + return load_config_file(h, vars, argv); +} +int save_config_filev(clicon_handle h, cvec *vars, cvec *argv) +{ + return save_config_file(h, vars, argv); +} +int delete_allv(clicon_handle h, cvec *vars, cvec *argv) +{ + return delete_all(h, vars, argv); +} +int discard_changesv(clicon_handle h, cvec *vars, cvec *argv) +{ + return discard_changes(h, vars, argv); +} int cli_notifyv(clicon_handle h, cvec *vars, cvec *argv) { return cli_notify(h, vars, argv); } +#endif /* COMPAT_CLIV */ /*! Lock database * @@ -1113,7 +1189,7 @@ cli_unlock(clicon_handle h, * tovar: Name of variable containing name of object to copy to. * @code * cli spec: - * copy snd to , cli_copy_config("candidate", "/sender[%s=%s]", "from", "n1", "n2"); + * copy snd to , cli_copy_config("candidate", "/sender[%s='%s']", "from", "n1", "n2"); * cli command: * copy snd from to to * @endcode @@ -1142,7 +1218,7 @@ cli_copy_config(clicon_handle h, cxobj *xerr; if (cvec_len(argv) != 5){ - clicon_err(OE_PLUGIN, 0, "%s: Requires four elements: ", __FUNCTION__); + clicon_err(OE_PLUGIN, 0, "Requires four elements: "); goto done; } /* First argv argument: Database */ @@ -1157,7 +1233,7 @@ cli_copy_config(clicon_handle h, tovar = cv_string_get(cvec_i(argv, 4)); /* Get from variable -> cv -> from name */ - if ((fromcv = cvec_find_var(cvv, fromvar)) == NULL){ + if ((fromcv = cvec_find(cvv, fromvar)) == NULL){ clicon_err(OE_PLUGIN, 0, "fromvar '%s' not found in cligen var list", fromvar); goto done; } @@ -1168,17 +1244,34 @@ cli_copy_config(clicon_handle h, clicon_err(OE_PLUGIN, errno, "cbuf_new"); goto done; } - /* Sanity check that xpath contains exactly one %s */ + /* Sanity check that xpath contains exactly two %s, ie [%s='%s'] */ j = 0; - for (i=0; i cv -> to name */ - if ((tocv = cvec_find_var(cvv, tovar)) == NULL){ + if ((tocv = cvec_find(cvv, tovar)) == NULL){ clicon_err(OE_PLUGIN, 0, "tovar '%s' not found in cligen var list", tovar); goto done; } @@ -1200,7 +1293,7 @@ cli_copy_config(clicon_handle h, goto done; xml_name_set(x2, "config"); cprintf(cb, "/%s", keyname); - if ((x = xpath_first(x2, cbuf_get(cb))) == NULL){ + if ((x = xpath_first(x2, "%s", cbuf_get(cb))) == NULL){ clicon_err(OE_PLUGIN, 0, "Field %s not found in copy tree", keyname); goto done; } @@ -1237,7 +1330,7 @@ cli_debug(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find_var(vars, "level")) == NULL) + if ((cv = cvec_find(vars, "level")) == NULL) cv = arg; level = cv_int32_get(cv); /* cli */ diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 67fa9cec..4ffb55ca 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -141,6 +141,7 @@ cli_expand_var_generate(clicon_handle h, * @param[in] ys yang_stmt of the node at hand * @param[in] cb0 The string where the result format string is inserted. * @see cli_dbxml This is where the xmlkeyfmt string is used + * @see pt_callback_reference in CLIgen where the actual callback overwrites the template */ static int cli_callback_generate(clicon_handle h, @@ -204,20 +205,51 @@ yang2cli_var_sub(clicon_handle h, } type = ytype?ytype->ys_argument:NULL; cvtypestr = cv_type2str(cvtype); + if (type && strcmp(type, "identityref") == 0) + cprintf(cb, "("); cprintf(cb, "<%s:%s", ys->ys_argument, cvtypestr); /* enumeration special case completion */ - if (type && (strcmp(type, "enumeration") == 0 || strcmp(type, "bits") == 0)){ - cprintf(cb, " choice:"); - i = 0; - while ((yi = yn_each((yang_node*)ytype, yi)) != NULL){ - if (yi->ys_keyword != Y_ENUM && yi->ys_keyword != Y_BIT) - continue; - if (i) - cprintf(cb, "|"); - cprintf(cb, "%s", yi->ys_argument); - i++; + if (type){ + if (strcmp(type, "enumeration") == 0 || strcmp(type, "bits") == 0){ + cprintf(cb, " choice:"); + i = 0; + while ((yi = yn_each((yang_node*)ytype, yi)) != NULL){ + if (yi->ys_keyword != Y_ENUM && yi->ys_keyword != Y_BIT) + continue; + if (i) + cprintf(cb, "|"); + cprintf(cb, "%s", yi->ys_argument); + i++; + } + } + else if (strcmp(type, "identityref") == 0){ + yang_stmt *ybaseref; + yang_stmt *ybaseid; + cg_var *cv = NULL; + char *name; + char *id; + /* Add a wildchar string first -let validate take it for default prefix */ + cprintf(cb, ">"); + if (helptext) + cprintf(cb, "(\"%s\")", helptext); + cprintf(cb, "|<%s:%s choice:", ys->ys_argument, cvtypestr); + if ((ybaseref = yang_find((yang_node*)ytype, Y_BASE, NULL)) != NULL && + (ybaseid = yang_find_identity(ys, ybaseref->ys_argument)) != NULL){ + i = 0; + while ((cv = cvec_each(ybaseid->ys_cvec, cv)) != NULL){ + if (i++) + cprintf(cb, "|"); + name = strdup(cv_name_get(cv)); + if ((id=strchr(name, ':')) != NULL) + *id = '\0'; + cprintf(cb, "%s:%s", name, id+1); + if (name) + free(name); + } + } } } + if (options & YANG_OPTIONS_FRACTION_DIGITS) cprintf(cb, " fraction-digits:%u", fraction_digits); if (options & (YANG_OPTIONS_RANGE|YANG_OPTIONS_LENGTH)){ @@ -253,16 +285,18 @@ yang2cli_var_sub(clicon_handle h, goto done; } } - cprintf(cb, "%s]", r); + cprintf(cb, "%s]", r); /* range */ free(r); r = NULL; } if (options & YANG_OPTIONS_PATTERN) cprintf(cb, " regexp:\"%s\"", pattern); - cprintf(cb, ">"); if (helptext) cprintf(cb, "(\"%s\")", helptext); + if (type && strcmp(type, "identityref") == 0) + cprintf(cb, ")"); + retval = 0; done: return retval; @@ -385,6 +419,7 @@ yang2cli_var(clicon_handle h, enum cv_type cvtype; int options = 0; int completionp; + char *type; if (yang_type_get(ys, &origtype, &yrestype, &options, &mincv, &maxcv, &pattern, &fraction_digits) < 0) @@ -413,11 +448,11 @@ yang2cli_var(clicon_handle h, cprintf(cb, ")"); } else{ - char *type; type = yrestype?yrestype->ys_argument:NULL; if (type) completionp = clicon_cli_genmodel_completion(h) && - strcmp(type, "enumeration") != 0 && + strcmp(type, "enumeration") != 0 && + strcmp(type, "identityref") != 0 && strcmp(type, "bits") != 0; else completionp = clicon_cli_genmodel_completion(h); @@ -748,7 +783,7 @@ yang2cli(clicon_handle h, cvec *globals; /* global variables from syntax */ if ((cbuf = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "%s: cbuf_new", __FUNCTION__); + clicon_err(OE_XML, errno, "cbuf_new"); goto done; } /* Traverse YANG specification: loop through statements */ diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index f1d9678e..bd10b8f8 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -71,7 +71,7 @@ #include "cli_handle.h" /* Command line options to be passed to getopt(3) */ -#define CLI_OPTS "hD:f:xl:F:1u:d:m:qpGLy:c:" +#define CLI_OPTS "hD:f:xl:F:1u:d:m:qpGLy:c:U:" /*! terminate cli application */ static int @@ -108,6 +108,8 @@ cli_signal_init (clicon_handle h) /*! Interactive CLI command loop * @param[in] h CLICON handle + * @retval 0 + * @retval -1 * @see cligen_loop */ static int @@ -117,17 +119,16 @@ cli_interactive(clicon_handle h) int res; char *cmd; char *new_mode; - int result; + int eval; /* Loop through all commands */ while(!cligen_exiting(cli_cligen(h))) { new_mode = cli_syntax_mode(h); if ((cmd = clicon_cliread(h)) == NULL) { cligen_exiting_set(cli_cligen(h), 1); /* EOF */ - retval = -1; goto done; } - if ((res = clicon_parse(h, cmd, &new_mode, &result)) < 0) + if ((res = clicon_parse(h, cmd, &new_mode, &eval)) < 0) goto done; } retval = 0; @@ -172,7 +173,7 @@ dump_configfile_xml_fn(FILE *fout, clicon_err(OE_UNIX, errno, "configure file: %s", filename); return -1; } - clicon_debug(2, "Reading config file %s", __FUNCTION__, filename); + clicon_debug(2, "%s: Reading config file %s", __FUNCTION__, filename); fprintf(fout, "\n"); while (fgets(line, sizeof(line), f)) { if ((cp = strchr(line, '\n')) != NULL) /* strip last \n */ @@ -193,7 +194,8 @@ dump_configfile_xml_fn(FILE *fout, } static void -usage(char *argv0, clicon_handle h) +usage(clicon_handle h, + char *argv0) { char *confsock = clicon_sock(h); char *plgdir = clicon_cli_dir(h); @@ -216,7 +218,8 @@ usage(char *argv0, clicon_handle h) "\t-L \t\tDebug print dynamic CLI syntax including completions and expansions\n" "\t-l \tLog on (s)yslog, std(e)rr or std(o)ut (stderr is default)\n" "\t-y \tOverride yang spec file (dont include .yang suffix)\n" - "\t-c \tSpecify cli spec file.\n", + "\t-c \tSpecify cli spec file.\n" + "\t-U \tOver-ride unix user with a pseudo user for NACM.\n", argv0, confsock ? confsock : "none", plgdir ? plgdir : "none" @@ -229,6 +232,7 @@ usage(char *argv0, clicon_handle h) int main(int argc, char **argv) { + int retval = -1; char c; int once; char *tmp; @@ -254,7 +258,9 @@ main(int argc, char **argv) /* Initiate CLICON handle */ if ((h = cli_handle_init()) == NULL) goto done; - /* Set username to clicon handle. Use in all communication to backend */ + /* Set username to clicon handle. Use in all communication to backend + * Note, can be overridden by -U + */ if ((pw = getpwuid(getuid())) == NULL){ clicon_err(OE_UNIX, errno, "getpwuid"); goto done; @@ -281,11 +287,11 @@ main(int argc, char **argv) break; case 'D' : /* debug */ if (sscanf(optarg, "%d", &debug) != 1) - usage(argv[0], h); + usage(h, argv[0]); break; case 'f': /* config file */ if (!strlen(optarg)) - usage(argv[0], h); + usage(h, argv[0]); clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg); break; case 'x': /* dump config file as xml (migration from .conf file)*/ @@ -303,9 +309,9 @@ main(int argc, char **argv) logdst = CLICON_LOG_STDOUT; break; default: - usage(argv[0], h); + usage(h, argv[0]); } - break; + break; } /* * Logs, error and debug to stderr or syslog, set debug level @@ -325,7 +331,7 @@ main(int argc, char **argv) /* Find and read configfile */ if (clicon_options_main(h) < 0){ if (help) - usage(argv[0], h); + usage(h, argv[0]); return -1; } @@ -350,17 +356,17 @@ main(int argc, char **argv) break; case 'u': /* config unix domain path/ ip host */ if (!strlen(optarg)) - usage(argv[0], h); + usage(h, argv[0]); clicon_option_str_set(h, "CLICON_SOCK", optarg); break; case 'd': /* Plugin directory: overrides configfile */ if (!strlen(optarg)) - usage(argv[0], h); + usage(h, argv[0]); clicon_option_str_set(h, "CLICON_CLI_DIR", optarg); break; case 'm': /* CLI syntax mode */ if (!strlen(optarg)) - usage(argv[0], h); + usage(h, argv[0]); clicon_option_str_set(h, "CLICON_CLI_MODE", optarg); break; case 'q' : /* Quiet mode */ @@ -383,8 +389,14 @@ main(int argc, char **argv) clicon_option_str_set(h, "CLICON_CLISPEC_FILE", optarg); break; } + case 'U': /* Clixon 'pseudo' user */ + if (!strlen(optarg)) + usage(h, argv[0]); + if (clicon_username_set(h, optarg) < 0) + goto done; + break; default: - usage(argv[0], h); + usage(h, argv[0]); break; } } @@ -393,7 +405,7 @@ main(int argc, char **argv) /* Defer: Wait to the last minute to print help message */ if (help) - usage(argv[0], h); + usage(h, argv[0]); /* Setup signal handlers */ cli_signal_init(h); @@ -445,7 +457,7 @@ main(int argc, char **argv) } if (!cli_syntax_mode(h)){ - fprintf (stderr, "FATAL: No cli mode set (use -m or CLICON_CLI_MODE)\n"); + fprintf(stderr, "FATAL: No cli mode set (use -m or CLICON_CLI_MODE)\n"); goto done; } if (cligen_tree_find(cli_cligen(h), cli_syntax_mode(h)) == NULL) @@ -476,11 +488,19 @@ main(int argc, char **argv) if (restarg != NULL && strlen(restarg)){ char *mode = cli_syntax_mode(h); int result; - clicon_parse(h, restarg, &mode, &result); + + /* */ + if (clicon_parse(h, restarg, &mode, &result) != 1){ + goto done; + } + if (result < 0) + goto done; } /* Go into event-loop unless -1 command-line */ if (!once) - cli_interactive(h); + retval = cli_interactive(h); + else + retval = 0; done: if (treename) free(treename); @@ -491,6 +511,5 @@ main(int argc, char **argv) clicon_log(LOG_NOTICE, "%s: %u Terminated\n", __PROGRAM__, getpid()); if (h) cli_terminate(h); - - return 0; + return retval; } diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index 04fc027c..f5781f87 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -97,7 +97,7 @@ syntax_mode_find(cli_syntax_t *stx, perror("malloc"); return NULL; } - memset (m, 0, sizeof (*m)); + memset(m, 0, sizeof(*m)); strncpy(m->csm_name, mode, sizeof(m->csm_name)-1); strncpy(m->csm_prompt, CLI_DEFAULT_PROMPT, sizeof(m->csm_prompt)-1); INSQ(m, stx->stx_modes); @@ -263,7 +263,7 @@ cli_load_syntax(clicon_handle h, if ((cp = clixon_plugin_find(h, plgnam)) != NULL) handle = cp->cp_handle; if (handle == NULL){ - clicon_err(OE_PLUGIN, 0, "CLICON_PLUGIN set to '%s' in %s but plugin %s.so not found in %s\n", + clicon_err(OE_PLUGIN, 0, "CLICON_PLUGIN set to '%s' in %s but plugin %s.so not found in %s", plgnam, filename, plgnam, clicon_cli_dir(h)); goto done; @@ -277,6 +277,9 @@ cli_load_syntax(clicon_handle h, } if (cligen_expandv_str2fn(pt, (expandv_str2fn_t*)clixon_str2fn, handle) < 0) goto done; + /* Variable translation functions */ + if (cligen_translate_str2fn(pt, (translate_str2fn_t*)clixon_str2fn, handle) < 0) + goto done; /* Make sure we have a syntax mode specified */ if (mode == NULL || strlen(mode) < 1) { /* may be null if not given in file */ @@ -311,7 +314,7 @@ done: * @param[in] h Clicon handle */ int -cli_syntax_load (clicon_handle h) +cli_syntax_load(clicon_handle h) { int retval = -1; char *plugin_dir = NULL; @@ -340,7 +343,7 @@ cli_syntax_load (clicon_handle h) clicon_err(OE_UNIX, errno, "malloc"); goto done; } - memset (stx, 0, sizeof (*stx)); /* Zero out all */ + memset(stx, 0, sizeof(*stx)); /* Zero out all */ cli_syntax_set(h, stx); @@ -441,6 +444,8 @@ cli_handler_err(FILE *f) /*! Evaluate a matched command * @param[in] h Clicon handle * @param[in] cmd The command string + * @retval int If there is a callback, the return value of the callback is returned, + * @retval 0 otherwise */ int clicon_eval(clicon_handle h, @@ -448,10 +453,12 @@ clicon_eval(clicon_handle h, cg_obj *match_obj, cvec *cvv) { + int retval = 0; + cli_output_reset(); if (!cligen_exiting(cli_cligen(h))) { clicon_err_reset(); - if (cligen_eval(cli_cligen(h), match_obj, cvv) < 0) { + if ((retval = cligen_eval(cli_cligen(h), match_obj, cvv)) < 0) { #if 0 /* This is removed since we get two error messages on failure. But maybe only sometime? Both a real log when clicon_err is called, and the here again. @@ -460,11 +467,10 @@ clicon_eval(clicon_handle h, #endif } } - return 0; + return retval; } - -/*! Given a command string, parse and evaluate. +/*! Given a command string, parse and if match single command, eval it. * Parse and evaluate the string according to * the syntax parse tree of the syntax mode specified by *mode. * If there is no match in the tree for the command, the parse hook @@ -475,19 +481,25 @@ clicon_eval(clicon_handle h, * @param[in] h Clicon handle * @param[in] cmd Command string * @param[in,out] modenamep Pointer to the mode string pointer - * @param[out] result -2 On eof (shouldnt happen) + * @param[out] evalres Evaluation result if retval=1 + * -2 On eof (shouldnt happen) * -1 On parse error * >=0 Number of matches + * @retval -2 Eof CG_EOF + * @retval -1 Error CG_ERROR + * @retval 0 No match CG_NOMATCH + * @retval 1 Exactly one match CG_MATCH + * @retval 2+ Multiple matches */ int clicon_parse(clicon_handle h, char *cmd, char **modenamep, - int *result) + int *evalres) { + int retval = -1; char *modename; char *modename0; - int res = -1; int r; cli_syntax_t *stx = NULL; cli_syntaxmode_t *smode; @@ -508,10 +520,10 @@ clicon_parse(clicon_handle h, else { if ((smode = syntax_mode_find(stx, modename, 0)) == NULL) { cli_output(f, "Can't find syntax mode '%s'\n", modename); - return -1; + goto done; } } - while(smode) { + if (smode){ modename0 = NULL; if ((pt = cligen_tree_active_get(cli_cligen(h))) != NULL) modename0 = pt->pt_name; @@ -527,20 +539,19 @@ clicon_parse(clicon_handle h, clicon_err(OE_UNIX, errno, "cvec_new"); goto done;; } - res = cliread_parse(cli_cligen(h), cmd, pt, &match_obj, cvv); - if (res != CG_MATCH) + retval = cliread_parse(cli_cligen(h), cmd, pt, &match_obj, cvv); + if (retval != CG_MATCH) pt_expand_cleanup_1(pt); /* XXX change to pt_expand_treeref_cleanup */ if (modename0){ cligen_tree_active_set(cli_cligen(h), modename0); modename0 = NULL; } - switch (res) { + switch (retval) { case CG_EOF: /* eof */ case CG_ERROR: cli_output(f, "CLI parse error: %s\n", cmd); - goto done; + break; case CG_NOMATCH: /* no match */ - smode = NULL; /* clicon_err(OE_CFG, 0, "CLI syntax error: \"%s\": %s", cmd, cli_nomatch(h));*/ cli_output(f, "CLI syntax error: \"%s\": %s\n", @@ -554,20 +565,18 @@ clicon_parse(clicon_handle h, if ((r = clicon_eval(h, cmd, match_obj, cvv)) < 0) cli_handler_err(stdout); pt_expand_cleanup_1(pt); /* XXX change to pt_expand_treeref_cleanup */ - if (result) - *result = r; - goto done; + if (evalres) + *evalres = r; break; default: cli_output(f, "CLI syntax error: \"%s\" is ambiguous\n", cmd); - goto done; break; - } + } /* switch retval */ } done: if (cvv) cvec_free(cvv); - return res; + return retval; } /*! Read command from CLIgen's cliread() using current syntax mode. @@ -667,9 +676,9 @@ cli_set_prompt(clicon_handle h, * @param[in] fmt Stdarg fmt string */ static int -prompt_fmt (char *prompt, - size_t plen, - char *fmt, ...) +prompt_fmt(char *prompt, + size_t plen, + char *fmt, ...) { va_list ap; char *s = fmt; @@ -689,7 +698,7 @@ prompt_fmt (char *prompt, if (*s == '%' && *++s) { switch(*s) { case 'H': /* Hostname */ - if (gethostname (hname, sizeof (hname)) != 0) + if (gethostname(hname, sizeof(hname)) != 0) strncpy(hname, "unknown", sizeof(hname)-1); cprintf(cb, "%s", hname); break; diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index f4753f3c..12d8c800 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -123,8 +123,7 @@ expand_dbvar(void *h, char *reason = NULL; if (argv == NULL || cvec_len(argv) != 2){ - clicon_err(OE_PLUGIN, 0, "%s: requires arguments: ", - __FUNCTION__); + clicon_err(OE_PLUGIN, 0, "requires arguments: "); goto done; } if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -132,7 +131,7 @@ expand_dbvar(void *h, goto done; } if ((cv = cvec_i(argv, 0)) == NULL){ - clicon_err(OE_PLUGIN, 0, "%s: Error when accessing argument "); + clicon_err(OE_PLUGIN, 0, "Error when accessing argument "); goto done; } dbstr = cv_string_get(cv); @@ -143,13 +142,13 @@ expand_dbvar(void *h, goto done; } if ((cv = cvec_i(argv, 1)) == NULL){ - clicon_err(OE_PLUGIN, 0, "%s: Error when accessing argument "); + clicon_err(OE_PLUGIN, 0, "Error when accessing argument "); goto done; } api_path_fmt = cv_string_get(cv); /* api_path_fmt = /interface/%s/address/%s --> ^/interface/eth0/address/.*$ - --> /interface/[name=eth0]/address + --> /interface/[name="eth0"]/address */ if (api_path_fmt2xpath(api_path_fmt, cvv, &xpath) < 0) goto done; @@ -161,7 +160,7 @@ expand_dbvar(void *h, goto done; if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); - goto done; + goto ok; } xcur = xt; /* default top-of-tree */ xpathcur = xpath; @@ -197,7 +196,7 @@ expand_dbvar(void *h, cli_output(stderr, "%s\n", reason); goto done; } - if ((xcur = xpath_first(xt, xpath)) == NULL){ + if ((xcur = xpath_first(xt, "%s", xpath)) == NULL){ clicon_err(OE_DB, 0, "xpath %s should return merged content", xpath); goto done; } @@ -205,7 +204,7 @@ expand_dbvar(void *h, /* One round to detect duplicates */ j = 0; - if (xpath_vec(xcur, xpathcur, &xvec, &xlen) < 0) + if (xpath_vec(xcur, "%s", &xvec, &xlen, xpathcur) < 0) goto done; for (i = 0; i < xlen; i++) { char *str; @@ -409,10 +408,6 @@ show_yang(clicon_handle h, yang_print(stdout, yn); return 0; } -int show_yangv(clicon_handle h, cvec *vars, cvec *argv) -{ - return show_yang(h, vars, argv); -} /*! Generic show configuration CLIGEN callback * Utility function used by cligen spec file @@ -421,11 +416,11 @@ int show_yangv(clicon_handle h, cvec *vars, cvec *argv) * @param[in] argv String vector: [] * Format of argv: * "running"|"candidate"|"startup" - * "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) - * xpath expression, that may contain one %, eg "/sender[name=%s]" + * "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) + * xpath expression, that may contain one %, eg "/sender[name="%s"]" * optional name of variable in cvv. If set, xpath must have a '%s' * @code - * show config id , cli_show_config("running","xml","iface[name=%s]","n"); + * show config id , cli_show_config("running","xml","iface[name="%s"]","n"); * @endcode */ int @@ -478,10 +473,11 @@ cli_show_config(clicon_handle h, if (xpath[i] == '%') j++; if (j != 1){ - clicon_err(OE_PLUGIN, 0, "xpath '%s' does not have a single '%%'"); + clicon_err(OE_PLUGIN, 0, "xpath '%s' does not have a single '%%'", + xpath); goto done; } - if ((cvattr = cvec_find_var(cvv, attr)) == NULL){ + if ((cvattr = cvec_find(cvv, attr)) == NULL){ clicon_err(OE_PLUGIN, 0, "attr '%s' not found in cligen var list", attr); goto done; } @@ -565,7 +561,7 @@ show_conf_xpath(clicon_handle h, int i; if (cvec_len(argv) != 1){ - clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be ", __FUNCTION__); + clicon_err(OE_PLUGIN, 0, "Requires one element to be "); goto done; } str = cv_string_get(cvec_i(argv, 0)); @@ -576,7 +572,7 @@ show_conf_xpath(clicon_handle h, clicon_err(OE_PLUGIN, 0, "No such db name: %s", str); goto done; } - cv = cvec_find_var(cvv, "xpath"); + cv = cvec_find(cvv, "xpath"); xpath = cv_string_get(cv); if (clicon_rpc_get_config(h, str, xpath, &xt) < 0) goto done; @@ -584,7 +580,7 @@ show_conf_xpath(clicon_handle h, clicon_rpc_generate_error("Get configuration", xerr); goto done; } - if (xpath_vec(xt, xpath, &xv, &xlen) < 0) + if (xpath_vec(xt, "%s", &xv, &xlen, xpath) < 0) goto done; for (i=0; i Generated API PATH + * "running"|"candidate"|"startup" + * "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) + */ +int +cli_show_auto(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + int retval = 1; + yang_spec *yspec; + char *api_path_fmt; /* xml key format */ + // char *api_path = NULL; /* xml key */ + char *db; + char *xpath; + char *formatstr; + enum format_enum format = FORMAT_XML; + cxobj *xt = NULL; + cxobj *xp; + cxobj *xerr; + enum genmodel_type gt; + + if (cvec_len(argv) != 3){ + clicon_err(OE_PLUGIN, 0, "Usage: * . (*) generated."); + goto done; + } + /* First argv argument: API_path format */ + api_path_fmt = cv_string_get(cvec_i(argv, 0)); + /* Second argv argument: Database */ + db = cv_string_get(cvec_i(argv, 1)); + /* Third format: output format */ + formatstr = cv_string_get(cvec_i(argv, 2)); + if ((format = format_str2int(formatstr)) < 0){ + clicon_err(OE_PLUGIN, 0, "Not valid format: %s", formatstr); + goto done; + } + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } + + // if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path) < 0) + // goto done; + if (api_path_fmt2xpath(api_path_fmt, cvv, &xpath) < 0) + goto done; + /* Get configuration from database */ + if (clicon_rpc_get_config(h, db, xpath, &xt) < 0) + goto done; + if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ + clicon_rpc_generate_error("Get configuration", xerr); + goto done; + } + if ((xp = xpath_first(xt, "%s", xpath)) != NULL) + /* Print configuration according to format */ + switch (format){ + case FORMAT_XML: + clicon_xml2file(stdout, xp, 0, 1); + break; + case FORMAT_JSON: + xml2json(stdout, xp, 1); + break; + case FORMAT_TEXT: + xml2txt(stdout, xp, 0); /* tree-formed text */ + break; + case FORMAT_CLI: + if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) + goto done; + xml2cli(stdout, xp, NULL, gt); /* cli syntax */ + break; + case FORMAT_NETCONF: + fprintf(stdout, "\n"); + clicon_xml2file(stdout, xp, 2, 1); + fprintf(stdout, "]]>]]>\n"); + break; + default: /* see cli_show_config() */ + break; + } + retval = 0; + done: + if (xt) + xml_free(xt); + return retval; +} + +#ifdef COMPAT_CLIV +int show_yangv(clicon_handle h, cvec *vars, cvec *argv) +{ + return show_yang(h, vars, argv); +} +int show_confv_xpath(clicon_handle h, cvec *vars, cvec *argv) +{ + return show_conf_xpath(h, vars, argv); +} +#endif /* COMPAT_CLIV */ diff --git a/apps/cli/clixon_cli_api.h b/apps/cli/clixon_cli_api.h index 9c0ececf..c31645ba 100644 --- a/apps/cli/clixon_cli_api.h +++ b/apps/cli/clixon_cli_api.h @@ -72,55 +72,51 @@ int cli_notification_register(clicon_handle h, char *stream, enum format_enum fo int cli_set(clicon_handle h, cvec *vars, cvec *argv); -int cli_setv(clicon_handle h, cvec *vars, cvec *argv); int cli_merge(clicon_handle h, cvec *vars, cvec *argv); -int cli_mergev(clicon_handle h, cvec *vars, cvec *argv); int cli_create(clicon_handle h, cvec *vars, cvec *argv); int cli_remove(clicon_handle h, cvec *vars, cvec *argv); int cli_del(clicon_handle h, cvec *vars, cvec *argv); -int cli_delv(clicon_handle h, cvec *vars, cvec *argv); int cli_debug_cli(clicon_handle h, cvec *vars, cvec *argv); -int cli_debug_cliv(clicon_handle h, cvec *vars, cvec *argv); + int cli_debug_backend(clicon_handle h, cvec *vars, cvec *argv); -int cli_debug_backendv(clicon_handle h, cvec *vars, cvec *argv); + + +int cli_debug_restconf(clicon_handle h, cvec *vars, cvec *argv); int cli_set_mode(clicon_handle h, cvec *vars, cvec *argv); -int cli_set_modev(clicon_handle h, cvec *vars, cvec *argv); + int cli_start_shell(clicon_handle h, cvec *vars, cvec *argv); -int cli_start_shellv(clicon_handle h, cvec *vars, cvec *argv); + int cli_quit(clicon_handle h, cvec *vars, cvec *argv); -int cli_quitv(clicon_handle h, cvec *vars, cvec *argv); + int cli_commit(clicon_handle h, cvec *vars, cvec *argv); -int cli_commitv(clicon_handle h, cvec *vars, cvec *argv); int cli_validate(clicon_handle h, cvec *vars, cvec *argv); -int cli_validatev(clicon_handle h, cvec *vars, cvec *argv); + int compare_dbs(clicon_handle h, cvec *vars, cvec *argv); -int compare_dbsv(clicon_handle h, cvec *vars, cvec *argv); int load_config_file(clicon_handle h, cvec *vars, cvec *argv); -int load_config_filev(clicon_handle h, cvec *vars, cvec *argv); int save_config_file(clicon_handle h, cvec *vars, cvec *argv); -int save_config_filev(clicon_handle h, cvec *vars, cvec *argv); + int delete_all(clicon_handle h, cvec *vars, cvec *argv); -int delete_allv(clicon_handle h, cvec *vars, cvec *argv); + int discard_changes(clicon_handle h, cvec *vars, cvec *argv); -int discard_changesv(clicon_handle h, cvec *vars, cvec *argv); + int cli_notify(clicon_handle h, cvec *cvv, cvec *argv); -int cli_notifyv(clicon_handle h, cvec *cvv, cvec *argv); + int db_copy(clicon_handle h, cvec *cvv, cvec *argv); @@ -139,11 +135,34 @@ int expandv_dbvar(void *h, char *name, cvec *cvv, cvec *argv, /* cli_show.c: CLIgen new vector arg callbacks */ int show_yang(clicon_handle h, cvec *vars, cvec *argv); -int show_yangv(clicon_handle h, cvec *vars, cvec *argv); + int show_conf_xpath(clicon_handle h, cvec *cvv, cvec *argv); -int show_confv_xpath(clicon_handle h, cvec *cvv, cvec *argv); + int cli_show_config(clicon_handle h, cvec *cvv, cvec *argv); +int cli_show_auto(clicon_handle h, cvec *cvv, cvec *argv); + +#ifdef COMPAT_CLIV +int cli_setv(clicon_handle h, cvec *vars, cvec *argv); +int cli_mergev(clicon_handle h, cvec *vars, cvec *argv); +int cli_delv(clicon_handle h, cvec *vars, cvec *argv); +int cli_debug_cliv(clicon_handle h, cvec *vars, cvec *argv); +int cli_debug_backendv(clicon_handle h, cvec *vars, cvec *argv); +int cli_set_modev(clicon_handle h, cvec *vars, cvec *argv); +int cli_start_shellv(clicon_handle h, cvec *vars, cvec *argv); +int cli_quitv(clicon_handle h, cvec *vars, cvec *argv); +int cli_commitv(clicon_handle h, cvec *vars, cvec *argv); +int cli_validatev(clicon_handle h, cvec *vars, cvec *argv); +int compare_dbsv(clicon_handle h, cvec *vars, cvec *argv); +int load_config_filev(clicon_handle h, cvec *vars, cvec *argv); +int save_config_filev(clicon_handle h, cvec *vars, cvec *argv); +int delete_allv(clicon_handle h, cvec *vars, cvec *argv); +int discard_changesv(clicon_handle h, cvec *vars, cvec *argv); +int cli_notifyv(clicon_handle h, cvec *cvv, cvec *argv); +int show_yangv(clicon_handle h, cvec *vars, cvec *argv); +int show_confv_xpath(clicon_handle h, cvec *cvv, cvec *argv); +#endif /* COMPAT_CLIV */ + #endif /* _CLIXON_CLI_API_H_ */ diff --git a/apps/netconf/netconf_hello.c b/apps/netconf/netconf_hello.c index 833a1c5c..9fc0c2c2 100644 --- a/apps/netconf/netconf_hello.c +++ b/apps/netconf/netconf_hello.c @@ -82,12 +82,15 @@ static int netconf_hello(cxobj *xn) { +#ifdef nyi cxobj *x; x = NULL; + while ((x = xpath_each(xn, "//capability", x)) != NULL) { - //fprintf(stderr, "cap: %s\n", xml_body(x)); + } +#endif return 0; } diff --git a/apps/netconf/netconf_lib.c b/apps/netconf/netconf_lib.c index 5e5e21d2..998b4ef0 100644 --- a/apps/netconf/netconf_lib.c +++ b/apps/netconf/netconf_lib.c @@ -164,7 +164,7 @@ netconf_get_target(cxobj *xn, cxobj *x; char *target = NULL; - if ((x = xpath_first(xn, path)) != NULL){ + if ((x = xpath_first(xn, "%s", path)) != NULL){ if (xpath_first(x, "candidate") != NULL) target = "candidate"; else diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index 20fd8cdf..9811d959 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -71,7 +71,7 @@ #include "netconf_rpc.h" /* Command line options to be passed to getopt(3) */ -#define NETCONF_OPTS "hDqf:d:Sy:" +#define NETCONF_OPTS "hDqf:d:Sy:U:" /*! Process incoming packet * @param[in] h Clicon handle @@ -134,6 +134,12 @@ process_incoming_packet(clicon_handle h, if ((cbret = cbuf_new()) != NULL){ if ((xc = xml_child_i(xret,0))!=NULL){ xa=NULL; + /* Copy message-id attribute from incoming to reply. + * RFC 6241: + * If additional attributes are present in an element, a NETCONF + * peer MUST return them unmodified in the element. This + * includes any "xmlns" attributes. + */ while ((xa = xml_child_each(xrpc, xa, CX_ATTR)) != NULL){ if ((xa2 = xml_dup(xa)) ==NULL) goto done; @@ -182,7 +188,7 @@ netconf_input_cb(int s, int poll; if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "%s: cbuf_new", __FUNCTION__); + clicon_err(OE_XML, errno, "cbuf_new"); return retval; } memset(buf, 0, sizeof(buf)); @@ -292,7 +298,8 @@ usage(clicon_handle h, "\t-f \tConfiguration file (mandatory)\n" "\t-d \tSpecify netconf plugin directory dir (default: %s)\n" "\t-S\t\tLog on syslog\n" - "\t-y \tOverride yang spec file (dont include .yang suffix)\n", + "\t-y \tOverride yang spec file (dont include .yang suffix)\n" + "\t-U \tOver-ride unix user with a pseudo user for NACM.\n", argv0, clicon_netconf_dir(h) ); @@ -379,6 +386,12 @@ main(int argc, clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", optarg); break; } + case 'U': /* Clixon 'pseudo' user */ + if (!strlen(optarg)) + usage(h, argv[0]); + if (clicon_username_set(h, optarg) < 0) + goto done; + break; default: usage(h, argv[0]); break; diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 83484335..da50636e 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -752,7 +752,7 @@ netconf_notification_cb(int s, goto done; /* handle close from remote end: this will exit the client */ if (eof){ - clicon_err(OE_PROTO, ESHUTDOWN, "%s: Socket unexpected close", __FUNCTION__); + clicon_err(OE_PROTO, ESHUTDOWN, "Socket unexpected close"); close(s); errno = ESHUTDOWN; event_unreg_fd(s, netconf_notification_cb); @@ -770,13 +770,13 @@ netconf_notification_cb(int s, /* find and apply filter */ if ((selector = xml_find_value(xfilter, "select")) == NULL) goto done; - if (xpath_first(xe, selector) == NULL) { + if (xpath_first(xe, "%s", selector) == NULL) { fprintf(stderr, "%s no match\n", __FUNCTION__); /* debug */ } } /* create netconf message */ if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_PLUGIN, errno, "%s: cbuf_new", __FUNCTION__); + clicon_err(OE_PLUGIN, errno, "cbuf_new"); goto done; } add_preamble(cb); /* Make it well-formed netconf xml */ @@ -888,12 +888,19 @@ netconf_application_rpc(clicon_handle h, goto done; } cbuf_reset(cb); - // if (xml_namespace(xn)) - cprintf(cb, "/%s:%s", xml_namespace(xn), xml_name(xn)); - // else - // cprintf(cb, "/%s", xml_name(xn)); /* XXX not accepdted by below */ + if (xml_namespace(xn) == NULL){ + xml_parse_va(xret, NULL, "" + "operation-failed" + "rpc" + "error" + "%s" + "Not recognized" + "", xml_name(xn)); + goto ok; + } + cprintf(cb, "/%s:%s", xml_namespace(xn), xml_name(xn)); /* Find yang rpc statement, return yang rpc statement if found */ - if (yang_abs_schema_nodeid(yspec, cbuf_get(cb), Y_RPC, &yrpc) < 0) + if (yang_abs_schema_nodeid(yspec, cbuf_get(cb), Y_RPC, &yrpc) < 0) goto done; /* Check if found */ if (yrpc != NULL){ @@ -937,6 +944,7 @@ netconf_application_rpc(clicon_handle h, retval = 1; /* handled by callback */ goto done; } + ok: retval = 0; done: if (cb) @@ -964,12 +972,26 @@ netconf_rpc_dispatch(clicon_handle h, int retval = -1; cxobj *xe; yang_spec *yspec = NULL; + char *username; + cxobj *xa; /* Check incoming RPC against system / netconf RPC:s */ if ((yspec = clicon_netconf_yang(h)) == NULL){ clicon_err(OE_YANG, ENOENT, "No netconf yang spec"); goto done; } + /* Tag username on all incoming requests in case they are forwarded as internal messages + * This may be unecesary since not all are forwarded. + * It may even be wrong if something else is done with the incoming message? + */ + if ((username = clicon_username_get(h)) != NULL){ + if ((xa = xml_new("username", xn, NULL)) == NULL) + goto done; + xml_type_set(xa, CX_ATTR); + if (xml_value_set(xa, username) < 0) + goto done; + } + xe = NULL; while ((xe = xml_child_each(xn, xe, CX_ELMNT)) != NULL) { if (strcmp(xml_name(xe), "get-config") == 0){ @@ -1047,5 +1069,8 @@ netconf_rpc_dispatch(clicon_handle h, } retval = 0; done: + /* Username attribute added at top - otherwise it is returned to sender */ + if ((xa = xml_find(xn, "username")) != NULL) + xml_purge(xa); return retval; } diff --git a/apps/restconf/README.md b/apps/restconf/README.md index d6c69b89..ce077300 100644 --- a/apps/restconf/README.md +++ b/apps/restconf/README.md @@ -2,7 +2,12 @@ ### Installation using Nginx -Define nginx config file/etc/nginx/sites-available/default +Ensure www-data is member of the CLICON_SOCK_GROUP (default clicon). If not, add it: +``` + sudo usermod -a -G clicon www-data +``` + +Define nginx config file: /etc/nginx/sites-available/default ``` server { ... @@ -17,6 +22,11 @@ Start nginx daemon ``` sudo /etc/init.d nginx start ``` +Alternatively, start it via systemd: +``` +sudo /etc/init.d/nginx start +sudo systemctl start start.service +``` Start clixon restconf daemon ``` diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index b65c3c27..d75dafd9 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -95,6 +95,9 @@ Mapping netconf error-tag -> status code */ +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif #include #include #include @@ -252,9 +255,9 @@ api_data_get2(clicon_handle h, } } else{ - if (xpath_vec(xret, path, &xvec, &xlen) < 0) + if (xpath_vec(xret, "%s", &xvec, &xlen, path) < 0) goto done; - clicon_debug(1, "%s: xpath:%s xlen:%d", __FUNCTION__, path, xlen); + clicon_debug(1, "%s: xpath:%s xlen:%d", __FUNCTION__, path, (int)xlen); if (use_xml){ for (i=0; i"); ym = NULL; while ((ym = yn_each((yang_node*)yspec, ym)) != NULL) { - if ((yprefix = yang_find((yang_node*)ym, Y_PREFIX, NULL)) != NULL) - prefix = yprefix->ys_argument; - else - continue; + modname = ym->ys_argument; yc = NULL; while ((yc = yn_each((yang_node*)ym, yc)) != NULL) { if (yc->ys_keyword != Y_RPC) continue; - cprintf(cbx, "<%s:%s />", prefix, yc->ys_argument); + cprintf(cbx, "<%s:%s />", modname, yc->ys_argument); } } cprintf(cbx, ""); @@ -1030,6 +1029,9 @@ api_operations_post(clicon_handle h, char *username; cbuf *cbret = NULL; int ret = 0; + char *prefix = NULL; + char *id = NULL; + yang_stmt *ys = NULL; clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path); if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -1047,9 +1049,28 @@ api_operations_post(clicon_handle h, } clicon_debug(1, "%s oppath: %s", __FUNCTION__, oppath); - /* Find yang rpc statement, return yang rpc statement if found */ - if (yang_abs_schema_nodeid(yspec, oppath, Y_RPC, &yrpc) < 0) + /* Find yang rpc statement, return yang rpc statement if found + * POST {+restconf}/operations/ + * + * The field identifies the module name and rpc identifier + * string for the desired operation. + */ + if (yang_nodeid_split(oppath+1, &prefix, &id) < 0) /* +1 skip / */ goto done; + if ((ys = yang_find((yang_node*)yspec, Y_MODULE, prefix)) == NULL){ + if (netconf_operation_failed_xml(&xerr, "protocol", "yang module not found") < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; + } + if ((yrpc = yang_find((yang_node*)ys, Y_RPC, id)) == NULL){ + if (netconf_operation_failed_xml(&xerr, "protocol", "yang node not found") < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; + } if (yrpc == NULL){ if (netconf_operation_failed_xml(&xerr, "protocol", "yang node not found") < 0) goto done; @@ -1224,6 +1245,10 @@ api_operations_post(clicon_handle h, retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (prefix) + free(prefix); + if (id) + free(id); if (xdata) xml_free(xdata); if (xtop) diff --git a/clixon.mk.cpp b/clixon.mk.cpp deleted file mode 100644 index 2f27c669..00000000 --- a/clixon.mk.cpp +++ /dev/null @@ -1,46 +0,0 @@ -# -# ***** BEGIN LICENSE BLOCK ***** -# -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren -# -# This file is part of CLIXON -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Alternatively, the contents of this file may be used under the terms of -# the GNU General Public License Version 3 or later (the "GPL"), -# in which case the provisions of the GPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of the GPL, and not to allow others to -# use your version of this file under the terms of Apache License version 2, -# indicate your decision by deleting the provisions above and replace them with -# the notice and other provisions required by the GPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the Apache License version 2 or the GPL. -# -# ***** END LICENSE BLOCK ***** -# - -# -# Include this file in your application Makefile using eg: -# -include $(datarootdir)/clixon/clixon.mk -# then you can use the DIRS below in your install rules. -# You also get rules for the application configure file. -# NOTE: APPNAME must be defined in the local Makefile - -clixon_DBSPECDIR=prefix/share/$(APPNAME) -clixon_SYSCONFDIR=sysconfdir -clixon_LOCALSTATEDIR=localstatedir/$(APPNAME) -clixon_LIBDIR=libdir/$(APPNAME) -clixon_DATADIR=datadir/clixon # for system yang files - diff --git a/configure b/configure index de6b70db..a6e6a366 100755 --- a/configure +++ b/configure @@ -621,6 +621,7 @@ ac_includes_default="\ ac_subst_vars='LTLIBOBJS LIBOBJS +CLIXON_DATADIR EGREP GREP LEXLIB @@ -634,12 +635,12 @@ EXEEXT ac_ct_CC with_restconf RANLIB -AR -EXE_SUFFIX SH_SUFFIX -AR_SUFFIX -OBJ_SUFFIX INSTALLFLAGS +INSTALL +INSTALL_DATA +INSTALL_SCRIPT +INSTALL_PROGRAM CPPFLAGS INCLUDES LDFLAGS @@ -705,6 +706,7 @@ SHELL' ac_subst_files='' ac_user_opts=' enable_option_checking +enable_debug with_cligen with_restconf with_configfile @@ -1339,6 +1341,12 @@ if test -n "$ac_init_help"; then cat <<\_ACEOF +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-debug Build with debug symbols, default: no + Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) @@ -2147,10 +2155,11 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu : ${INSTALLFLAGS="-s"} CLIXON_VERSION_MAJOR="3" -CLIXON_VERSION_MINOR="6" -CLIXON_VERSION_PATCH="1" +CLIXON_VERSION_MINOR="7" +CLIXON_VERSION_PATCH="0" CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" -# Fix to specific version (eg 3.5) or head (3) + +# Fix to specific CLIgen version (eg 3.5) or head (3) CLIGEN_VERSION="3" if test "$prefix" = "NONE"; then CLIGEN_PREFIX="$ac_default_prefix" @@ -2339,6 +2348,99 @@ test -n "$target_alias" && +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AmigaOS /C/install, which installs bootblocks on floppy discs +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# OS/2's system install, which has a completely different semantic +# ./install, which can be erroneously created by make from ./install.sh. +# Reject install programs that cannot install multiple files. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 +$as_echo_n "checking for a BSD-compatible install... " >&6; } +if test -z "$INSTALL"; then +if ${ac_cv_path_install+:} false; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + # Account for people who put trailing slashes in PATH elements. +case $as_dir/ in #(( + ./ | .// | /[cC]/* | \ + /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ + ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ + /usr/ucb/* ) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then + if test $ac_prog = install && + grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + elif test $ac_prog = install && + grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # program-specific install script used by HP pwplus--don't use. + : + else + rm -rf conftest.one conftest.two conftest.dir + echo one > conftest.one + echo two > conftest.two + mkdir conftest.dir + if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && + test -s conftest.one && test -s conftest.two && + test -s conftest.dir/conftest.one && + test -s conftest.dir/conftest.two + then + ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" + break 3 + fi + fi + fi + done + done + ;; +esac + + done +IFS=$as_save_IFS + +rm -rf conftest.one conftest.two conftest.dir + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL=$ac_cv_path_install + else + # As a last resort, use the slow shell script. Don't cache a + # value for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + INSTALL=$ac_install_sh + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 +$as_echo "$INSTALL" >&6; } + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + @@ -3279,6 +3381,27 @@ CPPFLAGS="-DHAVE_CONFIG_H ${CPPFLAGS}" { $as_echo "$as_me:${as_lineno-$LINENO}: result: compiler is $CC" >&5 $as_echo "compiler is $CC" >&6; } +# Debug flag +# Check whether --enable-debug was given. +if test "${enable_debug+set}" = set; then : + enableval=$enable_debug; + if test "$enableval" = no; then + ac_enable_debug=no + else + ac_enable_debug=yes + fi + +else + ac_enable_debug=no +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: debug is $ac_enable_debug" >&5 +$as_echo "debug is $ac_enable_debug" >&6; } +if test "$ac_enable_debug" = "yes"; then + CFLAGS="-g -Wall" + INSTALLFLAGS="" +fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: CPPFLAGS is $CPPFLAGS" >&5 $as_echo "CPPFLAGS is $CPPFLAGS" >&6; } @@ -3540,11 +3663,7 @@ _ACEOF fi -EXE_SUFFIX="" -OBJ_SUFFIX=".o" -AR_SUFFIX=".a" SH_SUFFIX=".so" -AR="ar" # This is for cligen @@ -4190,11 +4309,12 @@ fi done -# This is to find clixon system files in the source code. -# same as clixon_DATADIR defined in clixon.mk.cpp for Makefiles +# This is to find clixon system files in the source code and Makefile + +CLIXON_DATADIR="${prefix}/share/clixon" cat >>confdefs.h <<_ACEOF -#define CLIXON_DATADIR "${prefix}/share/clixon" +#define CLIXON_DATADIR "${CLIXON_DATADIR}" _ACEOF @@ -4207,7 +4327,7 @@ _ACEOF -ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/docker/Makefile extras/rpm/Makefile docker/Makefile docker/cli/Makefile docker/cli/Dockerfile docker/backend/Makefile docker/backend/Dockerfile docker/netconf/Makefile docker/netconf/Dockerfile datastore/Makefile datastore/text/Makefile yang/Makefile doc/Makefile" +ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile extras/rpm/Makefile docker/Makefile datastore/Makefile datastore/text/Makefile util/Makefile yang/Makefile doc/Makefile" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure @@ -4787,6 +4907,7 @@ gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' +INSTALL='$INSTALL' test -n "\$AWK" || AWK=awk _ACEOF @@ -4913,17 +5034,11 @@ do "etc/Makefile") CONFIG_FILES="$CONFIG_FILES etc/Makefile" ;; "etc/clixonrc") CONFIG_FILES="$CONFIG_FILES etc/clixonrc" ;; "example/Makefile") CONFIG_FILES="$CONFIG_FILES example/Makefile" ;; - "example/docker/Makefile") CONFIG_FILES="$CONFIG_FILES example/docker/Makefile" ;; "extras/rpm/Makefile") CONFIG_FILES="$CONFIG_FILES extras/rpm/Makefile" ;; "docker/Makefile") CONFIG_FILES="$CONFIG_FILES docker/Makefile" ;; - "docker/cli/Makefile") CONFIG_FILES="$CONFIG_FILES docker/cli/Makefile" ;; - "docker/cli/Dockerfile") CONFIG_FILES="$CONFIG_FILES docker/cli/Dockerfile" ;; - "docker/backend/Makefile") CONFIG_FILES="$CONFIG_FILES docker/backend/Makefile" ;; - "docker/backend/Dockerfile") CONFIG_FILES="$CONFIG_FILES docker/backend/Dockerfile" ;; - "docker/netconf/Makefile") CONFIG_FILES="$CONFIG_FILES docker/netconf/Makefile" ;; - "docker/netconf/Dockerfile") CONFIG_FILES="$CONFIG_FILES docker/netconf/Dockerfile" ;; "datastore/Makefile") CONFIG_FILES="$CONFIG_FILES datastore/Makefile" ;; "datastore/text/Makefile") CONFIG_FILES="$CONFIG_FILES datastore/text/Makefile" ;; + "util/Makefile") CONFIG_FILES="$CONFIG_FILES util/Makefile" ;; "yang/Makefile") CONFIG_FILES="$CONFIG_FILES yang/Makefile" ;; "doc/Makefile") CONFIG_FILES="$CONFIG_FILES doc/Makefile" ;; @@ -5369,6 +5484,10 @@ ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix # CONFIG_FILE # + case $INSTALL in + [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; + *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; + esac _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 @@ -5422,6 +5541,7 @@ s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +s&@INSTALL@&$ac_INSTALL&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ diff --git a/configure.ac b/configure.ac index 490a7e4a..964141c1 100644 --- a/configure.ac +++ b/configure.ac @@ -43,10 +43,11 @@ AC_INIT(lib/clixon/clixon.h.in) : ${INSTALLFLAGS="-s"} CLIXON_VERSION_MAJOR="3" -CLIXON_VERSION_MINOR="6" -CLIXON_VERSION_PATCH="1" +CLIXON_VERSION_MINOR="7" +CLIXON_VERSION_PATCH="0" CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" -# Fix to specific version (eg 3.5) or head (3) + +# Fix to specific CLIgen version (eg 3.5) or head (3) CLIGEN_VERSION="3" if test "$prefix" = "NONE"; then CLIGEN_PREFIX="$ac_default_prefix" @@ -73,18 +74,19 @@ AC_SUBST(CLIGEN_PREFIX) AC_MSG_RESULT(CLIXON version is ${CLIXON_VERSION}) AC_CANONICAL_TARGET + AC_SUBST(CC) AC_SUBST(CFLAGS) AC_SUBST(LDFLAGS) AC_SUBST(INCLUDES) AC_SUBST(CPPFLAGS) +AC_PROG_INSTALL +AC_SUBST(INSTALL) +AC_SUBST(INSTALL_DATA) +AC_SUBST(INSTALL_PROGRAM) AC_SUBST(INSTALLFLAGS) AC_SUBST(LIBS) -AC_SUBST(OBJ_SUFFIX) -AC_SUBST(AR_SUFFIX) AC_SUBST(SH_SUFFIX) -AC_SUBST(EXE_SUFFIX) -AC_SUBST(AR) AC_SUBST(RANLIB) AC_SUBST(with_restconf) # If yes, compile apps/restconf # @@ -94,6 +96,21 @@ AC_PROG_CPP CPPFLAGS="-DHAVE_CONFIG_H ${CPPFLAGS}" AC_MSG_RESULT(compiler is $CC) +# Debug flag +AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug],[Build with debug symbols, default: no]),[ + if test "$enableval" = no; then + ac_enable_debug=no + else + ac_enable_debug=yes + fi + ], + [ ac_enable_debug=no]) + +AC_MSG_RESULT(debug is $ac_enable_debug) +if test "$ac_enable_debug" = "yes"; then + CFLAGS="-g -Wall" + INSTALLFLAGS="" +fi AC_MSG_RESULT(CPPFLAGS is $CPPFLAGS) AC_MSG_RESULT(CFLAGS is $CFLAGS) @@ -113,11 +130,7 @@ if test "$prefix" = "NONE"; then fi AC_CHECK_LIB(m, main) -EXE_SUFFIX="" -OBJ_SUFFIX=".o" -AR_SUFFIX=".a" SH_SUFFIX=".so" -AR="ar" # This is for cligen AC_ARG_WITH(cligen, [ --with-cligen=dir Use CLIGEN here ] ) @@ -165,9 +178,10 @@ AC_CHECK_LIB(dl, dlopen) AC_CHECK_FUNCS(inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort strverscmp) -# This is to find clixon system files in the source code. -# same as clixon_DATADIR defined in clixon.mk.cpp for Makefiles -AC_DEFINE_UNQUOTED(CLIXON_DATADIR, "${prefix}/share/clixon", [Clixon data dir for system yang files etc]) +# This is to find clixon system files in the source code and Makefile +AC_SUBST(CLIXON_DATADIR) +CLIXON_DATADIR="${prefix}/share/clixon" +AC_DEFINE_UNQUOTED(CLIXON_DATADIR, "${CLIXON_DATADIR}", [Clixon data dir for system yang files etc]) # Default location for config file AC_DEFINE_UNQUOTED(CLIXON_DEFAULT_CONFIG,"${DEFAULT_CONFIG}",[Location for apps to find default config file]) @@ -187,18 +201,12 @@ AC_OUTPUT(Makefile etc/Makefile etc/clixonrc example/Makefile - example/docker/Makefile extras/rpm/Makefile docker/Makefile - docker/cli/Makefile - docker/cli/Dockerfile - docker/backend/Makefile - docker/backend/Dockerfile - docker/netconf/Makefile - docker/netconf/Dockerfile datastore/Makefile datastore/text/Makefile + util/Makefile yang/Makefile - doc/Makefile + doc/Makefile ) diff --git a/datastore/Makefile.in b/datastore/Makefile.in index 16beab79..98bc8a0e 100644 --- a/datastore/Makefile.in +++ b/datastore/Makefile.in @@ -78,8 +78,6 @@ APPL = datastore_client all: $(SUBDIRS) $(APPL) --include $(DESTDIR)$(datarootdir)/clixon/clixon.mk - .SUFFIXES: .SUFFIXES: .c .o @@ -101,14 +99,11 @@ install-include: for i in $(SUBDIRS); \ do (cd $$i ; $(MAKE) $(MFLAGS) $@)||exit 1; done; -install: $(APPL) - install -d -m 0755 $(DESTDIR)$(bindir) - install -m 0755 $(INSTALLFLAGS) $(APPL) $(DESTDIR)$(bindir) +install: for i in $(SUBDIRS); \ do (cd $$i && $(MAKE) $(MFLAGS) $@)||exit 1; done uninstall: - rm -f $(DESTDIR)$(bindir)/$(APPL) for i in $(SUBDIRS); \ do (cd $$i && $(MAKE) $(MFLAGS) $@)||exit 1; done diff --git a/datastore/keyvalue/Makefile.in b/datastore/keyvalue/Makefile.in index 19aa6fe6..08fc997f 100644 --- a/datastore/keyvalue/Makefile.in +++ b/datastore/keyvalue/Makefile.in @@ -63,7 +63,6 @@ OBJS = $(SRC:.c=.o) all: $(PLUGIN) --include $(DESTDIR)$(datarootdir)/clixon/clixon.mk $(PLUGIN): $(SRC) $(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -shared -o $@ -lc $^ $(LIBS) diff --git a/datastore/keyvalue/clixon_chunk.c b/datastore/keyvalue/clixon_chunk.c index cb197724..80db76da 100644 --- a/datastore/keyvalue/clixon_chunk.c +++ b/datastore/keyvalue/clixon_chunk.c @@ -92,7 +92,7 @@ chunk_initialize () chunk_pagesz = getpagesize(); - bzero (&chunk_heads, sizeof (chunk_heads)); + bzero (&chunk_heads, sizeof(chunk_heads)); for (idx = 0; idx < CHUNK_HEADS; idx++) { chunk_head_t *chead = &chunk_heads[idx]; @@ -121,7 +121,7 @@ chunk_initialize () */ chead->ch_size = (chead->ch_blksz / chead->ch_nchkperblk) - - sizeof (chunk_t); + - sizeof(chunk_t); } @@ -150,22 +150,22 @@ chunk_new_block (chunk_head_t *chead) PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (blk == MAP_FAILED) return -1; - memset ((void *)blk, 0, sizeof(*blk)); + memset((void *)blk, 0, sizeof(*blk)); /* Allocate chunk block */ blk->cb_blk = (void *) mmap(NULL, chead->ch_blksz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (blk->cb_blk == MAP_FAILED) { - munmap (blk, chead->ch_blksz); + munmap(blk, chead->ch_blksz); return -1; } - memset (blk->cb_blk, 0, chead->ch_blksz); + memset(blk->cb_blk, 0, chead->ch_blksz); /* Initialize chunk header */ blk->cb_head = chead; - INSQ (blk, chead->ch_blks); + INSQ(blk, chead->ch_blks); chead->ch_nblks++; /* Initialize chunks */ @@ -174,10 +174,10 @@ chunk_new_block (chunk_head_t *chead) cnk = (chunk_t *)c; cnk->c_blk = blk; - INSQ (cnk, chead->ch_free); + INSQ(cnk, chead->ch_free); chead->ch_nfree++; - c += (chead->ch_size + sizeof (chunk_t)); + c += (chead->ch_size + sizeof(chunk_t)); } @@ -188,7 +188,7 @@ chunk_new_block (chunk_head_t *chead) * chunk_release_block() - Unqueue a block, it's chunks and free mem */ static void -chunk_release_block (chunk_block_t *cblk) +chunk_release_block(chunk_block_t *cblk) { int idx; char *c; @@ -201,7 +201,7 @@ chunk_release_block (chunk_block_t *cblk) /* * Dequeue block */ - DELQ (cblk, chead->ch_blks, chunk_block_t *); + DELQ(cblk, chead->ch_blks, chunk_block_t *); chead->ch_nblks--; /* @@ -211,17 +211,17 @@ chunk_release_block (chunk_block_t *cblk) for (idx = 0; idx < chead->ch_nchkperblk; idx++) { cnk = (chunk_t *)c; - DELQ (cnk, chead->ch_free, chunk_t *); + DELQ(cnk, chead->ch_free, chunk_t *); chead->ch_nfree--; - c += (chead->ch_size + sizeof (chunk_t)); + c += (chead->ch_size + sizeof(chunk_t)); } /* * Free block */ - munmap ((void *)cblk->cb_blk, chead->ch_blksz); - munmap ((void *)cblk, sizeof(*cblk)); + munmap((void *)cblk->cb_blk, chead->ch_blksz); + munmap((void *)cblk, sizeof(*cblk)); } @@ -230,7 +230,7 @@ chunk_release_block (chunk_block_t *cblk) * chunk_alloc() - Map new chunk of memory */ static void * -chunk_alloc (size_t len) +chunk_alloc(size_t len) { register int idx; chunk_head_t *chead; @@ -258,21 +258,21 @@ chunk_alloc (size_t len) /* Get new block if necessary */ if (!chead->ch_nfree) - if (chunk_new_block (chead)) + if (chunk_new_block(chead)) return (void *)NULL; /* Move a free chunk to the in-use list */ cnk = chead->ch_free; - DELQ (cnk, chead->ch_free, chunk_t *); + DELQ(cnk, chead->ch_free, chunk_t *); chead->ch_nfree--; - INSQ (cnk, chead->ch_cnks); + INSQ(cnk, chead->ch_cnks); /* Add reference to the corresponding block */ cnk->c_blk->cb_ref++; #ifdef CHUNK_DIAG /* Clear diag info */ - bzero ((void *)&cnk->c_diag, sizeof (cnk->c_diag)); + bzero((void *)&cnk->c_diag, sizeof(cnk->c_diag)); #endif /* CHUNK_DIAG */ /* Return pointer to first byte after the chunk header */ @@ -285,9 +285,9 @@ chunk_alloc (size_t len) */ void * #ifdef CHUNK_DIAG -_chunk (size_t len, const char *name, const char *file, int line) +_chunk(size_t len, const char *name, const char *file, int line) #else -chunk (size_t len, const char *name) +chunk(size_t len, const char *name) #endif { int newgrp = 0; @@ -306,10 +306,10 @@ chunk (size_t len, const char *name) /* Get actual chunk */ - ptr = chunk_alloc (len); + ptr = chunk_alloc(len); if (!ptr) goto error; - cnk = (chunk_t *) (((char *)ptr) - sizeof (chunk_t)); + cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); #ifdef CHUNK_DIAG /* Store who reuested us @@ -329,7 +329,7 @@ chunk (size_t len, const char *name) if (chunk_grp) { tmp = chunk_grp; do { - if (!strcmp (tmp->cg_name, name)) { + if (!strcmp(tmp->cg_name, name)) { grp = tmp; break; } @@ -343,26 +343,26 @@ chunk (size_t len, const char *name) */ if ( !grp ) { - grp = (chunk_group_t *) chunk_alloc (sizeof (chunk_group_t)); + grp = (chunk_group_t *) chunk_alloc(sizeof(chunk_group_t)); if (!grp) goto error; - bzero (grp, sizeof (chunk_group_t)); + bzero(grp, sizeof(chunk_group_t)); - grp->cg_name = (char *) chunk_alloc (strlen (name) + 1); + grp->cg_name = (char *) chunk_alloc(strlen(name) + 1); if (!grp->cg_name) goto error; - bcopy (name, grp->cg_name, strlen(name)+1); + bcopy(name, grp->cg_name, strlen(name)+1); newgrp = 1; } /* Get new entry. */ - ent = (chunk_grpent_t *) chunk_alloc (sizeof (chunk_grpent_t)); + ent = (chunk_grpent_t *) chunk_alloc(sizeof(chunk_grpent_t)); if (!ent) goto error; - bzero (ent, sizeof (chunk_grpent_t)); + bzero(ent, sizeof(chunk_grpent_t)); /* Now put everything together */ @@ -371,22 +371,22 @@ chunk (size_t len, const char *name) ent->ce_cnk = cnk; ent->ce_grp = grp; - INSQ (ent, grp->cg_ent); + INSQ(ent, grp->cg_ent); if (newgrp) - INSQ (grp, chunk_grp); + INSQ(grp, chunk_grp); return (ptr); error: if (grp && newgrp) { if (grp->cg_name) - unchunk (grp->cg_name); - unchunk (grp); + unchunk(grp->cg_name); + unchunk(grp); } if (ent) - unchunk (ent); + unchunk(ent); if (ptr) - unchunk (ptr); + unchunk(ptr); return (void *) NULL; } @@ -397,9 +397,9 @@ chunk (size_t len, const char *name) */ void * #ifdef CHUNK_DIAG -_rechunk (void *ptr, size_t len, const char *name, const char *file, int line) +_rechunk(void *ptr, size_t len, const char *name, const char *file, int line) #else -rechunk (void *ptr, size_t len, const char *name) +rechunk(void *ptr, size_t len, const char *name) #endif { int idx; @@ -414,22 +414,22 @@ rechunk (void *ptr, size_t len, const char *name) */ if (!ptr) { #ifdef CHUNK_DIAG - return _chunk (len, name, file, line); + return _chunk(len, name, file, line); #else - return chunk (len, name); + return chunk(len, name); #endif } /* Zero length, free chunk */ if (len == 0) { - unchunk (ptr); + unchunk(ptr); return (void *) NULL; } /* Rewind pointer to beginning of chunk header */ - cnk = (chunk_t *) (((char *)ptr) - sizeof (chunk_t)); + cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); chead = cnk->c_blk->cb_head; /* Find sufficient sized block head @@ -451,22 +451,22 @@ rechunk (void *ptr, size_t len, const char *name) /* Get new chunk */ #ifdef CHUNK_DIAG - new = _chunk (len, name, file, line); + new = _chunk(len, name, file, line); #else - new = chunk (len, name); + new = chunk(len, name); #endif if (!new) return (void *) NULL; - newcnk = (chunk_t *) (((char *)new) - sizeof (chunk_t)); + newcnk = (chunk_t *) (((char *)new) - sizeof(chunk_t)); newchead = newcnk->c_blk->cb_head; /* Copy contents to new chunk */ - bcopy (ptr, new, MIN(newchead->ch_size, chead->ch_size)); + bcopy(ptr, new, MIN(newchead->ch_size, chead->ch_size)); /* Free old chunk */ - unchunk (ptr); + unchunk(ptr); return (new); @@ -476,7 +476,7 @@ rechunk (void *ptr, size_t len, const char *name) * unchunk() - Release chunk */ void -unchunk (void *ptr) +unchunk(void *ptr) { chunk_t *cnk; chunk_head_t *chead; @@ -489,14 +489,14 @@ unchunk (void *ptr) /* Rewind pointer to beginning of chunk header */ - cnk = (chunk_t *) (((char *)ptr) - sizeof (chunk_t)); + cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); cblk = cnk->c_blk; chead = cblk->cb_head; /* Move chunk back to free list */ - DELQ (cnk, chead->ch_cnks, chunk_t *); - INSQ (cnk, chead->ch_free); + DELQ(cnk, chead->ch_cnks, chunk_t *); + INSQ(cnk, chead->ch_free); chead->ch_nfree++; /* If chunk is grouped, remove from group. @@ -504,13 +504,13 @@ unchunk (void *ptr) ent = cnk->c_grpent; if (ent) { grp = ent->ce_grp; - DELQ (ent, grp->cg_ent, chunk_grpent_t *); - unchunk (ent); + DELQ(ent, grp->cg_ent, chunk_grpent_t *); + unchunk(ent); cnk->c_grpent = NULL; /* Group empty? */ if (!dont_unchunk_group && !grp->cg_ent) { - DELQ (grp, chunk_grp, chunk_group_t *); + DELQ(grp, chunk_grp, chunk_group_t *); unchunk(grp->cg_name); unchunk(grp); } @@ -528,7 +528,7 @@ unchunk (void *ptr) * unchunk_group() - Release all group chunks. */ void -unchunk_group (const char *name) +unchunk_group(const char *name) { chunk_group_t *tmp; chunk_group_t *grp = NULL; @@ -542,7 +542,7 @@ unchunk_group (const char *name) if (chunk_grp) { tmp = chunk_grp; do { - if (!strcmp (tmp->cg_name, name)) { + if (!strcmp(tmp->cg_name, name)) { grp = tmp; break; } @@ -560,16 +560,16 @@ unchunk_group (const char *name) dont_unchunk_group = 1; while (grp->cg_ent) { cnk = grp->cg_ent->ce_cnk; - unchunk ((chunk_t *)(((char *)cnk) + sizeof (chunk_t))); + unchunk((chunk_t *)(((char *)cnk) + sizeof(chunk_t))); } dont_unchunk_group = 0; /* Remove group from list and free it */ - DELQ (grp, chunk_grp, chunk_group_t *); - unchunk (grp->cg_name); - unchunk (grp); + DELQ(grp, chunk_grp, chunk_group_t *); + unchunk(grp->cg_name); + unchunk(grp); } /* @@ -577,9 +577,9 @@ unchunk_group (const char *name) */ void * #ifdef CHUNK_DIAG -_chunkdup (const void *ptr, size_t len, const char *name, const char *file, int line) +_chunkdup(const void *ptr, size_t len, const char *name, const char *file, int line) #else -chunkdup (const void *ptr, size_t len, const char *name) +chunkdup(const void *ptr, size_t len, const char *name) #endif { void *new; @@ -592,9 +592,9 @@ chunkdup (const void *ptr, size_t len, const char *name) /* Get new chunk */ #ifdef CHUNK_DIAG - new = _chunk (len, name, file, line); + new = _chunk(len, name, file, line); #else - new = chunk (len, name); + new = chunk(len, name); #endif if (!new) return (void *)NULL; @@ -610,7 +610,7 @@ chunkdup (const void *ptr, size_t len, const char *name) * chunksize() - Return size of memory chunk. */ size_t -chunksize (void *ptr) +chunksize(void *ptr) { chunk_t *cnk; chunk_head_t *chead; @@ -621,7 +621,7 @@ chunksize (void *ptr) /* Rewind pointer to beginning of chunk header */ - cnk = (chunk_t *) (((char *)ptr) - sizeof (chunk_t)); + cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); cblk = cnk->c_blk; chead = cblk->cb_head; @@ -635,10 +635,10 @@ chunksize (void *ptr) */ char * #ifdef CHUNK_DIAG -_chunk_strncat (const char *dst, const char *src, size_t n, const char *name, +_chunk_strncat(const char *dst, const char *src, size_t n, const char *name, const char *file, int line) #else -chunk_strncat (const char *dst, const char *src, size_t n, const char *name) +chunk_strncat(const char *dst, const char *src, size_t n, const char *name) #endif { size_t len; @@ -651,7 +651,7 @@ chunk_strncat (const char *dst, const char *src, size_t n, const char *name) #ifdef CHUNK_DIAG ptr = _rechunk(ptr, len, name, file, line); #else - ptr = rechunk (ptr, len, name); + ptr = rechunk(ptr, len, name); #endif if (ptr == NULL) return NULL; @@ -670,10 +670,10 @@ chunk_strncat (const char *dst, const char *src, size_t n, const char *name) */ char * #ifdef CHUNK_DIAG -_chunk_sprintf (const char *name, const char *file, +_chunk_sprintf(const char *name, const char *file, int line, const char *fmt, ...) #else -chunk_sprintf (const char *name, char *fmt, ...) +chunk_sprintf(const char *name, char *fmt, ...) #endif { size_t len; @@ -683,13 +683,13 @@ chunk_sprintf (const char *name, char *fmt, ...) /* Calculate formatted string length */ va_start(args, fmt); len = vsnprintf(NULL, 0, fmt, args) + 1; - va_end (args); + va_end(args); /* get chunk */ #ifdef CHUNK_DIAG - str = _chunk (len, name, file, line); + str = _chunk(len, name, file, line); #else - str = chunk (len, name); + str = chunk(len, name); #endif if (str == NULL) return NULL; @@ -697,7 +697,7 @@ chunk_sprintf (const char *name, char *fmt, ...) /* Format string */ va_start(args, fmt); len = vsnprintf(str, len, fmt, args); - va_end (args); + va_end(args); return str; } @@ -756,7 +756,7 @@ chunk_check(FILE *fout, const char *name) if (chunk_grp) { tmp = chunk_grp; do { - if (!strcmp (tmp->cg_name, name)) { + if (!strcmp(tmp->cg_name, name)) { grp = tmp; break; } diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c index aa529477..4e77418d 100644 --- a/datastore/keyvalue/clixon_keyvalue.c +++ b/datastore/keyvalue/clixon_keyvalue.c @@ -64,7 +64,7 @@ * * Alternative for xmlkeyfmt would be eg: * RESTCONF: /interfaces/interface=%s/ipv4/address/ip=%s (used) - * XPATH: /interfaces/interface[name=%s]/ipv4/address/[ip=%s] + * XPATH: /interfaces/interface[name='%s']/ipv4/address/[ip'=%s'] * * Paths through the code (for coverage) * cli_callback_generate +----------------+ @@ -375,7 +375,7 @@ get(char *dbname, arg = valvec[j++]; if (uri_percent_decode(arg, &argdec) < 0) goto done; - cprintf(cb, "[%s=%s]", cv_string_get(cvi), argdec); + cprintf(cb, "[%s='%s']", cv_string_get(cvi), argdec); free(argdec); argdec=NULL; } diff --git a/datastore/keyvalue/clixon_qdb.c b/datastore/keyvalue/clixon_qdb.c index d0b72b63..b80daf71 100644 --- a/datastore/keyvalue/clixon_qdb.c +++ b/datastore/keyvalue/clixon_qdb.c @@ -436,7 +436,7 @@ db_regexp(char *file, goto quit; } pair = &newpairs[npairs]; - memset (pair, 0, sizeof(*pair)); + memset(pair, 0, sizeof(*pair)); pair->dp_key = chunk_sprintf(label, "%s", key); if (regexp) @@ -451,7 +451,7 @@ db_regexp(char *file, } if ( ! noval) { if (vlen){ - pair->dp_val = chunkdup (val, vlen, label); + pair->dp_val = chunkdup(val, vlen, label); if (pair->dp_val == NULL) { clicon_err(OE_DB, errno, "%s: chunkdup", __FUNCTION__); goto quit; diff --git a/datastore/text/Makefile.in b/datastore/text/Makefile.in index cdc2adb5..23c2a7bf 100644 --- a/datastore/text/Makefile.in +++ b/datastore/text/Makefile.in @@ -63,8 +63,6 @@ OBJS = $(SRC:.c=.o) all: $(PLUGIN) --include $(DESTDIR)$(datarootdir)/clixon/clixon.mk - $(PLUGIN): $(SRC) ifeq ($(HOST_VENDOR),apple) $(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ -lc $^ $(LIBS) diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 9bc96f46..7b7d52d8 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -281,7 +281,8 @@ text_setopt(xmldb_handle xh, else if (strcmp(value,"json")==0) th->th_format = "json"; else{ - clicon_err(OE_PLUGIN, 0, "Option %s unrecognized format: %s", optname, value); + clicon_err(OE_PLUGIN, 0, "Option %s unrecognized format: %s", + optname, (char*)value); goto done; } } @@ -473,7 +474,7 @@ text_get(xmldb_handle xh, } /* xt == NULL */ /* Here xt looks like: ... */ - if (xpath_vec(xt, xpath?xpath:"/", &xvec, &xlen) < 0) + if (xpath_vec(xt, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) goto done; /* If vectors are specified then mark the nodes found with all ancestors @@ -806,7 +807,8 @@ text_modify_top(cxobj *x0, x1cname = xml_name(x1c); /* Get yang spec of the child */ if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ - clicon_err(OE_YANG, ENOENT, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", x1, x1cname); + clicon_err(OE_YANG, ENOENT, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", + xml_name(x1), x1cname); goto done; } /* See if there is a corresponding node in the base tree */ diff --git a/doc/FAQ.md b/doc/FAQ.md index 949f94ce..c1d130d0 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -1,4 +1,4 @@ -i# Clixon FAQ +# Clixon FAQ ## What is Clixon? @@ -24,7 +24,7 @@ Clixon is written in C. The plugins are written in C. The CLI specification uses cligen (http://cligen.se) ## How to best understand Clixon? -Run the Clixon example, in the example directory. +Run the Clixon example, in the [example](../example) directory. ## How do you build and install Clixon (and the example)? Clixon: @@ -41,14 +41,25 @@ The example: sudo make install ``` -## Do I need to setup anything? +## Do I need to setup anything? (IMPORTANT) The config demon requires a valid group to create a server UNIX socket. Define a valid CLICON_SOCK_GROUP in the config file or via the -g option or create the group and add the user to it. The default group is 'clicon'. +Add yourself and www-data, if you intend to use restconf. + On linux: +``` sudo groupadd clicon - sudo usermod -a -G clicon user + sudo usermod -a -G clicon + sudo usermod -a -G clicon www-data +``` + +Verify: +``` +grep clicon /etc/group +clicon:x:1001:,www-data +``` ## What about reference documentation? Clixon uses Doxygen for reference documentation. @@ -90,20 +101,14 @@ You can change where CLixon looks for the configuration FILE as follows: - FILE is /usr/local/etc/clixon.xml ## Can I run Clixon as docker containers? -Yes, the example works as docker containers as well. backend and cli needs a -common file-system so they need to run as a composed pair. + +Yes, the example works as docker containers as well. There should be a +prepared container in docker hib for the example where the backend and +CLI is bundled. ``` - cd example/docker - make docker # Prepares /data as shared file-system mount - run.sh # Starts an example backend and a cli +sudo docker run -ti --rm olofhagsand/clixon_example ``` -The containers are by default downloaded from dockerhib, but you may -build the containers locally: -``` - cd docker - make docker -``` -You may also push the containers with 'make push' but you may then consider changing the image name in the makefile. +Look in the example documentation for more info. ## How do I use netconf? @@ -127,7 +132,25 @@ You can access clixon via REST API using restconf, such as using curl. GET, PUT, POST are supported. You need a web-server, such as nginx, and start a restconf fcgi -daemon, clixon_restconf. Read more in the restconf docs. +daemon, clixon_restconf. + +For example, using nginx, install, and edit config file: /etc/nginx/sites-available/default: +``` +server { + ... + location /restconf { + root /usr/share/nginx/html/restconf; + fastcgi_pass unix:/www-data/fastcgi_restconf.sock; + include fastcgi_params; + } +} +``` +Start nginx daemon +``` +sudo /etc/init.d/nginx start +``` + +Read more in the restconf docs. Example: ``` @@ -178,6 +201,11 @@ You may also add a default method in the configuration file: ``` +## Can I use systemd with Clixon? + +Yes. Systemd example files are provide for the backend and the +restconf daemon as part of the [example](../example/systemd). + ## How can I add extra XML? There are two ways to add extra XML to running database after start. Note that this XML is not "committed" into running. @@ -342,5 +370,35 @@ plugin_credentials(clicon_handle h, To authenticate, the callback needs to return the value 1 and supply a username. -See [../apps/example/example_restconf.c] plugin_credentials() for +See [../apps/example/example_restconf.c] example_restconf_credentials() for an example of HTTP basic auth. + +## How do I write a CLI translator function? + +The CLI can perform variable translation. This is useful if you want to +prcess the input, such as hashing, encrypting or in other way +translate the input. + +Yang example: +``` +list translate{ + leaf value{ + type string; + } +} +``` + +CLI specification: +``` +translate value (),cli_set("/translate/value"); +``` + +If you run this example using the `incstr()` function which increments the characters in the input, you get this result: +``` +cli> translate value HAL +cli> show configuration +translate { + value IBM; +} +``` +You can perform translation on any type, not only strings. \ No newline at end of file diff --git a/docker/cli/Dockerfile.in b/docker/Dockerfile similarity index 72% rename from docker/cli/Dockerfile.in rename to docker/Dockerfile index 6b316d58..2a2ce179 100644 --- a/docker/cli/Dockerfile.in +++ b/docker/Dockerfile @@ -31,19 +31,41 @@ # ***** END LICENSE BLOCK ***** # -FROM ubuntu:14.04 -# 12.04 +FROM debian MAINTAINER Olof Hagsand ENV DEBIAN_FRONTEND noninteractive -RUN apt-get update && apt-get install -y libqdbm-dev + +RUN apt-get update && apt-get install -y \ + git make gcc flex bison \ + libfcgi-dev \ + libcurl4-openssl-dev + + RUN groupadd clicon -COPY libcligen.so.@CLIGEN_VERSION@ /usr/lib/ -COPY libclixon.so.@CLIXON_VERSION_MAJOR@ /usr/lib/ -COPY libclixon_cli.so.@CLIXON_VERSION_MAJOR@ /usr/lib/ -COPY clixon_cli /usr/bin/ + +# Create a directory to hold source-code, dependencies etc +RUN mkdir /clixon +WORKDIR /clixon + +# Clone cligen and clixon +RUN git clone https://github.com/olofhagsand/cligen.git +RUN git clone https://github.com/clicon/clixon.git + +# Build cligen +WORKDIR /clixon/cligen +RUN ./configure +RUN make +RUN make install + +# Build clixon +WORKDIR /clixon/clixon +RUN git checkout -b develop origin/develop +RUN ./configure +RUN make +RUN make install +RUN make install-include + RUN ldconfig -CMD ["/usr/bin/clixon_cli", "-f", "/data/clixon.conf"] - diff --git a/docker/Makefile.in b/docker/Makefile.in index 2a653879..58905f5b 100644 --- a/docker/Makefile.in +++ b/docker/Makefile.in @@ -38,46 +38,33 @@ CFLAGS = @CFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ +# Change this +IMAGE = olofhagsand/clixon + SHELL = /bin/sh -SUBDIRS = cli backend netconf -.PHONY: all clean depend install $(SUBDIRS) docker push +.PHONY: all clean depend install docker push -all: $(SUBDIRS) - -depend: - for i in $(SUBDIRS); \ - do (cd $$i; $(MAKE) $(MFLAGS) $@); done - -$(SUBDIRS): - (cd $@; $(MAKE) $(MFLAGS) all) - -install-include: - for i in $(SUBDIRS); \ - do (cd $$i ; $(MAKE) $(MFLAGS) $@); done; - -install: - for i in $(SUBDIRS); \ - do (cd $$i; $(MAKE) $(MFLAGS) $@); done - -uninstall: - for i in $(SUBDIRS); \ - do (cd $$i; $(MAKE) $(MFLAGS) $@); done +all: + @echo "Run make docker to build docker image" clean: - for i in $(SUBDIRS); \ - do (cd $$i; $(MAKE) $(MFLAGS) $@); done distclean: clean rm -f Makefile *~ .depend - for i in $(SUBDIRS); \ - do (cd $$i; $(MAKE) $(MFLAGS) $@); done docker: - for i in $(SUBDIRS); \ - do (cd $$i && $(MAKE) $(MFLAGS) $@); done + sudo docker build -t $(IMAGE) . push: - for i in $(SUBDIRS); \ - do (cd $$i && $(MAKE) $(MFLAGS) $@); done + sudo docker push $(IMAGE) + +depend: + +install-include: + +install: + +uninstall: + diff --git a/docker/README b/docker/README deleted file mode 100644 index 5fcd6d49..00000000 --- a/docker/README +++ /dev/null @@ -1,12 +0,0 @@ -This dir is not updated - -This dir contains docker code - how to build clixon as docker containers - cli Build olofhagsand/clixon_cli container - backend Build olofhagsand/clixon_backend container - netconf Build olofhagsand/clixon_netconf container - -Perform the build by 'make docker'. -You may also do 'make push' if you want to push the image, but you may then consider changing the image name (in the makefile:s). - -You may run the container directly by going directly to example and -the docker runtime scripts there \ No newline at end of file diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..95c9af33 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,19 @@ +# Clixon base docker image + +This directory contains code for building and pushing a Clixon docker +container. By default it is pushed to olofhagsand/clixon, but you can change +the IMAGE in Makefile.in and push it to another name. + +The clixon docker image is a base image that can be used to build +clixon applications. It has all the whole code for a clixon release +which it downloads from git - it does not use local code. + +See example/docker for how to build a docker application using the base image. + +Build and push +============== +Perform the build by 'make docker'. +You may also do 'make push' if you want to push the image, but you may then consider changing the image name (in the makefile:s). + +You may run the container directly by going directly to example and +the docker runtime scripts there \ No newline at end of file diff --git a/docker/backend/README b/docker/backend/README deleted file mode 100644 index d452c07a..00000000 --- a/docker/backend/README +++ /dev/null @@ -1,6 +0,0 @@ -Ensure that cligen and clixon has been built and installed. -sudo make docker -make push - -Then go to example and run the example as a docker container - diff --git a/docker/cli/Makefile.in b/docker/cli/Makefile.in deleted file mode 100644 index 349d4136..00000000 --- a/docker/cli/Makefile.in +++ /dev/null @@ -1,76 +0,0 @@ -# -# ***** BEGIN LICENSE BLOCK ***** -# -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren -# -# This file is part of CLIXON -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Alternatively, the contents of this file may be used under the terms of -# the GNU General Public License Version 3 or later (the "GPL"), -# in which case the provisions of the GPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of the GPL, and not to allow others to -# use your version of this file under the terms of Apache License version 2, -# indicate your decision by deleting the provisions above and replace them with -# the notice and other provisions required by the GPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the Apache License version 2 or the GPL. -# -# ***** END LICENSE BLOCK ***** -# -VPATH = @srcdir@ -prefix = @prefix@ -exec_prefix = @exec_prefix@ -srcdir = @srcdir@ -top_srcdir = @top_srcdir@ -prefix = @prefix@ -bindir = @bindir@ -sbindir = @sbindir@ -libdir = @libdir@ -includedir = @includedir@ -datarootdir = @datarootdir@ -# You may consider changing this -image = olofhagsand/clixon_cli - -all: - @echo "Run make docker to build docker image" -clean: - -distclean: clean - rm -f Makefile *~ .depend libcligen* libclixon* clixon_cli Dockerfile - -# Kind of reverse install, could have copied from src dir,... -.PHONY: docker push -docker: - cp $(DESTDIR)$(libdir)/libcligen.so.@CLIGEN_VERSION@ . - cp $(DESTDIR)$(libdir)/libclixon.so.@CLIXON_VERSION_MAJOR@ . - cp $(DESTDIR)$(libdir)/libclixon_cli.so.@CLIXON_VERSION_MAJOR@ . - cp $(DESTDIR)$(bindir)/clixon_cli . - sudo docker build -t $(image) . - -push: - sudo docker push $(image) - -install: - -uninstall: - -install-include: - -depend: -# $(CC) $(DEPENDFLAGS) $(INCLUDES) $(CFLAGS) -MM $(SRC) > .depend - -#include .depend - diff --git a/docker/cli/README b/docker/cli/README deleted file mode 100644 index d452c07a..00000000 --- a/docker/cli/README +++ /dev/null @@ -1,6 +0,0 @@ -Ensure that cligen and clixon has been built and installed. -sudo make docker -make push - -Then go to example and run the example as a docker container - diff --git a/docker/netconf/Dockerfile.in b/docker/netconf/Dockerfile.in deleted file mode 100644 index 9a8bcc45..00000000 --- a/docker/netconf/Dockerfile.in +++ /dev/null @@ -1,52 +0,0 @@ -# -# ***** BEGIN LICENSE BLOCK ***** -# -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren -# -# This file is part of CLIXON -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Alternatively, the contents of this file may be used under the terms of -# the GNU General Public License Version 3 or later (the "GPL"), -# in which case the provisions of the GPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of the GPL, and not to allow others to -# use your version of this file under the terms of Apache License version 2, -# indicate your decision by deleting the provisions above and replace them with -# the notice and other provisions required by the GPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the Apache License version 2 or the GPL. -# -# ***** END LICENSE BLOCK ***** -# - -FROM ubuntu:14.04 -# 12.04 -MAINTAINER Olof Hagsand -ENV DEBIAN_FRONTEND noninteractive -RUN apt-get update && apt-get install -y libqdbm-dev -RUN groupadd clicon -COPY libcligen.so.@CLIGEN_VERSION@ /usr/lib/ -COPY libclixon.so.@CLIXON_VERSION_MAJOR@ /usr/lib/ -COPY libclixon_netconf.so.@CLIXON_VERSION_MAJOR@ /usr/lib/ -COPY clixon_netconf /usr/bin/ -RUN ldconfig -CMD ["/usr/bin/clixon_netconf", "-f", "/data/clixon.conf"] - - - - - - - diff --git a/docker/netconf/Makefile.in b/docker/netconf/Makefile.in deleted file mode 100644 index d099d3d2..00000000 --- a/docker/netconf/Makefile.in +++ /dev/null @@ -1,76 +0,0 @@ -# -# ***** BEGIN LICENSE BLOCK ***** -# -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren -# -# This file is part of CLIXON -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Alternatively, the contents of this file may be used under the terms of -# the GNU General Public License Version 3 or later (the "GPL"), -# in which case the provisions of the GPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of the GPL, and not to allow others to -# use your version of this file under the terms of Apache License version 2, -# indicate your decision by deleting the provisions above and replace them with -# the notice and other provisions required by the GPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the Apache License version 2 or the GPL. -# -# ***** END LICENSE BLOCK ***** -# -VPATH = @srcdir@ -prefix = @prefix@ -exec_prefix = @exec_prefix@ -srcdir = @srcdir@ -top_srcdir = @top_srcdir@ -prefix = @prefix@ -bindir = @bindir@ -sbindir = @sbindir@ -libdir = @libdir@ -includedir = @includedir@ -datarootdir = @datarootdir@ -# You may consider changing this -image = olofhagsand/clixon_netconf - -all: - @echo "Run make docker to build docker image" -clean: - -distclean: clean - rm -f Makefile *~ .depend libcligen* libclixon* clixon_netconf Dockerfile - -# Kind of reverse install, could have copied from src dir,... -.PHONY: docker push -docker: - cp $(DESTDIR)$(libdir)/libcligen.so.@CLIGEN_VERSION@ . - cp $(DESTDIR)$(libdir)/libclixon.so.@CLIXON_VERSION_MAJOR@ . - cp $(DESTDIR)$(libdir)/libclixon_netconf.so.@CLIXON_VERSION_MAJOR@ . - cp $(DESTDIR)$(bindir)/clixon_netconf . - sudo docker build -t $(image) . - -push: - sudo docker push $(image) - -install: - -uninstall: - -install-include: - -depend: -# $(CC) $(DEPENDFLAGS) $(INCLUDES) $(CFLAGS) -MM $(SRC) > .depend - -#include .depend - diff --git a/docker/netconf/README b/docker/netconf/README deleted file mode 100644 index d452c07a..00000000 --- a/docker/netconf/README +++ /dev/null @@ -1,6 +0,0 @@ -Ensure that cligen and clixon has been built and installed. -sudo make docker -make push - -Then go to example and run the example as a docker container - diff --git a/docker/backend/Dockerfile.in b/example/Dockerfile similarity index 70% rename from docker/backend/Dockerfile.in rename to example/Dockerfile index 69929f80..24403dc5 100644 --- a/docker/backend/Dockerfile.in +++ b/example/Dockerfile @@ -31,21 +31,29 @@ # ***** END LICENSE BLOCK ***** # -FROM ubuntu:14.04 -# 12.04 +FROM olofhagsand/clixon MAINTAINER Olof Hagsand -ENV DEBIAN_FRONTEND noninteractive -RUN apt-get update && apt-get install -y libqdbm-dev -RUN groupadd clicon -COPY libcligen.so.@CLIGEN_VERSION@ /usr/lib/ -COPY libclixon.so.@CLIXON_VERSION_MAJOR@ /usr/lib/ -COPY libclixon_backend.so.@CLIXON_VERSION_MAJOR@ /usr/lib/ -COPY clixon_backend /usr/sbin/ -RUN ldconfig -RUN sudo groupadd clixon -CMD ["/usr/sbin/clixon_backend", "-F", "-f", "/data/clixon.conf"] - - + +RUN apt-get update && apt-get install -y \ + procps # ps for debugging +# Create a directory to hold source-code, dependencies etc +RUN mkdir /example +WORKDIR /example + +# Clone clixon (again) since example application is there. +# Replace this with your application +RUN git clone https://github.com/clicon/clixon.git + +# Build clixon +WORKDIR /example/clixon +RUN git checkout -b develop origin/develop +RUN ./configure +WORKDIR /example/clixon/example +RUN make +RUN make install +RUN install example.xml /usr/local/etc/clixon.xml + +CMD /usr/local/sbin/clixon_backend && /usr/local/bin/clixon_cli diff --git a/example/Makefile.in b/example/Makefile.in index 0be6a8aa..c95ff1de 100644 --- a/example/Makefile.in +++ b/example/Makefile.in @@ -37,8 +37,13 @@ prefix = @prefix@ bindir = @bindir@ includedir = @includedir@ datarootdir = @datarootdir@ +sysconfdir = @sysconfdir@ +datarootdir = @datarootdir@ +localstatedir = @localstatedir@ +libdir = @exec_prefix@/lib APPNAME = example + CC = @CC@ CFLAGS = @CFLAGS@ -rdynamic -fPIC INSTALLFLAGS = @INSTALLFLAGS@ @@ -51,12 +56,14 @@ CLI_PLUGIN = $(APPNAME)_cli.so NETCONF_PLUGIN = $(APPNAME)_netconf.so RESTCONF_PLUGIN = $(APPNAME)_restconf.so +# Example docker image. PLEASE CHANGE THIS +IMAGE = olofhagsand/clixon_example + PLUGINS = $(BE_PLUGIN) $(BE2_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(RESTCONF_PLUGIN) -all: $(PLUGINS) +.PHONY: all clean depend install docker push -# Note: clixon.mk has rules for clixon_DBSPECDIR, clixon_SYSCONFDIR, etc used below --include $(DESTDIR)$(datarootdir)/clixon/clixon.mk +all: $(PLUGINS) CLISPECS = $(APPNAME)_cli.cli @@ -69,6 +76,7 @@ YANGSPECS += ietf-routing@2014-10-26.yang YANGSPECS += ietf-ipv4-unicast-routing@2014-10-26.yang YANGSPECS += ietf-ipv6-unicast-routing@2014-10-26.yang YANGSPECS += ietf-ipsec@2016-03-09.yang +YANGSPECS += iana-if-type@2014-05-08.yang # Backend plugin BE_SRC = $(APPNAME)_backend.c @@ -105,42 +113,45 @@ OBJS = $(BE_OBJ) $(BE2_OBJ) $(CLI_OBJ) $(NETCONF_OBJ) $(RESTCONF_OBJ) clean: rm -f $(PLUGINS) $(OBJS) - (cd docker && $(MAKE) $(MFLAGS) $@) distclean: clean rm -f Makefile *~ .depend - (cd docker && $(MAKE) $(MFLAGS) $@) install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(BE2_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(RESTCONF_PLUGIN) $(APPNAME).xml - install -d -m 0755 $(DESTDIR)$(clixon_SYSCONFDIR) - install -m 0644 $(APPNAME).xml $(DESTDIR)$(clixon_SYSCONFDIR) - install -d -m 0755 $(DESTDIR)$(clixon_DBSPECDIR)/yang - install -m 0644 $(YANGSPECS) $(DESTDIR)$(clixon_DBSPECDIR)/yang - install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/cli - install -m 0644 $(INSTALLFLAGS) $(CLI_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/cli - install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/backend - install -m 0644 $(INSTALLFLAGS) $(BE_PLUGIN) $(BE2_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/backend - install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/netconf - install -m 0644 $(INSTALLFLAGS) $(NETCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/netconf - install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/restconf - install -m 0644 $(INSTALLFLAGS) $(RESTCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/restconf - install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/clispec - install -m 0644 $(CLISPECS) $(DESTDIR)$(clixon_LIBDIR)/clispec - install -d -m 0755 $(DESTDIR)$(clixon_LOCALSTATEDIR) - (cd docker && $(MAKE) $(MFLAGS) $@) + install -d -m 0755 $(DESTDIR)$(sysconfdir) + install -m 0644 $(APPNAME).xml $(DESTDIR)$(sysconfdir) + install -d -m 0755 $(DESTDIR)$(datarootdir)/$(APPNAME)/yang + install -m 0644 $(YANGSPECS) $(DESTDIR)$(datarootdir)/$(APPNAME)/yang + install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/cli + install -m 0644 $(INSTALLFLAGS) $(CLI_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/cli + install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/backend + install -m 0644 $(INSTALLFLAGS) $(BE_PLUGIN) $(BE2_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/backend + install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/netconf + install -m 0644 $(INSTALLFLAGS) $(NETCONF_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/netconf + install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/restconf + install -m 0644 $(INSTALLFLAGS) $(RESTCONF_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/restconf + install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/clispec + install -m 0644 $(CLISPECS) $(DESTDIR)$(libdir)/$(APPNAME)/clispec + install -d -m 0755 $(DESTDIR)$(localstatedir)/$(APPNAME) + +docker: + sudo docker build -t $(IMAGE) . + +push: + sudo docker push $(IMAGE) uninstall: - rm -rf $(DESTDIR)$(clixon_SYSCONFDIR)/$(APPNAME).xml - rm -rf $(DESTDIR)$(clixon_DBSPECDIR) - rm -rf $(DESTDIR)$(clixon_LOCALSTATEDIR) - rm -rf $(DESTDIR)$(clixon_LIBDIR) - (cd docker && $(MAKE) $(MFLAGS) $@) + echo "libdir:$(libdir)" + echo "libdir2:$(libdir2)" + rm -rf $(DESTDIR)$(sysconfdir)/$(APPNAME).xml + rm -rf $(DESTDIR)$(datarootdir)/$(APPNAME) + rm -rf $(DESTDIR)$(localstatedir)/$(APPNAME) + rm -rf $(DESTDIR)$(libdir)/$(APPNAME) install-include: depend: $(CC) $(DEPENDFLAGS) $(INCLUDES) $(CFLAGS) -MM $(SRC) > .depend - (cd docker && $(MAKE) $(MFLAGS) $@) #include .depend diff --git a/example/README.md b/example/README.md index cafd796f..0c6d6356 100644 --- a/example/README.md +++ b/example/README.md @@ -16,14 +16,20 @@ routing example. It contains the following files: * example_netconf.c Netconf callback plugin * Makefile.in Example makefile where plugins are built and installed + ## Compile and run + +Before you start, +* Make [group setup](../doc/FAQ.md#do-i-need-to-setup-anything-important) +* Setup [restconf](../doc/FAQ.md#how-do-i-use-restconf) + ``` cd example make && sudo make install ``` Start backend: ``` - clixon_backend -f /usr/local/etc/example.xml -I + sudo clixon_backend -f /usr/local/etc/example.xml -s init ``` Edit cli: ``` @@ -188,6 +194,10 @@ The example contains some stubs for authorization according to [RFC8341(NACM)](h * A NACM backend plugin reporting the mandatory NACM state variables. +## Systemd files + +Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example. + ## Run as docker container (Note not updated) @@ -196,5 +206,19 @@ cd docker # look in README ``` +## Docker + +Run the example as a docker container as follows: +``` +sudo docker run -ti --rm olofhagsand/clixon_example +``` + +Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push: +``` +make docker +make push +sudo docker run -ti --rm you/clixon_example +``` + diff --git a/example/docker/Makefile.in b/example/docker/Makefile.in deleted file mode 100644 index 492c727f..00000000 --- a/example/docker/Makefile.in +++ /dev/null @@ -1,80 +0,0 @@ -# -# ***** BEGIN LICENSE BLOCK ***** -# -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren -# -# This file is part of CLIXON -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Alternatively, the contents of this file may be used under the terms of -# the GNU General Public License Version 3 or later (the "GPL"), -# in which case the provisions of the GPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of the GPL, and not to allow others to -# use your version of this file under the terms of Apache License version 2, -# indicate your decision by deleting the provisions above and replace them with -# the notice and other provisions required by the GPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the Apache License version 2 or the GPL. -# -# ***** END LICENSE BLOCK ***** -# -VPATH = @srcdir@ -srcdir = @srcdir@ -top_srcdir = @top_srcdir@ -prefix = @prefix@ -bindir = @bindir@ -includedir = @includedir@ -datarootdir = @datarootdir@ - -APPNAME = routing - -all: $(APPNAME).conf - --include $(DESTDIR)$(datarootdir)/clixon/clixon.mk - -# Kind of reverse install, could have copied from src dir,... -.PHONY: docker push -docker: $(APPNAME).conf - install -d data - install -d data/yang - install -d data/backend - install -d data/cli - install -d data/netconf - install -d data/clispec - install $(APPNAME).conf data/clixon.conf # docker image assumes /data/clixon.conf - install ../*.yang data/yang/ - install ../routing_cli.so data/cli/ - install ../routing_backend.so data/backend/ - install ../routing_netconf.so data/netconf/ - install ../*.cli data/clispec - -clean: - rm -f $(APPNAME).conf - -distclean: clean - rm -f Makefile *~ .depend - rm -rf data - -install: - -uninstall: - -install-include: - -depend: - $(CC) $(DEPENDFLAGS) $(INCLUDES) $(CFLAGS) -MM $(SRC) > .depend - -#include .depend - diff --git a/example/docker/README b/example/docker/README deleted file mode 100644 index 2d93a6a9..00000000 --- a/example/docker/README +++ /dev/null @@ -1,7 +0,0 @@ -Run the ietf routing example as docker container. -Use the dockerhub container, or alternatively, build clicon as docker images -by doing make docker in the top builddir. - - (cd ..; make) # Make example - make docker # Create config file and shared file system - run.sh # Run a backend and a cli container diff --git a/example/docker/routing.conf.local b/example/docker/routing.conf.local deleted file mode 100644 index a5f96dd2..00000000 --- a/example/docker/routing.conf.local +++ /dev/null @@ -1,66 +0,0 @@ -# Main YANG module first parsed by parser (in CLICON_YANG_DIR). eg clicon.yang. - -# Location of configuration-file for default values (this file) -CLICON_CONFIGFILE /data/clixon.conf -# Location of YANG module and submodule files. Only if CLICON_DBSPEC_TYPE is YANG -CLICON_YANG_DIR /data/yang -# Option used to construct initial yang file: -# [@] -# This option is only relevant if CLICON_DBSPEC_TYPE is YANG -# CLICON_YANG_MODULE_MAIN clicon -# Option used to construct initial yang file: -# [@] -# This option is only relevant if CLICON_DBSPEC_TYPE is YANG -CLICON_YANG_MODULE_REVISION -# Candidate qdbm database -CLICON_CANDIDATE_DB /data/candidate_db -# Running qdbm database -CLICON_RUNNING_DB /data/running_db -# Location of backend .so plugins -CLICON_BACKEND_DIR /data/backend -# Location of netconf (frontend) .so plugins -CLICON_NETCONF_DIR /data/netconf -# Location of cli frontend .so plugins -CLICON_CLI_DIR /data/cli -# Location of frontend .cli cligen spec files -CLICON_CLISPEC_DIR /data/clispec -# Directory where to save configuration commit history (in XML). Snapshots -# are saved chronologically -CLICON_ARCHIVE_DIR /data -# XXX Name of startup configuration file (in XML) -CLICON_STARTUP_CONFIG /data/startup-config -# Address family for communicating with clixon_backend (UNIX|IPv4|IPv6) -CLICON_SOCK_FAMILY UNIX -# If family above is AF_UNIX: Unix socket for communicating with clixon_backend -# If family above is AF_INET: IPv4 address -CLICON_SOCK /data/routing.sock -# Inet socket port for communicating with clixon_backend (only IPv4|IPv6) -CLICON_SOCK_PORT 4535 -# Process-id file -CLICON_BACKEND_PIDFILE /data/routing.pidfile - -# Save values as XML in database instead of lvec:s. -# This is optimized for yang specified applications -# But not compatible with key-based application (eg Rost) -CLICON_DB_XML 1 - -# Startup CLI mode. This should match the CLICON_MODE in your startup clispec file -CLICON_CLI_MODE routing - -# Option used to construct initial yang file: -# [@] -# This option is only relevant if CLICON_DBSPEC_TYPE is YANG -CLICON_YANG_MODULE_MAIN ietf-ip - -# Option used to construct initial yang file: -# [@] -# This option is only relevant if CLICON_DBSPEC_TYPE is YANG -CLICON_YANG_MODULE_REVISION 2014-06-16 - -# Generate code for CLI completion of existing db symbols -# CLICON_CLI_GENMODEL_COMPLETION 0 -CLICON_CLI_GENMODEL_COMPLETION 1 - -# How to generate and show CLI syntax: VARS|ALL -# CLICON_CLI_GENMODEL_TYPE VARS -CLICON_CLI_GENMODEL_TYPE VARS diff --git a/example/docker/run.sh b/example/docker/run.sh deleted file mode 100755 index 295d5d4e..00000000 --- a/example/docker/run.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -# Start daemon and a cli docker containers . -# Note that they have a common file-system at /data -# -sudo docker run -td --net host -v $(pwd)/data:/data olofhagsand/clixon_backend -sudo docker run -ti --rm --net host -v $(pwd)/data:/data olofhagsand/clixon_cli - - diff --git a/example/example.yang b/example/example.yang index 14546f06..e90bfdef 100644 --- a/example/example.yang +++ b/example/example.yang @@ -1,13 +1,33 @@ module example { prefix ex; + import ietf-interfaces { + prefix if; + } import ietf-ip { prefix ip; } import ietf-routing { prefix rt; } + import iana-if-type { + prefix ianaift; + } description "Example code that includes ietf-ip and ietf-routing"; + /* Example interface type for tests, local callbacks, etc */ + identity eth { + base if:interface-type; + } + identity loopback { + base if:interface-type; + } + /* Translation function example - See also example_cli */ + + list translate{ + leaf value{ + type string; + } + } rpc client-rpc { description "Example local client-side RPC that is processed by the the netconf/restconf and not sent to the backend. diff --git a/example/example_backend.c b/example/example_backend.c index 495ad14f..4db69f71 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -180,7 +180,7 @@ empty(clicon_handle h, /* Clicon handle */ * Real code would poll state */ int -plugin_statedata(clicon_handle h, +example_statedata(clicon_handle h, char *xpath, cxobj *xstate) { @@ -190,7 +190,7 @@ plugin_statedata(clicon_handle h, /* Example of (static) statedata, real code would poll state */ if (xml_parse_string("" "eth0" - "eth" + "ex:eth" "42" "", NULL, &xstate) < 0) goto done; @@ -214,14 +214,14 @@ plugin_statedata(clicon_handle h, * @note This assumes example yang with interfaces/interface */ int -plugin_reset(clicon_handle h, - const char *db) +example_reset(clicon_handle h, + const char *db) { int retval = -1; cxobj *xt = NULL; if (xml_parse_string("" - "lolocal" + "loex:loopback" "", NULL, &xt) < 0) goto done; /* Replace parent w fiorst child */ @@ -250,7 +250,7 @@ plugin_reset(clicon_handle h, * can be processed with the standard getopt(3). */ int -plugin_start(clicon_handle h, +example_start(clicon_handle h, int argc, char **argv) { @@ -261,11 +261,11 @@ clixon_plugin_api *clixon_plugin_init(clicon_handle h); static clixon_plugin_api api = { "example", /* name */ - clixon_plugin_init, /* init */ - plugin_start, /* start */ + clixon_plugin_init, /* init - must be called clixon_plugin_init */ + example_start, /* start */ NULL, /* exit */ - .ca_reset=plugin_reset, /* reset */ - .ca_statedata=plugin_statedata, /* statedata */ + .ca_reset=example_reset, /* reset */ + .ca_statedata=example_statedata, /* statedata */ .ca_trans_begin=NULL, /* trans begin */ .ca_trans_validate=transaction_validate,/* trans validate */ .ca_trans_complete=NULL, /* trans complete */ diff --git a/example/example_backend_nacm.c b/example/example_backend_nacm.c index d6ed4600..f484362e 100644 --- a/example/example_backend_nacm.c +++ b/example/example_backend_nacm.c @@ -114,6 +114,13 @@ static clixon_plugin_api api = { clixon_plugin_api * clixon_plugin_init(clicon_handle h) { + char *nacm_mode; + clicon_debug(1, "%s backend nacm", __FUNCTION__); + nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE"); + if (nacm_mode==NULL || strcmp(nacm_mode, "disabled") == 0){ + clicon_debug(1, "%s CLICON_NACM_MODE not enabled: example nacm module disabled", __FUNCTION__); + return NULL; + } return &api; } diff --git a/example/example_cli.c b/example/example_cli.c index b8a91fa3..f5c11b29 100644 --- a/example/example_cli.c +++ b/example/example_cli.c @@ -67,7 +67,12 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv) cli_output(stderr, "arg = %s\n", cv_string_get(cvec_i(argv,0))); /* get string value */ /* Show eth0 interfaces config using XPATH */ - if (clicon_rpc_get_config(h, "running","/interfaces/interface[name=eth0]", + if (clicon_rpc_get_config(h, "running", +#ifdef COMPAT_XSL + "/interfaces/interface[name=eth0]", +#else + "/interfaces/interface[name='eth0']", +#endif &xret) < 0) goto done; @@ -90,17 +95,24 @@ fib_route_rpc(clicon_handle h, cxobj *xtop = NULL; cxobj *xrpc; cxobj *xret = NULL; + cxobj *xerr; /* User supplied variable in CLI command */ instance = cvec_find(cvv, "instance"); /* get a cligen variable from vector */ /* Create XML for fib-route netconf RPC */ - if (xml_parse_va(&xtop, NULL, "%s", cv_string_get(instance)) < 0) + if (xml_parse_va(&xtop, NULL, "%s", + clicon_username_get(h), + cv_string_get(instance)) < 0) goto done; /* Skip top-level */ xrpc = xml_child_i(xtop, 0); /* Send to backend */ if (clicon_rpc_netconf_xml(h, xrpc, &xret, NULL) < 0) goto done; + if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + clicon_rpc_generate_error("Get configuration", xerr); + goto done; + } /* Print result */ xml_print(stdout, xml_child_i(xret, 0)); retval = 0; @@ -137,3 +149,21 @@ clixon_plugin_init(clicon_handle h) return &api; } + +/*! Translate function from an original value to a new. + * In this case, assume string and increment characters, eg HAL->IBM + */ +int +incstr(cligen_handle h, + cg_var *cv) +{ + char *str; + int i; + + if (cv_type_get(cv) != CGV_STRING) + return 0; + str = cv_string_get(cv); + for (i=0; i),cli_set("/translate/value"); + # Note, when switching to PT, change datamodel to only @datamodel set @datamodel:example, cli_set(); merge @datamodel:example, cli_merge(); @@ -21,7 +24,7 @@ debug("Debugging parts of the system"), cli_debug_cli((int32)1);{ } copy("Copy and create a new object") { interface("Copy interface"){ - ("name of interface to copy from") to("Copy to interface") ("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s=%s]","name","name","toname"); + ("name of interface to copy from") to("Copy to interface") ("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname"); } } discard("Discard edits (rollback 0)"), discard_changes(); @@ -36,11 +39,21 @@ show("Show a particular state of the system"){ text("Show comparison in text"), compare_dbs((int32)1); } configuration("Show configuration"), cli_show_config("candidate", "text", "/");{ - xml("Show configuration as XML"), cli_show_config("candidate", "xml", "/"); - netconf("Show configuration as netconf edit-config operation"), cli_show_config("candidate", "netconf", "/"); - text("Show configuration as text"), cli_show_config("candidate","text","/"); - cli("Show configuration as cli commands"), cli_show_config("candidate", "cli", "/"); - json("Show configuration as cli commands"), cli_show_config("candidate", "json", "/"); + xml("Show configuration as XML"), cli_show_config("candidate", "xml", "/");{ + @datamodel:example, cli_show_auto("candidate", "text"); + } + cli("Show configuration as CLI commands"), cli_show_config("candidate", "cli", "/");{ + @datamodel:example, 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"); + } + text("Show configuration as text"), cli_show_config("candidate","text","/");{ + @datamodel:example, cli_show_auto("candidate", "text"); + } + json("Show configuration as JSON"), cli_show_config("candidate", "json", "/");{ + @datamodel:example, cli_show_auto("candidate", "json"); + } } } @@ -50,7 +63,7 @@ load("Load configuration from XML file") ("Filename (local file merge("Merge file with existent candidate"), load_config_file("filename", "merge"); } example("This is a comment") ("Just a random number"), mycallback("myarg"); -rpc("fib-route rpc") ("routing instance"), fib_route_rpc("myarg"); +rpc("ex:fib-route rpc") ("routing instance"), fib_route_rpc("myarg"); notify("Get notifications from backend"), cli_notify("ROUTING", "1", "text"); no("Negate") notify("Get notifications from backend"), cli_notify("ROUTING", "0", "xml"); lock,cli_lock("candidate"); diff --git a/example/iana-if-type@2014-05-08.yang b/example/iana-if-type@2014-05-08.yang new file mode 100644 index 00000000..da5b65b7 --- /dev/null +++ b/example/iana-if-type@2014-05-08.yang @@ -0,0 +1,1506 @@ + module iana-if-type { + namespace "urn:ietf:params:xml:ns:yang:iana-if-type"; + prefix ianaift; + + import ietf-interfaces { + prefix if; + } + + organization "IANA"; + contact + " Internet Assigned Numbers Authority + + Postal: ICANN + 4676 Admiralty Way, Suite 330 + Marina del Rey, CA 90292 + + Tel: +1 310 823 9358 + "; + description + "This YANG module defines YANG identities for IANA-registered + interface types. + + This YANG module is maintained by IANA and reflects the + 'ifType definitions' registry. + + The latest revision of this YANG module can be obtained from + the IANA web site. + + Requests for new values should be made to IANA via + email (iana@iana.org). + + Copyright (c) 2014 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + The initial version of this YANG module is part of RFC 7224; + see the RFC itself for full legal notices."; + reference + "IANA 'ifType definitions' registry. + "; + + revision 2014-05-08 { + description + "Initial revision."; + reference + "RFC 7224: IANA Interface Type YANG Module"; + } + + identity iana-interface-type { + base if:interface-type; + description + "This identity is used as a base for all interface types + defined in the 'ifType definitions' registry."; + } + identity other { + base iana-interface-type; + } + identity regular1822 { + base iana-interface-type; + } + identity hdh1822 { + base iana-interface-type; + } + identity ddnX25 { + base iana-interface-type; + } + identity rfc877x25 { + base iana-interface-type; + reference + "RFC 1382 - SNMP MIB Extension for the X.25 Packet Layer"; + } + identity ethernetCsmacd { + base iana-interface-type; + description + "For all Ethernet-like interfaces, regardless of speed, + as per RFC 3635."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity iso88023Csmacd { + base iana-interface-type; + status deprecated; + description + "Deprecated via RFC 3635. + Use ethernetCsmacd(6) instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity iso88024TokenBus { + base iana-interface-type; + } + identity iso88025TokenRing { + base iana-interface-type; + } + identity iso88026Man { + base iana-interface-type; + } + identity starLan { + base iana-interface-type; + status deprecated; + description + "Deprecated via RFC 3635. + Use ethernetCsmacd(6) instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity proteon10Mbit { + base iana-interface-type; + } + identity proteon80Mbit { + base iana-interface-type; + } + identity hyperchannel { + base iana-interface-type; + } + identity fddi { + base iana-interface-type; + reference + "RFC 1512 - FDDI Management Information Base"; + } + identity lapb { + base iana-interface-type; + reference + "RFC 1381 - SNMP MIB Extension for X.25 LAPB"; + } + identity sdlc { + base iana-interface-type; + } + identity ds1 { + base iana-interface-type; + description + "DS1-MIB."; + reference + "RFC 4805 - Definitions of Managed Objects for the + DS1, J1, E1, DS2, and E2 Interface Types"; + } + identity e1 { + base iana-interface-type; + status obsolete; + description + "Obsolete; see DS1-MIB."; + reference + "RFC 4805 - Definitions of Managed Objects for the + DS1, J1, E1, DS2, and E2 Interface Types"; + } + identity basicISDN { + base iana-interface-type; + description + "No longer used. See also RFC 2127."; + } + identity primaryISDN { + base iana-interface-type; + description + "No longer used. See also RFC 2127."; + } + identity propPointToPointSerial { + base iana-interface-type; + description + "Proprietary serial."; + } + identity ppp { + base iana-interface-type; + } + identity softwareLoopback { + base iana-interface-type; + } + identity eon { + base iana-interface-type; + description + "CLNP over IP."; + } + identity ethernet3Mbit { + base iana-interface-type; + } + identity nsip { + base iana-interface-type; + description + "XNS over IP."; + } + identity slip { + base iana-interface-type; + description + "Generic SLIP."; + } + identity ultra { + base iana-interface-type; + description + "Ultra Technologies."; + } + identity ds3 { + base iana-interface-type; + description + "DS3-MIB."; + reference + "RFC 3896 - Definitions of Managed Objects for the + DS3/E3 Interface Type"; + } + identity sip { + base iana-interface-type; + description + "SMDS, coffee."; + reference + "RFC 1694 - Definitions of Managed Objects for SMDS + Interfaces using SMIv2"; + } + identity frameRelay { + base iana-interface-type; + description + "DTE only."; + reference + "RFC 2115 - Management Information Base for Frame Relay + DTEs Using SMIv2"; + } + identity rs232 { + base iana-interface-type; + reference + "RFC 1659 - Definitions of Managed Objects for RS-232-like + Hardware Devices using SMIv2"; + } + identity para { + base iana-interface-type; + description + "Parallel-port."; + reference + "RFC 1660 - Definitions of Managed Objects for + Parallel-printer-like Hardware Devices using + SMIv2"; + } + identity arcnet { + base iana-interface-type; + description + "ARCnet."; + } + identity arcnetPlus { + base iana-interface-type; + description + "ARCnet Plus."; + } + identity atm { + base iana-interface-type; + description + "ATM cells."; + } + identity miox25 { + base iana-interface-type; + reference + "RFC 1461 - SNMP MIB extension for Multiprotocol + Interconnect over X.25"; + } + identity sonet { + base iana-interface-type; + description + "SONET or SDH."; + } + identity x25ple { + base iana-interface-type; + reference + "RFC 2127 - ISDN Management Information Base using SMIv2"; + } + identity iso88022llc { + base iana-interface-type; + } + identity localTalk { + base iana-interface-type; + } + identity smdsDxi { + base iana-interface-type; + } + identity frameRelayService { + base iana-interface-type; + description + "FRNETSERV-MIB."; + reference + "RFC 2954 - Definitions of Managed Objects for Frame + Relay Service"; + } + identity v35 { + base iana-interface-type; + } + identity hssi { + base iana-interface-type; + } + identity hippi { + base iana-interface-type; + } + identity modem { + base iana-interface-type; + description + "Generic modem."; + } + identity aal5 { + base iana-interface-type; + description + "AAL5 over ATM."; + } + identity sonetPath { + base iana-interface-type; + } + identity sonetVT { + base iana-interface-type; + } + identity smdsIcip { + base iana-interface-type; + description + "SMDS InterCarrier Interface."; + } + identity propVirtual { + base iana-interface-type; + description + "Proprietary virtual/internal."; + reference + "RFC 2863 - The Interfaces Group MIB"; + } + identity propMultiplexor { + base iana-interface-type; + description + "Proprietary multiplexing."; + reference + "RFC 2863 - The Interfaces Group MIB"; + } + identity ieee80212 { + base iana-interface-type; + description + "100BaseVG."; + } + identity fibreChannel { + base iana-interface-type; + description + "Fibre Channel."; + } + identity hippiInterface { + base iana-interface-type; + description + "HIPPI interfaces."; + } + identity frameRelayInterconnect { + base iana-interface-type; + status obsolete; + description + "Obsolete; use either + frameRelay(32) or frameRelayService(44)."; + } + identity aflane8023 { + base iana-interface-type; + description + "ATM Emulated LAN for 802.3."; + } + identity aflane8025 { + base iana-interface-type; + description + "ATM Emulated LAN for 802.5."; + } + identity cctEmul { + base iana-interface-type; + description + "ATM Emulated circuit."; + } + identity fastEther { + base iana-interface-type; + status deprecated; + description + "Obsoleted via RFC 3635. + ethernetCsmacd(6) should be used instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity isdn { + base iana-interface-type; + description + "ISDN and X.25."; + reference + "RFC 1356 - Multiprotocol Interconnect on X.25 and ISDN + in the Packet Mode"; + } + identity v11 { + base iana-interface-type; + description + "CCITT V.11/X.21."; + } + identity v36 { + base iana-interface-type; + description + "CCITT V.36."; + } + identity g703at64k { + base iana-interface-type; + description + "CCITT G703 at 64Kbps."; + } + identity g703at2mb { + base iana-interface-type; + status obsolete; + description + "Obsolete; see DS1-MIB."; + } + identity qllc { + base iana-interface-type; + description + "SNA QLLC."; + } + identity fastEtherFX { + base iana-interface-type; + status deprecated; + description + "Obsoleted via RFC 3635. + ethernetCsmacd(6) should be used instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity channel { + base iana-interface-type; + description + "Channel."; + } + identity ieee80211 { + base iana-interface-type; + description + "Radio spread spectrum."; + } + identity ibm370parChan { + base iana-interface-type; + description + "IBM System 360/370 OEMI Channel."; + } + identity escon { + base iana-interface-type; + description + "IBM Enterprise Systems Connection."; + } + identity dlsw { + base iana-interface-type; + description + "Data Link Switching."; + } + identity isdns { + base iana-interface-type; + description + "ISDN S/T interface."; + } + identity isdnu { + base iana-interface-type; + description + "ISDN U interface."; + } + identity lapd { + base iana-interface-type; + description + "Link Access Protocol D."; + } + identity ipSwitch { + base iana-interface-type; + description + "IP Switching Objects."; + } + identity rsrb { + base iana-interface-type; + description + "Remote Source Route Bridging."; + } + identity atmLogical { + base iana-interface-type; + description + "ATM Logical Port."; + reference + "RFC 3606 - Definitions of Supplemental Managed Objects + for ATM Interface"; + } + identity ds0 { + base iana-interface-type; + description + "Digital Signal Level 0."; + reference + "RFC 2494 - Definitions of Managed Objects for the DS0 + and DS0 Bundle Interface Type"; + } + identity ds0Bundle { + base iana-interface-type; + description + "Group of ds0s on the same ds1."; + reference + "RFC 2494 - Definitions of Managed Objects for the DS0 + and DS0 Bundle Interface Type"; + } + identity bsc { + base iana-interface-type; + description + "Bisynchronous Protocol."; + } + identity async { + base iana-interface-type; + description + "Asynchronous Protocol."; + } + identity cnr { + base iana-interface-type; + description + "Combat Net Radio."; + } + identity iso88025Dtr { + base iana-interface-type; + description + "ISO 802.5r DTR."; + } + identity eplrs { + base iana-interface-type; + description + "Ext Pos Loc Report Sys."; + } + identity arap { + base iana-interface-type; + description + "Appletalk Remote Access Protocol."; + } + identity propCnls { + base iana-interface-type; + description + "Proprietary Connectionless Protocol."; + } + identity hostPad { + base iana-interface-type; + description + "CCITT-ITU X.29 PAD Protocol."; + } + identity termPad { + base iana-interface-type; + description + "CCITT-ITU X.3 PAD Facility."; + } + identity frameRelayMPI { + base iana-interface-type; + description + "Multiproto Interconnect over FR."; + } + identity x213 { + base iana-interface-type; + description + "CCITT-ITU X213."; + } + identity adsl { + base iana-interface-type; + description + "Asymmetric Digital Subscriber Loop."; + } + identity radsl { + base iana-interface-type; + description + "Rate-Adapt. Digital Subscriber Loop."; + } + identity sdsl { + base iana-interface-type; + description + "Symmetric Digital Subscriber Loop."; + } + identity vdsl { + base iana-interface-type; + description + "Very H-Speed Digital Subscrib. Loop."; + } + identity iso88025CRFPInt { + base iana-interface-type; + description + "ISO 802.5 CRFP."; + } + identity myrinet { + base iana-interface-type; + description + "Myricom Myrinet."; + } + identity voiceEM { + base iana-interface-type; + description + "Voice recEive and transMit."; + } + identity voiceFXO { + base iana-interface-type; + description + "Voice Foreign Exchange Office."; + } + identity voiceFXS { + base iana-interface-type; + description + "Voice Foreign Exchange Station."; + } + identity voiceEncap { + base iana-interface-type; + description + "Voice encapsulation."; + } + identity voiceOverIp { + base iana-interface-type; + description + "Voice over IP encapsulation."; + } + identity atmDxi { + base iana-interface-type; + description + "ATM DXI."; + } + identity atmFuni { + base iana-interface-type; + description + "ATM FUNI."; + } + identity atmIma { + base iana-interface-type; + description + "ATM IMA."; + } + identity pppMultilinkBundle { + base iana-interface-type; + description + "PPP Multilink Bundle."; + } + identity ipOverCdlc { + base iana-interface-type; + description + "IBM ipOverCdlc."; + } + identity ipOverClaw { + base iana-interface-type; + description + "IBM Common Link Access to Workstn."; + } + identity stackToStack { + base iana-interface-type; + description + "IBM stackToStack."; + } + identity virtualIpAddress { + base iana-interface-type; + description + "IBM VIPA."; + } + identity mpc { + base iana-interface-type; + description + "IBM multi-protocol channel support."; + } + identity ipOverAtm { + base iana-interface-type; + description + "IBM ipOverAtm."; + reference + "RFC 2320 - Definitions of Managed Objects for Classical IP + and ARP Over ATM Using SMIv2 (IPOA-MIB)"; + } + identity iso88025Fiber { + base iana-interface-type; + description + "ISO 802.5j Fiber Token Ring."; + } + identity tdlc { + base iana-interface-type; + description + "IBM twinaxial data link control."; + } + identity gigabitEthernet { + base iana-interface-type; + status deprecated; + description + "Obsoleted via RFC 3635. + ethernetCsmacd(6) should be used instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity hdlc { + base iana-interface-type; + description + "HDLC."; + } + identity lapf { + base iana-interface-type; + description + "LAP F."; + } + identity v37 { + base iana-interface-type; + description + "V.37."; + } + identity x25mlp { + base iana-interface-type; + description + "Multi-Link Protocol."; + } + identity x25huntGroup { + base iana-interface-type; + description + "X25 Hunt Group."; + } + identity transpHdlc { + base iana-interface-type; + description + "Transp HDLC."; + } + identity interleave { + base iana-interface-type; + description + "Interleave channel."; + } + identity fast { + base iana-interface-type; + description + "Fast channel."; + } + identity ip { + base iana-interface-type; + description + "IP (for APPN HPR in IP networks)."; + } + identity docsCableMaclayer { + base iana-interface-type; + description + "CATV Mac Layer."; + } + identity docsCableDownstream { + base iana-interface-type; + description + "CATV Downstream interface."; + } + identity docsCableUpstream { + base iana-interface-type; + description + "CATV Upstream interface."; + } + identity a12MppSwitch { + base iana-interface-type; + description + "Avalon Parallel Processor."; + } + identity tunnel { + base iana-interface-type; + description + "Encapsulation interface."; + } + identity coffee { + base iana-interface-type; + description + "Coffee pot."; + reference + "RFC 2325 - Coffee MIB"; + } + identity ces { + base iana-interface-type; + description + "Circuit Emulation Service."; + } + identity atmSubInterface { + base iana-interface-type; + description + "ATM Sub Interface."; + } + identity l2vlan { + base iana-interface-type; + description + "Layer 2 Virtual LAN using 802.1Q."; + } + identity l3ipvlan { + base iana-interface-type; + description + "Layer 3 Virtual LAN using IP."; + } + identity l3ipxvlan { + base iana-interface-type; + description + "Layer 3 Virtual LAN using IPX."; + } + identity digitalPowerline { + base iana-interface-type; + description + "IP over Power Lines."; + } + identity mediaMailOverIp { + base iana-interface-type; + description + "Multimedia Mail over IP."; + } + identity dtm { + base iana-interface-type; + description + "Dynamic synchronous Transfer Mode."; + } + identity dcn { + base iana-interface-type; + description + "Data Communications Network."; + } + identity ipForward { + base iana-interface-type; + description + "IP Forwarding Interface."; + } + identity msdsl { + base iana-interface-type; + description + "Multi-rate Symmetric DSL."; + } + identity ieee1394 { + base iana-interface-type; + description + "IEEE1394 High Performance Serial Bus."; + } + identity if-gsn { + base iana-interface-type; + description + "HIPPI-6400."; + } + identity dvbRccMacLayer { + base iana-interface-type; + description + "DVB-RCC MAC Layer."; + } + identity dvbRccDownstream { + base iana-interface-type; + description + "DVB-RCC Downstream Channel."; + } + identity dvbRccUpstream { + base iana-interface-type; + description + "DVB-RCC Upstream Channel."; + } + identity atmVirtual { + base iana-interface-type; + description + "ATM Virtual Interface."; + } + identity mplsTunnel { + base iana-interface-type; + description + "MPLS Tunnel Virtual Interface."; + } + identity srp { + base iana-interface-type; + description + "Spatial Reuse Protocol."; + } + identity voiceOverAtm { + base iana-interface-type; + description + "Voice over ATM."; + } + identity voiceOverFrameRelay { + base iana-interface-type; + description + "Voice Over Frame Relay."; + } + identity idsl { + base iana-interface-type; + description + "Digital Subscriber Loop over ISDN."; + } + identity compositeLink { + base iana-interface-type; + description + "Avici Composite Link Interface."; + } + identity ss7SigLink { + base iana-interface-type; + description + "SS7 Signaling Link."; + } + identity propWirelessP2P { + base iana-interface-type; + description + "Prop. P2P wireless interface."; + } + identity frForward { + base iana-interface-type; + description + "Frame Forward Interface."; + } + identity rfc1483 { + base iana-interface-type; + description + "Multiprotocol over ATM AAL5."; + reference + "RFC 1483 - Multiprotocol Encapsulation over ATM + Adaptation Layer 5"; + } + identity usb { + base iana-interface-type; + description + "USB Interface."; + } + identity ieee8023adLag { + base iana-interface-type; + description + "IEEE 802.3ad Link Aggregate."; + } + identity bgppolicyaccounting { + base iana-interface-type; + description + "BGP Policy Accounting."; + } + identity frf16MfrBundle { + base iana-interface-type; + description + "FRF.16 Multilink Frame Relay."; + } + identity h323Gatekeeper { + base iana-interface-type; + description + "H323 Gatekeeper."; + } + identity h323Proxy { + base iana-interface-type; + description + "H323 Voice and Video Proxy."; + } + identity mpls { + base iana-interface-type; + description + "MPLS."; + } + identity mfSigLink { + base iana-interface-type; + description + "Multi-frequency signaling link."; + } + identity hdsl2 { + base iana-interface-type; + description + "High Bit-Rate DSL - 2nd generation."; + } + identity shdsl { + base iana-interface-type; + description + "Multirate HDSL2."; + } + identity ds1FDL { + base iana-interface-type; + description + "Facility Data Link (4Kbps) on a DS1."; + } + identity pos { + base iana-interface-type; + description + "Packet over SONET/SDH Interface."; + } + identity dvbAsiIn { + base iana-interface-type; + description + "DVB-ASI Input."; + } + identity dvbAsiOut { + base iana-interface-type; + description + "DVB-ASI Output."; + } + identity plc { + base iana-interface-type; + description + "Power Line Communications."; + } + identity nfas { + base iana-interface-type; + description + "Non-Facility Associated Signaling."; + } + identity tr008 { + base iana-interface-type; + description + "TR008."; + } + identity gr303RDT { + base iana-interface-type; + description + "Remote Digital Terminal."; + } + identity gr303IDT { + base iana-interface-type; + description + "Integrated Digital Terminal."; + } + identity isup { + base iana-interface-type; + description + "ISUP."; + } + identity propDocsWirelessMaclayer { + base iana-interface-type; + description + "Cisco proprietary Maclayer."; + } + identity propDocsWirelessDownstream { + base iana-interface-type; + description + "Cisco proprietary Downstream."; + } + identity propDocsWirelessUpstream { + base iana-interface-type; + description + "Cisco proprietary Upstream."; + } + identity hiperlan2 { + base iana-interface-type; + description + "HIPERLAN Type 2 Radio Interface."; + } + identity propBWAp2Mp { + base iana-interface-type; + description + "PropBroadbandWirelessAccesspt2Multipt (use of this value + for IEEE 802.16 WMAN interfaces as per IEEE Std 802.16f + is deprecated, and ieee80216WMAN(237) should be used + instead)."; + } + identity sonetOverheadChannel { + base iana-interface-type; + description + "SONET Overhead Channel."; + } + identity digitalWrapperOverheadChannel { + base iana-interface-type; + description + "Digital Wrapper."; + } + identity aal2 { + base iana-interface-type; + description + "ATM adaptation layer 2."; + } + identity radioMAC { + base iana-interface-type; + description + "MAC layer over radio links."; + } + identity atmRadio { + base iana-interface-type; + description + "ATM over radio links."; + } + identity imt { + base iana-interface-type; + description + "Inter-Machine Trunks."; + } + identity mvl { + base iana-interface-type; + description + "Multiple Virtual Lines DSL."; + } + identity reachDSL { + base iana-interface-type; + description + "Long Reach DSL."; + } + identity frDlciEndPt { + base iana-interface-type; + description + "Frame Relay DLCI End Point."; + } + identity atmVciEndPt { + base iana-interface-type; + description + "ATM VCI End Point."; + } + identity opticalChannel { + base iana-interface-type; + description + "Optical Channel."; + } + identity opticalTransport { + base iana-interface-type; + description + "Optical Transport."; + } + identity propAtm { + base iana-interface-type; + description + "Proprietary ATM."; + } + identity voiceOverCable { + base iana-interface-type; + description + "Voice Over Cable Interface."; + } + identity infiniband { + base iana-interface-type; + description + "Infiniband."; + } + identity teLink { + base iana-interface-type; + description + "TE Link."; + } + identity q2931 { + base iana-interface-type; + description + "Q.2931."; + } + identity virtualTg { + base iana-interface-type; + description + "Virtual Trunk Group."; + } + identity sipTg { + base iana-interface-type; + description + "SIP Trunk Group."; + } + identity sipSig { + base iana-interface-type; + description + "SIP Signaling."; + } + identity docsCableUpstreamChannel { + base iana-interface-type; + description + "CATV Upstream Channel."; + } + identity econet { + base iana-interface-type; + description + "Acorn Econet."; + } + identity pon155 { + base iana-interface-type; + description + "FSAN 155Mb Symetrical PON interface."; + } + identity pon622 { + base iana-interface-type; + description + "FSAN 622Mb Symetrical PON interface."; + } + identity bridge { + base iana-interface-type; + description + "Transparent bridge interface."; + } + identity linegroup { + base iana-interface-type; + description + "Interface common to multiple lines."; + } + identity voiceEMFGD { + base iana-interface-type; + description + "Voice E&M Feature Group D."; + } + identity voiceFGDEANA { + base iana-interface-type; + description + "Voice FGD Exchange Access North American."; + } + identity voiceDID { + base iana-interface-type; + description + "Voice Direct Inward Dialing."; + } + identity mpegTransport { + base iana-interface-type; + description + "MPEG transport interface."; + } + identity sixToFour { + base iana-interface-type; + status deprecated; + description + "6to4 interface (DEPRECATED)."; + reference + "RFC 4087 - IP Tunnel MIB"; + } + identity gtp { + base iana-interface-type; + description + "GTP (GPRS Tunneling Protocol)."; + } + identity pdnEtherLoop1 { + base iana-interface-type; + description + "Paradyne EtherLoop 1."; + } + identity pdnEtherLoop2 { + base iana-interface-type; + description + "Paradyne EtherLoop 2."; + } + identity opticalChannelGroup { + base iana-interface-type; + description + "Optical Channel Group."; + } + identity homepna { + base iana-interface-type; + description + "HomePNA ITU-T G.989."; + } + identity gfp { + base iana-interface-type; + description + "Generic Framing Procedure (GFP)."; + } + identity ciscoISLvlan { + base iana-interface-type; + description + "Layer 2 Virtual LAN using Cisco ISL."; + } + identity actelisMetaLOOP { + base iana-interface-type; + description + "Acteleis proprietary MetaLOOP High Speed Link."; + } + identity fcipLink { + base iana-interface-type; + description + "FCIP Link."; + } + identity rpr { + base iana-interface-type; + description + "Resilient Packet Ring Interface Type."; + } + identity qam { + base iana-interface-type; + description + "RF Qam Interface."; + } + identity lmp { + base iana-interface-type; + description + "Link Management Protocol."; + reference + "RFC 4327 - Link Management Protocol (LMP) Management + Information Base (MIB)"; + } + identity cblVectaStar { + base iana-interface-type; + description + "Cambridge Broadband Networks Limited VectaStar."; + } + identity docsCableMCmtsDownstream { + base iana-interface-type; + description + "CATV Modular CMTS Downstream Interface."; + } + identity adsl2 { + base iana-interface-type; + status deprecated; + description + "Asymmetric Digital Subscriber Loop Version 2 + (DEPRECATED/OBSOLETED - please use adsl2plus(238) + instead)."; + reference + "RFC 4706 - Definitions of Managed Objects for Asymmetric + Digital Subscriber Line 2 (ADSL2)"; + } + identity macSecControlledIF { + base iana-interface-type; + description + "MACSecControlled."; + } + identity macSecUncontrolledIF { + base iana-interface-type; + description + "MACSecUncontrolled."; + } + identity aviciOpticalEther { + base iana-interface-type; + description + "Avici Optical Ethernet Aggregate."; + } + identity atmbond { + base iana-interface-type; + description + "atmbond."; + } + identity voiceFGDOS { + base iana-interface-type; + description + "Voice FGD Operator Services."; + } + identity mocaVersion1 { + base iana-interface-type; + description + "MultiMedia over Coax Alliance (MoCA) Interface + as documented in information provided privately to IANA."; + } + identity ieee80216WMAN { + base iana-interface-type; + description + "IEEE 802.16 WMAN interface."; + } + identity adsl2plus { + base iana-interface-type; + description + "Asymmetric Digital Subscriber Loop Version 2 - + Version 2 Plus and all variants."; + } + identity dvbRcsMacLayer { + base iana-interface-type; + description + "DVB-RCS MAC Layer."; + reference + "RFC 5728 - The SatLabs Group DVB-RCS MIB"; + } + identity dvbTdm { + base iana-interface-type; + description + "DVB Satellite TDM."; + reference + "RFC 5728 - The SatLabs Group DVB-RCS MIB"; + } + identity dvbRcsTdma { + base iana-interface-type; + description + "DVB-RCS TDMA."; + reference + "RFC 5728 - The SatLabs Group DVB-RCS MIB"; + } + identity x86Laps { + base iana-interface-type; + description + "LAPS based on ITU-T X.86/Y.1323."; + } + identity wwanPP { + base iana-interface-type; + description + "3GPP WWAN."; + } + identity wwanPP2 { + base iana-interface-type; + description + "3GPP2 WWAN."; + } + identity voiceEBS { + base iana-interface-type; + description + "Voice P-phone EBS physical interface."; + } + identity ifPwType { + base iana-interface-type; + description + "Pseudowire interface type."; + reference + "RFC 5601 - Pseudowire (PW) Management Information Base (MIB)"; + } + identity ilan { + base iana-interface-type; + description + "Internal LAN on a bridge per IEEE 802.1ap."; + } + identity pip { + base iana-interface-type; + description + "Provider Instance Port on a bridge per IEEE 802.1ah PBB."; + } + identity aluELP { + base iana-interface-type; + description + "Alcatel-Lucent Ethernet Link Protection."; + } + identity gpon { + base iana-interface-type; + description + "Gigabit-capable passive optical networks (G-PON) as per + ITU-T G.948."; + } + identity vdsl2 { + base iana-interface-type; + description + "Very high speed digital subscriber line Version 2 + (as per ITU-T Recommendation G.993.2)."; + reference + "RFC 5650 - Definitions of Managed Objects for Very High + Speed Digital Subscriber Line 2 (VDSL2)"; + } + identity capwapDot11Profile { + base iana-interface-type; + description + "WLAN Profile Interface."; + reference + "RFC 5834 - Control and Provisioning of Wireless Access + Points (CAPWAP) Protocol Binding MIB for + IEEE 802.11"; + } + identity capwapDot11Bss { + base iana-interface-type; + description + "WLAN BSS Interface."; + reference + "RFC 5834 - Control and Provisioning of Wireless Access + Points (CAPWAP) Protocol Binding MIB for + IEEE 802.11"; + } + identity capwapWtpVirtualRadio { + base iana-interface-type; + description + "WTP Virtual Radio Interface."; + reference + "RFC 5833 - Control and Provisioning of Wireless Access + Points (CAPWAP) Protocol Base MIB"; + } + identity bits { + base iana-interface-type; + description + "bitsport."; + } + identity docsCableUpstreamRfPort { + base iana-interface-type; + description + "DOCSIS CATV Upstream RF Port."; + } + identity cableDownstreamRfPort { + base iana-interface-type; + description + "CATV downstream RF Port."; + } + identity vmwareVirtualNic { + base iana-interface-type; + description + "VMware Virtual Network Interface."; + } + identity ieee802154 { + base iana-interface-type; + description + "IEEE 802.15.4 WPAN interface."; + reference + "IEEE 802.15.4-2006"; + } + identity otnOdu { + base iana-interface-type; + description + "OTN Optical Data Unit."; + } + identity otnOtu { + base iana-interface-type; + description + "OTN Optical channel Transport Unit."; + } + identity ifVfiType { + base iana-interface-type; + description + "VPLS Forwarding Instance Interface Type."; + } + identity g9981 { + base iana-interface-type; + description + "G.998.1 bonded interface."; + } + identity g9982 { + base iana-interface-type; + description + "G.998.2 bonded interface."; + } + identity g9983 { + base iana-interface-type; + description + "G.998.3 bonded interface."; + } + identity aluEpon { + base iana-interface-type; + description + "Ethernet Passive Optical Networks (E-PON)."; + } + identity aluEponOnu { + base iana-interface-type; + description + "EPON Optical Network Unit."; + } + identity aluEponPhysicalUni { + base iana-interface-type; + description + "EPON physical User to Network interface."; + } + identity aluEponLogicalLink { + base iana-interface-type; + description + "The emulation of a point-to-point link over the EPON + layer."; + } + identity aluGponOnu { + base iana-interface-type; + description + "GPON Optical Network Unit."; + reference + "ITU-T G.984.2"; + } + identity aluGponPhysicalUni { + base iana-interface-type; + description + "GPON physical User to Network interface."; + reference + "ITU-T G.984.2"; + } + identity vmwareNicTeam { + base iana-interface-type; + description + "VMware NIC Team."; + } + } diff --git a/example/systemd/example.service b/example/systemd/example.service new file mode 100644 index 00000000..0bc4130d --- /dev/null +++ b/example/systemd/example.service @@ -0,0 +1,13 @@ +[Unit] +Description=Starts and stops a clixon example service on this system +Wants=example_restconf.service + +[Service] +Type=forking +User=root +RestartSec=60 +Restart=on-failure +ExecStart=/usr/local/sbin/clixon_backend -s running -f /usr/local/etc/example.xml + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/example/systemd/example_restconf.service b/example/systemd/example_restconf.service new file mode 100644 index 00000000..d47ff885 --- /dev/null +++ b/example/systemd/example_restconf.service @@ -0,0 +1,14 @@ +[Unit] +Description=Starts and stops an example clixon restconf service on this system +Wants=example.service +After=example.service + +[Service] +Type=simple +User=www-data +WorkingDirectory=/www-data +Restart=on-failure +ExecStart=/www-data/clixon_restconf -f /usr/local/etc/example.xml + +[Install] +WantedBy=multi-user.target diff --git a/include/clixon_config.h.in~ b/include/clixon_config.h.in~ deleted file mode 100644 index d356c165..00000000 --- a/include/clixon_config.h.in~ +++ /dev/null @@ -1,142 +0,0 @@ -/* include/clixon_config.h.in. Generated from configure.ac by autoheader. */ - -/* Clixon data dir for system yang files etc */ -#undef CLIXON_DATADIR - -/* Location for apps to find default config file */ -#undef CLIXON_DEFAULT_CONFIG - -/* Clixon major release */ -#undef CLIXON_VERSION_MAJOR - -/* Clixon minor release */ -#undef CLIXON_VERSION_MINOR - -/* Clixon path version */ -#undef CLIXON_VERSION_PATCH - -/* Clixon version string */ -#undef CLIXON_VERSION_STRING - -/* Define to 1 if you have the `alphasort' function. */ -#undef HAVE_ALPHASORT - -/* Define to 1 if you have the header file. */ -#undef HAVE_CLIGEN_CLIGEN_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_CRYPT_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_DEPOT_H - -/* Define to 1 if you have the `inet_aton' function. */ -#undef HAVE_INET_ATON - -/* Define to 1 if you have the header file. */ -#undef HAVE_INTTYPES_H - -/* Define to 1 if you have the `cligen' library (-lcligen). */ -#undef HAVE_LIBCLIGEN - -/* Define to 1 if you have the `crypt' library (-lcrypt). */ -#undef HAVE_LIBCRYPT - -/* Define to 1 if you have the `dl' library (-ldl). */ -#undef HAVE_LIBDL - -/* Define to 1 if you have the `fcgi' library (-lfcgi). */ -#undef HAVE_LIBFCGI - -/* Define to 1 if you have the `m' library (-lm). */ -#undef HAVE_LIBM - -/* Define to 1 if you have the `nsl' library (-lnsl). */ -#undef HAVE_LIBNSL - -/* Define to 1 if you have the `qdbm' library (-lqdbm). */ -#undef HAVE_LIBQDBM - -/* Define to 1 if you have the `socket' library (-lsocket). */ -#undef HAVE_LIBSOCKET - -/* Define to 1 if you have the header file. */ -#undef HAVE_LINUX_IF_VLAN_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_MEMORY_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_QDBM_DEPOT_H - -/* Define to 1 if you have the `sigaction' function. */ -#undef HAVE_SIGACTION - -/* Define to 1 if you have the `sigvec' function. */ -#undef HAVE_SIGVEC - -/* Define to 1 if you have the header file. */ -#undef HAVE_STDINT_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_STDLIB_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_STRINGS_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_STRING_H - -/* Define to 1 if you have the `strlcpy' function. */ -#undef HAVE_STRLCPY - -/* Define to 1 if you have the `strndup' function. */ -#undef HAVE_STRNDUP - -/* Define to 1 if you have the `strsep' function. */ -#undef HAVE_STRSEP - -/* Define to 1 if you have the `strverscmp' function. */ -#undef HAVE_STRVERSCMP - -/* Define to 1 if you have the header file. */ -#undef HAVE_SYS_STAT_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_SYS_TYPES_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_SYS_UCRED_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_UNISTD_H - -/* Define to 1 if you have the `versionsort' function. */ -#undef HAVE_VERSIONSORT - -/* Define to the address where bug reports for this package should be sent. */ -#undef PACKAGE_BUGREPORT - -/* Define to the full name of this package. */ -#undef PACKAGE_NAME - -/* Define to the full name and version of this package. */ -#undef PACKAGE_STRING - -/* Define to the one symbol short name of this package. */ -#undef PACKAGE_TARNAME - -/* Define to the home page for this package. */ -#undef PACKAGE_URL - -/* Define to the version of this package. */ -#undef PACKAGE_VERSION - -/* Define to 1 if you have the ANSI C header files. */ -#undef STDC_HEADERS - -/* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a - `char[]'. */ -#undef YYTEXT_POINTER - -#include diff --git a/include/clixon_config.h~ b/include/clixon_config.h~ deleted file mode 100644 index 5be37864..00000000 --- a/include/clixon_config.h~ +++ /dev/null @@ -1,134 +0,0 @@ -/* include/clixon_config.h. Generated from clixon_config.h.in by configure. */ -/* include/clixon_config.h.in. Generated from configure.ac by autoheader. */ - -/* Clixon major release */ -#define CLIXON_VERSION_MAJOR 3 - -/* Clixon minor release */ -#define CLIXON_VERSION_MINOR 3 - -/* Clixon path version */ -#define CLIXON_VERSION_PATCH 2 - -/* Clixon version string */ -#define CLIXON_VERSION_STRING "3.3.2" - -/* Define to 1 if you have the `alphasort' function. */ -#define HAVE_ALPHASORT 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_CLIGEN_CLIGEN_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_CRYPT_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_DEPOT_H */ - -/* Define to 1 if you have the `inet_aton' function. */ -#define HAVE_INET_ATON 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_INTTYPES_H 1 - -/* Define to 1 if you have the `crypt' library (-lcrypt). */ -#define HAVE_LIBCRYPT 1 - -/* Define to 1 if you have the `dl' library (-ldl). */ -#define HAVE_LIBDL 1 - -/* Define to 1 if you have the `fcgi' library (-lfcgi). */ -#define HAVE_LIBFCGI 1 - -/* Define to 1 if you have the `m' library (-lm). */ -#define HAVE_LIBM 1 - -/* Define to 1 if you have the `nsl' library (-lnsl). */ -#define HAVE_LIBNSL 1 - -/* Define to 1 if you have the `qdbm' library (-lqdbm). */ -#define HAVE_LIBQDBM 1 - -/* Define to 1 if you have the `socket' library (-lsocket). */ -/* #undef HAVE_LIBSOCKET */ - -/* Define to 1 if you have the header file. */ -#define HAVE_LINUX_IF_VLAN_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_MEMORY_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_QDBM_DEPOT_H 1 - -/* Define to 1 if you have the `sigaction' function. */ -#define HAVE_SIGACTION 1 - -/* Define to 1 if you have the `sigvec' function. */ -#define HAVE_SIGVEC 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STDINT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STDLIB_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STRINGS_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STRING_H 1 - -/* Define to 1 if you have the `strlcpy' function. */ -/* #undef HAVE_STRLCPY */ - -/* Define to 1 if you have the `strndup' function. */ -#define HAVE_STRNDUP 1 - -/* Define to 1 if you have the `strsep' function. */ -#define HAVE_STRSEP 1 - -/* Define to 1 if you have the `strverscmp' function. */ -#define HAVE_STRVERSCMP 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_STAT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_TYPES_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SYS_UCRED_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_UNISTD_H 1 - -/* Define to 1 if you have the `versionsort' function. */ -#define HAVE_VERSIONSORT 1 - -/* Define to the address where bug reports for this package should be sent. */ -#define PACKAGE_BUGREPORT "" - -/* Define to the full name of this package. */ -#define PACKAGE_NAME "" - -/* Define to the full name and version of this package. */ -#define PACKAGE_STRING "" - -/* Define to the one symbol short name of this package. */ -#define PACKAGE_TARNAME "" - -/* Define to the home page for this package. */ -#define PACKAGE_URL "" - -/* Define to the version of this package. */ -#define PACKAGE_VERSION "" - -/* Define to 1 if you have the ANSI C header files. */ -#define STDC_HEADERS 1 - -/* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a - `char[]'. */ -#define YYTEXT_POINTER 1 - -#include diff --git a/include/clixon_custom.h b/include/clixon_custom.h index eaad25ac..9a9fa38c 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -43,6 +43,26 @@ int strverscmp (__const char *__s1, __const char *__s2); #endif +/* Set if you want to enable "v" cli callback functions, such as cli_setv() + * This was obsoleted in 3.7 + */ +#undef COMPAT_CLIV +/* Set if you want to assert that all rpc messages have set username + */ +#undef RPC_USERNAME_ASSERT +/* Full xmlns validation check is made only if XML has associated YANG spec +*/ +#define XMLNS_YANG_ONLY 1 +/* Set if you want to enable old xpath functions in clixon_xsl.* instead of the + * the new xpath functions in clixon_xpath.* + * Note that when changing from old xpath code to new, calls on the form + * `x[a=str]` where `str` is a string (not a number or XML symbol), + * must be changed to: `x[a='str'] or x[a="str"]` + * Enabling COMPAT_XSL may make sense if you have written a lot of user code that + * relieson the error above. Or if a bug appears in the newimplementation. + * @see test/lib.sh + */ +#undef COMPAT_XSL diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index 05432227..dfa82bc4 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -82,6 +82,8 @@ #include #include #include +#include +#include #include #include diff --git a/lib/clixon/clixon_err.h b/lib/clixon/clixon_err.h index 8acc723c..8b8900ac 100644 --- a/lib/clixon/clixon_err.h +++ b/lib/clixon/clixon_err.h @@ -85,7 +85,11 @@ extern char clicon_err_reason[ERR_STRLEN]; * Prototypes */ int clicon_err_reset(void); +#if defined(__GNUC__) && __GNUC__ >= 3 +int clicon_err_fn(const char *fn, const int line, int level, int err, char *format, ...) __attribute__ ((format (printf, 5, 6))); +#else int clicon_err_fn(const char *fn, const int line, int level, int err, char *format, ...); +#endif char *clicon_strerror(int err); void *clicon_err_save(void); int clicon_err_restore(void *handle); diff --git a/lib/clixon/clixon_log.h b/lib/clixon/clixon_log.h index 01c6eac4..7b833427 100644 --- a/lib/clixon/clixon_log.h +++ b/lib/clixon/clixon_log.h @@ -62,10 +62,16 @@ extern int debug; int clicon_log_init(char *ident, int upto, int flags); int clicon_get_logflags(void); int clicon_log_str(int level, char *msg); +#if defined(__GNUC__) && __GNUC__ >= 3 +int clicon_log(int level, char *format, ...) __attribute__ ((format (printf, 2, 3))); +int clicon_debug(int dbglevel, char *format, ...) __attribute__ ((format (printf, 2, 3))); +#else int clicon_log(int level, char *format, ...); +int clicon_debug(int dbglevel, char *format, ...); +#endif clicon_log_notify_t *clicon_log_register_callback(clicon_log_notify_t *cb, void *arg); int clicon_debug_init(int dbglevel, FILE *f); -int clicon_debug(int dbglevel, char *format, ...); + char *mon2name(int md); #endif /* _CLIXON_LOG_H_ */ diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index f7aa7174..cd6038aa 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -41,7 +41,9 @@ /* * Constants */ -/* Hardcoded plugin symbol. Must exist in all plugins to kickstart */ +/* Hardcoded plugin symbol. Must exist in all plugins to kickstart + * @see clixon_plugin_init + */ #define CLIXON_PLUGIN_INIT "clixon_plugin_init" /* @@ -91,8 +93,9 @@ typedef int (plgexit_t)(clicon_handle); /* Plugin exit */ /* Plugin authorization. Set username option (or not) * @param[in] Clicon handle * @param[in] void*, eg Fastcgihandle request restconf - * @retval 0 if credentials OK - * @retval -1 credentials not OK + * @retval -1 Fatal error + * @retval 0 Credential not OK + * @retval 1 Credential OK */ typedef int (plgauth_t)(clicon_handle, void *); @@ -180,6 +183,7 @@ typedef struct clixon_plugin clixon_plugin; /*! Plugin initialization function. Must appear in all plugins * @param[in] h Clixon handle * @retval api Pointer to API struct + * @retval NULL Failure (if clixon_err() called), module disabled otherwise. * @see CLIXON_PLUGIN_INIT default symbol */ clixon_plugin_api *clixon_plugin_init(clicon_handle h); diff --git a/lib/clixon/clixon_proto.h b/lib/clixon/clixon_proto.h index ef79b6b4..fda6ba07 100644 --- a/lib/clixon/clixon_proto.h +++ b/lib/clixon/clixon_proto.h @@ -61,7 +61,11 @@ struct clicon_msg { char *format_int2str(enum format_enum showas); enum format_enum format_str2int(char *str); +#if defined(__GNUC__) && __GNUC__ >= 3 +struct clicon_msg *clicon_msg_encode(char *format, ...) __attribute__ ((format (printf, 1, 2))); +#else struct clicon_msg *clicon_msg_encode(char *format, ...); +#endif int clicon_msg_decode(struct clicon_msg *msg, cxobj **xml); int clicon_connect_unix(char *sockpath); diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index 311702b5..5b5789dd 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -46,6 +46,8 @@ static const map_str2int atmap[] = { {NULL, -1} }; * @endcode + * @see clicon_int2str + * @see clicon_str2int */ struct map_str2int{ char *ms_str; diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 6a0513ec..59e25913 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -69,7 +69,7 @@ typedef struct xml cxobj; /* struct defined in clicon_xml.c */ * @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); +typedef int (xml_applyfn_t)(cxobj *x, void *arg); /* * xml_flag() flags: @@ -80,6 +80,7 @@ typedef int (xml_applyfn_t)(cxobj *yn, void *arg); #define XML_FLAG_CHANGE 0x08 /* Node is changed (commits) or child changed rec */ #define XML_FLAG_NONE 0x10 /* Node is added as NONE */ + /* Sort and binary search of XML children * Experimental */ @@ -106,9 +107,6 @@ char *xml_value_append(cxobj *xn, char *val); enum cxobj_type xml_type(cxobj *xn); int xml_type_set(cxobj *xn, enum cxobj_type type); -cg_var *xml_cv_get(cxobj *xn); -int xml_cv_set(cxobj *xn, cg_var *cv); - int xml_child_nr(cxobj *xn); int xml_child_nr_type(cxobj *xn, enum cxobj_type type); cxobj *xml_child_i(cxobj *xn, int i); diff --git a/lib/clixon/clixon_xpath.h b/lib/clixon/clixon_xpath.h new file mode 100644 index 00000000..ce62fc23 --- /dev/null +++ b/lib/clixon/clixon_xpath.h @@ -0,0 +1,103 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10 + */ +#ifndef _CLIXON_XPATH_H +#define _CLIXON_XPATH_H + +/* + * Types + */ +enum xp_op{ + XO_AND, + XO_OR, + XO_DIV, + XO_MOD, + XO_ADD, + XO_MULT, + XO_SUB, + XO_EQ, + XO_NE, + XO_GE, + XO_LE, + XO_LT, + XO_GT, + XO_UNION, +}; + +/* Axis specifiers according to https://www.w3.org/TR/xpath-10/#NT-AxisName */ +enum axis_type{ + A_NAN = 0, /* Not set */ + A_ANCESTOR, + A_ANCESTOR_OR_SELF, + A_ATTRIBUTE, + A_CHILD, + A_DESCENDANT, + A_DESCENDANT_OR_SELF, + A_FOLLOWING, + A_FOLLOWING_SIBLING, + A_NAMESPACE, + A_PARENT, + A_PRECEEDING, + A_PRECEEDING_SIBLING, + A_SELF, + A_ROOT /* XXX Not in https://www.w3.org/TR/xpath-10 */ +}; + +/* + * Variables + */ +extern const map_str2int xpopmap[]; + +/* + * Prototypes + */ +#if defined(__GNUC__) && __GNUC__ >= 3 +int xpath_vec(cxobj *xcur, char *format, cxobj ***vec, size_t *veclen, ...) __attribute__ ((format (printf, 2, 5))); +int xpath_vec_flag(cxobj *xcur, char *format, uint16_t flags, + cxobj ***vec, size_t *veclen, ...) __attribute__ ((format (printf, 2, 6))); +cxobj *xpath_first(cxobj *xcur, char *format, ...) __attribute__ ((format (printf, 2, 3))); +int xpath_vec_bool(cxobj *xcur, char *format, ...) __attribute__ ((format (printf, 2, 3))); + +#else +int xpath_vec(cxobj *xcur, char *format, cxobj ***vec, size_t *veclen, ...); +int xpath_vec_flag(cxobj *xcur, char *format, uint16_t flags, + cxobj ***vec, size_t *veclen, ...); +cxobj *xpath_first(cxobj *xcur, char *format, ...); +int xpath_vec_bool(cxobj *xcur, char *format, ...); + +#endif +int xpath_vec_ctx(cxobj *xcur, char *xpath, xp_ctx **xrp); + +#endif /* _CLIXON_XPATH_H */ diff --git a/lib/clixon/clixon_xpath_ctx.h b/lib/clixon/clixon_xpath_ctx.h new file mode 100644 index 00000000..03854495 --- /dev/null +++ b/lib/clixon/clixon_xpath_ctx.h @@ -0,0 +1,103 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10 + * This file defines XPATH contexts using in traversing the XPATH parse tree. + */ +#ifndef _CLIXON_XPATH_CTX_H +#define _CLIXON_XPATH_CTX_H + +/* + * Types + */ + +/*! XPATH expression type + * An expression is evaluated to yield an object, which has one of the following four basic types: + * node-set (an unordered collection of nodes without duplicates) + * boolean (true or false) + * number (a floating-point number) + * string (a sequence of UCS characters) + */ +enum xp_objtype{ + XT_NODESET, + XT_BOOL, + XT_NUMBER, + XT_STRING +}; + +/* Expression evaluation occurs with respect to a context. XSLT and XPointer specify how the context is + * determined for XPath expressions used in XSLT and XPointer respectively. The context consists of: + * a node (the context node) + * a pair of non-zero positive integers (the context position and the context size) + * a set of variable bindings + * a function library + * the set of namespace declarations in scope for the expression + + * For each node in the node-set to be filtered, the PredicateExpr is + * evaluated with that node as the context node, with the number of nodes + * in the node-set as the context size, and with the proximity position + * of the node in the node-set with respect to the axis as the context + * position; if PredicateExpr evaluates to true for that node, the node + * is included in the new node-set; otherwise, it is not included. + */ +struct xp_ctx{ + enum xp_objtype xc_type; + cxobj **xc_nodeset; /* if type XT_NODESET */ + size_t xc_size; /* Length of nodeset */ + int xc_bool; /* if xc_type XT_BOOL */ + double xc_number; /* if xc_type XT_NUMBER */ + char *xc_string; /* if xc_type XT_STRING */ + cxobj *xc_node; /* Node in nodeset XXX maybe not needed*/ + cxobj *xc_initial; /* RFC 7960 10.1.1 extension: for current() */ + int xc_descendant; /* // */ + /* NYI: a set of variable bindings, set of namespace declarations */ +}; +typedef struct xp_ctx xp_ctx; + +/* + * Variables + */ +extern const map_str2int ctxmap[]; + +/* + * Prototypes + */ +int ctx_free(xp_ctx *xc); +xp_ctx *ctx_dup(xp_ctx *xc); +int ctx_nodeset_replace(xp_ctx *xc, cxobj **vec, size_t veclen); +int ctx_print(cbuf *cb, int id, xp_ctx *xc, char *str); +int ctx2boolean(xp_ctx *xc); +int ctx2string(xp_ctx *xc, char **str0); +int ctx2number(xp_ctx *xc, double *n0); + +#endif /* _CLIXON_XPATH_CTX_H */ diff --git a/lib/clixon/clixon_xsl.h b/lib/clixon/clixon_xsl.h index af1a4926..5d572528 100644 --- a/lib/clixon/clixon_xsl.h +++ b/lib/clixon/clixon_xsl.h @@ -39,10 +39,12 @@ /* * Prototypes */ -cxobj *xpath_first(cxobj *cxtop, char *format, ...); +int xpath_vec_xsl(cxobj *cxtop, char *xpath, cxobj ***vec, size_t *veclen); +int xpath_vec_flag_xsl(cxobj *cxtop, char *xpath, uint16_t flags, + cxobj ***vec, size_t *veclen); +cxobj *xpath_first_xsl(cxobj *cxtop, char *xpath); +#ifdef COMPAT_XSL cxobj *xpath_each(cxobj *xn_top, char *xpath, cxobj *prev); -int xpath_vec(cxobj *cxtop, char *format, cxobj ***vec, size_t *veclen, ...); -int xpath_vec_flag(cxobj *cxtop, char *xpath, uint16_t flags, - cxobj ***vec, size_t *veclen, ...); +#endif #endif /* _CLIXON_XSL_H */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index c77d64aa..18fbee6b 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -202,6 +202,7 @@ struct yang_stmt{ cvec *ys_cvec; /* List of stmt-specific variables Y_RANGE: range_min, range_max Y_LIST: vector of keys + Y_TYPE & identity: store all derived types */ yang_type_cache *ys_typecache; /* If ys_keyword==Y_TYPE, cache all typedef data except unions */ }; @@ -245,6 +246,7 @@ yang_stmt *yn_each(yang_node *yn, yang_stmt *ys); char *yang_key2str(int keyword); char *yarg_prefix(yang_stmt *ys); char *yarg_id(yang_stmt *ys); +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); @@ -252,9 +254,11 @@ yang_stmt *yang_find(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); yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, yang_class class); +char *yang_find_myprefix(yang_stmt *ys); int yang_order(yang_stmt *y); int yang_print(FILE *f, yang_node *yn); int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal); +yang_stmt *yang_parse_file(int fd, const char *name, yang_spec *ysp); int yang_parse(clicon_handle h, const char *yang_dir, const char *module, const char *revision, yang_spec *ysp); int yang_apply(yang_node *yn, enum rfc_6020 key, yang_applyfn_t fn, diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index b26efee5..a37680c9 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -45,6 +45,7 @@ sysconfdir = @sysconfdir@ HOST_VENDOR = @host_vendor@ SH_SUFFIX = @SH_SUFFIX@ + CLIXON_VERSION = @CLIXON_VERSION@ CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@ CLIXON_MINOR = @CLIXON_VERSION_MINOR@ @@ -52,6 +53,8 @@ CLIXON_MINOR = @CLIXON_VERSION_MINOR@ VPATH = @srcdir@ CC = @CC@ CFLAGS = -fPIC @CFLAGS@ +INSTALL = @INSTALL@ +INSTALL_LIB = @INSTALL@ INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ @@ -69,12 +72,13 @@ SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.c \ clixon_json.c clixon_yang.c clixon_yang_type.c \ clixon_hash.c clixon_options.c clixon_plugin.c \ clixon_proto.c clixon_proto_client.c \ - clixon_xsl.c clixon_sha1.c clixon_xml_db.c clixon_netconf_lib.c + clixon_xsl.c clixon_xpath.c clixon_xpath_ctx.c clixon_sha1.c \ + clixon_xml_db.c clixon_netconf_lib.c YACCOBJS := lex.clixon_xml_parse.o clixon_xml_parse.tab.o \ lex.clixon_yang_parse.o clixon_yang_parse.tab.o \ - lex.clixon_json_parse.o clixon_json_parse.tab.o - + lex.clixon_json_parse.o clixon_json_parse.tab.o \ + lex.clixon_xpath_parse.o clixon_xpath_parse.tab.o # Generated src GENSRC = build.c @@ -96,9 +100,11 @@ clean: rm -f clixon_xml_parse.tab.[ch] clixon_xml_parse.yy.[co] rm -f clixon_yang_parse.tab.[ch] clixon_yang_parse.[co] rm -f clixon_json_parse.tab.[ch] clixon_json_parse.[co] + rm -f clixon_xpath_parse.tab.[ch] clixon_xpath_parse.[co] rm -f lex.clixon_xml_parse.c rm -f lex.clixon_yang_parse.c rm -f lex.clixon_json_parse.c + rm -f lex.clixon_xpath_parse.c ############################################################################# # Implicit rules for lex and yacc. @@ -148,6 +154,18 @@ clixon_json_parse.tab.c clixon_json_parse.tab.h: clixon_json_parse.y lex.clixon_json_parse.o : lex.clixon_json_parse.c clixon_json_parse.tab.h $(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -Wno-error -c $< +# xpath parser +lex.clixon_xpath_parse.c : clixon_xpath_parse.l clixon_xpath_parse.tab.h + $(LEX) -Pclixon_xpath_parse clixon_xpath_parse.l # -d is debug + +clixon_xpath_parse.tab.c clixon_xpath_parse.tab.h: clixon_xpath_parse.y + $(YACC) -l -d -p clixon_xpath_parse clixon_xpath_parse.y # -t is debug + mv y.tab.c clixon_xpath_parse.tab.c + mv y.tab.h clixon_xpath_parse.tab.h + +lex.clixon_xpath_parse.o : lex.clixon_xpath_parse.c clixon_xpath_parse.tab.h + $(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -Wno-error -c $< + distclean: clean rm -f Makefile *~ .depend @@ -162,7 +180,6 @@ build.c: date +"const char CLIXON_BUILDSTR[64]=\"%Y.%m.%d %H:%M by `whoami` on `hostname`"\"\; > build.c; echo "const char CLIXON_VERSION[64]=\"$(CLIXON_VERSION)\""\; >> build.c; - $(MYLIB) : $(GENOBJS) $(OBJS) ifeq ($(HOST_VENDOR),apple) $(CC) $(LDFLAGS) -shared -o $@ $(GENOBJS) $(OBJS) $(LIBS) -undefined dynamic_lookup -o $@ @@ -182,8 +199,8 @@ install: install-lib install-include: install-lib: $(MYLIB) - install -m 0755 -d $(DESTDIR)$(libdir) - install -m 0644 $(INSTALLFLAGS) $(MYLIB) $(DESTDIR)$(libdir) + $(INSTALL) -m 0755 -d $(DESTDIR)$(libdir) + $(INSTALL_LIB) -m 0644 $(INSTALLFLAGS) $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon.so.3 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon.so diff --git a/lib/src/clixon_err.c b/lib/src/clixon_err.c index c50148e0..aa4576df 100644 --- a/lib/src/clixon_err.c +++ b/lib/src/clixon_err.c @@ -102,7 +102,8 @@ static struct errvec EV[] = { }; static char * -clicon_strerror1(int err, struct errvec vec[]) +clicon_strerror1(int err, + struct errvec vec[]) { struct errvec *ev; @@ -134,18 +135,18 @@ clicon_err_reset(void) * - Set global reason string clicon_err_reason * @note: err direction (syslog and/or stderr) controlled by clicon_log_init() * - * @param fn Inline function name (when called from clicon_err() macro) - * @param line Inline file line number (when called from clicon_err() macro) - * @param err Error number, typically errno - * @param suberr Sub-error number - * @param reason Error string, format with argv + * @param[in] fn Inline function name (when called from clicon_err() macro) + * @param[in] line Inline file line number (when called from clicon_err() macro) + * @param[in] err Error number, typically errno + * @param[in] suberr Sub-error number + * @param[in] reason Error string, format with argv */ int clicon_err_fn(const char *fn, - const int line, - int category, - int suberr, - char *reason, ...) + const int line, + int category, + int suberr, + char *reason, ...) { va_list args; int len; diff --git a/lib/src/clixon_event.c b/lib/src/clixon_event.c index f23420ac..1569f69f 100644 --- a/lib/src/clixon_event.c +++ b/lib/src/clixon_event.c @@ -267,7 +267,7 @@ event_poll(int fd) FD_ZERO(&fdset); FD_SET(fd, &fdset); if ((retval = select(FD_SETSIZE, &fdset, NULL, NULL, &tnull)) < 0) - clicon_err(OE_EVENTS, errno, "%s select1: %s", __FUNCTION__, strerror(errno)); + clicon_err(OE_EVENTS, errno, "select"); return retval; } @@ -307,18 +307,17 @@ event_loop(void) if (n == -1) { if (errno == EINTR){ clicon_debug(1, "%s select: %s", __FUNCTION__, strerror(errno)); - clicon_err(OE_EVENTS, errno, "%s select1: %s", __FUNCTION__, strerror(errno)); + clicon_err(OE_EVENTS, errno, "select"); retval = 0; } else - clicon_err(OE_EVENTS, errno, "%s select2", __FUNCTION__); + clicon_err(OE_EVENTS, errno, "select"); goto err; } if (n==0){ /* Timeout */ e = ee_timers; ee_timers = ee_timers->e_next; - clicon_debug(2, "%s timeout: %s[%x]", - __FUNCTION__, e->e_string, e->e_arg); + clicon_debug(2, "%s timeout: %s", __FUNCTION__, e->e_string); if ((*e->e_fn)(0, e->e_arg) < 0){ free(e); goto err; @@ -331,8 +330,7 @@ event_loop(void) break; e_next = e->e_next; if(e->e_type == EVENT_FD && FD_ISSET(e->e_fd, &fdset)){ - clicon_debug(2, "%s: FD_ISSET: %s[%x]", - __FUNCTION__, e->e_string, e->e_arg); + clicon_debug(2, "%s: FD_ISSET: %s", __FUNCTION__, e->e_string); if ((*e->e_fn)(e->e_fd, e->e_arg) < 0){ clicon_debug(1, "%s Error in: %s", __FUNCTION__, e->e_string); goto err; diff --git a/lib/src/clixon_file.c b/lib/src/clixon_file.c index 12df1b83..160d3145 100644 --- a/lib/src/clixon_file.c +++ b/lib/src/clixon_file.c @@ -241,12 +241,11 @@ group_name2gid(char *name, gr = &g0; /* This leaks memory in ubuntu */ if (getgrnam_r(name, gr, buf, sizeof(buf), >mp) < 0){ - clicon_err(OE_UNIX, errno, "%s: getgrnam_r(%s): %s", - __FUNCTION__, name, strerror(errno)); + clicon_err(OE_UNIX, errno, "getgrnam_r(%s)", name); return -1; } if (gtmp == NULL){ - clicon_err(OE_UNIX, 0, "%s: No such group: %s", __FUNCTION__, name); + clicon_err(OE_UNIX, 0, "No such group: %s", name); fprintf(stderr, "No such group %s\n", name); return -1; } diff --git a/lib/src/clixon_hash.c b/lib/src/clixon_hash.c index 4debe9c2..aa397d4d 100644 --- a/lib/src/clixon_hash.c +++ b/lib/src/clixon_hash.c @@ -52,24 +52,24 @@ * clicon_hash_t *hash = hash_init(); * * n = 234; - * hash_add (hash, "APA", &n, sizeof(n)); + * hash_add(hash, "APA", &n, sizeof(n)); * hash_dump(hash, stdout); * * puts("----"); * - * hash_add (hash, "BEPA", "hoppla Polle!", strlen("hoppla Polle!")+1); + * hash_add(hash, "BEPA", "hoppla Polle!", strlen("hoppla Polle!")+1); * puts((char *)hash_value(hash, "BEPA", NULL)); * hash_dump(hash, stdout); * * puts("----"); * * n = 33; - * hash_add (hash, "CEPA", &n, sizeof(n)); + * hash_add(hash, "CEPA", &n, sizeof(n)); * hash_dump(hash, stdout); * * puts("----"); * - * hash_del (hash, "APA"); + * hash_del(hash, "APA"); * hash_dump(hash, stdout); * * hash_free(hash); @@ -118,11 +118,11 @@ hash_init (void) { clicon_hash_t *hash; - if ((hash = (clicon_hash_t *)malloc (sizeof (clicon_hash_t) * HASH_SIZE)) == NULL){ + if ((hash = (clicon_hash_t *)malloc(sizeof(clicon_hash_t) * HASH_SIZE)) == NULL){ clicon_err(OE_UNIX, errno, "malloc: %s", strerror(errno)); return NULL; } - memset (hash, 0, sizeof(clicon_hash_t)*HASH_SIZE); + memset(hash, 0, sizeof(clicon_hash_t)*HASH_SIZE); return hash; } @@ -167,7 +167,7 @@ hash_lookup(clicon_hash_t *hash, h = hash[bkt]; if (h) { do { - if (!strcmp (h->h_key, key)) + if (!strcmp(h->h_key, key)) return h; h = NEXTQ(clicon_hash_t, h); } while (h != hash[bkt]); @@ -218,15 +218,15 @@ hash_add(clicon_hash_t *hash, clicon_hash_t new = NULL; /* If variable exist, don't allocate a new. just replace value */ - h = hash_lookup (hash, key); + h = hash_lookup(hash, key); if (h == NULL) { - if ((new = (clicon_hash_t)malloc (sizeof (*new))) == NULL){ + if ((new = (clicon_hash_t)malloc(sizeof(*new))) == NULL){ clicon_err(OE_UNIX, errno, "malloc: %s", strerror(errno)); goto catch; } - memset (new, 0, sizeof (*new)); + memset(new, 0, sizeof(*new)); - new->h_key = strdup (key); + new->h_key = strdup(key); if (new->h_key == NULL){ clicon_err(OE_UNIX, errno, "strdup: %s", strerror(errno)); goto catch; @@ -241,11 +241,11 @@ hash_add(clicon_hash_t *hash, clicon_err(OE_UNIX, errno, "malloc: %s", strerror(errno)); goto catch; } - memcpy (newval, val, vlen); + memcpy(newval, val, vlen); /* Free old value if existing variable */ if (h->h_val) - free (h->h_val); + free(h->h_val); h->h_val = newval; h->h_vlen = vlen; @@ -258,8 +258,8 @@ hash_add(clicon_hash_t *hash, catch: if (new) { if (new->h_key) - free (new->h_key); - free (new); + free(new->h_key); + free(new); } return NULL; @@ -279,15 +279,15 @@ hash_del(clicon_hash_t *hash, { clicon_hash_t h; - h = hash_lookup (hash, key); + h = hash_lookup(hash, key); if (h == NULL) return -1; DELQ(h, hash[hash_bucket(key)], clicon_hash_t); - free (h->h_key); - free (h->h_val); - free (h); + free(h->h_key); + free(h->h_val); + free(h); return 0; } diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index b14592cc..794b35f2 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -691,8 +691,9 @@ int json_parse_str(char *str, cxobj **xt) { - if ((*xt = xml_new("top", NULL, NULL)) == NULL) - return -1; + if (*xt == NULL) + if ((*xt = xml_new("top", NULL, NULL)) == NULL) + return -1; return json_parse(str, "", *xt); } @@ -729,16 +730,14 @@ json_parse_file(int fd, int len = 0; if ((jsonbuf = malloc(jsonbuflen)) == NULL){ - clicon_err(OE_XML, errno, "%s: malloc", __FUNCTION__); + clicon_err(OE_XML, errno, "malloc"); goto done; } memset(jsonbuf, 0, jsonbuflen); ptr = jsonbuf; while (1){ if ((ret = read(fd, &ch, 1)) < 0){ - clicon_err(OE_XML, errno, "%s: read: [pid:%d]\n", - __FUNCTION__, - (int)getpid()); + clicon_err(OE_XML, errno, "read"); break; } if (ret != 0) @@ -755,7 +754,7 @@ json_parse_file(int fd, oldjsonbuflen = jsonbuflen; jsonbuflen *= 2; if ((jsonbuf = realloc(jsonbuf, jsonbuflen)) == NULL){ - clicon_err(OE_XML, errno, "%s: realloc", __FUNCTION__); + clicon_err(OE_XML, errno, "realloc"); goto done; } memset(jsonbuf+oldjsonbuflen, 0, jsonbuflen-oldjsonbuflen); @@ -775,7 +774,7 @@ json_parse_file(int fd, /* * Turn this on to get a json parse and pretty print test program - * Usage: xpath + * Usage: json * read json from input * Example compile: gcc -g -o json -I. -I../clixon ./clixon_json.c -lclixon -lcligen @@ -792,7 +791,8 @@ usage(char *argv0) } int -main(int argc, char **argv) +main(int argc, + char **argv) { cxobj *xt; cxobj *xc; diff --git a/lib/src/clixon_json_parse.y b/lib/src/clixon_json_parse.y index 55fb5f5a..1cf6a583 100644 --- a/lib/src/clixon_json_parse.y +++ b/lib/src/clixon_json_parse.y @@ -101,7 +101,7 @@ object. /* typecast macro */ #define _JY ((struct clicon_json_yacc_arg *)_jy) -#define _YYERROR(msg) {clicon_debug(2, "YYERROR %s '%s' %d", (msg), clixon_json_parsetext, _JY->jy_linenum); YYERROR;} +#define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_json_parsetext, _JY->jy_linenum); YYERROR;} /* add _yy to error paramaters */ #define YY_(msgid) msgid @@ -139,7 +139,8 @@ extern int clixon_json_parseget_lineno (void); */ void -clixon_json_parseerror(void *_jy, char *s) +clixon_json_parseerror(void *_jy, + char *s) { clicon_err(OE_XML, 0, "%s on line %d: %s at or before: '%s'", _JY->jy_name, @@ -192,7 +193,10 @@ json_current_clone(struct clicon_json_yacc_arg *jy) { cxobj *xn; - assert(xn = jy->jy_current); + if (jy->jy_current == NULL){ + return -1; + } + xn = jy->jy_current; json_current_pop(jy); if (jy->jy_current) json_current_new(jy, xml_name(xn)); @@ -258,7 +262,7 @@ array : '[' ']' ; valuelist : value - | valuelist { json_current_clone(_JY);} ',' value + | valuelist { if (json_current_clone(_JY)< 0) _YYERROR("stack?");} ',' value ; /* quoted string */ diff --git a/lib/src/clixon_log.c b/lib/src/clixon_log.c index e55bcba0..21d24bcc 100644 --- a/lib/src/clixon_log.c +++ b/lib/src/clixon_log.c @@ -157,13 +157,12 @@ slogtime(void) /*! Make a logging call to syslog (or stderr). - * - * This is the _only_ place the actual syslog (or stderr) logging is made in clicon,.. - * @note syslog makes itw own filtering, but if log to stderr we do it here - * @see clicon_debug() * * @param[in] level log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG. Thisis OR:d with facility == LOG_USER * @param[in] msg Message to print as argv. + * This is the _only_ place the actual syslog (or stderr) logging is made in clicon,.. + * @note syslog makes itw own filtering, but if log to stderr we do it here + * @see clicon_debug */ int clicon_log_str(int level, @@ -214,13 +213,13 @@ clicon_log_str(int level, /*! Make a logging call to syslog using variable arg syntax. * - * See also clicon_log_init() and clicon_log_str() - * + * @param[in] level log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG. This + * is OR:d with facility == LOG_USER + * @param[in] format Message to print as argv. * @code clicon_log(LOG_NOTICE, "%s: dump to dtd not supported", __PROGRAM__); * @endcode - * @param[in] level log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG. Thisis OR:d with facility == LOG_USER - * @param[in] format Message to print as argv. + * @see cicon_log_init and clicon_log_str */ int clicon_log(int level, diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 45c642e0..cfd0d76c 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -68,6 +68,8 @@ #include "clixon_xml.h" #include "clixon_plugin.h" #include "clixon_xsl.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" #include "clixon_xml_map.h" /* Mapping between Clicon startup modes string <--> constants, @@ -148,7 +150,7 @@ clicon_option_readfile_xml(clicon_hash_t *copt, clicon_err(OE_UNIX, errno, "configure file: %s", filename); return -1; } - clicon_debug(2, "Reading config file %s", __FUNCTION__, filename); + clicon_debug(2, "%s: Reading config file %s", __FUNCTION__, filename); fd = fileno(f); if (xml_parse_file(fd, "", yspec, &xt) < 0) goto done; @@ -186,6 +188,7 @@ clicon_option_readfile_xml(clicon_hash_t *copt, /*! Initialize option values * * Set default options, Read config-file, Check that all values are set. + * Read clixon system config files * @param[in] h clicon handle */ int @@ -289,10 +292,10 @@ clicon_option_str_set(clicon_handle h, /*! Get options as integer but stored as string * - * @param h clicon handle - * @param name name of option - * @retval int An integer as aresult of atoi - * @retval -1 If option does not exist + * @param[in] h clicon handle + * @param[in] name name of option + * @retval int An integer as aresult of atoi + * @retval -1 If option does not exist * @code * if (clicon_option_exists(h, "X") * return clicon_option_int(h, "X"); @@ -330,10 +333,10 @@ clicon_option_int_set(clicon_handle h, /*! Get options as bool but stored as string * - * @param h clicon handle - * @param name name of option - * @retval 0 false, or does not exist, or does not have a boolean value - * @retval 1 true + * @param[in] h clicon handle + * @param[in] name name of option + * @retval 0 false, or does not exist, or does not have a boolean value + * @retval 1 true * @code * if (clicon_option_exists(h, "X") * return clicon_option_bool(h, "X"); diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index c8ce70e7..2631938c 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -126,7 +127,7 @@ clixon_plugin_each_revert(clicon_handle h, int nr) { int i; - clixon_plugin *cp; + clixon_plugin *cp = NULL; clixon_plugin *cpnext = NULL; if (cpprev == NULL) @@ -192,7 +193,7 @@ plugin_load_one(clicon_handle h, dlerror(); /* Clear any existing error */ if ((handle = dlopen(file, dlflags)) == NULL) { error = (char*)dlerror(); - clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error"); + clicon_err(OE_PLUGIN, errno, "dlopen: %s", error ? error : "Unknown error"); goto done; } /* call plugin_init() if defined, eg CLIXON_PLUGIN_INIT or CLIXON_BACKEND_INIT */ @@ -204,12 +205,16 @@ plugin_load_one(clicon_handle h, clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error); goto done; } + clicon_err_reset(); if ((api = initfn(h)) == NULL) { - clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file); - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_err(OE_DB, 0, "Unknown error: %s: plugin_init does not make clicon_err call on error", - file); - goto err; + if (!clicon_errno){ /* if clicon_err() is not called then log and continue */ + clicon_log(LOG_WARNING, "Warning: failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file); + dlclose(handle); + } + else{ + clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file); + goto err; + } } /* Note: sizeof clixon_plugin_api which is largest of clixon_plugin_api:s */ if ((cp = (clixon_plugin *)malloc(sizeof(struct clixon_plugin))) == NULL){ @@ -228,7 +233,8 @@ plugin_load_one(clicon_handle h, snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s", (int)strlen(name), name); - cp->cp_api = *api; + if (api) + cp->cp_api = *api; clicon_debug(1, "%s", __FUNCTION__); done: return cp; @@ -342,7 +348,7 @@ clixon_plugin_exit(clicon_handle h) } if (dlclose(cp->cp_handle) != 0) { error = (char*)dlerror(); - clicon_err(OE_PLUGIN, errno, "dlclose: %s\n", error ? error : "Unknown error"); + clicon_err(OE_PLUGIN, errno, "dlclose: %s", error ? error : "Unknown error"); } } if (_clixon_plugins){ @@ -430,7 +436,7 @@ rpc_callback_register(clicon_handle h, clicon_err(OE_DB, errno, "malloc: %s", strerror(errno)); goto done; } - memset (rc, 0, sizeof (*rc)); + memset(rc, 0, sizeof(*rc)); rc->rc_callback = cb; rc->rc_arg = arg; rc->rc_tag = strdup(tag); /* XXX strdup memleak */ @@ -466,7 +472,7 @@ rpc_callback_delete_all(void) * @param[in] h clicon handle * @param[in] xn Sub-tree (under xorig) at child of rpc: . * @param[out] xret Return XML, error or OK - * @param[in] arg Domain-speific arg (eg client_entry) + * @param[in] arg Domain-speific arg (eg client_entry) * * @retval -1 Error * @retval 0 OK, not found handler. diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index f9c9da9f..2a71992e 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -203,7 +203,7 @@ clicon_connect_unix(char *sockpath) clicon_debug(2, "%s: connecting to %s", __FUNCTION__, addr.sun_path); if (connect(s, (struct sockaddr *)&addr, SUN_LEN(&addr)) < 0){ if (errno == EACCES) - clicon_err(OE_CFG, errno, "connecting unix socket: %s.\n" + clicon_err(OE_CFG, errno, "connecting unix socket: %s." "Client should be member of group $CLICON_SOCK_GROUP: ", sockpath); else @@ -273,7 +273,7 @@ msg_dump(struct clicon_msg *msg) for (i=0; iop_len); i++){ snprintf(buf, sizeof(buf), "%s%02x", buf2, ((char*)msg)[i]&0xff); if ((i+1)%32==0){ - clicon_debug(2, buf); + clicon_debug(2, "%s", buf); snprintf(buf, sizeof(buf), "%s:", __FUNCTION__); } else @@ -282,7 +282,7 @@ msg_dump(struct clicon_msg *msg) strncpy(buf2, buf, sizeof(buf2)); } if (i%32) - clicon_debug(2, buf); + clicon_debug(2, "%s", buf); return 0; } @@ -302,7 +302,7 @@ clicon_msg_send(int s, msg_dump(msg); if (atomicio((ssize_t (*)(int, void *, size_t))write, s, msg, ntohl(msg->op_len)) < 0){ - clicon_err(OE_CFG, errno, "%s", __FUNCTION__); + clicon_err(OE_CFG, errno, "atomicio"); clicon_log(LOG_WARNING, "%s: write: %s len:%u msg:%s", __FUNCTION__, strerror(errno), ntohs(msg->op_len), msg->op_body); goto done; @@ -344,7 +344,7 @@ clicon_msg_rcv(int s, set_signal(SIGINT, atomicio_sig_handler, &oldhandler); if ((hlen = atomicio(read, s, &hdr, sizeof(hdr))) < 0){ - clicon_err(OE_CFG, errno, "%s", __FUNCTION__); + clicon_err(OE_CFG, errno, "atomicio"); goto done; } if (hlen == 0){ @@ -353,7 +353,7 @@ clicon_msg_rcv(int s, goto done; } if (hlen != sizeof(hdr)){ - clicon_err(OE_CFG, errno, "%s: header too short (%d)", __FUNCTION__, hlen); + clicon_err(OE_CFG, errno, "header too short (%d)", hlen); goto done; } mlen = ntohl(hdr.op_len); @@ -365,11 +365,11 @@ clicon_msg_rcv(int s, } memcpy(*msg, &hdr, hlen); if ((len2 = atomicio(read, s, (*msg)->op_body, mlen - sizeof(hdr))) < 0){ - clicon_err(OE_CFG, errno, "%s: read", __FUNCTION__); + clicon_err(OE_CFG, errno, "read"); goto done; } if (len2 != mlen - sizeof(hdr)){ - clicon_err(OE_CFG, errno, "%s: body too short", __FUNCTION__); + clicon_err(OE_CFG, errno, "body too short"); goto done; } if (debug > 1) @@ -504,7 +504,7 @@ clicon_rpc(int s, if (clicon_msg_rcv(s, &reply, &eof) < 0) goto done; if (eof){ - clicon_err(OE_PROTO, ESHUTDOWN, "%s: Socket unexpected close", __FUNCTION__); + clicon_err(OE_PROTO, ESHUTDOWN, "Socket unexpected close"); close(s); errno = ESHUTDOWN; goto done; diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 7c1bec04..35c4b28c 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -65,6 +65,9 @@ #include "clixon_xml.h" #include "clixon_plugin.h" #include "clixon_xsl.h" +#include "clixon_string.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" #include "clixon_proto.h" #include "clixon_err.h" #include "clixon_proto_client.h" @@ -91,6 +94,9 @@ clicon_rpc_msg(clicon_handle h, cxobj *xret = NULL; yang_spec *yspec; +#ifdef RPC_USERNAME_ASSERT + assert(strstr(msg->op_body, "username")!=NULL); /* XXX */ +#endif clicon_debug(1, "%s request:%s", __FUNCTION__, msg->op_body); if ((sock = clicon_sock(h)) == NULL){ clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set"); diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index d9892e90..655bc9f1 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -54,7 +54,6 @@ #include "clixon_string.h" #include "clixon_err.h" - /*! Split string into a vector based on character delimiters. Using malloc * * The given string is split into a vector where the delimiter can be @@ -63,6 +62,17 @@ * The vector returned is one single memory block that must be freed * by the caller * + * @code + * char **vec = NULL; + * int nvec; + * if ((vec = clicon_strsep("/home/user/src/clixon", "/", &nvec)) == NULL) + * err; + * for (i=0; i': - len += strlen("> "); - break; - default: + if (cdata){ + if (strncmp(&str[i], "]]>", strlen("]]>")) == 0) + cdata = 0; len++; } + else + switch (str[i]){ + case '&': + len += strlen("& "); + break; + case '<': + if (strncmp(&str[i], "': + len += strlen("> "); + break; + default: + len++; + } } len++; /* trailing \0 */ + /* We know length, allocate encoding buffer */ if ((esc = malloc(len)) == NULL){ clicon_err(OE_UNIX, errno, "malloc"); goto done; } memset(esc, 0, len); - j = 0; + + /* Same code again, but now actually encode into output buffer */ + j = 0; cdata = 0; for (i=0; i", strlen("]]>")) == 0){ + cdata = 0; + esc[j++] = str[i++]; + esc[j++] = str[i++]; + } + esc[j++] = str[i]; + } + else switch (str[i]){ case '&': if ((l=snprintf(&esc[j], 7, "& ")) < 0){ @@ -321,6 +356,11 @@ xml_chardata_encode(char *str, j += l; break; case '<': + if (strncmp(&str[i], " string */ @@ -181,7 +180,7 @@ xml_namespace(cxobj *xn) return xn->x_namespace; } -/*! Set name of xnode, name is copied +/*! Set name space of xnode, namespace is copied * @param[in] xn xml node * @param[in] namespace new namespace, null-terminated string, copied by function * @retval -1 on error with clicon-err set @@ -204,6 +203,74 @@ xml_namespace_set(cxobj *xn, return 0; } +/*! See if xmlns:= exists, if so return + * + * @param[in] xn XML node + * @param[in] nsn Namespace name + * @retval URI return associated URI if found + * @retval NULL No namespace name binding found for nsn + */ +static char * +xmlns_check(cxobj *xn, + char *nsn) +{ + cxobj *x = NULL; + char *xns; + + while ((x = xml_child_each(xn, x, -1)) != NULL) + if ((xns = xml_namespace(x)) && strcmp(xns, "xmlns")==0 && + strcmp(xml_name(x), nsn) == 0) + return xml_value(x); + return NULL; +} + +/*! Check namespace of xml node by searhing recursively among ancestors + * @param[in] xn xml node + * @param[in] namespace check validity of namespace + * @retval 0 Found / validated or no yang spec + * @retval -1 Not found + * @note This function is grossly inefficient + */ +static int +xml_namespace_check(cxobj *xn, + void *arg) +{ + cxobj *xp = NULL; + char *nsn; + char *n; + yang_stmt *ys = xml_spec(xn); + + /* No namespace name - comply */ + if ((nsn = xml_namespace(xn)) == NULL) + return 0; + /* Check if NSN defined in same node */ + if (xmlns_check(xn, nsn) != NULL) + return 0; + /* Check if NSN defined in some ancestor */ + while ((xp = xml_parent(xn)) != NULL) { + if (xmlns_check(xp, nsn) != NULL) + return 0; + xn = xp; + } +#ifdef XMLNS_YANG_ONLY + if (ys == NULL) + return 0; /* If no yang spec */ + else +#endif + { + /* Check if my namespace */ + if ((n = yang_find_myprefix(ys)) != NULL && strcmp(nsn,n)==0) + return 0; + /* Check if any imported module */ + if (yang_find_module_by_prefix(ys, nsn) != NULL) + return 0; + } + /* Not found, error */ + clicon_err(OE_XML, ENOENT, "Namespace name %s in %s:%s not found", + nsn, nsn, xml_name(xn)); + return -1; +} + /*! Get parent of xnode * @param[in] xn xml node * @retval parent xml node @@ -345,35 +412,6 @@ xml_type_set(cxobj *xn, return old; } -/*! Get cligen variable associated with node - * @param[in] xn xml node - * @retval cv Cligen variable if set - * @retval NULL If not set, or not applicable - */ -cg_var * -xml_cv_get(cxobj *xn) -{ - if (xn->x_cv) - return xn->x_cv; - else - return NULL; -} - -/*! Set cligen variable associated with node - * @param[in] xn xml node - * @param[in] cv Cligen variable or NULL - * @retval 0 if OK - */ -int -xml_cv_set(cxobj *xn, - cg_var *cv) -{ - if (xn->x_cv) - free(xn->x_cv); - xn->x_cv = cv; - return 0; -} - /*! Get number of children * @param[in] xn xml node * @retval number of children in XML tree @@ -869,7 +907,6 @@ xml_find_value(cxobj *xt, * Explaining picture: * xt --> x --> bx (x_type=CX_BODY) * x_name=name return x_value - */ char * xml_find_body(cxobj *xt, @@ -932,8 +969,6 @@ xml_free(cxobj *x) free(x->x_value); if (x->x_namespace) free(x->x_namespace); - if (x->x_cv) - cv_free(x->x_cv); for (i=0; ix_childvec_len; i++){ if ((xc = x->x_childvec[i]) != NULL){ xml_free(xc); @@ -1247,6 +1282,9 @@ _xml_parse(const char *str, goto done; if (clixon_xml_parseparse(&ya) != 0) /* yacc returns 1 on error */ goto done; + /* Verify namespaces after parsing */ + if (xml_apply0(xt, CX_ELMNT, xml_namespace_check, NULL) < 0) + goto done; /* Sort the complete tree after parsing */ if (yspec){ if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0) @@ -1321,7 +1359,7 @@ xml_parse_file(int fd, ptr = xmlbuf; while (1){ if ((ret = read(fd, &ch, 1)) < 0){ - clicon_err(OE_XML, errno, "read: [pid:%d]\n", + clicon_err(OE_XML, errno, "read: [pid:%d]", (int)getpid()); break; } @@ -1449,8 +1487,6 @@ int xml_copy_one(cxobj *x0, cxobj *x1) { - cg_var *cv1; - xml_type_set(x1, xml_type(x0)); if (xml_value(x0)){ /* malloced string */ if ((x1->x_value = strdup(x0->x_value)) == NULL){ @@ -1461,14 +1497,6 @@ xml_copy_one(cxobj *x0, if (xml_name(x0)) /* malloced string */ if ((xml_name_set(x1, xml_name(x0))) < 0) return -1; - if (xml_cv_get(x0)){ - if ((cv1 = cv_dup(xml_cv_get(x0))) == NULL){ - clicon_err(OE_XML, errno, "cv_dup"); - return -1; - } - if ((xml_cv_set(x1, cv1)) < 0) - return -1; - } return 0; } @@ -1658,7 +1686,6 @@ xml_apply0(cxobj *xn, return retval; } - /*! Apply a function call recursively on all ancestors * Recursively traverse upwards to all ancestor nodes in a parse-tree and apply fn(arg) for * each object found. The function is called with the xml node and an @@ -1737,7 +1764,7 @@ xml_body_parse(cxobj *xb, goto done; } if (cvret == 0){ /* parsing failed */ - clicon_err(OE_XML, errno, "Parsing CV: %s", &reason); + clicon_err(OE_XML, errno, "Parsing CV: %s", reason); if (reason) free(reason); } @@ -1861,7 +1888,6 @@ xml_operation2str(enum operation_type op) } } - /* * Turn this on to get a xml parse and pretty print test program * Usage: xpath @@ -1871,7 +1897,7 @@ xml_operation2str(enum operation_type op) * Example run: echo "" | xml */ -#if 0 /* Test program */ +#if 1 /* Test program */ static int usage(char *argv0) @@ -1883,7 +1909,7 @@ usage(char *argv0) int main(int argc, char **argv) { - cxobj *xt; + cxobj *xt = NULL; cxobj *xc; cbuf *cb = cbuf_new(); @@ -1891,8 +1917,8 @@ main(int argc, char **argv) usage(argv[0]); return 0; } - if (xml_parse_file(0, "", NULL,&xt) < 0){ - fprintf(stderr, "parsing 2\n"); + if (xml_parse_file(0, "", NULL, &xt) < 0){ + fprintf(stderr, "xml parse error %s\n", clicon_err_reason); return -1; } xc = NULL; diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index 8702910d..b909385b 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -87,7 +87,7 @@ xmldb_plugin_load(clicon_handle h, dlerror(); /* Clear any existing error */ if ((handle = dlopen(filename, RTLD_NOW|RTLD_GLOBAL)) == NULL) { error = (char*)dlerror(); - clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error"); + clicon_err(OE_PLUGIN, errno, "dlopen: %s", error ? error : "Unknown error"); goto done; } /* Try v1 */ @@ -162,7 +162,7 @@ xmldb_plugin_unload(clicon_handle h) dlerror(); /* Clear any existing error */ if (dlclose(handle) != 0) { error = (char*)dlerror(); - clicon_err(OE_PLUGIN, errno, "dlclose: %s\n", error ? error : "Unknown error"); + clicon_err(OE_PLUGIN, errno, "dlclose: %s", error ? error : "Unknown error"); /* Just report no -1 return*/ } retval = 0; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 1be9d750..301dfa0d 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -83,6 +83,8 @@ #include "clixon_options.h" #include "clixon_xml.h" #include "clixon_plugin.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" #include "clixon_xsl.h" #include "clixon_log.h" #include "clixon_err.h" @@ -226,7 +228,7 @@ xml2cli(FILE *f, return retval; } -/*! Validate an xml node of type leafref, ensure the value is one of that path's reference +/*! Validate xml node of type leafref, ensure the value is one of that path's reference * @param[in] xt XML leaf node of type leafref * @param[in] ytype Yang type statement belonging to the XML node */ @@ -249,7 +251,7 @@ validate_leafref(cxobj *xt, clicon_err(OE_DB, 0, "Leafref %s requires path statement", ytype->ys_argument); goto done; } - if (xpath_vec(xt, ypath->ys_argument, &xvec, &xlen) < 0) + if (xpath_vec(xt, "%s", &xvec, &xlen, ypath->ys_argument) < 0) goto done; for (i = 0; i < xlen; i++) { x = xvec[i]; @@ -270,6 +272,72 @@ validate_leafref(cxobj *xt, return retval; } +/*! Validate xml node of type identityref, ensure value is a defined identity + * Check if a given node has value derived from base identity. This is + * a run-time check necessary when validating eg netconf. + * Valid values for an identityref are any identities derived from all + * the identityref's base identities. + * Example: + * b0 --> b1 --> b2 (b1 & b2 are derived) + * identityref b2 + * base b0; + * This function does: derived_from(b2, b0); + * @param[in] xt XML leaf node of type identityref + * @param[in] ys Yang spec of leaf + * @param[in] ytype Yang type field of type identityref + * @see ys_populate_identity where the derived types are set + * @see RFC7950 Sec 9.10.2: + + */ +static int +validate_identityref(cxobj *xt, + yang_stmt *ys, + yang_stmt *ytype) +{ + int retval = -1; + char *node; + yang_stmt *ybaseref; /* This is the type's base reference */ + yang_stmt *ybaseid; + char *prefix = NULL; + cbuf *cb = NULL; + + /* Get idref value. Then see if this value is derived from ytype. + * Always add default prefix because derived identifiers are stored with + * prefixes in the base identifiers derived-list. + */ + if ((node = xml_body(xt)) == NULL) + return 0; + if (strchr(node, ':') == NULL){ + prefix = yang_find_myprefix(ys); + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "%s:%s", prefix, node); + node = cbuf_get(cb); + } + /* This is the type's base reference */ + if ((ybaseref = yang_find((yang_node*)ytype, Y_BASE, NULL)) == NULL){ + clicon_err(OE_DB, 0, "Identityref validation failed, no base"); + goto done; + } + /* This is the actual base identity */ + if ((ybaseid = yang_find_identity(ybaseref, ybaseref->ys_argument)) == NULL){ + clicon_err(OE_DB, 0, "Identityref validation failed, no base identity"); + goto done; + } + /* Here check if node is in the derived node list of the base identity */ + if (cvec_find(ybaseid->ys_cvec, node) == NULL){ + clicon_err(OE_DB, 0, "Identityref validation failed, %s not derived from %s", node, ybaseid->ys_argument); + goto done; + } + retval = 0; + done: + if (cb) + cbuf_free(cb); + return retval; +} + /*! Validate a single XML node with yang specification for added entry * 1. Check if mandatory leafs present as subs. * 2. Check leaf values, eg int ranges and string regexps. @@ -360,28 +428,91 @@ xml_yang_validate_all(cxobj *xt, void *arg) { int retval = -1; - yang_stmt *ys; - yang_stmt *ytype; + yang_stmt *ys; /* yang node */ + yang_stmt *yc; /* yang child */ + yang_stmt *ye; /* yang must error-message */ + char *xpath; + int nr; /* if not given by argument (overide) use default link and !Node has a config sub-statement and it is false */ if ((ys = xml_spec(xt)) != NULL && yang_config(ys) != 0){ + /* Node-specific validation */ switch (ys->ys_keyword){ case Y_LEAF: /* fall thru */ case Y_LEAF_LIST: /* Special case if leaf is leafref, then first check against current xml tree - */ - if ((ytype = yang_find((yang_node*)ys, Y_TYPE, NULL)) != NULL && - strcmp(ytype->ys_argument, "leafref") == 0) - if (validate_leafref(xt, ytype) < 0) - goto done; + */ + if ((yc = yang_find((yang_node*)ys, Y_TYPE, NULL)) != NULL){ + if (strcmp(yc->ys_argument, "leafref") == 0){ + if (validate_leafref(xt, yc) < 0) + goto done; + } + else if (strcmp(yc->ys_argument, "identityref") == 0){ + if (validate_identityref(xt, ys, yc) < 0) + goto done; + } + } + if ((yc = yang_find((yang_node*)ys, Y_MIN_ELEMENTS, NULL)) != NULL){ + /* The behavior of the constraint depends on the type of the + * leaf-list's or list's closest ancestor node in the schema tree + * that is not a non-presence container (see Section 7.5.1): + * o If no such ancestor exists in the schema tree, the constraint + * is enforced. + * o Otherwise, if this ancestor is a case node, the constraint is + * enforced if any other node from the case exists. + * o Otherwise, it is enforced if the ancestor node exists. + */ +#if 0 + cxobj *xp; + cxobj *x; + int i; + + if ((xp = xml_parent(xt)) != NULL){ + nr = atoi(yc->ys_argument); + x = NULL; + i = 0; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) + i++; + } +#endif + } + if ((yc = yang_find((yang_node*)ys, Y_MAX_ELEMENTS, NULL)) != NULL){ + + } break; default: break; } + /* must sub-node RFC 7950 Sec 7.5.3. Can be several. */ + yc = NULL; + while ((yc = yn_each((yang_node*)ys, yc)) != NULL) { + if (yc->ys_keyword != Y_MUST) + continue; + xpath = yc->ys_argument; /* "must" has xpath argument */ + if ((nr = xpath_vec_bool(xt, "%s", xpath)) < 0) + goto done; + if (!nr){ + if ((ye = yang_find((yang_node*)yc, Y_ERROR_MESSAGE, NULL)) != NULL) + clicon_err(OE_DB, 0, "%s", ye->ys_argument); + else + clicon_err(OE_DB, 0, "xpath %s validation failed", xml_name(xt)); + goto done; + } + } + /* "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ + if ((yc = yang_find((yang_node*)ys, Y_WHEN, NULL)) != NULL){ + xpath = yc->ys_argument; /* "when" has xpath argument */ + if ((nr = xpath_vec_bool(xt, "%s", xpath)) < 0) + goto done; + if (!nr){ + clicon_err(OE_DB, 0, "xpath %s validation failed", xml_name(xt)); + goto done; + } + } } retval = 0; done: @@ -394,6 +525,7 @@ xml_yang_validate_all(cxobj *xt, * @param[out] cvv CLIgen variable vector. Should be freed by cvec_free() * @retval 0 Everything OK, cvv allocated and set * @retval -1 Something wrong, clicon_err() called to set error. No cvv returned + * @note cvv Should be freed by cvec_free() after use. * 'Not recursive' means that only one level of XML bodies is translated to cvec:s. * If range is wriong (eg 1000 for uint8) a warning is logged, the value is * skipped, and continues. @@ -426,7 +558,7 @@ xml2cvec(cxobj *xt, char *name; xc = NULL; - /* Tried to allocate whole cvv here,but some cg_vars may be invalid */ + /* Tried to allocate whole cvv here, but some cg_vars may be invalid */ if ((cvv = cvec_new(0)) == NULL){ clicon_err(OE_UNIX, errno, "cvec_new"); goto err; @@ -913,7 +1045,7 @@ api_path_fmt2api_path(char *api_path_fmt, * @example * api_path_fmt: /interface/%s/address/%s * cvv: name=eth0 - * xpath: /interface/[name=eth0]/address + * xpath: /interface/[name='eth0']/address * @example * api_path_fmt: /ip/me/%s (if key) * cvv: - @@ -956,7 +1088,13 @@ api_path_fmt2xpath(char *api_path_fmt, clicon_err(OE_UNIX, errno, "cv2str_dup"); goto done; } - cprintf(cb, "[%s=%s]", cv_name_get(cv), str); + cprintf(cb, +#ifdef COMPAT_XSL + "[%s=%s]", +#else + "[%s='%s']", +#endif + cv_name_get(cv), str); free(str); } } @@ -1301,7 +1439,6 @@ xml_spec_populate(cxobj *x, return retval; } - /*! Translate from restconf api-path in cvv form to xml xpath * eg a/b=c -> a/[b=c] * @param[in] yspec Yang spec @@ -1374,7 +1511,13 @@ api_path2xpath_cvv(yang_spec *yspec, cprintf(xpath, "/%s", name); v = val; while ((cvi = cvec_each(cvk, cvi)) != NULL){ - cprintf(xpath, "[%s=%s]", cv_string_get(cvi), v); + cprintf(xpath, +#ifdef COMPAT_XSL + "[%s=%s]", +#else + "[%s='%s']", +#endif + cv_string_get(cvi), v); v += strlen(v)+1; } if (val) @@ -1644,6 +1787,7 @@ xml_merge1(cxobj *x0, cxobj *x1c; /* mod child */ char *x1bstr; /* mod body string */ yang_stmt *yc; /* yang child */ + cbuf *cbr = NULL; /* Reason buffer */ assert(x1 && xml_type(x1) == CX_ELMNT); assert(y0); @@ -1682,9 +1826,16 @@ xml_merge1(cxobj *x0, x1cname = xml_name(x1c); /* Get yang spec of the child */ if ((yc = yang_find_datanode(y0, x1cname)) == NULL){ - if (reason && (*reason = strdup("XML node has no corresponding yang specification (Invalid XML or wrong Yang spec?")) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); - goto done; + if (reason){ + if ((cbr = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cprintf(cbr, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", xml_name(x1), x1cname); + if ((*reason = strdup(cbuf_get(cbr))) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } } break; } @@ -1701,6 +1852,8 @@ xml_merge1(cxobj *x0, ok: retval = 0; done: + if (cbr) + cbuf_free(cbr); return retval; } @@ -1708,7 +1861,7 @@ xml_merge1(cxobj *x0, * @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] x1 xml tree which modifies base * @param[in] yspec Yang spec - * @param[out] reason If retval=0 a malloced string. Needs to be freed by caller + * @param[out] reason If retval=0, reason is set. Malloced. Needs to be freed by caller * @retval 0 OK. If reason is set, Yang error * @retval -1 Error * @note both x0 and x1 need to be top-level trees @@ -1726,6 +1879,7 @@ xml_merge(cxobj *x0, cxobj *x0c; /* base child */ cxobj *x1c; /* mod child */ yang_stmt *yc; + cbuf *cbr = NULL; /* Reason buffer */ /* Loop through children of the modification tree */ x1c = NULL; @@ -1733,9 +1887,16 @@ xml_merge(cxobj *x0, x1cname = xml_name(x1c); /* Get yang spec of the child */ if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ - if (reason && (*reason = strdup("XML node has no corresponding yang specification (Invalid XML or wrong Yang spec?")) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); - goto done; + if (reason){ + if ((cbr = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cprintf(cbr, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", xml_name(x1), x1cname); + if ((*reason = strdup(cbuf_get(cbr))) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } } break; } @@ -1749,6 +1910,8 @@ xml_merge(cxobj *x0, } retval = 0; /* OK */ done: + if (cbr) + cbuf_free(cbr); return retval; } diff --git a/lib/src/clixon_xml_parse.l b/lib/src/clixon_xml_parse.l index e0c39884..57000d0e 100644 --- a/lib/src/clixon_xml_parse.l +++ b/lib/src/clixon_xml_parse.l @@ -77,6 +77,7 @@ int clixon_xml_parsewrap(void) %x START %s STATEA %s AMPERSAND +%s CDATA %s CMNT %s STR %s TEXTDECL @@ -103,17 +104,23 @@ int clixon_xml_parsewrap(void) """ { BEGIN(START); return ECOMMENT; } \n _YA->ya_linenum++; diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index ab9d5193..8a80ff8d 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -43,12 +43,11 @@ %token NAME CHARDATA %token VER ENC -%token BSLASH ESLASH +%token BSLASH ESLASH %token BTEXT ETEXT %token BCOMMENT ECOMMENT - -%type attvalue attqname +%type attvalue %lex-param {void *_ya} /* Add this argument to parse() and lex() function */ %parse-param {void *_ya} @@ -117,7 +116,7 @@ xml_parse_version(struct xml_parse_yacc_arg *ya, char *ver) { if(strcmp(ver, "1.0")){ - clicon_err(OE_XML, errno, "Wrong XML version %s expected 1.0\n", ver); + clicon_err(OE_XML, errno, "Wrong XML version %s expected 1.0", ver); free(ver); return -1; } @@ -125,15 +124,41 @@ xml_parse_version(struct xml_parse_yacc_arg *ya, return 0; } -/*! Parse Qualified name +/*! Parse Qualified name --> Unprefixed name * @param[in] ya XML parser yacc handler struct * @param[in] prefix Prefix, namespace, or NULL * @param[in] localpart Name */ static int -xml_parse_qname(struct xml_parse_yacc_arg *ya, - char *prefix, - char *name) +xml_parse_unprefixed_name(struct xml_parse_yacc_arg *ya, + char *name) +{ + int retval = -1; + cxobj *x; + yang_stmt *y = NULL; /* yang node */ + cxobj *xp; /* xml parent */ + + xp = ya->ya_xparent; + if (xml_child_spec(name, xp, ya->ya_yspec, &y) < 0) + goto done; + if ((x = xml_new(name, xp, y)) == NULL) + goto done; + ya->ya_xelement = x; + retval = 0; + done: + free(name); + return retval; +} + +/*! Parse Qualified name -> PrefixedName + * @param[in] ya XML parser yacc handler struct + * @param[in] prefix Prefix, namespace, or NULL + * @param[in] localpart Name + */ +static int +xml_parse_prefixed_name(struct xml_parse_yacc_arg *ya, + char *prefix, + char *name) { int retval = -1; cxobj *x; @@ -197,7 +222,7 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya, goto done; } if (xml_namespace(x)!=NULL){ - clicon_err(OE_XML, 0, "XML parse sanity check failed: %s:%s vs %s\n", + clicon_err(OE_XML, 0, "XML parse sanity check failed: %s:%s vs %s", xml_namespace(x), xml_name(x), name); goto done; } @@ -235,7 +260,7 @@ xml_parse_bslash2(struct xml_parse_yacc_arg *ya, cxobj *xc; if (strcmp(xml_name(x), name)){ - clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s\n", + clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s", xml_namespace(x), xml_name(x), namespace, @@ -244,7 +269,7 @@ xml_parse_bslash2(struct xml_parse_yacc_arg *ya, } if (xml_namespace(x)==NULL || strcmp(xml_namespace(x), namespace)){ - clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s\n", + clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s", xml_namespace(x), xml_name(x), namespace, @@ -276,44 +301,29 @@ xml_parse_bslash2(struct xml_parse_yacc_arg *ya, static int xml_parse_attr(struct xml_parse_yacc_arg *ya, - char *qname, + char *prefix, + char *name, char *attval) { int retval = -1; cxobj *xa; - if ((xa = xml_new(qname, ya->ya_xelement, NULL)) == NULL) + if ((xa = xml_new(name, ya->ya_xelement, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); + if (prefix && xml_namespace_set(xa, prefix) < 0) + goto done; if (xml_value_set(xa, attval) < 0) goto done; retval = 0; done: - free(qname); + free(name); + if (prefix) + free(prefix); free(attval); return retval; } -/*! Parse Attribue Qualified name, Just transform prefix:name into a new string - * - */ -static char* -xml_merge_attqname(struct xml_parse_yacc_arg *ya, - char *prefix, - char *name) -{ - char *str; - int len = strlen(prefix)+strlen(name)+2; - - if ((str=malloc(len)) == NULL) - return NULL; - snprintf(str, len, "%s:%s", prefix, name); - free(prefix); - free(name); - return str; -} - - %} %% @@ -344,9 +354,9 @@ element : '<' qname attrs element1 { clicon_debug(3, "element -> < qname attrs element1"); } ; -qname : NAME { if (xml_parse_qname(_YA, NULL, $1) < 0) YYABORT; +qname : NAME { if (xml_parse_unprefixed_name(_YA, $1) < 0) YYABORT; clicon_debug(3, "qname -> NAME %s", $1);} - | NAME ':' NAME { if (xml_parse_qname(_YA, $1, $3) < 0) YYABORT; + | NAME ':' NAME { if (xml_parse_prefixed_name(_YA, $1, $3) < 0) YYABORT; clicon_debug(3, "qname -> NAME : NAME");} ; @@ -385,15 +395,10 @@ attrs : attrs attr | ; -attr : attqname '=' attvalue { if (xml_parse_attr(_YA, $1, $3) < 0) YYABORT; } +attr : NAME '=' attvalue { if (xml_parse_attr(_YA, NULL, $1, $3) < 0) YYABORT; } + | NAME ':' NAME '=' attvalue { if (xml_parse_attr(_YA, $1, $3, $5) < 0) YYABORT; } ; -attqname : NAME {$$ = $1;} - | NAME ':' NAME - { if (($$ = xml_merge_attqname(_YA, $1, $3)) == NULL) YYABORT; } - ; - - attvalue : '\"' CHARDATA '\"' { $$=$2; /* $2 must be consumed */} | '\"' '\"' { $$=strdup(""); /* $2 must be consumed */} ; diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c new file mode 100644 index 00000000..173b30e5 --- /dev/null +++ b/lib/src/clixon_xpath.c @@ -0,0 +1,1364 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, indicate + your decision by deleting the provisions above and replace them with the + notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10 + */ +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include "clixon_err.h" +#include "clixon_log.h" +#include "clixon_string.h" +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_xsl.h" +#include "clixon_xpath_parse.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" + +/* + * Variables + */ +/* Mapping between XPATH operator string <--> int */ +const map_str2int xpopmap[] = { + {"and", XO_AND}, + {"or", XO_OR}, + {"div", XO_DIV}, + {"mod", XO_MOD}, + {"+", XO_ADD}, + {"*", XO_MULT}, + {"-", XO_SUB}, + {"=", XO_EQ}, + {"!=", XO_NE}, + {">=", XO_GE}, + {"<=", XO_LE}, + {"<", XO_LT}, + {">", XO_GT}, + {"|", XO_UNION}, + {NULL, -1} +}; + +/* Mapping between axis type string <--> int */ +static const map_str2int axismap[] = { + {"self", A_SELF}, + {"child", A_CHILD}, + {"parent", A_PARENT}, + {"root", A_ROOT}, + {"ancestor", A_ANCESTOR}, + {"descendant-or-self", A_DESCENDANT_OR_SELF}, + {NULL, -1} +}; + +static const map_str2int xpath_tree_map[] = { + {"expr", XP_EXP}, + {"andexpr", XP_AND}, + {"relexpr", XP_RELEX}, + {"addexpr", XP_ADD}, + {"unionexpr", XP_UNION}, + {"pathexpr", XP_PATHEXPR}, + {"locationpath", XP_LOCPATH}, + {"abslocpath", XP_ABSPATH}, + {"rellocpath", XP_RELLOCPATH}, + {"step", XP_STEP}, + {"nodetest", XP_NODE}, + {"nodetest fn", XP_NODE_FN}, + {"predicates", XP_PRED}, + {"primaryexpr", XP_PRI0}, + {"primaryexpr nr", XP_PRIME_NR}, + {"primaryexpr str", XP_PRIME_STR}, + {"primaryexpr fn", XP_PRIME_FN}, + {NULL, -1} +}; + +/* + * XPATH parse tree type + */ +/*! Print XPATH parse tree */ +static int +xpath_tree_print0(cbuf *cb, + xpath_tree *xs, + int level) +{ + cprintf(cb, "%*s%s:", level*3, "", clicon_int2str(xpath_tree_map, xs->xs_type)); + if (xs->xs_s0){ + cprintf(cb, "\"%s\" ", xs->xs_s0); + if (xs->xs_s1) + cprintf(cb,"\"%s\" ", xs->xs_s1); + } + cprintf(cb, "\n"); + if (xs->xs_c0) + xpath_tree_print0(cb, xs->xs_c0,level+1); + if (xs->xs_c1) + xpath_tree_print0(cb, xs->xs_c1, level+1); + return 0; +} + +static int +xpath_tree_print(cbuf *cb, + xpath_tree *xs) +{ + xpath_tree_print0(cb, xs, 0); + return 0; +} + +static int +xpath_tree_free( + xpath_tree *xs) +{ + if (xs->xs_s0) + free(xs->xs_s0); + if (xs->xs_s1) + free(xs->xs_s1); + if (xs->xs_c0) + xpath_tree_free(xs->xs_c0); + if (xs->xs_c1) + xpath_tree_free(xs->xs_c1); + free(xs); + return 0; +} + +/*! Make a nodetest + * @param[in] xs XPATH stack of type XP_NODE or XP_NODE_FN + * @retval 0 Match + * @retval 1 No match + * - node() is true for any node of any type whatsoever. + * - text() is true for any text node. + */ +static int +nodetest_eval(cxobj *x, + xpath_tree *xs) +{ + char *fn; + + if (xs->xs_type == XP_NODE){ + /* Namespaces is s0, name is s1 */ + if (strcmp(xs->xs_s1, "*")==0) + return 1; + else if (strcmp(xml_name(x), xs->xs_s1)==0) + return 1; + else + return 0; + } + else if (xs->xs_type == XP_NODE_FN){ + fn = xs->xs_s0; + if (strcmp(fn, "node")==0) + return 1; + else if (strcmp(fn, "text")==0) + return 1; + } + return 0; +} + +int +nodetest_recursive(cxobj *xn, + xpath_tree *nodetest, + int node_type, + uint16_t flags, + cxobj ***vec0, + size_t *vec0len) +{ + int retval = -1; + cxobj *xsub; + cxobj **vec = *vec0; + size_t veclen = *vec0len; + + xsub = NULL; + while ((xsub = xml_child_each(xn, xsub, node_type)) != NULL) { + if (nodetest_eval(xsub, nodetest) == 1){ + clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(xsub, flags)); + if (flags==0x0 || xml_flag(xsub, flags)) + if (cxvec_append(xsub, &vec, &veclen) < 0) + goto done; + // continue; /* Dont go deeper */ + } + if (nodetest_recursive(xsub, nodetest, node_type, flags, &vec, &veclen) < 0) + goto done; + } + retval = 0; + *vec0 = vec; + *vec0len = veclen; + done: + return retval; +} + +static int xp_eval(xp_ctx *xc, xpath_tree *xs, xp_ctx **xrp); + +/*! Evaluate xpath step rule of an XML tree + * + * @param[in] xc0 Incoming context + * @param[in] xs XPATH node tree + * @param[out] xrp Resulting context + * + * - A node test that is a QName is true if and only if the type of the node (see [5 Data Model]) + * is the principal node type and has an expanded-name equal to the expanded-name specified by the QName. + * - A node test * is true for any node of the principal node type. + * - node() is true for any node of any type whatsoever. + * - text() is true for any text node. + */ +static int +xp_eval_step(xp_ctx *xc0, + xpath_tree *xs, + xp_ctx **xrp) +{ + int retval = -1; + int i; + cxobj *x; + cxobj *xv; + cxobj *xp; + cxobj **vec = NULL; + size_t veclen = 0; + xpath_tree *nodetest = xs->xs_c0; + xp_ctx *xc = NULL; + + /* Create new xc */ + if ((xc = ctx_dup(xc0)) == NULL) + goto done; + switch (xs->xs_int){ + case A_ANCESTOR: + break; + case A_ANCESTOR_OR_SELF: + break; + case A_ATTRIBUTE: /* principal node type is attribute */ + break; + case A_CHILD: + if (xc->xc_descendant){ + for (i=0; ixc_size; i++){ + xv = xc->xc_nodeset[i]; + if (nodetest_recursive(xv, nodetest, CX_ELMNT, 0x0, &vec, &veclen) < 0) + goto done; + } + xc->xc_descendant = 0; + } + else{ + if (nodetest->xs_type==XP_NODE_FN && + nodetest->xs_s0 && + strcmp(nodetest->xs_s0,"current")==0){ + if (cxvec_append(xc->xc_initial, &vec, &veclen) < 0) + goto done; + } + else for (i=0; ixc_size; i++){ + xv = xc->xc_nodeset[i]; + x = NULL; + while ((x = xml_child_each(xv, x, CX_ELMNT)) != NULL) { + /* xs->xs_c0 is nodetest */ + if (nodetest == NULL || nodetest_eval(x, nodetest)) + if (cxvec_append(x, &vec, &veclen) < 0) + goto done; + } + } + } + ctx_nodeset_replace(xc, vec, veclen); + break; + case A_DESCENDANT: + case A_DESCENDANT_OR_SELF: + for (i=0; ixc_size; i++){ + xv = xc->xc_nodeset[i]; + if (nodetest_recursive(xv, xs->xs_c0, CX_ELMNT, 0x0, &vec, &veclen) < 0) + goto done; + } + ctx_nodeset_replace(xc, vec, veclen); + break; + case A_FOLLOWING: + break; + case A_FOLLOWING_SIBLING: + break; + case A_NAMESPACE: /* principal node type is namespace */ + break; + case A_PARENT: + veclen = xc->xc_size; + vec = xc->xc_nodeset; + xc->xc_size = 0; + xc->xc_nodeset = NULL; + for (i=0; ixc_nodeset, &xc->xc_size) < 0) + goto done; + } + if (vec){ + free(vec); + vec = NULL; + } + break; + case A_PRECEEDING: + break; + case A_PRECEEDING_SIBLING: + break; + case A_SELF: + break; + default: + clicon_err(OE_XML, 0, "No such axisname: %d", xs->xs_int); + goto done; + break; + } + if (xs->xs_c1){ + if (xp_eval(xc, xs->xs_c1, xrp) < 0) + goto done; + } + else{ + *xrp = xc; + xc = NULL; + } + assert(*xrp); + retval = 0; + done: + if (xc) + ctx_free(xc); + return retval; +} + +/*! Evaluate xpath predicates rule + * + * pred -> pred expr + * @param[in] xc Incoming context + * @param[in] xs XPATH node tree + * @param[out] xrp Resulting context + * + * A predicate filters a node-set with respect to an axis to produce a new + * node-set. For each node in the node-set to be filtered, the PredicateExpr is + * evaluated with that node as the context node, with the number of nodes in + * the node-set as the context size, and with the proximity position of the node + * in the node-set with respect to the axis as the context position; if + * PredicateExpr evaluates to true for that node, the node is included in the + * new node-set; otherwise, it is not included. + * A PredicateExpr is evaluated by evaluating the Expr and converting the result + * to a boolean. If the result is a + * - number, the result will be converted to true if the number is equal to the + * context position and will be converted to false otherwise; + * - if the result is not a number, then the result will be converted as if by a + * call to the boolean function. + * Thus a location path para[3] is equivalent to para[position()=3]. + */ +static int +xp_eval_predicate(xp_ctx *xc, + xpath_tree *xs, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr0 = NULL; + xp_ctx *xr1 = NULL; + xp_ctx *xrc = NULL; + int i; + cxobj *x; + xp_ctx *xcc; + + if (xs->xs_c0 == NULL){ /* empty */ + if ((xr0 = ctx_dup(xc)) == NULL) + goto done; + } + else{ /* eval previous predicates */ + if (xp_eval(xc, xs->xs_c0, &xr0) < 0) + goto done; + } + if (xs->xs_c1){ + /* Loop over each node in the nodeset */ + assert (xr0->xc_type == XT_NODESET); + if ((xr1 = malloc(sizeof(*xr1))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr1, 0, sizeof(*xr1)); + xr1->xc_type = XT_NODESET; + xr1->xc_node = xc->xc_node; + xr1->xc_initial = xc->xc_initial; + for (i=0; ixc_size; i++){ + x = xr0->xc_nodeset[i]; + /* Create new context */ + if ((xcc = malloc(sizeof(*xcc))) == NULL){ + clicon_err(OE_XML, errno, "malloc"); + goto done; + } + memset(xcc, 0, sizeof(*xcc)); + xcc->xc_type = XT_NODESET; + xcc->xc_initial = xc->xc_initial; + xcc->xc_node = x; + /* For each node in the node-set to be filtered, the PredicateExpr is + * evaluated with that node as the context node */ + if (cxvec_append(x, &xcc->xc_nodeset, &xcc->xc_size) < 0) + goto done; + if (xp_eval(xcc, xs->xs_c1, &xrc) < 0) + goto done; + if (xcc) + ctx_free(xcc); + if (xrc->xc_type == XT_NUMBER){ + /* If the result is a number, the result will be converted to true + if the number is equal to the context position */ + if ((int)xrc->xc_number == i) + if (cxvec_append(x, &xr1->xc_nodeset, &xr1->xc_size) < 0) + goto done; + } + else { + /* if PredicateExpr evaluates to true for that node, the node is + included in the new node-set */ + if (ctx2boolean(xrc)) + if (cxvec_append(x, &xr1->xc_nodeset, &xr1->xc_size) < 0) + goto done; + } + if (xrc) + ctx_free(xrc); + } + + } + assert(xr0||xr1); + if (xr1){ + *xrp = xr1; + xr1 = NULL; + } + else + if (xr0){ + *xrp = xr0; + xr0 = NULL; + } + retval = 0; + done: + assert(retval==0); + if (xr0) + ctx_free(xr0); + if (xr1) + ctx_free(xr1); + return retval; +} + +/*! Given two XPATH contexts, eval logical operations: or,and + * The logical operators convert their operands to booleans + * @param[in] xc1 Context of operand1 + * @param[in] xc2 Context of operand2 + * @param[in] op Relational operator + * @param[out] xrp Result context + * @retval 0 OK + * @retval -1 Error + */ +static int +xp_logop(xp_ctx *xc1, + xp_ctx *xc2, + enum xp_op op, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr = NULL; + int b1; + int b2; + + if ((xr = malloc(sizeof(*xr))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr, 0, sizeof(*xr)); + xr->xc_initial = xc1->xc_initial; + xr->xc_type = XT_BOOL; + if ((b1 = ctx2boolean(xc1)) < 0) + goto done; + if ((b2 = ctx2boolean(xc2)) < 0) + goto done; + switch (op){ + case XO_AND: + xr->xc_bool = b1 && b2; + break; + case XO_OR: + xr->xc_bool = b1 || b2; + break; + default: + clicon_err(OE_UNIX, errno, "%s:Invalid operator %s in this context", + __FUNCTION__, clicon_int2str(xpopmap,op)); + goto done; + } + *xrp = xr; + retval = 0; + done: + return retval; +} + +/*! Given two XPATH contexts, eval numeric operations: +-*,div,mod + * The numeric operators convert their operands to numbers as if by + * calling the number function. + * @param[in] xc1 Context of operand1 + * @param[in] xc2 Context of operand2 + * @param[in] op Relational operator + * @param[out] xrp Result context + * @retval 0 OK + * @retval -1 Error + */ +static int +xp_numop(xp_ctx *xc1, + xp_ctx *xc2, + enum xp_op op, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr = NULL; + double n1; + double n2; + + if ((xr = malloc(sizeof(*xr))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr, 0, sizeof(*xr)); + xr->xc_initial = xc1->xc_initial; + xr->xc_type = XT_NUMBER; + if (ctx2number(xc1, &n1) < 0) + goto done; + if (ctx2number(xc2, &n2) < 0) + goto done; + if (isnan(n1) || isnan(n2)) + xr->xc_number = NAN; + else + switch (op){ + case XO_DIV: + xr->xc_number = n1/n2; + break; + case XO_MOD: + xr->xc_number = ((int)n1)%((int)n2); + break; + case XO_ADD: + xr->xc_number = n1+n2; + break; + case XO_MULT: + xr->xc_number = n1*n2; + break; + case XO_SUB: + xr->xc_number = n1-n2; + break; + default: + clicon_err(OE_UNIX, errno, "Invalid operator %s in this context", + clicon_int2str(xpopmap,op)); + goto done; + } + *xrp = xr; + retval = 0; + done: + return retval; +} + +/*! Given two XPATH contexts, eval relational operations: <>= + * A RelationalExpr is evaluated by comparing the objects that result from + * evaluating the two operands. + * This is covered: + * (a) Both are INTs, BOOLs, STRINGs. Result type is boolean + * (b) Both are nodesets and one is empty. Result type is boolean. + * (c) One is nodeset and other is INT or STRING. Result type is nodeset + * (d) All others (eg two nodesets, BOOL+STRING) are not supported. + * Op is = EQ + * From XPATH 1.0 standard, the evaluation has three variants: + * (1) comparisons that involve node-sets are defined in terms of comparisons that + * do not involve node-sets; this is defined uniformly for =, !=, <=, <, >= and >. + * (2) comparisons that do not involve node-sets are defined for = and !=. + * (3) comparisons that do not involve node-sets are defined for <=, <, >= and >. + * @param[in] xc1 Context of operand1 + * @param[in] xc2 Context of operand2 + * @param[in] op Relational operator + * @param[out] xrp Result context + * @retval 0 OK + * @retval -1 Error + */ +static int +xp_relop(xp_ctx *xc1, + xp_ctx *xc2, + enum xp_op op, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr = NULL; + xp_ctx *xc; + cxobj *x; + int i; + int j; + int b; + char *s1; + char *s2; + int reverse = 0; + double n1, n2; + + if ((xr = malloc(sizeof(*xr))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr, 0, sizeof(*xr)); + xr->xc_initial = xc1->xc_initial; + xr->xc_type = XT_BOOL; + if (xc1->xc_type == xc2->xc_type){ /* cases (2-3) above */ + switch (xc1->xc_type){ + case XT_NODESET: + /* If both are node-sets, then it is true iff the string value of one + node in the first node-set and one in the second node-set is true */ + for (i=0; ixc_size; i++){ + if ((s1 = xml_body(xc1->xc_nodeset[i])) == NULL){ + xr->xc_bool = 0; + goto ok; + } + for (j=0; jxc_size; j++){ + if ((s2 = xml_body(xc2->xc_nodeset[j])) == NULL){ + xr->xc_bool = 0; + goto ok; + } + switch(op){ + case XO_EQ: + xr->xc_bool = (strcmp(s1, s2)==0); + break; + case XO_NE: + xr->xc_bool = (strcmp(s1, s2)!=0); + break; + case XO_GE: + xr->xc_bool = (strcmp(s1, s2)>=0); + break; + case XO_LE: + xr->xc_bool = (strcmp(s1, s2)<=0); + break; + case XO_LT: + xr->xc_bool = (strcmp(s1, s2)<0); + break; + case XO_GT: + xr->xc_bool = (strcmp(s1, s2)>0); + break; + default: + clicon_err(OE_XML, 0, "Operator %s not supported for nodeset/nodeset comparison", clicon_int2str(xpopmap,op)); + goto done; + break; + } + if (xr->xc_bool) /* enough to find a single node */ + break; + } + if (xr->xc_bool) /* enough to find a single node */ + break; + } + break; + case XT_BOOL: + xr->xc_bool = (xc1->xc_bool == xc2->xc_bool); + break; + case XT_NUMBER: + xr->xc_bool = (xc1->xc_number == xc2->xc_number); + break; + case XT_STRING: + xr->xc_bool = (strcmp(xc1->xc_string, xc2->xc_string)==0); + break; + } + } + else if (xc1->xc_type != XT_NODESET && + xc2->xc_type != XT_NODESET){ + clicon_err(OE_XML, 0, "Mixed types not supported, %d %d", xc1->xc_type, xc2->xc_type); + goto done; + } + else{ /* one is nodeset, ie (1) above */ + if (xc2->xc_type == XT_NODESET){ + xc = xc2; + xc2 = xc1; + xc1 = xc; + reverse++; /* reverse */ + } + /* xc1 is nodeset + * xc2 is something else */ + switch (xc2->xc_type){ + case XT_BOOL: + /* comparison on the boolean and the result of converting the + node-set to a boolean using the boolean function is true. */ + b = ctx2boolean(xc1); + switch(op){ + case XO_EQ: + xr->xc_bool = (b == xc2->xc_bool); + break; + case XO_NE: + xr->xc_bool = (b != xc2->xc_bool); + break; + default: + clicon_err(OE_XML, 0, "Operator %s not supported for nodeset and bool", clicon_int2str(xpopmap,op)); + goto done; + break; + } /* switch op */ + break; + case XT_STRING: + /* If one object to be compared is a node-set and the + other is a string, then the comparison will be true if and only + if there is a node in the node-set such that the result of + performing the comparison on the string-value of the node and + the other string is true.*/ + s2 = xc2->xc_string; + for (i=0; ixc_size; i++){ + x = xc1->xc_nodeset[i]; /* node in nodeset */ + s1 = xml_body(x); + switch(op){ + case XO_EQ: + xr->xc_bool = (strcmp(s1, s2)==0); + break; + case XO_NE: + xr->xc_bool = (strcmp(s1, s2)); + break; + default: + clicon_err(OE_XML, 0, "Operator %s not supported for nodeset and string", clicon_int2str(xpopmap,op)); + goto done; + break; + } + if (xr->xc_bool) /* enough to find a single node */ + break; + } + break; + case XT_NUMBER: + for (i=0; ixc_size; i++){ + x = xc1->xc_nodeset[i]; /* node in nodeset */ + if (sscanf(xml_body(x), "%lf", &n1) != 1) + n1 = NAN; + n2 = xc2->xc_number; + switch(op){ + case XO_EQ: + xr->xc_bool = (n1 == n2); + break; + case XO_NE: + xr->xc_bool = (n1 != n2); + break; + case XO_GE: + xr->xc_bool = reverse?(n2 >= n1):(n1 >= n2); + break; + case XO_LE: + xr->xc_bool = reverse?(n2 <= n1):(n1 <= n2); + break; + case XO_LT: + xr->xc_bool = reverse?(n2 < n1):(n1 < n2); + break; + case XO_GT: + xr->xc_bool = reverse?(n2 > n1):(n1 > n2); + break; + default: + clicon_err(OE_XML, 0, "Operator %s not supported for nodeset and number", clicon_int2str(xpopmap,op)); + goto done; + break; + } + if (xr->xc_bool) /* enough to find a single node */ + break; + } + break; + default: + clicon_err(OE_XML, 0, "Type %d not supported", xc2->xc_type); + } /* switch type */ + } + ok: + /* Just ensure bool is 0 or 1 */ + if (xr->xc_type == XT_BOOL && xr->xc_bool != 0) + xr->xc_bool = 1; + *xrp = xr; + retval = 0; + done: + return retval; +} + +/*! Given two XPATH contexts, eval union operation + * Both operands must be nodesets, otherwise empty nodeset is returned + * @param[in] xc1 Context of operand1 + * @param[in] xc2 Context of operand2 + * @param[in] op Relational operator + * @param[out] xrp Result context + * @retval 0 OK + * @retval -1 Error + */ +static int +xp_union(xp_ctx *xc1, + xp_ctx *xc2, + enum xp_op op, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr = NULL; + int i; + + if (op != XO_UNION){ + clicon_err(OE_UNIX, errno, "%s:Invalid operator %s in this context", + __FUNCTION__, clicon_int2str(xpopmap,op)); + goto done; + } + if ((xr = malloc(sizeof(*xr))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr, 0, sizeof(*xr)); + xr->xc_initial = xc1->xc_initial; + xr->xc_type = XT_NODESET; + + for (i=0; ixc_size; i++) + if (cxvec_append(xc1->xc_nodeset[i], &xr->xc_nodeset, &xr->xc_size) < 0) + goto done; + for (i=0; ixc_size; i++){ + if (cxvec_append(xc2->xc_nodeset[i], &xr->xc_nodeset, &xr->xc_size) < 0) + goto done; + } + *xrp = xr; + retval = 0; + done: + return retval; +} + + +/*! Evaluate an XPATH on an XML tree + + * The initial sequence of steps selects a set of nodes relative to a context node. + * Each node in that set is used as a context node for the following step. + * @param[in] xc Incoming context + * @param[in] xs XPATH node tree + * @param[out] xrp Resulting context + */ +static int +xp_eval(xp_ctx *xc, + xpath_tree *xs, + xp_ctx **xrp) + +{ + int retval = -1; + cxobj *x; + xp_ctx *xr0 = NULL; + xp_ctx *xr1 = NULL; + xp_ctx *xr2 = NULL; + int use_xr0 = 0; /* In 2nd child use transitively result of 1st child */ + + if (debug){ + cbuf *cb; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + ctx_print(cb, +2, xc, (char*)clicon_int2str(xpath_tree_map, xs->xs_type)); + clicon_debug(1, "%s", cbuf_get(cb)); + cbuf_free(cb); + } + /* Pre-actions before check first child c0 + */ + switch (xs->xs_type){ + case XP_RELLOCPATH: + if (xs->xs_int == A_DESCENDANT_OR_SELF) + xc->xc_descendant = 1; /* XXX need to set to 0 in sub */ + break; + case XP_ABSPATH: + /* Set context node to top node, and nodeset to that node only */ + x = xc->xc_node; + while (xml_parent(x) != NULL) + x = xml_parent(x); + xc->xc_node = x; + xc->xc_nodeset[0] = x; + xc->xc_size=1; + /* // is short for /descendant-or-self::node()/ */ + if (xs->xs_int == A_DESCENDANT_OR_SELF) + xc->xc_descendant = 1; /* XXX need to set to 0 in sub */ + + break; + case XP_STEP: /* XP_NODE is first argument -not called explicitly */ + if (xp_eval_step(xc, xs, xrp) < 0) + goto done; + goto ok; + break; + case XP_PRED: + if (xp_eval_predicate(xc, xs, xrp) < 0) + goto done; + goto ok; + break; + default: + break; + } + /* Eval first child c0 + */ + if (xs->xs_c0){ + if (xp_eval(xc, xs->xs_c0, &xr0) < 0) + goto done; + } + /* Actions between first and second child + */ + switch (xs->xs_type){ + case XP_EXP: + break; + case XP_AND: + break; + case XP_RELEX: /* relexpr --> addexpr | relexpr relop addexpr */ + break; + case XP_ADD: /* combine mult and add ops */ + break; + case XP_UNION: + break; + case XP_PATHEXPR: + break; + case XP_LOCPATH: + break; + case XP_ABSPATH: + use_xr0++; + /* Special case, no c0 or c1, single "/" */ + if (xs->xs_c0 == NULL){ + if ((xr0 = malloc(sizeof(*xr0))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr0, 0, sizeof(*xr0)); + xr0->xc_initial = xc->xc_initial; + xr0->xc_type = XT_NODESET; + x = NULL; + while ((x = xml_child_each(xc->xc_node, x, CX_ELMNT)) != NULL) { + if (cxvec_append(x, &xr0->xc_nodeset, &xr0->xc_size) < 0) + goto done; + } + } + break; + case XP_RELLOCPATH: + use_xr0++; + if (xs->xs_int == A_DESCENDANT_OR_SELF) + xc->xc_descendant = 1; /* XXX need to set to 0 in sub */ + break; + case XP_NODE: + break; + case XP_NODE_FN: + break; + case XP_PRI0: + break; + case XP_PRIME_NR: /* primaryexpr -> [] */ + if ((xr0 = malloc(sizeof(*xr0))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr0, 0, sizeof(*xr0)); + xr0->xc_initial = xc->xc_initial; + xr0->xc_type = XT_NUMBER; + xr0->xc_number = xs->xs_double; + break; + case XP_PRIME_STR: + if ((xr0 = malloc(sizeof(*xr0))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr0, 0, sizeof(*xr0)); + xr0->xc_initial = xc->xc_initial; + xr0->xc_type = XT_STRING; + xr0->xc_string = xs->xs_s0?strdup(xs->xs_s0):NULL; + break; + case XP_PRIME_FN: + break; + default: + break; + } + /* Eval second child c0 + * Note, some operators 8like locationpath, need transitive context (use_xr0) + */ + if (xs->xs_c1) + if (xp_eval(use_xr0?xr0:xc, xs->xs_c1, &xr1) < 0) + goto done; + /* Actions after second child + */ + if (xs->xs_c1) + switch (xs->xs_type){ + case XP_AND: /* combine and and or ops */ + if (xp_logop(xr0, xr1, xs->xs_int, &xr2) < 0) + goto done; + break; + case XP_RELEX: /* relexpr --> addexpr | relexpr relop addexpr */ + if (xp_relop(xr0, xr1, xs->xs_int, &xr2) < 0) + goto done; + break; + case XP_ADD: /* combine mult and add ops */ + if (xp_numop(xr0, xr1, xs->xs_int, &xr2) < 0) + goto done; + break; + case XP_UNION: /* combine and and or ops */ + if (xp_union(xr0, xr1, xs->xs_int, &xr2) < 0) + goto done; + default: + break; + } + xc->xc_descendant = 0; + assert(xr0||xr1||xr2); + if (xr2){ + *xrp = xr2; + xr2 = NULL; + } + else if (xr1){ + *xrp = xr1; + xr1 = NULL; + } + else + if (xr0){ + *xrp = xr0; + xr0 = NULL; + } + ok: + if (debug){ + cbuf *cb; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + ctx_print(cb, -2, *xrp, (char*)clicon_int2str(xpath_tree_map, xs->xs_type)); + clicon_debug(1, "%s", cbuf_get(cb)); + cbuf_free(cb); + } + retval = 0; + done: + if (xr2) + ctx_free(xr2); + if (xr1) + ctx_free(xr1); + if (xr0) + ctx_free(xr0); + return retval; +} + +/*! Given XML tree and xpath, returns xpath context + * @param[in] xcur XML-tree where to search + * @param[in] xpath String with XPATH 1.0 syntax + * @param[out] xrp Return XPATH context + * @retval 0 OK + * @retval -1 Error + */ +int +xpath_vec_ctx(cxobj *xcur, + char *xpath, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx xc = {0,}; + struct clicon_xpath_yacc_arg xy = {0,}; + + xy.xy_parse_string = xpath; + xy.xy_name = "xpath parser"; + xy.xy_linenum = 1; + if (xpath_scan_init(&xy) < 0) + goto done; + if (xpath_parse_init(&xy) < 0) + goto done; + if (clixon_xpath_parseparse(&xy) != 0) { /* yacc returns 1 on error */ + clicon_log(LOG_NOTICE, "XPATH error: on line %d", xy.xy_linenum); + if (clicon_errno == 0) + clicon_err(OE_XML, 0, "XPATH parser error with no error code (should not happen)"); + goto done; + } + if (debug){ + cbuf *cb = cbuf_new(); + xpath_tree_print(cb, xy.xy_top); + clicon_debug(1, "xpath parse tree:\n%s", cbuf_get(cb)); + cbuf_free(cb); + } + xc.xc_type = XT_NODESET; + xc.xc_node = xcur; + xc.xc_initial = xcur; + if (cxvec_append(xcur, &xc.xc_nodeset, &xc.xc_size) < 0) + goto done; + if (xp_eval(&xc, xy.xy_top, xrp) < 0) + goto done; + if (xc.xc_nodeset){ + free(xc.xc_nodeset); + xc.xc_nodeset = NULL; + } + /* done: */ + xpath_parse_exit(&xy); + xpath_scan_exit(&xy); + retval = 0; + done: + if (xy.xy_top) + xpath_tree_free(xy.xy_top); + return retval; +} + +/*! Xpath nodeset function where only the first matching entry is returned + * args: + * @param[in] xcur xml-tree where to search + * @param[in] xpath string with XPATH syntax + * @retval xml-tree of first match + * @retval NULL Error or not found + * + * @code + * cxobj *x; + * if ((x = xpath_first(xtop, "//symbol/foo")) != NULL) { + * ... + * } + * @endcode + * @note the returned pointer points into the original tree so should not be freed fter use. + * @note return value does not see difference between error and not found + * @see also xpath_vec. + */ +cxobj * +xpath_first(cxobj *xcur, + char *format, + ...) +{ + cxobj *cx = NULL; + va_list ap; + size_t len; + char *xpath = NULL; + xp_ctx *xr = NULL; + + va_start(ap, format); + len = vsnprintf(NULL, 0, format, ap); + va_end(ap); + /* allocate a message string exactly fitting the message length */ + if ((xpath = malloc(len+1)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + /* second round: compute write message from reason and args */ + va_start(ap, format); + if (vsnprintf(xpath, len+1, format, ap) < 0){ + clicon_err(OE_UNIX, errno, "vsnprintf"); + va_end(ap); + goto done; + } + va_end(ap); +#ifdef COMPAT_XSL + if ((cx = xpath_first_xsl(xcur, xpath)) == NULL) + goto done; +#else + if (xpath_vec_ctx(xcur, xpath, &xr) < 0) + goto done; + + if (xr && xr->xc_type == XT_NODESET && xr->xc_size) + cx = xr->xc_nodeset[0]; +#endif + done: + if (xr) + ctx_free(xr); + if (xpath) + free(xpath); + return cx; +} + +/*! Given XML tree and xpath, returns nodeset as xml node vector + * If result is not nodeset, return empty nodeset + * @param[in] xcur xml-tree where to search + * @param[in] xpath stdarg string with XPATH 1.0 syntax + * @param[out] vec vector of xml-trees. Vector must be free():d after use + * @param[out] veclen returns length of vector in return value + * @retval 0 OK + * @retval -1 Error + * @code + * cxobj **vec; + * size_t veclen; + * if (xpath_vec(xcur, "//symbol/foo", &vec, &veclen) < 0) + * goto err; + * for (i=0; ixc_type == XT_NODESET){ + *vec = xr->xc_nodeset; + xr->xc_nodeset = NULL; + *veclen = xr->xc_size; + } +#endif + retval = 0; + done: + if (xr) + ctx_free(xr); + if (xpath) + free(xpath); + return retval; +} + +/* Xpath that returns a vector of matches (only nodes marked with flags) + * @param[in] xcur xml-tree where to search + * @param[in] xpath string with XPATH syntax + * @param[in] flags Set of flags that return nodes must match (0 if all) + * @param[out] vec vector of xml-trees. Vector must be free():d after use + * @param[out] veclen returns length of vector in return value + * @retval 0 OK + * @retval -1 error. + * @code + * cxobj **vec; + * size_t veclen; + * if (xpath_vec_flag(xcur, "//symbol/foo", XML_FLAG_ADD, &vec, &veclen) < 0) + * goto err; + * for (i=0; ixc_type == XT_NODESET){ + for (i=0; ixc_size; i++){ + x = xr->xc_nodeset[i]; + if (flags==0x0 || xml_flag(x, flags)) + if (cxvec_append(x, vec, veclen) < 0) + goto done; + } + } +#endif + + retval = 0; + done: + if (xr) + ctx_free(xr); + if (xpath) + free(xpath); + return retval; +} + +/*! Given XML tree and xpath, returns boolean + * @param[in] xcur xml-tree where to search + * @param[in] xpath stdarg string with XPATH 1.0 syntax + * @retval 1 True + * @retval 0 False + * @retval -1 Error + */ +int +xpath_vec_bool(cxobj *xcur, + char *format, + ...) +{ + int retval = -1; + va_list ap; + size_t len; + char *xpath = NULL; + xp_ctx *xr = NULL; + + va_start(ap, format); + len = vsnprintf(NULL, 0, format, ap); + va_end(ap); + /* allocate a message string exactly fitting the message length */ + if ((xpath = malloc(len+1)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + /* second round: compute write message from reason and args */ + va_start(ap, format); + if (vsnprintf(xpath, len+1, format, ap) < 0){ + clicon_err(OE_UNIX, errno, "vsnprintf"); + va_end(ap); + goto done; + } + va_end(ap); + if (xpath_vec_ctx(xcur, xpath, &xr) < 0) + goto done; + if (xr) + retval = ctx2boolean(xr); + done: + if (xr) + ctx_free(xr); + if (xpath) + free(xpath); + return retval; +} + diff --git a/lib/src/clixon_xpath_ctx.c b/lib/src/clixon_xpath_ctx.c new file mode 100644 index 00000000..721df45c --- /dev/null +++ b/lib/src/clixon_xpath_ctx.c @@ -0,0 +1,294 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, indicate + your decision by deleting the provisions above and replace them with the + notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10 + * This file defines XPATH contexts using in traversing the XPATH parse tree. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include "clixon_err.h" +#include "clixon_log.h" +#include "clixon_string.h" +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_xpath_parse.h" +#include "clixon_xpath_ctx.h" + +/* + * Variables + */ +const map_str2int ctxmap[] = { + {"nodeset", XT_NODESET}, + {"bool", XT_BOOL}, + {"number", XT_NUMBER}, + {"string", XT_STRING}, + {NULL, -1} +}; + +/*! Free xpath context */ +int +ctx_free(xp_ctx *xc) +{ + if (xc->xc_nodeset) + free(xc->xc_nodeset); + if (xc->xc_string) + free(xc->xc_string); + free(xc); + return 0; +} + +/*! Duplicate xpath context */ +xp_ctx * +ctx_dup(xp_ctx *xc0) +{ + static xp_ctx *xc = NULL; + + if ((xc = malloc(sizeof(*xc))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xc, 0, sizeof(*xc)); + *xc = *xc0; + if (xc0->xc_size){ + if ((xc->xc_nodeset = calloc(xc0->xc_size, sizeof(cxobj*))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + goto done; + } + memcpy(xc->xc_nodeset, xc0->xc_nodeset, xc->xc_size*sizeof(cxobj*)); + } + if (xc0->xc_string) + if ((xc->xc_string = strdup(xc0->xc_string)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + done: + return xc; +} + +/*! Print XPATH context */ +int +ctx_print(cbuf *cb, + int id, + xp_ctx *xc, + char *str) +{ + static int ident = 0; + int i; + + if (id<0) + ident += id; + cprintf(cb, "%*s%s ", ident, "", str?str:""); + if (id>0) + ident += id; + if (xc){ + cprintf(cb, "%s: ", (char*)clicon_int2str(ctxmap, xc->xc_type)); + switch (xc->xc_type){ + case XT_NODESET: + for (i=0; ixc_size; i++) + cprintf(cb, "%s ", xml_name(xc->xc_nodeset[i])); + break; + case XT_BOOL: + cprintf(cb, "%s", xc->xc_bool?"true":"false"); + break; + case XT_NUMBER: + cprintf(cb, "%lf", xc->xc_number); + break; + case XT_STRING: + cprintf(cb, "%s", xc->xc_string); + break; + } + } + return 0; +} + +/*! Convert xpath context to boolean according to boolean() function in XPATH spec + * @param[in] xc XPATH context + * @retval 0 False + * @retval 1 True + * a number is true if and only if it is neither positive or negative zero nor NaN + * a node-set is true if and only if it is non-empty + * a string is true if and only if its length is non-zero + * an object of a type other than the four basic types is converted to a boolean + * in a way that is dependent on that type + */ +int +ctx2boolean(xp_ctx *xc) +{ + int b = -1; + switch (xc->xc_type){ + case XT_NODESET: + b = (xc->xc_size != 0); + break; + case XT_BOOL: + b = xc->xc_bool; + break; + case XT_NUMBER: + b = (xc->xc_number != 0.0 && xc->xc_number != NAN); + break; + case XT_STRING: + b = (xc->xc_string && strlen(xc->xc_string)); + break; + } + return b; +} + +/*! Convert xpath context to string according to string() function in XPATH spec + * @param[in] xc XPATH context + * @param[out] str0 Malloced result string + * @retval 0 OK + * @retval -1 Error + * @note string malloced. + */ +int +ctx2string(xp_ctx *xc, + char **str0) +{ + int retval = -1; + char *str = NULL; + int len; + char *b; + + switch (xc->xc_type){ + case XT_NODESET: + if (xc->xc_size && (b = xml_body(xc->xc_nodeset[0]))){ + if ((str = strdup(b)) == NULL){ + clicon_err(OE_XML, errno, "strdup"); + goto done; + } + } + else + if ((str = strdup("")) == NULL){ + clicon_err(OE_XML, errno, "strdup"); + goto done; + } + break; + case XT_BOOL: + if ((str = strdup(xc->xc_bool == 0?"false":"true")) == NULL){ + clicon_err(OE_XML, errno, "strdup"); + goto done; + } + break; + case XT_NUMBER: + len = snprintf(NULL, 0, "%0lf", xc->xc_number); + len++; + if ((str = malloc(len)) == NULL){ + clicon_err(OE_XML, errno, "malloc"); + goto done; + } + snprintf(str, len, "%0lf", xc->xc_number); + break; + case XT_STRING: + if ((str = strdup(xc->xc_string)) == NULL){ + clicon_err(OE_XML, errno, "strdup"); + goto done; + } + break; + } + *str0 = str; + retval = 0; + done: + return retval; +} + +/*! Convert xpath context to number according to number() function in XPATH spec + * @param[in] xc XPATH context + * @param[out] n0 Floating point or NAN + * @retval 0 OK + * @retval -1 Error + */ +int +ctx2number(xp_ctx *xc, + double *n0) +{ + int retval = -1; + char *str = NULL; + double n; + + switch (xc->xc_type){ + case XT_NODESET: + if (ctx2string(xc, &str) < 0) + goto done; + if (sscanf(str, "%lf",&n) != 1) + n = NAN; + break; + case XT_BOOL: + n = (double)xc->xc_bool; + break; + case XT_NUMBER: + n = xc->xc_number; + break; + case XT_STRING: + if (sscanf(xc->xc_string, "%lf",&n) != 1) + n = NAN; + break; + } + *n0 = n; + retval = 0; + done: + if (str) + free(str); + return retval; +} + +/*! Replace a nodeset of a XPATH context with a new nodeset + */ +int +ctx_nodeset_replace(xp_ctx *xc, + cxobj **vec, + size_t veclen) +{ + if (xc->xc_nodeset) + free(xc->xc_nodeset); + xc->xc_nodeset = vec; + xc->xc_size = veclen; + return 0; +} + diff --git a/lib/src/clixon_xpath_parse.h b/lib/src/clixon_xpath_parse.h new file mode 100644 index 00000000..1affce3c --- /dev/null +++ b/lib/src/clixon_xpath_parse.h @@ -0,0 +1,101 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + */ +#ifndef _CLIXON_XPATH_PARSE_H_ +#define _CLIXON_XPATH_PARSE_H_ + +/* + * Types + */ +/* used as non-terminal type in yacc rules */ +enum xp_type{ + XP_EXP, + XP_AND, + XP_RELEX, + XP_ADD, + XP_UNION, + XP_PATHEXPR, + XP_LOCPATH, + XP_ABSPATH, + XP_RELLOCPATH, + XP_STEP, + XP_NODE, + XP_NODE_FN, + XP_PRED, + XP_PRI0, + XP_PRIME_NR, + XP_PRIME_STR, + XP_PRIME_FN, +}; + +/*! XPATH Parsing generates a tree of nodes that is later traversed + */ +struct xpath_tree{ + enum xp_type xs_type; + int xs_int; + double xs_double; + char *xs_s0; + char *xs_s1; + struct xpath_tree *xs_c0; /* child 0 */ + struct xpath_tree *xs_c1; /* child 1 */ +}; +typedef struct xpath_tree xpath_tree; + +struct clicon_xpath_yacc_arg{ /* XXX: mostly unrelevant */ + const char *xy_name; /* Name of syntax (for error string) */ + int xy_linenum; /* Number of \n in parsed buffer */ + char *xy_parse_string; /* original (copy of) parse string */ + void *xy_lexbuf; /* internal parse buffer from lex */ + xpath_tree *xy_top; +}; + +/* + * Variables + */ +extern char *clixon_xpath_parsetext; + +/* + * Prototypes + */ +int xpath_scan_init(struct clicon_xpath_yacc_arg *jy); +int xpath_scan_exit(struct clicon_xpath_yacc_arg *jy); + +int xpath_parse_init(struct clicon_xpath_yacc_arg *jy); +int xpath_parse_exit(struct clicon_xpath_yacc_arg *jy); + +int clixon_xpath_parselex(void *); +int clixon_xpath_parseparse(void *); +void clixon_xpath_parseerror(void *, char*); + +#endif /* _CLIXON_XPATH_PARSE_H_ */ diff --git a/lib/src/clixon_xpath_parse.l b/lib/src/clixon_xpath_parse.l new file mode 100644 index 00000000..5ac54644 --- /dev/null +++ b/lib/src/clixon_xpath_parse.l @@ -0,0 +1,174 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + */ + +%{ + +#include "clixon_config.h" + +#include +#include +#include +#include +#include + +#include "clixon_xpath_parse.tab.h" /* generated */ + +#include + +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_log.h" +#include "clixon_string.h" +#include "clixon_xml.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" +#include "clixon_xpath_parse.h" + +/* Redefine main lex function so that you can send arguments to it: _yy is added to arg list */ +#define YY_DECL int clixon_xpath_parselex(void *_yy) + +/* Dont use input function (use user-buffer) */ +#define YY_NO_INPUT + +/* typecast macro */ +#define _XY ((struct clicon_xpath_yacc_arg *)_yy) + +#define MAXBUF 4*4*64*1024 + +#undef clixon_xpath_parsewrap +int +clixon_xpath_parsewrap(void) +{ + return 1; +} + + + +%} + +digit [0-9] +integer {digit}+ +real ({digit}+[.]{digit}*)|({digit}*[.]{digit}+) + +%x TOKEN +%s QLITERAL +%s ALITERAL + +%% +[ \t] +\n { _XY->xy_linenum++; } +\r { } +<> { return X_EOF; } +".." { return DOUBLEDOT; } +[()\[\]\.@,/:|] { return *yytext; } +"::" { return DOUBLECOLON; } +and { clixon_xpath_parselval.intval = clicon_str2int(xpopmap, yytext); return LOGOP; } +or { clixon_xpath_parselval.intval = clicon_str2int(xpopmap, yytext); return LOGOP; } +div { clixon_xpath_parselval.intval = clicon_str2int(xpopmap,yytext); return ADDOP; } +mod { clixon_xpath_parselval.intval = clicon_str2int(xpopmap,yytext); return ADDOP; } +[+*\-] { clixon_xpath_parselval.intval = clicon_str2int(xpopmap,yytext); return ADDOP; } +\? { return *yytext; } +"//" { return DOUBLESLASH; } +"!=" { clixon_xpath_parselval.intval = clicon_str2int(xpopmap,yytext); return RELOP; } +">=" { clixon_xpath_parselval.intval = clicon_str2int(xpopmap,yytext);return RELOP; } +"<=" { clixon_xpath_parselval.intval = clicon_str2int(xpopmap,yytext);return RELOP; } +[<>=] { clixon_xpath_parselval.intval = clicon_str2int(xpopmap,yytext);return RELOP; } +last { clixon_xpath_parselval.string = strdup(yytext); return FUNCTIONNAME; } +position { clixon_xpath_parselval.string = strdup(yytext); return FUNCTIONNAME; } +count { clixon_xpath_parselval.string = strdup(yytext); return FUNCTIONNAME; } +ancestor { clixon_xpath_parselval.intval = A_ANCESTOR; return AXISNAME; } +ancestor-or-self { clixon_xpath_parselval.intval = A_ANCESTOR_OR_SELF; return AXISNAME; } +attribute { clixon_xpath_parselval.intval = A_ATTRIBUTE; return AXISNAME; } +child { clixon_xpath_parselval.intval = A_CHILD; return AXISNAME; } +descendant { clixon_xpath_parselval.intval = A_DESCENDANT; return AXISNAME; } +descendant-or-self { clixon_xpath_parselval.intval = A_DESCENDANT_OR_SELF; return AXISNAME; } +following { clixon_xpath_parselval.intval = A_FOLLOWING; return AXISNAME; } +following-sibling { clixon_xpath_parselval.intval = A_FOLLOWING_SIBLING; return AXISNAME; } +namespace { clixon_xpath_parselval.intval = A_NAMESPACE; return AXISNAME; } +parent { clixon_xpath_parselval.intval = A_PARENT; return AXISNAME; } +preceding { clixon_xpath_parselval.intval = A_PRECEEDING; return AXISNAME; } +preceding-sibling { clixon_xpath_parselval.intval = A_PRECEEDING_SIBLING; return AXISNAME; } +self { clixon_xpath_parselval.intval = A_SELF; return AXISNAME; } +current { clixon_xpath_parselval.string = strdup(yytext); return NODETYPE; } +comment { clixon_xpath_parselval.string = strdup(yytext); return NODETYPE; } +text { clixon_xpath_parselval.string = strdup(yytext); return NODETYPE; } +processing-instructions { clixon_xpath_parselval.string = strdup(yytext); return NODETYPE; } +node { clixon_xpath_parselval.string = strdup(yytext); return NODETYPE; } +\" { BEGIN(QLITERAL); return QUOTE; } +\' { BEGIN(ALITERAL); return APOST; } +\-?({integer}|{real}) { sscanf(yytext,"%lf",&clixon_xpath_parselval.dval); return NUMBER;} +[0-9A-Za-z_\-]+ { clixon_xpath_parselval.string = strdup(yytext); + return NAME; /* rather be catch-all */ + } +. { fprintf(stderr,"LEXICAL ERROR\n"); return -1; } +\" { BEGIN(TOKEN); return QUOTE; } +. { clixon_xpath_parselval.string = strdup(yytext); + return CHAR;} +\' { BEGIN(TOKEN); return APOST; } +. { clixon_xpath_parselval.string = strdup(yytext); + return CHAR;} + +%% + + +/*! Initialize scanner. + */ +int +xpath_scan_init(struct clicon_xpath_yacc_arg *xy) +{ + BEGIN(TOKEN); + xy->xy_lexbuf = yy_scan_string (xy->xy_parse_string); +#if 1 /* XXX: just to use unput to avoid warning */ + if (0) + yyunput(0, ""); +#endif + + return 0; +} + +/* + * free buffers + * Even within Flex version 2.5 (this is assumed), freeing buffers is different. + */ +int +xpath_scan_exit(struct clicon_xpath_yacc_arg *xy) +{ + yy_delete_buffer(xy->xy_lexbuf); + clixon_xpath_parselex_destroy(); /* modern */ + return 0; +} + diff --git a/lib/src/clixon_xpath_parse.y b/lib/src/clixon_xpath_parse.y new file mode 100644 index 00000000..0e601167 --- /dev/null +++ b/lib/src/clixon_xpath_parse.y @@ -0,0 +1,289 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * XPATH Parser + * From https://www.w3.org/TR/xpath-10/ + * The primary syntactic construct in XPath is the expression. + * An expression matches the production Expr + * see https://www.w3.org/TR/xpath-10/#NT-Expr) + * Lexical structure is defined by ExprToken, see + * see https://www.w3.org/TR/xpath-10/#exprlex + */ + +%start start + +%union { + int intval; + double dval; + char *string; + void *stack; /* xpath_tree */ +} + +%token AXISNAME +%token LOGOP +%token ADDOP +%token RELOP + +%token NUMBER + +%token X_EOF +%token QUOTE +%token APOST +%token CHAR +%token NAME +%token NODETYPE +%token DOUBLEDOT +%token DOUBLECOLON +%token DOUBLESLASH +%token FUNCTIONNAME + +%type axisspec + +%type string +%type expr +%type andexpr +%type relexpr +%type addexpr +%type unionexpr +%type pathexpr +%type locationpath +%type abslocpath +%type rellocpath +%type step +%type nodetest +%type predicates +%type primaryexpr + +%lex-param {void *_xy} /* Add this argument to parse() and lex() function */ +%parse-param {void *_xy} + +%{ +/* Here starts user C-code */ + +/* typecast macro */ +#define _XY ((struct clicon_xpath_yacc_arg *)_xy) + +#define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_xpath_parsetext, _XY->xy_linenum); YYERROR;} + +/* add _yy to error paramaters */ +#define YY_(msgid) msgid + +#include "clixon_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "clixon_err.h" +#include "clixon_log.h" +#include "clixon_queue.h" +#include "clixon_string.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" + +#include "clixon_xpath_parse.h" + +extern int clixon_xpath_parseget_lineno (void); + +/* + also called from yacc generated code * +*/ + +void +clixon_xpath_parseerror(void *_xy, + char *s) +{ + clicon_err(OE_XML, 0, "%s on line %d: %s at or before: '%s'", + _XY->xy_name, + _XY->xy_linenum , + s, + clixon_xpath_parsetext); + return; +} + +int +xpath_parse_init(struct clicon_xpath_yacc_arg *xy) +{ + // clicon_debug_init(2, NULL); + return 0; +} + +int +xpath_parse_exit(struct clicon_xpath_yacc_arg *xy) +{ + return 0; +} + +static xpath_tree * +xp_new(enum xp_type type, + int i0, + double d0, + char *s0, + char *s1, + xpath_tree *c0, + xpath_tree *c1) +{ + xpath_tree *xs = NULL; + + if ((xs = malloc(sizeof(xpath_tree))) == NULL){ + clicon_err(OE_XML, errno, "malloc"); + goto done; + } + memset(xs, 0, sizeof(*xs)); + xs->xs_type = type; + xs->xs_int = i0; + xs->xs_double = d0; + xs->xs_s0 = s0; + xs->xs_s1 = s1; + xs->xs_c0 = c0; + xs->xs_c1 = c1; + done: + return xs; +} + +%} + +%% + +/* +*/ + +start : expr X_EOF { _XY->xy_top=$1;clicon_debug(1,"start->expr"); YYACCEPT; } + | locationpath X_EOF { _XY->xy_top=$1;clicon_debug(1,"start->locationpath"); YYACCEPT; } + ; + +expr : expr LOGOP andexpr { $$=xp_new(XP_EXP,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"expr->expr or andexpr"); } + | andexpr { $$=xp_new(XP_EXP,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"expr-> andexpr"); } + ; + +andexpr : andexpr LOGOP relexpr { $$=xp_new(XP_AND,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"andexpr-> andexpr and relexpr"); } + | relexpr { $$=xp_new(XP_AND,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"andexpr-> relexpr"); } + ; + +relexpr : relexpr RELOP addexpr { $$=xp_new(XP_RELEX,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"relexpr-> relexpr relop addexpr"); } + | addexpr { $$=xp_new(XP_RELEX,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"relexpr-> addexpr"); } + ; + +addexpr : addexpr ADDOP unionexpr { $$=xp_new(XP_ADD,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"addexpr-> addexpr ADDOP unionexpr"); } + | unionexpr { $$=xp_new(XP_ADD,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"addexpr-> unionexpr"); } + ; + +/* node-set */ +unionexpr : unionexpr '|' pathexpr { $$=xp_new(XP_UNION,A_NAN,0.0,NULL,NULL,$1, $3);clicon_debug(1,"unionexpr-> unionexpr | pathexpr"); } + | pathexpr { $$=xp_new(XP_UNION,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"unionexpr-> pathexpr"); } + ; + +pathexpr : locationpath { $$=xp_new(XP_PATHEXPR,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"pathexpr-> locationpath"); } + | primaryexpr { $$=xp_new(XP_PATHEXPR,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"pathexpr-> primaryexpr"); } + ; + +/* location path returns a node-set */ +locationpath : rellocpath { $$=xp_new(XP_LOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(1,"locationpath-> rellocpath"); } + | abslocpath { $$=xp_new(XP_LOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(1,"locationpath-> abslocpath"); } + ; + +abslocpath : '/' { $$=xp_new(XP_ABSPATH,A_ROOT,0.0,NULL,NULL,NULL, NULL);clicon_debug(1,"abslocpath-> /"); } + | '/' rellocpath { $$=xp_new(XP_ABSPATH,A_ROOT,0.0,NULL,NULL,$2, NULL);clicon_debug(1,"abslocpath->/ rellocpath");} + /* // is short for /descendant-or-self::node()/ */ + | DOUBLESLASH rellocpath {$$=xp_new(XP_ABSPATH,A_DESCENDANT_OR_SELF,0.0,NULL,NULL,$2, NULL); clicon_debug(1,"abslocpath-> // rellocpath"); } + ; + +rellocpath : step { $$=xp_new(XP_RELLOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(1,"rellocpath-> step"); } + | rellocpath '/' step { $$=xp_new(XP_RELLOCPATH,A_NAN,0.0,NULL,NULL,$1, $3);clicon_debug(1,"rellocpath-> rellocpath / step"); } + | rellocpath DOUBLESLASH step { $$=xp_new(XP_RELLOCPATH,A_DESCENDANT_OR_SELF,0.0,NULL,NULL,$1, $3); clicon_debug(1,"rellocpath-> rellocpath // step"); } + ; + +step : axisspec nodetest predicates {$$=xp_new(XP_STEP,$1,0.0, NULL, NULL, $2, $3);clicon_debug(1,"step->axisspec(%d) nodetest", $1); } + | '.' predicates { $$=xp_new(XP_STEP,A_SELF, 0.0,NULL, NULL, NULL, $2); clicon_debug(1,"step-> ."); } + | DOUBLEDOT predicates { $$=xp_new(XP_STEP, A_PARENT, 0.0,NULL, NULL, NULL, $2); clicon_debug(1,"step-> .."); } + ; + +axisspec : AXISNAME DOUBLECOLON { clicon_debug(1,"axisspec-> AXISNAME(%d) ::", $1); $$=$1;} + | '@' { $$=A_ATTRIBUTE; clicon_debug(1,"axisspec-> @"); } + | { clicon_debug(1,"axisspec-> "); $$=A_CHILD;} + ; + +nodetest : '*' { $$=xp_new(XP_NODE,A_NAN,0.0, NULL, NULL, NULL, NULL); clicon_debug(1,"nodetest-> *"); } + | NAME { $$=xp_new(XP_NODE,A_NAN,0.0, NULL, $1, NULL, NULL); clicon_debug(1,"nodetest-> name(%s)",$1); } + | NAME ':' NAME { $$=xp_new(XP_NODE,A_NAN,0.0, $1, $3, NULL, NULL);clicon_debug(1,"nodetest-> name(%s) : name(%s)", $1, $3); } + | NAME ':' '*' { $$=xp_new(XP_NODE,A_NAN,0.0, $1, NULL, NULL, NULL);clicon_debug(1,"nodetest-> name(%s) : *", $1); } + | NODETYPE '(' ')' { $$=xp_new(XP_NODE_FN,A_NAN,0.0, $1, NULL, NULL, NULL); clicon_debug(1,"nodetest-> nodetype()"); } + ; + +/* evaluates to boolean */ +predicates : predicates '[' expr ']' { $$=xp_new(XP_PRED,A_NAN,0.0, NULL, NULL, $1, $3); clicon_debug(1,"predicates-> [ expr ]"); } + | { $$=xp_new(XP_PRED,A_NAN,0.0, NULL, NULL, NULL, NULL); clicon_debug(1,"predicates->"); } + ; + +primaryexpr : '(' expr ')' { $$=xp_new(XP_PRI0,A_NAN,0.0, NULL, NULL, $2, NULL); clicon_debug(1,"primaryexpr-> ( expr )"); } + | NUMBER { $$=xp_new(XP_PRIME_NR,A_NAN, $1, NULL, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> NUMBER(%lf)", $1); } + | QUOTE string QUOTE { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, $2, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> \" string \""); } + | QUOTE QUOTE { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, NULL, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> \" \""); } + | APOST string APOST { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, $2, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> ' string '"); } + | APOST APOST { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, NULL, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> ' '"); } + | FUNCTIONNAME '(' ')' { $$=xp_new(XP_PRIME_FN,A_NAN,0.0, $1, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> functionname ( arguments )"); } + ; + +/* XXX Adding this between FUNCTIONNAME() breaks parser,.. +arguments : arguments expr { clicon_debug(1,"arguments-> arguments expr"); } + | { clicon_debug(1,"arguments-> "); } + ; +*/ +string : string CHAR { + int len = strlen($1); + $$ = realloc($1, len+strlen($2) + 1); + sprintf($$+len, "%s", $2); + free($2); + clicon_debug(1,"string-> string CHAR"); + } + | CHAR { clicon_debug(1,"string-> "); } + ; + + +%% + diff --git a/lib/src/clixon_xsl.c b/lib/src/clixon_xsl.c index 22be2b63..86ab22e8 100644 --- a/lib/src/clixon_xsl.c +++ b/lib/src/clixon_xsl.c @@ -35,55 +35,17 @@ * NOTE: there is a main function at the end of this file where you can test out * different xpath expressions. * Look at the end of the file for a test unit program + +The code is implemented according to XPATH 1.0: + https://www.w3.org/TR/xpath-10/ + +The primary syntactic construct in XPath is the expression. An expression matches +the production Expr (see https://www.w3.org/TR/xpath-10/#NT-Expr) */ -/* -See https://www.w3.org/TR/xpath/ +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif -Implementation of a limited xslt xpath syntax. Some examples. Given the following -xml tree: - - 42 - 99 - 22 - - -With the following xpath examples. There are some diffs and many limitations compared -to the xml standards: - / whole tree ... - /bbb - /aaa/bbb 42 - 99 - //bbb as above - //b?b as above - //b\* as above - //b\*\/ccc 42 - 99 - //\*\/ccc 42 - 99 - 22 --- //bbb@x x="hello" - //bbb[@x] 42 - 99 - //bbb[@x=hello] 42 - //bbb[@x="hello"] as above - //bbb[0] 42 - //bbb[ccc=99] 99 ---- //\*\/[ccc=99] same as above - '//bbb | //ddd' 42 - 99 - 22 (NB spaces) - etc - For xpath v1.0 see http://www.w3.org/TR/xpath/ -record[name=c][time=d] -in - - c - - 45654df4-2292-45d3-9ca5-ee72452568a8 - - - - */ #include #include #include @@ -94,6 +56,8 @@ in #include #include #include +#include +#include /* cligen */ #include @@ -107,14 +71,18 @@ in #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" #include "clixon_xsl.h" + /* Constants */ #define XPATH_VEC_START 128 /* * Types */ + struct searchvec{ cxobj **sv_v0; /* here is result */ int sv_v0len; @@ -126,13 +94,22 @@ typedef struct searchvec searchvec; /* Local types */ -enum axis_type{ - A_SELF, - A_CHILD, - A_PARENT, - A_ROOT, - A_ANCESTOR, - A_DESCENDANT_OR_SELF, /* actually descendant-or-self */ + +struct xpath_predicate{ + struct xpath_predicate *xp_next; + char *xp_expr; +}; + +/* XPATH Axis according to https://www.w3.org/TR/xpath-10/#NT-Step + * Axis ::= AxisSpecifier NodeTest Predicate* + * Eg "child:: + */ +struct xpath_element{ + struct xpath_element *xe_next; + enum axis_type xe_type; + char *xe_prefix; /* eg for namespaces */ + char *xe_str; /* eg for child */ + struct xpath_predicate *xe_predicate; /* eg within [] */ }; /* Mapping between axis type string <--> int */ @@ -146,23 +123,12 @@ static const map_str2int axismap[] = { {NULL, -1} }; -struct xpath_predicate{ - struct xpath_predicate *xp_next; - char *xp_expr; -}; - -struct xpath_element{ - struct xpath_element *xe_next; - enum axis_type xe_type; - char *xe_prefix; /* eg for namespaces */ - char *xe_str; /* eg for child */ - struct xpath_predicate *xe_predicate; /* eg within [] */ -}; - static int xpath_split(char *xpathstr, char **pathexpr); +/*! Print xpath structure for debug */ static int -xpath_print(FILE *f, struct xpath_element *xplist) +xpath_print(FILE *f, + struct xpath_element *xplist) { struct xpath_element *xe; struct xpath_predicate *xp; @@ -204,7 +170,7 @@ xpath_parse_predicate(struct xpath_element *xe, } memset(xp, 0, sizeof(*xp)); if ((xp->xp_expr = strdup(s)) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); goto done; } xp->xp_next = xe->xe_predicate; @@ -216,6 +182,11 @@ xpath_parse_predicate(struct xpath_element *xe, return retval; } +/*! XPATH parse, create new child element + * @param[in] atype Axis type, see https://www.w3.org/TR/xpath-10/#axes + * @param[in] str + * @param[out] xpnext + */ static int xpath_element_new(enum axis_type atype, char *str, @@ -235,7 +206,7 @@ xpath_element_new(enum axis_type atype, xe->xe_type = atype; if (str){ if ((str1 = strdup(str)) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); goto done; } if (xpath_split(str1, &pred) < 0) /* Can be more predicates */ @@ -246,20 +217,20 @@ xpath_element_new(enum axis_type atype, *local = '\0'; local++; if ((xe->xe_prefix = strdup(str1)) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); goto done; } } else local = str1; if ((xe->xe_str = strdup(local)) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); goto done; } } else{ if ((xe->xe_str = strdup("*")) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); goto done; } } @@ -308,8 +279,17 @@ xpath_free(struct xpath_element *xplist) return 0; } -/* - * // is short for /descendant-or-self::node()/ +/*! Parse xpath to xpath_element structure + + * + * [1] LocationPath ::= RelativeLocationPath + * | AbsoluteLocationPath + * [2] AbsoluteLocationPath ::= '/' RelativeLocationPath? + * | AbbreviatedAbsoluteLocationPath + * [3] RelativeLocationPath ::= Step + | RelativeLocationPath '/' Step + | AbbreviatedRelativeLocationPath + * @see https://www.w3.org/TR/xpath-10/#NT-LocationPath */ static int xpath_parse(char *xpath, @@ -325,7 +305,7 @@ xpath_parse(char *xpath, int esc = 0; if ((s0 = strdup(xpath)) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); goto done; } s = s0; @@ -354,6 +334,7 @@ xpath_parse(char *xpath, } s++; } + /* Iterate through steps (s), see https://www.w3.org/TR/xpath-10/#NT-Step */ s = s0; for (i=0; ixe_str, CX_ELMNT, flags, &vec1, &vec1len) < 0) + if (xpath_recursive_find(xv, xe->xe_str, CX_ELMNT, flags, &vec1, &vec1len) < 0) goto done; } } @@ -740,7 +705,6 @@ xpath_find(cxobj *xcur, } } } - for (xp = xe->xe_predicate; xp; xp = xp->xp_next){ if (xpath_expr(xcur, xp->xp_expr, flags, &vec0, &vec0len) < 0) goto done; @@ -781,7 +745,7 @@ xpath_split(char *xpathstr, } } if (pe==NULL){ - clicon_err(OE_XML, errno, "%s: mismatched []: %s", __FUNCTION__, xpathstr); + clicon_err(OE_XML, errno, "mismatched []: %s", xpathstr); goto done; } } @@ -800,6 +764,7 @@ xpath_split(char *xpathstr, * @param[in] flags if != 0, only match xml nodes matching flags * @param[out] vec2 Result XML node vector * @param[out] vec2len Length of result vector. + * @see https://www.w3.org/TR/xpath-10/#NT-LocationPath */ static int xpath_exec(cxobj *xcur, @@ -833,14 +798,20 @@ xpath_exec(cxobj *xcur, * @param[in] xcur xml-tree where to search * @param[in] xpath string with XPATH syntax * @param[in] flags if != 0, only match xml nodes matching flags - * @param[in] vec1 vector of XML trees - * @param[in] vec1len length of XML trees + * @param[out] vec1 vector of XML trees + * @param[out] vec1len length of XML trees * For example: xpath = //a | //b. * xpath_first+ splits xpath up in several subcalls * (eg xpath=//a and xpath=//b) and collects the results. * Note: if a match is found in both, two (or more) same results will be * returned. * Note, this could be 'folded' into xpath1 but I judged it too complex. + * @see https://www.w3.org/TR/xpath-10/#NT-Expr + * An 'Expr' is composed of compositions of and, or, =, +, -, down to: + * PathExpr ::= LocationPath + * | FilterExpr + * | FilterExpr '/' RelativeLocationPath + * | FilterExpr '//' RelativeLocationPath */ static int xpath_choice(cxobj *xcur, @@ -850,15 +821,15 @@ xpath_choice(cxobj *xcur, size_t *vec1len) { int retval = -1; - char *s0; + char *s0 = NULL; char *s1; char *s2; char *xpath; cxobj **vec0 = NULL; size_t vec0len = 0; - + if ((s0 = strdup(xpath0)) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); goto done; } s2 = s1 = s0; @@ -880,7 +851,7 @@ xpath_choice(cxobj *xcur, goto done; } retval = 0; - done: + done: if (s0) free(s0); if (vec0) @@ -888,11 +859,26 @@ xpath_choice(cxobj *xcur, return retval; } -/*! Help function to xpath_first +/*! Xpath nodeset function where only the first matching entry is returned + * args: + * @param[in] xcur xml-tree where to search + * @param[in] xpath string with XPATH syntax + * @retval xml-tree of first match + * @retval NULL Error or not found + * + * @code + * cxobj *x; + * if ((x = xpath_first(xtop, "//symbol/foo")) != NULL) { + * ... + * } + * @endcode + * @note the returned pointer points into the original tree so should not be freed fter use. + * @note return value does not see difference between error and not found + * @see xpath_first. This is obsolete and only enabled with COMPAT_XSL */ -static cxobj * -xpath_first0(cxobj *xcur, - char *xpath) +cxobj * +xpath_first_xsl(cxobj *xcur, + char *xpath) { cxobj **vec1 = NULL; size_t vec1len = 0; @@ -910,57 +896,7 @@ xpath_first0(cxobj *xcur, return xn; } -/*! A restricted xpath function where the first matching entry is returned - * See xpath1() on details for subset. - * args: - * @param[in] xcur xml-tree where to search - * @param[in] xpath string with XPATH syntax - * @retval xml-tree of first match - * @retval NULL Error or not found - * - * @code - * cxobj *x; - * if ((x = xpath_first(xtop, "//symbol/foo")) != NULL) { - * ... - * } - * @endcode - * @note the returned pointer points into the original tree so should not be freed after use. - * @note return value does not see difference between error and not found - * @see also xpath_vec. - */ -cxobj * -xpath_first(cxobj *xcur, - char *format, - ...) -{ - cxobj *retval = NULL; - va_list ap; - size_t len; - char *xpath; - - va_start(ap, format); - len = vsnprintf(NULL, 0, format, ap); - va_end(ap); - /* allocate a message string exactly fitting the message length */ - if ((xpath = malloc(len+1)) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - /* second round: compute write message from reason and args */ - va_start(ap, format); - if (vsnprintf(xpath, len+1, format, ap) < 0){ - clicon_err(OE_UNIX, errno, "vsnprintf"); - va_end(ap); - goto done; - } - va_end(ap); - retval = xpath_first0(xcur, xpath); - done: - if (xpath) - free(xpath); - return retval; -} - +#ifdef COMPAT_XSL /*! A restricted xpath iterator that loops over all matching entries. Dont use. * * See xpath1() on details for subset. @@ -976,10 +912,9 @@ xpath_first(cxobj *xcur, * } * @endcode * - * Note that the returned pointer points into the original tree so should not be freed + * @note The returned pointer points into the original tree so should not be freed * after use. - * @see also xpath, xpath_vec. - * NOTE: uses a static variable: consider replacing with xpath_vec() instead + * @note obsolete. Dont use */ cxobj * xpath_each(cxobj *xcur, @@ -1018,11 +953,12 @@ xpath_each(cxobj *xcur, done: return xn; } +#endif /*! A restricted xpath that returns a vector of matches * * See xpath1() on details for subset -. * @param[in] xcur xml-tree where to search + * @param[in] xcur xml-tree where to search * @param[in] xpath string with XPATH syntax * @param[out] vec vector of xml-trees. Vector must be free():d after use * @param[out] veclen returns length of vector in return value @@ -1043,41 +979,22 @@ xpath_each(cxobj *xcur, * @note Although the returned vector must be freed after use, * the returned xml trees should not. * @see also xpath_first, xpath_each. + * @see xpath_vec. This is obsolete and only enabled with COMPAT_XSL */ int -xpath_vec(cxobj *xcur, - char *format, - cxobj ***vec, - size_t *veclen, - ...) +xpath_vec_xsl(cxobj *xcur, + char *xpath, + cxobj ***vec, + size_t *veclen) { int retval = -1; - va_list ap; - size_t len; - char *xpath; - va_start(ap, veclen); - len = vsnprintf(NULL, 0, format, ap); - va_end(ap); - /* allocate a message string exactly fitting the message length */ - if ((xpath = malloc(len+1)) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - /* second round: compute write message from reason and args */ - va_start(ap, veclen); - if (vsnprintf(xpath, len+1, format, ap) < 0){ - clicon_err(OE_UNIX, errno, "vsnprintf"); - va_end(ap); - goto done; - } - va_end(ap); *vec = NULL; *veclen = 0; - retval = xpath_choice(xcur, xpath, 0x0, vec, veclen); + if (xpath_choice(xcur, xpath, 0x0, vec, veclen) < 0) + goto done; + retval = 0; done: - if (xpath) - free(xpath); return retval; } @@ -1102,99 +1019,23 @@ xpath_vec(cxobj *xcur, * @endcode * @Note that although the returned vector must be freed after use, the returned xml * trees need not be. - * @see also xpath_vec This is a specialized version. + * @see xpath_vec_flag. This is obsolete and only enabled with COMPAT_XSL */ int -xpath_vec_flag(cxobj *xcur, - char *format, - uint16_t flags, - cxobj ***vec, - size_t *veclen, - ...) +xpath_vec_flag_xsl(cxobj *xcur, + char *xpath, + uint16_t flags, + cxobj ***vec, + size_t *veclen) { int retval = -1; - va_list ap; - size_t len; - char *xpath; - va_start(ap, veclen); - len = vsnprintf(NULL, 0, format, ap); - va_end(ap); - /* allocate a message string exactly fitting the message length */ - if ((xpath = malloc(len+1)) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - /* second round: compute write message from reason and args */ - va_start(ap, veclen); - if (vsnprintf(xpath, len+1, format, ap) < 0){ - clicon_err(OE_UNIX, errno, "vsnprintf"); - va_end(ap); - goto done; - } - va_end(ap); *vec=NULL; *veclen = 0; - retval = xpath_choice(xcur, xpath, flags, vec, veclen); + if (xpath_choice(xcur, xpath, flags, vec, veclen) < 0) + goto done; + retval = 0; done: - if (xpath) - free(xpath); return retval; } -/* - * Turn this on to get an xpath test program - * Usage: xpath [] - * read xml from input - * Example compile: - gcc -g -o xpath -I. -I../clixon ./clixon_xsl.c -lclixon -lcligen - * Example run: - echo "" | xpath "a" -*/ -#if 0 /* Test program */ - - -static int -usage(char *argv0) -{ - fprintf(stderr, "usage:%s .\n\tInput on stdin\n", argv0); - exit(0); -} - -int -main(int argc, char **argv) -{ - int i; - cxobj **xv - cxobj *x; - cxobj *xn; - size_t xlen = 0; - - if (argc != 2){ - usage(argv[0]); - return 0; - } - if (xml_parse_file(0, &x, "") < 0){ - fprintf(stderr, "parsing 2\n"); - return -1; - } - printf("\n"); - - if (xpath_vec(x, argv[1], &xv, &xlen) < 0) - return -1; - if (xv){ - for (i=0; i #include #include +#include #define __USE_GNU /* strverscmp */ #include #include #include #include +#include +#include #include #include #include @@ -74,6 +77,8 @@ #include "clixon_yang_type.h" #include "clixon_yang_parse.h" +/* Size of json read buffer when reading from file*/ +#define BUFLEN 1024 /* Mapping between yang keyword string <--> clicon constants */ static const map_str2int ykmap[] = { @@ -158,7 +163,7 @@ yspec_new(void) yang_spec *yspec; if ((yspec = malloc(sizeof(*yspec))) == NULL){ - clicon_err(OE_YANG, errno, "%s: malloc", __FUNCTION__); + clicon_err(OE_YANG, errno, "malloc"); return NULL; } memset(yspec, 0, sizeof(*yspec)); @@ -176,7 +181,7 @@ ys_new(enum rfc_6020 keyw) yang_stmt *ys; if ((ys = malloc(sizeof(*ys))) == NULL){ - clicon_err(OE_YANG, errno, "%s: malloc", __FUNCTION__); + clicon_err(OE_YANG, errno, "malloc"); return NULL; } memset(ys, 0, sizeof(*ys)); @@ -184,7 +189,7 @@ ys_new(enum rfc_6020 keyw) /* The cvec contains stmt-specific variables. Only few stmts need variables so the cvec could be lazily created to save some heap and cycles. */ if ((ys->ys_cvec = cvec_new(0)) == NULL){ - clicon_err(OE_YANG, errno, "%s: cvec_new", __FUNCTION__); + clicon_err(OE_YANG, errno, "cvec_new"); return NULL; } return ys; @@ -247,7 +252,7 @@ yn_realloc(yang_node *yn) yn->yn_len++; if ((yn->yn_stmt = realloc(yn->yn_stmt, (yn->yn_len)*sizeof(yang_stmt *))) == 0){ - clicon_err(OE_YANG, errno, "%s: realloc", __FUNCTION__); + clicon_err(OE_YANG, errno, "realloc"); return -1; } yn->yn_stmt[yn->yn_len - 1] = NULL; /* init field */ @@ -255,6 +260,13 @@ yn_realloc(yang_node *yn) } /*! Copy yang statement recursively from old to new + * @param[in] ynew New empty (but created) yang statement (to) + * @param[in] yold Old existing yang statement (from) + * @code + * yang_stmt *new = ys_new(Y_LEAF); + * if (ys_cp(new, old) < 0) + * err; + * @endcode */ int ys_cp(yang_stmt *ynew, @@ -269,22 +281,22 @@ ys_cp(yang_stmt *ynew, ynew->ys_parent = NULL; if (yold->ys_stmt) if ((ynew->ys_stmt = calloc(yold->ys_len, sizeof(yang_stmt *))) == NULL){ - clicon_err(OE_YANG, errno, "%s: calloc", __FUNCTION__); + clicon_err(OE_YANG, errno, "calloc"); goto done; } if (yold->ys_argument) if ((ynew->ys_argument = strdup(yold->ys_argument)) == NULL){ - clicon_err(OE_YANG, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_YANG, errno, "strdup"); goto done; } if (yold->ys_cv) if ((ynew->ys_cv = cv_dup(yold->ys_cv)) == NULL){ - clicon_err(OE_YANG, errno, "%s: cv_dup", __FUNCTION__); + clicon_err(OE_YANG, errno, "cv_dup"); goto done; } if (yold->ys_cvec) if ((ynew->ys_cvec = cvec_dup(yold->ys_cvec)) == NULL){ - clicon_err(OE_YANG, errno, "%s: cvec_dup", __FUNCTION__); + clicon_err(OE_YANG, errno, "cvec_dup"); goto done; } if (yold->ys_typecache){ @@ -304,8 +316,10 @@ ys_cp(yang_stmt *ynew, return retval; } -/*! Create a new yang node and copy the contents recursively from the original. - * +/*! Create a new yang node and copy the contents recursively from the original. * + * @param[in] old Old existing yang statement (from) + * @retval NULL Error + * @retval new New created yang statement * This may involve duplicating strings, etc. * The new yang tree needs to be freed by ys_free(). * The parent of new is NULL, it needs to be explicityl inserted somewhere @@ -328,7 +342,6 @@ ys_dup(yang_stmt *old) return new; } - /*! Insert yang statement as child of a parent yang_statement, last in list * * Also add parent to child as up-pointer @@ -598,8 +611,8 @@ yang_find_schemanode(yang_node *yn, /*! Find first matching data node in all (sub)modules in a yang spec * * @param[in] ysp Yang specification - * @param[in] argument if NULL, match any(first) argument. XXX is that really a case? - * @param[in] schemanode If set look for schema nodes, otherwise only data nodes + * @param[in] argument Name of node. If NULL match first + * @param[in] class See yang_class for class of yang nodes * A yang specification has modules as children which in turn can have * syntax-nodes as children. This function goes through all the modules to * look for nodes. Note that if a child to a module is a choice, @@ -790,7 +803,7 @@ yarg_id(yang_stmt *ys) return id; } -/* Assume argument is id on the type: <[prefix:]id>, return 'prefix' +/*! Assume argument is id on the type: <[prefix:]id>, return 'prefix' * @param[in] ys A yang statement * @retval NULL No prefix * @retval prefix Malloced string that needs to be freed by caller. @@ -810,6 +823,44 @@ yarg_prefix(yang_stmt *ys) } +/*! Split yang node identifier into prefix and identifer. + * @param[in] node-id + * @param[out] prefix Malloced string. May be NULL. + * @param[out] id Malloced identifier. + * @retval 0 OK + * @retval -1 Error + * @note caller need to free id and prefix after use + */ +int +yang_nodeid_split(char *nodeid, + char **prefix, + char **id) +{ + int retval = -1; + char *str; + + if ((str = strchr(nodeid, ':')) == NULL){ + if ((*id = strdup(nodeid)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + } + else{ + if ((*prefix = strdup(nodeid)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + (*prefix)[str-nodeid] = '\0'; + str++; + if ((*id = strdup(str)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + } + retval = 0; + done: + return retval; +} /*! Given a yang statement and a prefix, return yang module to that prefix * Note, not the other module but the proxy import statement only @@ -896,7 +947,7 @@ yang_print(FILE *f, cbuf *cb = NULL; if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_YANG, errno, "%s: cbuf_new", __FUNCTION__); + clicon_err(OE_YANG, errno, "cbuf_new"); goto done; } if (yang_print_cbuf(cb, yn, 0) < 0) @@ -992,14 +1043,14 @@ ys_populate_leaf(yang_stmt *ys, goto done; /* 2. Create the CV using cvtype and name it */ if ((cv = cv_new(cvtype)) == NULL){ - clicon_err(OE_YANG, errno, "%s: cv_new", __FUNCTION__); + clicon_err(OE_YANG, errno, "cv_new"); goto done; } if (options & YANG_OPTIONS_FRACTION_DIGITS && cvtype == CGV_DEC64) /* XXX: Seems misplaced? / too specific */ cv_dec64_n_set(cv, fraction_digits); if (cv_name_set(cv, ys->ys_argument) == NULL){ - clicon_err(OE_YANG, errno, "%s: cv_new_set", __FUNCTION__); + clicon_err(OE_YANG, errno, "cv_new_set"); goto done; } /* 3. Check if default value. Here we parse the string in the default-stmt @@ -1077,7 +1128,7 @@ ys_populate_range(yang_stmt *ys, yparent = ys->ys_parent; /* Find parent: type */ if (yparent->yn_keyword != Y_TYPE){ - clicon_err(OE_YANG, 0, "%s: parent should be type", __FUNCTION__); + clicon_err(OE_YANG, 0, "parent should be type"); goto done; } if (yang_type_resolve(ys, (yang_stmt*)yparent, &yrestype, @@ -1097,7 +1148,8 @@ ys_populate_range(yang_stmt *ys, } if ((maxstr = strstr(minstr, "..")) != NULL){ if (strlen(maxstr) < 2){ - clicon_err(OE_YANG, 0, "range statement: %s not on the form: .."); + clicon_err(OE_YANG, 0, "range statement: %s not on the form: ..", + ys->ys_argument); goto done; } minstr[maxstr-minstr] = '\0'; @@ -1160,6 +1212,8 @@ ys_populate_range(yang_stmt *ys, /*! Sanity check yang type statement * XXX: Replace with generic parent/child type-check + * @param[in] ys The yang statement (type) to populate. + * @ */ static int ys_populate_type(yang_stmt *ys, @@ -1191,28 +1245,86 @@ ys_populate_type(yang_stmt *ys, return retval; } -/*! Sanity check yang type statement +/*! Sanity check yang identity statement recursively + * + * Find base identities if any and add this identity to derived list. + * Do this recursively + * @param[in] ys The yang identity to populate. + * @param[in] arg If set contains a derived identifier + * @see validate_identityref which in runtime validates actual valoues */ static int ys_populate_identity(yang_stmt *ys, void *arg) { int retval = -1; - yang_stmt *ybase; + yang_stmt *yc = NULL; + yang_stmt *ybaseid; + cg_var *cv; + char *derid; + char *baseid; + char *prefix = NULL; + cbuf *cb = NULL; + char *idref = (char*)arg; + char *p; - if ((ybase = yang_find((yang_node*)ys, Y_BASE, NULL)) == NULL) - return 0; - if ((yang_find_identity(ys, ybase->ys_argument)) == NULL){ - clicon_err(OE_YANG, 0, "Identity %s not found (base type of %s)", - ybase->ys_argument, ys->ys_argument); - goto done; + if (idref == NULL){ + /* Create derived identity through prefix:id if not recursively called*/ + derid = ys->ys_argument; /* derived id */ + if ((prefix = yarg_prefix(ys)) == NULL){ + if ((p = yang_find_myprefix(ys)) != NULL) + prefix = strdup(yang_find_myprefix(ys)); + } + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if (prefix) + cprintf(cb, "%s:%s", prefix, derid); + else + cprintf(cb, "%s", derid); + idref = cbuf_get(cb); + } + /* Iterate through all base statements and check the base identity exists + * AND populate the base identity recursively + */ + while ((yc = yn_each((yang_node*)ys, yc)) != NULL) { + if (yc->ys_keyword != Y_BASE) + continue; + baseid = yc->ys_argument; + if (((ybaseid = yang_find_identity(ys, baseid))) == NULL){ + clicon_err(OE_YANG, 0, "No such identity: %s", baseid); + goto done; + } + // continue; /* root identity */ + /* Check if derived id is already in base identifier */ + if (cvec_find(ybaseid->ys_cvec, idref) != NULL) + continue; + /* Add derived id to ybaseid */ + if ((cv = cv_new(CGV_STRING)) == NULL){ + clicon_err(OE_UNIX, errno, "cv_new"); + goto done; + } + /* add prefix */ + cv_name_set(cv, idref); + cvec_append_var(ybaseid->ys_cvec, cv); /* cv copied */ + if (cv){ + cv_free(cv); + cv = NULL; + } + /* Transitive to the root */ + if (ys_populate_identity(ybaseid, idref) < 0) + goto done; } retval = 0; done: + if (prefix) + free(prefix); + if (cb) + cbuf_free(cb); return retval; } - /*! Populate with cligen-variables, default values, etc. Sanity checks on complete tree. * * We do this in 2nd pass after complete parsing to be sure to have a complete parse-tree @@ -1433,7 +1545,7 @@ yang_expand(yang_node *yn) size = (yn->yn_len - i - 1)*sizeof(struct yang_stmt *); yn->yn_len += glen - 1; if (glen && (yn->yn_stmt = realloc(yn->yn_stmt, (yn->yn_len)*sizeof(yang_stmt *))) == 0){ - clicon_err(OE_YANG, errno, "%s: realloc", __FUNCTION__); + clicon_err(OE_YANG, errno, "realloc"); return -1; } /* Then move all existing elements up from i+1 (not uses-stmt) */ @@ -1474,10 +1586,10 @@ yang_expand(yang_node *yn) * Syntax parsing. A string is input and a syntax-tree is returned (or error). * A variable record is also returned containing a list of (global) variable values. * (cloned from cligen) - * @param h CLICON handle - * @param str String of yang statements - * @param name Log string, typically filename - * @param ysp Yang specification. Should ave been created by caller using yspec_new + * @param[in] h CLICON handle + * @param[in] str String of yang statements + * @param[in] name Log string, typically filename + * @param[in] ysp Yang specification. Should ave been created by caller using yspec_new * @retval ymod Top-level yang (sub)module * @retval NULL Error encountered * Calling order: @@ -1488,21 +1600,22 @@ yang_expand(yang_node *yn) * clixon_yang_parseparse # Actual yang parsing using yacc */ static yang_stmt * -yang_parse_str(clicon_handle h, - char *str, +yang_parse_str(char *str, const char *name, /* just for errs */ yang_spec *yspec) { struct clicon_yang_yacc_arg yy = {0,}; yang_stmt *ymod = NULL; - yy.yy_handle = h; + if (yspec == NULL){ + clicon_err(OE_YANG, 0, "Yang parse need top level yang spec"); + goto done; + } yy.yy_name = (char*)name; yy.yy_linenum = 1; yy.yy_parse_string = str; yy.yy_stack = NULL; yy.yy_module = NULL; /* this is the return value - the module/sub-module */ - if (ystack_push(&yy, (yang_node*)yspec) == NULL) goto done; if (strlen(str)){ /* Not empty */ @@ -1526,18 +1639,71 @@ yang_parse_str(clicon_handle h, ymod = yy.yy_module; done: ystack_pop(&yy); + if (yy.yy_stack) + free (yy.yy_stack); return ymod; /* top-level (sub)module */ } -/*! Read an opened file into a string and call yang string parsing +/*! Parse yang spec from an open file descriptor + * @param[in] fd File descriptor containing the YANG file as ASCII characters + * @param[in] name For debug, eg filename + * @param[in] ysp Yang specification. Should ave been created by caller using yspec_new + * @retval ymod Top-level yang (sub)module + * @retval NULL Error + */ +yang_stmt * +yang_parse_file(int fd, + const char *name, + yang_spec *ysp) +{ + char *buf = NULL; + int i; + int c; + int len; + yang_stmt *ymod = NULL; + int ret; + + len = BUFLEN; /* any number is fine */ + if ((buf = malloc(len)) == NULL){ + perror("pt_file malloc"); + return NULL; + } + memset(buf, 0, len); + i = 0; /* position in buf */ + while (1){ /* read the whole file */ + if ((ret = read(fd, &c, 1)) < 0){ + clicon_err(OE_XML, errno, "read"); + break; + } + if (ret == 0) + break; /* eof */ + if (len==i){ + if ((buf = realloc(buf, 2*len)) == NULL){ + clicon_err(OE_XML, errno, "realloc"); + goto done; + } + memset(buf+len, 0, len); + len *= 2; + } + buf[i++] = (char)(c&0xff); + } /* read a line */ + if ((ymod = yang_parse_str(buf, name, ysp)) < 0) + goto done; + done: + if (buf) + free(buf); + return ymod; /* top-level (sub)module */ +} + +/*! Open a file, read into a string and invoke yang parsing * * Similar to clicon_yang_str(), just read a file first * (cloned from cligen) - * @param h CLICON handle - * @param filename Name of file - * @param ysp Yang specification. Should ave been created by caller using yspec_new - * @retval ymod Top-level yang (sub)module - * @retval NULL Error encountered + * @param[in] h CLICON handle + * @param[in] filename Name of file + * @param[in] ysp Yang specification. Should ave been created by caller using yspec_new + * @retval ymod Top-level yang (sub)module + * @retval NULL Error encountered * The database symbols are inserted in alphabetical order. * Calling order: @@ -1548,57 +1714,27 @@ yang_parse_str(clicon_handle h, * clixon_yang_parseparse # Actual yang parsing using yacc */ static yang_stmt * -yang_parse_file(clicon_handle h, - const char *filename, - yang_spec *ysp - ) +yang_parse_filename(const char *filename, + yang_spec *ysp) { - char *buf = NULL; - int i; - int c; - int len; yang_stmt *ymod = NULL; - FILE *f = NULL; - struct stat st; + int fd = -1; + struct stat st; clicon_log(LOG_DEBUG, "Parsing yang file: %s", filename); if (stat(filename, &st) < 0){ clicon_err(OE_YANG, errno, "%s not found", filename); goto done; } - if ((f = fopen(filename, "r")) == NULL){ - clicon_err(OE_UNIX, errno, "fopen(%s)", filename); + if ((fd = open(filename, O_RDONLY)) < 0){ + clicon_err(OE_YANG, errno, "open(%s)", filename); goto done; } - - len = 1024; /* any number is fine */ - if ((buf = malloc(len)) == NULL){ - perror("pt_file malloc"); - return NULL; - } - memset(buf, 0, len); - - i = 0; /* position in buf */ - while (1){ /* read the whole file */ - if ((c = fgetc(f)) == EOF) - break; - if (len==i){ - if ((buf = realloc(buf, 2*len)) == NULL){ - fprintf(stderr, "%s: realloc: %s\n", __FUNCTION__, strerror(errno)); - goto done; - } - memset(buf+len, 0, len); - len *= 2; - } - buf[i++] = (char)(c&0xff); - } /* read a line */ - if ((ymod = yang_parse_str(h, buf, filename, ysp)) < 0) + if ((ymod = yang_parse_file(fd, filename, ysp)) < 0) goto done; done: - if (f) - fclose(f); - if (buf) - free(buf); + if (fd != -1) + close(fd); return ymod; /* top-level (sub)module */ } @@ -1613,8 +1749,7 @@ yang_parse_file(clicon_handle h, * @retval -1 Error */ static int -yang_parse_find_match(clicon_handle h, - const char *yang_dir, +yang_parse_find_match(const char *yang_dir, const char *module, cbuf *fbuf) { @@ -1658,7 +1793,7 @@ yang_parse_find_match(clicon_handle h, * @param[in] h CLICON handle * @param[in] yang_dir Directory where all YANG module files reside * @param[in] module Name of main YANG module. Or absolute file name. - * @param[in] revision Optional module revision date + * @param[in] revision Module revision date or NULL * @param[in] ysp Yang specification. Should have been created by caller using yspec_new * @retval ymod Top-level yang (sub)module * @retval NULL Error encountered @@ -1671,8 +1806,7 @@ yang_parse_find_match(clicon_handle h, * clixon_yang_parseparse # Actual yang parsing using yacc */ static yang_stmt * -yang_parse_recurse(clicon_handle h, - const char *yang_dir, +yang_parse_recurse(const char *yang_dir, const char *module, const char *revision, yang_spec *ysp) @@ -1686,26 +1820,26 @@ yang_parse_recurse(clicon_handle h, int nr; if (module[0] == '/'){ - if ((ymod = yang_parse_file(h, module, ysp)) == NULL) + if ((ymod = yang_parse_filename(module, ysp)) == NULL) goto done; } else { if ((fbuf = cbuf_new()) == NULL){ - clicon_err(OE_YANG, errno, "%s: cbuf_new", __FUNCTION__); + clicon_err(OE_YANG, errno, "cbuf_new"); goto done; } if (revision) cprintf(fbuf, "%s/%s@%s.yang", yang_dir, module, revision); else{ /* No specific revision, Match a yang file */ - if ((nr = yang_parse_find_match(h, yang_dir, module, fbuf)) < 0) + if ((nr = yang_parse_find_match(yang_dir, module, fbuf)) < 0) goto done; if (nr == 0){ - clicon_err(OE_YANG, errno, "No matching %s yang files found (expected module name or absolute filename)", module); + clicon_err(OE_YANG, errno, "No matching %s yang files found in %s (expected module name or absolute filename)", module, yang_dir); goto done; } } - if ((ymod = yang_parse_file(h, cbuf_get(fbuf), ysp)) == NULL) + if ((ymod = yang_parse_filename(cbuf_get(fbuf), ysp)) == NULL) goto done; } @@ -1720,7 +1854,7 @@ yang_parse_recurse(clicon_handle h, subrevision = NULL; if (yang_find((yang_node*)ysp, Y_MODULE, modname) == NULL) /* recursive call */ - if (yang_parse_recurse(h, yang_dir, modname, subrevision, ysp) == NULL){ + if (yang_parse_recurse(yang_dir, modname, subrevision, ysp) == NULL){ ymod = NULL; goto done; } @@ -1779,7 +1913,7 @@ ys_schemanode_check(yang_stmt *ys, * @param[in] h CLICON handle * @param[in] yang_dir Directory where all YANG module files reside (except mainfile) * @param[in] mainmod Name of main YANG module. Or absolute file name. - * @param[in] revision Optional main module revision date. + * @param[in] revision Main module revision date string or NULL * @param[out] ysp Yang specification. Should ave been created by caller using yspec_new * @retval 0 Everything OK * @retval -1 Error encountered @@ -1788,7 +1922,8 @@ ys_schemanode_check(yang_stmt *ys, * @note if mainmod is filename, revision is not considered. * Calling order: * yang_parse # Parse top-level yang module. Expand and populate yang tree - * yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them + * yang_parse_recurse # Parse one yang module, go through its (sub)modules, + * parse them and then recursively parse them * yang_parse_file # Read yang file into a string * yang_parse_str # Set up yacc parser and call it given a string * clixon_yang_parseparse # Actual yang parsing using yacc @@ -1804,7 +1939,7 @@ yang_parse(clicon_handle h, yang_stmt *ymod; /* Top-level yang (sub)module */ /* Step 1: parse from text to yang parse-tree. */ - if ((ymod = yang_parse_recurse(h, yang_dir, mainmodule, revision, ysp)) == NULL) + if ((ymod = yang_parse_recurse(yang_dir, mainmodule, revision, ysp)) == NULL) goto done; /* Add top module name as dbspec-name */ clicon_dbspec_name_set(h, ymod->ys_argument); @@ -1988,16 +2123,16 @@ schema_nodeid_vec(yang_node *yn, * Assume schema nodeid:s have prefixes, (actually the first). * @see yang_desc_schema_nodeid * @see RFC7950 6.5 - o schema node: A node in the schema tree. One of action, container, - leaf, leaf-list, list, choice, case, rpc, input, output, - notification, anydata, and anyxml. + * o schema node: A node in the schema tree. One of action, container, + * leaf, leaf-list, list, choice, case, rpc, input, output, + * notification, anydata, and anyxml. * Used in yang: deviation, top-level augment */ int -yang_abs_schema_nodeid(yang_spec *yspec, - char *schema_nodeid, +yang_abs_schema_nodeid(yang_spec *yspec, + char *schema_nodeid, enum rfc_6020 keyword, - yang_stmt **yres) + yang_stmt **yres) { int retval = -1; char **vec = NULL; @@ -2014,13 +2149,13 @@ yang_abs_schema_nodeid(yang_spec *yspec, goto done; } if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL){ - clicon_err(OE_YANG, errno, "%s: strsep", __FUNCTION__); + clicon_err(OE_YANG, errno, "strsep"); goto done; } /* Assume schema nodeid looks like: "/prefix:id[/prefix:id]*" */ if (nvec < 2){ - clicon_err(OE_YANG, 0, "%s: NULL or truncated path: %s", - __FUNCTION__, schema_nodeid); + clicon_err(OE_YANG, 0, "NULL or truncated path: %s", + schema_nodeid); goto done; } /* split : */ @@ -2029,7 +2164,7 @@ yang_abs_schema_nodeid(yang_spec *yspec, goto ok; } if ((prefix = strdup(vec[1])) == NULL){ - clicon_err(OE_UNIX, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_UNIX, errno, "strdup"); goto done; } prefix[id-vec[1]] = '\0'; @@ -2042,7 +2177,6 @@ yang_abs_schema_nodeid(yang_spec *yspec, } } if (ymod == NULL){ /* Try with topnode */ - if ((ys = yang_find_topnode(yspec, id, YC_SCHEMANODE)) == NULL){ clicon_err(OE_YANG, 0, "Module with id:%s:%s not found", prefix,id); goto done; @@ -2051,6 +2185,11 @@ yang_abs_schema_nodeid(yang_spec *yspec, clicon_err(OE_YANG, 0, "Module with id:%s:%s not found2", prefix,id); goto done; } + if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) != NULL && + strcmp(yprefix->ys_argument, prefix) != 0){ + clicon_err(OE_YANG, 0, "Module with id:%s:%s not found", prefix,id); + goto done; + } } if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, keyword, yres) < 0) goto done; @@ -2117,7 +2256,7 @@ ys_parse(yang_stmt *ys, assert(ys->ys_cv == NULL); /* Cv:s are parsed in different places, difficult to separate */ if ((ys->ys_cv = cv_new(cvtype)) == NULL){ - clicon_err(OE_YANG, errno, "%s: cv_new", __FUNCTION__); + clicon_err(OE_YANG, errno, "cv_new"); goto done; } if ((cvret = cv_parse1(ys->ys_argument, ys->ys_cv, &reason)) < 0){ /* error */ @@ -2143,7 +2282,7 @@ ys_parse(yang_stmt *ys, * That is, siblings, etc, may not be there. Complete checks are made in * ys_populate instead. * @param[in] ys yang statement - * @param[in] extra Yang extra for cornercases (unknown/extension) + * @param[in] extra Yang extra for cornercases (unknown/extension). * * The cv:s created in parse-tree as follows: * fraction-digits : Create cv as uint8, check limits [1:8] (must be made in 1st pass) @@ -2186,7 +2325,7 @@ ys_parse_sub(yang_stmt *ys, goto done; } if ((ys->ys_cv = cv_new(CGV_STRING)) == NULL){ - clicon_err(OE_YANG, errno, "%s: cv_new", __FUNCTION__); + clicon_err(OE_YANG, errno, "cv_new"); goto done; } if ((cvret = cv_parse1(extra, ys->ys_cv, &reason)) < 0){ /* error */ @@ -2197,12 +2336,11 @@ ys_parse_sub(yang_stmt *ys, clicon_err(OE_YANG, errno, "Parsing CV: %s", reason); goto done; } - free(extra); break; default: break; } - ok: + ok: retval = 0; done: if (prefix) @@ -2268,9 +2406,9 @@ yang_spec_netconf(clicon_handle h) } /*! Read, parse and save application yang specification as option - * @param h clicon handle - * @param f file to print to (if printspec enabled) - * @param printspec print database (YANG) specification as read from file + * @param[in] h clicon handle + * @param[in] f file to print to (if printspec enabled) + * @param[in] printspec print database (YANG) specification as read from file */ yang_spec* yang_spec_main(clicon_handle h) @@ -2280,7 +2418,7 @@ yang_spec_main(clicon_handle h) char *yang_module; char *yang_revision; - if ((yang_dir = clicon_yang_dir(h)) == NULL){ + if ((yang_dir = clicon_yang_dir(h)) == NULL){ clicon_err(OE_FATAL, 0, "CLICON_YANG_DIR option not set"); goto done; } diff --git a/lib/src/clixon_yang_parse.h b/lib/src/clixon_yang_parse.h index 0b9b1ec4..baa136df 100644 --- a/lib/src/clixon_yang_parse.h +++ b/lib/src/clixon_yang_parse.h @@ -51,7 +51,6 @@ struct ys_stack{ }; struct clicon_yang_yacc_arg{ /* XXX: mostly unrelevant */ - clicon_handle yy_handle; /* cligen_handle */ char *yy_name; /* Name of syntax (for error string) */ int yy_linenum; /* Number of \n in parsed buffer */ char *yy_parse_string; /* original (copy of) parse string */ diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index eb48253f..2dc7d8f2 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -260,7 +260,7 @@ ysp_add(struct clicon_yang_yacc_arg *yy, clicon_err(OE_YANG, errno, "No stack"); goto err; } - yn = ystack->ys_node; + assert(yn = ystack->ys_node); if ((ys = ys_new(keyword)) == NULL) goto err; /* NOTE: does not make a copy of string, ie argument is 'consumed' here */ @@ -343,8 +343,9 @@ unknown_stmt : ustring ':' ustring ';' } | ustring ':' ustring ' ' string ';' { char *id; if ((id=prefix_id_join($1, $3)) == NULL) _YYERROR("0"); - if (ysp_add(_yy, Y_UNKNOWN, id, $5) == NULL) _YYERROR("0"); + if (ysp_add(_yy, Y_UNKNOWN, id, $5) == NULL){ _YYERROR("0"); } clicon_debug(2,"unknown-stmt -> ustring : ustring string"); + if ($5) free($5); } ; diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 1d7f57d4..68204c9b 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #define __USE_GNU /* strverscmp */ @@ -354,7 +355,7 @@ clicon_type2cv(char *origtype, if (restype != NULL){ yang2cv_type(restype, cvtype); if (*cvtype == CGV_ERR){ - clicon_err(OE_DB, 0, "%s: \"%s\" type not translated", __FUNCTION__, restype); + clicon_err(OE_DB, 0, "\"%s\" type not translated", restype); goto done; } } @@ -365,7 +366,7 @@ clicon_type2cv(char *origtype, */ yang2cv_type(origtype, cvtype); if (*cvtype == CGV_ERR){ - clicon_err(OE_DB, 0, "%s: \"%s\": type not resolved", __FUNCTION__, origtype); + clicon_err(OE_DB, 0, "\"%s\": type not resolved", origtype); goto done; } } @@ -380,7 +381,7 @@ clicon_type2cv(char *origtype, (rmax && (i) > cv_##type##_get(rmax))) -/*! +/*! Validate CLIgen variable * @retval -1 Error (fatal), with errno set to indicate error * @retval 0 Validation not OK, malloced reason is returned. Free reason with free() * @retval 1 Validation OK @@ -399,9 +400,13 @@ cv_validate1(cg_var *cv, int retval = 1; /* OK */ int retval2; yang_stmt *yi = NULL; - uint64_t u = 0; - int64_t i = 0; + unsigned int u = 0; + int i = 0; char *str; + int found; + char **vec = NULL; + int nvec; + char *v; if (reason && *reason){ free(*reason); @@ -413,7 +418,7 @@ cv_validate1(cg_var *cv, i = cv_int8_get(cv); if (range_check(i, range_min, range_max, int8)){ if (reason) - *reason = cligen_reason("Number out of range: %ld", i); + *reason = cligen_reason("Number out of range: %d", i); retval = 0; break; } @@ -424,7 +429,7 @@ cv_validate1(cg_var *cv, i = cv_int16_get(cv); if (range_check(i, range_min, range_max, int16)){ if (reason) - *reason = cligen_reason("Number out of range: %ld", i); + *reason = cligen_reason("Number out of range: %d", i); retval = 0; break; } @@ -435,29 +440,31 @@ cv_validate1(cg_var *cv, i = cv_int32_get(cv); if (range_check(i, range_min, range_max, int32)){ if (reason) - *reason = cligen_reason("Number out of range: %ld", i); + *reason = cligen_reason("Number out of range: %d", i); retval = 0; break; } } break; - case CGV_INT64: + case CGV_INT64:{ + int64_t i64; if ((options & YANG_OPTIONS_RANGE) != 0){ - i = cv_int64_get(cv); + i64 = cv_int64_get(cv); if (range_check(i, range_min, range_max, int64)){ if (reason) - *reason = cligen_reason("Number out of range: %ld", i); + *reason = cligen_reason("Number out of range: %" PRId64, i64); retval = 0; break; } } break; + } case CGV_UINT8: if ((options & YANG_OPTIONS_RANGE) != 0){ u = cv_uint8_get(cv); if (range_check(u, range_min, range_max, uint8)){ if (reason) - *reason = cligen_reason("Number out of range: %lu", u); + *reason = cligen_reason("Number out of range: %u", u); retval = 0; break; } @@ -468,7 +475,7 @@ cv_validate1(cg_var *cv, u = cv_uint16_get(cv); if (range_check(u, range_min, range_max, uint16)){ if (reason) - *reason = cligen_reason("Number out of range: %lu", u); + *reason = cligen_reason("Number out of range: %u", u); retval = 0; break; } @@ -479,29 +486,31 @@ cv_validate1(cg_var *cv, u = cv_uint32_get(cv); if (range_check(u, range_min, range_max, uint32)){ if (reason) - *reason = cligen_reason("Number out of range: %lu", u); + *reason = cligen_reason("Number out of range: %u", u); retval = 0; break; } } break; - case CGV_UINT64: + case CGV_UINT64:{ + uint64_t u64; if ((options & YANG_OPTIONS_RANGE) != 0){ - u = cv_uint64_get(cv); + u64 = cv_uint64_get(cv); if (range_check(u, range_min, range_max, uint64)){ if (reason) - *reason = cligen_reason("Number out of range: %lu", u); + *reason = cligen_reason("Number out of range: %" PRIu64, u64); retval = 0; break; } } break; + } case CGV_DEC64: if ((options & YANG_OPTIONS_RANGE) != 0){ i = cv_int64_get(cv); if (range_check(i, range_min, range_max, int64)){ if (reason) - *reason = cligen_reason("Number out of range: %ld", i); + *reason = cligen_reason("Number out of range: %d", i); retval = 0; break; } @@ -510,29 +519,57 @@ cv_validate1(cg_var *cv, case CGV_STRING: case CGV_REST: str = cv_string_get(cv); - if (restype && - (strcmp(restype, "enumeration") == 0 || strcmp(restype, "bits") == 0)){ - int found = 0; - while ((yi = yn_each((yang_node*)yrestype, yi)) != NULL){ - if (yi->ys_keyword != Y_ENUM && yi->ys_keyword != Y_BIT) - continue; - if (strcmp(yi->ys_argument, str) == 0){ - found++; + if (restype){ + if (strcmp(restype, "enumeration") == 0){ + found = 0; + while ((yi = yn_each((yang_node*)yrestype, yi)) != NULL){ + if (yi->ys_keyword != Y_ENUM) + continue; + if (strcmp(yi->ys_argument, str) == 0){ + found++; + break; + } + } + if (!found){ + if (reason) + *reason = cligen_reason("'%s' does not match enumeration", str); + retval = 0; break; } } - if (!found){ - if (reason) - *reason = cligen_reason("'%s' does not match enumeration", str); - retval = 0; - break; + if (strcmp(restype, "bits") == 0){ + /* The lexical representation of the bits type is a space-separated list + * of the names of the bits that are set. A zero-length string thus + * represents a value where no bits are set. + */ + if ((vec = clicon_strsep(str, " \t", &nvec)) == NULL) + goto done; + for (i=0; iys_keyword != Y_BIT) + continue; + if (strcmp(yi->ys_argument, v) == 0){ + found++; + break; + } + } + if (!found){ + if (reason) + *reason = cligen_reason("'%s' does not match enumeration", v); + retval = 0; + break; + } + } } } if ((options & YANG_OPTIONS_LENGTH) != 0){ u = strlen(str); if (range_check(u, range_min, range_max, uint64)){ if (reason) - *reason = cligen_reason("string length out of range: %lu", u); + *reason = cligen_reason("string length out of range: %u", u); retval = 0; break; } @@ -571,9 +608,11 @@ cv_validate1(cg_var *cv, case CGV_EMPTY: /* XXX */ break; } - if (reason && *reason) assert(retval == 0); /* validation failed with error reason */ + done: + if (vec) + free(vec); return retval; } @@ -713,8 +752,8 @@ ys_cv_validate(cg_var *cv, if (cvtype == CGV_STRING && cv_type_get(ycv) == CGV_REST) ; else { - clicon_err(OE_DB, 0, "%s: Type mismatch data:%s != yang:%s", - __FUNCTION__, cv_type2str(cvtype), cv_type2str(cv_type_get(ycv))); + clicon_err(OE_DB, 0, "Type mismatch data:%s != yang:%s", + cv_type2str(cvtype), cv_type2str(cv_type_get(ycv))); goto done; } } @@ -728,7 +767,7 @@ ys_cv_validate(cg_var *cv, } else if ((retval = cv_validate1(cv, cvtype, options, range_min, range_max, pattern, - yrestype, restype, reason)) < 0) + yrestype, restype, reason)) < 0) goto done; done: if (cvt) @@ -764,9 +803,9 @@ ys_typedef_up(yang_stmt *ys) return (yang_stmt*)ys; } -/*! Return yang-stmt of identity +/*! Find identity yang-stmt This is a sanity check of base identity of identity-ref and for identity - statements. + statements when parsing. Return true if node is identityref and is derived from identity_name The derived-from() function returns true if the (first) node (in @@ -779,12 +818,17 @@ ys_typedef_up(yang_stmt *ys) identityref's base identity. 1. (base) identity must exist (be found). This is a sanity check of the specification and also necessary for identity statements. + (This is what is dine here) 2. Check if a given node has value derived from base identity. This is a run-time check necessary when validating eg netconf. + (This is validation) 3. Find all valid derived identities from a identityref base identity. - This is for cli generation. - Så vad är det denna function ska göra? Svar: 1 -*/ + (This is for cli generation) + * @param[in] ys Yang spec of id statement + * @param[in] identity Identity string -check if it exists + * @retval 0 OK + * @see validate_identityref for (2) above + */ yang_stmt * yang_find_identity(yang_stmt *ys, char *identity) @@ -864,14 +908,14 @@ resolve_restrictions(yang_stmt *yrange, } /*! Recursively resolve a yang type to built-in type with optional restrictions - * @param [in] ys yang-stmt from where the current search is based - * @param [in] ytype yang-stmt object containing currently resolving type - * @param [out] yrestype resolved type. return built-in type or NULL. mandatory - * @param [out] options pointer to flags field of optional values. optional - * @param [out] mincv pointer to cv with min range or length. If options&YANG_OPTIONS_RANGE - * @param [out] maxcv pointer to cv with max range or length. If options&YANG_OPTIONS_RANGE - * @param [out] pattern pointer to static string of yang string pattern. optional - * @param [out] fraction for decimal64, how many digits after period + * @param[in] ys yang-stmt from where the current search is based + * @param[in] ytype yang-stmt object containing currently resolving type + * @param[out] yrestype resolved type. return built-in type or NULL. mandatory + * @param[out] options pointer to flags field of optional values. optional + * @param[out] mincv pointer to cv with min range or length. If options&YANG_OPTIONS_RANGE + * @param[out] maxcv pointer to cv with max range or length. If options&YANG_OPTIONS_RANGE + * @param[out] pattern pointer to static string of yang string pattern. optional + * @param[out] fraction for decimal64, how many digits after period * @retval 0 OK. Note yrestype may still be NULL. * @retval -1 Error, clicon_err handles errors * The setting of the options argument has the following semantics: @@ -956,7 +1000,7 @@ yang_type_resolve(yang_stmt *ys, if (rytypedef != NULL){ /* We have found a typedef */ /* Find associated type statement */ if ((rytype = yang_find((yang_node*)rytypedef, Y_TYPE, NULL)) == NULL){ - clicon_err(OE_DB, 0, "%s: mandatory type object is not found", __FUNCTION__); + clicon_err(OE_DB, 0, "mandatory type object is not found"); goto done; } /* recursively resolve this new type */ @@ -994,14 +1038,14 @@ yang_type_resolve(yang_stmt *ys, * if (options & YANG_OPTIONS_PATTERN != 0) * printf("regexp: %s\n", pattern); * @endcode - * @param [in] ys yang-stmt, leaf or leaf-list - * @param [out] origtype original type may be derived or built-in - * @param [out] yrestype pointer to resolved type stmt. should be built-in or NULL - * @param [out] options pointer to flags field of optional values - * @param [out] mincv pointer to cv of min range or length. optional - * @param [out] maxcv pointer to cv of max range or length. optional - * @param [out] pattern pointer to static string of yang string pattern. optional - * @param [out] fraction for decimal64, how many digits after period + * @param[in] ys yang-stmt, leaf or leaf-list + * @param[out] origtype original type may be derived or built-in + * @param[out] yrestype pointer to resolved type stmt. should be built-in or NULL + * @param[out] options pointer to flags field of optional values + * @param[out] mincv pointer to cv of min range or length. optional + * @param[out] maxcv pointer to cv of max range or length. optional + * @param[out] pattern pointer to static string of yang string pattern. optional + * @param[out] fraction for decimal64, how many digits after period * @retval 0 OK, but note that restype==NULL means not resolved. * @retval -1 Error, clicon_err handles errors * The setting of the options argument has the following semantics: @@ -1031,7 +1075,7 @@ yang_type_get(yang_stmt *ys, *options = 0x0; /* Find mandatory type */ if ((ytype = yang_find((yang_node*)ys, Y_TYPE, NULL)) == NULL){ - clicon_err(OE_DB, 0, "%s: mandatory type object is not found", __FUNCTION__); + clicon_err(OE_DB, 0, "mandatory type object is not found"); goto done; } /* XXX: here we seem to have some problems if type is union */ diff --git a/test/Jenkinsfile b/test/Jenkinsfile new file mode 100644 index 00000000..01219556 --- /dev/null +++ b/test/Jenkinsfile @@ -0,0 +1,36 @@ +node { + stage('Checkout') { + git url: 'https://github.com/clicon/clixon.git' + checkout scm + } + + stage('Configure') { + /* `make check` returns non-zero on test failures, + * using `true` to allow the Pipeline to continue nonetheless + */ + sh 'make clean' + sh './configure' + } + + stage('Make') { + /* `make check` returns non-zero on test failures, + * using `true` to allow the Pipeline to continue nonetheless + */ + sh 'make' + } + stage('Make install') { + sh 'sudo make install' + } + stage('Make install-include') { + sh 'sudo make install-include' + } + stage('Make Example') { + sh 'cd example' + sh 'make' + sh 'sudo make install' + } + stage('Testing') { + sh 'cd test' + sh './all.sh' + } +} diff --git a/test/lib.sh b/test/lib.sh index 061730f6..35ab1c7a 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -5,6 +5,10 @@ testnr=0 testname= +# Set to 1 to enable old XSL implementation. Set to nothing, or comment if new. +# @see include/clixon_custom.h +#COMPAT_XSL=1 + # For memcheck #clixon_cli="valgrind --leak-check=full --show-leak-kinds=all clixon_cli" clixon_cli=clixon_cli @@ -36,10 +40,11 @@ err(){ if [ $# -gt 1 ]; then echo "Received: $2" fi - echo -e "\e[0m:" + echo -e "\e[0m" echo "$ret"| od -t c > $dir/clixon-ret echo "$expect"| od -t c > $dir/clixon-expect - diff $dir/clixon-ret $dir/clixon-expect + diff $dir/clixon-expect $dir/clixon-ret + exit $testnr } @@ -55,21 +60,28 @@ new2(){ >&2 echo -n "Test$testnr [$1]" } -# clixon tester. First arg is command and second is expected outcome +# clixon command tester. +# Arguments: +# - command, +# - expected command return value (0 if OK) +# - expected stdout outcome, +# - expected2 stdout outcome, expectfn(){ cmd=$1 - expect=$2 + retval=$2 + expect="$3" - if [ $# = 3 ]; then - expect2=$3 + if [ $# = 4 ]; then + expect2=$4 else expect2= fi ret=$($cmd) - -# if [ $? -ne 0 ]; then -# err "wrong args" -# fi + if [ $? -ne $retval ]; then + echo -e "\e[31m\nError in Test$testnr [$testname]:" + echo -e "\e[0m:" + exit -1 + fi # Match if both are empty string if [ -z "$ret" -a -z "$expect" ]; then return @@ -79,7 +91,6 @@ expectfn(){ fi # grep extended grep match=`echo $ret | grep -EZo "$expect"` - if [ -z "$match" ]; then err "$expect" "$ret" fi @@ -104,18 +115,29 @@ expecteq(){ fi } -# clixon tester. First arg is command second is stdin and -# third is expected outcome +# Pipe stdin to command +# Arguments: +# - Command +# - expected command return value (0 if OK) +# - stdin input +# - expected stdout outcome expecteof(){ cmd=$1 - input=$2 - expect=$3 + retval=$2 + input=$3 + expect=$4 # Do while read stuff ret=$($cmd<$RULES]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$RULES]]>]]>" "^]]>]]>$" new "commit it" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new2 "auth get (no user: access denied)" expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' @@ -195,6 +195,7 @@ expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf +new "Kill backend" pid=`pgrep clixon_backend` if [ -z "$pid" ]; then err "backend already dead" diff --git a/test/test_auth_ext.sh b/test/test_auth_ext.sh index 373fca1f..c1806891 100755 --- a/test/test_auth_ext.sh +++ b/test/test_auth_ext.sh @@ -1,7 +1,7 @@ #!/bin/bash # Authentication and authorization and IETF NACM # External NACM file -# See RFC 8321 A.2 +# See RFC 8341 A.2 # But replaced ietf-netconf-monitoring with * APPNAME=example @@ -16,9 +16,11 @@ nacmfile=$dir/nacmfile cat < $cfg $cfg - /usr/local/share/$APPNAME/yang + /usr/local/share/example/yang $fyang /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/backend + example_backend.so$ /usr/local/lib/$APPNAME/restconf /usr/local/lib/$APPNAME/cli $APPNAME @@ -36,6 +38,12 @@ EOF cat < $fyang module $APPNAME{ prefix ex; + import ietf-interfaces { + prefix if; + } + import iana-if-type { + prefix ianaift; + } container authentication { description "Example code for enabling www basic auth and some example users"; @@ -61,6 +69,21 @@ module $APPNAME{ type int32; description "something to edit"; } + container interfaces-state { + config false; + list interface{ + key "name"; + leaf name{ + type string; + } + leaf type{ + type string; + } + leaf if-index { + type int32; + } + } + } } EOF @@ -75,7 +98,6 @@ cat < $nacmfile admin admin adm1 - olof limited @@ -147,17 +169,18 @@ sudo clixon_backend -zf $cfg -y $fyang if [ $? -ne 0 ]; then err fi - +sleep 1 new "start backend -s init -f $cfg -y $fyang" # start new backend -sudo clixon_backend -s init -f $cfg -y $fyang +sudo $clixon_backend -s init -f $cfg -y $fyang if [ $? -ne 0 ]; then err fi +sleep 1 new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf -sleep 1 + new "start restconf daemon (-a is enable http basic auth)" sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang -- -a @@ -167,7 +190,7 @@ new "restconf DELETE whole datastore" expecteq "$(curl -u adm1:bar -sS -X DELETE http://localhost/restconf/data)" "" new2 "auth get" -expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data)" '{"data": null} +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}} ' new "Set x to 0" @@ -203,9 +226,28 @@ expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf new2 "guest edit nacm" expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +new "cli show conf as admin" +expectfn "$clixon_cli -1 -U adm1 -l o -f $cfg -y $fyang show conf" 0 "^x 1;$" + +new "cli show conf as limited" +expectfn "$clixon_cli -1 -U wilma -l o -f $cfg -y $fyang show conf" 0 "^x 1;$" + +new "cli show conf as guest" +expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang show conf" 255 "protocol access-denied" + +new "cli rpc as admin" +expectfn "$clixon_cli -1 -U adm1 -l o -f $cfg -y $fyang rpc ipv4" 0 "2.3.4.5" + +new "cli rpc as limited" +expectfn "$clixon_cli -1 -U wilma -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol access-denied default deny" + +new "cli rpc as guest" +expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol access-denied access denied" + new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf +new "Kill backend" pid=`pgrep clixon_backend` if [ -z "$pid" ]; then err "backend already dead" diff --git a/test/test_cli.sh b/test/test_cli.sh index ec71a707..716fb9d3 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -18,6 +18,7 @@ cat < $cfg $cfg /usr/local/share/$APPNAME/yang $APPNAME + /usr/local/lib/$APPNAME/backend /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/cli $APPNAME @@ -36,81 +37,82 @@ if [ $? -ne 0 ]; then err fi new "start backend -s init -f $cfg" -sudo $clixon_backend -s init -f $cfg +sudo $clixon_backend -s init -f $cfg + if [ $? -ne 0 ]; then err fi new "cli tests" new "cli configure top" -expectfn "$clixon_cli -1 -f $cfg set interfaces" "^$" +expectfn "$clixon_cli -1 -f $cfg set interfaces" 0 "^$" new "cli show configuration top (no presence)" -expectfn "$clixon_cli -1 -f $cfg show conf cli" "^$" +expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "^$" new "cli configure delete top" -expectfn "$clixon_cli -1 -f $cfg delete interfaces" "^$" +expectfn "$clixon_cli -1 -f $cfg delete interfaces" 0 "^$" new "cli show configuration delete top" -expectfn "$clixon_cli -1 -f $cfg show conf cli" "^$" +expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "^$" new "cli configure" -expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0" "^$" +expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0" 0 "^$" new "cli show configuration" -expectfn "$clixon_cli -1 -f $cfg show conf cli" "^interfaces interface eth/0/0 enabled true" +expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "^interfaces interface eth/0/0 enabled true" new "cli configure using encoded chars data <&" -expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description \"foo<&bar\"" "" +expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description \"foo<&bar\"" 0 "" + new "cli configure using encoded chars name <&" -expectfn "$clixon_cli -1 -f $cfg set interfaces interface fddi&< type eth" "" +expectfn "$clixon_cli -1 -f $cfg set interfaces interface fddi&< type ianaift:ethernetCsmacd" 0 "" new "cli failed validate" -expectfn "$clixon_cli -1 -f $cfg -l o validate" "Missing mandatory variable" +expectfn "$clixon_cli -1 -f $cfg -l o validate" 255 "Missing mandatory variable" new "cli configure more" -expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 ipv4 address 1.2.3.4 prefix-length 24" "^$" -expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description mydesc" "^$" -expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 type bgp" "^$" +expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 ipv4 address 1.2.3.4 prefix-length 24" 0 "^$" +expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description mydesc" 0 "^$" +expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 type ex:eth" 0 "^$" new "cli show xpath description" -expectfn "$clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description" "mydesc" +expectfn "$clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description" 0 "mydesc" new "cli delete description" -expectfn "$clixon_cli -1 -f $cfg -l o delete interfaces interface eth/0/0 description mydesc" +expectfn "$clixon_cli -1 -f $cfg -l o delete interfaces interface eth/0/0 description mydesc" 0 "" new "cli show xpath no description" -expectfn "$clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description" "^$" +expectfn "$clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description" 0 "^$" new "cli copy interface" -expectfn "$clixon_cli -1 -f $cfg copy interface eth/0/0 to eth99" "^$" +expectfn "$clixon_cli -1 -f $cfg copy interface eth/0/0 to eth99" 0 "^$" new "cli success validate" -expectfn "$clixon_cli -1 -f $cfg -l o validate" "^$" +expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$" new "cli commit" -expectfn "$clixon_cli -1 -f $cfg -l o commit" "^$" +expectfn "$clixon_cli -1 -f $cfg -l o commit" 0 "^$" new "cli save" -expectfn "$clixon_cli -1 -f $cfg -l o save /tmp/foo" "^$" +expectfn "$clixon_cli -1 -f $cfg -l o save /tmp/foo" 0 "^$" new "cli delete all" -expectfn "$clixon_cli -1 -f $cfg -l o delete all" "^$" +expectfn "$clixon_cli -1 -f $cfg -l o delete all" 0 "^$" new "cli load" -expectfn "$clixon_cli -1 -f $cfg -l o load /tmp/foo" "^$" +expectfn "$clixon_cli -1 -f $cfg -l o load /tmp/foo" 0 "^$" new "cli check load" -expectfn "$clixon_cli -1 -f $cfg -l o show conf cli" "^interfaces interface name eth/0/0 type bgp -interfaces interface eth/0/0 ipv4 enabled true" +expectfn "$clixon_cli -1 -f $cfg -l o show conf cli" 0 "interfaces interface eth/0/0 ipv4 enabled true" new "cli debug" -expectfn "$clixon_cli -1 -f $cfg -l o debug level 1" "^$" +expectfn "$clixon_cli -1 -f $cfg -l o debug level 1" 0 "^$" # How to test this? -expectfn "$clixon_cli -1 -f $cfg -l o debug level 0" "^$" +expectfn "$clixon_cli -1 -f $cfg -l o debug level 0" 0 "^$" new "cli rpc" -expectfn "$clixon_cli -1 -f $cfg -l o rpc ipv4" "^" +expectfn "$clixon_cli -1 -f $cfg -l o rpc ipv4" 0 "ipv4" "2.3.4.5" new "Kill backend" # Check if still alive diff --git a/test/test_datastore.sh b/test/test_datastore.sh index b0714b4d..8427ed9f 100755 --- a/test/test_datastore.sh +++ b/test/test_datastore.sh @@ -6,7 +6,7 @@ . ./lib.sh fyang=$dir/ietf-ip.yang -datastore=datastore_client +datastore=../datastore/datastore_client cat < $fyang module ietf-ip{ @@ -57,99 +57,99 @@ run(){ conf="-d candidate -b $mydir -p ../datastore/$name/$name.so -y $dir -m ietf-ip" new "datastore $name init" - expectfn "$datastore $conf init" "" + expectfn "$datastore $conf init" 0 "" # Whole tree operations new "datastore $name put all replace" - expectfn "$datastore $conf put replace $db" "" + expectfn "$datastore $conf put replace $db" 0 "" new "datastore $name get" - expectfn "$datastore $conf get /" "^$db$" + expectfn "$datastore $conf get /" 0 "^$db$" new "datastore $name put all remove" - expectfn "$datastore $conf put remove " + expectfn "$datastore $conf put remove " 0 "" new "datastore $name get" - expectfn "$datastore $conf get /" "^$" + expectfn "$datastore $conf get /" 0 "^$" new "datastore $name put all merge" - expectfn "$datastore $conf put merge $db" "" + expectfn "$datastore $conf put merge $db" 0 "" new "datastore $name get" - expectfn "$datastore $conf get /" "^$db$" + expectfn "$datastore $conf get /" 0 "^$db$" new "datastore $name put all delete" - expectfn "$datastore $conf put remove " + expectfn "$datastore $conf put remove " 0 "" new "datastore $name get" - expectfn "$datastore $conf get /" "^$" + expectfn "$datastore $conf get /" 0 "^$" new "datastore $name put all create" - expectfn "$datastore $conf put create $db" "" + expectfn "$datastore $conf put create $db" 0 "" new "datastore $name get" - expectfn "$datastore $conf get /" "^$db$" + expectfn "$datastore $conf get /" 0 "^$db$" new "datastore $name put top create" - expectfn "$datastore $conf put create " "" # error + expectfn "$datastore $conf put create " 0 "" # error # Single key operations # leaf new "datastore $name put all delete" - expectfn "$datastore $conf delete" "" + expectfn "$datastore $conf delete" 0 "" new "datastore $name init" - expectfn "$datastore $conf init" "" + expectfn "$datastore $conf init" 0 "" new "datastore $name create leaf" - expectfn "$datastore $conf put create 13newentry" + expectfn "$datastore $conf put create 13newentry" 0 "" new "datastore $name create leaf" - expectfn "$datastore $conf put create 13newentry" + expectfn "$datastore $conf put create 13newentry" 0 "" new "datastore $name delete leaf" - expectfn "$datastore $conf put delete 13" + expectfn "$datastore $conf put delete 13" 0 "" new "datastore $name replace leaf" - expectfn "$datastore $conf put create 13newentry" + expectfn "$datastore $conf put create 13newentry" 0 "" new "datastore $name remove leaf" - expectfn "$datastore $conf put remove " + expectfn "$datastore $conf put remove " 0 "" new "datastore $name remove leaf" - expectfn "$datastore $conf put remove 13" + expectfn "$datastore $conf put remove 13" 0 "" new "datastore $name delete leaf" - expectfn "$datastore $conf put delete " + expectfn "$datastore $conf put delete " 0 "" new "datastore $name merge leaf" - expectfn "$datastore $conf put merge nalle" + expectfn "$datastore $conf put merge nalle" 0 "" new "datastore $name replace leaf" - expectfn "$datastore $conf put replace nalle" + expectfn "$datastore $conf put replace nalle" 0 "" new "datastore $name merge leaf" - expectfn "$datastore $conf put merge 13newentry" + expectfn "$datastore $conf put merge 13newentry" 0 "" new "datastore $name replace leaf" - expectfn "$datastore $conf put replace 13newentry" + expectfn "$datastore $conf put replace 13newentry" 0 "" new "datastore $name create leaf" - expectfn "$datastore $conf put create aaa" + expectfn "$datastore $conf put create aaa" 0 "" new "datastore $name create leaf" - expectfn "$datastore $conf put create 13newentry" + expectfn "$datastore $conf put create 13newentry" 0 "" new "datastore other db init" - expectfn "$datastore -d kalle -b $mydir -p ../datastore/$name/$name.so -y $dir -m ietf-ip init" + expectfn "$datastore -d kalle -b $mydir -p ../datastore/$name/$name.so -y $dir -m ietf-ip init" 0 "" new "datastore other db copy" - expectfn "$datastore $conf copy kalle" "" + expectfn "$datastore $conf copy kalle" 0 "" diff $mydir/kalle_db $mydir/candidate_db new "datastore lock" - expectfn "$datastore $conf lock 756" "" + expectfn "$datastore $conf lock 756" 0 "" #leaf-list diff --git a/test/test_identity.sh b/test/test_identity.sh new file mode 100755 index 00000000..684d7416 --- /dev/null +++ b/test/test_identity.sh @@ -0,0 +1,195 @@ +#!/bin/bash +# Identity and identityref tests +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh + +cfg=$dir/conf_yang.xml +fyang=$dir/example-my-crypto.yang + +cat < $cfg + + $cfg + $dir + $fyang + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/backend + example_backend.so$ + /usr/local/lib/$APPNAME/netconf + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +EOF + +# Example from RFC7950 Sec 7.18 and 9.10 +# with two changes: the leaf statement is in the original module and +# a transitive dependent identifier (foo) +cat < $dir/example-crypto-base.yang + module example-crypto-base { + yang-version 1.1; + namespace "urn:example:crypto-base"; + prefix "crypto"; + + identity crypto-alg { + description + "Base identity from which all crypto algorithms + are derived."; + } + identity symmetric-key { + description + "Base identity used to identify symmetric-key crypto + algorithms."; + } + identity public-key { + description + "Base identity used to identify public-key crypto + algorithms."; + } + } + +EOF + +cat < $dir/example-des.yang + module example-des { + yang-version 1.1; + namespace "urn:example:des"; + prefix "des"; + import "example-crypto-base" { + prefix "crypto"; + } + identity des { + base "crypto:crypto-alg"; + base "crypto:symmetric-key"; + description "DES crypto algorithm."; + } + identity des3 { + base "crypto:crypto-alg"; + base "crypto:symmetric-key"; + description "Triple DES crypto algorithm."; + } + } +EOF + +cat < $fyang + module example { + yang-version 1.1; + namespace "urn:example:my-crypto"; + prefix mc; + import "example-crypto-base" { + prefix "crypto"; + } + import "example-des" { + prefix "des"; + } + identity aes { + base "crypto:crypto-alg"; + } + identity foo { + description "transitive dependent identifier"; + base "des:des"; + } + leaf crypto { + description "Value can be any transitively derived from crypto-alg"; + type identityref { + base "crypto:crypto-alg"; + } + } + container aes-parameters { + when "../crypto = 'mc:aes'"; + } + } +EOF + +# kill old backend (if any) +new "kill old backend" +sudo clixon_backend -zf $cfg +if [ $? -ne 0 ]; then + err +fi +new "start backend -s init -f $cfg -y $fyang" +# start new backend +sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1 +if [ $? -ne 0 ]; then + err +fi + +new "Set crypto to aes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "aes]]>]]>" "^]]>]]>$" + +new "netconf validate " +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "Set crypto to mc:aes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "mc:aes]]>]]>" "^]]>]]>$" + +new "netconf validate" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "Set crypto to des:des3" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "des:des3]]>]]>" "^]]>]]>$" + +new "netconf validate" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "Set crypto to mc:foo" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "mc:foo]]>]]>" "^]]>]]>$" + +new "netconf validate" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "Set crypto to des:des3 using xmlns" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "des:des3]]>]]>" "^]]>]]>$" + +new "netconf validate" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +# XXX this is not supported +#new "Set crypto to x:des3 using xmlns" +#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "x:des3]]>]]>" "^]]>]]>$" + +#new "netconf validate" +#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "Set crypto to foo:bar" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo:bar]]>]]>" "^]]>]]>$" + +new "netconf validate" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorIdentityref validation failed, foo:bar not derived from crypto-alg]]>]]>$" + +new "cli set crypto to mc:aes" +expectfn "$clixon_cli -1 -f $cfg -l o set crypto mc:aes" 0 "^$" + +new "cli validate" +expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$" + +new "cli set crypto to aes" +expectfn "$clixon_cli -1 -f $cfg -l o set crypto aes" 0 "^$" + +new "cli validate" +expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$" + +new "cli set crypto to des:des3" +expectfn "$clixon_cli -1 -f $cfg -l o set crypto des:des3" 0 "^$" + +new "cli validate" +expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$" + +new "Kill backend" +# Check if still alive +pid=`pgrep clixon_backend` +if [ -z "$pid" ]; then + err "backend already dead" +fi +# kill backend +sudo clixon_backend -zf $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +rm -rf $dir diff --git a/test/test_install.sh b/test/test_install.sh index 5e88336b..032bba4a 100755 --- a/test/test_install.sh +++ b/test/test_install.sh @@ -19,9 +19,6 @@ fi if [ ! -d $dir/www-data ]; then err $dir/www-data fi -if [ ! -f $dir/usr/local/share/clixon/clixon.mk ]; then - err $dir/usr/local/share/clixon/clixon.mk -fi if [ ! -f $dir/usr/local/share/clixon/clixon-config* ]; then err $dir/usr/local/share/clixon/clixon-config* fi diff --git a/test/test_json.sh b/test/test_json.sh new file mode 100755 index 00000000..b3f9e4d6 --- /dev/null +++ b/test/test_json.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Test: JSON parser tests +#PROG="valgrind --leak-check=full --show-leak-kinds=all ../util/clixon_util_json" +PROG=../util/clixon_util_json + +# include err() and new() functions and creates $dir +. ./lib.sh + +new "json parse" +expecteof "$PROG" 0 '{"foo": -23}' "^-23$" + +new "json parse list" +expecteof "$PROG" 0 '{"a":[0,1,2,3]}' "^0123$" + +rm -rf $dir diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 1b7c2990..0e93e704 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -24,9 +24,19 @@ EOF cat < $fyang module example{ + prefix ex; + import ietf-interfaces { + prefix if; + } import ietf-ip { prefix ip; } + identity eth { + base if:interface-type; + } + identity lo { + base if:interface-type; + } container default-address { leaf absname { type leafref { @@ -68,63 +78,66 @@ fi new "start backend -s init -f $cfg -y $fyang" # start new backend -sudo clixon_backend -s init -f $cfg -y $fyang +sudo $clixon_backend -s init -f $cfg -y $fyang if [ $? -ne 0 ]; then err fi new "leafref base config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" " -eth0 eth
192.0.2.1
192.0.2.2
-lolo
127.0.0.1
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " +eth0ex:eth
192.0.2.1
192.0.2.2
+loex:lo
127.0.0.1
]]>]]>" "^]]>]]>$" new "leafref get config" -expecteof "$clixon_netconf -qf $cfg" ']]>]]>' '^eth0' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' new "leafref base commit" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "leafref get config" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' new "leafref add wrong ref" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth3
10.0.4.6
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth3
10.0.4.6
]]>]]>" "^]]>]]>$" new "leafref validate XXX shouldnt really be operation-failed, more work in validate code" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^operation-failed" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failed" new "leafref discard-changes" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "leafref add correct absref" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^]]>]]>$" new "leafref add correct relref" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^]]>]]>$" # XXX add address new "leafref validate (ok)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" new "leafref delete leaf" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth0]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^" new "leafref validate (should fail)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^operation-failed" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failed" new "leafref discard-changes" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "cli leafref lo" -expectfn "$clixon_cli -1f $cfg -y $fyang -l o set default-address absname lo" "^$" +expectfn "$clixon_cli -1f $cfg -y $fyang -l o set default-address absname lo" 0 "^$" new "cli leafref validate" -expectfn "$clixon_cli -1f $cfg -y $fyang -l o validate" "^$" +expectfn "$clixon_cli -1f $cfg -y $fyang -l o validate" 0 "^$" new "cli sender" -expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender a" "^$" +expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender a" 0 "^$" new "cli sender template" -expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender b template a" "^$" +expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender b template a" 0 "^$" new "Kill backend" # Check if still alive diff --git a/test/test_list.sh b/test/test_list.sh new file mode 100755 index 00000000..25e2ee37 --- /dev/null +++ b/test/test_list.sh @@ -0,0 +1,129 @@ +#!/bin/bash +# Yang list / leaf-list operations. min/max-elements + +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh + +exit 0 # NYI + +cfg=$dir/conf_yang.xml +fyang=$dir/test.yang + +cat < $cfg + + $cfg + /usr/local/share/$APPNAME/yang + $APPNAME + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +EOF + +cat < $fyang +module $APPNAME{ + container c{ + presence true; + list a0{ + key k; + leaf k { + type string; + } + min-elements 0; + max-elements "unbounded"; + } + list a1{ + key k; + leaf k { + type string; + } + description "There must be at least one of these"; + min-elements 1; + max-elements 2; + } + leaf-list b0{ + type string; + min-elements 0; + max-elements "unbounded"; + } + leaf-list b1{ + description "There must be at least one of these"; + type string; + min-elements 1; + max-elements 2; + } + } +} +EOF + +# kill old backend (if any) +new "kill old backend" +sudo clixon_backend -zf $cfg -y $fyang +if [ $? -ne 0 ]; then + err +fi + +new "start backend -s init -f $cfg -y $fyang" +# start new backend +sudo $clixon_backend -s init -f $cfg -y $fyang +if [ $? -ne 0 ]; then + err +fi + +new "minmax: minimal" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace00]]>]]>" "^]]>]]>$" + +new "minmax: minimal validate ok" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "minmax: maximal" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace01unbounded0101unbounded01]]>]]>" "^]]>]]>$" + +new "minmax: validate ok" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "minmax: empty" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace]]>]]>" "^]]>]]>$" + +new "minmax: validate should fail" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "minmax: no list" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0]]>]]>" "^]]>]]>$" + +new "minmax: validate should fail" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "minmax: no leaf-list" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0]]>]]>" "^]]>]]>$" + +new "minmax: validate should fail" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "minmax: Too large list" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0120]]>]]>" "^]]>]]>$" + +new "minmax: validate should fail" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "minmax: Too large leaf-list" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0012]]>]]>" "^]]>]]>$" + +new "minmax: validate should fail" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + + + +# kill backend +sudo clixon_backend -zf $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +rm -rf $dir diff --git a/test/test_netconf.sh b/test/test_netconf.sh index b6fc58ac..a520c09a 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -6,6 +6,7 @@ APPNAME=example cfg=$dir/conf_yang.xml fyang=$dir/netconf.yang +tmp=$dir/tmp.x cat < $cfg @@ -30,6 +31,9 @@ EOF cat < $fyang module example{ prefix ex; + import ietf-interfaces { + prefix if; + } import ietf-ip { prefix ip; } @@ -42,6 +46,9 @@ module example{ } rpc empty { } + identity eth { + base if:interface-type; + } rpc client-rpc { description "Example local client-side RPC that is processed by the the netconf/restconf and not sent to the backend. @@ -69,131 +76,170 @@ if [ $? -ne 0 ]; then fi new "start backend -s init -f $cfg -y $fyang" # start new backend -sudo clixon_backend -s init -f $cfg -y $fyang # -D 1 +sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1 if [ $? -ne 0 ]; then err fi new "netconf get-config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' new "Add subtree eth/0/0 using none which should not change anything" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "noneeth/0/0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "noneeth/0/0]]>]]>" "^]]>]]>$" new "Check nothing added" -expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' new "Add subtree eth/0/0 using none and create which should add eth/0/0" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 'eth/0/0ethnone ]]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^]]>]]>$" +# Too many quotes, (single inside double inside single) need to fool bash +if [ -z "$COMPAT_XSL" ]; then +cat < $tmp # new +]]>]]> +EOF +else # old +cat < $tmp +]]>]]> +EOF +fi new "Check eth/0/0 added using xpath" -expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' "^eth/0/0ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth/0/0ex:ethtrue]]>]]>$" new "Re-create same eth/0/0 which should generate error" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 'eth/0/0ethnone ]]>]]>' "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^" new "Delete eth/0/0 using none config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 'eth/0/0ethnone ]]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^]]>]]>$" new "Check deleted eth/0/0 (non-presence container)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' new "Re-Delete eth/0/0 using none should generate error" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 'eth/0/0ethnone ]]>]]>' "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^" new "netconf edit config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth/0/0eth1true
9.2.3.424
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth/0/0eth1true
9.2.3.424
]]>]]>" "^]]>]]>$" + +# Too many quotes +if [ -z "$COMPAT_XSL" ]; then +cat < $tmp # new +]]>]]> +EOF +else +cat < $tmp # old +]]>]]> +EOF +fi new "netconf get config xpath" -expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' "^eth1true]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth1true]]>]]>$" + +# Too many quotes +if [ -z "$COMPAT_XSL" ]; then +cat < $tmp # new +]]>]]> +EOF +else +cat < $tmp # old +]]>]]> +EOF +fi new "netconf get config xpath parent" -expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' "^eth/0/0trueeth1truetruefalse
9.2.3.424
]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth/0/0trueeth1truetruefalse
9.2.3.424
]]>]]>$" new "netconf validate missing type" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get empty config2" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit extra xml" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit config eth1" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth1eth]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth1ex:eth]]>]]>" "^]]>]]>$" new "netconf validate" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf commit" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit config merge" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth2ethmerge]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth2ex:ethmerge]]>]]>" "^]]>]]>$" new "netconf edit ampersand encoding(<&): name:'eth&' type:'t<>'" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth& t< > ]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth& t< > ]]>]]>" "^]]>]]>$" new "netconf get replaced config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^eth& t< > trueeth1ethtrueeth2ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^eth& t< > trueeth1ex:ethtrueeth2ex:ethtrue]]>]]>$" new "cli show configuration eth& - encoding tests" -expectfn "$clixon_cli -1 -f $cfg -y $fyang show conf cli" "interfaces interface eth& type t<> +expectfn "$clixon_cli -1 -f $cfg -y $fyang show conf cli" 0 "interfaces interface eth& type t<> interfaces interface eth& enabled true" +new "netconf edit CDATA" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth/0/0ex:eth]]>]]>" "^]]>]]>$" + +#new "netconf get CDATA" +#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "eth/0/0true]]>]]>" + + new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit state operation should fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth1eth]]>]]>" "^invalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth1ex:eth]]>]]>" "^invalid-value" new "netconf get state operation" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^eth0eth42]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^eth0ex:eth42]]>]]>$" new "netconf lock/unlock" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" new "netconf lock/lock" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf lock" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "close-session" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "kill-session" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "44]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "44]]>]]>" "^]]>]]>$" new "copy startup" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get startup" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^eth1ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^eth1ex:ethtrue]]>]]>$" new "netconf delete startup" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf check empty startup" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf rpc" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "ipv4ipv4]]>]]>" "^ipv4" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ipv4ipv4]]>]]>" "^ipv4" -new "netconf rpc w/o namespace" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "ipv4ipv4]]>]]>" "^ipv4" +new "netconf rpc without namespace" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ipv4ipv4]]>]]>" "^ipv4" new "netconf empty rpc" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf client-side rpc" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "example]]>]]>" "^ok]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "example]]>]]>" "^ok]]>]]>$" new "netconf subscription" expectwait "$clixon_netconf -qf $cfg -y $fyang" "ROUTING]]>]]>" "^]]>]]>Routing notification]]>]]>$" 30 diff --git a/test/test_order.sh b/test/test_order.sh index a046d5c6..848e839a 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -112,60 +112,78 @@ fi new "start backend" # start new backend -sudo clixon_backend -s running -f $cfg -y $fyang +sudo $clixon_backend -s running -f $cfg -y $fyang if [ $? -ne 0 ]; then err fi # Check as file new "verify running from start, should be: l,c,y0,y1,y2,y3; y1 and y3 sorted. Note this fails if XML_SORT set to false" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^hejhoppdbcaabcddbarabarcbarbbarabarbbarcbardbar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhoppdbcaabcddbarabarcbarbbarabarbbarcbardbar]]>]]>$" + +if [ -z "$COMPAT_XSL" ]; then #new new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^abar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^abar]]>]]>$" new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^abar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^abar]]>]]>$" new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^bbar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$" new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^bbar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$" + +else # old + +new "get each ordered-by user leaf-list" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^abar]]>]]>$" + +new "get each ordered-by user leaf-list" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^abar]]>]]>$" + +new "get each ordered-by user leaf-list" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$" + +new "get each ordered-by user leaf-list" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$" + +fi new "delete candidate" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" # LEAF_LISTS new "add two entries to leaf-list user order" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "cb]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "cb]]>]]>" "^]]>]]>$" new "add one entry to leaf-list user order" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "a]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "a]]>]]>" "^]]>]]>$" new "netconf commit" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "add one entry to leaf-list user order after commit" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "0]]>]]>" "^]]>]]>$" new "netconf commit" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "verify leaf-list user order in running (as entered)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^cba0]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^cba0]]>]]>$" # LISTS new "add two entries to list user order" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "cbarbfoo]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "cbarbfoo]]>]]>" "^]]>]]>$" new "add one entry to list user order" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "afie]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "afie]]>]]>" "^]]>]]>$" new "verify list user order (as entered)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^cbarbfooafie]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^cbarbfooafie]]>]]>$" pid=`pgrep clixon_backend` if [ -z "$pid" ]; then diff --git a/test/test_perf.sh b/test/test_perf.sh index 8ad933e1..7fe241a0 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -63,7 +63,7 @@ fi new "start backend -s init -f $cfg -y $fyang" # start new backend -sudo clixon_backend -s init -f $cfg -y $fyang +sudo $clixon_backend -s init -f $cfg -y $fyang if [ $? -ne 0 ]; then err fi @@ -99,13 +99,13 @@ expecteof_file "time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf commit large config again" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf add small (1 entry) config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" "xy]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "xy]]>]]>" "^]]>]]>$" new "netconf get $req small config" time -p for (( i=0; i<$req; i++ )); do @@ -132,7 +132,7 @@ time -p for (( i=0; i<$req; i++ )); do done new "netconf get large config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^0011" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^0011" new "generate large leaf-list config" echo -n "replace" > $fconfig @@ -147,7 +147,7 @@ expecteof_file "time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf add $req small leaf-list config" time -p for (( i=0; i<$req; i++ )); do @@ -156,13 +156,13 @@ time -p for (( i=0; i<$req; i++ )); do done | $clixon_netconf -qf $cfg -y $fyang > /dev/null new "netconf add small leaf-list config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" "x]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "x]]>]]>" "^]]>]]>$" new "netconf commit small leaf-list config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get large leaf-list config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^01" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^01" new "Kill backend" # Check if still alive diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 344b521f..7201eb74 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -31,6 +31,9 @@ EOF cat < $fyang module example{ prefix ex; + import ietf-interfaces { + prefix if; + } import ietf-ip { prefix ip; } @@ -41,6 +44,9 @@ module example{ prefix "inet"; revision-date "2013-07-15"; } + identity eth { + base if:interface-type; + } rpc empty { } rpc input { @@ -68,7 +74,7 @@ module example{ EOF # This is a fixed 'state' implemented in routing_backend. It is assumed to be always there -state='{"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}}' +state='{"interfaces-state": {"interface": \[{"name": "eth0","type": "ex:eth","if-index": 42}\]}}' # kill old backend (if any) new "kill old backend" @@ -77,7 +83,7 @@ if [ $? -ne 0 ]; then err fi new "start backend -s init -f $cfg -y $fyang" -sudo clixon_backend -s init -f $cfg -y $fyang # -D 1 +sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1 if [ $? -ne 0 ]; then err fi @@ -86,7 +92,7 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang # -D +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang -D sleep 1 @@ -107,12 +113,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r ' new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"ex:client-rpc": null,"rt:fib-route": null,"rt:route-count": null}} +expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:empty": null,"example:input": null,"example:output": null,"example:client-rpc": null,"ietf-routing:fib-route": null,"ietf-routing:route-count": null}} ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) -expect="" +expect="" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -130,147 +136,150 @@ if [ -z "$match" ]; then fi new "restconf options. RFC 8040 4.1" -expectfn "curl -i -s -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE" +expectfn "curl -i -s -X OPTIONS http://localhost/restconf/data" 0 "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE" new "restconf head. RFC 8040 4.2" -expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK" +expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK" #Content-Type: application/yang-data+json" new2 "restconf empty rpc" -expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" "" +expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/example:empty)" "" new2 "restconf get empty config + state json" -expecteq "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} +expecteq "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}} ' new "restconf get empty config + state xml" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data) -expect="eth0eth42" +expect="eth0ex:eth42" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf get data/interfaces-state/interface=eth0 json" -expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0)" '{"interface": [{"name": "eth0","type": "eth","if-index": 42}]} +expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0)" '{"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]} ' new "restconf get state operation eth0 xml" # Cant get shell macros to work, inline matching from lib.sh ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/interfaces-state/interface=eth0) -expect="eth0eth42" +expect="eth0ex:eth42" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf get state operation eth0 type json" -expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "eth"} +expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "ex:eth"} ' new "restconf get state operation eth0 type xml" # Cant get shell macros to work, inline matching from lib.sh ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/interfaces-state/interface=eth0/type) -expect="eth" +expect="ex:eth" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf GET datastore" -expecteq "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} +expecteq "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}} ' # Exact match new "restconf Add subtree to datastore using POST" -expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' 'HTTP/1.1 200 OK' +expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 200 OK' new "restconf Re-add subtree which should give error" -expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' +expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' # XXX Cant get this to work -#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' +#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' new "restconf Check interfaces eth/0/0 added" -expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}} +expectfn "curl -s -G http://localhost/restconf/data" 0 '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "ex:eth","if-index": 42}\]}} ' new2 "restconf delete interfaces" expecteq $(curl -s -X DELETE http://localhost/restconf/data/interfaces) "" new "restconf Check empty config" -expectfn "curl -sG http://localhost/restconf/data" "$state" +expectfn "curl -sG http://localhost/restconf/data" 0 "$state" new "restconf Add interfaces subtree eth/0/0 using POST" -expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces' "" +expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}} http://localhost/restconf/data/interfaces' 0 "" # XXX cant get this to work -#expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"eth","enabled":true}}' http://localhost/restconf/data/interfaces)" "" +#expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" "" new2 "restconf Check eth/0/0 added" -expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} +expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}} ' new2 "restconf Re-post eth/0/0 which should generate error" -expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}' http://localhost/restconf/data/interfaces)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "Add leaf description using POST" expecteq "$(curl -s -X POST -d '{"description":"The-first-interface"}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" new "Add nothing using POST" -expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' '"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "malformed-message","error-type": "rpc","error-severity": "error","error-message": " on line 1: syntax error at or before:' +expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "malformed-message","error-type": "rpc","error-severity": "error","error-message": " on line 1: syntax error at or before:' new2 "restconf Check description added" -expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} +expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}} ' new "restconf delete eth/0/0" expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" new "Check deleted eth/0/0" -expectfn 'curl -s -G http://localhost/restconf/data' $state +expectfn 'curl -s -G http://localhost/restconf/data' 0 $state new2 "restconf Re-Delete eth/0/0 using none should generate error" expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-missing","error-type": "application","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} ' new "restconf Add subtree eth/0/0 using PUT" -expecteq "$(curl -s -X PUT -d '{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" +expecteq "$(curl -s -X PUT -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" new2 "restconf get subtree" -expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} +expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}} ' new2 "restconf rpc using POST json" -expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}} +expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}} ' # Cant get this to work due to quoting #new2 "restconf rpc using POST wrong JSON" -#expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/rt:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}}} ' +#expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}}} ' new2 "restconf rpc using POST json w/o mandatory element" -expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' +expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' new2 "restconf rpc non-existing rpc w/o namespace" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' new2 "restconf rpc non-existing rpc" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ex:kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/example:kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' new2 "restconf rpc missing name" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Operation name expected"}}}} ' new2 "restconf rpc missing input" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/rt:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' new "restconf rpc using POST xml" -ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route) +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route) expect="ipv42.3.4.5" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi +new2 "restconf rpc using wrong prefix" +expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang module not found"}}}} ' + new "restconf local client rpc using POST xml" -ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"request":"example"}}' http://localhost/restconf/operations/ex:client-rpc) +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"request":"example"}}' http://localhost/restconf/operations/example:client-rpc) expect="ok" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then @@ -278,11 +287,10 @@ if [ -z "$match" ]; then fi # XXX cant get -H to work -#expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' 'ipv42.3.4.5' +#expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/ietf-routing:fib-route' 'ipv42.3.4.5' # Cant get shell macros to work, inline matching from lib.sh - new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index df401e24..dfd7a265 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -50,7 +50,8 @@ if [ $? -ne 0 ]; then err fi new "start backend -s init -f $cfg -y $fyang" -sudo clixon_backend -s init -f $cfg -y $fyang +sudo $clixon_backend -s init -f $cfg -y $fyang + if [ $? -ne 0 ]; then err fi @@ -66,22 +67,22 @@ sleep 1 new "restconf tests" new "restconf POST initial tree" -expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' "" +expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' +expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' new "restconf GET interface" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0" '{"interface": \[{"name": "local0","type": "regular"}\]}' +expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0" 0 '{"interface": \[{"name": "local0","type": "regular"}\]}' new "restconf GET if-type" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0/type" '{"type": "regular"}' +expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0/type" 0 '{"type": "regular"}' new "restconf POST interface without mandatory type" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/cont1' '"error-message": "Missing mandatory variable: type"' +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/cont1' 0 '"error-message": "Missing mandatory variable: type"' new "restconf POST interface" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "" +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' 0 "" new2 "restconf POST again" expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/cont1)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' @@ -90,51 +91,50 @@ new2 "restconf POST from top" expecteq "$(curl -s -X POST -d '{"cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "restconf DELETE" -expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' "" +expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' 0 "" new "restconf GET null datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": null}' +expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": null}' new "restconf POST initial tree" -expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' "" +expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" new "restconf GET initial tree" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' +expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' new "restconf DELETE whole datastore" -expectfn 'curl -s -X DELETE http://localhost/restconf/data' "" +expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" new "restconf GET null datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": null}' +expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": null}' new "restconf PUT initial datastore" -expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' "" +expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' +expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' new "restconf PUT replace datastore" -expectfn 'curl -s -X PUT -d {"data":{"cont2":{"name":"foo"}}} http://localhost/restconf/data' "" +expectfn 'curl -s -X PUT -d {"data":{"cont2":{"name":"foo"}}} http://localhost/restconf/data' 0 "" new "restconf GET replaced datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont2": {"name": "foo"}}}' - +expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont2": {"name": "foo"}}}' new "restconf PUT initial datastore again" -expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' "" +expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" new "restconf PUT change interface" -expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/cont1/interface=local0' "" +expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/cont1/interface=local0' 0 "" new "restconf GET datastore atm" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}}' +expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}}' new "restconf PUT add interface" -expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' "" +expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 "" new "restconf PUT change key error" -expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "api-path keys do not match data keys"}}}}' +expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "api-path keys do not match data keys"}}}}' new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf diff --git a/test/test_startup.sh b/test/test_startup.sh index d7b5d6a8..d3cdcd54 100755 --- a/test/test_startup.sh +++ b/test/test_startup.sh @@ -43,7 +43,7 @@ run(){ run - eth + ex:eth @@ -55,7 +55,7 @@ EOF startup - eth + ex:eth
@@ -67,7 +67,7 @@ EOF extra - eth + ex:eth
@@ -87,7 +87,7 @@ EOF fi new "Check $mode" - expecteof "$clixon_netconf -qf $cfg" ']]>]]>' "^$expect]]>]]>$" + expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^$expect]]>]]>$" new "Kill backend" # Check if still alive @@ -103,8 +103,8 @@ EOF } run init '' -run none 'runethtrue' -run running 'extraethtruelolocaltruerunethtrue' -run startup 'extraethtruelolocaltruestartupethtrue' +run none 'runex:ethtrue' +run running 'extraex:ethtrueloex:loopbacktruerunex:ethtrue' +run startup 'extraex:ethtrueloex:loopbacktruestartupex:ethtrue' rm -rf $dir diff --git a/test/test_type.sh b/test/test_type.sh index 7788ee02..0c1f11ef 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -122,6 +122,17 @@ module example{ range "min..max"; } }*/ + typedef mybits { + description "Test adding several bits"; + type bits { + bit create; + bit read; + bit write; + } + } + leaf mbits{ + type mybits; + } } EOF @@ -133,49 +144,62 @@ if [ $? -ne 0 ]; then err fi new "start backend -s init -f $cfg -y $fyang" -sudo clixon_backend -s init -f $cfg -y $fyang +sudo $clixon_backend -s init -f $cfg -y $fyang if [ $? -ne 0 ]; then err fi new "cli set ab" -expectfn "$clixon_cli -1f $cfg -l o -y $fyang set list a.b.a.b" "^$" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set list a.b.a.b" 0 "^$" new "cli set cd" -expectfn "$clixon_cli -1f $cfg -l o -y $fyang set list c.d.c.d" "^$" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set list c.d.c.d" 0 "^$" new "cli set ef" -expectfn "$clixon_cli -1f $cfg -l o -y $fyang set list e.f.e.f" "^$" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set list e.f.e.f" 0 "^$" new "cli set ab fail" -expectfn "$clixon_cli -1f $cfg -l o -y $fyang set list a&b&a&b" "^CLI syntax error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set list a&b&a&b" 255 "^CLI syntax error" new "cli set ad fail" -expectfn "$clixon_cli -1f $cfg -l o -y $fyang set list a.b.c.d" "^CLI syntax error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set list a.b.c.d" 255 "^CLI syntax error" new "cli validate" -expectfn "$clixon_cli -1f $cfg -l o -y $fyang -l o validate" "^$" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang -l o validate" 0 "^$" new "cli commit" -expectfn "$clixon_cli -1f $cfg -l o -y $fyang -l o commit" "^$" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang -l o commit" 0 "^$" new "netconf validate ok" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf set ab wrong" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "a.b& c.d]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "a.b& c.d]]>]]>" "^]]>]]>$" new "netconf validate" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf commit" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "cli enum value" -expectfn "$clixon_cli -1f $cfg -l o -y $fyang set status down" "^$" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set status down" 0 "^$" + +new "cli bits value" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set mbits create" 0 "^$" + +#XXX No, cli cant assign two bit values +#new "cli bits two values" +#expectfn "$clixon_cli -1f $cfg -l o -y $fyang set mbits \"create read\"" 0 "^$" + +new "netconf bits two values" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "create read]]>]]>" "^]]>]]>$" + +new "cli bits validate" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang validate" 0 "^$" new "Kill backend" # Check if still alive diff --git a/test/test_when_must.sh b/test/test_when_must.sh new file mode 100755 index 00000000..ab4126e1 --- /dev/null +++ b/test/test_when_must.sh @@ -0,0 +1,151 @@ +#!/bin/bash +# Yang when and must conditional xpath specification +# Testing of validation phase. + +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh + +cfg=$dir/conf_yang.xml +fyang=$dir/test.yang + +cat < $cfg + + $cfg + /usr/local/share/$APPNAME/yang + $APPNAME + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +EOF + +cat < $fyang +module $APPNAME{ + prefix ex; + identity routing-protocol { + description + "Base identity from which routing protocol identities are + derived."; + } + identity direct { + base routing-protocol; + description + "Routing pseudo-protocol which provides routes to directly + connected networks."; + } + identity static { + base routing-protocol; + description + "Static routing pseudo-protocol."; + } + list whenex { + key "type name"; + leaf type { + type identityref { + base routing-protocol; + } + } + leaf name { + type string; + } + leaf route-preference { + type uint32; + } + container static-routes { + when "../type='static'" { + description + "This container is only valid for the 'static' + routing protocol."; + } + presence true; + } + } + container interface { + leaf ifType { + type enumeration { + enum ethernet; + enum atm; + } + } + leaf ifMTU { + type uint32; + } + must 'ifType != "ethernet" or ifMTU = 1500' { + error-message "An Ethernet MTU must be 1500"; + } + must 'ifType != "atm" or' + + ' (ifMTU <= 17966 and ifMTU >= 64)' { + error-message "An ATM MTU must be 64 .. 17966"; + } + } +} +EOF + +# kill old backend (if any) +new "kill old backend" +sudo clixon_backend -zf $cfg -y $fyang +if [ $? -ne 0 ]; then + err +fi + +new "start backend -s init -f $cfg -y $fyang" +# start new backend +sudo $clixon_backend -s init -f $cfg -y $fyang +if [ $? -ne 0 ]; then + err +fi + +new "when: add static route" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "staticr1]]>]]>" "^]]>]]>$" + +new "when: validate ok" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "when: add direct route" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "directr2]]>]]>" "^]]>]]>$" + +new "when get config" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^directr2staticr1]]>]]>$" + +new "when: validate fail" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorxpath static-routes validation failed]]>]]>$" + +new "when: discard-changes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "must: add interface" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ethernet1500]]>]]>" "^]]>]]>$" + +new "must: validate ok" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "must: add atm interface" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "atm32]]>]]>" "^]]>]]>$" + +new "must: atm validate fail" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorAn ATM MTU must be 64 .. 17966]]>]]>$" + +new "must: add eth interface" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ethernet989]]>]]>" "^]]>]]>$" + +new "must: eth validate fail" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorAn Ethernet MTU must be 1500]]>]]>" + +# Check if still alive +pid=`pgrep clixon_backend` +if [ -z "$pid" ]; then + err "backend already dead" +fi +# kill backend +sudo clixon_backend -zf $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +rm -rf $dir diff --git a/test/test_xml.sh b/test/test_xml.sh new file mode 100755 index 00000000..9550c402 --- /dev/null +++ b/test/test_xml.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# Test: XML parser tests +#PROG="valgrind --leak-check=full --show-leak-kinds=all ../util/clixon_util_xml" +PROG=../util/clixon_util_xml + +# include err() and new() functions and creates $dir +. ./lib.sh + +new "xml parse" +expecteof "$PROG" 0 "" "^$" + +XML=$(cat <An example of escaped CENDs + + y" so I guess that means that z > x ]]> + + + +]]> + +]]> + +EOF +) +new "xml CDATA" +expecteof "$PROG" 0 "$XML" "^An example of escaped CENDs + y\" so I guess that means that z > x ]]> +]]>]]>$" + +XML=$(cat <Less than: < , greater than: > ampersand: & +EOF +) +new "xml encode <>&" +expecteof "$PROG" 0 "$XML" "^$XML$" + +XML=$(cat <To allow attribute values to contain both single and double quotes, the apostrophe or single-quote character ' may be represented as ' and the double-quote character as " +EOF +) +new "xml optional encode single and double quote" +expecteof "$PROG" 0 "$XML" "^To allow attribute values to contain both single and double quotes, the apostrophe or single-quote character ' may be represented as ' and the double-quote character as \"$" + +rm -rf $dir + diff --git a/test/test_xpath.sh b/test/test_xpath.sh new file mode 100755 index 00000000..d8fcca34 --- /dev/null +++ b/test/test_xpath.sh @@ -0,0 +1,192 @@ +#!/bin/bash +# Test: XPATH tests +#PROG="valgrind --leak-check=full --show-leak-kinds=all ../util/clixon_util_xpath" +PROG=../util/clixon_util_xpath + +# include err() and new() functions and creates $dir +. ./lib.sh + +# XML file (alt provide it in stdin after xpath) +xml=$dir/xml.xml +xml2=$dir/xml2.xml +xml3=$dir/xml3.xml +xml4=$dir/xml4.xml + +cat < $xml + + + 42 + + + 99 + + + 22 + + +EOF + +cat < $xml2 + + + e0 + + true + + + +e0 +myfamily + + v6ur:ipv6-unicast + foo + + + + + bar + myfamily + + + + 22 + 99 + responder-only + rt:static + bar + 0 + + ethernet + 1500 + + +EOF + +# Multiple leaf-list +cat < $xml3 + + foo + 42 + bar + + + 99 + foo + +EOF + +new "xpath /" +expecteof "$PROG -f $xml -p /" 0 "" "^nodeset:0:429922$" + +new "xpath /aaa" +expecteof "$PROG -f $xml -p /aaa" 0 "" "^nodeset:0:429922$" + +new "xpath /bbb" +expecteof "$PROG -f $xml -p /bbb" 0 "" "^nodeset:$" + +new "xpath /aaa/bbb" +expecteof "$PROG -f $xml -p /aaa/bbb" 0 "" "^0:42 +1:99$" + +new "xpath //bbb" +expecteof "$PROG -f $xml -p //bbb" 0 "" "0:42 +1:99" + +new "xpath //b?b" +#expecteof "$PROG -f $xml" 0 "//b?b" "" + +new "xpath //b*" +#expecteof "$PROG -f $xml" 0 "//b*" "" + +new "xpath //b*/ccc" +#expecteof "$PROG -f $xml" 0 "//b*/ccc" "" + +new "xpath //bbb[0]" +expecteof "$PROG -f $xml -p //bbb[0]" 0 "" "^nodeset:0:42$" + +new "xpath //bbb[ccc=99]" +expecteof "$PROG -f $xml -p //bbb[ccc=99]" 0 "" "^nodeset:0:99$" + +new "xpath ../connection-type = 'responder-only'" +expecteof "$PROG -f $xml2 -p ../connection-type='responder-only' -i /aaa/bbb/here" 0 "" "^bool:true$" + +new "xpath ../connection-type = 'no-responder'" +expecteof "$PROG -f $xml2 -p ../connection-type='no-responder' -i /aaa/bbb/here" 0 "" "^bool:false$" + +new "xpath . <= 0.75 * ../max-rtr-adv-interval" +expecteof "$PROG -f $xml2 -i /aaa/bbb/here" 0 ". <= 0.75 * ../max-rtr-adv-interval" "^bool:true$" + +new "xpath . > 0.75 * ../max-rtr-adv-interval" +expecteof "$PROG -f $xml2 -i /aaa/bbb/here" 0 ". > 0.75 * ../max-rtr-adv-interval" "^bool:false$" + +new "xpath . <= ../valid-lifetime" +expecteof "$PROG -f $xml2 -i /aaa/bbb/here" 0 ". <= ../valid-lifetime" "^bool:true$" + +new "xpath ../../rt:address-family = 'v6ur:ipv6-unicast'" +expecteof "$PROG -f $xml2 -i /aaa/bbb/here" 0 "../../rt:address-family = 'v6ur:ipv6-unicast'" "^bool:true$" + +new "xpath ../../../rt:address-family = 'v6ur:ipv6-unicast'" +expecteof "$PROG -f $xml2 -i /aaa/bbb/here2/here" 0 "../../../rt:address-family = 'v6ur:ipv6-unicast'" "^bool:true$" + +new "xpath /if:interfaces/if:interface[if:name=current()/rt:name]/ip:ipv6/ip:enabled='true'" +expecteof "$PROG -f $xml2" 0 "/if:interfaces/if:interface[if:name=current()/rt:name]/ip:ipv6/ip:enabled='true'" "^bool:true$" + +new "xpath rt:address-family='v6ur:ipv6-unicast'" +expecteof "$PROG -f $xml2 -i /aaa" 0 "rt:address-family='v6ur:ipv6-unicast'" "^bool:true$" + +new "xpath ../type='rt:static'" +expecteof "$PROG -f $xml2 -i /aaa/bbb/here" 0 "../type='rt:static'" "^bool:true$" + +new "xpath rib-name != ../../name" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "rib-name != ../../name" "^bool:true$" + +new "xpath routing/ribs/rib[name=current()/rib-name]/address-family=../../address-family" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "routing/ribs/rib[name=current()/rib-name]/address-family=../../address-family" "^bool:true$" + +new "xpath ifType = \"ethernet\" or ifMTU = 1500" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType = \"ethernet\" or ifMTU = 1500" "^bool:true$" + +new "xpath ifType != \"ethernet\" or ifMTU = 1500" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType != \"ethernet\" or ifMTU = 1500" "^bool:true$" + +new "xpath ifType = \"ethernet\" or ifMTU = 1400" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType = \"ethernet\" or ifMTU = 1400" "^bool:true$" + +new "xpath ifType != \"ethernet\" or ifMTU = 1400" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType != \"ethernet\" or ifMTU = 1400" "^bool:false$" + +new "xpath ifType = \"ethernet\" and ifMTU = 1500" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType = \"ethernet\" and ifMTU = 1500" "^bool:true$" + +new "xpath ifType != \"ethernet\" and ifMTU = 1500" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType != \"ethernet\" and ifMTU = 1500" "^bool:false$" + +new "xpath ifType = \"ethernet\" and ifMTU = 1400" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType = \"ethernet\" and ifMTU = 1400" "^bool:false$" + +new "xpath ifType != \"ethernet\" and ifMTU = 1400" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType != \"ethernet\" and ifMTU = 1400" "^bool:false$" + +new "xpath ifType != \"atm\" or (ifMTU <= 17966 and ifMTU >= 64)" +expecteof "$PROG -f $xml2 -i /aaa/bbb" 0 "ifType != \"atm\" or (ifMTU <= 17966 and ifMTU >= 64)" "^bool:true$" + +new "xpath .[name='bar']" +expecteof "$PROG -f $xml2 -p .[name='bar'] -i /aaa/bbb/routing/ribs/rib" 0 "" "^nodeset:0:barmyfamily$" + +new "Multiple entries" +new "xpath bbb[ccc='foo']" +expecteof "$PROG -f $xml3 -p bbb[ccc='foo']" 0 "" "^nodeset:0:foo42bar1:99foo$" + +new "xpath bbb[ccc='42']" +expecteof "$PROG -f $xml3 -p bbb[ccc='42']" 0 "" "^nodeset:0:foo42bar$" + +new "xpath bbb[ccc=99] (number w/o quotes)" +expecteof "$PROG -f $xml3 -p bbb[ccc=99]" 0 "" "^nodeset:0:99foo$" + +new "xpath bbb[ccc='bar']" +expecteof "$PROG -f $xml3 -p bbb[ccc='bar']" 0 "" "^nodeset:0:foo42bar$" + +new "xpath bbb[ccc='fie']" +expecteof "$PROG -f $xml3 -p bbb[ccc='fie']" 0 "" "^nodeset:$" + +rm -rf $dir diff --git a/test/test_yang.sh b/test/test_yang.sh index 52d4a70f..7ebbbf12 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Test4: Yang specifics: multi-keys and empty type +# Yang specifics: multi-keys and empty type APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh @@ -105,93 +105,101 @@ fi new "start backend -s init -f $cfg -y $fyang" # start new backend -sudo clixon_backend -s init -f $cfg -y $fyang +sudo $clixon_backend -s init -f $cfg -y $fyang if [ $? -ne 0 ]; then err fi new "cli defined extension" -expectfn "$clixon_cli -1f $cfg -y $fyang show version" "3." +expectfn "$clixon_cli -1f $cfg -y $fyang show version" 0 "3." new "cli not defined extension" # This text yields an error, but the test cannot detect the error message yet -#expectfn "$clixon_cli -1f $cfg -y $fyangerr show version" "Yang error: Extension ex:not-defined not found" +#expectfn "$clixon_cli -1f $cfg -y $fyangerr show version" 0 "Yang error: Extension ex:not-defined not found" new "netconf edit config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "125one]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "125one]]>]]>" "^]]>]]>$" new "netconf commit" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" # text empty type in running new "netconf commit 2nd" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get config xpath" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^125one]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^125one]]>]]>$" new "netconf edit leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "hejhopp]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "hejhopp]]>]]>" "^]]>]]>$" new "netconf get leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^hejhopp]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$" +if [ -z "$COMPAT_XSL" ]; then # new new "netconf get leaf-list path" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^hejhopp]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$" +else # old +new "netconf get leaf-list path" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$" +fi new "netconf get (should be some)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^125one]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^125one]]>]]>$" new "cli set leaf-list" -expectfn "$clixon_cli -1f $cfg -y $fyang set x f e foo" "" +expectfn "$clixon_cli -1f $cfg -y $fyang set x f e foo" 0 "" new "cli show leaf-list" -expectfn "$clixon_cli -1f $cfg -y $fyang show xpath /x/f/e" "foo" +expectfn "$clixon_cli -1f $cfg -y $fyang show xpath /x/f/e" 0 "foo" new "netconf set state data (not allowed)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "42]]>]]>" "^invalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^invalid-value" new "netconf set presence and not present" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get presence only" -expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf get presence only" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "netconf anyxml" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf validate anyxml" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf delete candidate" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" # Check 3-keys new "netconf add one 3-key entry" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "111one]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "111one]]>]]>" "^]]>]]>$" new "netconf check add one 3-key" -expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '111one]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111one]]>]]>' new "netconf add another (with same 1st key)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "121two]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "121two]]>]]>" "^]]>]]>$" new "netconf check add another" -expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '111one121two]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111one121two]]>]]>' new "netconf replace first" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "111replace]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "111replace]]>]]>" "^]]>]]>$" new "netconf check replace" -expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '111replace121two]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111replace121two]]>]]>' new "netconf delete first" -expecteof "$clixon_netconf -qf $cfg -y $fyang" '111]]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111]]>]]>' "^]]>]]>$" new "netconf check delete" -expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '121two]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '121two]]>]]>' # Check if still alive pid=`pgrep clixon_backend` diff --git a/test/test_yang_parse.sh b/test/test_yang_parse.sh new file mode 100755 index 00000000..5c3736eb --- /dev/null +++ b/test/test_yang_parse.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Test: XML parser tests +#PROG="valgrind --leak-check=full --show-leak-kinds=all ../util/clixon_util_yang" +PROG=../util/clixon_util_yang + +# include err() and new() functions and creates $dir +. ./lib.sh + +YANG=$(cat < .depend + $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(APPSRC) > .depend #include .depend diff --git a/util/README.md b/util/README.md new file mode 100644 index 00000000..542cb620 --- /dev/null +++ b/util/README.md @@ -0,0 +1,5 @@ +# Clixon utils + +This directory contains Clixon utility programs, ie, programs that are +good to have for testing, analysis, etc, but not an actual part of +delivered code. \ No newline at end of file diff --git a/util/clixon_util_json.c b/util/clixon_util_json.c new file mode 100644 index 00000000..dd16e0e0 --- /dev/null +++ b/util/clixon_util_json.c @@ -0,0 +1,102 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * JSON support functions. + * JSON syntax is according to: + * http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon/clixon.h" + +/* + * Turn this on to get a json parse and pretty print test program + * Usage: xpath + * read json from input + * Example compile: + gcc -g -o json -I. -I../clixon ./clixon_json.c -lclixon -lcligen + * Example run: + echo '{"foo": -23}' | ./json +*/ +static int +usage(char *argv0) +{ + fprintf(stderr, "usage:%s.\n\tInput on stdin\n", argv0); + exit(0); +} + +int +main(int argc, + char **argv) +{ + cxobj *xt = NULL; + cxobj *xc; + cbuf *cb = cbuf_new(); + + if (argc != 1){ + usage(argv[0]); + return 0; + } + clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR); + if (json_parse_file(0, NULL, &xt) < 0) + goto done; + xc = NULL; + while ((xc = xml_child_each(xt, xc, -1)) != NULL) + clicon_xml2cbuf(cb, xc, 0, 0); /* print xml */ + fprintf(stdout, "%s", cbuf_get(cb)); + done: + if (xt) + xml_free(xt); + if (cb) + cbuf_free(cb); + return 0; +} diff --git a/util/clixon_util_xml.c b/util/clixon_util_xml.c new file mode 100644 index 00000000..72730000 --- /dev/null +++ b/util/clixon_util_xml.c @@ -0,0 +1,107 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * XML support functions. + * @see https://www.w3.org/TR/2008/REC-xml-20081126 + * https://www.w3.org/TR/2009/REC-xml-names-20091208 + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon/clixon.h" + +/* + * Turn this on to get a xml parse and pretty print test program + * Usage: xpath + * read xml from input + * Example compile: + gcc -g -o xml -I. -I../clixon ./clixon_xml.c -lclixon -lcligen + * Example run: + echo "" | xml +*/ +static int +usage(char *argv0) +{ + fprintf(stderr, "usage:%s.\n\tInput on stdin\n", argv0); + exit(0); +} + +int +main(int argc, char **argv) +{ + cxobj *xt = NULL; + cxobj *xc; + cbuf *cb = cbuf_new(); + + if (argc != 1){ + usage(argv[0]); + return 0; + } + if (xml_parse_file(0, "
", NULL, &xt) < 0){ + fprintf(stderr, "xml parse error %s\n", clicon_err_reason); + goto done; + } + xc = NULL; + while ((xc = xml_child_each(xt, xc, -1)) != NULL) + clicon_xml2cbuf(cb, xc, 0, 0); /* print xml */ + fprintf(stdout, "%s\n", cbuf_get(cb)); +#if 0 + cbuf_reset(cb); + xmltree2cbuf(cb, xt, 0); /* dump data structures */ + fprintf(stderr, "%s\n", cbuf_get(cb)); +#endif + done: + if (xt) + xml_free(xt); + if (cb) + cbuf_free(cb); + return 0; +} + + diff --git a/util/clixon_util_xpath.c b/util/clixon_util_xpath.c new file mode 100644 index 00000000..2ee8d2e8 --- /dev/null +++ b/util/clixon_util_xpath.c @@ -0,0 +1,231 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + +See https://www.w3.org/TR/xpath/ + + * Turn this on to get an xpath test program + * Usage: xpath [] + * read xpath on first line and xml on rest of lines from input + * Example compile: + gcc -g -o xpath -I. -I../clixon ./clixon_xsl.c -lclixon -lcligen + * Example run: +echo "a\n" | xpath +*/ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon/clixon.h" + +static int +usage(char *argv0) +{ + fprintf(stderr, "usage:%s [options]\n" + "where options are\n" + "\t-h \t\tHelp\n" + "\t-D \tDebug\n" + "\t-f \tXML file\n" + "\t-p \tPrimary XPATH string\n" + "\t-i \t(optional) Initial XPATH string\n" + "and the following extra rules:\n" + "\tif -f is not given, XML input is expected on stdin\n" + "\tif -p is not given, is expected as the first line on stdin\n" + "This means that with no arguments, and XML is expected on stadin.\n", + argv0 + ); + exit(0); +} + +static int +ctx_print2(cbuf *cb, + xp_ctx *xc) +{ + int i; + + cprintf(cb, "%s:", (char*)clicon_int2str(ctxmap, xc->xc_type)); + switch (xc->xc_type){ + case XT_NODESET: + for (i=0; ixc_size; i++){ + cprintf(cb, "%d:", i); + clicon_xml2cbuf(cb, xc->xc_nodeset[i], 0, 0); + } + break; + case XT_BOOL: + cprintf(cb, "%s", xc->xc_bool?"true":"false"); + break; + case XT_NUMBER: + cprintf(cb, "%lf", xc->xc_number); + break; + case XT_STRING: + cprintf(cb, "%s", xc->xc_string); + break; + } + return 0; +} + +int +main(int argc, char **argv) +{ + int retval = -1; + char *argv0 = argv[0]; + int i; + cxobj **xv = NULL; + cxobj *x0 = NULL; + cxobj *x; + char c; + int len; + char *buf = NULL; + int ret; + int fd = 0; /* unless overriden by argv[1] */ + char *xpath = NULL; + char *xpath0 = NULL; + char *filename; + xp_ctx *xc = NULL; + cbuf *cb = NULL; + + clicon_log_init("xpath", LOG_DEBUG, CLICON_LOG_STDERR); + optind = 1; + opterr = 0; + while ((c = getopt(argc, argv, "hDf:p:i:")) != -1) + switch (c) { + case 'h': + usage(argv0); + break; + case 'D': + debug++; + break; + case 'f': /* XML file */ + filename = optarg; + if ((fd = open(filename, O_RDONLY)) < 0){ + clicon_err(OE_UNIX, errno, "open(%s)", argv[1]); + goto done; + } + break; + case 'p': /* Primary XPATH string */ + xpath = optarg; + break; + case 'i': /* Optional initial XPATH string */ + xpath0 = optarg; + break; + default: + usage(argv[0]); + break; + } + if (xpath==NULL){ + /* First read xpath */ + len = 1024; /* any number is fine */ + if ((buf = malloc(len)) == NULL){ + perror("pt_file malloc"); + return -1; + } + memset(buf, 0, len); + i = 0; + while (1){ + if ((ret = read(0, &c, 1)) < 0){ + perror("read"); + goto done; + } + if (ret == 0) + break; + if (c == '\n') + break; + if (len==i){ + if ((buf = realloc(buf, 2*len)) == NULL){ + fprintf(stderr, "%s: realloc: %s\n", __FUNCTION__, strerror(errno)); + return -1; + } + memset(buf+len, 0, len); + len *= 2; + } + buf[i++] = (char)(c&0xff); + } + xpath = buf; + } + /* + * If fd=0, then continue reading from stdin (after CR) + * If fd>0, reading from file opened as argv[1] + */ + if (xml_parse_file(fd, "", NULL, &x0) < 0){ + fprintf(stderr, "Error: parsing: %s\n", clicon_err_reason); + return -1; + } + /* If xpath0 given, position current x */ + if (xpath0){ + if ((x = xpath_first(x0, "%s", xpath0)) == NULL){ + fprintf(stderr, "Error: xpath0 returned NULL\n"); + return -1; + } + } + else + x = x0; + + /* Parse XML */ + if (xpath_vec_ctx(x, xpath, &xc) < 0) + return -1; + /* Print results */ + cb = cbuf_new(); + ctx_print2(cb, xc); + fprintf(stdout, "%s\n", cbuf_get(cb)); + retval = 0; + done: + if (cb) + cbuf_free(cb); + if (xc) + ctx_free(xc); + if (xv) + free(xv); + if (buf) + free(buf); + if (x0) + xml_free(x0); + if (fd > 0) + close(fd); + return retval; +} diff --git a/util/clixon_util_yang.c b/util/clixon_util_yang.c new file mode 100644 index 00000000..22847b0d --- /dev/null +++ b/util/clixon_util_yang.c @@ -0,0 +1,93 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#define __USE_GNU /* strverscmp */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* cligen */ +#include + +/* clixon */ +#include "clixon/clixon.h" + +/* +*/ +static int +usage(char *argv0) +{ + fprintf(stderr, "usage:%s.\n\tInput on stdin\n", argv0); + exit(0); +} + +int +main(int argc, char **argv) +{ + yang_spec *yspec = NULL; + + if (argc != 1){ + usage(argv[0]); + return -1; + } + if ((yspec = yspec_new()) == NULL) + goto done; + if (yang_parse_file(0, "yang test", yspec) < 0){ + fprintf(stderr, "xml parse error %s\n", clicon_err_reason); + return -1; + } + yang_print(stdout, (yang_node*)yspec); + done: + if (yspec) + yspec_free(yspec); + return 0; +} + diff --git a/yang/Makefile.in b/yang/Makefile.in index 66b2621f..469f026b 100644 --- a/yang/Makefile.in +++ b/yang/Makefile.in @@ -38,6 +38,8 @@ bindir = @bindir@ includedir = @includedir@ datarootdir = @datarootdir@ +CLIXON_DATADIR = @CLIXON_DATADIR@ + YANGSPECS = clixon-config@2018-04-30.yang YANGSPECS += ietf-netconf@2011-06-01.yang YANGSPECS += ietf-netconf-acm@2018-02-14.yang @@ -48,22 +50,17 @@ APPNAME = clixon # subdir ehere these files are installed all: -# Note: clixon.mk has a rule for: --include $(DESTDIR)$(datarootdir)/clixon/clixon.mk - clean: distclean: clean rm -f Makefile *~ .depend install: $(YANGSPECS) - echo $(DESTDIR)$(datarootdir)/clixon/clixon.mk - echo $(DESTDIR)$(clixon_DATADIR) - install -d -m 0755 $(DESTDIR)$(clixon_DATADIR) - install -m 0644 $(YANGSPECS) $(DESTDIR)$(clixon_DATADIR) + install -d -m 0755 $(DESTDIR)$(CLIXON_DATADIR) + install -m 0644 $(YANGSPECS) $(DESTDIR)$(CLIXON_DATADIR) -uninstall: - (cd $(DESTDIR)$(clixon_DATADIR); rm -rf $(YANGSPECS)) +uninstall: + (cd $(DESTDIR)$(CLIXON_DATADIR); rm -rf *.yang) install-include: