diff --git a/lib/clixon/clixon_data.h b/lib/clixon/clixon_data.h new file mode 100644 index 00000000..81c40fd1 --- /dev/null +++ b/lib/clixon/clixon_data.h @@ -0,0 +1,113 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 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 ***** + + * + * Access functions for clixon data. + * Free-typed values for runtime getting and setting. + * Accessed with clicon_data(h). + */ + +#ifndef _CLIXON_DATA_H_ +#define _CLIXON_DATA_H_ + +/* + * Constants + */ +/* default group membership to access config unix socket */ +#define CLICON_SOCK_GROUP "clicon" + +/* + * Types + */ +/* Struct per database in hash */ +typedef struct { + int de_pid; + cxobj *de_xml; /* cache */ +} db_elmnt; + +/* + * Prototypes + */ +yang_spec * clicon_dbspec_yang(clicon_handle h); +int clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys); + +cxobj * clicon_nacm_ext(clicon_handle h); +int clicon_nacm_ext_set(clicon_handle h, cxobj *xn); + +yang_spec * clicon_config_yang(clicon_handle h); +int clicon_config_yang_set(clicon_handle h, struct yang_spec *ys); + +cxobj *clicon_conf_xml(clicon_handle h); +int clicon_conf_xml_set(clicon_handle h, cxobj *x); + +#ifdef XXX +plghndl_t clicon_xmldb_plugin_get(clicon_handle h); +int clicon_xmldb_plugin_set(clicon_handle h, plghndl_t handle); + +void *clicon_xmldb_api_get(clicon_handle h); +int clicon_xmldb_api_set(clicon_handle h, void *xa_api); + + +void *clicon_xmldb_handle_get(clicon_handle h); +int clicon_xmldb_handle_set(clicon_handle h, void *xh); +#endif + +db_elmnt *clicon_db_elmnt_get(clicon_handle h, const char *db); +int clicon_db_elmnt_set(clicon_handle h, const char *db, db_elmnt *xc); + +/**/ +/* Set and get authorized user name */ +char *clicon_username_get(clicon_handle h); +int clicon_username_set(clicon_handle h, void *username); + +/* Set and get startup status */ +enum startup_status clicon_startup_status_get(clicon_handle h); +int clicon_startup_status_set(clicon_handle h, enum startup_status status); + +/* Set and get socket fd (ie backend server socket / restconf fcgx socket */ +int clicon_socket_get(clicon_handle h); +int clicon_socket_set(clicon_handle h, int s); + +/*! Set and get module state full and brief cached tree */ +cxobj *clicon_modst_cache_get(clicon_handle h, int brief); +int clicon_modst_cache_set(clicon_handle h, int brief, cxobj *xms); + +/*! Set and get yang/xml module revision changelog */ +cxobj *clicon_xml_changelog_get(clicon_handle h); +int clicon_xml_changelog_set(clicon_handle h, cxobj *xchlog); + +/*! Set and get user command-line options (after --) */ +int clicon_argv_get(clicon_handle h, int *argc, char ***argv); +int clicon_argv_set(clicon_handle h, char *argv0, int argc, char **argv); + +#endif /* _CLIXON_DATA_H_ */ diff --git a/lib/src/clixon_data.c b/lib/src/clixon_data.c new file mode 100644 index 00000000..8f32bb84 --- /dev/null +++ b/lib/src/clixon_data.c @@ -0,0 +1,595 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 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 ***** + + * + * Access functions for clixon data. + * Free-typed values for runtime getting and setting. + * Accessed with clicon_data(h). + */ +#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 +#include + +/* cligen */ +#include + +/* clicon */ +#include "clixon_err.h" +#include "clixon_string.h" +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_log.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_xml_sort.h" +#include "clixon_options.h" +#include "clixon_plugin.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" +#include "clixon_data.h" + +/*! Get YANG specification for application + * Must use hash functions directly since they are not strings. + */ +yang_spec * +clicon_dbspec_yang(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + size_t len; + void *p; + + if ((p = hash_value(cdat, "dbspec_yang", &len)) != NULL) + return *(yang_spec **)p; + return NULL; +} + +/*! Set yang specification for application + * ys must be a malloced pointer + */ +int +clicon_dbspec_yang_set(clicon_handle h, + struct yang_spec *ys) +{ + clicon_hash_t *cdat = clicon_data(h); + + /* It is the pointer to ys that should be copied by hash, + so we send a ptr to the ptr to indicate what to copy. + */ + if (hash_add(cdat, "dbspec_yang", &ys, sizeof(ys)) == NULL) + return -1; + return 0; +} + +/*! Get NACM (rfc 8341) XML parse tree if external not in std xml config + * @param[in] h Clicon handle + * @retval xn XML NACM tree, or NULL + * @note only used if config option CLICON_NACM_MODE is external + * @see clicon_nacm_ext_set + */ +cxobj * +clicon_nacm_ext(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + size_t len; + void *p; + + if ((p = hash_value(cdat, "nacm_xml", &len)) != NULL) + return *(cxobj **)p; + return NULL; +} + +/*! Set NACM (rfc 8341) external XML parse tree, free old if any + * @param[in] h Clicon handle + * @param[in] xn XML Nacm tree + * @note only used if config option CLICON_NACM_MODE is external + * @see clicon_nacm_ext + */ +int +clicon_nacm_ext_set(clicon_handle h, + cxobj *xn) +{ + clicon_hash_t *cdat = clicon_data(h); + cxobj *xo; + + if ((xo = clicon_nacm_ext(h)) != NULL) + xml_free(xo); + /* It is the pointer to xn that should be copied by hash, + so we send a ptr to the ptr to indicate what to copy. + */ + if (hash_add(cdat, "nacm_xml", &xn, sizeof(xn)) == NULL) + return -1; + return 0; +} + + +#if 1 /* Temporary function until "Top-level Yang symbol cannot be called "config"" is fixed */ +/*! Get YANG specification for clixon config + * Must use hash functions directly since they are not strings. + */ +yang_spec * +clicon_config_yang(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + size_t len; + void *p; + + if ((p = hash_value(cdat, "control_yang", &len)) != NULL) + return *(yang_spec **)p; + return NULL; +} + +/*! Set yang specification for control + * ys must be a malloced pointer + */ +int +clicon_config_yang_set(clicon_handle h, + struct yang_spec *ys) +{ + clicon_hash_t *cdat = clicon_data(h); + + /* It is the pointer to ys that should be copied by hash, + so we send a ptr to the ptr to indicate what to copy. + */ + if (hash_add(cdat, "control_yang", &ys, sizeof(ys)) == NULL) + return -1; + return 0; +} +#endif + +/*! Get YANG specification for Clixon system options and features + * Must use hash functions directly since they are not strings. + * Example: features are typically accessed directly in the config tree. + */ +cxobj * +clicon_conf_xml(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + size_t len; + void *p; + + if ((p = hash_value(cdat, "clixon_conf", &len)) != NULL) + return *(cxobj **)p; + return NULL; +} + +/*! Set YANG specification for Clixon system options and features + * ys must be a malloced pointer + */ +int +clicon_conf_xml_set(clicon_handle h, + cxobj *x) +{ + clicon_hash_t *cdat = clicon_data(h); + + /* It is the pointer to x that should be copied by hash, + * so we send a ptr to the ptr to indicate what to copy. + */ + if (hash_add(cdat, "clixon_conf", &x, sizeof(x)) == NULL) + return -1; + return 0; +} + +#ifdef XXX +/*! Get xmldb datastore plugin handle, as used by dlopen/dlsym/dlclose */ +plghndl_t +clicon_xmldb_plugin_get(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + size_t len; + void *p; + + if ((p = hash_value(cdat, "xmldb_plugin", &len)) != NULL) + return *(plghndl_t*)p; + return NULL; +} + +/*! Set xmldb datastore plugin handle, as used by dlopen/dlsym/dlclose */ +int +clicon_xmldb_plugin_set(clicon_handle h, + plghndl_t handle) +{ + clicon_hash_t *cdat = clicon_data(h); + + if (hash_add(cdat, "xmldb_plugin", &handle, sizeof(void*)) == NULL) + return -1; + return 0; +} + + +/*! Get XMLDB API struct pointer + * @param[in] h Clicon handle + * @retval xa XMLDB API struct + * @note xa is really of type struct xmldb_api* + */ +void * +clicon_xmldb_api_get(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + size_t len; + void *xa; + + if ((xa = hash_value(cdat, "xmldb_api", &len)) != NULL) + return *(void**)xa; + return NULL; +} + +/*! Set or reset XMLDB API struct pointer + * @param[in] h Clicon handle + * @param[in] xa XMLDB API struct + * @note xa is really of type struct xmldb_api* + */ +int +clicon_xmldb_api_set(clicon_handle h, + void *xa) +{ + clicon_hash_t *cdat = clicon_data(h); + + /* It is the pointer to xa_api that should be copied by hash, + so we send a ptr to the ptr to indicate what to copy. + */ + if (hash_add(cdat, "xmldb_api", &xa, sizeof(void*)) == NULL) + return -1; + return 0; +} + + +/*! Get XMLDB storage handle + * @param[in] h Clicon handle + * @retval xh XMLDB storage handle. If not connected return NULL + */ +void * +clicon_xmldb_handle_get(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + size_t len; + void *xh; + + if ((xh = hash_value(cdat, "xmldb_handle", &len)) != NULL) + return *(void**)xh; + return NULL; +} + +/*! Set or reset XMLDB storage handle + * @param[in] h Clicon handle + * @param[in] xh XMLDB storage handle. If NULL reset it + * @note Just keep note of it, dont allocate it or so. + */ +int +clicon_xmldb_handle_set(clicon_handle h, + void *xh) +{ + clicon_hash_t *cdat = clicon_data(h); + + if (hash_add(cdat, "xmldb_handle", &xh, sizeof(void*)) == NULL) + return -1; + return 0; +} +#endif /* XXX */ + + +/*! Get authorized user name + * @param[in] h Clicon handle + * @retval xh XMLDB storage handle. If not connected return NULL + */ +char * +clicon_username_get(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + + return (char*)hash_value(cdat, "username", NULL); +} + +/*! Set authorized user name + * @param[in] h Clicon handle + * @param[in] xh XMLDB storage handle. If NULL reset it + * @note Just keep note of it, dont allocate it or so. + */ +int +clicon_username_set(clicon_handle h, + void *username) +{ + clicon_hash_t *cdat = clicon_data(h); + + if (username == NULL) + return hash_del(cdat, "username"); + return hash_add(cdat, "username", username, strlen(username)+1)==NULL?-1:0; +} + +/*! Get backend daemon startup status + * @param[in] h Clicon handle + * @retval status Startup status + */ +enum startup_status +clicon_startup_status_get(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + void *p; + + if ((p = hash_value(cdat, "startup_status", NULL)) != NULL) + return *(enum startup_status *)p; + return STARTUP_ERR; +} + +/*! Set backend daemon startup status + * @param[in] h Clicon handle + * @param[in] status Startup status + * @retval 0 OK + * @retval -1 Error (when setting value) + */ +int +clicon_startup_status_set(clicon_handle h, + enum startup_status status) +{ + clicon_hash_t *cdat = clicon_data(h); + if (hash_add(cdat, "startup_status", &status, sizeof(status))==NULL) + return -1; + return 0; +} + +/*! Get socket fd (ie backend server socket / restconf fcgx socket) + * @param[in] h Clicon handle + * @retval -1 No open socket + * @retval s Socket + */ +int +clicon_socket_get(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + void *p; + + if ((p = hash_value(cdat, "socket", NULL)) == NULL) + return -1; + return *(int*)p; +} + +/*! Set socket fd (ie backend server socket / restconf fcgx socket) + * @param[in] h Clicon handle + * @param[in] s Open socket (or -1 to close) + * @retval 0 OK + * @retval -1 Error + */ +int +clicon_socket_set(clicon_handle h, + int s) +{ + clicon_hash_t *cdat = clicon_data(h); + + if (s == -1) + return hash_del(cdat, "socket"); + return hash_add(cdat, "socket", &s, sizeof(int))==NULL?-1:0; +} + +/*! Get module state cache + * @param[in] h Clicon handle + * @param[in] brief 0: Full module state tree, 1: Brief tree (datastore) + * @retval xms Module state cache XML tree + * xms is on the form: ... + */ +cxobj * +clicon_modst_cache_get(clicon_handle h, + int brief) +{ + clicon_hash_t *cdat = clicon_data(h); + void *p; + + if ((p = hash_value(cdat, brief?"modst_brief":"modst_full", NULL)) != NULL) + return *(cxobj **)p; + return NULL; +} + +/*! Set module state cache + * @param[in] h Clicon handle + * @param[in] brief 0: Full module state tree, 1: Brief tree (datastore) + * @param[in] xms Module state cache XML tree + * @retval 0 OK + * @retval -1 Error + */ +int +clicon_modst_cache_set(clicon_handle h, + int brief, + cxobj *xms) +{ + clicon_hash_t *cdat = clicon_data(h); + cxobj *x; + + if ((x = clicon_modst_cache_get(h, brief)) != NULL) + xml_free(x); + if (xms == NULL) + goto ok; + assert(strcmp(xml_name(xms),"modules-state")==0); + if ((x = xml_dup(xms)) == NULL) + return -1; + if (hash_add(cdat, brief?"modst_brief":"modst_full", &x, sizeof(x))==NULL) + return -1; + ok: + return 0; +} + +/*! Get yang module changelog + * @param[in] h Clicon handle + * @retval xch Module revision changelog XML tree + * @see draft-wang-netmod-module-revision-management-01 + */ +cxobj * +clicon_xml_changelog_get(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + void *p; + + if ((p = hash_value(cdat, "xml-changelog", NULL)) != NULL) + return *(cxobj **)p; + return NULL; +} + +/*! Set xml module changelog + * @param[in] h Clicon handle + * @param[in] s Module revision changelog XML tree + * @retval 0 OK + * @retval -1 Error + * @see draft-wang-netmod-module-revision-management-01 + */ +int +clicon_xml_changelog_set(clicon_handle h, + cxobj *xchlog) +{ + clicon_hash_t *cdat = clicon_data(h); + + if (hash_add(cdat, "xml-changelog", &xchlog, sizeof(xchlog))==NULL) + return -1; + return 0; +} + +/*! Get user clicon command-line options argv, argc (after --) + * @param[in] h Clicon handle + * @param[out] argc + * @param[out] argv + * @retval 0 OK + * @retval -1 Error + */ +int +clicon_argv_get(clicon_handle h, + int *argc, + char ***argv) + +{ + clicon_hash_t *cdat = clicon_data(h); + void *p; + + if (argc){ + if ((p = hash_value(cdat, "argc", NULL)) == NULL) + return -1; + *argc = *(int*)p; + } + if (argv){ + if ((p = hash_value(cdat, "argv", NULL)) == NULL) + return -1; + *argv = (char**)p; + } + return 0; +} + +/*! Set clicon user command-line options argv, argc (after --) + * @param[in] h Clicon handle + * @param[in] prog argv[0] - the program name + * @param[in] argc Length of argv + * @param[in] argv Array of command-line options or NULL + * @retval 0 OK + * @retval -1 Error + * @note If argv=NULL deallocate allocated argv vector if exists. + */ +int +clicon_argv_set(clicon_handle h, + char *prgm, + int argc, + char **argv) +{ + int retval = -1; + clicon_hash_t *cdat = clicon_data(h); + char **argvv = NULL; + size_t len; + + /* add space for null-termination and argv[0] program name */ + len = argc+2; + if ((argvv = calloc(len, sizeof(char*))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + goto done; + } + memcpy(argvv+1, argv, argc*sizeof(char*)); + argvv[0] = prgm; + /* Note the value is the argv vector (which is copied) */ + if (hash_add(cdat, "argv", argvv, len*sizeof(char*))==NULL) + goto done; + argc += 1; + if (hash_add(cdat, "argc", &argc, sizeof(argc))==NULL) + goto done; + retval = 0; + done: + if (argvv) + free(argvv); + return retval; +} + +/*! Get xml database element including pid and xml cache + * @param[in] h Clicon handle + * @param[in] db Name of database + * @retval de Database element + * @retval NULL None found + * @note these use db_elmnt hash, not data + */ +db_elmnt * +clicon_db_elmnt_get(clicon_handle h, + const char *db) +{ + clicon_hash_t *cdat = clicon_db_elmnt(h); + void *p; + + if ((p = hash_value(cdat, db, NULL)) != NULL) + return (db_elmnt *)p; + return NULL; +} + +/*! Set xml database element including pid and xml cache + * @param[in] h Clicon handle + * @param[in] db Name of database + * @param[in] de Database element + * @retval 0 OK + * @retval -1 Error + * XXX add prefix to db to ensure uniqueness? + * @note these use db_elmnt hash, not data +*/ +int +clicon_db_elmnt_set(clicon_handle h, + const char *db, + db_elmnt *de) +{ + clicon_hash_t *cdat = clicon_db_elmnt(h); + + if (hash_add(cdat, db, de, sizeof(*de))==NULL) + return -1; + return 0; +} diff --git a/lib/src/clixon_datastore_read.c b/lib/src/clixon_datastore_read.c new file mode 100644 index 00000000..3515f4f2 --- /dev/null +++ b/lib/src/clixon_datastore_read.c @@ -0,0 +1,593 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon_err.h" +#include "clixon_string.h" +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_log.h" +#include "clixon_file.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_xml_sort.h" +#include "clixon_options.h" +#include "clixon_data.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" +#include "clixon_xml_map.h" +#include "clixon_json.h" +#include "clixon_nacm.h" +#include "clixon_netconf_lib.h" +#include "clixon_yang_module.h" + +#include "clixon_datastore.h" +#include "clixon_datastore_read.h" + +#define handle(xh) (assert(text_handle_check(xh)==0),(struct text_handle *)(xh)) + +/*! Ensure that xt only has a single sub-element and that is "config" + */ +static int +singleconfigroot(cxobj *xt, + cxobj **xp) +{ + int retval = -1; + cxobj *x = NULL; + int i = 0; + + /* There should only be one element and called config */ + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ + i++; + if (strcmp(xml_name(x), "config")){ + clicon_err(OE_DB, ENOENT, "Wrong top-element %s expected config", + xml_name(x)); + goto done; + } + } + if (i != 1){ + clicon_err(OE_DB, ENOENT, "Top-element is not unique, expecting single config"); + goto done; + } + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ + if (xml_rm(x) < 0) + goto done; + if (xml_free(xt) < 0) + goto done; + *xp = x; + break; + } + retval = 0; + done: + return retval; +} +/*! Given XML tree x0 with marked nodes, copy marked nodes to new tree x1 + * Two marks are used: XML_FLAG_MARK and XML_FLAG_CHANGE + * + * The algorithm works as following: + * (1) Copy individual nodes marked with XML_FLAG_CHANGE + * until nodes marked with XML_FLAG_MARK are reached, where + * (2) the complete subtree of that node is copied. + * (3) Special case: key nodes in lists are copied if any node in list is marked + * @note you may want to check:!yang_config(ys) + */ +static int +xml_copy_marked(cxobj *x0, + cxobj *x1) +{ + int retval = -1; + int mark; + cxobj *x; + cxobj *xcopy; + int iskey; + yang_stmt *yt; + char *name; + + assert(x0 && x1); + yt = xml_spec(x0); /* can be null */ + /* Copy all attributes */ + x = NULL; + while ((x = xml_child_each(x0, x, CX_ATTR)) != NULL) { + name = xml_name(x); + if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL) + goto done; + if (xml_copy(x, xcopy) < 0) + goto done; + } + + /* Go through children to detect any marked nodes: + * (3) Special case: key nodes in lists are copied if any + * node in list is marked + */ + mark = 0; + x = NULL; + while ((x = xml_child_each(x0, x, CX_ELMNT)) != NULL) { + if (xml_flag(x, XML_FLAG_MARK|XML_FLAG_CHANGE)){ + mark++; + break; + } + } + x = NULL; + while ((x = xml_child_each(x0, x, CX_ELMNT)) != NULL) { + name = xml_name(x); + if (xml_flag(x, XML_FLAG_MARK)){ + /* (2) the complete subtree of that node is copied. */ + if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL) + goto done; + if (xml_copy(x, xcopy) < 0) + goto done; + continue; + } + if (xml_flag(x, XML_FLAG_CHANGE)){ + /* Copy individual nodes marked with XML_FLAG_CHANGE */ + if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL) + goto done; + if (xml_copy_marked(x, xcopy) < 0) /* */ + goto done; + } + /* (3) Special case: key nodes in lists are copied if any + * node in list is marked */ + if (mark && yt && yt->ys_keyword == Y_LIST){ + /* XXX: I think yang_key_match is suboptimal here */ + if ((iskey = yang_key_match((yang_node*)yt, name)) < 0) + goto done; + if (iskey){ + if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL) + goto done; + if (xml_copy(x, xcopy) < 0) + goto done; + } + } + } + retval = 0; + done: + return retval; +} + +/*! Read module-state in an XML tree + * + * @param[in] th Datastore text handle + * @param[in] yspec Top-level yang spec + * @param[in] xt XML tree + * @param[out] msd If set, return modules-state differences + * + * Read mst (module-state-tree) from xml tree (if any) and compare it with + * the system state mst. + * This can happen: + * 1) There is no modules-state info in the file + * 2) There is module state info in the file + * 3) For each module state m in the file: + * 3a) There is no such module in the system + * 3b) File module-state matches system + * 3c) File module-state does not match system + */ +static int +text_read_modstate(clicon_handle h, + yang_spec *yspec, + cxobj *xt, + modstate_diff_t *msd) +{ + int retval = -1; + cxobj *xmodst; + cxobj *xm = NULL; + cxobj *xm2; + cxobj *xs; + char *name; /* module name */ + char *mrev; /* file revision */ + char *srev; /* system revision */ + cxobj *xmcache = NULL; + + xmcache = clicon_modst_cache_get(h, 1); + if ((xmodst = xml_find_type(xt, NULL, "modules-state", CX_ELMNT)) == NULL){ + /* 1) There is no modules-state info in the file */ + } + else if (xmcache && msd){ + /* Create diff trees */ + if (xml_parse_string("", yspec, &msd->md_del) < 0) + goto done; + if (xml_rootchild(msd->md_del, 0, &msd->md_del) < 0) + goto done; + if (xml_parse_string("", yspec, &msd->md_mod) < 0) + goto done; + if (xml_rootchild(msd->md_mod, 0, &msd->md_mod) < 0) + goto done; + + /* 3) For each module state m in the file */ + while ((xm = xml_child_each(xmodst, xm, CX_ELMNT)) != NULL) { + if (strcmp(xml_name(xm), "module")) + continue; /* ignore other tags, such as module-set-id */ + if ((name = xml_find_body(xm, "name")) == NULL) + continue; + /* 3a) There is no such module in the system */ + if ((xs = xpath_first(xmcache, "module[name=\"%s\"]", name)) == NULL){ + // fprintf(stderr, "%s: Module %s: not in system\n", __FUNCTION__, name); + if ((xm2 = xml_dup(xm)) == NULL) + goto done; + if (xml_addsub(msd->md_del, xm2) < 0) + goto done; + continue; + } + /* These two shouldnt happen since revision is key, just ignore */ + if ((mrev = xml_find_body(xm, "revision")) == NULL) + continue; + if ((srev = xml_find_body(xs, "revision")) == NULL) + continue; + if (strcmp(mrev, srev)==0){ + /* 3b) File module-state matches system */ + // fprintf(stderr, "%s: Module %s: file \"%s\" and system revisions match\n", __FUNCTION__, name, mrev); + } + else{ + /* 3c) File module-state does not match system */ + // fprintf(stderr, "%s: Module %s: file \"%s\" and system \"%s\" revisions do not match\n", __FUNCTION__, name, mrev, srev); + if ((xm2 = xml_dup(xm)) == NULL) + goto done; + if (xml_addsub(msd->md_mod, xm2) < 0) + goto done; + } + } + } + /* The module-state is removed from the input XML tree. This is done + * in all cases, whether CLICON_XMLDB_MODSTATE is on or not. + * Clixon systems with CLICON_XMLDB_MODSTATE disabled ignores it + */ + if (xmodst){ + if (xml_purge(xmodst) < 0) + goto done; + } + retval = 0; + done: + return retval; +} + +/*! Common read function that reads an XML tree from file + * @param[in] th Datastore text handle + * @param[in] db Symbolic database name, eg "candidate", "running" + * @param[in] yspec Top-level yang spec + * @param[out] xp XML tree read from file + * @param[out] msd If set, return modules-state differences + */ +int +xmldb_readfile(clicon_handle h, + const char *db, + yang_spec *yspec, + cxobj **xp, + modstate_diff_t *msd) +{ + int retval = -1; + cxobj *x0 = NULL; + char *dbfile = NULL; + int fd = -1; + char *format; + + if (xmldb_db2file(h, db, &dbfile) < 0) + goto done; + if (dbfile==NULL){ + clicon_err(OE_XML, 0, "dbfile NULL"); + goto done; + } + if ((fd = open(dbfile, O_RDONLY)) < 0) { + clicon_err(OE_UNIX, errno, "open(%s)", dbfile); + goto done; + } + /* Parse file into XML tree */ + format = clicon_option_str(h, "CLICON_XMLDB_FORMAT"); + if (format && strcmp(format, "json")==0){ + if ((json_parse_file(fd, yspec, &x0)) < 0) + goto done; + } + else if ((xml_parse_file(fd, "", yspec, &x0)) < 0) + goto done; + /* Always assert a top-level called "config". + To ensure that, deal with two cases: + 1. File is empty -> rename top-level to "config" */ + if (xml_child_nr(x0) == 0){ + if (xml_name_set(x0, "config") < 0) + goto done; + } + /* 2. File is not empty ... -> replace root */ + else{ + /* There should only be one element and called config */ + if (singleconfigroot(x0, &x0) < 0) + goto done; + } + /* From Clixon 3.10,datastore files may contain module-state defining + * which modules are used in the file. + */ + if (text_read_modstate(h, yspec, x0, msd) < 0) + goto done; + if (xp){ + *xp = x0; + x0 = NULL; + } + retval = 0; + done: + if (fd != -1) + close(fd); + if (dbfile) + free(dbfile); + if (x0) + xml_free(x0); + return retval; +} + +/*! Get content of database using xpath. return a set of matching sub-trees + * The function returns a minimal tree that includes all sub-trees that match + * xpath. + * This is a clixon datastore plugin of the the xmldb api + * @param[in] h Clicon handle + * @param[in] db Name of database to search in (filename including dir path + * @param[in] xpath String with XPATH syntax. or NULL for all + * @param[in] config If set only configuration data, else also state + * @param[out] xret Single return XML tree. Free with xml_free() + * @param[out] msd If set, return modules-state differences + * @retval 0 OK + * @retval -1 Error + * @see xmldb_get the generic API function + */ +int +xmldb_get_nocache(clicon_handle h, + const char *db, + char *xpath, + int config, + cxobj **xtop, + modstate_diff_t *msd) +{ + int retval = -1; + char *dbfile = NULL; + yang_spec *yspec; + cxobj *xt = NULL; + cxobj *x; + int fd = -1; + cxobj **xvec = NULL; + size_t xlen; + int i; + char *format; + + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_YANG, ENOENT, "No yang spec"); + goto done; + } + if (xmldb_db2file(h, db, &dbfile) < 0) + goto done; + if (dbfile==NULL){ + clicon_err(OE_XML, 0, "dbfile NULL"); + goto done; + } + if ((fd = open(dbfile, O_RDONLY)) < 0){ + clicon_err(OE_UNIX, errno, "open(%s)", dbfile); + goto done; + } + /* Parse file into XML tree */ + format = clicon_option_str(h, "CLICON_XMLDB_FORMAT"); + if (format && strcmp(format, "json")==0){ + if ((json_parse_file(fd, yspec, &xt)) < 0) + goto done; + } + else if ((xml_parse_file(fd, "", yspec, &xt)) < 0) + goto done; + /* Always assert a top-level called "config". + To ensure that, deal with two cases: + 1. File is empty -> rename top-level to "config" */ + if (xml_child_nr(xt) == 0){ + if (xml_name_set(xt, "config") < 0) + goto done; + } + /* 2. File is not empty ... -> replace root */ + else{ + /* There should only be one element and called config */ + if (singleconfigroot(xt, &xt) < 0) + goto done; + } + /* Here xt looks like: ... */ + /* Given the xpath, return a vector of matches in xvec */ + if (xpath_vec(xt, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) + goto done; + + /* If vectors are specified then mark the nodes found with all ancestors + * and filter out everything else, + * otherwise return complete tree. + */ + if (xvec != NULL) + for (i=0; i1) + clicon_xml2file(stderr, xt, 0, 1); + *xtop = xt; + xt = NULL; + retval = 0; + done: + if (xt) + xml_free(xt); + if (dbfile) + free(dbfile); + if (xvec) + free(xvec); + if (fd != -1) + close(fd); + return retval; +} + +/*! Get content of database using xpath. return a set of matching sub-trees + * The function returns a minimal tree that includes all sub-trees that match + * xpath. + * This is a clixon datastore plugin of the the xmldb api + * @param[in] h Clicon handle + * @param[in] db Name of database to search in (filename including dir path + * @param[in] xpath String with XPATH syntax. or NULL for all + * @param[in] config If set only configuration data, else also state + * @param[out] xret Single return XML tree. Free with xml_free() + * @param[out] msd If set, return modules-state differences + * @retval 0 OK + * @retval -1 Error + * @see xmldb_get the generic API function + */ +int +xmldb_get_cache(clicon_handle h, + const char *db, + char *xpath, + int config, + cxobj **xtop, + modstate_diff_t *msd) +{ + int retval = -1; + yang_spec *yspec; + cxobj *x0t = NULL; /* (cached) top of tree */ + cxobj *x0; + cxobj **xvec = NULL; + size_t xlen; + int i; + db_elmnt *de = NULL; + cxobj *x1t = NULL; + db_elmnt de0 = {0,}; + + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_YANG, ENOENT, "No yang spec"); + goto done; + } + de = clicon_db_elmnt_get(h, db); + if (de == NULL || de->de_xml == NULL){ /* Cache miss, read XML from file */ + /* If there is no xml x0 tree (in cache), then read it from file */ + if (xmldb_readfile(h, db, yspec, &x0t, msd) < 0) + goto done; + /* XXX: should we validate file if read from disk? + * Argument against: we may want to have a semantically wrong file and wish + * to edit? + */ + de0.de_xml = x0t; + clicon_db_elmnt_set(h, db, &de0); + } /* x0t == NULL */ + else + x0t = de->de_xml; + /* Here x0t looks like: ... */ + /* Given the xpath, return a vector of matches in xvec + * Can we do everything in one go? + * 0) Make a new tree + * 1) make the xpath check + * 2) iterate thru matches (maybe this can be folded into the xpath_vec?) + * a) for every node that is found, copy to new tree + * b) if config dont dont state data + */ + + /* Here xt looks like: ... */ + if (xpath_vec(x0t, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) + goto done; + + /* Make new tree by copying top-of-tree from x0t to x1t */ + if ((x1t = xml_new(xml_name(x0t), NULL, xml_spec(x0t))) == NULL) + goto done; + /* Iterate through the match vector + * For every node found in x0, mark the tree up to t1 + */ + for (i=0; i.. but is .. */ + /* XXX where should we apply default values once? */ + if (xml_apply(x1t, CX_ELMNT, xml_default, NULL) < 0) + goto done; + + /* Copy the matching parts of the (relevant) XML tree. + * If cache was empty, also update to datastore cache + */ + if (debug>1) + clicon_xml2file(stderr, x1t, 0, 1); + *xtop = x1t; + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (xvec) + free(xvec); + return retval; +} + diff --git a/lib/src/clixon_datastore_read.h b/lib/src/clixon_datastore_read.h new file mode 100644 index 00000000..b7a20ff6 --- /dev/null +++ b/lib/src/clixon_datastore_read.h @@ -0,0 +1,46 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 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 ***** + + * Datastore text-based XML read functions + */ +#ifndef _CLIXON_DATASTORE_READ_H +#define _CLIXON_DATASTORE_READ_H + +/* + * Prototypes + */ +int xmldb_get_cache(clicon_handle h, const char *db, char *xpath, int config, cxobj **xret, modstate_diff_t *msd); +int xmldb_get_nocache(clicon_handle h, const char *db, char *xpath, int config, cxobj **xret, modstate_diff_t *msd); +int xmldb_readfile(clicon_handle h, const char *db, yang_spec *yspec, cxobj **xp, modstate_diff_t *msd); + +#endif /* _CLIXON_DATASTORE_READ_H */