clixon/apps/cli/cli_show.c

736 lines
19 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>
/* Exported functions in this file are in clixon_cli_api.h */
#include "clixon_cli_api.h"
static int xml2csv(FILE *f, cxobj *x, cvec *cvv);
//static int xml2csv_raw(FILE *f, cxobj *x);
/*! Completion callback intended for automatically generated data model
*
* Returns an expand-type list of commands as used by cligen 'expand'
* functionality.
*
* Assume callback given in a cligen spec: a <x:int expand_dbvar("arg")
* @param[in] h clicon handle
* @param[in] name Name of this function (eg "expand_dbvar")
* @param[in] cvv The command so far. Eg: cvec [0]:"a 5 b"; [1]: x=5;
* @param[in] arg Argument given at the callback "<db> <xmlkeyfmt>"
* @param[out] len len of return commands & helptxt
* @param[out] commands vector of function pointers to callback functions
* @param[out] helptxt vector of pointers to helptexts
* @see cli_expand_var_generate This is where arg is generated
*/
int
expand_dbvar(void *h,
char *name,
cvec *cvv,
cg_var *arg,
int *nr,
char ***commands,
char ***helptexts)
{
int nvec;
char **vec = NULL;
int retval = -1;
char *xkfmt;
char *str;
char *dbstr;
cxobj *xt = NULL;
char *xkpath = NULL;
cxobj **xvec = NULL;
size_t xlen = 0;
cxobj *x;
char *bodystr;
int i;
int j;
int k;
int i0;
if (arg == NULL || (str = cv_string_get(arg)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
goto done;
}
/* In the example, str = "candidate /x/m1/%s/b" */
if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){
clicon_err(OE_PLUGIN, errno, "clicon_strsplit");
goto done;
}
dbstr = vec[0];
if (strcmp(dbstr, "running") != 0 &&
strcmp(dbstr, "candidate") != 0){
clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
goto done;
}
xkfmt = vec[1];
/* xkfmt = /interface/%s/address/%s
--> ^/interface/eth0/address/.*$
--> /interface/[name=eth0]/address
*/
if (xmlkeyfmt2xpath(xkfmt, cvv, &xkpath) < 0)
goto done;
if (xmldb_get(h, dbstr, xkpath, 1, &xt, &xvec, &xlen) < 0)
goto done;
/* One round to detect duplicates
* XXX The code below would benefit from some cleanup
*/
j = 0;
for (i = 0; i < xlen; i++) {
char *str;
x = xvec[i];
if (xml_type(x) == CX_BODY)
bodystr = xml_value(x);
else
bodystr = xml_body(x);
if (bodystr == NULL){
clicon_err(OE_CFG, 0, "No xml body");
goto done;
}
/* detect duplicates */
for (k=0; k<j; k++){
if (xml_type(xvec[k]) == CX_BODY)
str = xml_value(xvec[k]);
else
str = xml_body(xvec[k]);
if (strcmp(str, bodystr)==0)
break;
}
if (k==j) /* not duplicate */
xvec[j++] = x;
}
xlen = j;
i0 = *nr;
*nr += xlen;
if ((*commands = realloc(*commands, sizeof(char *) * (*nr))) == NULL) {
clicon_err(OE_UNIX, errno, "realloc: %s", strerror (errno));
goto done;
}
for (i = 0; i < xlen; i++) {
x = xvec[i];
if (xml_type(x) == CX_BODY)
bodystr = xml_value(x);
else
bodystr = xml_body(x);
if (bodystr == NULL){
clicon_err(OE_CFG, 0, "No xml body");
goto done;
}
(*commands)[i0+i] = strdup(bodystr);
}
retval = 0;
done:
unchunk_group(__FUNCTION__);
if (xvec)
free(xvec);
if (xt)
xml_free(xt);
if (xkpath)
free(xkpath);
return retval;
}
/*! List files in a directory
*/
int
expand_dir(char *dir, int *nr, char ***commands, mode_t flags, int detail)
{
DIR *dirp;
struct dirent *dp;
struct stat st;
char *str;
char *cmd;
int len;
int retval = -1;
struct passwd *pw;
char filename[MAXPATHLEN];
if ((dirp = opendir(dir)) == 0){
fprintf(stderr, "expand_dir: opendir(%s) %s\n",
dir, strerror(errno));
return -1;
}
*nr = 0;
while ((dp = readdir(dirp)) != NULL) {
if (
#if 0
strcmp(dp->d_name, ".") != 0 &&
strcmp(dp->d_name, "..") != 0
#else
dp->d_name[0] != '.'
#endif
) {
snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp->d_name);
if (lstat(filename, &st) == 0){
if ((st.st_mode & flags) == 0)
continue;
#if EXPAND_RECURSIVE
if (S_ISDIR(st.st_mode)) {
int nrsav = *nr;
if(expand_dir(filename, nr, commands, detail) < 0)
goto quit;
while(nrsav < *nr) {
len = strlen(dp->d_name) + strlen((*commands)[nrsav]) + 2;
if((str = malloc(len)) == NULL) {
fprintf(stderr, "expand_dir: malloc: %s\n",
strerror(errno));
goto quit;
}
snprintf(str, len-1, "%s/%s",
dp->d_name, (*commands)[nrsav]);
free((*commands)[nrsav]);
(*commands)[nrsav] = str;
nrsav++;
}
continue;
}
#endif
if ((cmd = strdup(dp->d_name)) == NULL) {
fprintf(stderr, "expand_dir: strdup: %s\n",
strerror(errno));
goto quit;
}
if (0 &&detail){
if ((pw = getpwuid(st.st_uid)) == NULL){
fprintf(stderr, "expand_dir: getpwuid(%d): %s\n",
st.st_uid, strerror(errno));
goto quit;
}
len = strlen(cmd) +
strlen(pw->pw_name) +
#ifdef __FreeBSD__
strlen(ctime(&st.st_mtimespec.tv_sec)) +
#else
strlen(ctime(&st.st_mtim.tv_sec)) +
#endif
strlen("{ by }") + 1 /* \0 */;
if ((str=realloc(cmd, strlen(cmd)+len)) == NULL) {
fprintf(stderr, "expand_dir: malloc: %s\n",
strerror(errno));
goto quit;
}
snprintf(str + strlen(dp->d_name),
len - strlen(dp->d_name),
"{%s by %s}",
#ifdef __FreeBSD__
ctime(&st.st_mtimespec.tv_sec),
#else
ctime(&st.st_mtim.tv_sec),
#endif
pw->pw_name
);
cmd = str;
}
if (((*commands) =
realloc(*commands, ((*nr)+1)*sizeof(char**))) == NULL){
perror("expand_dir: realloc");
goto quit;
}
(*commands)[(*nr)] = cmd;
(*nr)++;
if (*nr >= 128) /* Limit number of options */
break;
}
}
}
retval = 0;
quit:
closedir(dirp);
return retval;
}
/*! Generic function for showing configurations.
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>]
* @param[out] xt Configuration as xml tree.
* Format of arg:
* <dbname> "running", "candidate"
* <xpath> xpath expression
* <varname> optional name of variable in cvv. If set, xpath must have a '%s'
* @code
* show config id <n:string>, show_conf_as("running interfaces/interface[name=%s] n");
* @endcode
*/
static int
show_conf_as(clicon_handle h,
cvec *cvv,
cg_var *arg,
cxobj **xt) /* top xml */
{
int retval = -1;
char *db;
char **vec = NULL;
int nvec;
char *str;
char *xpath;
char *attr = NULL;
cbuf *cbx = NULL;
int i;
int j;
cg_var *cvattr;
char *val = NULL;
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, "format error \"%s\" - expected <dbname> <xpath> [<attr>]", str);
goto done;
}
/* Dont get attr here, take it from arg instead */
db = vec[0];
if (strcmp(db, "running") != 0 && strcmp(db, "candidate") != 0) {
clicon_err(OE_PLUGIN, 0, "No such db name: %s", db);
goto done;
}
xpath = vec[1];
if ((cbx = cbuf_new()) == NULL){
clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done;
}
if (nvec == 3){
attr = vec[2];
j = 0;
for (i=0; i<strlen(xpath); i++)
if (xpath[i] == '%')
j++;
if (j != 1){
clicon_err(OE_PLUGIN, 0, "xpath '%s' does not have a single '%%'");
goto done;
}
if ((cvattr = cvec_find_var(cvv, attr)) == NULL){
clicon_err(OE_PLUGIN, 0, "attr '%s' not found in cligen var list", attr);
goto done;
}
if ((val = cv2str_dup(cvattr)) == NULL){
clicon_err(OE_PLUGIN, errno, "cv2str_dup");
goto done;
}
cprintf(cbx, xpath, val);
}
else
cprintf(cbx, "%s", xpath);
if (xmldb_get(h, db, cbuf_get(cbx), 0, xt, NULL, NULL) < 0)
goto done;
retval = 0;
done:
if (val)
free(val);
if (cbx)
cbuf_free(cbx);
unchunk_group(__FUNCTION__);
return retval;
}
/*! Show a configuration database on stdout using XML format
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>]
* @param[in] netconf If set print as netconf edit-config, otherwise just xml
* @see show_conf_as the main function
*/
static int
show_conf_as_xml1(clicon_handle h,
cvec *cvv,
cg_var *arg,
int netconf)
{
cxobj *xt = NULL;
cxobj *xc;
int retval = -1;
if (show_conf_as(h, cvv, arg, &xt) < 0)
goto done;
if (netconf) /* netconf prefix */
fprintf(stdout, "<rpc><edit-config><target><candidate/></target><config>\n");
xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, -1)) != NULL)
clicon_xml2file(stdout, xc, netconf?2:0, 1);
if (netconf) /* netconf postfix */
fprintf(stdout, "</config></edit-config></rpc>]]>]]>\n");
retval = 0;
done:
if (xt)
xml_free(xt);
return retval;
}
/*! Show configuration as prettyprinted xml
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>]
* @see show_conf_as the main function
*/
int
show_conf_as_xml(clicon_handle h,
cvec *cvv,
cg_var *arg)
{
return show_conf_as_xml1(h, cvv, arg, 0);
}
/*! Show configuration as prettyprinted xml with netconf hdr/tail
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>]
* @see show_conf_as the main function
*/
int
show_conf_as_netconf(clicon_handle h,
cvec *cvv,
cg_var *arg)
{
return show_conf_as_xml1(h, cvv, arg, 1);
}
/*! Show configuration as JSON
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>]
* @see show_conf_as the main function
*/
int
show_conf_as_json(clicon_handle h,
cvec *cvv,
cg_var *arg)
{
cxobj *xt = NULL;
int retval = -1;
if (show_conf_as(h, cvv, arg, &xt) < 0)
goto done;
xml2json(stdout, xt, 1);
retval = 0;
done:
if (xt)
xml_free(xt);
return retval;
}
/*! Show configuration as text givebn an xpath
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath>
* @note Hardcoded that a variable in cvv is named "xpath"
*/
int
show_conf_xpath(clicon_handle h,
cvec *cvv,
cg_var *arg)
{
int retval = -1;
char *str;
char *xpath;
cg_var *cv;
cxobj *xt = NULL;
cxobj **xv = NULL;
size_t xlen;
int i;
if (arg == NULL || (str = cv_string_get(arg)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
goto done;
}
/* Dont get attr here, take it from arg instead */
if (strcmp(str, "running") != 0 && strcmp(str, "candidate") != 0){
clicon_err(OE_PLUGIN, 0, "No such db name: %s", str);
goto done;
}
cv = cvec_find_var(cvv, "xpath");
xpath = cv_string_get(cv);
if (xmldb_get(h, str, xpath, 1, &xt, &xv, &xlen) < 0)
goto done;
for (i=0; i<xlen; i++)
xml_print(stdout, xv[i]);
retval = 0;
done:
if (xv)
free(xv);
if (xt)
xml_free(xt);
unchunk_group(__FUNCTION__);
return retval;
}
/*! Show configuration as text
* Utility function used by cligen spec file
*/
static int
show_conf_as_text1(clicon_handle h, cvec *cvv, cg_var *arg)
{
cxobj *xt = NULL;
cxobj *xc;
int retval = -1;
if (show_conf_as(h, cvv, arg, &xt) < 0)
goto done;
xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, -1)) != NULL)
xml2txt(stdout, xc, 0); /* tree-formed text */
retval = 0;
done:
if (xt)
xml_free(xt);
unchunk_group(__FUNCTION__);
return retval;
}
/* Show configuration as commands, ie not tree format but as one-line commands
*/
static int
show_conf_as_command(clicon_handle h, cvec *cvv, cg_var *arg, char *prepend)
{
cxobj *xt = NULL;
cxobj *xc;
enum genmodel_type gt;
int retval = -1;
if ((xt = xml_new("tmp", NULL)) == NULL)
goto done;
if (show_conf_as(h, cvv, arg, &xt) < 0)
goto done;
xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, -1)) != NULL){
if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR)
goto done;
xml2cli(stdout, xc, prepend, gt, __FUNCTION__); /* cli syntax */
}
retval = 0;
done:
if (xt)
xml_free(xt);
unchunk_group(__FUNCTION__);
return retval;
}
int
show_conf_as_text(clicon_handle h, cvec *cvv, cg_var *arg)
{
return show_conf_as_text1(h, cvv, arg);
}
int
show_conf_as_cli(clicon_handle h, cvec *cvv, cg_var *arg)
{
return show_conf_as_command(h, cvv, arg, NULL); /* XXX: how to set prepend? */
}
int
show_yang(clicon_handle h, cvec *cvv, cg_var *arg)
{
yang_node *yn;
char *str = NULL;
yang_spec *yspec;
yspec = clicon_dbspec_yang(h);
if (arg != NULL){
str = cv_string_get(arg);
yn = (yang_node*)yang_find((yang_node*)yspec, 0, str);
}
else
yn = (yang_node*)yspec;
yang_print(stdout, yn, 0);
return 0;
}
#ifdef notused
/*! XML to CSV raw variant
* @see xml2csv
*/
static int
xml2csv_raw(FILE *f, cxobj *x)
{
cxobj *xc;
cxobj *xb;
int retval = -1;
int i = 0;
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL) {
if (xml_child_nr(xc)){
xb = xml_child_i(xc, 0);
if (xml_type(xb) == CX_BODY){
if (i++)
fprintf(f, ";");
fprintf(f, "%s", xml_value(xb));
}
}
}
fprintf(f, "\n");
retval = 0;
return retval;
}
#endif
/*! Translate XML -> CSV commands
* Can only be made in a 'flat tree', ie on the form:
* <X><A>B</A></X> -->
* Type, A
* X, B
* @param[in] f Output file
* @param[in] x XML tree
* @param[in] cvv A vector of field names present in XML
* This means that only fields in x that are listed in cvv will be printed.
*/
static int
xml2csv(FILE *f, cxobj *x, cvec *cvv)
{
cxobj *xe, *xb;
int retval = -1;
cg_var *vs;
fprintf(f, "%s", xml_name(x));
xe = NULL;
vs = NULL;
while ((vs = cvec_each(cvv, vs))) {
if ((xe = xml_find(x, cv_name_get(vs))) == NULL){
fprintf(f, ";");
continue;
}
if (xml_child_nr(xe)){
xb = xml_child_i(xe, 0);
fprintf(f, ";%s", xml_value(xb));
}
}
fprintf(f, "\n");
retval = 0;
return retval;
}
static int
show_conf_as_csv1(clicon_handle h, cvec *cvv0, cg_var *arg)
{
cxobj *xt = NULL;
cxobj *xc;
int retval = -1;
cvec *cvv=NULL;
char *str;
if (show_conf_as(h, cvv0, arg, &xt) < 0)
goto done;
xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, -1)) != NULL){
if ((str = chunk_sprintf(__FUNCTION__, "%s[]", xml_name(xc))) == NULL)
goto done;
#ifdef NOTYET /* yang-spec? */
if (ds==NULL && (ds = key2spec_key(dbspec, str)) != NULL){
cg_var *vs;
fprintf(stdout, "Type");
cvv = db_spec2cvec(ds);
vs = NULL;
while ((vs = cvec_each(cvv, vs)))
fprintf(stdout, ";%s", cv_name_get(vs));
fprintf(stdout, "\n");
} /* Now values just need to follow,... */
#endif /* yang-spec? */
if (cvv== NULL)
goto done;
xml2csv(stdout, xc, cvv); /* csv syntax */
}
retval = 0;
done:
if (xt)
xml_free(xt);
unchunk_group(__FUNCTION__);
return retval;
}
int
show_conf_as_csv(clicon_handle h, cvec *cvv, cg_var *arg)
{
return show_conf_as_csv1(h, cvv, arg);
}