clixon/lib/src/clixon_datastore_write.c
Olof hagsand 2fe185d683 * Support for multiple patterns as described in RFC7950 Section 9.4.7
* Added regex cache to type resolution
* Added compiled regexp parameter as part of internal yang type resolution functions
* All internal `ys_populate_*()` functions (except ys_populate()) have switched parameters: `clicon_handle, yang_stmt *)`
2019-05-29 11:39:09 +02:00

781 lines
23 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 *****
*/
#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>
/* 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_json.h"
#include "clixon_nacm.h"
#include "clixon_netconf_lib.h"
#include "clixon_yang_module.h"
#include "clixon_xml_map.h"
#include "clixon_datastore.h"
#include "clixon_datastore_write.h"
#include "clixon_datastore_read.h"
#include "clixon_datastore_tree.h"
/*! 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(clicon_handle h,
cxobj *x0,
yang_stmt *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;
int changed = 0; /* Only if x0p's children have changed-> sort is necessary */
/* 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 (yang_keyword_get(y0) == Y_LEAF_LIST || yang_keyword_get(y0) == 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;
#ifdef USE_XML_INSERT
/* Add new xml node but without parent - insert when node fully
copied (see changed conditional below) */
if ((x0 = xml_new(x1name, NULL, (yang_stmt*)y0)) == NULL)
goto done;
#else
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
goto done;
#endif
changed++;
/* 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(yang_parent_get(y0), 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;
}
}
}
#ifdef USE_XML_INSERT
if (changed){
if (xml_insert(x0p, x0) < 0)
goto done;
}
#endif
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 (yang_keyword_get(y0) == Y_ANYXML ||
yang_keyword_get(y0) == 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;
}
#ifdef USE_XML_INSERT
/* Add new xml node but without parent - insert when node fully
* copied (see changed conditional below)
* Note x0 may dangle cases if exit before changed conditional
*/
if ((x0 = xml_new(x1name, NULL, (yang_stmt*)y0)) == NULL)
goto done;
#else
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
goto done;
#endif
changed++;
/* 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(h, x0c, 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;
}
#ifdef USE_XML_INSERT
if (changed){
if (xml_insert(x0p, x0) < 0)
goto done;
}
#endif
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 */
#ifndef USE_XML_INSERT
if (changed)
xml_sort(x0p, h);
#endif
retval = 1;
done:
#ifdef USE_XML_INSERT
/* Remove dangling added objects */
if (changed && x0 && xml_parent(x0)==NULL)
xml_purge(x0);
#endif
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(clicon_handle h,
cxobj *x0,
cxobj *x1,
yang_stmt *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(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(h, x0c, 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)
*/
static 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 (yang_keyword_get(y) == Y_CONTAINER &&
xml_child_nr_notype(x, CX_ATTR)==0 &&
yang_find(y, Y_PRESENCE, NULL) == NULL)
xml_flag_set(x, XML_FLAG_MARK); /* Mark, remove later */
retval = 0;
done:
return retval;
}
/*! Modify database given an xml tree and an operation
*
* @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] xt 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
* The xml may contain the "operation" attribute which defines the operation.
* @code
* cxobj *xt;
* cxobj *xret = NULL;
* if (xml_parse_string("<a>17</a>", yspec, &xt) < 0)
* err;
* if ((ret = xmldb_put(h, "running", OP_MERGE, xt, username, cbret)) < 0)
* err;
* if (ret==0)
* cbret contains netconf error message
* @endcode
* @note that you can add both config data and state data. In comparison,
* xmldb_get has a parameter to get config data only.
* @note if xret is non-null, it may contain error message
*/
int
xmldb_put(clicon_handle h,
const char *db,
enum operation_type op,
cxobj *x1,
char *username,
cbuf *cbret)
{
int retval = -1;
char *dbfile = NULL;
FILE *f = NULL;
cbuf *cb = NULL;
yang_stmt *yspec;
cxobj *x0 = NULL;
db_elmnt *de = NULL;
int ret;
cxobj *xnacm = NULL;
char *mode;
cxobj *xnacm0 = NULL;
cxobj *xmodst = NULL;
cxobj *x;
int permit = 0; /* nacm permit all */
char *format;
if (cbret == NULL){
clicon_err(OE_XML, EINVAL, "cbret is NULL");
goto done;
}
if ((yspec = clicon_dbspec_yang(h)) == 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 ((de = clicon_db_elmnt_get(h, db)) != NULL){
if (clicon_datastore_cache(h) != DATASTORE_NOCACHE)
x0 = de->de_xml;
}
/* If there is no xml x0 tree (in cache), then read it from file */
if (x0 == NULL){
if (xmldb_readfile(h, 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 = clicon_option_str(h, "CLICON_NACM_MODE");
if (mode){
if (strcmp(mode, "external")==0)
xnacm0 = clicon_nacm_ext(h);
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(h, 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|XML_FLAG_MARK)) < 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 (clicon_datastore_cache(h) != DATASTORE_NOCACHE){
db_elmnt de0 = {0,};
if (de != NULL)
de0 = *de;
if (de0.de_xml == NULL){
de0.de_xml = x0;
clicon_db_elmnt_set(h, db, &de0);
}
}
if (xmldb_db2file(h, 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 ((x = clicon_modst_cache_get(h, 1)) != NULL){
if ((xmodst = xml_dup(x)) == NULL)
goto done;
if (xml_addsub(x0, xmodst) < 0)
goto done;
}
if ((format = clicon_option_str(h, "CLICON_XMLDB_FORMAT")) == NULL){
clicon_err(OE_CFG, ENOENT, "No CLICON_XMLDB_FORMAT");
goto done;
}
if (strcmp(format, "tree") == 0){
if (datastore_tree_write(h, dbfile, x0) < 0)
goto done;
}
else{
if ((f = fopen(dbfile, "w")) == NULL){
clicon_err(OE_CFG, errno, "Creating file %s", dbfile);
goto done;
}
if (strcmp(format,"json")==0){
if (xml2json(f, x0, clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) < 0)
goto done;
}
else if (clicon_xml2file(f, x0, 0, clicon_option_bool(h, "CLICON_XMLDB_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 (x0 && clicon_datastore_cache(h) == DATASTORE_NOCACHE)
xml_free(x0);
return retval;
fail:
retval = 0;
goto done;
}