1579 lines
58 KiB
C
1579 lines
58 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (C) 2009-2019 Olof Hagsand
|
|
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
|
|
|
This file is part of CLIXON.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
Alternatively, the contents of this file may be used under the terms of
|
|
the GNU General Public License Version 3 or later (the "GPL"),
|
|
in which case the provisions of the GPL are applicable instead
|
|
of those above. If you wish to allow use of your version of this file only
|
|
under the terms of the GPL, and not to allow others to
|
|
use your version of this file under the terms of Apache License version 2,
|
|
indicate your decision by deleting the provisions above and replace them with
|
|
the notice and other provisions required by the GPL. If you do not delete
|
|
the provisions above, a recipient may use your version of this file under
|
|
the terms of any one of the Apache License version 2 or the GPL.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
|
|
*/
|
|
|
|
#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 <stdint.h>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <syslog.h>
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
/* cligen */
|
|
#include <cligen/cligen.h>
|
|
|
|
/* clixon */
|
|
#include "clixon_string.h"
|
|
#include "clixon_map.h"
|
|
#include "clixon_queue.h"
|
|
#include "clixon_hash.h"
|
|
#include "clixon_digest.h"
|
|
#include "clixon_handle.h"
|
|
#include "clixon_yang.h"
|
|
#include "clixon_xml.h"
|
|
#include "clixon_err.h"
|
|
#include "clixon_log.h"
|
|
#include "clixon_debug.h"
|
|
#include "clixon_file.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_type.h"
|
|
#include "clixon_yang_module.h"
|
|
#include "clixon_yang_schema_mount.h"
|
|
#include "clixon_xml_nsctx.h"
|
|
#include "clixon_xml_io.h"
|
|
#include "clixon_xml_bind.h"
|
|
#include "clixon_xml_default.h"
|
|
#include "clixon_xml_map.h"
|
|
#include "clixon_datastore.h"
|
|
#include "clixon_datastore_write.h"
|
|
#include "clixon_datastore_read.h"
|
|
|
|
/* Local types */
|
|
/* Argument to apply for recursive call to xmldb_multi write calls
|
|
* @see xmldb_multi_read_arg
|
|
*/
|
|
struct xmldb_multi_write_arg {
|
|
clixon_handle *mw_h;
|
|
const char *mw_db;
|
|
int mw_pretty;
|
|
withdefaults_type mw_wdef;
|
|
enum format_enum mw_format;
|
|
};
|
|
|
|
/*! Given an attribute name and its expected namespace, find its value
|
|
*
|
|
* An attribute may have a prefix(or NULL). The routine finds the associated
|
|
* xmlns binding to find the namespace: <namespace>:<name>.
|
|
* If such an attribute is not found, failure is returned with cbret set,
|
|
* If such an attribute is found, its string value is returned and removed from XML
|
|
* @param[in] x XML node (where to look for attribute)
|
|
* @param[in] name Attribute name
|
|
* @param[in] ns (Expected) Namespace of attribute
|
|
* @param[out] cbret Error message (if retval=0)
|
|
* @param[out] valp Malloced value (if retval=1)
|
|
* @retval 1 OK
|
|
* @retval 0 Failed (cbret set)
|
|
* @retval -1 Error
|
|
* @note as a side.effect the attribute is removed
|
|
*/
|
|
static int
|
|
attr_ns_value(cxobj *x,
|
|
char *name,
|
|
char *ns,
|
|
cbuf *cbret,
|
|
char **valp)
|
|
{
|
|
int retval = -1;
|
|
cxobj *xa;
|
|
char *ans = NULL; /* attribute namespace */
|
|
char *val = NULL;
|
|
|
|
/* prefix=NULL since we do not know the prefix */
|
|
if ((xa = xml_find_type(x, NULL, name, CX_ATTR)) != NULL){
|
|
if (xml2ns(xa, xml_prefix(xa), &ans) < 0)
|
|
goto done;
|
|
if (ans == NULL){ /* the attribute exists, but no namespace */
|
|
if (netconf_bad_attribute(cbret, "application", name, "Unresolved attribute prefix (no namespace?)") < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
/* the attribute exists, but not w expected namespace */
|
|
if (ns == NULL ||
|
|
strcmp(ans, ns) == 0){
|
|
if ((val = strdup(xml_value(xa))) == NULL){
|
|
clixon_err(OE_UNIX, errno, "malloc");
|
|
goto done;
|
|
}
|
|
xml_purge(xa);
|
|
}
|
|
}
|
|
*valp = val;
|
|
val = NULL;
|
|
retval = 1;
|
|
done:
|
|
if (val)
|
|
free(val);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! When new body is added, some needs type lookup is made and namespace checked
|
|
*
|
|
* This includes identityrefs, paths
|
|
* This code identifies x0 as an identityref, looks at the _body_ string and ensures the right
|
|
* namespace is inserted in x1.
|
|
* @param[in] x1 Base xml tree (can be NULL in add scenarios)
|
|
* @param[in] x0 XML tree which modifies base
|
|
* @param[in] x0p Parent of x0
|
|
* @param[in] x1bstr Body string of x1
|
|
* @param[in] y Yang of x0 (and x1)
|
|
* @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0.
|
|
* @retval 1 OK
|
|
* @retval 0 Failed (cbret set)
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
check_body_namespace(cxobj *x0,
|
|
cxobj *x0p,
|
|
cxobj *x1,
|
|
char *x1bstr,
|
|
yang_stmt *y,
|
|
cbuf *cbret)
|
|
{
|
|
int retval = -1;
|
|
char *prefix = NULL;
|
|
char *ns0 = NULL;
|
|
char *ns1 = NULL;
|
|
cxobj *x;
|
|
int isroot;
|
|
cbuf *cberr = NULL;
|
|
|
|
/* XXX: need to identify root better than hiereustics and strcmp,... */
|
|
isroot = xml_parent(x0p)==NULL &&
|
|
strcmp(xml_name(x0p), DATASTORE_TOP_SYMBOL) == 0 &&
|
|
xml_prefix(x0p)==NULL;
|
|
if (nodeid_split(x1bstr, &prefix, NULL) < 0)
|
|
goto done;
|
|
if (prefix == NULL)
|
|
goto ok; /* skip */
|
|
if (xml2ns(x1, prefix, &ns0) < 0)
|
|
goto done;
|
|
if (xml2ns(x0, prefix, &ns1) < 0)
|
|
goto done;
|
|
if (ns0 != NULL && ns1 != NULL){ /* namespace exists in both x1 and x0 */
|
|
if (strcmp(ns0, ns1)){
|
|
/* prefixes in x1 and x0 refers to different namespaces
|
|
* XXX return netconf error instead bad-attribue?
|
|
*/
|
|
if ((cberr = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
cprintf(cberr, "identityref: \"%s\": namespace collision %s vs %s", x1bstr, ns0, ns1);
|
|
if (netconf_invalid_value(cbret, "application", cbuf_get(cberr)) < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
}
|
|
else if (ns0 != NULL && ns1 == NULL){ /* namespace exists in x0 but not in x1: add it to x1*/
|
|
if (isroot)
|
|
x = x0;
|
|
else
|
|
x = x0p;
|
|
if (nscache_set(x, prefix, ns0) < 0)
|
|
goto done;
|
|
/* Create xmlns attribute to x0 XXX same code ^*/
|
|
if (prefix){
|
|
if (xml_add_attr(x, prefix, ns0, "xmlns", NULL) == NULL)
|
|
goto done;
|
|
}
|
|
else
|
|
if (xml_add_attr(x, "xmlns", ns0, NULL, NULL) == NULL)
|
|
goto done;
|
|
xml_sort(x); /* Ensure attr is first / XXX xml_insert? */
|
|
}
|
|
#if 0
|
|
else if (ns0 == NULL && ns1 != NULL){ /* namespace exists in x1 but not in x0: OK (but request is realy invalid */
|
|
}
|
|
#endif
|
|
else{ /* Namespace does not exist in x0: error */
|
|
if ((cberr = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
cprintf(cberr, "identityref: \"%s\": prefix \"%s\" has no associated namespace", x1bstr, prefix);
|
|
if (netconf_invalid_value(cbret, "application", cbuf_get(cberr)) < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
ok:
|
|
retval = 1;
|
|
done:
|
|
if (cberr)
|
|
cbuf_free(cberr);
|
|
if (prefix)
|
|
free(prefix);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Check yang when condition between a new xml x1 and old x0
|
|
*
|
|
* Check if there is a when condition. First try it on the new request (x1), then on the
|
|
* existing (x0).
|
|
* This is according to RFC 7950 8.3.2 NETCONF <edit-config> Processing
|
|
* During this processing [of edit-config] :
|
|
* o Modification requests for nodes tagged with "when", and the "when"
|
|
* condition evaluates to "false". In this case, the server MUST
|
|
* reply with an "unknown-element" <error-tag> in the <rpc-error>.
|
|
* This is somewhat strange since this is different from checking "when" at validation
|
|
* @param[in] x0p Parent of x0
|
|
* @param[in] x1 XML tree which modifies base
|
|
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
|
|
* @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0.
|
|
* @retval 1 OK
|
|
* @retval 0 Failed (cbret set)
|
|
* @retval -1 Error
|
|
* @note There may be some combination cases (x0+x1) that are not covered in this function.
|
|
*/
|
|
static int
|
|
check_when_condition(cxobj *x0p,
|
|
cxobj *x1,
|
|
yang_stmt *y0,
|
|
cbuf *cbret)
|
|
{
|
|
int retval = -1;
|
|
char *xpath = NULL;
|
|
cvec *nsc = NULL;
|
|
int nr;
|
|
yang_stmt *y = NULL;
|
|
cbuf *cberr = NULL;
|
|
cxobj *x1p;
|
|
|
|
if ((y = y0) != NULL ||
|
|
(y = (yang_stmt*)xml_spec(x1)) != NULL){
|
|
if ((xpath = yang_when_xpath_get(y)) != NULL){
|
|
nsc = yang_when_nsc_get(y);
|
|
x1p = xml_parent(x1);
|
|
if ((nr = xpath_vec_bool(x1p, nsc, "%s", xpath)) < 0) /* Try request */
|
|
goto done;
|
|
if (nr == 0){
|
|
/* Try existing tree */
|
|
if ((nr = xpath_vec_bool(x0p, nsc, "%s", xpath)) < 0)
|
|
goto done;
|
|
if (nr == 0){
|
|
if ((cberr = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
cprintf(cberr, "Node '%s' tagged with 'when' condition '%s' in module '%s' evaluates to false in edit-config operation (see RFC 7950 Sec 8.3.2)",
|
|
yang_argument_get(y),
|
|
xpath,
|
|
yang_argument_get(ys_module(y)));
|
|
if (netconf_unknown_element(cbret, "application", yang_argument_get(y),
|
|
cbuf_get(cberr)) < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
retval = 1;
|
|
done:
|
|
if (nsc)
|
|
cvec_free(nsc);
|
|
if (cberr)
|
|
cbuf_free(cberr);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Check if x0/y0 is part of other choice/case than y1 recursively , if so purge
|
|
*
|
|
* @retval 1 yes, y0 is in other case than y1
|
|
* @retval 0 No, y0 it is not in other case than y1
|
|
*/
|
|
static int
|
|
choice_is_other(yang_stmt *y0c,
|
|
yang_stmt *y0case,
|
|
yang_stmt *y0choice,
|
|
yang_stmt *y1c,
|
|
yang_stmt *y1case,
|
|
yang_stmt *y1choice)
|
|
{
|
|
yang_stmt *ycase;
|
|
yang_stmt *ychoice;
|
|
|
|
if (y0choice == y1choice){
|
|
if ((y0case == NULL && y0c != y1c) ||
|
|
y0case != y1case){
|
|
return 1;
|
|
}
|
|
}
|
|
else {
|
|
/* First recurse y0 */
|
|
if (yang_choice_case_get(y0choice, &ycase, &ychoice)){
|
|
if (choice_is_other(y0choice, ycase, ychoice,
|
|
y1c, y1case, y1choice) == 1)
|
|
return 1;
|
|
}
|
|
/* Second recurse y1 */
|
|
if (yang_choice_case_get(y1choice, &ycase, &ychoice)){
|
|
if (choice_is_other(y0choice, y0case, y0choice,
|
|
y1choice, ycase, ychoice) == 1)
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*! Check if choice nodes and implicitly remove all other cases.
|
|
*
|
|
* Special case is if yc parent (yp) is choice/case
|
|
* then find x0 child with same yc even though it does not match lexically
|
|
* However this will give another y0c != yc
|
|
* @param[in] x0 Base tree node
|
|
* @param[in] y1c Yang spec of tree child. If null revert to linear search.
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*
|
|
* From RFC 7950 Sec 7.9
|
|
* Since only one of the choice's cases can be valid at any time in the
|
|
* data tree, the creation of a node from one case implicitly deletes
|
|
* all nodes from all other cases. If a request creates a node from a
|
|
* case, the server will delete any existing nodes that are defined in
|
|
* other cases inside the choice.
|
|
*/
|
|
static int
|
|
choice_delete_other(cxobj *x0,
|
|
yang_stmt *y1c)
|
|
{
|
|
int retval = -1;
|
|
cxobj *x0c;
|
|
cxobj *x0prev;
|
|
yang_stmt *y0c;
|
|
yang_stmt *y0case;
|
|
yang_stmt *y0choice;
|
|
yang_stmt *y1case = NULL;
|
|
yang_stmt *y1choice = NULL;
|
|
|
|
if (yang_choice_case_get(y1c, &y1case, &y1choice) == 0)
|
|
goto ok;
|
|
x0prev = NULL;
|
|
x0c = NULL;
|
|
while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) {
|
|
if ((y0c = xml_spec(x0c)) == NULL ||
|
|
yang_parent_get(y0c) == NULL){
|
|
x0prev = x0c;
|
|
continue;
|
|
}
|
|
if (yang_choice_case_get(y0c, &y0case, &y0choice) == 0){
|
|
x0prev = x0c;
|
|
continue;
|
|
}
|
|
/* Check if x0/y0 is part of other choice/case than y1 recursively , if so purge */
|
|
if (choice_is_other(y0c, y0case, y0choice, y1c, y1case, y1choice) == 1){
|
|
if (xml_purge(x0c) < 0)
|
|
goto done;
|
|
x0c = x0prev;
|
|
continue;
|
|
}
|
|
x0prev = x0c;
|
|
}
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
|
|
* @param[in] x0p Parent of x0
|
|
* @param[in] x0t Top level of existing tree, eg needed for NACM rules
|
|
* @param[in] x1 XML tree which modifies base
|
|
* @param[in] x1t Request root node (nacm needs this)
|
|
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 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 OK
|
|
* @retval 0 Failed (cbret set)
|
|
* @retval -1 Error
|
|
* Assume x0 and x1 are same on entry and that y is the spec
|
|
* @see text_modify_top
|
|
* RFC 7950 Sec 7.7.9(leaf-list), 7.8.6(lists)
|
|
* In an "ordered-by user" list, the attributes "insert" and "key" in
|
|
* the YANG XML namespace can be used to control where
|
|
* in the list the entry is inserted.
|
|
* Marks added objects with XML_FLAG_ADD
|
|
*/
|
|
static int
|
|
text_modify(clixon_handle h,
|
|
cxobj *x0,
|
|
cxobj *x0p,
|
|
cxobj *x0t,
|
|
cxobj *x1,
|
|
cxobj *x1t,
|
|
yang_stmt *y0,
|
|
enum operation_type op,
|
|
char *username,
|
|
cxobj *xnacm,
|
|
int permit,
|
|
cbuf *cbret)
|
|
{
|
|
int retval = -1;
|
|
char *opstr = NULL;
|
|
char *x1name;
|
|
char *x1cname; /* child name */
|
|
cxobj *x0c; /* base child */
|
|
cxobj *x0b; /* base body */
|
|
cxobj *x1c; /* mod child */
|
|
char *x0bstr; /* mod body string */
|
|
char *x1bstr; /* mod body string */
|
|
yang_stmt *yc; /* yang child */
|
|
cxobj **x0vec = NULL;
|
|
int i;
|
|
int j;
|
|
int ret;
|
|
char *instr = NULL;
|
|
char *keystr = NULL;
|
|
char *valstr = NULL;
|
|
enum insert_type insert = INS_LAST;
|
|
int changed = 0; /* Only if x0p's children have changed-> sort necessary */
|
|
cvec *nscx1 = NULL;
|
|
char *createstr = NULL;
|
|
yang_stmt *yrestype = NULL;
|
|
char *restype;
|
|
int ismount = 0;
|
|
yang_stmt *mount_yspec = NULL;
|
|
|
|
if (x1 == NULL){
|
|
clixon_err(OE_XML, EINVAL, "x1 is missing");
|
|
goto done;
|
|
}
|
|
if ((ret = check_when_condition(x0p, x1, y0, cbret)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
/* Check for operations embedded in tree according to netconf */
|
|
if ((ret = attr_ns_value(x1, "operation", NETCONF_BASE_NAMESPACE,
|
|
cbret, &opstr)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
if (opstr != NULL)
|
|
if (xml_operation(opstr, &op) < 0)
|
|
goto done;
|
|
if ((ret = attr_ns_value(x1, "objectcreate", NULL, cbret, &createstr)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
if (createstr != NULL &&
|
|
(op == OP_REPLACE || op == OP_MERGE || op == OP_CREATE)){
|
|
if (x0 == NULL || xml_default_nopresence(x0, 0, 0)){ /* does not exist or is default */
|
|
if (strcmp(createstr, "false")==0){
|
|
/* RFC 8040 4.6 PATCH:
|
|
* If the target resource instance does not exist, the server MUST NOT create it.
|
|
*/
|
|
if (netconf_data_missing(cbret,
|
|
"RFC 8040 4.6. PATCH: If the target resource instance does not exist, the server MUST NOT create it") < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
clicon_data_set(h, "objectexisted", "false");
|
|
}
|
|
else{ /* exists */
|
|
clicon_data_set(h, "objectexisted", "true");
|
|
}
|
|
}
|
|
x1name = xml_name(x1);
|
|
|
|
if (yang_keyword_get(y0) == Y_LEAF_LIST ||
|
|
yang_keyword_get(y0) == Y_LEAF){
|
|
/* This is a check that a leaf does not have sub-elements
|
|
* such as: <leaf>a <leaf>b</leaf> </leaf>
|
|
*/
|
|
if (xml_child_nr_type(x1, CX_ELMNT)){
|
|
if (netconf_unknown_element(cbret, "application", x1name, "Leaf contains sub-element") < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
/* If leaf-list and ordered-by user, then get yang:insert attribute
|
|
* See RFC 7950 Sec 7.7.9
|
|
*/
|
|
if (yang_keyword_get(y0) == Y_LEAF_LIST &&
|
|
yang_find(y0, Y_ORDERED_BY, "user") != NULL){
|
|
if ((ret = attr_ns_value(x1,
|
|
"insert", YANG_XML_NAMESPACE,
|
|
cbret, &instr)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
if (instr != NULL &&
|
|
xml_attr_insert2val(instr, &insert) < 0)
|
|
goto done;
|
|
if ((ret = attr_ns_value(x1,
|
|
"value", YANG_XML_NAMESPACE,
|
|
cbret, &valstr)) < 0)
|
|
goto done;
|
|
/* if insert/before, value attribute must be there */
|
|
if ((insert == INS_AFTER || insert == INS_BEFORE) &&
|
|
valstr == NULL){
|
|
if (netconf_missing_attribute(cbret, "application", "<bad-attribute>value</bad-attribute>", "Missing value attribute when insert is before or after") < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
}
|
|
x1bstr = xml_body(x1);
|
|
switch(op){
|
|
case OP_CREATE:
|
|
if (x0 && xml_flag(x0, XML_FLAG_DEFAULT)==0){
|
|
if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
case OP_REPLACE: /* fall thru */
|
|
case OP_MERGE:
|
|
if (!(op == OP_MERGE && (instr==NULL))) {
|
|
/* Remove existing, also applies to merge in the special case
|
|
* of ordered-by user and (changed) insert attribute.
|
|
*/
|
|
if (!permit && xnacm){
|
|
if ((ret = nacm_datanode_write(h, x1, x1t,
|
|
(x0 == NULL || xml_default_nopresence(x0, 0, 0))?NACM_CREATE:NACM_UPDATE,
|
|
username, xnacm, cbret)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
permit = 1;
|
|
}
|
|
/* XXX: Note, if there is an error in adding the object later, the
|
|
* original object is not reverted.
|
|
*/
|
|
if (x0){
|
|
xml_purge(x0);
|
|
x0 = NULL;
|
|
}
|
|
} /* OP_MERGE & insert */
|
|
/* If default flag, clear it, since replaced */
|
|
if (x0 && xml_flag(x0, XML_FLAG_DEFAULT))
|
|
xml_flag_reset(x0, XML_FLAG_DEFAULT);
|
|
case OP_NONE: /* fall thru */
|
|
if (x0==NULL){
|
|
if ((op != OP_NONE) && !permit && xnacm){
|
|
if ((ret = nacm_datanode_write(h, x1, x1t, NACM_CREATE, username, xnacm, cbret)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
permit = 1;
|
|
}
|
|
/* Add new xml node but without parent - insert when node fully
|
|
copied (see changed conditional below) */
|
|
if ((x0 = xml_new(x1name, NULL, CX_ELMNT)) == NULL)
|
|
goto done;
|
|
xml_spec_set(x0, y0);
|
|
/* Get namespace from x1
|
|
* Check if namespace exists in x0 parent
|
|
* if not add new binding and replace in x0.
|
|
* See also xmlns copying of attributes in the body section below
|
|
* Note that this may add "unnecessary" namespace declarations
|
|
*/
|
|
if (assign_namespace_element(x1, x0, x0p) < 0)
|
|
goto done;
|
|
changed++;
|
|
if (op==OP_NONE)
|
|
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
|
|
if (x1bstr){ /* empty type does not have body */ /* XXX Here x0 = <b></b> */
|
|
if ((x0b = xml_new("body", x0, CX_BODY)) == NULL)
|
|
goto done;
|
|
}
|
|
}
|
|
/* Some bodies (eg identityref) requires proper namespace setup, so a type lookup is
|
|
* necessary.
|
|
*/
|
|
if (yang_type_get(y0, NULL, &yrestype, NULL, NULL, NULL, NULL, NULL) < 0)
|
|
goto done;
|
|
if (yrestype == NULL){
|
|
clixon_err(OE_CFG, EFAULT, "No restype (internal error)");
|
|
goto done;
|
|
}
|
|
restype = yang_argument_get(yrestype);
|
|
/* Differentiate between an empty type (NULL) and an empty string "" */
|
|
if (x1bstr==NULL && strcmp(restype, "string")==0)
|
|
x1bstr="";
|
|
if (x1bstr){
|
|
if (strcmp(restype, "identityref") == 0){
|
|
x1bstr = clixon_trim2(x1bstr, " \t\n");
|
|
if ((ret = check_body_namespace(x0, x0p, x1, x1bstr, y0, cbret)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
}
|
|
else{
|
|
/* Some bodies strip pretty-printed here, unsure where to do this,.. */
|
|
if (strcmp(restype, "enumeration") == 0 ||
|
|
strcmp(restype, "bits") == 0)
|
|
x1bstr = clixon_trim2(x1bstr, " \t\n");
|
|
#if 0 /* Passes regression test without, keep for some time until other test requires it */
|
|
/* If origin body has namespace definitions, copy them. The reason is that
|
|
* some bodies rely on namespace prefixes, such as NACM path, but there is
|
|
* no way we can know this here.
|
|
* However, this may lead to namespace collisions if these prefixes are not
|
|
* canonical, and may collide with the assign_namespace_element() above (but that
|
|
* is for element symbols)
|
|
* Oh well.
|
|
*/
|
|
if (assign_namespace_body(x1, x0) < 0)
|
|
goto done;
|
|
#endif
|
|
}
|
|
/* XXX here x1bstr is checked for null, while adding an empty string above */
|
|
if ((x0b = xml_body_get(x0)) == NULL && x1bstr && strlen(x1bstr)){
|
|
if ((x0b = xml_new("body", x0, CX_BODY)) == NULL)
|
|
goto done;
|
|
}
|
|
x0bstr = xml_value(x0b);
|
|
if (x0bstr==NULL || strcmp(x0bstr, x1bstr)){
|
|
if ((op != OP_NONE) && !permit && xnacm){
|
|
if ((ret = nacm_datanode_write(h, x1, x1t,
|
|
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;
|
|
xml_flag_set(x0, XML_FLAG_ADD);
|
|
/* If a default value ies replaced, then reset default flag */
|
|
if (xml_flag(x0, XML_FLAG_DEFAULT))
|
|
xml_flag_reset(x0, XML_FLAG_DEFAULT);
|
|
}
|
|
} /* x1bstr */
|
|
if (changed){
|
|
if (xml_insert(x0p, x0, insert, valstr, NULL) < 0)
|
|
goto done;
|
|
xml_flag_set(x0, XML_FLAG_ADD);
|
|
}
|
|
break;
|
|
case OP_DELETE:
|
|
if (x0==NULL || xml_flag(x0, XML_FLAG_DEFAULT) != 0){
|
|
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(h, x0, x0t, NACM_DELETE, username, xnacm, cbret)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
}
|
|
x0bstr = xml_body(x0);
|
|
/* Purge if x1 value is NULL(match-all) or both values are equal */
|
|
if ((x1bstr == NULL) ||
|
|
((x0bstr=xml_body(x0)) != NULL && strcmp(x0bstr, x1bstr)==0)){
|
|
if (xml_purge(x0) < 0)
|
|
goto done;
|
|
xml_flag_set(x0p, XML_FLAG_DEL);
|
|
}
|
|
else {
|
|
if (op == OP_DELETE){
|
|
if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
} /* switch op */
|
|
} /* if LEAF|LEAF_LIST */
|
|
else { /* eg Y_CONTAINER, Y_LIST, Y_ANYXML */
|
|
/* If list and ordered-by user, then get insert attribute
|
|
<user nc:operation="create"
|
|
yang:insert="after"
|
|
yang:key="[ex:first-name='fred']
|
|
[ex:surname='flintstone']">
|
|
* See RFC 7950 Sec 7.8.6
|
|
*/
|
|
if (yang_keyword_get(y0) == Y_LIST &&
|
|
yang_find(y0, Y_ORDERED_BY, "user") != NULL){
|
|
if ((ret = attr_ns_value(x1,
|
|
"insert", YANG_XML_NAMESPACE,
|
|
cbret, &instr)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
if (instr != NULL &&
|
|
xml_attr_insert2val(instr, &insert) < 0)
|
|
goto done;
|
|
if ((ret = attr_ns_value(x1,
|
|
"key", YANG_XML_NAMESPACE,
|
|
cbret, &keystr)) < 0)
|
|
goto done;
|
|
|
|
/* if insert/before, key attribute must be there */
|
|
if ((insert == INS_AFTER || insert == INS_BEFORE) &&
|
|
keystr == NULL){
|
|
if (netconf_missing_attribute(cbret, "application", "<bad-attribute>key</bad-attribute>", "Missing key attribute when insert is before or after") < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
/* If keystr is set, need a full namespace context */
|
|
if (keystr && xml_nsctx_node(x1, &nscx1) < 0)
|
|
goto done;
|
|
}
|
|
switch(op){
|
|
case OP_CREATE:
|
|
if (x0 && xml_flag(x0, XML_FLAG_DEFAULT)==0){
|
|
if (xml_default_nopresence(x0, 0, 0) == 0){
|
|
if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
}
|
|
case OP_REPLACE: /* fall thru */
|
|
case OP_MERGE:
|
|
if (!(op == OP_MERGE && instr==NULL)){
|
|
/* Remove existing, also applies to merge in the special case
|
|
* of ordered-by user and (changed) insert attribute.
|
|
*/
|
|
if (!permit && xnacm){
|
|
if ((ret = nacm_datanode_write(h, x1, x1t, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
permit = 1;
|
|
}
|
|
/* XXX: Note, if there is an error in adding the object later, the
|
|
* original object is not reverted.
|
|
*/
|
|
if (x0){
|
|
xml_purge(x0);
|
|
x0 = NULL;
|
|
}
|
|
} /* OP_MERGE & insert */
|
|
case OP_NONE: /* fall thru */
|
|
/* Special case: anyxml, just replace tree,
|
|
See rfc6020 7.10.3
|
|
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 ||
|
|
xml_flag(x1, XML_FLAG_ANYDATA)){
|
|
if (op == OP_NONE)
|
|
break;
|
|
if (op==OP_MERGE && !permit && xnacm){
|
|
if ((ret = nacm_datanode_write(h, x1, x1t, 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, CX_ELMNT)) == NULL)
|
|
goto done;
|
|
if (xml_copy(x1, x0) < 0)
|
|
goto done;
|
|
break;
|
|
} /* anyxml, anydata */
|
|
if (x0==NULL){
|
|
if (op==OP_MERGE && !permit && xnacm){
|
|
if ((ret = nacm_datanode_write(h, x1, x1t, NACM_CREATE, username, xnacm, cbret)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
permit = 1;
|
|
}
|
|
/* 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, CX_ELMNT)) == NULL)
|
|
goto done;
|
|
xml_spec_set(x0, y0);
|
|
#ifdef XML_PARENT_CANDIDATE
|
|
xml_parent_candidate_set(x0, x0p);
|
|
#endif
|
|
changed++;
|
|
/* Get namespace from x1
|
|
* Check if namespace exists in x0 parent
|
|
* if not add new binding and replace in x0.
|
|
*/
|
|
if (assign_namespace_element(x1, x0, x0p) < 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_type(x1, CX_ELMNT), sizeof(x1))) == NULL){
|
|
clixon_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 by child matching */
|
|
if ((yc = yang_find_datanode(y0, x1cname)) == NULL){
|
|
if (clicon_option_bool(h, "CLICON_YANG_SCHEMA_MOUNT"))
|
|
yc = xml_spec(x1c);
|
|
if (yc == NULL){
|
|
if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1){
|
|
/* Add dummy Y_ANYDATA yang stmt, see ysp_add */
|
|
if ((yc = yang_anydata_add(y0, x1cname)) < 0)
|
|
goto done;
|
|
xml_spec_set(x1c, yc);
|
|
clixon_log(h, LOG_WARNING,
|
|
"%s: %d: No YANG spec for %s, anydata used",
|
|
__FUNCTION__, __LINE__, x1cname);
|
|
}
|
|
else{
|
|
if (netconf_unknown_element(cbret, "application", x1cname, "Unassigned yang spec") < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
/* There is a cornercase (eg augment) of multi-namespace trees where
|
|
* the yang child has a different namespace.
|
|
* As an alternative, return in populate where this is detected first time.
|
|
*/
|
|
if (yc != xml_spec(x1c)){
|
|
clixon_err(OE_YANG, errno, "XML node \"%s\" not in namespace %s",
|
|
x1cname, yang_find_mynamespace(y0));
|
|
goto done;
|
|
}
|
|
/* Check if existing choice/case should be deleted */
|
|
if (choice_delete_other(x0, yc) < 0)
|
|
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 (x0c) {
|
|
/* Duplicate check can happen if multiple operations on same object, whihc should be filtered, just silently drop */
|
|
for (j=0; j<i; j++)
|
|
if (x0vec[j] == x0c)
|
|
break;
|
|
if (j==i)
|
|
x0vec[i] = x0c; /* != NULL if x0c is matching x1c */
|
|
}
|
|
i++;
|
|
}
|
|
/* 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) {
|
|
x0c = x0vec[i++];
|
|
x1cname = xml_name(x1c);
|
|
if ((yc = yang_find_datanode(y0, x1cname)) == NULL){
|
|
if (clicon_option_bool(h, "CLICON_YANG_SCHEMA_MOUNT"))
|
|
yc = xml_spec(x1c);
|
|
}
|
|
if (clicon_option_bool(h, "CLICON_YANG_SCHEMA_MOUNT")){
|
|
/* Check if xc is unresolved mountpoint, ie no yang mount binding yet */
|
|
if ((ismount = xml_yang_mount_get(h, x1c, NULL, &mount_yspec)) < 0)
|
|
goto done;
|
|
if (ismount && mount_yspec == NULL &&
|
|
!xml_flag(x1c, XML_FLAG_ANYDATA)){
|
|
ret = 1;
|
|
}
|
|
else{
|
|
if ((ret = text_modify(h, x0c, x0, x0t, x1c, x1t,
|
|
yc, op,
|
|
username, xnacm, permit, cbret)) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
else if ((ret = text_modify(h, x0c, x0, x0t, x1c, x1t,
|
|
yc, 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;
|
|
}
|
|
if (changed){
|
|
#ifdef XML_PARENT_CANDIDATE
|
|
xml_parent_candidate_set(x0, NULL);
|
|
#endif
|
|
if (xml_insert(x0p, x0, insert, keystr, nscx1) < 0)
|
|
goto done;
|
|
xml_flag_set(x0, XML_FLAG_ADD);
|
|
}
|
|
break;
|
|
case OP_DELETE:
|
|
if (x0==NULL || xml_flag(x0, XML_FLAG_DEFAULT) != 0){
|
|
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(h, x0, x0t, NACM_DELETE, username, xnacm, cbret)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
}
|
|
if (xml_purge(x0) < 0)
|
|
goto done;
|
|
xml_flag_set(x0p, XML_FLAG_DEL);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
} /* CONTAINER switch op */
|
|
} /* else Y_CONTAINER */
|
|
retval = 1;
|
|
done:
|
|
if (valstr)
|
|
free(valstr);
|
|
if (keystr)
|
|
free(keystr);
|
|
if (instr)
|
|
free(instr);
|
|
if (opstr)
|
|
free(opstr);
|
|
if (createstr)
|
|
free(createstr);
|
|
if (nscx1)
|
|
xml_nsctx_free(nscx1);
|
|
/* Remove dangling added objects */
|
|
if (changed && x0 && xml_parent(x0)==NULL)
|
|
xml_purge(x0);
|
|
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] h Clixon handle
|
|
* @param[in] x0t Base xml tree (can be NULL in add scenarios)
|
|
* @param[in] x1t 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 OK
|
|
* @retval 0 Failed (cbret set)
|
|
* @retval -1 Error
|
|
* @see text_modify
|
|
*/
|
|
static int
|
|
text_modify_top(clixon_handle h,
|
|
cxobj *x0t,
|
|
cxobj *x1t,
|
|
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 = NULL;
|
|
int ret;
|
|
char *createstr = NULL;
|
|
|
|
/* Check for operations embedded in tree according to netconf */
|
|
if ((ret = attr_ns_value(x1t,
|
|
"operation", NETCONF_BASE_NAMESPACE,
|
|
cbret, &opstr)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
if (opstr != NULL)
|
|
if (xml_operation(opstr, &op) < 0)
|
|
goto done;
|
|
if ((ret = attr_ns_value(x1t, "objectcreate", NULL, cbret, &createstr)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
/* Special case if incoming x1t is empty, top-level only <config/> */
|
|
if (xml_child_nr_type(x1t, CX_ELMNT) == 0){
|
|
if (xml_child_nr_type(x0t, 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(h, x0t, x0t, NACM_DELETE, username, xnacm, cbret)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
permit = 1;
|
|
}
|
|
while ((x0c = xml_child_i(x0t, 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 (createstr != NULL){
|
|
if (xml_child_nr_type(x0t, CX_ELMNT)) /* base tree not empty */
|
|
clicon_data_set(h, "objectexisted", "true");
|
|
else
|
|
clicon_data_set(h, "objectexisted", "false");
|
|
}
|
|
if (!permit && xnacm){
|
|
if ((ret = nacm_datanode_write(h, x1t, x1t, NACM_UPDATE, username, xnacm, cbret)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
permit = 1;
|
|
}
|
|
while ((x0c = xml_child_i(x0t, 0)) != 0)
|
|
if (xml_purge(x0c) < 0)
|
|
goto done;
|
|
}
|
|
/* Loop through children of the modification tree */
|
|
x1c = NULL;
|
|
while ((x1c = xml_child_each(x1t, 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 (ymod != NULL &&
|
|
clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1){
|
|
/* Add dummy Y_ANYDATA yang stmt, see ysp_add */
|
|
if ((yc = yang_anydata_add(ymod, x1cname)) < 0)
|
|
goto done;
|
|
xml_spec_set(x1c, yc);
|
|
clixon_log(h, LOG_WARNING,
|
|
"%s: %d: No YANG spec for %s, anydata used",
|
|
__FUNCTION__, __LINE__, x1cname);
|
|
}
|
|
else{
|
|
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(x0t, x1c, yc, &x0c) < 0)
|
|
goto done;
|
|
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;
|
|
}
|
|
if ((ret = text_modify(h, x0c, x0t, x0t, x1c, x1t,
|
|
yc, 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:
|
|
if (opstr)
|
|
free(opstr);
|
|
if (createstr)
|
|
free(createstr);
|
|
return retval;
|
|
fail: /* cbret set */
|
|
retval = 0;
|
|
goto done;
|
|
} /* text_modify_top */
|
|
|
|
/*! Mark ancestor if any changes to children. Also mark changed xml as cache dirty
|
|
*
|
|
* @param[in] x XML node
|
|
* @param[in] arg General-purpose argument
|
|
* @retval 2 Locally abort this subtree, continue with others
|
|
* @retval 1 Abort, dont continue with others, return 1 to end user
|
|
* @retval 0 OK, continue
|
|
* @retval -1 Error, aborted at first error encounter, return -1 to end user
|
|
* @note WHEN node are not checked, but should be updated when doing validate. The reason is
|
|
* that clixon needs a global traversal to re-evaluate WHEN nodes depending on changed targets
|
|
*/
|
|
static int
|
|
xml_mark_added_ancestors(cxobj *x,
|
|
void *arg)
|
|
{
|
|
int flags = (intptr_t)arg;
|
|
|
|
if (xml_flag(x, flags)){
|
|
xml_apply_ancestor(x, (xml_applyfn_t*)xml_flag_set, (void*)(XML_FLAG_CHANGE));
|
|
return 2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
xml_mark_cache_dirty(cxobj *x,
|
|
void *arg)
|
|
{
|
|
if (xml_flag(x, XML_FLAG_CHANGE)){
|
|
xml_flag_set(x, XML_FLAG_CACHE_DIRTY);
|
|
return 0;
|
|
}
|
|
else if (xml_flag(x, XML_FLAG_ADD|XML_FLAG_DEL)){
|
|
if (xml_apply0(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)(XML_FLAG_CACHE_DIRTY)) < 0)
|
|
return -1;
|
|
}
|
|
return 2;
|
|
}
|
|
|
|
/*! 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] 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
|
|
* The xml may contain the "operation" attribute which defines the operation.
|
|
* @code
|
|
* cxobj *xt;
|
|
* cxobj *xret = NULL;
|
|
* if (clixon_xml_parse_string("<a>17</a>", YB_NONE, NULL, &xt, NULL) < 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 if xret is non-null, it may contain error message
|
|
* @note x1 may change as a side-effect (eg operation attributes are stripped)
|
|
*/
|
|
int
|
|
xmldb_put(clixon_handle h,
|
|
const char *db,
|
|
enum operation_type op,
|
|
cxobj *x1,
|
|
char *username,
|
|
cbuf *cbret)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yspec;
|
|
cxobj *x0 = NULL;
|
|
db_elmnt *de = NULL;
|
|
db_elmnt de0 = {0,};
|
|
int ret;
|
|
cxobj *xnacm = NULL;
|
|
int permit = 0; /* nacm permit all */
|
|
cvec *nsc = NULL; /* nacm namespace context */
|
|
int firsttime = 0;
|
|
cxobj *xerr = NULL;
|
|
|
|
clixon_debug(CLIXON_DBG_DATASTORE|CLIXON_DBG_DETAIL, "db %s", db);
|
|
if (cbret == NULL){
|
|
clixon_err(OE_XML, EINVAL, "cbret is NULL");
|
|
goto done;
|
|
}
|
|
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
|
clixon_err(OE_YANG, ENOENT, "No yang spec");
|
|
goto done;
|
|
}
|
|
if (x1 && strcmp(xml_name(x1), NETCONF_INPUT_CONFIG) != 0){
|
|
clixon_err(OE_XML, 0, "Top-level symbol of modification tree is %s, expected \"%s\"",
|
|
xml_name(x1), NETCONF_INPUT_CONFIG);
|
|
goto done;
|
|
}
|
|
if ((de = clicon_db_elmnt_get(h, db)) != NULL){
|
|
x0 = de->de_xml; /* XXX flag is not XML_FLAG_TOP */
|
|
}
|
|
/* If there is no xml x0 tree (in cache), then read it from file */
|
|
if (x0 == NULL){
|
|
firsttime++; /* to avoid leakage on error, see fail from text_modify */
|
|
/* xml looks like: <top><config><x>... where "x" is a top-level symbol in a module */
|
|
if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &x0, de, NULL, &xerr)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
}
|
|
if (strcmp(xml_name(x0), DATASTORE_TOP_SYMBOL) !=0 ||
|
|
xml_flag(x0, XML_FLAG_TOP) == 0){
|
|
clixon_err(OE_XML, 0, "Top-level symbol is %s, expected \"%s\"",
|
|
xml_name(x0), DATASTORE_TOP_SYMBOL);
|
|
goto done;
|
|
}
|
|
/* Here x0 looks like: <config>...</config> */
|
|
xnacm = clicon_nacm_cache(h);
|
|
permit = (xnacm==NULL);
|
|
/* Here assume if xnacm is set and !permit do NACM */
|
|
clicon_data_del(h, "objectexisted");
|
|
/*
|
|
* 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){
|
|
/* If first time and quit here, x0 is not written back into cache and leaks */
|
|
if (firsttime && x0){
|
|
xml_free(x0);
|
|
x0 = NULL;
|
|
}
|
|
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;
|
|
/* Mark ancestor if any changes to children. */
|
|
if (xml_apply(x0, CX_ELMNT, xml_mark_added_ancestors, (void*)(XML_FLAG_ADD|XML_FLAG_DEL)) < 0)
|
|
goto done;
|
|
/* Mark changed xml as cache dirty */
|
|
if (xml_apply(x0, CX_ELMNT, xml_mark_cache_dirty, NULL) < 0)
|
|
goto done;
|
|
/* Remove empty non-presence containers recursively.
|
|
*/
|
|
if (xml_default_nopresence(x0, 3, XML_FLAG_ADD|XML_FLAG_DEL) < 0)
|
|
goto done;
|
|
/* Complete defaults in incoming x1
|
|
*/
|
|
if (xml_global_defaults(h, x0, nsc, "/", yspec, 0) < 0)
|
|
goto done;
|
|
/* Add default recursive values */
|
|
if (xml_default_recurse(x0, 0, XML_FLAG_ADD|XML_FLAG_DEL) < 0)
|
|
goto done;
|
|
/* Write back to datastore cache if first time */
|
|
if (de != NULL)
|
|
de0 = *de;
|
|
if (de0.de_xml == NULL)
|
|
de0.de_xml = x0;
|
|
de0.de_empty = (xml_child_nr(de0.de_xml) == 0);
|
|
clicon_db_elmnt_set(h, db, &de0);
|
|
/* Write cache to file unless volatile (ie stop syncing to store) */
|
|
if (xmldb_volatile_get(h, db) == 0){
|
|
if (xmldb_write_cache2file(h, db) < 0)
|
|
goto done;
|
|
/* Clear flags from previous steps + dirty */
|
|
if (xml_apply(x0, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
|
|
(void*)(XML_FLAG_NONE|XML_FLAG_ADD|XML_FLAG_DEL|XML_FLAG_CHANGE|XML_FLAG_CACHE_DIRTY)) < 0)
|
|
goto done;
|
|
}
|
|
else {
|
|
/* Clear flags from previous steps */
|
|
if (xml_apply(x0, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
|
|
(void*)(XML_FLAG_NONE|XML_FLAG_ADD|XML_FLAG_DEL|XML_FLAG_CHANGE)) < 0)
|
|
goto done;
|
|
}
|
|
retval = 1;
|
|
done:
|
|
clixon_debug(CLIXON_DBG_DATASTORE | CLIXON_DBG_DETAIL, "retval:%d", retval);
|
|
if (xerr)
|
|
xml_free(xerr);
|
|
if (nsc)
|
|
xml_nsctx_free(nsc);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Callback function for xmldb-multi write
|
|
*
|
|
* Look for link attribute in XML, and if found open the linked file for parsing
|
|
* @param[in] x XML node
|
|
* @param[in] arg
|
|
* @retval 2 Locally abort this subtree, continue with others
|
|
* @retval 1 Abort, dont continue with others, return 1 to end user
|
|
* @retval 0 OK, continue
|
|
* @retval -1 Error, aborted at first error encounter, return -1 to end user
|
|
*/
|
|
static int
|
|
xmldb_multi_write_applyfn(cxobj *x,
|
|
void *arg)
|
|
{
|
|
struct xmldb_multi_write_arg *mw = (struct xmldb_multi_write_arg *) arg;
|
|
int retval = -1;
|
|
clixon_handle h = mw->mw_h;
|
|
yang_stmt *y;
|
|
int exist = 0;
|
|
char *xpath = NULL;
|
|
char *hexstr = NULL;
|
|
cbuf *cb = NULL;
|
|
char *subdir = NULL;
|
|
char *dbfile;
|
|
struct stat st = {0,};
|
|
int fd = -1;
|
|
FILE *fsub = NULL;
|
|
|
|
if (xml_child_nr_type(x, CX_ELMNT) > 0 &&
|
|
(y = xml_spec(x)) != NULL){
|
|
if (yang_extension_value(y, "xmldb-split", CLIXON_LIB_NS, &exist, NULL) < 0)
|
|
goto done;
|
|
if (exist){
|
|
if (xml2xpath(x, NULL, 1, 0, &xpath) < 0)
|
|
goto done;
|
|
if (clixon_digest_hex(xpath, &hexstr) < 0)
|
|
goto done;
|
|
if (xmldb_db2subdir(h, mw->mw_db, &subdir) < 0)
|
|
goto done;
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_XML, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
cprintf(cb, "%s/%s.xml", subdir, hexstr);
|
|
dbfile = cbuf_get(cb);
|
|
if (xml_flag(x, XML_FLAG_CACHE_DIRTY) ||
|
|
lstat(dbfile, &st) < 0){
|
|
clixon_debug(CLIXON_DBG_DATASTORE, "Open: %s for writing", dbfile);
|
|
if ((fd = open(dbfile, O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU)) < 0) {
|
|
clixon_err(OE_UNIX, errno, "open(%s)", dbfile);
|
|
goto done;
|
|
}
|
|
if ((fsub = fdopen(fd, "w")) == NULL){
|
|
clixon_err(OE_CFG, errno, "fdopen(%s)", dbfile);
|
|
goto done;
|
|
}
|
|
/* Dont recurse multi-file yet */
|
|
if (clixon_xml2file1(fsub, x, 0, mw->mw_pretty, NULL, fprintf, 1, 0, mw->mw_wdef, 0) < 0)
|
|
goto done;
|
|
}
|
|
retval = 2; /* Locally abort */
|
|
goto done;
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (fsub != NULL)
|
|
fclose(fsub);
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
if (subdir)
|
|
free(subdir);
|
|
if (xpath)
|
|
free(xpath);
|
|
if (hexstr)
|
|
free(hexstr);
|
|
return retval;
|
|
}
|
|
|
|
/* Given open file, xml-tree, and wdef, add modstate, get format and write to file
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] f Output file
|
|
* @param[in] xt Top of XML tree
|
|
* @param[in] format Output format
|
|
* @param[in] pretty Pretty-print
|
|
* @param[in] wdef With-defaults parameter
|
|
* @param[in] multi If split into multiple files
|
|
* @param[in] multidb Database name (only if multi)
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
xmldb_dump(clixon_handle h,
|
|
FILE *f,
|
|
cxobj *xt,
|
|
enum format_enum format,
|
|
int pretty,
|
|
withdefaults_type wdef,
|
|
int multi,
|
|
const char *multidb)
|
|
{
|
|
int retval = -1;
|
|
struct xmldb_multi_write_arg mw = {0,};
|
|
cxobj *xm;
|
|
cxobj *xmodst = NULL;
|
|
|
|
/* Add modstate */
|
|
if ((xm = clicon_modst_cache_get(h, 1)) != NULL){
|
|
if ((xmodst = xml_dup(xm)) == NULL)
|
|
goto done;
|
|
#if 1
|
|
/* This is inserted last in config, seems preferred */
|
|
if (xml_addsub(xt, xmodst) < 0)
|
|
goto done;
|
|
#else
|
|
/* This is inserted first in config */
|
|
if (xml_child_insert_pos(xt, xmodst, 0) < 0)
|
|
goto done;
|
|
#endif
|
|
xml_parent_set(xmodst, xt);
|
|
}
|
|
switch (format){
|
|
case FORMAT_XML:
|
|
if (clixon_xml2file1(f, xt, 0, pretty, NULL, fprintf, 0, 0, wdef, multi) < 0)
|
|
goto done;
|
|
if (multi){
|
|
mw.mw_h = h;
|
|
mw.mw_db = multidb;
|
|
mw.mw_pretty = pretty;
|
|
mw.mw_wdef = wdef;
|
|
mw.mw_format = format;
|
|
if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xmldb_multi_write_applyfn, &mw) < 0)
|
|
goto done;
|
|
}
|
|
break;
|
|
case FORMAT_JSON:
|
|
if (multi){
|
|
clixon_err(OE_CFG, errno, "JSON+multi not supported");
|
|
goto done;
|
|
}
|
|
if (clixon_json2file(f, xt, pretty, fprintf, 0, 0) < 0)
|
|
goto done;
|
|
break;
|
|
default:
|
|
clixon_err(OE_XML, 0, "Format %s not supported", format_int2str(format));
|
|
goto done;
|
|
break;
|
|
}
|
|
/* Remove modules state after writing to file */
|
|
if (xmodst && xml_purge(xmodst) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Given datastore, get cache and format, set wdef, add modstate and print to multiple files
|
|
*
|
|
* Also add mod-state if applicable
|
|
* @param[in] h Clixon handle
|
|
* @param[in] db Name of database to search in (filename including dir path
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
xmldb_write_cache2file(clixon_handle h,
|
|
const char *db)
|
|
{
|
|
int retval = -1;
|
|
cxobj *xt;
|
|
char *formatstr;
|
|
enum format_enum format = FORMAT_XML;
|
|
withdefaults_type wdef = WITHDEFAULTS_EXPLICIT;
|
|
int pretty;
|
|
int multi;
|
|
FILE *f = NULL;
|
|
char *dbfile = NULL;
|
|
|
|
if ((xt = xmldb_cache_get(h, db)) == NULL){
|
|
clixon_err(OE_XML, 0, "XML cache not found");
|
|
goto done;
|
|
}
|
|
pretty = clicon_option_bool(h, "CLICON_XMLDB_PRETTY");
|
|
multi = clicon_option_bool(h, "CLICON_XMLDB_MULTI");
|
|
if ((formatstr = clicon_option_str(h, "CLICON_XMLDB_FORMAT")) != NULL){
|
|
if ((format = format_str2int(formatstr)) < 0){
|
|
clixon_err(OE_XML, 0, "Format %s invalid", formatstr);
|
|
goto done;
|
|
}
|
|
}
|
|
if (xmldb_db2file(h, db, &dbfile) < 0)
|
|
goto done;
|
|
if ((f = fopen(dbfile, "w")) == NULL){
|
|
clixon_err(OE_CFG, errno, "fopen(%s)", dbfile);
|
|
goto done;
|
|
}
|
|
if (xmldb_dump(h, f, xt, format, pretty, wdef, multi, db) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
if (dbfile)
|
|
free(dbfile);
|
|
if (f)
|
|
fclose(f);
|
|
return retval;
|
|
}
|