clixon/datastore/clixon_xmldb_text.c
2019-03-28 13:16:44 +01:00

1904 lines
53 KiB
C

/*
*
***** 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 *****
1000 entries
valgrind --tool=callgrind datastore_client -d candidate -b /tmp/text -p ../datastore/text/text.so -y /tmp -m ietf-ip mget 300 /x/y[a=574][b=574] > /dev/null
xml_copy_marked 87% 200x
yang_key_match 81% 600K
yang_arg2cvec 52% 400K
cvecfree 23% 400K
10000 entries
valgrind --tool=callgrind datastore_client -d candidate -b /tmp/text -p ../datastore/text/text.so -y /tmp -m ietf-ip mget 10 /x/y[a=574][b=574] > /dev/null
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <fnmatch.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <assert.h>
#include <syslog.h>
#include <fcntl.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include "clixon_xmldb_text.h"
#define handle(xh) (assert(text_handle_check(xh)==0),(struct text_handle *)(xh))
/* Magic to ensure plugin sanity. */
#define TEXT_HANDLE_MAGIC 0x7f54da29
/*! Internal structure of text datastore handle.
*/
struct text_handle {
int th_magic; /* magic */
char *th_dbdir; /* Directory of database files */
yang_spec *th_yangspec; /* Yang spec if this datastore */
clicon_hash_t *th_dbs; /* Hash of db_elements. key is dbname */
int th_cache; /* Keep datastore text in memory so that get
operation need only read memory.
Write to file on modification or file change.
Assumes single backend*/
char *th_format; /* Datastroe format: xml / json */
int th_pretty; /* Store xml/json pretty-printed. */
char *th_nacm_mode;
cxobj *th_nacm_xtree;
cxobj *th_modst; /* According to RFC7895 for rev mgmnt
* Should be set only if CLICON_XMLDB_MODSTATE is true
*/
};
/* Struct per database in hash */
struct db_element{
int de_pid;
cxobj *de_xml; /* cache */
};
/*! Check struct magic number for sanity checks
* return 0 if OK, -1 if fail.
*/
static int
text_handle_check(xmldb_handle xh)
{
/* Dont use handle macro to avoid recursion */
struct text_handle *th = (struct text_handle *)(xh);
return th->th_magic == TEXT_HANDLE_MAGIC ? 0 : -1;
}
/*! Translate from symbolic database name to actual filename in file-system
* @param[in] th text handle handle
* @param[in] db Symbolic database name, eg "candidate", "running"
* @param[out] filename Filename. Unallocate after use with free()
* @retval 0 OK
* @retval -1 Error
* @note Could need a way to extend which databases exists, eg to register new.
* The currently allowed databases are:
* candidate, tmp, running, result
* The filename reside in CLICON_XMLDB_DIR option
*/
static int
text_db2file(struct text_handle *th,
const char *db,
char **filename)
{
int retval = -1;
cbuf *cb = NULL;
char *dir;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if ((dir = th->th_dbdir) == NULL){
clicon_err(OE_XML, errno, "dbdir not set");
goto done;
}
cprintf(cb, "%s/%s_db", dir, db);
if ((*filename = strdup4(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Connect to a datastore plugin
* @retval handle Use this handle for other API calls
* @retval NULL Error
*/
xmldb_handle
text_connect(void)
{
struct text_handle *th;
xmldb_handle xh = NULL;
int size;
size = sizeof(struct text_handle);
if ((th = malloc(size)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(th, 0, size);
th->th_magic = TEXT_HANDLE_MAGIC;
th->th_format = "xml"; /* default */
th->th_pretty = 1; /* default */
th->th_cache = 1; /* default */
if ((th->th_dbs = hash_init()) == NULL)
goto done;
xh = (xmldb_handle)th;
done:
return xh;
}
/*! Disconnect from to a datastore plugin and deallocate handle
* @param[in] xh XMLDB handle, disconect and deallocate from this handle
* @retval 0 OK
*/
int
text_disconnect(xmldb_handle xh)
{
int retval = -1;
struct text_handle *th = handle(xh);
struct db_element *de;
char **keys = NULL;
size_t klen;
int i;
clicon_debug(1, "%s", __FUNCTION__);
if (th){
if (th->th_dbdir)
free(th->th_dbdir);
if (th->th_dbs){
if (th->th_cache){
if (hash_keys(th->th_dbs, &keys, &klen) < 0)
goto done;
for(i = 0; i < klen; i++)
if ((de = hash_value(th->th_dbs, keys[i], NULL)) != NULL){
if (de->de_xml)
xml_free(de->de_xml);
}
if (keys)
free(keys);
}
hash_free(th->th_dbs);
}
if (th->th_nacm_mode)
free(th->th_nacm_mode);
if (th->th_modst)
xml_free(th->th_modst);
free(th);
}
retval = 0;
done:
return retval;
}
/*! Get value of generic plugin option. Type of value is givenby context
* @param[in] xh XMLDB handle
* @param[in] optname Option name
* @param[out] value Pointer to Value of option
* @retval 0 OK
* @retval -1 Error
*/
int
text_getopt(xmldb_handle xh,
char *optname,
void **value)
{
int retval = -1;
struct text_handle *th = handle(xh);
if (strcmp(optname, "yangspec") == 0)
*value = th->th_yangspec;
else if (strcmp(optname, "dbdir") == 0)
*value = th->th_dbdir;
else if (strcmp(optname, "xml_cache") == 0)
*value = &th->th_cache;
else if (strcmp(optname, "format") == 0)
*value = th->th_format;
else if (strcmp(optname, "pretty") == 0)
*value = &th->th_pretty;
else if (strcmp(optname, "nacm_mode") == 0)
*value = &th->th_nacm_mode;
else if (strcmp(optname, "nacm_xtree") == 0)
*value = th->th_nacm_xtree;
else if (strcmp(optname, "modules_state") == 0)
*value = th->th_modst;
else{
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
goto done;
}
retval = 0;
done:
return retval;
}
/*! Set value of generic plugin option. Type of value is given by context
* @param[in] xh XMLDB handle
* @param[in] optname Option name: yangspec, xml_cache, format, prettyprint
* @param[in] value Value of option
* @retval 0 OK
* @retval -1 Error
*/
int
text_setopt(xmldb_handle xh,
char *optname,
void *value)
{
int retval = -1;
struct text_handle *th = handle(xh);
if (strcmp(optname, "yangspec") == 0)
th->th_yangspec = (yang_spec*)value;
else if (strcmp(optname, "dbdir") == 0){
if (value && (th->th_dbdir = strdup((char*)value)) == NULL){
clicon_err(OE_UNIX, 0, "strdup");
goto done;
}
}
else if (strcmp(optname, "xml_cache") == 0){
th->th_cache = (intptr_t)value;
}
else if (strcmp(optname, "format") == 0){
if (strcmp(value,"xml")==0)
th->th_format = "xml";
else if (strcmp(value,"json")==0)
th->th_format = "json";
else{
clicon_err(OE_PLUGIN, 0, "Option %s unrecognized format: %s",
optname, (char*)value);
goto done;
}
}
else if (strcmp(optname, "pretty") == 0){
th->th_pretty = (intptr_t)value;
}
else if (strcmp(optname, "nacm_mode") == 0){
if (value && (th->th_nacm_mode = strdup((char*)value)) == NULL){
clicon_err(OE_UNIX, 0, "strdup");
goto done;
}
}
else if (strcmp(optname, "nacm_xtree") == 0){
th->th_nacm_xtree = (cxobj*)value;
}
else if (strcmp(optname, "modules_state") == 0){
th->th_modst = (cxobj*)value;
}
else{
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
goto done;
}
retval = 0;
done:
return retval;
}
/*! 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;
}
static int
text_get_nocache(struct text_handle *th,
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;
if ((yspec = th->th_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if (text_db2file(th, 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 */
if (strcmp(th->th_format,"json")==0){
if ((json_parse_file(fd, yspec, &xt)) < 0)
goto done;
}
else if ((xml_parse_file(fd, "</config>", yspec, &xt)) < 0)
goto done;
/* Always assert a top-level called "config".
To ensure that, deal with two cases:
1. File is empty <top/> -> 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 <top><config>...</config></top> -> replace root */
else{
/* There should only be one element and called config */
if (singleconfigroot(xt, &xt) < 0)
goto done;
}
/* Here xt looks like: <config>...</config> */
/* 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; i<xlen; i++){
x = xvec[i];
xml_flag_set(x, XML_FLAG_MARK);
}
/* Remove everything that is not marked */
if (!xml_flag(xt, XML_FLAG_MARK))
if (xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL) < 0)
goto done;
/* reset flag */
if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
goto done;
/* filter out state (operations) data if config not set. Mark all nodes
that are not config data */
if (config){
if (xml_apply(xt, CX_ELMNT, xml_non_config_data, NULL) < 0)
goto done;
/* Remove (prune) nodes that are marked (that does not pass test) */
if (xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1) < 0)
goto done;
}
/* Add default values (if not set) */
if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0)
goto done;
#if 0 /* debug */
if (xml_apply0(xt, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: sort verify failed #2", __FUNCTION__);
#endif
if (debug>1)
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;
}
/*! 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(struct text_handle *th,
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 */
if ((xmodst = xml_find_type(xt, NULL, "modules-state", CX_ELMNT)) == NULL){
/* 1) There is no modules-state info in the file */
}
else if (th->th_modst && msd){
/* Create diff trees */
if (xml_parse_string("<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\"/>", yspec, &msd->md_del) < 0)
goto done;
if (xml_rootchild(msd->md_del, 0, &msd->md_del) < 0)
goto done;
if (xml_parse_string("<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\"/>", 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;
if ((name = xml_find_body(xm, "name")) == NULL)
continue;
/* 3a) There is no such module in the system */
if ((xs = xpath_first(th->th_modst, "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
*/
static int
text_readfile(struct text_handle *th,
const char *db,
yang_spec *yspec,
cxobj **xp,
modstate_diff_t *msd)
{
int retval = -1;
cxobj *x0 = NULL;
char *dbfile = NULL;
int fd = -1;
if (text_db2file(th, 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 */
if (strcmp(th->th_format,"json")==0){
if ((json_parse_file(fd, yspec, &x0)) < 0)
goto done;
}
else if ((xml_parse_file(fd, "</config>", yspec, &x0)) < 0)
goto done;
/* Always assert a top-level called "config".
To ensure that, deal with two cases:
1. File is empty <top/> -> 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 <top><config>...</config></top> -> 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(th, 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] th Datastore text 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
*/
static int
text_get_cache(struct text_handle *th,
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;
struct db_element *de = NULL;
cxobj *x1t = NULL;
struct db_element de0 = {0,};
if ((yspec = th->th_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
de = hash_value(th->th_dbs, db, NULL);
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 (text_readfile(th, 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;
hash_add(th->th_dbs, db, &de0, sizeof(de0));
} /* x0t == NULL */
else
x0t = de->de_xml;
/* Here x0t looks like: <config>...</config> */
/* 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: <config>...</config> */
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<xlen; i++){
x0 = xvec[i];
xml_flag_set(x0, XML_FLAG_MARK);
xml_apply_ancestor(x0, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
}
if (xml_copy_marked(x0t, x1t) < 0) /* config */
goto done;
if (xml_apply(x0t, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
goto done;
if (xml_apply(x1t, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
goto done;
/* x1t is wrong here should be <config><system>.. but is <system>.. */
/* 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:
if (xvec)
free(xvec);
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] th Datastore text 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
text_get(xmldb_handle xh,
const char *db,
char *xpath,
int config,
cxobj **xtop,
modstate_diff_t *msd)
{
struct text_handle *th = handle(xh);
if (th->th_cache)
return text_get_cache(th, db, xpath, config, xtop, msd);
else
return text_get_nocache(th, db, xpath, config, xtop, msd);
}
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
* @param[in] th Datastore text handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[in] x0p Parent of x0
* @param[in] x1 XML tree which modifies base
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[in] username User name of requestor for nacm
* @param[in] xnacm NACM XML tree (only if !permit)
* @param[in] permit If set, no NACM tests using xnacm required
* @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0.
* @retval -1 Error
* @retval 0 Failed (cbret set)
* @retval 1 OK
* Assume x0 and x1 are same on entry and that y is the spec
* @see text_modify_top
*/
static int
text_modify(struct text_handle *th,
cxobj *x0,
yang_node *y0,
cxobj *x0p,
cxobj *x1,
enum operation_type op,
char *username,
cxobj *xnacm,
int permit,
cbuf *cbret)
{
int retval = -1;
char *opstr;
char *x1name;
char *x1cname; /* child name */
cxobj *x0a; /* attribute */
cxobj *x1a; /* attribute */
cxobj *x0c; /* base child */
cxobj *x0b; /* base body */
cxobj *x1c; /* mod child */
char *xns; /* namespace */
char *x0bstr; /* mod body string */
char *x1bstr; /* mod body string */
yang_stmt *yc; /* yang child */
cxobj **x0vec = NULL;
int i;
int ret;
assert(x1 && xml_type(x1) == CX_ELMNT);
assert(y0);
/* Check for operations embedded in tree according to netconf */
if ((opstr = xml_find_value(x1, "operation")) != NULL)
if (xml_operation(opstr, &op) < 0)
goto done;
x1name = xml_name(x1);
if (y0->yn_keyword == Y_LEAF_LIST || y0->yn_keyword == Y_LEAF){
x1bstr = xml_body(x1);
switch(op){
case OP_CREATE:
if (x0){
if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
goto done;
goto fail;
}
case OP_NONE: /* fall thru */
case OP_MERGE:
case OP_REPLACE:
if (x0==NULL){
if ((op != OP_NONE) && !permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
// int iamkey=0;
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
goto done;
/* Copy xmlns attributes */
x1a = NULL;
while ((x1a = xml_child_each(x1, x1a, CX_ATTR)) != NULL)
if (strcmp(xml_name(x1a),"xmlns")==0 ||
((xns = xml_prefix(x1a)) && strcmp(xns, "xmlns")==0)){
if ((x0a = xml_dup(x1a)) == NULL)
goto done;
if (xml_addsub(x0, x0a) < 0)
goto done;
}
#if 0
/* If it is key I dont want to mark it */
if ((iamkey=yang_key_match(y0->yn_parent, x1name)) < 0)
goto done;
if (!iamkey && op==OP_NONE)
#else
if (op==OP_NONE)
#endif
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
if (x1bstr){ /* empty type does not have body */
if ((x0b = xml_new("body", x0, NULL)) == NULL)
goto done;
xml_type_set(x0b, CX_BODY);
}
}
if (x1bstr){
if ((x0b = xml_body_get(x0)) != NULL){
x0bstr = xml_value(x0b);
if (x0bstr==NULL || strcmp(x0bstr, x1bstr)){
if ((op != OP_NONE) && !permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x1,
x0bstr==NULL?NACM_CREATE:NACM_UPDATE,
username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
if (xml_value_set(x0b, x1bstr) < 0)
goto done;
}
}
}
break;
case OP_DELETE:
if (x0==NULL){
if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0)
goto done;
goto fail;
}
case OP_REMOVE: /* fall thru */
if (x0){
if ((op != OP_NONE) && !permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
if (xml_purge(x0) < 0)
goto done;
}
break;
default:
break;
} /* switch op */
} /* if LEAF|LEAF_LIST */
else { /* eg Y_CONTAINER, Y_LIST, Y_ANYXML */
switch(op){
case OP_CREATE:
if (x0){
if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
goto done;
goto fail;
}
case OP_REPLACE: /* fall thru */
if (!permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
if (x0){
xml_purge(x0);
x0 = NULL;
}
case OP_MERGE: /* fall thru */
case OP_NONE:
/* Special case: anyxml, just replace tree,
See rfc6020 7.10.3:n
An anyxml node is treated as an opaque chunk of data. This data
can be modified in its entirety only.
Any "operation" attributes present on subelements of an anyxml
node are ignored by the NETCONF server.*/
if (y0->yn_keyword == Y_ANYXML || y0->yn_keyword == Y_ANYDATA){
if (op == OP_NONE)
break;
if (op==OP_MERGE && !permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x0, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
if (x0){
xml_purge(x0);
}
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
goto done;
if (xml_copy(x1, x0) < 0)
goto done;
break;
}
if (x0==NULL){
if (op==OP_MERGE && !permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x0, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
goto done;
/* Copy xmlns attributes */
x1a = NULL;
while ((x1a = xml_child_each(x1, x1a, CX_ATTR)) != NULL)
if (strcmp(xml_name(x1a),"xmlns")==0 ||
((xns = xml_prefix(x1a)) && strcmp(xns, "xmlns")==0)){
if ((x0a = xml_dup(x1a)) == NULL)
goto done;
if (xml_addsub(x0, x0a) < 0)
goto done;
}
if (op==OP_NONE)
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
}
/* First pass: Loop through children of the x1 modification tree
* collect matching nodes from x0 in x0vec (no changes to x0 children)
*/
if ((x0vec = calloc(xml_child_nr(x1), sizeof(x1))) == NULL){
clicon_err(OE_UNIX, errno, "calloc");
goto done;
}
x1c = NULL;
i = 0;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c);
/* Get yang spec of the child */
if ((yc = yang_find_datanode(y0, x1cname)) == NULL){
clicon_err(OE_YANG, errno, "No yang node found: %s", x1cname);
goto done;
}
/* See if there is a corresponding node in the base tree */
x0c = NULL;
if (match_base_child(x0, x1c, yc, &x0c) < 0)
goto done;
#if 1
if (x0c && (yc != xml_spec(x0c))){
/* There is a match but is should be replaced (choice)*/
if (xml_purge(x0c) < 0)
goto done;
x0c = NULL;
}
#endif
x0vec[i++] = x0c; /* != NULL if x0c is matching x1c */
}
/* Second pass: Loop through children of the x1 modification tree again
* Now potentially modify x0:s children
* Here x0vec contains one-to-one matching nodes of x1:s children.
*/
x1c = NULL;
i = 0;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c);
x0c = x0vec[i++];
yc = yang_find_datanode(y0, x1cname);
if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op,
username, xnacm, permit, cbret)) < 0)
goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */
if (ret == 0)
goto fail;
}
break;
case OP_DELETE:
if (x0==NULL){
if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0)
goto done;
goto fail;
}
case OP_REMOVE: /* fall thru */
if (x0){
if (!permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
if (xml_purge(x0) < 0)
goto done;
}
break;
default:
break;
} /* CONTAINER switch op */
} /* else Y_CONTAINER */
xml_sort(x0p, NULL);
retval = 1;
done:
if (x0vec)
free(x0vec);
return retval;
fail: /* cbret set */
retval = 0;
goto done;
} /* text_modify */
/*! Modify a top-level base tree x0 with modification tree x1
* @param[in] th Datastore text handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] x1 XML tree which modifies base
* @param[in] yspec Top-level yang spec (if y is NULL)
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[in] username User name of requestor for nacm
* @param[in] xnacm NACM XML tree (only if !permit)
* @param[in] permit If set, no NACM tests using xnacm required
* @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0.
* @retval -1 Error
* @retval 0 Failed (cbret set)
* @retval 1 OK
* @see text_modify
*/
static int
text_modify_top(struct text_handle *th,
cxobj *x0,
cxobj *x1,
yang_spec *yspec,
enum operation_type op,
char *username,
cxobj *xnacm,
int permit,
cbuf *cbret)
{
int retval = -1;
char *x1cname; /* child name */
cxobj *x0c; /* base child */
cxobj *x1c; /* mod child */
yang_stmt *yc; /* yang child */
yang_stmt *ymod;/* yang module */
char *opstr;
int ret;
/* Assure top-levels are 'config' */
assert(x0 && strcmp(xml_name(x0),"config")==0);
assert(x1 && strcmp(xml_name(x1),"config")==0);
/* Check for operations embedded in tree according to netconf */
if ((opstr = xml_find_value(x1, "operation")) != NULL)
if (xml_operation(opstr, &op) < 0)
goto done;
/* Special case if x1 is empty, top-level only <config/> */
if (xml_child_nr_type(x1, CX_ELMNT) == 0){
if (xml_child_nr_type(x0, CX_ELMNT)){ /* base tree not empty */
switch(op){
case OP_DELETE:
case OP_REMOVE:
case OP_REPLACE:
if (!permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
while ((x0c = xml_child_i(x0, 0)) != 0)
if (xml_purge(x0c) < 0)
goto done;
break;
default:
break;
}
}
else /* base tree empty */
switch(op){
#if 0 /* According to RFC6020 7.5.8 you cant delete a non-existing object.
On the other hand, the top-level cannot be removed anyway.
Additionally, I think this is irritating so I disable it.
I.e., curl -u andy:bar -sS -X DELETE http://localhost/restconf/data
*/
case OP_DELETE:
if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0)
goto done;
goto fail;
break;
#endif
default:
break;
}
}
/* Special case top-level replace */
else if (op == OP_REPLACE || op == OP_DELETE){
if (!permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x1, NACM_UPDATE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
while ((x0c = xml_child_i(x0, 0)) != 0)
if (xml_purge(x0c) < 0)
goto done;
}
/* Loop through children of the modification tree */
x1c = NULL;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c);
/* Get yang spec of the child */
yc = NULL;
if (ys_module_by_xml(yspec, x1c, &ymod) <0)
goto done;
if (ymod != NULL)
yc = yang_find_datanode((yang_node*)ymod, x1cname);
if (yc == NULL){
if (netconf_unknown_element(cbret, "application", x1cname, "Unassigned yang spec") < 0)
goto done;
goto fail;
}
/* See if there is a corresponding node in the base tree */
if (match_base_child(x0, x1c, yc, &x0c) < 0)
goto done;
#if 1
if (x0c && (yc != xml_spec(x0c))){
/* There is a match but is should be replaced (choice)*/
if (xml_purge(x0c) < 0)
goto done;
x0c = NULL;
}
#endif
if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op,
username, xnacm, permit, cbret)) < 0)
goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */
if (ret == 0)
goto fail;
}
// ok:
retval = 1;
done:
return retval;
fail: /* cbret set */
retval = 0;
goto done;
} /* text_modify_top */
/*! For containers without presence and no children(except attrs), remove
* @param[in] x XML tree node
* See section 7.5.1 in rfc6020bis-02.txt:
* No presence:
* those that exist only for organizing the hierarchy of data nodes:
* the container has no meaning of its own, existing
* only to contain child nodes. This is the default style.
* (Remove these if no children)
* Presence:
* the presence of the container itself is
* configuration data, representing a single bit of configuration data.
* The container acts as both a configuration knob and a means of
* organizing related configuration. These containers are explicitly
* created and deleted.
* (Dont touch these)
*/
int
xml_container_presence(cxobj *x,
void *arg)
{
int retval = -1;
yang_stmt *y; /* yang node */
if ((y = (yang_stmt*)xml_spec(x)) == NULL){
retval = 0;
goto done;
}
/* Mark node that is: container, have no children, dont have presence */
if (y->ys_keyword == Y_CONTAINER &&
xml_child_nr_notype(x, CX_ATTR)==0 &&
yang_find((yang_node*)y, Y_PRESENCE, NULL) == NULL)
xml_flag_set(x, XML_FLAG_MARK); /* Mark, remove later */
retval = 0;
done:
return retval;
}
/*! Modify database provided an xml tree and an operation
* This is a clixon datastore plugin of the the xmldb api
* @param[in] h CLICON handle
* @param[in] db running or candidate
* @param[in] op Top-level operation, can be superceded by other op in tree
* @param[in] x1 xml-tree. Top-level symbol is dummy
* @param[in] username User name for nacm
* @param[out] cbret Initialized cligen buffer. On exit contains XML if retval == 0
* @retval 1 OK
* @retval 0 Failed, cbret contains error xml message
* @retval -1 Error
* @see xmldb_put the generic API function
*/
int
text_put(xmldb_handle xh,
const char *db,
enum operation_type op,
cxobj *x1,
char *username,
cbuf *cbret)
{
int retval = -1;
struct text_handle *th = handle(xh);
char *dbfile = NULL;
FILE *f = NULL;
cbuf *cb = NULL;
yang_spec *yspec;
cxobj *x0 = NULL;
struct db_element *de = NULL;
int ret;
cxobj *xnacm = NULL;
char *mode;
cxobj *xnacm0 = NULL;
cxobj *xmodst = NULL;
int permit = 0; /* nacm permit all */
if (cbret == NULL){
clicon_err(OE_XML, EINVAL, "cbret is NULL");
goto done;
}
if ((yspec = th->th_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if (x1 && strcmp(xml_name(x1),"config")!=0){
clicon_err(OE_XML, 0, "Top-level symbol of modification tree is %s, expected \"config\"",
xml_name(x1));
goto done;
}
if (th->th_cache){
if ((de = hash_value(th->th_dbs, db, NULL)) != NULL)
x0 = de->de_xml;
}
/* If there is no xml x0 tree (in cache), then read it from file */
if (x0 == NULL){
if (text_readfile(th, db, yspec, &x0, NULL) < 0)
goto done;
}
if (strcmp(xml_name(x0),"config")!=0){
clicon_err(OE_XML, 0, "Top-level symbol is %s, expected \"config\"",
xml_name(x0));
goto done;
}
/* Here x0 looks like: <config>...</config> */
#if 0 /* debug */
if (xml_apply0(x1, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__);
#endif
mode = th->th_nacm_mode;
if (mode){
if (strcmp(mode, "external")==0)
xnacm0 = th->th_nacm_xtree;
else if (strcmp(mode, "internal")==0)
xnacm0 = x0;
}
if (xnacm0 != NULL &&
(xnacm = xpath_first(xnacm0, "nacm")) != NULL){
/* Pre-NACM access step, if permit, then dont do any nacm checks in
* text_modify_* below */
if ((permit = nacm_access(mode, xnacm, username)) < 0)
goto done;
}
/* Here assume if xnacm is set and !permit do NACM */
/*
* Modify base tree x with modification x1. This is where the
* new tree is made.
*/
if ((ret = text_modify_top(th, x0, x1, yspec, op, username, xnacm, permit, cbret)) < 0)
goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */
if (ret == 0)
goto fail;
/* Remove NONE nodes if all subs recursively are also NONE */
if (xml_tree_prune_flagged_sub(x0, XML_FLAG_NONE, 0, NULL) <0)
goto done;
if (xml_apply(x0, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
(void*)XML_FLAG_NONE) < 0)
goto done;
/* Mark non-presence containers that do not have children */
if (xml_apply(x0, CX_ELMNT, (xml_applyfn_t*)xml_container_presence, NULL) < 0)
goto done;
/* Remove (prune) nodes that are marked (non-presence containers w/o children) */
if (xml_tree_prune_flagged(x0, XML_FLAG_MARK, 1) < 0)
goto done;
#if 0 /* debug */
if (xml_apply0(x0, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #3", __FUNCTION__);
#endif
/* Write back to datastore cache if first time */
if (th->th_cache){
struct db_element de0 = {0,};
if (de != NULL)
de0 = *de;
if (de0.de_xml == NULL){
de0.de_xml = x0;
hash_add(th->th_dbs, db, &de0, sizeof(de0));
}
}
if (text_db2file(th, db, &dbfile) < 0)
goto done;
if (dbfile==NULL){
clicon_err(OE_XML, 0, "dbfile NULL");
goto done;
}
/* Add module revision info before writing to file)
* Only if CLICON_XMLDB_MODSTATE is set
*/
if (th->th_modst){
if ((xmodst = xml_dup(th->th_modst)) == NULL)
goto done;
if (xml_addsub(x0, xmodst) < 0)
goto done;
}
if ((f = fopen(dbfile, "w")) == NULL){
clicon_err(OE_CFG, errno, "Creating file %s", dbfile);
goto done;
}
if (strcmp(th->th_format,"json")==0){
if (xml2json(f, x0, th->th_pretty) < 0)
goto done;
}
else if (clicon_xml2file(f, x0, 0, th->th_pretty) < 0)
goto done;
/* Remove modules state after writing to file
*/
if (xmodst && xml_purge(xmodst) < 0)
goto done;
retval = 1;
done:
if (f != NULL)
fclose(f);
if (dbfile)
free(dbfile);
if (cb)
cbuf_free(cb);
if (!th->th_cache && x0)
xml_free(x0);
return retval;
fail:
retval = 0;
goto done;
}
/*! Copy database from db1 to db2
* @param[in] xh XMLDB handle
* @param[in] from Source database
* @param[in] to Destination database
* @retval -1 Error
* @retval 0 OK
*/
int
text_copy(xmldb_handle xh,
const char *from,
const char *to)
{
int retval = -1;
struct text_handle *th = handle(xh);
char *fromfile = NULL;
char *tofile = NULL;
struct db_element *de1 = NULL;
struct db_element *de2 = NULL;
struct db_element de0 = {0,};
cxobj *x1 = NULL;
cxobj *x2 = NULL;
/* XXX lock */
if (th->th_cache){
/* 1. "to" xml tree in x1 */
if ((de1 = hash_value(th->th_dbs, from, NULL)) != NULL)
x1 = de1->de_xml;
if ((de2 = hash_value(th->th_dbs, to, NULL)) != NULL)
x2 = de2->de_xml;
if (x1 == NULL && x2 == NULL){
/* do nothing */
}
else if (x1 == NULL){ /* free x2 and set to NULL */
xml_free(x2);
x2 = NULL;
}
else if (x2 == NULL){ /* create x2 and copy x1 to it */
if ((x2 = xml_new(xml_name(x1), NULL, xml_spec(x1))) == NULL)
goto done;
if (xml_copy(x1, x2) < 0)
goto done;
}
else{ /* copy x1 to x2 */
xml_free(x2);
if ((x2 = xml_new(xml_name(x1), NULL, xml_spec(x1))) == NULL)
goto done;
if (xml_copy(x1, x2) < 0)
goto done;
}
if (x1 || x2){
if (de2)
de0 = *de2;
de0.de_xml = x2; /* The new tree */
hash_add(th->th_dbs, to, &de0, sizeof(de0));
}
}
if (text_db2file(th, from, &fromfile) < 0)
goto done;
if (text_db2file(th, to, &tofile) < 0)
goto done;
/* Copy the files themselves (above only in-memory cache) */
if (clicon_file_copy(fromfile, tofile) < 0)
goto done;
retval = 0;
done:
if (fromfile)
free(fromfile);
if (tofile)
free(tofile);
return retval;
}
/*! Lock database
* @param[in] xh XMLDB handle
* @param[in] db Database
* @param[in] pid Process id
* @retval -1 Error
* @retval 0 OK
*/
int
text_lock(xmldb_handle xh,
const char *db,
int pid)
{
struct text_handle *th = handle(xh);
struct db_element *de = NULL;
struct db_element de0 = {0,};
if ((de = hash_value(th->th_dbs, db, NULL)) != NULL)
de0 = *de;
de0.de_pid = pid;
hash_add(th->th_dbs, db, &de0, sizeof(de0));
clicon_debug(1, "%s: locked by %u", db, pid);
return 0;
}
/*! Unlock database
* @param[in] xh XMLDB handle
* @param[in] db Database
* @param[in] pid Process id
* @retval -1 Error
* @retval 0 OK
* Assume all sanity checks have been made
*/
int
text_unlock(xmldb_handle xh,
const char *db)
{
struct text_handle *th = handle(xh);
struct db_element *de = NULL;
if ((de = hash_value(th->th_dbs, db, NULL)) != NULL){
de->de_pid = 0;
hash_add(th->th_dbs, db, de, sizeof(*de));
}
return 0;
}
/*! Unlock all databases locked by pid (eg process dies)
* @param[in] xh XMLDB handle
* @param[in] pid Process / Session id
* @retval -1 Error
* @retval 0 Ok
*/
int
text_unlock_all(xmldb_handle xh,
int pid)
{
int retval = -1;
struct text_handle *th = handle(xh);
char **keys = NULL;
size_t klen;
int i;
struct db_element *de;
if (hash_keys(th->th_dbs, &keys, &klen) < 0)
goto done;
for(i = 0; i < klen; i++)
if ((de = hash_value(th->th_dbs, keys[i], NULL)) != NULL &&
de->de_pid == pid){
de->de_pid = 0;
hash_add(th->th_dbs, keys[i], de, sizeof(*de));
}
retval = 0;
done:
if (keys)
free(keys);
return retval;
}
/*! Check if database is locked
* @param[in] xh XMLDB handle
* @param[in] db Database
* @retval -1 Error
* @retval 0 Not locked
* @retval >0 Id of locker
*/
int
text_islocked(xmldb_handle xh,
const char *db)
{
struct text_handle *th = handle(xh);
struct db_element *de;
if ((de = hash_value(th->th_dbs, db, NULL)) == NULL)
return 0;
return de->de_pid;
}
/*! Check if db exists
* @param[in] xh XMLDB handle
* @param[in] db Database
* @retval -1 Error
* @retval 0 No it does not exist
* @retval 1 Yes it exists
*/
int
text_exists(xmldb_handle xh,
const char *db)
{
int retval = -1;
struct text_handle *th = handle(xh);
char *filename = NULL;
struct stat sb;
if (text_db2file(th, db, &filename) < 0)
goto done;
if (lstat(filename, &sb) < 0)
retval = 0;
else
retval = 1;
done:
if (filename)
free(filename);
return retval;
}
/*! Delete database. Remove file
* @param[in] xh XMLDB handle
* @param[in] db Database
* @retval -1 Error
* @retval 0 OK
*/
int
text_delete(xmldb_handle xh,
const char *db)
{
int retval = -1;
char *filename = NULL;
struct text_handle *th = handle(xh);
struct db_element *de = NULL;
cxobj *xt = NULL;
struct stat sb;
if (th->th_cache){
if ((de = hash_value(th->th_dbs, db, NULL)) != NULL){
if ((xt = de->de_xml) != NULL){
xml_free(xt);
de->de_xml = NULL;
}
}
}
if (text_db2file(th, db, &filename) < 0)
goto done;
if (lstat(filename, &sb) == 0)
if (unlink(filename) < 0){
clicon_err(OE_DB, errno, "unlink %s", filename);
goto done;
}
retval = 0;
done:
if (filename)
free(filename);
return retval;
}
/*! Create / init database
* If it exists dont change.
* @param[in] xh XMLDB handle
* @param[in] db Database
* @retval 0 OK
* @retval -1 Error
*/
int
text_create(xmldb_handle xh,
const char *db)
{
int retval = -1;
struct text_handle *th = handle(xh);
char *filename = NULL;
int fd = -1;
struct db_element *de = NULL;
cxobj *xt = NULL;
if (th->th_cache){ /* XXX This should not really happen? */
if ((de = hash_value(th->th_dbs, db, NULL)) != NULL){
if ((xt = de->de_xml) != NULL){
assert(xt==NULL); /* XXX */
xml_free(xt);
de->de_xml = NULL;
}
}
}
if (text_db2file(th, db, &filename) < 0)
goto done;
if ((fd = open(filename, O_CREAT|O_WRONLY, S_IRWXU)) == -1) {
clicon_err(OE_UNIX, errno, "open(%s)", filename);
goto done;
}
retval = 0;
done:
if (filename)
free(filename);
if (fd != -1)
close(fd);
return retval;
}
/*! plugin exit function */
int
text_plugin_exit(void)
{
return 0;
}
static const struct xmldb_api api;
static const struct xmldb_api api;
/*! plugin init function */
void *
clixon_xmldb_plugin_init(int version)
{
if (version != XMLDB_API_VERSION){
clicon_err(OE_DB, 0, "Invalid version %d expected %d",
version, XMLDB_API_VERSION);
goto done;
}
return (void*)&api;
done:
return NULL;
}
static const struct xmldb_api api = {
1,
XMLDB_API_MAGIC,
clixon_xmldb_plugin_init,
text_plugin_exit,
text_connect,
text_disconnect,
text_getopt,
text_setopt,
text_get,
text_put,
text_copy,
text_lock,
text_unlock,
text_unlock_all,
text_islocked,
text_exists,
text_delete,
text_create,
};
#if 0 /* Test program */
/*
* Turn this on to get an xpath test program
* Usage: clicon_xpath [<xpath>]
* read xml from input
* Example compile:
gcc -g -o xmldb -I. -I../clixon ./clixon_xmldb.c -lclixon -lcligen
*/
static int
usage(char *argv0)
{
fprintf(stderr, "usage:\n%s\tget <db> <yangdir> <yangmod> [<xpath>]\t\txml on stdin\n", argv0);
fprintf(stderr, "\tput <db> <yangdir> <yangmod> set|merge|delete\txml to stdout\n");
exit(0);
}
int
main(int argc,
char **argv)
{
cxobj *xt;
cxobj *xn;
char *xpath;
enum operation_type op;
char *cmd;
char *db;
char *yangmod; /* yang file */
yang_spec *yspec = NULL;
clicon_handle h;
cbuf *cbret = NULL;
int ret;
if ((h = clicon_handle_init()) == NULL)
goto done;
clicon_log_init("xmldb", LOG_DEBUG, CLICON_LOG_STDERR);
if (argc < 4){
usage(argv[0]);
goto done;
}
cmd = argv[1];
db = argv[2];
yangmod = argv[4];
db_init(db);
if ((yspec = yspec_new()) == NULL)
goto done
if (yang_spec_parse_module(h, yangmod, NULL, yspec) < 0)
goto done;
if (strcmp(cmd, "get")==0){
if (argc < 5)
usage(argv[0]);
xpath = argc>5?argv[5]:NULL;
if (xmldb_get(h, db, xpath, &xt, NULL, 1, NULL) < 0)
goto done;
if (strcmp(th->th_format,"json")==0){
if (xml2json(stdout, xt, th->th_pretty) < 0)
goto done;
}
else{
if (clicon_xml2file(stdout, xt, 0, th->th_pretty) < 0)
goto done;
}
}
else
if (strcmp(cmd, "put")==0){
if (argc != 6)
usage(argv[0]);
if (xml_parse_file(0, "</clicon>", NULL, &xt) < 0)
goto done;
if (xml_rootchild(xt, 0, &xn) < 0)
goto done;
if (strcmp(argv[5], "set") == 0)
op = OP_REPLACE;
else
if (strcmp(argv[4], "merge") == 0)
op = OP_MERGE;
else if (strcmp(argv[5], "delete") == 0)
op = OP_REMOVE;
else
usage(argv[0]);
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((ret = xmldb_put(h, db, op, NULL, xn, cbret)) < 0)
goto done;
if (ret == 0)
fprintf(stderr, "%s\n", cbuf_get(cbret));
}
else
usage(argv[0]);
printf("\n");
done:
if (cbret)
cbuf_free(cbret);
return 0;
}
#endif /* Test program */