From 2febe43866ffcaba92eea6b7c0c8947ea9d5515c Mon Sep 17 00:00:00 2001 From: Corey Minyard Date: Wed, 22 May 2024 16:04:36 -0500 Subject: [PATCH 01/10] hello: Add a backend file that prints out info Add a backend, this will print out information about the transaction, but that's it. Signed-off-by: Corey Minyard --- hello/src/Makefile.in | 17 ++++- hello/src/hello.xml.in | 1 + hello/src/hello_backend_plugin.c | 114 +++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 hello/src/hello_backend_plugin.c diff --git a/hello/src/Makefile.in b/hello/src/Makefile.in index 9b2d27d..4193069 100644 --- a/hello/src/Makefile.in +++ b/hello/src/Makefile.in @@ -59,7 +59,17 @@ YANGSPECS = clixon-hello@2019-04-17.yang .PHONY: all clean depend install -all: +# Example backend plugin +# There may also be restconf/cli/netconf plugins which are not covered here, see +# eg clixon main example +PLUGIN = $(APPNAME)_backend_plugin.so +PLUGIN_SRC = $(APPNAME)_backend_plugin.c +PLUGIN_OBJ = $(PLUGIN_SRC:%.c=%.o) + +all: $(PLUGIN) + +$(PLUGIN): $(PLUGIN_OBJ) + $(CC) -Wall -shared $(LDFLAGS) -o $@ -lc $< -lclixon -lclixon_backend .SUFFIXES: .c .o @@ -70,23 +80,26 @@ all: CLISPECS = $(APPNAME)_cli.cli clean: + rm -f $(PLUGIN) $(PLUGIN_OBJ) distclean: clean rm -f Makefile *~ .depend -install: $(CLISPECS) $(APPNAME).xml autocli.xml +install: $(CLISPECS) $(APPNAME).xml autocli.xml $(PLUGIN) install -d -m 0755 $(DESTDIR)$(sysconfdir)/clixon install -d -m 0755 $(DESTDIR)$(sysconfdir)/clixon/$(APPNAME) install -m 0644 $(APPNAME).xml $(DESTDIR)$(sysconfdir)/clixon install -m 0644 autocli.xml $(DESTDIR)$(sysconfdir)/clixon/$(APPNAME) install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME) install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/clispec + install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/backend install -m 0644 $(CLISPECS) $(DESTDIR)$(libdir)/$(APPNAME)/clispec install -d -m 0755 $(DESTDIR)$(localstatedir) install -d -m 0755 $(DESTDIR)$(localstatedir)/$(APPNAME) install -m 0644 startup_db $(DESTDIR)$(localstatedir)/$(APPNAME)/ install -d -m 0755 $(DESTDIR)$(YANG_INSTALLDIR) install -m 0644 $(YANGSPECS) $(DESTDIR)$(YANG_INSTALLDIR) + install -m 0644 $(INSTALLFLAGS) $(PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/backend uninstall: rm -f $(DESTDIR)$(sysconfdir)/clixon/$(APPNAME).xml diff --git a/hello/src/hello.xml.in b/hello/src/hello.xml.in index 6e129df..9851d49 100644 --- a/hello/src/hello.xml.in +++ b/hello/src/hello.xml.in @@ -7,6 +7,7 @@ clixon-hello hello @LIBDIR@/hello/clispec + @LIBDIR@/hello/backend @LOCALSTATEDIR@/hello/hello.sock @LOCALSTATEDIR@/hello/hello.pidfile diff --git a/hello/src/hello_backend_plugin.c b/hello/src/hello_backend_plugin.c new file mode 100644 index 0000000..cb71b0e --- /dev/null +++ b/hello/src/hello_backend_plugin.c @@ -0,0 +1,114 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2021-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* clicon */ +#include + +/* Clicon library functions. */ +#include + +/* These include signatures for plugin and transaction callbacks. */ +#include + +int hello_validate(clicon_handle h, transaction_data td) { + cxobj **cxvec; + size_t i, len; + + printf("*****hello validate*****\n"); + printf("src\n"); + xml_print(stdout, transaction_src(td)); + printf("target\n"); + xml_print(stdout, transaction_target(td)); + printf("dvec\n"); + cxvec = transaction_dvec(td); + len = transaction_dlen(td); + for (i = 0; i < len; i++) { + printf(" [%lu] ", i); + xml_print(stdout, cxvec[i]); + } + printf("avec\n"); + cxvec = transaction_avec(td); + len = transaction_alen(td); + for (i = 0; i < len; i++) { + printf(" [%lu] ", i); + xml_print(stdout, cxvec[i]); + } + printf("scvec\n"); + cxvec = transaction_scvec(td); + len = transaction_clen(td); + for (i = 0; i < len; i++) { + printf(" [%lu] ", i); + xml_print(stdout, cxvec[i]); + } + printf("tcvec\n"); + cxvec = transaction_tcvec(td); + for (i = 0; i < len; i++) { + printf(" [%lu] ", i); + xml_print(stdout, cxvec[i]); + } + return 0; +} + +int hello_commit(clicon_handle h, transaction_data td) { + printf("*****hello commit*****\n"); + return 0; +} + +/* Forward declaration */ +clixon_plugin_api *clixon_plugin_init(clicon_handle h); + +static clixon_plugin_api api = { + "hello backend", + clixon_plugin_init, + .ca_trans_commit = hello_commit, + .ca_trans_validate = hello_validate, +}; + +clixon_plugin_api * +clixon_plugin_init(clicon_handle h) { + printf("*****hello init*****\n"); + return &api; +} From aec77c2c112ab51a7258a6b5b84202836de63064 Mon Sep 17 00:00:00 2001 From: Corey Minyard Date: Fri, 24 May 2024 13:36:20 -0500 Subject: [PATCH 02/10] hello: Extend the backend Add validation of the data, keep a local state and return the proper state information. Signed-off-by: Corey Minyard --- hello/src/hello_backend_plugin.c | 166 +++++++++++++++++++++++++------ 1 file changed, 133 insertions(+), 33 deletions(-) diff --git a/hello/src/hello_backend_plugin.c b/hello/src/hello_backend_plugin.c index cb71b0e..3f6bc62 100644 --- a/hello/src/hello_backend_plugin.c +++ b/hello/src/hello_backend_plugin.c @@ -43,6 +43,7 @@ #include #include #include +#include /* clicon */ #include @@ -53,56 +54,155 @@ /* These include signatures for plugin and transaction callbacks. */ #include -int hello_validate(clicon_handle h, transaction_data td) { - cxobj **cxvec; +static bool hello_state = false; + +static int +hello_begin(clicon_handle h, transaction_data td) { + int *data; + int rv; + + printf("*****hello begin*****\n"); + data = malloc(sizeof(*data)); + if (!data) { + clixon_err(OE_XML, 0, "Could not allocate memory"); + return -1; + } + *data = -1; + rv = transaction_arg_set(td, data); + if (rv) { + free(data); + return rv; + } + return 0; +} + +static int +hello_end(clicon_handle h, transaction_data td) { + int *data = transaction_arg(td); + printf("*****hello end*****\n"); + free(data); + return 0; +} + +static int +find_hello_world(cxobj *vec) +{ + bool world_found = false, ns_found = false; + cxobj *c; + int j; + char *ns = NULL; + + if (strcmp(xml_name(vec), "hello") != 0) + return 0; + for (j = 0; (c = xml_child_i(vec, j)); j++) { + if (strcmp(xml_name(c), "xmlns") == 0) { + if (ns) { + clixon_err(OE_XML, 0, "Multiple xmlns in hello"); + return -1; + } + ns = xml_value(c); + if (!ns || strcmp(ns, "urn:example:hello") != 0) + return 0; + ns_found = true; + continue; + } + if (strcmp(xml_name(c), "world") != 0) { + clixon_err(OE_XML, 0, "Non-\"world\" in hello vec: %s", + xml_name(c)); + return -1; + } + if (world_found) { + clixon_err(OE_XML, 0, "Multiple \"world\" in hello vec"); + return -1; + } + world_found = true; + } + + if (ns_found && world_found) + return 1; + return 0; +} + +static int +hello_validate(clicon_handle h, transaction_data td) { + int *data = transaction_arg(td); + cxobj **vec; size_t i, len; printf("*****hello validate*****\n"); - printf("src\n"); - xml_print(stdout, transaction_src(td)); - printf("target\n"); - xml_print(stdout, transaction_target(td)); - printf("dvec\n"); - cxvec = transaction_dvec(td); + transaction_print(stdout, td); + + vec = transaction_dvec(td); len = transaction_dlen(td); for (i = 0; i < len; i++) { - printf(" [%lu] ", i); - xml_print(stdout, cxvec[i]); + switch (find_hello_world(vec[i])) { + case 0: + break; + case 1: + *data = 0; + break; + default: + return -1; + } } - printf("avec\n"); - cxvec = transaction_avec(td); + + vec = transaction_avec(td); len = transaction_alen(td); for (i = 0; i < len; i++) { - printf(" [%lu] ", i); - xml_print(stdout, cxvec[i]); - } - printf("scvec\n"); - cxvec = transaction_scvec(td); - len = transaction_clen(td); - for (i = 0; i < len; i++) { - printf(" [%lu] ", i); - xml_print(stdout, cxvec[i]); - } - printf("tcvec\n"); - cxvec = transaction_tcvec(td); - for (i = 0; i < len; i++) { - printf(" [%lu] ", i); - xml_print(stdout, cxvec[i]); + switch (find_hello_world(vec[i])) { + case 0: + break; + case 1: + *data = 1; + break; + default: + return -1; + } } + return 0; } -int hello_commit(clicon_handle h, transaction_data td) { - printf("*****hello commit*****\n"); +static int +hello_commit(clicon_handle h, transaction_data td) { + int *data = transaction_arg(td); + + printf("*****hello commit*****: %d\n", *data); + if (*data >= 0) + hello_state = *data; return 0; } -/* Forward declaration */ -clixon_plugin_api *clixon_plugin_init(clicon_handle h); +static int +hello_statedata(clixon_handle h, cvec *nsc, char *xpath, cxobj *xtop) +{ + int retval = -1; + cxobj **xvec = NULL; + + printf("*****hello statedata*****: %d\n", hello_state); + + if (hello_state) { + if (clixon_xml_parse_string("" + "" + "", YB_NONE, NULL, &xtop, NULL) < 0) + goto done; + } else { + if (clixon_xml_parse_string("", YB_NONE, NULL, &xtop, NULL) < 0) + goto done; + } + retval = 0; + done: + if (xvec) + free(xvec); + return retval; +} static clixon_plugin_api api = { - "hello backend", - clixon_plugin_init, + .ca_name = "hello backend", + .ca_init = clixon_plugin_init, + .ca_statedata = hello_statedata, + .ca_trans_begin = hello_begin, + .ca_trans_end = hello_end, .ca_trans_commit = hello_commit, .ca_trans_validate = hello_validate, }; From 4e02a4c1adc6a53b8399c152d49d0c4586b1a7ef Mon Sep 17 00:00:00 2001 From: Corey Minyard Date: Fri, 24 May 2024 14:54:29 -0500 Subject: [PATCH 03/10] hello: use a file for the backend Use the existence of a file for the status for the hello world backend. Signed-off-by: Corey Minyard --- hello/src/hello_backend_plugin.c | 66 ++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/hello/src/hello_backend_plugin.c b/hello/src/hello_backend_plugin.c index 3f6bc62..5528e8a 100644 --- a/hello/src/hello_backend_plugin.c +++ b/hello/src/hello_backend_plugin.c @@ -54,8 +54,16 @@ /* These include signatures for plugin and transaction callbacks. */ #include -static bool hello_state = false; +/* + * This file reflects the state of hello world. If it exists, hello + * world is set. If it does not exit, then hello world is not set. + */ +#define WORLD_FILE "/tmp/world" +/* + * Allocate an integer for us to store the results of validation in. + * That was we don't have to parse again in the commit. + */ static int hello_begin(clicon_handle h, transaction_data td) { int *data; @@ -79,29 +87,46 @@ hello_begin(clicon_handle h, transaction_data td) { static int hello_end(clicon_handle h, transaction_data td) { int *data = transaction_arg(td); + printf("*****hello end*****\n"); free(data); return 0; } +/* + * Validate that the current XML object is of the form: + * + * + * + */ static int find_hello_world(cxobj *vec) { bool world_found = false, ns_found = false; cxobj *c; int j; - char *ns = NULL; if (strcmp(xml_name(vec), "hello") != 0) return 0; + /* + * It would be nice to be able to use nscache_xxx() for this. + * However, it's not always set for some reason. The xmlns entry + * is always in the children, though, so use that. + */ for (j = 0; (c = xml_child_i(vec, j)); j++) { if (strcmp(xml_name(c), "xmlns") == 0) { - if (ns) { + char *ns; + + if (ns_found) { clixon_err(OE_XML, 0, "Multiple xmlns in hello"); return -1; } ns = xml_value(c); - if (!ns || strcmp(ns, "urn:example:hello") != 0) + if (!ns) { + clixon_err(OE_XML, 0, "xmlns has no value"); + return -1; + } + if (strcmp(ns, "urn:example:hello") != 0) return 0; ns_found = true; continue; @@ -115,6 +140,7 @@ find_hello_world(cxobj *vec) clixon_err(OE_XML, 0, "Multiple \"world\" in hello vec"); return -1; } + world_found = true; } @@ -166,10 +192,32 @@ hello_validate(clicon_handle h, transaction_data td) { static int hello_commit(clicon_handle h, transaction_data td) { int *data = transaction_arg(td); + FILE *f; + int rv; printf("*****hello commit*****: %d\n", *data); - if (*data >= 0) - hello_state = *data; + switch (*data) { + case 0: + rv = remove(WORLD_FILE); + if (rv < 0) { + clixon_err(OE_XML, 0, "Error deleting %s: %s", + WORLD_FILE, strerror(errno)); + return -1; + } + break; + + case 1: + f = fopen("/tmp/world", "w"); + if (!f) { + clixon_err(OE_XML, 0, "Error creating %s: %s", + WORLD_FILE, strerror(errno)); + return -1; + } + fclose(f); + + default: + break; + } return 0; } @@ -178,10 +226,12 @@ hello_statedata(clixon_handle h, cvec *nsc, char *xpath, cxobj *xtop) { int retval = -1; cxobj **xvec = NULL; + FILE *f = fopen(WORLD_FILE, "r"); - printf("*****hello statedata*****: %d\n", hello_state); + printf("*****hello statedata*****\n"); - if (hello_state) { + if (f) { + fclose(f); if (clixon_xml_parse_string("" "" "", YB_NONE, NULL, &xtop, NULL) < 0) From a70a0922ab024972eed2fe5ea3e76f66033a76c8 Mon Sep 17 00:00:00 2001 From: Corey Minyard Date: Fri, 24 May 2024 16:34:29 -0500 Subject: [PATCH 04/10] hello: Change the value to an enumeration This lets us demonstrate changing the value. The /tmp/world file contains a "to" value. Signed-off-by: Corey Minyard --- hello/src/clixon-hello@2019-04-17.yang | 12 +- hello/src/hello_backend_plugin.c | 170 ++++++++++++++++++------- 2 files changed, 135 insertions(+), 47 deletions(-) diff --git a/hello/src/clixon-hello@2019-04-17.yang b/hello/src/clixon-hello@2019-04-17.yang index 89c30ac..c40defc 100644 --- a/hello/src/clixon-hello@2019-04-17.yang +++ b/hello/src/clixon-hello@2019-04-17.yang @@ -6,9 +6,15 @@ module clixon-hello { description "Clixon hello world example"; } - container hello{ - container world{ - presence true; + container hello { + leaf to { + type enumeration { + enum city; + enum state; + enum country; + enum world; + } + description "To whom are we saying hello?"; } } } diff --git a/hello/src/hello_backend_plugin.c b/hello/src/hello_backend_plugin.c index 5528e8a..683bd16 100644 --- a/hello/src/hello_backend_plugin.c +++ b/hello/src/hello_backend_plugin.c @@ -57,16 +57,22 @@ /* * This file reflects the state of hello world. If it exists, hello * world is set. If it does not exit, then hello world is not set. + * If it exists, it contains the "to" value. */ #define WORLD_FILE "/tmp/world" +struct hello_data { + char to[10]; + enum { HELLO_NOP, HELLO_ADD, HELLO_DEL } op; +}; + /* * Allocate an integer for us to store the results of validation in. * That was we don't have to parse again in the commit. */ static int hello_begin(clicon_handle h, transaction_data td) { - int *data; + struct hello_data *data; int rv; printf("*****hello begin*****\n"); @@ -75,7 +81,8 @@ hello_begin(clicon_handle h, transaction_data td) { clixon_err(OE_XML, 0, "Could not allocate memory"); return -1; } - *data = -1; + data->op = HELLO_NOP; + memset(data->to, 0, sizeof(data->to)); rv = transaction_arg_set(td, data); if (rv) { free(data); @@ -86,25 +93,31 @@ hello_begin(clicon_handle h, transaction_data td) { static int hello_end(clicon_handle h, transaction_data td) { - int *data = transaction_arg(td); + struct hello_data *data = transaction_arg(td); printf("*****hello end*****\n"); free(data); return 0; } +static const char *valid_tos[] = { "city", "state", "country", "world", NULL }; + /* * Validate that the current XML object is of the form: * - * + * world * + * Returns the contents of "to" in str. + * + * Return value is 0 for not found, 1 for found, and -1 for error. */ static int -find_hello_world(cxobj *vec) +find_hello_to(cxobj *vec, const char **str) { - bool world_found = false, ns_found = false; - cxobj *c; - int j; + bool to_found = false, ns_found = false; + cxobj *c, *c2; + int j, k; + char *s; if (strcmp(xml_name(vec), "hello") != 0) return 0; @@ -131,29 +144,56 @@ find_hello_world(cxobj *vec) ns_found = true; continue; } - if (strcmp(xml_name(c), "world") != 0) { - clixon_err(OE_XML, 0, "Non-\"world\" in hello vec: %s", + if (strcmp(xml_name(c), "to") != 0) { + clixon_err(OE_XML, 0, "Non-\"to\" in hello vec: %s", xml_name(c)); return -1; } - if (world_found) { - clixon_err(OE_XML, 0, "Multiple \"world\" in hello vec"); + if (to_found) { + clixon_err(OE_XML, 0, "Multiple \"to\" in hello vec"); + return -1; + } + + c2 = xml_child_i(c, 0); + if (!c2) { + clixon_err(OE_XML, 0, "The \"to\" element doesn't have data"); + return -1; + } + + if (strcmp(xml_name(c2), "body") != 0) { + clixon_err(OE_XML, 0, "The \"to\" element doesn't have a body"); + return -1; + } + s = xml_value(c2); + if (!s) { + clixon_err(OE_XML, 0, "The \"to\" element doesn't have a value"); + return -1; + } + + for (k = 0; valid_tos[k]; k++) { + if (strcmp(valid_tos[k], s) == 0) + break; + } + if (!valid_tos[k]) { + clixon_err(OE_XML, 0, "Invalid \"to\" element: %s", s); return -1; } - world_found = true; + *str = valid_tos[k]; + to_found = true; } - if (ns_found && world_found) + if (ns_found && to_found) return 1; return 0; } static int hello_validate(clicon_handle h, transaction_data td) { - int *data = transaction_arg(td); + struct hello_data *data = transaction_arg(td); cxobj **vec; size_t i, len; + int rv; printf("*****hello validate*****\n"); transaction_print(stdout, td); @@ -161,29 +201,52 @@ hello_validate(clicon_handle h, transaction_data td) { vec = transaction_dvec(td); len = transaction_dlen(td); for (i = 0; i < len; i++) { - switch (find_hello_world(vec[i])) { - case 0: - break; - case 1: - *data = 0; - break; - default: + const char *place; + + rv = find_hello_to(vec[i], &place); + if (rv == -1) return -1; - } + if (!rv) + continue; + strncpy(data->to, place, sizeof(data->to) - 1); + data->op = HELLO_DEL; } vec = transaction_avec(td); len = transaction_alen(td); for (i = 0; i < len; i++) { - switch (find_hello_world(vec[i])) { - case 0: - break; - case 1: - *data = 1; - break; - default: + const char *place; + + rv = find_hello_to(vec[i], &place); + if (rv == -1) return -1; - } + if (!rv) + continue; + strncpy(data->to, place, sizeof(data->to) - 1); + data->op = HELLO_ADD; + } + + /* + * We do not look at the old data (_scvec), we just look at the + * changed data. The only thing that can change is what is in + * "to", so we check the parent of "to" to verify it. + */ + len = transaction_clen(td); + vec = transaction_tcvec(td); + for (i = 0; i < len; i++) { + cxobj *p = xml_parent(vec[i]); + const char *place; + + if (!p) + continue; + + rv = find_hello_to(p, &place); + if (rv == -1) + return -1; + if (!rv) + continue; + strncpy(data->to, place, sizeof(data->to) - 1); + data->op = HELLO_ADD; } return 0; @@ -191,13 +254,13 @@ hello_validate(clicon_handle h, transaction_data td) { static int hello_commit(clicon_handle h, transaction_data td) { - int *data = transaction_arg(td); + struct hello_data *data = transaction_arg(td); FILE *f; int rv; - printf("*****hello commit*****: %d\n", *data); - switch (*data) { - case 0: + printf("*****hello commit*****: %d\n", data->op); + switch (data->op) { + case HELLO_DEL: rv = remove(WORLD_FILE); if (rv < 0) { clixon_err(OE_XML, 0, "Error deleting %s: %s", @@ -206,16 +269,17 @@ hello_commit(clicon_handle h, transaction_data td) { } break; - case 1: + case HELLO_ADD: f = fopen("/tmp/world", "w"); if (!f) { clixon_err(OE_XML, 0, "Error creating %s: %s", WORLD_FILE, strerror(errno)); return -1; } + fprintf(f, "%s", data->to); fclose(f); - default: + case HELLO_NOP: break; } return 0; @@ -224,27 +288,45 @@ hello_commit(clicon_handle h, transaction_data td) { static int hello_statedata(clixon_handle h, cvec *nsc, char *xpath, cxobj *xtop) { - int retval = -1; + int rv = -1; cxobj **xvec = NULL; FILE *f = fopen(WORLD_FILE, "r"); + char to[10], xmlstr[200]; + int k; - printf("*****hello statedata*****\n"); + printf("*****hello statedata*****: %p\n", f); if (f) { + memset(to, 0, sizeof(to)); + k = fread(to, 1, 9, f); fclose(f); - if (clixon_xml_parse_string("" - "" - "", YB_NONE, NULL, &xtop, NULL) < 0) + + if (!k) { + clixon_err(OE_XML, 0, "Empty %s contents", WORLD_FILE); + goto done; + } + for (k = 0; valid_tos[k]; k++) { + if (strcmp(valid_tos[k], to) == 0) + break; + } + if (!valid_tos[k]) { + clixon_err(OE_XML, 0, "Invalid %s contents: %s", WORLD_FILE, to); + goto done; + } + + snprintf(xmlstr, sizeof(xmlstr), + "%s", to); + if (clixon_xml_parse_string(xmlstr, YB_NONE, NULL, &xtop, NULL) < 0) goto done; } else { if (clixon_xml_parse_string("", YB_NONE, NULL, &xtop, NULL) < 0) goto done; } - retval = 0; + rv = 0; done: if (xvec) free(xvec); - return retval; + return rv; } static clixon_plugin_api api = { From 20473c94f9d832c492c9aee353d80491456b5aa6 Mon Sep 17 00:00:00 2001 From: Corey Minyard Date: Sat, 25 May 2024 17:50:07 -0500 Subject: [PATCH 05/10] hello: Update the version of the hello yang file Because it's changed... Signed-off-by: Corey Minyard --- hello/src/clixon-hello@2019-04-17.yang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hello/src/clixon-hello@2019-04-17.yang b/hello/src/clixon-hello@2019-04-17.yang index c40defc..ef38176 100644 --- a/hello/src/clixon-hello@2019-04-17.yang +++ b/hello/src/clixon-hello@2019-04-17.yang @@ -2,7 +2,7 @@ module clixon-hello { yang-version 1.1; namespace "urn:example:hello"; prefix he; - revision 2019-04-17 { + revision 2024-05-25 { description "Clixon hello world example"; } From ebc97d6a54178894c3fa2eef4f6546f3fe1f08d8 Mon Sep 17 00:00:00 2001 From: Corey Minyard Date: Tue, 28 May 2024 13:49:50 -0500 Subject: [PATCH 06/10] hello: Fix the yang filename Make the versions match. Signed-off-by: Corey Minyard --- hello/src/Makefile.in | 2 +- ...lixon-hello@2019-04-17.yang => clixon-hello@2024-05-25.yang} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename hello/src/{clixon-hello@2019-04-17.yang => clixon-hello@2024-05-25.yang} (100%) diff --git a/hello/src/Makefile.in b/hello/src/Makefile.in index 4193069..f6cc0c9 100644 --- a/hello/src/Makefile.in +++ b/hello/src/Makefile.in @@ -55,7 +55,7 @@ CPPFLAGS = @CPPFLAGS@ -fPIC YANG_INSTALLDIR = $(datadir)/clixon/$(APPNAME) # Local yang files to install -YANGSPECS = clixon-hello@2019-04-17.yang +YANGSPECS = clixon-hello@2024-05-25.yang .PHONY: all clean depend install diff --git a/hello/src/clixon-hello@2019-04-17.yang b/hello/src/clixon-hello@2024-05-25.yang similarity index 100% rename from hello/src/clixon-hello@2019-04-17.yang rename to hello/src/clixon-hello@2024-05-25.yang From 6f5f5a225c7c714f40c99fc48069726198c05c82 Mon Sep 17 00:00:00 2001 From: Corey Minyard Date: Tue, 28 May 2024 13:51:59 -0500 Subject: [PATCH 07/10] hello: Add printing of the full xml source and target trees To see what's in them. The standard functions don't print the prefix or flags. Signed-off-by: Corey Minyard --- hello/src/hello_backend_plugin.c | 65 ++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/hello/src/hello_backend_plugin.c b/hello/src/hello_backend_plugin.c index 683bd16..d1eb8fa 100644 --- a/hello/src/hello_backend_plugin.c +++ b/hello/src/hello_backend_plugin.c @@ -188,6 +188,66 @@ find_hello_to(cxobj *vec, const char **str) return 0; } +static const char *xml_flag_strs[] = { + "mark", + "transient", + "add", + "del", + "change", + "none", + "default", + "top", + "bodykey", + "anydata", + NULL +}; + +static void +print_xml_flags(FILE *f, uint16_t flags) +{ + unsigned int i; + + for (i = 0; xml_flag_strs[i]; i++) { + if (flags & 1 << i) + fprintf(f, " %s", xml_flag_strs[i]); + } +} + +static void +print_xml(FILE *f, int indent, cxobj *x) +{ + char *name; + char *value; + char *prefix; + uint16_t flags; + cxobj *c; + unsigned int i; + + if (!x) + return; + name = xml_name(x); + value = xml_value(x); + prefix = xml_prefix(x); + flags = xml_flag(x, 0xffff); + for (i = 0; i < indent; i++) + fputc(' ', f); + fputs(name, f); + if (value) { + fputc('=', f); + fputs(value, f); + } + if (prefix) { + fputc('(', f); + fputs(value, f); + fputc(')', f); + } + print_xml_flags(f, flags); + fputc('\n', f); + + for (i = 0; (c = xml_child_i(x, i)); i++) + print_xml(f, indent + 2, c); +} + static int hello_validate(clicon_handle h, transaction_data td) { struct hello_data *data = transaction_arg(td); @@ -196,6 +256,11 @@ hello_validate(clicon_handle h, transaction_data td) { int rv; printf("*****hello validate*****\n"); + printf("src:\n"); + print_xml(stdout, 2, transaction_src(td)); + printf("target:\n"); + print_xml(stdout, 2, transaction_target(td)); + printf("transaction:\n"); transaction_print(stdout, td); vec = transaction_dvec(td); From a998b43e32355780f9cb93650ec4f5cf13dc04fa Mon Sep 17 00:00:00 2001 From: Corey Minyard Date: Tue, 28 May 2024 18:30:49 -0500 Subject: [PATCH 08/10] hello: Clean up xml print Handle body nodes more cleanly. Signed-off-by: Corey Minyard --- hello/src/hello_backend_plugin.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/hello/src/hello_backend_plugin.c b/hello/src/hello_backend_plugin.c index d1eb8fa..8ac1998 100644 --- a/hello/src/hello_backend_plugin.c +++ b/hello/src/hello_backend_plugin.c @@ -216,9 +216,7 @@ print_xml_flags(FILE *f, uint16_t flags) static void print_xml(FILE *f, int indent, cxobj *x) { - char *name; - char *value; - char *prefix; + char *name, *value, *prefix, *s; uint16_t flags; cxobj *c; unsigned int i; @@ -242,10 +240,15 @@ print_xml(FILE *f, int indent, cxobj *x) fputc(')', f); } print_xml_flags(f, flags); - fputc('\n', f); - for (i = 0; (c = xml_child_i(x, i)); i++) - print_xml(f, indent + 2, c); + s = xml_body(x); + if (s) { + fprintf(f, ": %s\n", s); + } else { + fputc('\n', f); + for (i = 0; (c = xml_child_i(x, i)); i++) + print_xml(f, indent + 2, c); + } } static int From 6fc2d8034416ed29ab98fcdd4a814ba4c15ec2f3 Mon Sep 17 00:00:00 2001 From: Corey Minyard Date: Wed, 5 Jun 2024 07:58:37 -0500 Subject: [PATCH 09/10] hello: Clean up the hello world example changes Add a lot of comments, replace hand-coded xml functions with standard one, use clixon_debug() instead of printf in a lot of places, and comment out the printing of the XML trees by default. Also show an example of both parsing the source and target trees or parsing the add,delete,change vectors. Signed-off-by: Corey Minyard --- hello/src/hello_backend_plugin.c | 417 ++++++++++++++++++------------- 1 file changed, 247 insertions(+), 170 deletions(-) diff --git a/hello/src/hello_backend_plugin.c b/hello/src/hello_backend_plugin.c index 8ac1998..bd4b2a0 100644 --- a/hello/src/hello_backend_plugin.c +++ b/hello/src/hello_backend_plugin.c @@ -55,47 +55,68 @@ #include /* - * This file reflects the state of hello world. If it exists, hello - * world is set. If it does not exit, then hello world is not set. - * If it exists, it contains the "to" value. + * This file reflects the "system state" of hello world. If it + * exists, hello world is set. If it does not exit, then hello world + * is not set. If it exists, it contains the "to" value. This is + * what is set in the commit call and fetched in the statedata call. */ #define WORLD_FILE "/tmp/world" +#define HELLO_NAMESPACE "urn:example:hello" + +/* + * Set this to 1 to print out the XML data received in validate. + * Only really useful if you run clixon_backend in foreground (-F). + */ +#define DEBUG_XML_STRINGS 0 + +/* + * The operation to perform on the data. This is allocated in the + * begin function, freed in the end function, set in the validate + * function, and implemented in the commit function. + */ struct hello_data { - char to[10]; - enum { HELLO_NOP, HELLO_ADD, HELLO_DEL } op; + char to[10]; /* On HELLO_ADD, the value to put into WORLD_FILE. */ + enum { + HELLO_NOP, /* Don't do anything. */ + HELLO_ADD, /* Create or update the WORLD_FILE. */ + HELLO_DEL /* Delete the WORLD_FILE. */ + } op; }; /* - * Allocate an integer for us to store the results of validation in. - * That was we don't have to parse again in the commit. + * Allocate a structure for us to store the results of validation in. + * This is so we don't have to parse again in the commit. */ static int hello_begin(clicon_handle h, transaction_data td) { struct hello_data *data; int rv; - printf("*****hello begin*****\n"); + clixon_debug(CLIXON_DBG_DEFAULT, "Entry\n"); data = malloc(sizeof(*data)); if (!data) { - clixon_err(OE_XML, 0, "Could not allocate memory"); - return -1; + clixon_err(OE_XML, 0, "Could not allocate memory"); + return -1; } data->op = HELLO_NOP; memset(data->to, 0, sizeof(data->to)); rv = transaction_arg_set(td, data); if (rv) { - free(data); - return rv; + free(data); + return rv; } return 0; } +/* + * Free the data for the transaction. + */ static int hello_end(clicon_handle h, transaction_data td) { struct hello_data *data = transaction_arg(td); - printf("*****hello end*****\n"); + clixon_debug(CLIXON_DBG_DEFAULT, "Entry\n"); free(data); return 0; } @@ -114,80 +135,59 @@ static const char *valid_tos[] = { "city", "state", "country", "world", NULL }; static int find_hello_to(cxobj *vec, const char **str) { - bool to_found = false, ns_found = false; - cxobj *c, *c2; - int j, k; - char *s; + bool to_found = false; + cxobj *c = NULL; + int i; + char *s, *ns; if (strcmp(xml_name(vec), "hello") != 0) - return 0; + return 0; + /* - * It would be nice to be able to use nscache_xxx() for this. - * However, it's not always set for some reason. The xmlns entry - * is always in the children, though, so use that. + * Get the namespace of this object. You can't use xml2ns() here, + * it will search up the tree for a namespace, and and possible + * return the default namespace. We want it for this object only. */ - for (j = 0; (c = xml_child_i(vec, j)); j++) { - if (strcmp(xml_name(c), "xmlns") == 0) { - char *ns; - - if (ns_found) { - clixon_err(OE_XML, 0, "Multiple xmlns in hello"); - return -1; - } - ns = xml_value(c); - if (!ns) { - clixon_err(OE_XML, 0, "xmlns has no value"); - return -1; - } - if (strcmp(ns, "urn:example:hello") != 0) - return 0; - ns_found = true; - continue; - } - if (strcmp(xml_name(c), "to") != 0) { - clixon_err(OE_XML, 0, "Non-\"to\" in hello vec: %s", - xml_name(c)); - return -1; - } - if (to_found) { - clixon_err(OE_XML, 0, "Multiple \"to\" in hello vec"); - return -1; - } - - c2 = xml_child_i(c, 0); - if (!c2) { - clixon_err(OE_XML, 0, "The \"to\" element doesn't have data"); - return -1; - } - - if (strcmp(xml_name(c2), "body") != 0) { - clixon_err(OE_XML, 0, "The \"to\" element doesn't have a body"); - return -1; - } - s = xml_value(c2); - if (!s) { - clixon_err(OE_XML, 0, "The \"to\" element doesn't have a value"); - return -1; - } - - for (k = 0; valid_tos[k]; k++) { - if (strcmp(valid_tos[k], s) == 0) - break; - } - if (!valid_tos[k]) { - clixon_err(OE_XML, 0, "Invalid \"to\" element: %s", s); - return -1; - } - - *str = valid_tos[k]; - to_found = true; + ns = xml_find_type_value(vec, NULL, "xmlns", CX_ATTR); + if (!ns || strcmp(ns, HELLO_NAMESPACE) != 0) + return 0; + + while ((c = xml_child_each(vec, c, CX_ELMNT))) { + if (strcmp(xml_name(c), "to") != 0) { + clixon_err(OE_XML, 0, "Non-\"to\" in hello vec: %s", + xml_name(c)); + return -1; + } + if (to_found) { + clixon_err(OE_XML, 0, "Multiple \"to\" in hello vec"); + return -1; + } + + s = xml_body(c); + if (!s) { + clixon_err(OE_XML, 0, "The \"to\" element doesn't have a value"); + return -1; + } + + for (i = 0; valid_tos[i]; i++) { + if (strcmp(valid_tos[i], s) == 0) + break; + } + if (!valid_tos[i]) { + clixon_err(OE_XML, 0, "Invalid \"to\" element: %s", s); + return -1; + } + + *str = valid_tos[i]; + to_found = true; } - if (ns_found && to_found) - return 1; + if (to_found) + return 1; return 0; } +#if DEBUG_XML_STRINGS static const char *xml_flag_strs[] = { "mark", "transient", @@ -208,8 +208,8 @@ print_xml_flags(FILE *f, uint16_t flags) unsigned int i; for (i = 0; xml_flag_strs[i]; i++) { - if (flags & 1 << i) - fprintf(f, " %s", xml_flag_strs[i]); + if (flags & 1 << i) + fprintf(f, " %s", xml_flag_strs[i]); } } @@ -222,76 +222,98 @@ print_xml(FILE *f, int indent, cxobj *x) unsigned int i; if (!x) - return; + return; name = xml_name(x); value = xml_value(x); prefix = xml_prefix(x); flags = xml_flag(x, 0xffff); for (i = 0; i < indent; i++) - fputc(' ', f); + fputc(' ', f); fputs(name, f); if (value) { - fputc('=', f); - fputs(value, f); + fputc('=', f); + fputs(value, f); } if (prefix) { - fputc('(', f); - fputs(value, f); - fputc(')', f); + fputc('(', f); + fputs(prefix, f); + fputc(')', f); } print_xml_flags(f, flags); s = xml_body(x); if (s) { - fprintf(f, ": %s\n", s); + fprintf(f, ": %s\n", s); } else { - fputc('\n', f); - for (i = 0; (c = xml_child_i(x, i)); i++) - print_xml(f, indent + 2, c); + fputc('\n', f); + for (i = 0; (c = xml_child_i(x, i)); i++) + print_xml(f, indent + 2, c); } } +#endif + +/* + * There are two basic ways to process the data given to validate (and + * commit). This define chooses which. Setting to 1 causes validate + * to go through the add/delete/changed xml tree and handle those. + * Setting to 0 caused validate to directly process the source (the + * old values) and target (the new values) trees for values that are + * flagged ADD, DELETE, or CHANGED. + */ +#define PROCESS_VECS 0 +/* + * Validate the XML. If we find that the WORLD_FILE needs to change, + * we modify the hello_data struct to reflect what needs to be done. + */ static int hello_validate(clicon_handle h, transaction_data td) { struct hello_data *data = transaction_arg(td); + int rv; + const char *place; +#if PROCESS_VECS cxobj **vec; size_t i, len; - int rv; +#else + cxobj *x, *xt; +#endif - printf("*****hello validate*****\n"); - printf("src:\n"); + clixon_debug(CLIXON_DBG_DEFAULT, "Entry\n"); +#if DEBUG_XML_STRINGS + clixon_debug(CLIXON_DBG_DEFAULT, "src:\n"); print_xml(stdout, 2, transaction_src(td)); - printf("target:\n"); + clixon_debug(CLIXON_DBG_DEFAULT, "target:\n"); print_xml(stdout, 2, transaction_target(td)); - printf("transaction:\n"); + clixon_debug(CLIXON_DBG_DEFAULT, "transaction:\n"); transaction_print(stdout, td); +#endif +#if PROCESS_VECS + /* First look through dvec to see if we need to delete WORLD_FILE. */ vec = transaction_dvec(td); len = transaction_dlen(td); for (i = 0; i < len; i++) { - const char *place; - - rv = find_hello_to(vec[i], &place); - if (rv == -1) - return -1; - if (!rv) - continue; - strncpy(data->to, place, sizeof(data->to) - 1); - data->op = HELLO_DEL; + rv = find_hello_to(vec[i], &place); + if (rv == -1) + return -1; + if (!rv) + continue; + strncpy(data->to, place, sizeof(data->to) - 1); + data->op = HELLO_DEL; } + /* Next look through avec to see if we need to add WORLD_FILE. */ vec = transaction_avec(td); len = transaction_alen(td); for (i = 0; i < len; i++) { - const char *place; - - rv = find_hello_to(vec[i], &place); - if (rv == -1) - return -1; - if (!rv) - continue; - strncpy(data->to, place, sizeof(data->to) - 1); - data->op = HELLO_ADD; + + rv = find_hello_to(vec[i], &place); + if (rv == -1) + return -1; + if (!rv) + continue; + strncpy(data->to, place, sizeof(data->to) - 1); + data->op = HELLO_ADD; } /* @@ -302,57 +324,101 @@ hello_validate(clicon_handle h, transaction_data td) { len = transaction_clen(td); vec = transaction_tcvec(td); for (i = 0; i < len; i++) { - cxobj *p = xml_parent(vec[i]); - const char *place; - - if (!p) - continue; - - rv = find_hello_to(p, &place); - if (rv == -1) - return -1; - if (!rv) - continue; - strncpy(data->to, place, sizeof(data->to) - 1); - data->op = HELLO_ADD; + cxobj *p = xml_parent(vec[i]); + + if (!p) + continue; + + rv = find_hello_to(p, &place); + if (rv == -1) + return -1; + if (!rv) + continue; + strncpy(data->to, place, sizeof(data->to) - 1); + data->op = HELLO_ADD; + } +#else + /* + * Get the previous data. Here we only care about deletes, we + * pick up changes in the target tree. + */ + xt = transaction_src(td); + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + rv = find_hello_to(x, &place); + if (rv == -1) + return -1; + if (!rv) + continue; + if (xml_flag(x, XML_FLAG_DEL)) { + strncpy(data->to, place, sizeof(data->to) - 1); + data->op = HELLO_DEL; + } + } + + /* + * Now go through the new data. We look for additions and + * changes, they are treated the same for this. + */ + xt = transaction_target(td); + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + rv = find_hello_to(x, &place); + if (rv == -1) + return -1; + if (!rv) + continue; + if (xml_flag(x, XML_FLAG_ADD | XML_FLAG_CHANGE)) { + strncpy(data->to, place, sizeof(data->to) - 1); + data->op = HELLO_ADD; + } } +#endif return 0; } +/* + * Take the value of hello_data and apply it. If a change was + * requested, the validate call will have set the data appropriately. + */ static int hello_commit(clicon_handle h, transaction_data td) { struct hello_data *data = transaction_arg(td); FILE *f; int rv; - printf("*****hello commit*****: %d\n", data->op); + clixon_debug(CLIXON_DBG_DEFAULT, "op: %d\n", data->op); switch (data->op) { case HELLO_DEL: - rv = remove(WORLD_FILE); - if (rv < 0) { - clixon_err(OE_XML, 0, "Error deleting %s: %s", - WORLD_FILE, strerror(errno)); - return -1; - } - break; + rv = remove(WORLD_FILE); + if (rv < 0 && errno != ENOENT) { + clixon_err(OE_XML, 0, "Error deleting %s: %s", + WORLD_FILE, strerror(errno)); + return -1; + } + break; case HELLO_ADD: - f = fopen("/tmp/world", "w"); - if (!f) { - clixon_err(OE_XML, 0, "Error creating %s: %s", - WORLD_FILE, strerror(errno)); - return -1; - } - fprintf(f, "%s", data->to); - fclose(f); + f = fopen("/tmp/world", "w"); + if (!f) { + clixon_err(OE_XML, 0, "Error creating %s: %s", + WORLD_FILE, strerror(errno)); + return -1; + } + fprintf(f, "%s", data->to); + fclose(f); case HELLO_NOP: - break; + break; } return 0; } +/* + * Retrieve the current state of WORLD_FILE from the filesystem and + * return it in the XML structure xtop. + */ static int hello_statedata(clixon_handle h, cvec *nsc, char *xpath, cxobj *xtop) { @@ -361,34 +427,45 @@ hello_statedata(clixon_handle h, cvec *nsc, char *xpath, cxobj *xtop) FILE *f = fopen(WORLD_FILE, "r"); char to[10], xmlstr[200]; int k; - - printf("*****hello statedata*****: %p\n", f); + char *s; + int found = 0; + + clixon_debug(CLIXON_DBG_DEFAULT, "file: %p\n", f); + clixon_debug(CLIXON_DBG_DEFAULT, " xpath=%s\n", xpath); + for (k = 0; (s = cvec_i_str(nsc, k)) != NULL; k++) { + clixon_debug(CLIXON_DBG_DEFAULT, " nsc(%d)=%s\n", k, s); + if (strcmp(s, HELLO_NAMESPACE) == 0) + found = 1; + } + if (!found) + return -1; if (f) { - memset(to, 0, sizeof(to)); - k = fread(to, 1, 9, f); - fclose(f); - - if (!k) { - clixon_err(OE_XML, 0, "Empty %s contents", WORLD_FILE); - goto done; - } - for (k = 0; valid_tos[k]; k++) { - if (strcmp(valid_tos[k], to) == 0) - break; - } - if (!valid_tos[k]) { - clixon_err(OE_XML, 0, "Invalid %s contents: %s", WORLD_FILE, to); - goto done; - } - - snprintf(xmlstr, sizeof(xmlstr), - "%s", to); - if (clixon_xml_parse_string(xmlstr, YB_NONE, NULL, &xtop, NULL) < 0) - goto done; + memset(to, 0, sizeof(to)); + k = fread(to, 1, 9, f); + fclose(f); + + if (!k) { + clixon_err(OE_XML, 0, "Empty %s contents", WORLD_FILE); + goto done; + } + for (k = 0; valid_tos[k]; k++) { + if (strcmp(valid_tos[k], to) == 0) + break; + } + if (!valid_tos[k]) { + clixon_err(OE_XML, 0, "Invalid %s contents: %s", WORLD_FILE, to); + goto done; + } + + snprintf(xmlstr, sizeof(xmlstr), + "%s", + HELLO_NAMESPACE, to); + if (clixon_xml_parse_string(xmlstr, YB_NONE, NULL, &xtop, NULL) < 0) + goto done; } else { - if (clixon_xml_parse_string("", YB_NONE, NULL, &xtop, NULL) < 0) - goto done; + if (clixon_xml_parse_string("", YB_NONE, NULL, &xtop, NULL) < 0) + goto done; } rv = 0; done: @@ -409,6 +486,6 @@ static clixon_plugin_api api = { clixon_plugin_api * clixon_plugin_init(clicon_handle h) { - printf("*****hello init*****\n"); + clixon_debug(CLIXON_DBG_DEFAULT, "Entry\n"); return &api; } From 90964a592622dbb212fd3635d80ee72364509629 Mon Sep 17 00:00:00 2001 From: Corey Minyard Date: Fri, 7 Jun 2024 23:17:39 -0500 Subject: [PATCH 10/10] Fix the revision of the yang module You aren't supposed to delete the old ones, just add new ones. Signed-off-by: Corey Minyard --- hello/src/clixon-hello@2024-05-25.yang | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hello/src/clixon-hello@2024-05-25.yang b/hello/src/clixon-hello@2024-05-25.yang index ef38176..ee5ebef 100644 --- a/hello/src/clixon-hello@2024-05-25.yang +++ b/hello/src/clixon-hello@2024-05-25.yang @@ -3,6 +3,10 @@ module clixon-hello { namespace "urn:example:hello"; prefix he; revision 2024-05-25 { + description + "Converted to an enumeration"; + } + revision 2019-04-17 { description "Clixon hello world example"; }