* RFC 8528 yang schema mount-points: * Made expand_dbvar and cli_dbxml mountpoint-aware (RFC 8528) * autocli supportgenerate * Made api_path2xml and xml2api_path mount-point-aware * Temporar fix in clixon_custom.h: XPATH_CANONICAL_SKIP_CHECK * `xml2xpath()`: Added `apostrophe` as 4th parameter, default 0 * removed extra assert.h includes
1573 lines
47 KiB
C
1573 lines
47 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
|
Copyright (C) 2017-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 <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <time.h>
|
|
#include <ctype.h>
|
|
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <syslog.h>
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/param.h>
|
|
#include <sys/mount.h>
|
|
#include <pwd.h>
|
|
|
|
/* cligen */
|
|
#include <cligen/cligen.h>
|
|
|
|
/* clicon */
|
|
#include <clixon/clixon.h>
|
|
|
|
#include "clixon_cli_api.h"
|
|
#include "cli_plugin.h"
|
|
#include "cli_common.h"
|
|
|
|
/*! Register log notification stream
|
|
* @param[in] h Clicon handle
|
|
* @param[in] stream Event stream. CLICON is predefined, others are application-defined
|
|
* @param[in] filter Filter. For xml notification ie xpath: .[name="kalle"]
|
|
* @param[in] status 0 for stop, 1 to start
|
|
* @param[in] fn Callback function called when notification occurs
|
|
* @param[in] arg Argument to function note
|
|
* Note this calls cligen_regfd which may callback on cli command interpretator
|
|
*/
|
|
int
|
|
cli_notification_register(clicon_handle h,
|
|
char *stream,
|
|
enum format_enum format,
|
|
char *filter,
|
|
int status,
|
|
int (*fn)(int, void*),
|
|
void *arg)
|
|
{
|
|
int retval = -1;
|
|
char *logname = NULL;
|
|
void *p;
|
|
int s;
|
|
clicon_hash_t *cdat = clicon_data(h);
|
|
size_t len;
|
|
int s_exist = -1;
|
|
|
|
len = strlen("log_socket_") + strlen(stream) + 1;
|
|
if ((logname = malloc(len)) == NULL){
|
|
clicon_err(OE_UNIX, errno, "malloc");
|
|
goto done;
|
|
}
|
|
snprintf(logname, len, "log_socket_%s", stream);
|
|
if ((p = clicon_hash_value(cdat, logname, &len)) != NULL)
|
|
s_exist = *(int*)p;
|
|
|
|
if (status){ /* start */
|
|
if (s_exist!=-1){
|
|
clicon_err(OE_PLUGIN, 0, "Result log socket already exists");
|
|
goto done;
|
|
}
|
|
if (clicon_rpc_create_subscription(h, stream, filter, &s) < 0)
|
|
goto done;
|
|
if (cligen_regfd(s, fn, arg) < 0)
|
|
goto done;
|
|
if (clicon_hash_add(cdat, logname, &s, sizeof(s)) == NULL)
|
|
goto done;
|
|
}
|
|
else{ /* stop */
|
|
if (s_exist != -1){
|
|
cligen_unregfd(s_exist);
|
|
}
|
|
clicon_hash_del(cdat, logname);
|
|
#if 0 /* cant turn off */
|
|
if (clicon_rpc_create_subscription(h, status, stream, format, filter, NULL) < 0)
|
|
goto done;
|
|
#endif
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (logname)
|
|
free(logname);
|
|
return retval;
|
|
}
|
|
|
|
/* Signal functions
|
|
* This is for CLIgen to handle these signals, eg ^Ĉ means abort command, not program
|
|
*/
|
|
void
|
|
cli_signal_block(clicon_handle h)
|
|
{
|
|
clicon_signal_block (SIGTSTP);
|
|
clicon_signal_block (SIGQUIT);
|
|
clicon_signal_block (SIGCHLD);
|
|
if (!clicon_quiet_mode(h))
|
|
clicon_signal_block (SIGINT);
|
|
}
|
|
|
|
void
|
|
cli_signal_unblock(clicon_handle h)
|
|
{
|
|
clicon_signal_unblock (SIGTSTP);
|
|
clicon_signal_unblock (SIGQUIT);
|
|
clicon_signal_unblock (SIGCHLD);
|
|
clicon_signal_unblock (SIGINT);
|
|
}
|
|
|
|
/*
|
|
* Flush pending signals for a given signal type
|
|
*/
|
|
void
|
|
cli_signal_flush(clicon_handle h)
|
|
{
|
|
/* XXX A bit rough. Use sigpending() and more clever logic ?? */
|
|
|
|
sigfn_t h1, h2, h3, h4;
|
|
|
|
set_signal(SIGTSTP, SIG_IGN, &h1);
|
|
set_signal(SIGQUIT, SIG_IGN, &h2);
|
|
set_signal(SIGCHLD, SIG_IGN, &h3);
|
|
set_signal(SIGINT, SIG_IGN, &h4);
|
|
|
|
cli_signal_unblock (h);
|
|
|
|
set_signal(SIGTSTP, h1, NULL);
|
|
set_signal(SIGQUIT, h2, NULL);
|
|
set_signal(SIGCHLD, h3, NULL);
|
|
set_signal(SIGINT, h4, NULL);
|
|
|
|
cli_signal_block (h);
|
|
}
|
|
|
|
/*! Create body and add last CLI variable vector as value
|
|
* Create and add an XML body as child of XML node xbot. Set its value to the last
|
|
* CLI variable vector element.
|
|
*/
|
|
static int
|
|
dbxml_body(cxobj *xbot,
|
|
cvec *cvv)
|
|
{
|
|
int retval = -1;
|
|
char *str = NULL;
|
|
cxobj *xb;
|
|
cg_var *cval;
|
|
int len;
|
|
|
|
len = cvec_len(cvv);
|
|
cval = cvec_i(cvv, len-1);
|
|
if ((str = cv2str_dup(cval)) == NULL){
|
|
clicon_err(OE_UNIX, errno, "cv2str_dup");
|
|
goto done;
|
|
}
|
|
if ((xb = xml_new("body", xbot, CX_BODY)) == NULL)
|
|
goto done;
|
|
if (xml_value_set(xb, str) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
if (str)
|
|
free(str);
|
|
return retval;
|
|
}
|
|
|
|
/*! Special handling of identityref:s whose body may be: <namespace prefix>:<id>
|
|
* Ensure the namespace is declared if it exists in YANG
|
|
*/
|
|
static int
|
|
identityref_add_ns(cxobj *x,
|
|
void *arg)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yspec = (yang_stmt *)arg;
|
|
yang_stmt *y;
|
|
yang_stmt *yrestype; /* resolved type */
|
|
char *restype; /* resolved type */
|
|
char *origtype = NULL; /* original type */
|
|
char *pf = NULL;
|
|
yang_stmt *yns;
|
|
char *ns = NULL;
|
|
|
|
if ((y = xml_spec(x)) != NULL &&
|
|
yang_keyword_get(y) == Y_LEAF){
|
|
if (yang_type_get(y, &origtype, &yrestype, NULL, NULL, NULL, NULL, NULL) < 0)
|
|
goto done;
|
|
restype = yrestype?yang_argument_get(yrestype):NULL;
|
|
if (strcmp(restype, "identityref") == 0){
|
|
if (nodeid_split(xml_body(x), &pf, NULL) < 0)
|
|
goto done;
|
|
// search if already defined
|
|
if (pf != NULL){
|
|
if (xml2ns(x, pf, &ns) < 0)
|
|
goto done;
|
|
if (ns == NULL &&
|
|
(yns = yang_find_module_by_prefix_yspec(yspec, pf)) != NULL){
|
|
if ((ns = yang_find_mynamespace(yns)) != NULL)
|
|
if (xmlns_set(x, pf, ns) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (origtype)
|
|
free(origtype);
|
|
if (pf)
|
|
free(pf);
|
|
return retval;
|
|
}
|
|
|
|
/*! Given a top-level yspec and montpoint xpath compute a set of
|
|
*
|
|
* Manipulate top-level and a mointpoint:
|
|
* YSPEC: yspec0 yspec1
|
|
* XML: top0-->bot0-->top1-->bot1
|
|
* API-PATH: api-path0 api-path1
|
|
* api-path01---------
|
|
* The result computed from the top-level yspec and montpoint xpath are:
|
|
* - api_pathfmt10 Combined api-path for both trees
|
|
*/
|
|
int
|
|
mtpoint_paths(yang_stmt *yspec0,
|
|
char *mtpoint,
|
|
char *api_path_fmt1,
|
|
char **api_path_fmt01)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *yu = NULL;
|
|
yang_stmt *ybot0 = NULL;
|
|
cvec *nsc0 = NULL;
|
|
int ret;
|
|
char *api_path_fmt0;
|
|
cbuf *cb = NULL;
|
|
cxobj *xbot0 = NULL;
|
|
cxobj *xtop0 = NULL;
|
|
yang_stmt *yspec1;
|
|
|
|
if (api_path_fmt01 == NULL){
|
|
clicon_err(OE_FATAL, EINVAL, "arg is NULL");
|
|
goto done;
|
|
}
|
|
if ((xtop0 = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
|
|
goto done;
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
if (yang_path_arg(yspec0, mtpoint, &yu) < 0)
|
|
goto done;
|
|
if (yu == NULL){
|
|
clicon_err(OE_FATAL, 0, "yu not found");
|
|
goto done;
|
|
}
|
|
if (yang_mount_get(yu, mtpoint, &yspec1) < 0)
|
|
goto done;
|
|
if (yspec1 == NULL){
|
|
clicon_err(OE_FATAL, 0, "yspec1 not found");
|
|
goto done;
|
|
}
|
|
xbot0 = xtop0;
|
|
if (xml_nsctx_yangspec(yspec0, &nsc0) < 0)
|
|
goto done;
|
|
if ((ret = xpath2xml(mtpoint, nsc0, xtop0, yspec0, &xbot0, &ybot0, NULL)) < 0)
|
|
goto done;
|
|
if (xbot0 == NULL){
|
|
clicon_err(OE_YANG, 0, "No xbot");
|
|
goto done;
|
|
}
|
|
if (yang2api_path_fmt(ybot0, 0, &api_path_fmt0) < 0)
|
|
goto done;
|
|
if (api_path_fmt0 == NULL){
|
|
clicon_err(OE_YANG, 0, "No api_path_fmt0");
|
|
goto done;
|
|
}
|
|
cprintf(cb, "%s%s", api_path_fmt0, api_path_fmt1);
|
|
if ((*api_path_fmt01 = strdup(cbuf_get(cb))) == NULL){
|
|
clicon_err(OE_YANG, errno, "strdup");
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
if (api_path_fmt0)
|
|
free(api_path_fmt0);
|
|
if (nsc0)
|
|
cvec_free(nsc0);
|
|
return retval;
|
|
}
|
|
|
|
/*! Modify xml datastore from a callback using xml key format strings
|
|
* @param[in] h Clicon handle
|
|
* @param[in] cvv Vector of cli string and instantiated variables
|
|
* @param[in] argv Vector: <apipathfmt> [<mointpt>], eg "/aaa/%s"
|
|
* @param[in] op Operation to perform on database
|
|
* @param[in] nsctx Namespace context for last value added
|
|
* cvv first contains the complete cli string, and then a set of optional
|
|
* instantiated variables.
|
|
* If the last node is a leaf, the last cvv element is added as a value. This value
|
|
* Example:
|
|
* cvv[0] = "set interfaces interface eth0 type bgp"
|
|
* cvv[1] = "eth0"
|
|
* cvv[2] = "bgp"
|
|
* argv[0] = "/interfaces/interface/%s/type"
|
|
* op: OP_MERGE
|
|
* @see cli_callback_generate where arg is generated
|
|
* @note The last value may require namespace binding present in nsctx. Note that the nsctx
|
|
* cannot normally be supplied by the clispec functions, such as cli_set, but need to be
|
|
* generated by a function such as clixon_instance_id_bind() or other programmatically.
|
|
*/
|
|
int
|
|
cli_dbxml(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv,
|
|
enum operation_type op,
|
|
cvec *nsctx)
|
|
{
|
|
int retval = -1;
|
|
char *api_path_fmt; /* xml key format */
|
|
char *api_path_fmt01 = NULL;
|
|
char *api_path = NULL;
|
|
cg_var *arg;
|
|
cbuf *cb = NULL;
|
|
cxobj *xbot = NULL; /* xpath, NULL if datastore */
|
|
yang_stmt *y = NULL; /* yang spec of xpath */
|
|
cxobj *xtop = NULL; /* xpath root */
|
|
cxobj *xerr = NULL;
|
|
int ret;
|
|
cg_var *cv;
|
|
int cvvi = 0;
|
|
char *mtpoint = NULL;
|
|
yang_stmt *yspec0 = NULL;
|
|
|
|
if (cvec_len(argv) != 1 && cvec_len(argv) != 2){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Requires one element to be xml key format string");
|
|
goto done;
|
|
}
|
|
/* Top-level yspec */
|
|
if ((yspec0 = clicon_dbspec_yang(h)) == NULL){
|
|
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
|
goto done;
|
|
}
|
|
arg = cvec_i(argv, 0);
|
|
api_path_fmt = cv_string_get(arg);
|
|
if (cvec_len(argv) > 1){
|
|
arg = cvec_i(argv, 1);
|
|
mtpoint = cv_string_get(arg);
|
|
}
|
|
/* Remove all keywords */
|
|
if (cvec_exclude_keys(cvv) < 0)
|
|
goto done;
|
|
if (mtpoint){
|
|
/* Get and combined api-path01 */
|
|
if (mtpoint_paths(yspec0, mtpoint, api_path_fmt, &api_path_fmt01) < 0)
|
|
goto done;
|
|
/* Transform template format string + cvv to actual api-path
|
|
* cvvi indicates if all cvv entries were used
|
|
*/
|
|
if (api_path_fmt2api_path(api_path_fmt01, cvv, &api_path, &cvvi) < 0)
|
|
goto done;
|
|
}
|
|
else {
|
|
/* Only top-level tree */
|
|
/* Transform template format string + cvv to actual api-path
|
|
* cvvi indicates if all cvv entries were used
|
|
*/
|
|
if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path, &cvvi) < 0)
|
|
goto done;
|
|
}
|
|
/* Create config top-of-tree */
|
|
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
|
|
goto done;
|
|
xbot = xtop;
|
|
if (api_path){
|
|
if ((ret = api_path2xml(api_path, yspec0, xtop, YC_DATANODE, 1, &xbot, &y, &xerr)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
cprintf(cb, "api-path syntax error \"%s\": ", api_path_fmt);
|
|
if (netconf_err2cb(xerr, cb) < 0)
|
|
goto done;
|
|
clicon_err(OE_CFG, EINVAL, "%s", cbuf_get(cb));
|
|
goto done;
|
|
}
|
|
}
|
|
if (xml_add_attr(xbot, "operation", xml_operation2str(op), NETCONF_BASE_PREFIX, NULL) < 0)
|
|
goto done;
|
|
/* Add body last in case of leaf */
|
|
if (cvec_len(cvv) > 1 &&
|
|
(yang_keyword_get(y) == Y_LEAF)){
|
|
/* Add the body last if there is remaining element that was not used in the
|
|
* earlier api-path transformation.
|
|
* This is to handle differences between:
|
|
* DELETE <foo>bar</foo> and DELETE <foo/>
|
|
* i.e., (1) deletion of a specific leaf entry vs (2) deletion of any entry
|
|
* Discussion: one can claim (1) is "bad" usage but one could see cases where
|
|
* you would want to delete a value if it has a specific value but not otherwise
|
|
*/
|
|
if (cvvi != cvec_len(cvv))
|
|
if (dbxml_body(xbot, cvv) < 0)
|
|
goto done;
|
|
/* Loop over namespace context and add them to this leaf node */
|
|
cv = NULL;
|
|
while ((cv = cvec_each(nsctx, cv)) != NULL){
|
|
char *ns = cv_string_get(cv);
|
|
char *pf = cv_name_get(cv);
|
|
if (ns && pf && xmlns_set(xbot, pf, ns) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
/* Special handling of identityref:s whose body may be: <namespace prefix>:<id>
|
|
* Ensure the namespace is declared if it exists in YANG
|
|
*/
|
|
if ((ret = xml_apply0(xbot, CX_ELMNT, identityref_add_ns, yspec0)) < 0)
|
|
goto done;
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clicon_err(OE_XML, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
if (clixon_xml2cbuf(cb, xtop, 0, 0, -1, 0) < 0)
|
|
goto done;
|
|
if (clicon_rpc_edit_config(h, "candidate", OP_NONE, cbuf_get(cb)) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
if (api_path_fmt01)
|
|
free(api_path_fmt01);
|
|
if (xerr)
|
|
xml_free(xerr);
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
if (api_path)
|
|
free(api_path);
|
|
if (xtop)
|
|
xml_free(xtop);
|
|
return retval;
|
|
}
|
|
|
|
/*! Set datastore xml entry
|
|
* @param[in] h Clicon handle
|
|
* @param[in] cvv Vector of cli string and instantiated variables
|
|
* @param[in] argv Vector. First element xml key format string, eg "/aaa/%s"
|
|
*/
|
|
int
|
|
cli_set(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
int retval = -1;
|
|
|
|
if (cli_dbxml(h, cvv, argv, OP_REPLACE, NULL) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Merge datastore xml entry
|
|
* @param[in] h Clicon handle
|
|
* @param[in] cvv Vector of cli string and instantiated variables
|
|
* @param[in] argv Vector. First element xml key format string, eg "/aaa/%s"
|
|
*/
|
|
int
|
|
cli_merge(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
int retval = -1;
|
|
|
|
if (cli_dbxml(h, cvv, argv, OP_MERGE, NULL) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Create datastore xml entry
|
|
* @param[in] h Clicon handle
|
|
* @param[in] cvv Vector of cli string and instantiated variables
|
|
* @param[in] argv Vector. First element xml key format string, eg "/aaa/%s"
|
|
*/
|
|
int
|
|
cli_create(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
int retval = -1;
|
|
|
|
if (cli_dbxml(h, cvv, argv, OP_CREATE, NULL) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
/*! Remove datastore xml entry
|
|
* @param[in] h Clicon handle
|
|
* @param[in] cvv Vector of cli string and instantiated variables
|
|
* @param[in] argv Vector. First element xml key format string, eg "/aaa/%s"
|
|
* @see cli_del
|
|
*/
|
|
int
|
|
cli_remove(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
int retval = -1;
|
|
|
|
if (cli_dbxml(h, cvv, argv, OP_REMOVE, NULL) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Delete datastore xml
|
|
* @param[in] h Clicon handle
|
|
* @param[in] cvv Vector of cli string and instantiated variables
|
|
* @param[in] argv Vector. First element xml key format string, eg "/aaa/%s"
|
|
*/
|
|
int
|
|
cli_del(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
int retval = -1;
|
|
|
|
if (cli_dbxml(h, cvv, argv, OP_REMOVE, NULL) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Set debug level on CLI client (not backend daemon)
|
|
* @param[in] h Clicon handle
|
|
* @param[in] vars If variable "level" exists, its integer value is used
|
|
* @param[in] arg Else use the integer value of argument
|
|
* @note The level is either what is specified in arg as int argument.
|
|
* _or_ if a 'level' variable is present in vars use that value instead.
|
|
*/
|
|
int
|
|
cli_debug_cli(clicon_handle h,
|
|
cvec *vars,
|
|
cvec *argv)
|
|
{
|
|
int retval = -1;
|
|
cg_var *cv;
|
|
int level;
|
|
|
|
if ((cv = cvec_find_var(vars, "level")) == NULL){
|
|
if (cvec_len(argv) != 1){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1");
|
|
goto done;
|
|
}
|
|
cv = cvec_i(argv, 0);
|
|
}
|
|
level = cv_int32_get(cv);
|
|
/* cli */
|
|
clicon_debug_init(level, NULL); /* 0: dont debug, 1:debug */
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Set debug level on backend daemon (not CLI)
|
|
* @param[in] h Clicon handle
|
|
* @param[in] vars If variable "level" exists, its integer value is used
|
|
* @param[in] arg Else use the integer value of argument
|
|
* @note The level is either what is specified in arg as int argument.
|
|
* _or_ if a 'level' variable is present in vars use that value instead.
|
|
*/
|
|
int
|
|
cli_debug_backend(clicon_handle h,
|
|
cvec *vars,
|
|
cvec *argv)
|
|
{
|
|
int retval = -1;
|
|
cg_var *cv;
|
|
int level;
|
|
|
|
if ((cv = cvec_find_var(vars, "level")) == NULL){
|
|
if (cvec_len(argv) != 1){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1");
|
|
goto done;
|
|
}
|
|
cv = cvec_i(argv, 0);
|
|
}
|
|
level = cv_int32_get(cv);
|
|
/* config daemon */
|
|
retval = clicon_rpc_debug(h, level);
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Set debug level on restconf daemon
|
|
* @param[in] h Clicon handle
|
|
* @param[in] vars If variable "level" exists, its integer value is used
|
|
* @param[in] arg Else use the integer value of argument
|
|
* @note The level is either what is specified in arg as int argument.
|
|
* _or_ if a 'level' variable is present in vars use that value instead.
|
|
* @notes
|
|
* 1. clixon-restconf.yang is used (so that debug config can be set)
|
|
* 2. AND the <restconf> XML is in running db not in clixon-config (so that restconf read the new config from backend)
|
|
* 3 CLICON_BACKEND_RESTCONF_PROCESS is true (so that backend restarts restconf)
|
|
*/
|
|
int
|
|
cli_debug_restconf(clicon_handle h,
|
|
cvec *vars,
|
|
cvec *argv)
|
|
{
|
|
int retval = -1;
|
|
cg_var *cv;
|
|
int level;
|
|
|
|
if ((cv = cvec_find_var(vars, "level")) == NULL){
|
|
if (cvec_len(argv) != 1){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1");
|
|
goto done;
|
|
}
|
|
cv = cvec_i(argv, 0);
|
|
}
|
|
level = cv_int32_get(cv);
|
|
/* restconf daemon */
|
|
retval = clicon_rpc_restconf_debug(h, level);
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
|
|
/*! Set syntax mode
|
|
*/
|
|
int
|
|
cli_set_mode(clicon_handle h,
|
|
cvec *vars,
|
|
cvec *argv)
|
|
{
|
|
int retval = -1;
|
|
char *str = NULL;
|
|
|
|
if (cvec_len(argv) != 1){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Requires one element to be cli mode");
|
|
goto done;
|
|
}
|
|
str = cv_string_get(cvec_i(argv, 0));
|
|
cli_set_syntax_mode(h, str);
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Start bash from cli callback
|
|
* Typical usage: shell("System Bash") <source:rest>, cli_start_shell();
|
|
* @param[in] h Clixon handle
|
|
* @param[in] cvv Vector of command variables
|
|
* @param[in] argv [<shell>], defaults to "sh"
|
|
*/
|
|
int
|
|
cli_start_shell(clicon_handle h,
|
|
cvec *vars,
|
|
cvec *argv)
|
|
{
|
|
char *cmd;
|
|
char *shcmd = "sh";
|
|
struct passwd *pw;
|
|
int retval = -1;
|
|
char bcmd[128];
|
|
cg_var *cv1 = cvec_i(vars, 1);
|
|
sigset_t oldsigset;
|
|
struct sigaction oldsigaction[32] = {{{0,},},};
|
|
|
|
if (cvec_len(argv) > 1){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Received %d arguments. Expected: [<shell>]",
|
|
cvec_len(argv));
|
|
goto done;
|
|
}
|
|
if (cvec_len(argv) == 1){
|
|
shcmd = cv_string_get(cvec_i(argv, 0));
|
|
}
|
|
cmd = (cvec_len(vars)>1 ? cv_string_get(cv1) : NULL);
|
|
if ((pw = getpwuid(getuid())) == NULL){
|
|
clicon_err(OE_UNIX, errno, "getpwuid");
|
|
goto done;
|
|
}
|
|
if (chdir(pw->pw_dir) < 0){
|
|
clicon_err(OE_UNIX, errno, "chdir");
|
|
endpwent();
|
|
goto done;
|
|
}
|
|
endpwent();
|
|
|
|
if (clixon_signal_save(&oldsigset, oldsigaction) < 0)
|
|
goto done;
|
|
cli_signal_flush(h);
|
|
cli_signal_unblock(h);
|
|
if (cmd){
|
|
snprintf(bcmd, 128, "%s -c \"%s\"", shcmd, cmd);
|
|
if (system(bcmd) < 0){
|
|
cli_signal_block(h);
|
|
clicon_err(OE_UNIX, errno, "system(bash -c)");
|
|
goto done;
|
|
}
|
|
}
|
|
else{
|
|
snprintf(bcmd, 128, "%s ", shcmd); /* -l (login shell) but is applicable to bash only */
|
|
if (system(bcmd) < 0){
|
|
cli_signal_block(h);
|
|
clicon_err(OE_UNIX, errno, "system(bash)");
|
|
goto done;
|
|
}
|
|
}
|
|
cli_signal_block(h);
|
|
#if 0 /* Allow errcodes from bash */
|
|
if (retval != 0){
|
|
clicon_err(OE_UNIX, errno, "system(%s) %d", cmd, retval);
|
|
goto done;
|
|
}
|
|
#endif
|
|
if (clixon_signal_restore(&oldsigset, oldsigaction) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Generic quit callback
|
|
*/
|
|
int
|
|
cli_quit(clicon_handle h,
|
|
cvec *vars,
|
|
cvec *argv)
|
|
{
|
|
cligen_exiting_set(cli_cligen(h), 1);
|
|
return 0;
|
|
}
|
|
|
|
/*! Generic commit callback
|
|
* @param[in] argv No arguments expected
|
|
*/
|
|
int
|
|
cli_commit(clicon_handle h,
|
|
cvec *vars,
|
|
cvec *argv)
|
|
{
|
|
int retval = -1;
|
|
uint32_t timeout = 0; /* any non-zero value means "confirmed-commit" */
|
|
cg_var *timeout_var;
|
|
char *persist = NULL;
|
|
char *persist_id = NULL;
|
|
int confirmed;
|
|
int cancel;
|
|
|
|
confirmed = (cvec_find_str(vars, "confirmed") != NULL);
|
|
cancel = (cvec_find_str(vars, "cancel") != NULL);
|
|
if ((timeout_var = cvec_find(vars, "timeout")) != NULL) {
|
|
timeout = cv_uint32_get(timeout_var);
|
|
clicon_debug(1, "commit confirmed with timeout %ul", timeout);
|
|
}
|
|
persist = cvec_find_str(vars, "persist-val");
|
|
persist_id = cvec_find_str(vars, "persist-id-val");
|
|
if (clicon_rpc_commit(h, confirmed, cancel, timeout, persist, persist_id) < 1)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Generic validate callback
|
|
*/
|
|
int
|
|
cli_validate(clicon_handle h,
|
|
cvec *vars,
|
|
cvec *argv)
|
|
{
|
|
int retval = -1;
|
|
|
|
if (clicon_rpc_validate(h, "candidate") < 1)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Compare two dbs using XML. Write to file and run diff
|
|
* @param[in] h Clicon handle
|
|
* @param[in] cvv
|
|
* @param[in] argv arg: 0 as xml, 1: as text
|
|
*/
|
|
int
|
|
compare_dbs(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
cxobj *xc1 = NULL; /* running xml */
|
|
cxobj *xc2 = NULL; /* candidate xml */
|
|
cxobj *xerr = NULL;
|
|
int retval = -1;
|
|
enum format_enum format;
|
|
|
|
if (cvec_len(argv) > 1){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Requires 0 or 1 element. If given: astext flag 0|1");
|
|
goto done;
|
|
}
|
|
if (cvec_len(argv) && cv_int32_get(cvec_i(argv, 0)) == 1)
|
|
format = FORMAT_TEXT;
|
|
else
|
|
format = FORMAT_XML;
|
|
if (clicon_rpc_get_config(h, NULL, "running", "/", NULL, NULL, &xc1) < 0)
|
|
goto done;
|
|
if ((xerr = xpath_first(xc1, NULL, "/rpc-error")) != NULL){
|
|
clixon_netconf_error(xerr, "Get configuration", NULL);
|
|
goto done;
|
|
}
|
|
if (clicon_rpc_get_config(h, NULL, "candidate", "/", NULL, NULL, &xc2) < 0)
|
|
goto done;
|
|
if ((xerr = xpath_first(xc2, NULL, "/rpc-error")) != NULL){
|
|
clixon_netconf_error(xerr, "Get configuration", NULL);
|
|
goto done;
|
|
}
|
|
if (clixon_compare_xmls(xc1, xc2, format, cligen_output) < 0) /* astext? */
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
if (xc1)
|
|
xml_free(xc1);
|
|
if (xc2)
|
|
xml_free(xc2);
|
|
return retval;
|
|
}
|
|
|
|
/*! Load a configuration file to candidate database
|
|
* Utility function used by cligen spec file
|
|
* Note that the CLI function makes no Validation of the XML sent to the backend
|
|
* @param[in] h CLICON handle
|
|
* @param[in] cvv Vector of variables (where <varname> is found)
|
|
* @param[in] argv A string: "<varname> <operation> [<format>]"
|
|
* <varname> is name of a variable occuring in "cvv" containing filename
|
|
* <operation> : merge or replace
|
|
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
|
|
*
|
|
* @note that "filename" is local on client filesystem not backend.
|
|
* @note file is assumed to have a dummy top-tag, eg <clicon></clicon>
|
|
* @code
|
|
* # cligen spec
|
|
* load file <name2:string>, load_config_file("name2","merge");
|
|
* @endcode
|
|
* @see save_config_file
|
|
*/
|
|
int
|
|
load_config_file(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
int ret = -1;
|
|
struct stat st;
|
|
char *filename = NULL;
|
|
int replace;
|
|
cg_var *cv;
|
|
char *opstr;
|
|
char *varstr;
|
|
FILE *fp = NULL;
|
|
cxobj *xt = NULL;
|
|
cxobj *x;
|
|
cbuf *cbxml = NULL;
|
|
char *formatstr = NULL;
|
|
enum format_enum format = FORMAT_XML;
|
|
yang_stmt *yspec;
|
|
cxobj *xerr = NULL;
|
|
char *lineptr = NULL;
|
|
|
|
if (cvec_len(argv) < 2 || cvec_len(argv) > 4){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Received %d arguments. Expected: <dbname>,<varname>[,<format>]",
|
|
cvec_len(argv));
|
|
goto done;
|
|
}
|
|
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
|
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
|
goto done;
|
|
}
|
|
if (cvec_len(argv) > 2){
|
|
formatstr = cv_string_get(cvec_i(argv, 2));
|
|
if ((int)(format = format_str2int(formatstr)) < 0){
|
|
clicon_err(OE_PLUGIN, 0, "Not valid format: %s", formatstr);
|
|
goto done;
|
|
}
|
|
}
|
|
varstr = cv_string_get(cvec_i(argv, 0));
|
|
opstr = cv_string_get(cvec_i(argv, 1));
|
|
if (strcmp(opstr, "merge") == 0)
|
|
replace = 0;
|
|
else if (strcmp(opstr, "replace") == 0)
|
|
replace = 1;
|
|
else{
|
|
clicon_err(OE_PLUGIN, 0, "No such op: %s, expected merge or replace", opstr);
|
|
goto done;
|
|
}
|
|
if ((cv = cvec_find(cvv, varstr)) == NULL){
|
|
clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr);
|
|
goto done;
|
|
}
|
|
filename = cv_string_get(cv);
|
|
if (stat(filename, &st) < 0){
|
|
clicon_err(OE_UNIX, errno, "load_config: stat(%s)", filename);
|
|
goto done;
|
|
}
|
|
/* Open and parse local file into xml */
|
|
if ((fp = fopen(filename, "r")) == NULL){
|
|
clicon_err(OE_UNIX, errno, "fopen(%s)", filename);
|
|
goto done;
|
|
}
|
|
switch (format){
|
|
case FORMAT_XML:
|
|
if ((ret = clixon_xml_parse_file(fp, YB_NONE, yspec, &xt, &xerr)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
clixon_netconf_error(xerr, "Loading", filename);
|
|
goto done;
|
|
}
|
|
break;
|
|
case FORMAT_JSON:
|
|
if ((ret = clixon_json_parse_file(fp, 1, YB_NONE, yspec, &xt, &xerr)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
clixon_netconf_error(xerr, "Loading", filename);
|
|
goto done;
|
|
}
|
|
break;
|
|
case FORMAT_TEXT:
|
|
/* text parser requires YANG and since load/save files have a "config" top-level
|
|
* the yang-bind parameter must be YB_MODULE_NEXT
|
|
*/
|
|
if ((ret = clixon_text_syntax_parse_file(fp, YB_MODULE_NEXT, yspec, &xt, &xerr)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
clixon_netconf_error(xerr, "Loading", filename);
|
|
goto done;
|
|
}
|
|
break;
|
|
case FORMAT_CLI:
|
|
{
|
|
char *mode = cli_syntax_mode(h);
|
|
cligen_result result; /* match result */
|
|
int evalresult = 0; /* if result == 1, calback result */
|
|
size_t n;
|
|
|
|
while(!cligen_exiting(cli_cligen(h))) {
|
|
lineptr = NULL; n = 0;
|
|
if (getline(&lineptr, &n, fp) < 0){
|
|
if (errno){
|
|
clicon_err(OE_UNIX, errno, "getline");
|
|
goto done;
|
|
}
|
|
goto ok; /* eof, skip backend rpc since this is done by cli code */
|
|
}
|
|
if (clicon_parse(h, lineptr, &mode, &result, &evalresult) < 0)
|
|
goto done;
|
|
if (result != 1) /* Not unique match */
|
|
goto done;
|
|
if (evalresult < 0)
|
|
goto done;
|
|
if (lineptr){
|
|
free(lineptr);
|
|
lineptr = NULL;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
clicon_err(OE_PLUGIN, 0, "format: %s not implemented", formatstr);
|
|
goto done;
|
|
break;
|
|
}
|
|
if (xt == NULL)
|
|
goto done;
|
|
if ((cbxml = cbuf_new()) == NULL)
|
|
goto done;
|
|
x = NULL;
|
|
while ((x = xml_child_each(xt, x, -1)) != NULL) {
|
|
/* Read as datastore-top but transformed into an edit-config "config" */
|
|
xml_name_set(x, NETCONF_INPUT_CONFIG);
|
|
}
|
|
if (clixon_xml2cbuf(cbxml, xt, 0, 0, -1, 1) < 0)
|
|
goto done;
|
|
if (clicon_rpc_edit_config(h, "candidate",
|
|
replace?OP_REPLACE:OP_MERGE,
|
|
cbuf_get(cbxml)) < 0)
|
|
goto done;
|
|
ok:
|
|
ret = 0;
|
|
done:
|
|
if (cbxml)
|
|
cbuf_free(cbxml);
|
|
if (lineptr)
|
|
free(lineptr);
|
|
if (xerr)
|
|
xml_free(xerr);
|
|
if (xt)
|
|
xml_free(xt);
|
|
if (fp)
|
|
fclose(fp);
|
|
return ret;
|
|
}
|
|
|
|
/*! Copy database to local file as XMLn
|
|
*
|
|
* Utility function used by cligen spec file
|
|
* @param[in] h CLICON handle
|
|
* @param[in] cvv variable vector (containing <varname>)
|
|
* @param[in] argv a string: "<dbname> <varname> [<format>]"
|
|
* <dbname> is running, candidate, or startup
|
|
* <varname> is name of cligen variable in the "cvv" vector containing file name
|
|
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
|
|
* Note that "filename" is local on client filesystem not backend.
|
|
* The function can run without a local database
|
|
* @note The file is saved with dummy top-tag: clicon: <clicon></clicon>
|
|
* @code
|
|
* save file <name:string>, save_config_file("running name");
|
|
* @endcode
|
|
* @see load_config_file
|
|
*/
|
|
int
|
|
save_config_file(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
int retval = -1;
|
|
char *filename = NULL;
|
|
cg_var *cv;
|
|
char *dbstr;
|
|
char *varstr;
|
|
cxobj *xt = NULL;
|
|
cxobj *xerr;
|
|
FILE *f = NULL;
|
|
char *formatstr;
|
|
enum format_enum format = FORMAT_XML;
|
|
char *prefix = "set "; /* XXX hardcoded */
|
|
int pretty = 1; /* XXX hardcoded */
|
|
|
|
if (cvec_len(argv) < 2 || cvec_len(argv) > 4){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Received %d arguments. Expected: <dbname>,<varname>[,<format>]",
|
|
cvec_len(argv));
|
|
goto done;
|
|
}
|
|
if (cvec_len(argv) > 2){
|
|
formatstr = cv_string_get(cvec_i(argv, 2));
|
|
if ((int)(format = format_str2int(formatstr)) < 0){
|
|
clicon_err(OE_PLUGIN, 0, "Not valid format: %s", formatstr);
|
|
goto done;
|
|
}
|
|
}
|
|
dbstr = cv_string_get(cvec_i(argv, 0));
|
|
if (strcmp(dbstr, "running") != 0 &&
|
|
strcmp(dbstr, "candidate") != 0 &&
|
|
strcmp(dbstr, "startup") != 0) {
|
|
clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
|
|
goto done;
|
|
}
|
|
varstr = cv_string_get(cvec_i(argv, 1));
|
|
if ((cv = cvec_find(cvv, varstr)) == NULL){
|
|
clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr);
|
|
goto done;
|
|
}
|
|
filename = cv_string_get(cv);
|
|
if (clicon_rpc_get_config(h, NULL, dbstr,"/", NULL, NULL, &xt) < 0)
|
|
goto done;
|
|
if (xt == NULL){
|
|
clicon_err(OE_CFG, 0, "get config: empty tree"); /* Shouldnt happen */
|
|
goto done;
|
|
}
|
|
if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){
|
|
clixon_netconf_error(xerr, "Get configuration", NULL);
|
|
goto done;
|
|
}
|
|
/* get-config returns a <data> tree. Save as <config> tree so it can be used
|
|
* as data-store.
|
|
*/
|
|
if (xml_name_set(xt, DATASTORE_TOP_SYMBOL) < 0)
|
|
goto done;
|
|
if ((f = fopen(filename, "w")) == NULL){
|
|
clicon_err(OE_CFG, errno, "Creating file %s", filename);
|
|
goto done;
|
|
}
|
|
switch (format){
|
|
case FORMAT_XML:
|
|
if (clixon_xml2file(f, xt, 0, pretty, fprintf, 0, 1) < 0)
|
|
goto done;
|
|
break;
|
|
case FORMAT_JSON:
|
|
if (clixon_json2file(f, xt, pretty, fprintf, 0, 1) < 0)
|
|
goto done;
|
|
break;
|
|
case FORMAT_TEXT:
|
|
if (clixon_txt2file(f, xt, 0, fprintf, 0, 1) < 0)
|
|
goto done;
|
|
break;
|
|
case FORMAT_CLI:
|
|
if (clixon_cli2file(h, f, xt, prefix, fprintf, 1) < 0)
|
|
goto done;
|
|
break;
|
|
case FORMAT_NETCONF:
|
|
fprintf(f, "<rpc xmlns=\"%s\" %s><edit-config><target><candidate/></target>",
|
|
NETCONF_BASE_NAMESPACE, NETCONF_MESSAGE_ID_ATTR);
|
|
fprintf(f, "\n");
|
|
if (clixon_xml2file(f, xt, 0, pretty, fprintf, 0, 1) < 0)
|
|
goto done;
|
|
fprintf(f, "</edit-config></rpc>]]>]]>\n");
|
|
break;
|
|
} /* switch */
|
|
retval = 0;
|
|
/* Fall through */
|
|
done:
|
|
if (xt)
|
|
xml_free(xt);
|
|
if (f != NULL)
|
|
fclose(f);
|
|
return retval;
|
|
}
|
|
|
|
/*! Delete all elements in a database
|
|
* Utility function used by cligen spec file
|
|
*/
|
|
int
|
|
delete_all(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
char *dbstr;
|
|
int retval = -1;
|
|
|
|
if (cvec_len(argv) != 1){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Requires one element: dbname");
|
|
goto done;
|
|
}
|
|
dbstr = cv_string_get(cvec_i(argv, 0));
|
|
if (strcmp(dbstr, "running") != 0 &&
|
|
strcmp(dbstr, "candidate") != 0 &&
|
|
strcmp(dbstr, "startup") != 0){
|
|
clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
|
|
goto done;
|
|
}
|
|
if (clicon_rpc_delete_config(h, dbstr) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Discard all changes in candidate and replace with running
|
|
*/
|
|
int
|
|
discard_changes(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
return clicon_rpc_discard_changes(h);
|
|
|
|
}
|
|
/*! Copy from one database to another, eg running->startup
|
|
* @param[in] argv a string: "<db1> <db2>" Copy from db1 to db2
|
|
*/
|
|
int
|
|
db_copy(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
char *db1;
|
|
char *db2;
|
|
|
|
db1 = cv_string_get(cvec_i(argv, 0));
|
|
db2 = cv_string_get(cvec_i(argv, 1));
|
|
return clicon_rpc_copy_config(h, db1, db2);
|
|
}
|
|
|
|
/*! This is the callback used by cli_setlog to print log message in CLI
|
|
* param[in] s UNIX socket from backend where message should be read
|
|
* param[in] arg format: txt, xml, xml2txt, xml2json
|
|
*/
|
|
static int
|
|
cli_notification_cb(int s,
|
|
void *arg)
|
|
{
|
|
int retval = -1;
|
|
struct clicon_msg *reply = NULL;
|
|
int eof;
|
|
cxobj *xt = NULL;
|
|
enum format_enum format = (enum format_enum)arg;
|
|
int ret;
|
|
|
|
/* get msg (this is the reason this function is called) */
|
|
if (clicon_msg_rcv(s, 0, &reply, &eof) < 0)
|
|
goto done;
|
|
if (eof){
|
|
clicon_err(OE_PROTO, ESHUTDOWN, "Socket unexpected close");
|
|
close(s);
|
|
errno = ESHUTDOWN;
|
|
clixon_event_unreg_fd(s, cli_notification_cb);
|
|
goto done;
|
|
}
|
|
/* XXX pass yang_spec and use xerr*/
|
|
if ((ret = clicon_msg_decode(reply, NULL, NULL, &xt, NULL)) < 0)
|
|
goto done;
|
|
if (ret == 0){ /* will not happen since no yspec ^*/
|
|
clicon_err(OE_NETCONF, EFAULT, "Notification malformed");
|
|
goto done;
|
|
}
|
|
switch (format){
|
|
case FORMAT_JSON:
|
|
if (clixon_json2file(stdout, xt, 1, cligen_output, 1, 1) < 0)
|
|
goto done;
|
|
case FORMAT_TEXT:
|
|
if (clixon_txt2file(stdout, xt, 0, cligen_output, 1, 1) < 0)
|
|
goto done;
|
|
break;
|
|
case FORMAT_XML:
|
|
if (clixon_xml2file(stdout, xt, 0, 1, cligen_output, 1, 1) < 0)
|
|
goto done;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (xt)
|
|
xml_free(xt);
|
|
if (reply)
|
|
free(reply);
|
|
return retval;
|
|
}
|
|
|
|
/*! Make a notify subscription to backend and un/register callback for return messages.
|
|
*
|
|
* @param[in] h Clicon handle
|
|
* @param[in] cvv Not used
|
|
* @param[in] arg A string with <log stream name> <stream status> [<format>]
|
|
* where <status> is "0" or "1"
|
|
* and <format> is XXX
|
|
* Example code: Start logging of mystream and show logs as xml
|
|
* @code
|
|
* cmd("comment"), cli_notify("mystream","1","xml");
|
|
* @endcode
|
|
* XXX: format is a memory leak
|
|
*/
|
|
int
|
|
cli_notify(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
char *stream = NULL;
|
|
int retval = -1;
|
|
int status;
|
|
char *formatstr = NULL;
|
|
enum format_enum format = FORMAT_TEXT;
|
|
|
|
if (cvec_len(argv) != 2 && cvec_len(argv) != 3){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Requires arguments: <logstream> <status> [<format>]");
|
|
goto done;
|
|
}
|
|
stream = cv_string_get(cvec_i(argv, 0));
|
|
status = atoi(cv_string_get(cvec_i(argv, 1)));
|
|
if (cvec_len(argv) > 2){
|
|
formatstr = cv_string_get(cvec_i(argv, 2));
|
|
format = format_str2int(formatstr);
|
|
}
|
|
if (cli_notification_register(h,
|
|
stream,
|
|
format,
|
|
"",
|
|
status,
|
|
cli_notification_cb,
|
|
(void*)format) < 0)
|
|
goto done;
|
|
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Lock database
|
|
*
|
|
* @param[in] h Clicon handle
|
|
* @param[in] cvv Not used
|
|
* @param[in] arg A string with <database>
|
|
* @code
|
|
* lock("comment"), cli_lock("running");
|
|
* @endcode
|
|
* XXX: format is a memory leak
|
|
*/
|
|
int
|
|
cli_lock(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
char *db;
|
|
int retval = -1;
|
|
|
|
if (cvec_len(argv) != 1){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Requires arguments: <db>");
|
|
goto done;
|
|
}
|
|
db = cv_string_get(cvec_i(argv, 0));
|
|
if (clicon_rpc_lock(h, db) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Unlock database
|
|
*
|
|
* @param[in] h Clicon handle
|
|
* @param[in] cvv Not used
|
|
* @param[in] arg A string with <database>
|
|
* @code
|
|
* lock("comment"), cli_lock("running");
|
|
* @endcode
|
|
* XXX: format is a memory leak
|
|
*/
|
|
int
|
|
cli_unlock(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
char *db;
|
|
int retval = -1;
|
|
|
|
if (cvec_len(argv) != 1){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Requires arguments: <db>");
|
|
goto done;
|
|
}
|
|
db = cv_string_get(cvec_i(argv, 0));
|
|
if (clicon_rpc_unlock(h, db) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Copy one configuration object to antother
|
|
*
|
|
* Works for objects that are items in a yang list with a keyname, eg as:
|
|
* list sender{
|
|
* key name;
|
|
* leaf name{...
|
|
*
|
|
* @param[in] h CLICON handle
|
|
* @param[in] cvv Vector of variables from CLIgen command-line
|
|
* @param[in] argv Vector: <db>, <xpath>, <field>, <fromvar>, <tovar>
|
|
* Explanation of argv fields:
|
|
* db: Database name, eg candidate|tmp|startup
|
|
* xpath: XPATH expression with exactly two %s pointing to field and from name
|
|
* namespace: XPATH default namespace
|
|
* field: Name of list key, eg name
|
|
* fromvar: Name of variable containing name of object to copy from (given by xpath)
|
|
* tovar: Name of variable containing name of object to copy to.
|
|
* @code
|
|
* cli spec:
|
|
* copy snd <n1:string> to <n2:string>, cli_copy_config("candidate", "/sender[%s='%s']", "urn:example:clixon", "from", "n1", "n2");
|
|
* cli command:
|
|
* copy snd from to to
|
|
* @endcode
|
|
*/
|
|
int
|
|
cli_copy_config(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
int retval = -1;
|
|
char *db;
|
|
cxobj *x1 = NULL;
|
|
cxobj *x2 = NULL;
|
|
cxobj *x;
|
|
char *xpath;
|
|
char *namespace;
|
|
int i;
|
|
int j;
|
|
cbuf *cb = NULL;
|
|
char *keyname;
|
|
char *fromvar;
|
|
cg_var *fromcv;
|
|
char *fromname = NULL;
|
|
char *tovar;
|
|
cg_var *tocv;
|
|
char *toname;
|
|
cxobj *xerr;
|
|
cvec *nsc = NULL;
|
|
size_t len;
|
|
|
|
if (cvec_len(argv) != 6){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Requires 6 elements: <db> <xpath> <namespace> <keyname> <from> <to>");
|
|
goto done;
|
|
}
|
|
/* First argv argument: Database */
|
|
db = cv_string_get(cvec_i(argv, 0));
|
|
/* Second argv argument: xpath */
|
|
xpath = cv_string_get(cvec_i(argv, 1));
|
|
/* Third argv argument: namespace */
|
|
namespace = cv_string_get(cvec_i(argv, 2));
|
|
/* Third argv argument: name of keyname */
|
|
keyname = cv_string_get(cvec_i(argv, 3));
|
|
/* Fourth argv argument: from variable */
|
|
fromvar = cv_string_get(cvec_i(argv, 4));
|
|
/* Fifth argv argument: to variable */
|
|
tovar = cv_string_get(cvec_i(argv, 5));
|
|
|
|
/* Get from variable -> cv -> from name */
|
|
if ((fromcv = cvec_find(cvv, fromvar)) == NULL){
|
|
clicon_err(OE_PLUGIN, 0, "fromvar '%s' not found in cligen var list", fromvar);
|
|
goto done;
|
|
}
|
|
/* Get from name from cv */
|
|
fromname = cv_string_get(fromcv);
|
|
/* Create xpath */
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clicon_err(OE_PLUGIN, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
/* Sanity check that xpath contains exactly two %s, ie [%s='%s'] */
|
|
len = strlen(xpath);
|
|
j = 0;
|
|
for (i=0; i<len; i++){
|
|
if (xpath[i] == '%')
|
|
j++;
|
|
}
|
|
if (j != 2){
|
|
clicon_err(OE_PLUGIN, 0, "xpath '%s' does not have two '%%'", xpath);
|
|
goto done;
|
|
}
|
|
cprintf(cb, xpath, keyname, fromname);
|
|
if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL)
|
|
goto done;
|
|
/* Get from object configuration and store in x1 */
|
|
if (clicon_rpc_get_config(h, NULL, db, cbuf_get(cb), nsc, NULL, &x1) < 0)
|
|
goto done;
|
|
if ((xerr = xpath_first(x1, NULL, "/rpc-error")) != NULL){
|
|
clixon_netconf_error(xerr, "Get configuration", NULL);
|
|
goto done;
|
|
}
|
|
|
|
/* Get to variable -> cv -> to name */
|
|
if ((tocv = cvec_find(cvv, tovar)) == NULL){
|
|
clicon_err(OE_PLUGIN, 0, "tovar '%s' not found in cligen var list", tovar);
|
|
goto done;
|
|
}
|
|
toname = cv_string_get(tocv);
|
|
/* Create copy xml tree x2 */
|
|
if ((x2 = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
|
|
goto done;
|
|
if (xml_copy(x1, x2) < 0)
|
|
goto done;
|
|
xml_name_set(x2, NETCONF_INPUT_CONFIG);
|
|
cprintf(cb, "/%s", keyname);
|
|
|
|
if ((x = xpath_first(x2, nsc, "%s", cbuf_get(cb))) == NULL){
|
|
clicon_err(OE_PLUGIN, 0, "Field %s not found in copy tree", keyname);
|
|
goto done;
|
|
}
|
|
x = xml_find(x, "body");
|
|
xml_value_set(x, toname);
|
|
/* resuse cb */
|
|
cbuf_reset(cb);
|
|
/* create xml copy tree and merge it with database configuration */
|
|
if (clixon_xml2cbuf(cb, x2, 0, 0, -1, 0) < 0)
|
|
goto done;
|
|
if (clicon_rpc_edit_config(h, db, OP_MERGE, cbuf_get(cb)) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
if (nsc)
|
|
xml_nsctx_free(nsc);
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
if (x1 != NULL)
|
|
xml_free(x1);
|
|
if (x2 != NULL)
|
|
xml_free(x2);
|
|
return retval;
|
|
}
|
|
|
|
int
|
|
cli_help(clicon_handle h, cvec *vars, cvec *argv)
|
|
{
|
|
cligen_handle ch = cli_cligen(h);
|
|
parse_tree *pt;
|
|
|
|
pt = cligen_pt_active_get(ch);
|
|
return cligen_help(ch, stdout, pt);
|
|
}
|
|
|
|
/*! CLI support function for restarting a plugin
|
|
*
|
|
* @param[in] h Clicon handle
|
|
* @param[in] cvv Not used
|
|
* @param[in] arg A string with <database>
|
|
* @code
|
|
* restart("comment") , cli_restart_plugin("myplugin", "restart");
|
|
* @endcode
|
|
*/
|
|
int
|
|
cli_restart_plugin(clicon_handle h,
|
|
cvec *cvv,
|
|
cvec *argv)
|
|
{
|
|
int retval = -1;
|
|
cg_var *cv;
|
|
char *plugin;
|
|
|
|
if ((cv = cvec_find_var(cvv, "plugin")) == NULL){
|
|
if (cvec_len(argv) != 1){
|
|
clicon_err(OE_PLUGIN, EINVAL, "Requires plugin variable");
|
|
goto done;
|
|
}
|
|
cv = cvec_i(argv, 0);
|
|
}
|
|
plugin = cv_string_get(cv);
|
|
retval = clicon_rpc_restart_plugin(h, plugin);
|
|
done:
|
|
return retval;
|
|
}
|
|
|