clixon/datastore/text/clixon_xmldb_text.c
2017-04-17 19:47:32 +02:00

1020 lines
25 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2017 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>
/* 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 */
};
/*! 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;
}
/*! Database locking for candidate and running non-persistent
* Store an integer for running and candidate containing
* the session-id of the client holding the lock.
* @note This should probably be on file-system
*/
static int _running_locked = 0;
static int _candidate_locked = 0;
static int _startup_locked = 0;
/*! 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,
char *db,
char **filename)
{
int retval = -1;
cbuf *cb;
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;
}
if (strcmp(db, "running") != 0 &&
strcmp(db, "candidate") != 0 &&
strcmp(db, "startup") != 0 &&
strcmp(db, "tmp") != 0){
clicon_err(OE_XML, 0, "No such database: %s", db);
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;
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);
if (th){
if (th->th_dbdir)
free(th->th_dbdir);
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{
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 givenby context
* @param[in] xh XMLDB handle
* @param[in] optname Option name
* @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{
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
goto done;
}
retval = 0;
done:
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.
* @param[in] xh XMLDB handle
* @param[in] dbname Name of database to search in (filename including dir path
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[out] xtop Single XML tree which xvec points to. Free with xml_free()
* @param[out] xvec Vector of xml trees. Free after use.
* @param[out] xlen Length of vector.
* @retval 0 OK
* @retval -1 Error
* @code
* cxobj *xt;
* cxobj **xvec;
* size_t xlen;
* if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]",
* &xt, &xvec, &xlen) < 0)
* err;
* for (i=0; i<xlen; i++){
* xn = xv[i];
* ...
* }
* xml_free(xt);
* free(xvec);
* @endcode
* @note if xvec is given, then purge tree, if not return whole tree.
* @see xpath_vec
* @see xmldb_get
*/
int
text_get(xmldb_handle xh,
char *db,
char *xpath,
cxobj **xtop,
cxobj ***xvec0,
size_t *xlen0)
{
int retval = -1;
char *dbfile = NULL;
yang_spec *yspec;
cxobj *xt = NULL;
int fd = -1;
cxobj **xvec = NULL;
size_t xlen;
int i;
struct text_handle *th = handle(xh);
if (text_db2file(th, db, &dbfile) < 0)
goto done;
if (dbfile==NULL){
clicon_err(OE_XML, 0, "dbfile NULL");
goto done;
}
if ((yspec = th->th_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
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 ((clicon_xml_parse_file(fd, &xt, "</config>")) < 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{
assert(xml_child_nr(xt)==1);
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
}
/* XXX Maybe the below is general function and should be moved to xmldb? */
if (xpath_vec(xt, xpath?xpath:"/", &xvec, &xlen) < 0)
goto done;
/* If vectors are specified then filter out everything else,
* otherwise return complete tree.
*/
if (xvec != NULL){
for (i=0; i<xlen; i++)
xml_flag_set(xvec[i], XML_FLAG_MARK);
}
/* Top is special case */
if (!xml_flag(xt, XML_FLAG_MARK))
if (xml_tree_prune_unmarked(xt, NULL) < 0)
goto done;
if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
goto done;
if (xvec0 && xlen0){
*xvec0 = xvec;
xvec = NULL;
*xlen0 = xlen;
xlen = 0;
}
if (0){ /* No xml_spec(xt) */
if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0)
goto done;
/* XXX does not work for top-level */
if (xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0)
goto done;
if (xml_apply(xt, CX_ELMNT, xml_sanity, NULL) < 0)
goto done;
}
if (debug>1)
clicon_xml2file(stderr, xt, 0, 1);
*xtop = xt;
retval = 0;
done:
// xml_free(xt);
if (fd != -1)
close(fd);
return retval;
}
/*! Check if child with fullmatch exists
* param[in] cvk vector of index keys
*/
static cxobj *
find_keys(cxobj *xt,
char *name,
cvec *cvk,
char **valvec)
{
cxobj *xi = NULL;
int j;
char *keyname;
char *val;
cg_var *cvi;
char *body;
while ((xi = xml_child_each(xt, xi, CX_ELMNT)) != NULL)
if (strcmp(xml_name(xi), name) == 0){
j = 0; /* All keys must match. */
cvi = NULL;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
val = valvec[j++];
if ((body = xml_find_body(xi, keyname)) == NULL)
continue;
if (strcmp(body, val))
continue;
}
return xi;
}
return NULL;
}
/*! Create tree from api-path, ie fill in xml tree from the path
*/
static int
text_create_tree(char *xk,
cxobj *xt,
enum operation_type op,
yang_spec *yspec,
cxobj **xp)
{
int retval = -1;
char **vec = NULL;
int nvec;
int i;
int j;
char *name;
char *restval;
yang_stmt *y = NULL;
yang_stmt *ykey;
cxobj *x = NULL;
cxobj *xn = NULL; /* new */
cxobj *xb; /* body */
cvec *cvk = NULL; /* vector of index keys */
char **valvec = NULL;
int nvalvec;
cg_var *cvi;
char *keyname;
char *val2;
x = xt;
if (xk == NULL || *xk!='/'){
clicon_err(OE_DB, 0, "Invalid key: %s", xk);
goto done;
}
if ((vec = clicon_strsep(xk, "/", &nvec)) == NULL)
goto done;
/* Remove trailing '/'. Like in /a/ -> /a */
if (nvec > 1 && !strlen(vec[nvec-1]))
nvec--;
if (nvec < 1){
clicon_err(OE_XML, 0, "Malformed key: %s", xk);
goto done;
}
i = 1;
while (i<nvec){
name = vec[i]; /* E.g "x=1,2" -> name:x restval=1,2 */
if ((restval = index(name, '=')) != NULL){
*restval = '\0';
restval++;
}
if (y == NULL) /* top-node */
y = yang_find_topnode(yspec, name);
else
y = yang_find_syntax((yang_node*)y, name);
if (y == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
i++;
switch (y->ys_keyword){
case Y_LEAF_LIST:
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
fprintf(stderr, "XXX create =%s\n", restval);
x = xn;
break;
case Y_LIST:
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
if (valvec)
free(valvec);
if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
goto done;
if (cvec_len(cvk) != nvalvec){
clicon_err(OE_XML, errno, "List %s key length mismatch", name);
goto done;
}
cvi = NULL;
/* Check if exists, if not, create */
if ((xn = find_keys(x, name, cvk, valvec)) == NULL){
/* create them, bit not if delete op */
if (op == OP_DELETE || op == OP_REMOVE){
retval = 0;
goto done;
}
if ((xn = xml_new(name, x)) == NULL)
goto done;
xml_type_set(xn, CX_ELMNT);
x = xn;
j = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
val2 = valvec[j++];
if ((xn = xml_new(keyname, x)) == NULL)
goto done;
xml_type_set(xn, CX_ELMNT);
if ((xb = xml_new(keyname, xn)) == NULL)
goto done;
xml_type_set(xb, CX_BODY);
if (xml_value_set(xb, val2) <0)
goto done;
}
}
if (cvk){
cvec_free(cvk);
cvk = NULL;
}
break;
default: /* eg Y_CONTAINER */
if ((xn = xml_find(x, name)) == NULL){
/* Already removed */
if (op == OP_DELETE || op == OP_REMOVE){
retval = 0;
goto done;
}
if ((xn = xml_new(name, x)) == NULL)
goto done;
xml_type_set(xn, CX_ELMNT);
}
x = xn;
break;
}
}
*xp = x;
retval = 0;
done:
return retval;
}
/*! Modify database provided an xml tree and an operation
*
* @param[in] xh XMLDB handle
* @param[in] db running or candidate
* @param[in] xt xml-tree. Top-level symbol is dummy
* @param[in] op OP_MERGE: just add it.
* OP_REPLACE: first delete whole database
* OP_NONE: operation attribute in xml determines operation
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13])
* @retval 0 OK
* @retval -1 Error
* The xml may contain the "operation" attribute which defines the operation.
* @code
* cxobj *xt;
* if (clicon_xml_parse_str("<a>17</a>", &xt) < 0)
* err;
* if (xmldb_put(h, "running", OP_MERGE, NULL, xt) < 0)
* err;
* @endcode
* @see xmldb_put_xkey for single key
*/
int
text_put(xmldb_handle xh,
char *db,
enum operation_type op,
char *api_path,
cxobj *xadd)
{
int retval = -1;
struct text_handle *th = handle(xh);
char *dbfile = NULL;
int fd = -1;
cbuf *cb = NULL;
cbuf *xpcb = NULL; /* xpath cbuf */
yang_spec *yspec;
cxobj *xt = NULL;
cxobj *x = NULL;
cxobj *xc;
cxobj *xcopy = NULL;
if (xadd == NULL || strcmp(xml_name(xadd),"config")){
clicon_err(OE_XML, 0, "Misformed xml, should start with <config/>");
goto done;
}
if (text_db2file(th, db, &dbfile) < 0)
goto done;
if (dbfile==NULL){
clicon_err(OE_XML, 0, "dbfile NULL");
goto done;
}
if ((yspec = th->th_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
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 ((clicon_xml_parse_file(fd, &xt, "</config>")) < 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{
assert(xml_child_nr(xt)==1);
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
}
/* If xpath find first occurence or api-path (this is where we apply xml) */
if (api_path){
if (text_create_tree(api_path, xt, op, yspec, &x) < 0)
goto done;
}
else
x = xt;
assert(x);
/* Here point of where xt is applied to xt is 'x' */
switch (op){
case OP_CREATE:
if (x){
clicon_err(OE_DB, 0, "OP_CREATE: already exists in database");
goto done;
}
case OP_REPLACE:
while ((xc = xml_child_i(x, 0)) != NULL)
xml_purge(xc);
case OP_MERGE:
/* Loop thru xadd's children */
xc = NULL;
while ((xc = xml_child_each(xadd, xc, CX_ELMNT)) != NULL){
if ((xcopy = xml_new("new", x)) == NULL)
goto done;
xml_copy(xc, xcopy);
}
break;
case OP_DELETE:
if (x==NULL){
clicon_err(OE_DB, 0, "OP_DELETE: does not exist in database");
goto done;
}
case OP_REMOVE:
while ((xc = xml_child_i(x, 0)) != NULL)
xml_purge(xc);
break;
case OP_NONE:
break;
}
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (clicon_xml2cbuf(cb, xt, 0, 0) < 0)
goto done;
/* Reopen file in write mode */
close(fd);
if ((fd = open(dbfile, O_WRONLY | O_TRUNC, S_IRWXU)) < 0) {
clicon_err(OE_UNIX, errno, "open(%s)", dbfile);
goto done;
}
if (write(fd, cbuf_get(cb), cbuf_len(cb)+1) < 0){
clicon_err(OE_UNIX, errno, "write(%s)", dbfile);
goto done;
}
retval = 0;
done:
if (fd != -1)
close(fd);
if (cb)
cbuf_free(cb);
if (xpcb)
cbuf_free(xpcb);
if (xt)
xml_free(xt);
return retval;
}
/*! Copy database from db1 to db2
* @param[in] xh XMLDB handle
* @param[in] from Source database copy
* @param[in] to Destination database
* @retval -1 Error
* @retval 0 OK
*/
int
text_copy(xmldb_handle xh,
char *from,
char *to)
{
int retval = -1;
struct text_handle *th = handle(xh);
char *fromfile = NULL;
char *tofile = NULL;
/* XXX lock */
if (text_db2file(th, from, &fromfile) < 0)
goto done;
if (text_db2file(th, to, &tofile) < 0)
goto done;
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,
char *db,
int pid)
{
// struct text_handle *th = handle(xh);
if (strcmp("running", db) == 0)
_running_locked = pid;
else if (strcmp("candidate", db) == 0)
_candidate_locked = pid;
else if (strcmp("startup", db) == 0)
_startup_locked = pid;
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,
char *db)
{
// struct text_handle *th = handle(xh);
if (strcmp("running", db) == 0)
_running_locked = 0;
else if (strcmp("candidate", db) == 0)
_candidate_locked = 0;
else if (strcmp("startup", db) == 0)
_startup_locked = 0;
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)
{
// struct text_handle *th = handle(xh);
if (_running_locked == pid)
_running_locked = 0;
if (_candidate_locked == pid)
_candidate_locked = 0;
if (_startup_locked == pid)
_startup_locked = 0;
return 0;
}
/*! 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,
char *db)
{
// struct text_handle *th = handle(xh);
if (strcmp("running", db) == 0)
return (_running_locked);
else if (strcmp("candidate", db) == 0)
return(_candidate_locked);
else if (strcmp("startup", db) == 0)
return(_startup_locked);
return 0;
}
/*! 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,
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,
char *db)
{
int retval = -1;
char *filename = NULL;
struct text_handle *th = handle(xh);
if (text_db2file(th, db, &filename) < 0)
goto done;
if (unlink(filename) < 0){
clicon_err(OE_DB, errno, "unlink %s", filename);
goto done;
}
retval = 0;
done:
return retval;
}
/*! Initialize database
* @param[in] xh XMLDB handle
* @param[in] db Database
* @retval 0 OK
* @retval -1 Error
*/
int
text_init(xmldb_handle xh,
char *db)
{
int retval = -1;
struct text_handle *th = handle(xh);
char *filename = NULL;
int fd = -1;
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 init function */
int
text_plugin_exit(void)
{
return 0;
}
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_init,
};
#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 *yangdir;
char *yangmod;
yang_spec *yspec = NULL;
clicon_handle h;
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];
yangdir = argv[3];
yangmod = argv[4];
db_init(db);
if ((yspec = yspec_new()) == NULL)
goto done
if (yang_parse(h, yangdir, 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, NULL) < 0)
goto done;
clicon_xml2file(stdout, xt, 0, 1);
}
else
if (strcmp(cmd, "put")==0){
if (argc != 6)
usage(argv[0]);
if (clicon_xml_parse_file(0, &xt, "</clicon>") < 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 (xmldb_put(h, db, op, NULL, xn) < 0)
goto done;
}
else
usage(argv[0]);
printf("\n");
done:
return 0;
}
#endif /* Test program */