Moved restconf_stream.c -> restconf_stream_fcgi.c, made some generaizations and documented what was hardcoded to FCGI
This commit is contained in:
parent
73bbcded87
commit
e2b3cdb3f6
8 changed files with 148 additions and 94 deletions
|
|
@ -94,7 +94,8 @@ APPSRC += restconf_main_$(with_restconf).c
|
|||
|
||||
# Fcgi-specific source including main
|
||||
ifeq ($(with_restconf),fcgi)
|
||||
APPSRC += restconf_stream.c
|
||||
# Streams notifications have some fcgi specific handling
|
||||
APPSRC += restconf_stream_$(with_restconf).c
|
||||
endif
|
||||
|
||||
APPOBJ = $(APPSRC:.c=.o)
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ restconf_reply_send(void *req0,
|
|||
FCGX_FPrintF(req->out, "%s", cbuf_get(cb));
|
||||
FCGX_FPrintF(req->out, "\r\n");
|
||||
}
|
||||
FCGX_FFlush(req->out); /* Is this only for notification ? */
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
|
|
|||
|
|
@ -492,7 +492,17 @@ main(int argc,
|
|||
}
|
||||
}
|
||||
else if (strncmp(path+1, stream_path, strlen(stream_path)) == 0) {
|
||||
api_stream(h, req, stream_path, &finish);
|
||||
char *query = NULL;
|
||||
cvec *qvec = NULL;
|
||||
query = restconf_param_get(h, "QUERY_STRING");
|
||||
if (query != NULL && strlen(query))
|
||||
if (str2cvec(query, '&', '=', &qvec) < 0)
|
||||
goto done;
|
||||
api_stream(h, req, qvec, stream_path, &finish);
|
||||
if (qvec){
|
||||
cvec_free(qvec);
|
||||
qvec = NULL;
|
||||
}
|
||||
}
|
||||
else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) {
|
||||
api_well_known(h, req); /* */
|
||||
|
|
@ -509,7 +519,7 @@ main(int argc,
|
|||
if (finish)
|
||||
FCGX_Finish_r(req);
|
||||
else{ /* A handler is forked so we initiate a new request after instead
|
||||
of finnishing the old */
|
||||
of finishing the old */
|
||||
if (FCGX_InitRequest(req, sock, 0) != 0){
|
||||
clicon_err(OE_CFG, errno, "FCGX_InitRequest");
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,6 @@
|
|||
*/
|
||||
int stream_child_free(clicon_handle h, int pid);
|
||||
int stream_child_freeall(clicon_handle h);
|
||||
int api_stream(clicon_handle h, FCGX_Request *r, char *streampath, int *finish);
|
||||
int api_stream(clicon_handle h, void *req, cvec *qvec, char *streampath, int *finish);
|
||||
|
||||
#endif /* _RESTCONF_STREAM_H_ */
|
||||
|
|
|
|||
|
|
@ -56,6 +56,12 @@
|
|||
query parameters, defined in Section 4.8. Refer to Appendix B.3.6
|
||||
for filter parameter examples.
|
||||
|
||||
* Note that this implementation includes some hardcoded things for FCGI.
|
||||
* These are:
|
||||
* - req->listen_sock is used to register incoming fd events from (nginx) fcgi server
|
||||
* - The stream_child struct copies the FCGX_Request by value, so FCGX_Free() can be called
|
||||
* asynchronously
|
||||
* - In the forked variant, FCGX_Finish_r() and FCGX_Free() are called (minor)
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
|
|
@ -99,13 +105,13 @@
|
|||
*/
|
||||
#define STREAM_FORK 1
|
||||
|
||||
/* Keep track of children - whjen they exit - their FCGX handle needs to be
|
||||
/* Keep track of children - when they exit - their FCGX handle needs to be
|
||||
* freed with FCGX_Free(&rbk, 0);
|
||||
*/
|
||||
struct stream_child{
|
||||
qelem_t sc_q; /* queue header */
|
||||
int sc_pid; /* Child process id */
|
||||
FCGX_Request sc_r; /* FCGI stream data */
|
||||
FCGX_Request sc_r; /* FCGI stream data. XXX this is by value */
|
||||
};
|
||||
/* Linked list of children
|
||||
* @note could hang STREAM_CHILD list on clicon handle instead.
|
||||
|
|
@ -127,7 +133,7 @@ stream_child_free(clicon_handle h,
|
|||
do {
|
||||
if (pid == sc->sc_pid){
|
||||
DELQ(sc, STREAM_CHILD, struct stream_child *);
|
||||
FCGX_Free(&sc->sc_r, 0);
|
||||
FCGX_Free(&sc->sc_r, 0); /* XXX pointer to actual copied struct */
|
||||
free(sc);
|
||||
goto done;
|
||||
}
|
||||
|
|
@ -145,13 +151,15 @@ stream_child_freeall(clicon_handle h)
|
|||
|
||||
while ((sc = STREAM_CHILD) != NULL){
|
||||
DELQ(sc, STREAM_CHILD, struct stream_child *);
|
||||
FCGX_Free(&sc->sc_r, 1);
|
||||
FCGX_Free(&sc->sc_r, 1); /* XXX pointer to actual copied struct */
|
||||
free(sc);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Callback when stream notifications arrive from backend
|
||||
* @param[in] s Socket
|
||||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||
*/
|
||||
static int
|
||||
restconf_stream_cb(int s,
|
||||
|
|
@ -230,13 +238,13 @@ restconf_stream_cb(int s,
|
|||
|
||||
/*! Send subsctription to backend
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] r Fastcgi request handle
|
||||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||
* @param[in] name Stream name
|
||||
* @param[out] sp Socket -1 if not set
|
||||
*/
|
||||
static int
|
||||
restconf_stream(clicon_handle h,
|
||||
FCGX_Request *r,
|
||||
void *req,
|
||||
char *name,
|
||||
cvec *qvec,
|
||||
int pretty,
|
||||
|
|
@ -279,19 +287,22 @@ restconf_stream(clicon_handle h,
|
|||
if (clicon_rpc_netconf(h, cbuf_get(cb), &xret, &s) < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){
|
||||
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
|
||||
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
|
||||
/* Setting up stream */
|
||||
FCGX_SetExitStatus(201, r->out); /* Created */
|
||||
FCGX_FPrintF(r->out, "Status: 201 Created\r\n");
|
||||
FCGX_FPrintF(r->out, "Content-Type: text/event-stream\r\n");
|
||||
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
|
||||
FCGX_FPrintF(r->out, "Connection: keep-alive\r\n");
|
||||
FCGX_FPrintF(r->out, "X-Accel-Buffering: no\r\n");
|
||||
FCGX_FPrintF(r->out, "\r\n");
|
||||
FCGX_FFlush(r->out);
|
||||
if (restconf_reply_header(req, "Content-Type", "text/event-stream") < 0)
|
||||
goto done;
|
||||
if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
|
||||
goto done;
|
||||
if (restconf_reply_header(req, "Connection", "keep-alive") < 0)
|
||||
goto done;
|
||||
if (restconf_reply_header(req, "X-Accel-Buffering", "no") < 0)
|
||||
goto done;
|
||||
if (restconf_reply_send(req, 201, NULL) < 0)
|
||||
goto done;
|
||||
*sp = s;
|
||||
ok:
|
||||
retval = 0;
|
||||
|
|
@ -308,11 +319,16 @@ restconf_stream(clicon_handle h,
|
|||
#include "restconf_lib.h"
|
||||
#include "restconf_stream.h"
|
||||
|
||||
/*! Listen sock callback (from proxy?)
|
||||
* @param[in] s Socket
|
||||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||
*/
|
||||
static int
|
||||
stream_checkuplink(int s,
|
||||
void *arg)
|
||||
{
|
||||
FCGX_Request *r = (FCGX_Request *)arg;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
if (FCGX_GetError(r->out) != 0){ /* break loop */
|
||||
clicon_debug(1, "%s FCGX_GetError upstream", __FUNCTION__);
|
||||
|
|
@ -343,26 +359,29 @@ stream_timeout(int s,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*! Process a FastCGI request
|
||||
* @param[in] r Fastcgi request handle
|
||||
/*! Process a stream request
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
|
||||
* @param[in] streampath URI path for streams, eg /streams, see CLICON_STREAM_PATH
|
||||
* @param[out] finish Set to zero, if request should not be finnished by upper layer
|
||||
*/
|
||||
int
|
||||
api_stream(clicon_handle h,
|
||||
FCGX_Request *r,
|
||||
void *req,
|
||||
cvec *qvec,
|
||||
char *streampath,
|
||||
int *finish)
|
||||
{
|
||||
int retval = -1;
|
||||
FCGX_Request *rfcgi = (FCGX_Request *)req; /* XXX */
|
||||
char *path;
|
||||
char *query;
|
||||
char *method;
|
||||
char **pvec = NULL;
|
||||
int pn;
|
||||
cvec *qvec = NULL;
|
||||
cvec *dvec = NULL;
|
||||
cvec *pcvec = NULL; /* for rest api */
|
||||
cbuf *cb = NULL;
|
||||
char *data;
|
||||
char *indata;
|
||||
int authenticated = 0;
|
||||
int pretty;
|
||||
restconf_media media_out = YANG_DATA_XML; /* XXX default */
|
||||
|
|
@ -377,44 +396,41 @@ api_stream(clicon_handle h,
|
|||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
path = restconf_uripath(h);
|
||||
query = restconf_param_get(h, "QUERY_STRING");
|
||||
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
|
||||
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
|
||||
goto done;
|
||||
/* Sanity check of path. Should be /stream/<name> */
|
||||
if (pn != 3){
|
||||
restconf_notfound(h, r);
|
||||
restconf_notfound(h, req);
|
||||
goto ok;
|
||||
}
|
||||
if (strlen(pvec[0]) != 0){
|
||||
retval = restconf_notfound(h, r);
|
||||
retval = restconf_notfound(h, req);
|
||||
goto done;
|
||||
}
|
||||
if (strcmp(pvec[1], streampath)){
|
||||
retval = restconf_notfound(h, r);
|
||||
retval = restconf_notfound(h, req);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((method = pvec[2]) == NULL){
|
||||
retval = restconf_notfound(h, r);
|
||||
retval = restconf_notfound(h, req);
|
||||
goto done;
|
||||
}
|
||||
clicon_debug(1, "%s: method=%s", __FUNCTION__, method);
|
||||
if (str2cvec(query, '&', '=', &qvec) < 0)
|
||||
goto done;
|
||||
|
||||
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
|
||||
goto done;
|
||||
/* data */
|
||||
if ((cb = restconf_get_indata(r)) == NULL)
|
||||
goto done;
|
||||
data = cbuf_get(cb);
|
||||
clicon_debug(1, "%s DATA=%s", __FUNCTION__, data);
|
||||
if (str2cvec(data, '&', '=', &dvec) < 0)
|
||||
if ((cb = restconf_get_indata(req)) == NULL)
|
||||
goto done;
|
||||
indata = cbuf_get(cb);
|
||||
clicon_debug(1, "%s DATA=%s", __FUNCTION__, indata);
|
||||
|
||||
/* If present, check credentials. See "plugin_credentials" in plugin
|
||||
* See RFC 8040 section 2.5
|
||||
*/
|
||||
if ((authenticated = clixon_plugin_auth_all(h, r)) < 0)
|
||||
if ((authenticated = clixon_plugin_auth_all(h, req)) < 0)
|
||||
goto done;
|
||||
clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
|
||||
|
||||
|
|
@ -427,22 +443,20 @@ api_stream(clicon_handle h,
|
|||
if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0)
|
||||
goto done;
|
||||
if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
|
||||
if (api_return_err(h, r, xerr, pretty, media_out, 0) < 0)
|
||||
if (api_return_err(h, req, xerr, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
goto ok;
|
||||
}
|
||||
clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
|
||||
if (restconf_stream(h, r, method, qvec, pretty, media_out, &s) < 0)
|
||||
if (restconf_stream(h, req, method, qvec, pretty, media_out, &s) < 0)
|
||||
goto done;
|
||||
if (s != -1){
|
||||
#ifdef STREAM_FORK
|
||||
if ((pid = fork()) == 0){ /* child */
|
||||
if (pvec)
|
||||
free(pvec);
|
||||
if (dvec)
|
||||
cvec_free(dvec);
|
||||
if (qvec)
|
||||
cvec_free(qvec);
|
||||
if (pcvec)
|
||||
|
|
@ -457,26 +471,27 @@ api_stream(clicon_handle h,
|
|||
/* Listen to backend socket */
|
||||
if (clixon_event_reg_fd(s,
|
||||
restconf_stream_cb,
|
||||
(void*)r,
|
||||
req,
|
||||
"stream socket") < 0)
|
||||
goto done;
|
||||
if (clixon_event_reg_fd(r->listen_sock,
|
||||
if (clixon_event_reg_fd(rfcgi->listen_sock,
|
||||
stream_checkuplink,
|
||||
(void*)r,
|
||||
req,
|
||||
"stream socket") < 0)
|
||||
goto done;
|
||||
/* Poll upstream errors */
|
||||
stream_timeout(0, (void*)r);
|
||||
stream_timeout(0, req);
|
||||
/* Start loop */
|
||||
clixon_event_loop();
|
||||
close(s);
|
||||
clixon_event_unreg_fd(s, restconf_stream_cb);
|
||||
clixon_event_unreg_fd(r->listen_sock, restconf_stream_cb);
|
||||
clixon_event_unreg_timeout(stream_timeout, (void*)r);
|
||||
clixon_event_unreg_fd(rfcgi->listen_sock,
|
||||
restconf_stream_cb);
|
||||
clixon_event_unreg_timeout(stream_timeout, (void*)req);
|
||||
clicon_exit_reset();
|
||||
#ifdef STREAM_FORK
|
||||
FCGX_Finish_r(r);
|
||||
FCGX_Free(r, 0);
|
||||
FCGX_Finish_r(rfcgi);
|
||||
FCGX_Free(rfcgi, 0);
|
||||
restconf_terminate(h);
|
||||
exit(0);
|
||||
}
|
||||
|
|
@ -490,7 +505,8 @@ api_stream(clicon_handle h,
|
|||
}
|
||||
memset(sc, 0, sizeof(struct stream_child));
|
||||
sc->sc_pid = pid;
|
||||
sc->sc_r = *r;
|
||||
sc->sc_r = *rfcgi; /* XXX by value */
|
||||
|
||||
ADDQ(sc, STREAM_CHILD);
|
||||
*finish = 0; /* If spawn child, we should not finish this stream */
|
||||
#endif /* STREAM_FORK */
|
||||
|
|
@ -501,10 +517,6 @@ api_stream(clicon_handle h,
|
|||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||
if (pvec)
|
||||
free(pvec);
|
||||
if (dvec)
|
||||
cvec_free(dvec);
|
||||
if (qvec)
|
||||
cvec_free(qvec);
|
||||
if (pcvec)
|
||||
cvec_free(pcvec);
|
||||
if (cb)
|
||||
|
|
@ -37,8 +37,10 @@
|
|||
* argc/argv after -- in clixon_backend:
|
||||
* -r enable the reset function
|
||||
* -s enable the state function
|
||||
* -S read state data from file, otherwise construct it programmatically
|
||||
* -S <file> read state data from file, otherwise construct it programmatically (requires -s)
|
||||
* -i read state file on init not by request for optimization (requires -sS <file>)
|
||||
* -u enable upgrade function - auto-upgrade testing
|
||||
* -U general-purpose upgrade
|
||||
* -t enable transaction logging (cal syslog for every transaction)
|
||||
*/
|
||||
#include <stdio.h>
|
||||
|
|
@ -372,14 +374,26 @@ example_statedata(clicon_handle h,
|
|||
if (clixon_xml_parse_file(fd, YB_MODULE, yspec, NULL, &xt, NULL) < 0)
|
||||
goto done;
|
||||
close(fd);
|
||||
if ((x1 = xpath_first(xt, nsc, "%s", xpath)) != NULL){
|
||||
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath) < 0)
|
||||
goto done;
|
||||
for (i=0; i<xlen; i++){
|
||||
x1 = xvec[i];
|
||||
xml_flag_set(x1, XML_FLAG_MARK);
|
||||
}
|
||||
/* Remove everything that is not marked */
|
||||
if (xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL) < 0)
|
||||
goto done;
|
||||
for (i=0; i<xlen; i++){
|
||||
x1 = xvec[i];
|
||||
xml_flag_reset(x1, XML_FLAG_MARK);
|
||||
}
|
||||
if (xml_copy(xt, xstate) < 0)
|
||||
goto done;
|
||||
|
||||
if (xvec){
|
||||
free(xvec);
|
||||
xvec = 0;
|
||||
xlen = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,8 +175,6 @@ main(int argc,
|
|||
goto done;
|
||||
if (clicon_conf_xml_set(h, xcfg) < 0)
|
||||
goto done;
|
||||
xcfg = xml_new("clixon-config", NULL, CX_ELMNT);
|
||||
clicon_conf_xml_set(h, xcfg);
|
||||
optind = 1;
|
||||
opterr = 0;
|
||||
while ((c = getopt(argc, argv, UTIL_XML_OPTS)) != -1)
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
#include "clixon/clixon.h"
|
||||
|
||||
/* Command line options passed to getopt(3) */
|
||||
#define UTIL_XML_MOD_OPTS "hD:o:y:b:x:p:s"
|
||||
#define UTIL_XML_MOD_OPTS "hD:o:y:Y:b:x:p:s"
|
||||
|
||||
enum opx{
|
||||
OPX_ERROR = -1,
|
||||
|
|
@ -95,8 +95,9 @@ usage(char *argv0)
|
|||
"where options are\n"
|
||||
"\t-h \t\tHelp\n"
|
||||
"\t-D <level>\tDebug\n"
|
||||
"\t-o <op> \tOperation: insert or merge\n"
|
||||
"\t-o <op> \tOperation: parent, insert or merge\n"
|
||||
"\t-y <file> \tYANG spec file\n"
|
||||
"\t-Y <dir> \tYang dirs (can be several)\n"
|
||||
"\t-b <base> \tXML base expression\n"
|
||||
"\t-x <xml> \tXML to insert\n"
|
||||
"\t-p <xpath>\tXpath to where in base and XML\n"
|
||||
|
|
@ -120,7 +121,7 @@ main(int argc, char **argv)
|
|||
yang_stmt *yspec = NULL;
|
||||
cxobj *x0 = NULL;
|
||||
cxobj *x1 = NULL;
|
||||
cxobj *xb;
|
||||
cxobj *xb = NULL;
|
||||
cxobj *xi = NULL;
|
||||
cxobj *xi1 = NULL;
|
||||
cxobj *xerr = NULL;
|
||||
|
|
@ -130,10 +131,15 @@ main(int argc, char **argv)
|
|||
enum opx opx = OPX_ERROR;
|
||||
char *reason = NULL;
|
||||
int dbg = 0;
|
||||
cxobj *xcfg = NULL;
|
||||
|
||||
clicon_log_init("clixon_insert", LOG_DEBUG, CLICON_LOG_STDERR);
|
||||
if ((h = clicon_handle_init()) == NULL)
|
||||
goto done;
|
||||
if ((xcfg = xml_new("clixon-config", NULL, CX_ELMNT)) == NULL)
|
||||
goto done;
|
||||
if (clicon_conf_xml_set(h, xcfg) < 0)
|
||||
goto done;
|
||||
optind = 1;
|
||||
opterr = 0;
|
||||
while ((c = getopt(argc, argv, UTIL_XML_MOD_OPTS)) != -1)
|
||||
|
|
@ -151,6 +157,10 @@ main(int argc, char **argv)
|
|||
case 'y': /* YANG spec file */
|
||||
yangfile = optarg;
|
||||
break;
|
||||
case 'Y':
|
||||
if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'b': /* Base XML expression */
|
||||
x0str = optarg;
|
||||
break;
|
||||
|
|
@ -187,7 +197,9 @@ main(int argc, char **argv)
|
|||
goto done;
|
||||
}
|
||||
/* Get base subtree by xpath */
|
||||
if ((xb = xpath_first(x0, NULL, "%s", xpath)) == NULL){
|
||||
if (xpath == NULL)
|
||||
xb = x0;
|
||||
else if ((xb = xpath_first(x0, NULL, "%s", xpath)) == NULL){
|
||||
clicon_err(OE_XML, 0, "xpath: %s not found in x0", xpath);
|
||||
goto done;
|
||||
}
|
||||
|
|
@ -208,7 +220,7 @@ main(int argc, char **argv)
|
|||
}
|
||||
break;
|
||||
case OPX_MERGE:
|
||||
/* Parse insert XML */
|
||||
/* Parse merge XML */
|
||||
if ((ret = clixon_xml_parse_string(x1str, YB_MODULE, yspec, &x1, &xerr)) < 0){
|
||||
clicon_err(OE_XML, 0, "Parsing insert xml: %s", x1str);
|
||||
goto done;
|
||||
|
|
@ -217,7 +229,9 @@ main(int argc, char **argv)
|
|||
clixon_netconf_error(xerr, "Parsing secondary xml", NULL);
|
||||
goto done;
|
||||
}
|
||||
if ((xi = xpath_first(x1, NULL, "%s", xpath)) == NULL){
|
||||
if (xpath == NULL)
|
||||
xi = x1;
|
||||
else if ((xi = xpath_first(x1, NULL, "%s", xpath)) == NULL){
|
||||
clicon_err(OE_XML, 0, "xpath: %s not found in xi", xpath);
|
||||
goto done;
|
||||
}
|
||||
|
|
@ -239,7 +253,9 @@ main(int argc, char **argv)
|
|||
goto done;
|
||||
}
|
||||
/* Get secondary subtree by xpath */
|
||||
if ((xi = xpath_first(x1, NULL, "%s", xpath)) == NULL){
|
||||
if (xpath == NULL)
|
||||
xi = x1;
|
||||
else if ((xi = xpath_first(x1, NULL, "%s", xpath)) == NULL){
|
||||
clicon_err(OE_XML, 0, "xpath: %s not found in xi", xpath);
|
||||
goto done;
|
||||
}
|
||||
|
|
@ -275,6 +291,8 @@ main(int argc, char **argv)
|
|||
xml_free(x0);
|
||||
if (x1)
|
||||
xml_free(x1);
|
||||
if (xcfg)
|
||||
xml_free(xcfg);
|
||||
if (xerr)
|
||||
xml_free(xerr);
|
||||
if (reason)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue