clixon/apps/cli/cli_common.c
2017-02-01 13:07:26 +01:00

1157 lines
30 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 <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>
#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif
#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>
#include <assert.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include "clixon_cli_api.h"
#include "cli_common.h"
/*! Initialize candidate database
* We have implemented these:
* shared - all users share a common candidate db
*/
int
init_candidate_db(clicon_handle h)
{
int retval = -1;
if (xmldb_exists(h, "running") != 1){
clicon_err(OE_FATAL, 0, "Running db does not exist");
goto err;
}
if (xmldb_exists(h, "candidate") != 1)
if (clicon_rpc_copy(h, "running", "candidate") < 0)
goto err;
retval = 0;
err:
return retval;
}
/*! Exit candidate db
* (private canddidates should be removed?)
*/
int
exit_candidate_db(clicon_handle h)
{
return 0;
}
/*! 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;
void *p;
int s;
clicon_hash_t *cdat = clicon_data(h);
size_t len;
int s_exist = -1;
if ((logname = chunk_sprintf(__FUNCTION__, "log_socket_%s", stream)) == NULL){
clicon_err(OE_PLUGIN, errno, "%s: chunk_sprintf", __FUNCTION__);
goto done;
}
if ((p = hash_value(cdat, logname, &len)) != NULL)
s_exist = *(int*)p;
if (status){ /* start */
if (s_exist!=-1){
clicon_err(OE_PLUGIN, 0, "%s: result log socket already exists", __FUNCTION__);
goto done;
}
if (clicon_rpc_subscription(h, status, stream, format, filter, &s) < 0)
goto done;
if (cligen_regfd(s, fn, arg) < 0)
goto done;
if (hash_add(cdat, logname, &s, sizeof(s)) == NULL)
goto done;
}
else{ /* stop */
if (s_exist != -1){
cligen_unregfd(s_exist);
}
hash_del(cdat, logname);
if (clicon_rpc_subscription(h, status, stream, format, filter, NULL) < 0)
goto done;
}
retval = 0;
done:
unchunk_group(__FUNCTION__);
return retval;
}
/* Signal functions, not exported to API */
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);
}
/*! Modify xml database 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. First element xml key format string, eg "/aaa/%s"
* @param[in] op Operation to perform on database
* Cvv will contain forst the complete cli string, and then a set of optional
* instantiated variables.
* 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
*/
static int
cli_dbxmlv(clicon_handle h,
cvec *cvv,
cvec *argv,
enum operation_type op)
{
int retval = -1;
char *str = NULL;
char *xkfmt; /* xml key format */
char *xk = NULL; /* xml key */
cg_var *cval;
int len;
cg_var *arg;
if (cvec_len(argv) != 1){
clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be xml key format string", __FUNCTION__);
goto done;
}
arg = cvec_i(argv, 0);
xkfmt = cv_string_get(arg);
if (xmlkeyfmt2key(xkfmt, cvv, &xk) < 0)
goto done;
len = cvec_len(cvv);
if (len > 1){
cval = cvec_i(cvv, len-1);
if ((str = cv2str_dup(cval)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
}
if (clicon_rpc_change(h, "candidate", op, xk, str) < 0)
goto done;
if (clicon_autocommit(h)) {
if (clicon_rpc_commit(h, "candidate", "running", 0, 0) < 0)
goto done;
}
retval = 0;
done:
if (str)
free(str);
if (xk)
free(xk);
return retval;
}
int
cli_setv(clicon_handle h, cvec *cvv, cvec *argv)
{
int retval = 1;
if (cli_dbxmlv(h, cvv, argv, OP_REPLACE) < 0)
goto done;
retval = 0;
done:
return retval;
}
int
cli_mergev(clicon_handle h, cvec *cvv, cvec *argv)
{
int retval = -1;
if (cli_dbxmlv(h, cvv, argv, OP_MERGE) < 0)
goto done;
retval = 0;
done:
return retval;
}
int
cli_delv(clicon_handle h, cvec *cvv, cvec *argv)
{
int retval = -1;
if (cli_dbxmlv(h, cvv, argv, OP_REMOVE) < 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_cliv(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, 0, "%s: Requires either label var or single arg: 0|1", __FUNCTION__);
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_backendv(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, 0, "%s: Requires either label var or single arg: 0|1", __FUNCTION__);
goto done;
}
cv = cvec_i(argv, 0);
}
level = cv_int32_get(cv);
/* config daemon */
retval = clicon_rpc_debug(h, level);
done:
return retval;
}
/*! Set syntax mode
*/
int
cli_set_modev(clicon_handle h,
cvec *vars,
cvec *argv)
{
int retval = -1;
char *str = NULL;
if (cvec_len(argv) != 1){
clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be cli mode", __FUNCTION__);
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
* XXX Application specific??
*/
int
cli_start_shellv(clicon_handle h,
cvec *vars,
cvec *argv)
{
char *cmd;
struct passwd *pw;
int retval;
char bcmd[128];
cg_var *cv1 = cvec_i(vars, 1);
cmd = (cvec_len(vars)>1 ? cv_string_get(cv1) : NULL);
if ((pw = getpwuid(getuid())) == NULL){
fprintf(stderr, "%s: getpwuid: %s\n",
__FUNCTION__, strerror(errno));
return -1;
}
if (chdir(pw->pw_dir) < 0){
fprintf(stderr, "%s: chdir(%s): %s\n",
__FUNCTION__, pw->pw_dir, strerror(errno));
endpwent();
return -1;
}
endpwent();
cli_signal_flush(h);
cli_signal_unblock(h);
if (cmd){
snprintf(bcmd, 128, "bash -l -c \"%s\"", cmd);
if ((retval = system(bcmd)) < 0){
cli_signal_block(h);
fprintf(stderr, "%s: system(bash -c): %s\n",
__FUNCTION__, strerror(errno));
return -1;
}
}
else
if ((retval = system("bash -l")) < 0){
cli_signal_block(h);
fprintf(stderr, "%s: system(bash): %s\n",
__FUNCTION__, strerror(errno));
return -1;
}
cli_signal_block(h);
#if 0 /* Allow errcodes from bash */
if (retval != 0){
fprintf(stderr, "%s: system(%s) code=%d\n", __FUNCTION__, cmd, retval);
return -1;
}
#endif
return 0;
}
/*! Generic quit callback
*/
int
cli_quitv(clicon_handle h,
cvec *vars,
cvec *argv)
{
cli_set_exiting(h, 1);
return 0;
}
/*! Generic commit callback
* @param[in] arg If 1, then snapshot and copy to startup config
*/
int
cli_commitv(clicon_handle h,
cvec *vars,
cvec *argv)
{
int retval = -1;
int snapshot;
if (cvec_len(argv) > 1){
clicon_err(OE_PLUGIN, 0, "%s: Requires 0 or 1 element. If given: snapshot flag 0|1", __FUNCTION__);
goto done;
}
if (cvec_len(argv))
snapshot = cv_int32_get(cvec_i(argv, 0));
else
snapshot = 0;
if ((retval = clicon_rpc_commit(h,
"candidate",
"running",
snapshot, /* snapshot */
snapshot)) < 0){ /* startup */
cli_output(stderr, "Commit failed. Edit and try again or discard changes");
goto done;
}
retval = 0;
done:
return retval;
}
/*! Generic validate callback
*/
int
cli_validatev(clicon_handle h,
cvec *vars,
cvec *argv)
{
int retval = -1;
if ((retval = clicon_rpc_validate(h, "candidate")) < 0)
clicon_err(OE_CFG, 0, "Validate failed. Edit and try again or discard changes");
return retval;
}
/*! Compare two dbs using XML. Write to file and run diff
*/
static int
compare_xmls(cxobj *xc1,
cxobj *xc2,
int astext)
{
int fd;
FILE *f;
char filename1[MAXPATHLEN];
char filename2[MAXPATHLEN];
char cmd[MAXPATHLEN];
int retval = -1;
cxobj *xc;
snprintf(filename1, sizeof(filename1), "/tmp/cliconXXXXXX");
snprintf(filename2, sizeof(filename2), "/tmp/cliconXXXXXX");
if ((fd = mkstemp(filename1)) < 0){
clicon_err(OE_UNDEF, errno, "tmpfile: %s", strerror (errno));
goto done;
}
if ((f = fdopen(fd, "w")) == NULL)
goto done;
xc = NULL;
if (astext)
while ((xc = xml_child_each(xc1, xc, -1)) != NULL)
xml2txt(f, xc, 0);
else
while ((xc = xml_child_each(xc1, xc, -1)) != NULL)
xml_print(f, xc);
fclose(f);
close(fd);
if ((fd = mkstemp(filename2)) < 0){
clicon_err(OE_UNDEF, errno, "mkstemp: %s", strerror (errno));
goto done;
}
if ((f = fdopen(fd, "w")) == NULL)
goto done;
xc = NULL;
if (astext)
while ((xc = xml_child_each(xc2, xc, -1)) != NULL)
xml2txt(f, xc, 0);
else
while ((xc = xml_child_each(xc2, xc, -1)) != NULL)
xml_print(f, xc);
fclose(f);
close(fd);
snprintf(cmd, sizeof(cmd), "/usr/bin/diff -dU 1 %s %s | grep -v @@ | sed 1,2d", filename1, filename2);
if (system(cmd) < 0)
goto done;
retval = 0;
done:
unlink(filename1);
unlink(filename2);
return retval;
}
/*! Compare two dbs using XML. Write to file and run diff
* @param[in] h Clicon handle
* @param[in] cvv
* @param[in] arg arg: 0 as xml, 1: as text
*/
int
compare_dbsv(clicon_handle h,
cvec *cvv,
cvec *argv)
{
cxobj *xc1 = NULL; /* running xml */
cxobj *xc2 = NULL; /* candidate xml */
int retval = -1;
int astext;
if (cvec_len(argv) > 1){
clicon_err(OE_PLUGIN, 0, "%s: Requires 0 or 1 element. If given: astext flag 0|1", __FUNCTION__);
goto done;
}
if (cvec_len(argv))
astext = cv_int32_get(cvec_i(argv, 0));
else
astext = 0;
if (xmldb_get(h, "running", "/", &xc1, NULL, NULL) < 0)
goto done;
if (xmldb_get(h, "candidate", "/", &xc2, NULL, NULL) < 0)
goto done;
if (compare_xmls(xc1, xc2, astext) < 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
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables (where <varname> is found)
* @param[in] argv A string: "<varname> (merge|replace)"
* <varname> is name of a variable occuring in "cvv" containing filename
* @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_filev("name2","merge");
* @endcode
* @see save_config_file
*/
int
load_config_filev(clicon_handle h,
cvec *cvv,
cvec *argv)
{
int ret = -1;
struct stat st;
char **vecp;
char *filename;
int replace;
cg_var *cv;
char *opstr;
char *varstr;
int fd = -1;
cxobj *xt = NULL;
cxobj *xn;
cxobj *x;
cbuf *cbxml;
if (cvec_len(argv) != 2){
if (cvec_len(argv)==1)
clicon_err(OE_PLUGIN, 0, "Got single argument:\"%s\". Expected \"<varname>,<op>\"", cv_string_get(cvec_i(argv,0)));
else
clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: <varname>,<op>", cvec_len(argv));
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_var(cvv, varstr)) == NULL){
clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr);
goto done;
}
if ((vecp = clicon_realpath(NULL, cv_string_get(cv), __FUNCTION__)) == NULL){
cli_output(stderr, "Failed to resolve filename\n");
goto done;
}
filename = vecp[0];
if (stat(filename, &st) < 0){
clicon_err(OE_UNIX, 0, "load_config: stat(%s): %s",
filename, strerror(errno));
goto done;
}
/* Open and parse local file into xml */
if ((fd = open(filename, O_RDONLY)) < 0){
clicon_err(OE_UNIX, errno, "%s: open(%s)", __FUNCTION__, filename);
goto done;
}
if (clicon_xml_parse_file(fd, &xt, "</clicon>") < 0)
goto done;
if ((xn = xml_child_i(xt, 0)) != NULL){
if ((cbxml = cbuf_new()) == NULL)
goto done;
x = NULL;
while ((x = xml_child_each(xn, x, -1)) != NULL)
if (clicon_xml2cbuf(cbxml, x, 0, 0) < 0)
goto done;
if (clicon_rpc_xmlput(h, "candidate",
replace?OP_REPLACE:OP_MERGE,
"",
cbuf_get(cbxml)) < 0)
goto done;
cbuf_free(cbxml);
}
ret = 0;
done:
unchunk_group(__FUNCTION__);
if (xt)
xml_free(xt);
if (fd != -1)
close(fd);
return ret;
}
/*! Copy database to local file
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv variable vector (containing <varname>)
* @param[in] arg a string: "<dbname> <varname>"
* <dbname> is running or candidate
* <varname> is name of cligen variable in the "cvv" vector containing file name
* 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_filev(clicon_handle h,
cvec *cvv,
cvec *argv)
{
int retval = -1;
char **vecp;
char *filename;
cg_var *cv;
char *dbstr;
char *varstr;
cxobj *xt = NULL;
FILE *f = NULL;
if (cvec_len(argv) != 2){
if (cvec_len(argv)==1)
clicon_err(OE_PLUGIN, 0, "%s: Got single argument:\"%s\". Expected \"<dbname>,<varname>\"", cv_string_get(cvec_i(argv,0)));
else
clicon_err(OE_PLUGIN, 0, "%s: Got %d arguments. Expected: <dbname>,<varname>", cvec_len(argv));
goto done;
}
#if 0
if (arg == NULL || (str = cv_string_get(arg)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
goto done;
}
if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){
clicon_err(OE_PLUGIN, errno, "clicon_strsplit");
goto done;
}
if (nvec != 2){
clicon_err(OE_PLUGIN, 0, "Arg syntax is <dbname> <varname>");
goto done;
}
#endif
dbstr = cv_string_get(cvec_i(argv, 0));
varstr = cv_string_get(cvec_i(argv, 1));
if (strcmp(dbstr, "running") != 0 && strcmp(dbstr, "candidate") != 0) {
clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
goto done;
}
if ((cv = cvec_find_var(cvv, varstr)) == NULL){
clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr);
goto done;
}
if ((vecp = clicon_realpath(NULL, cv_string_get(cv), __FUNCTION__)) == NULL){
cli_output(stderr, "Failed to resolve filename\n");
goto done;
}
filename = vecp[0];
if (xmldb_get(h, dbstr, "/", &xt, NULL, NULL) < 0)
goto done;
if ((f = fopen(filename, "wb")) == NULL){
clicon_err(OE_CFG, errno, "Creating file %s", filename);
goto done;
}
if (xml_print(f, xt) < 0)
goto done;
retval = 0;
/* Fall through */
done:
unchunk_group(__FUNCTION__);
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_allv(clicon_handle h,
cvec *cvv,
cvec *argv)
{
char *dbstr;
int retval = -1;
if (cvec_len(argv) != 1){
clicon_err(OE_PLUGIN, 0, "%s: Requires one element: dbname", __FUNCTION__);
goto done;
}
dbstr = cv_string_get(cvec_i(argv, 0));
if (strcmp(dbstr, "running") != 0 && strcmp(dbstr, "candidate") != 0){
clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
goto done;
}
if (clicon_rpc_change(h, "candidate",
OP_REMOVE,
"/", "") < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Discard all changes in candidate and replace with running
*/
int
discard_changesv(clicon_handle h,
cvec *cvv,
cvec *argv)
{
return clicon_rpc_copy(h, "running", "candidate");
}
/* These are strings that can be used as 3rd argument to cli_setlog */
static const char *SHOWAS_TXT = "txt";
static const char *SHOWAS_XML = "xml";
static const char *SHOWAS_XML2TXT = "xml2txt";
static const char *SHOWAS_XML2JSON = "xml2json";
/*! 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)
{
struct clicon_msg *reply;
enum clicon_msg_type type;
int eof;
int retval = -1;
char *eventstr = NULL;
int level;
cxobj *xt = NULL;
cxobj *xn;
char *format = (char*)arg;
/* get msg (this is the reason this function is called) */
if (clicon_msg_rcv(s, &reply, &eof, __FUNCTION__) < 0)
goto done;
if (eof){
clicon_err(OE_PROTO, ESHUTDOWN, "%s: Socket unexpected close", __FUNCTION__);
close(s);
errno = ESHUTDOWN;
event_unreg_fd(s, cli_notification_cb);
goto done;
}
if (format == NULL)
goto done;
type = ntohs(reply->op_type);
switch (type){
case CLICON_MSG_NOTIFY:
if (clicon_msg_notify_decode(reply, &level, &eventstr, __FUNCTION__) < 0)
goto done;
if (strcmp(format, SHOWAS_TXT) == 0){
fprintf(stdout, "%s", eventstr);
}
else
if (strcmp(format, SHOWAS_XML) == 0){
if (clicon_xml_parse_string(&eventstr, &xt) < 0)
goto done;
if ((xn = xml_child_i(xt, 0)) != NULL)
if (xml_print(stdout, xn) < 0)
goto done;
}
else
if (strcmp(format, SHOWAS_XML2TXT) == 0){
if (clicon_xml_parse_string(&eventstr, &xt) < 0)
goto done;
if ((xn = xml_child_i(xt, 0)) != NULL)
if (xml2txt(stdout, xn, 0) < 0)
goto done;
}
else
if (strcmp(format, SHOWAS_XML2JSON) == 0){
if (clicon_xml_parse_string(&eventstr, &xt) < 0)
goto done;
if ((xn = xml_child_i(xt, 0)) != NULL){
if (xml2json(stdout, xn, 0) < 0)
goto done;
}
}
break;
default:
clicon_err(OE_PROTO, 0, "%s: unexpected reply: %d",
__FUNCTION__, type);
goto done;
break;
}
retval = 0;
done:
if (xt)
xml_free(xt);
unchunk_group(__FUNCTION__); /* event allocated by chunk */
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_notifyv("mystream","1","xml");
* @endcode
* XXX: format is a memory leak
*/
int
cli_notifyv(clicon_handle h,
cvec *cvv,
cvec *argv)
{
char *stream = NULL;
int retval = -1;
int status;
char *formatstr = NULL;
enum format_enum format = MSG_NOTIFY_TXT;
if (cvec_len(argv) != 2 && cvec_len(argv) != 3){
clicon_err(OE_PLUGIN, 0, "%s Requires arguments: <logstream> <status> [<format>]", __FUNCTION__);
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));
if (strcmp(formatstr, "SHOWAS_TXT") != 0)
format = MSG_NOTIFY_XML;
}
if (cli_notification_register(h,
stream,
format,
"",
status,
cli_notification_cb,
(void*)formatstr) < 0)
goto done;
retval = 0;
done:
return retval;
}
/* Here are backward compatible cligen callback functions used when
* the option: CLICON_CLIGEN_CALLBACK_SINGLE_ARG is set.
*/
cb_single_arg(cli_set)
cb_single_arg(cli_merge)
cb_single_arg(cli_del)
cb_single_arg(cli_debug_cli)
cb_single_arg(cli_debug_backend)
cb_single_arg(cli_set_mode)
cb_single_arg(cli_start_shell)
cb_single_arg(cli_quit)
//cb_single_arg(cli_commit)
int cli_commit(clicon_handle h, cvec *cvv, cg_var *arg)
{
int retval=-1;
cvec *argv = NULL;
if (arg){
if (cv_type_get(arg) > CGV_EMPTY){
cligen_output(stderr, "%s: Illegal cvtype. This is most probably a single-argument cligen callback being used in a multi-argument setting. This can happen if option CLICON_CLIGEN_CALLBACK_SINGLE_ARG is 0 but you call a single argument callback (eg %s) from a .cli file. Please change to a multi-argument callback\n", __FUNCTION__, __FUNCTION__);
goto done;
}
if ((argv = cvec_from_var(arg)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_from_var");
goto done;
}
}
retval = cli_commitv(h, cvv, argv);
done:
if (argv) cvec_free(argv);
return retval;
}
cb_single_arg(cli_validate)
cb_single_arg(compare_dbs)
cb_single_arg(delete_all)
cb_single_arg(discard_changes)
/* Follows some functions not covered by translation macro */
int
load_config_file(clicon_handle h,
cvec *cvv,
cg_var *arg)
{
int retval=-1;
cvec *argv;
cg_var *cv;
char *str;
char **vec;
int nvec;
/* Split string into two parts and build a cvec of it and supply that to
the multi-arg callback */
if (arg == NULL || (str = cv_string_get(arg)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
goto done;
}
if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){
clicon_err(OE_PLUGIN, errno, "clicon_strsplit");
goto done;
}
if (nvec != 2){
clicon_err(OE_PLUGIN, 0, "Arg syntax is <varname> <replace|merge>");
goto done;
}
if ((argv = cvec_new(nvec)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_from_var");
goto done;
}
cv = cvec_i(argv, 0);
cv_type_set(cv, CGV_STRING);
cv_string_set(cv, vec[0]);
cv = cvec_i(argv, 1);
cv_type_set(cv, CGV_STRING);
cv_string_set(cv, vec[1]);
retval = load_config_filev(h, cvv, argv);
done:
unchunk_group(__FUNCTION__);
return retval;
}
int
save_config_file(clicon_handle h,
cvec *cvv,
cg_var *arg)
{
int retval=-1;
cvec *argv;
cg_var *cv;
char *str;
char **vec;
int nvec;
/* Split string into two parts and build a cvec of it and supply that to
the multi-arg callback */
if (arg == NULL || (str = cv_string_get(arg)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
goto done;
}
if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){
clicon_err(OE_PLUGIN, errno, "clicon_strsplit");
goto done;
}
if (nvec != 2){
clicon_err(OE_PLUGIN, 0, "Arg syntax is <dbname> <varname>");
goto done;
}
if ((argv = cvec_new(nvec)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_from_var");
goto done;
}
cv = cvec_i(argv, 0);
cv_type_set(cv, CGV_STRING);
cv_string_set(cv, vec[0]);
cv = cvec_i(argv, 1);
cv_type_set(cv, CGV_STRING);
cv_string_set(cv, vec[1]);
retval = save_config_filev(h, cvv, argv);
done:
unchunk_group(__FUNCTION__);
return retval;
}
int
cli_notify(clicon_handle h,
cvec *cvv,
cg_var *arg)
{
int retval=-1;
cvec *argv;
cg_var *cv;
char *str;
char **vec;
int nvec;
/* Split string into two parts and build a cvec of it and supply that to
the multi-arg callback */
if (arg == NULL || (str = cv_string_get(arg)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
goto done;
}
if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){
clicon_err(OE_PLUGIN, errno, "clicon_strsplit");
goto done;
}
if (nvec != 2 && nvec != 3){
clicon_err(OE_PLUGIN, 0, "Arg syntax is <logstream> <status> [<format>]");
goto done;
}
if ((argv = cvec_new(nvec)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_from_var");
goto done;
}
cv = cvec_i(argv, 0);
cv_type_set(cv, CGV_STRING);
cv_string_set(cv, vec[0]);
cv = cvec_i(argv, 1);
cv_type_set(cv, CGV_STRING);
cv_string_set(cv, vec[1]);
if (nvec > 2){
cv = cvec_i(argv, 2);
cv_type_set(cv, CGV_STRING);
cv_string_set(cv, vec[2]);
}
retval = cli_notifyv(h, cvv, argv);
done:
unchunk_group(__FUNCTION__);
return retval;
}
/*
* cli_debug
* set debug level on stderr (not syslog).
* 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.
* XXX obsolete. Use cli_debug_cliv or cli_debug_backendv instead
*/
int
cli_debug(clicon_handle h, cvec *vars, cg_var *arg)
{
cg_var *cv;
int level;
if ((cv = cvec_find_var(vars, "level")) == NULL)
cv = arg;
level = cv_int32_get(cv);
/* cli */
clicon_debug_init(level, NULL); /* 0: dont debug, 1:debug */
/* config daemon */
if (clicon_rpc_debug(h, level) < 0)
goto done;
done:
return 0;
}