Inital commit
This commit is contained in:
parent
edc5e091bb
commit
d6e393ea58
145 changed files with 58117 additions and 0 deletions
68
apps/Makefile.in
Normal file
68
apps/Makefile.in
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#
|
||||
# Makefile
|
||||
#
|
||||
# Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
#
|
||||
# This file is part of CLICON.
|
||||
#
|
||||
# CLICON is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# CLICON is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with CLICON; see the file COPYING. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
VPATH = @srcdir@
|
||||
srcdir = @srcdir@
|
||||
top_srcdir = @top_srcdir@
|
||||
CC = @CC@
|
||||
CFLAGS = @CFLAGS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LIBS = @LIBS@
|
||||
|
||||
SHELL = /bin/sh
|
||||
|
||||
SUBDIRS = cli backend dbctrl netconf
|
||||
|
||||
.PHONY: all clean depend install $(SUBDIRS)
|
||||
|
||||
all: $(SUBDIRS)
|
||||
|
||||
depend:
|
||||
for i in $(SUBDIRS); \
|
||||
do (cd $$i; $(MAKE) $(MFLAGS) $@); done
|
||||
|
||||
$(SUBDIRS):
|
||||
(cd $@; $(MAKE) $(MFLAGS) all)
|
||||
|
||||
install-include:
|
||||
for i in $(SUBDIRS); \
|
||||
do (cd $$i ; $(MAKE) $(MFLAGS) $@); done;
|
||||
|
||||
install:
|
||||
for i in $(SUBDIRS); \
|
||||
do (cd $$i; $(MAKE) $(MFLAGS) $@); done
|
||||
|
||||
uninstall:
|
||||
for i in $(SUBDIRS); \
|
||||
do (cd $$i; $(MAKE) $(MFLAGS) $@); done
|
||||
|
||||
clean:
|
||||
for i in $(SUBDIRS); \
|
||||
do (cd $$i; $(MAKE) $(MFLAGS) $@); done
|
||||
|
||||
distclean: clean
|
||||
rm -f Makefile *~ .depend
|
||||
for i in $(SUBDIRS); \
|
||||
do (cd $$i; $(MAKE) $(MFLAGS) $@); done
|
||||
|
||||
tags:
|
||||
find $(srcdir) -name '*.[chyl]' -print | etags -
|
||||
137
apps/backend/Makefile.in
Normal file
137
apps/backend/Makefile.in
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#
|
||||
# Makefile
|
||||
#
|
||||
# Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
#
|
||||
# This file is part of CLICON.
|
||||
#
|
||||
# CLICON is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# CLICON is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with CLICON; see the file COPYING. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
VPATH = @srcdir@
|
||||
srcdir = @srcdir@
|
||||
top_srcdir = @top_srcdir@
|
||||
CC = @CC@
|
||||
CFLAGS = @CFLAGS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
|
||||
prefix = @prefix@
|
||||
exec_prefix = @exec_prefix@
|
||||
bindir = @bindir@
|
||||
libdir = @libdir@
|
||||
sbindir = @sbindir@
|
||||
libexecdir = @libexecdir@
|
||||
localstatedir = @localstatedir@
|
||||
sysconfdir = @sysconfdir@
|
||||
includedir = @includedir@
|
||||
|
||||
SH_SUFFIX = @SH_SUFFIX@
|
||||
CLICON_MAJOR = @CLICON_VERSION_MAJOR@
|
||||
CLICON_MINOR = @CLICON_VERSION_MINOR@
|
||||
|
||||
# Use this clicon lib for linking
|
||||
CLICON_LIB = libclicon.so.$(CLICON_MAJOR).$(CLICON_MINOR)
|
||||
# Location of system plugins
|
||||
CLICON_BACKEND_SYSDIR = $(libdir)/clicon/plugins/backend
|
||||
|
||||
# For dependency. A little strange that we rely on it being built in the src dir
|
||||
# even though it may exist in $(libdir). But the new version may not have been installed yet.
|
||||
LIBDEPS = $(top_srcdir)/lib/src/$(CLICON_LIB)
|
||||
|
||||
LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLICON_LIB) -lpthread
|
||||
CPPFLAGS = @CPPFLAGS@ -fPIC
|
||||
INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
|
||||
|
||||
# Not accessible from plugin
|
||||
APPSRC = backend_main.c backend_socket.c backend_client.c \
|
||||
backend_lock.c backend_commit.c backend_plugin.c
|
||||
|
||||
APPOBJ = $(APPSRC:.c=.o)
|
||||
APPL = clicon_backend
|
||||
|
||||
#SHLIB = clicon_backend
|
||||
MYNAME = clicon_backend
|
||||
MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX)
|
||||
MYLIB = $(MYLIBLINK).$(CLICON_MAJOR).$(CLICON_MINOR)
|
||||
MYLIBSO = $(MYLIBLINK).$(CLICON_MAJOR)
|
||||
|
||||
# Accessible from plugin
|
||||
LIBSRC = clicon_backend_transaction.c clicon_backend_handle.c
|
||||
LIBOBJ = $(LIBSRC:.c=.o)
|
||||
|
||||
all: $(MYLIB) $(APPL) test
|
||||
|
||||
clean:
|
||||
rm -f *.core $(APPL) $(APPOBJ) $(LIBOBJ) $(MYLIB) $(MYLIBSO) $(MYLIBLINK)
|
||||
|
||||
distclean: clean
|
||||
rm -f Makefile *~ .depend
|
||||
|
||||
# Put demon in bin
|
||||
# Put other executables in libexec/
|
||||
# Also create a libexec/ directory for writeable/temporary files.
|
||||
# Put config file in etc/
|
||||
install: install-lib $(APPL)
|
||||
install -d $(DESTDIR)$(sbindir)
|
||||
install $(APPL) $(DESTDIR)$(sbindir)
|
||||
|
||||
install-lib: $(MYLIB)
|
||||
install -d $(DESTDIR)$(libdir)
|
||||
install $(MYLIB) $(DESTDIR)$(libdir)
|
||||
ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclicon_config.so.2
|
||||
ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclicon_config.so
|
||||
install -d $(DESTDIR)$(libdir)/clicon/plugins/backend
|
||||
|
||||
uninstall:
|
||||
rm -f $(sbindir)/$(APPL)
|
||||
rm -f $(libdir)/$(MYLIB)
|
||||
rm -f $(includedir)/clicon/*
|
||||
|
||||
install-include: clicon_backend.h clicon_backend_api.h
|
||||
install -d $(DESTDIR)$(includedir)/clicon
|
||||
install -m 644 $^ $(DESTDIR)$(includedir)/clicon
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .c .o
|
||||
|
||||
.c.o:
|
||||
$(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" -DCLICON_BACKEND_SYSDIR=\"$(CLICON_BACKEND_SYSDIR)\" $(CPPFLAGS) $(CFLAGS) -c $<
|
||||
|
||||
# Just link test programs
|
||||
test.c :
|
||||
echo "main(){}" > $@
|
||||
|
||||
test: test.c $(LIBOBJ)
|
||||
$(CC) $(INCLUDES) $(LDFLAGS) $< $(LIBOBJ) -L. -l:$(MYLIB) $(LIBS) -o $@
|
||||
|
||||
$(APPL) : $(APPOBJ) $(MYLIBLINK) $(LIBDEPS)
|
||||
$(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) -L. -l:$(MYLIB) $(LIBS) -o $@
|
||||
|
||||
$(MYLIB): $(LIBOBJ)
|
||||
$(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ -lc $(LIBOBJ) -Wl,-soname=$(MYLIBSO)
|
||||
|
||||
# link-name is needed for application linking, eg for clicon_cli and clicon_backend
|
||||
$(MYLIBLINK) : $(MYLIB)
|
||||
# ln -sf $(MYLIB) $(MYLIBSO)
|
||||
# ln -sf $(MYLIB) $@
|
||||
|
||||
TAGS:
|
||||
find . -name '*.[chyl]' -print | etags -
|
||||
|
||||
depend:
|
||||
$(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend
|
||||
|
||||
#include .depend
|
||||
|
||||
1066
apps/backend/backend_client.c
Normal file
1066
apps/backend/backend_client.c
Normal file
File diff suppressed because it is too large
Load diff
64
apps/backend/backend_client.h
Normal file
64
apps/backend/backend_client.h
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _BACKEND_CLIENT_H_
|
||||
#define _BACKEND_CLIENT_H_
|
||||
|
||||
/*
|
||||
* Types
|
||||
*/
|
||||
/*
|
||||
* Client entry.
|
||||
* Keep state about every connected client.
|
||||
*/
|
||||
struct client_entry{
|
||||
struct client_entry *ce_next; /* The clients linked list */
|
||||
struct sockaddr ce_addr; /* The clients (UNIX domain) address */
|
||||
int ce_s; /* stream socket to client */
|
||||
int ce_nr; /* Client number (for dbg/tracing) */
|
||||
int ce_stat_in; /* Nr of received msgs from client */
|
||||
int ce_stat_out;/* Nr of sent msgs to client */
|
||||
int ce_pid; /* Process id */
|
||||
int ce_uid; /* User id of calling process */
|
||||
clicon_handle ce_handle; /* clicon config handle (all clients have same?) */
|
||||
struct client_subscription *ce_subscription; /* notification subscriptions */
|
||||
};
|
||||
|
||||
/* Notification subscription info
|
||||
* @see subscription in config_handle.c
|
||||
*/
|
||||
struct client_subscription{
|
||||
struct client_subscription *su_next;
|
||||
int su_s; /* stream socket */
|
||||
enum format_enum su_format; /* format of notification stream */
|
||||
char *su_stream;
|
||||
char *su_filter;
|
||||
};
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int backend_client_rm(clicon_handle h, struct client_entry *ce);
|
||||
int config_snapshot(clicon_handle h, char *dbname, char *dir);
|
||||
|
||||
int from_client(int fd, void *arg);
|
||||
|
||||
#endif /* _BACKEND_CLIENT_H_ */
|
||||
684
apps/backend/backend_commit.c
Normal file
684
apps/backend/backend_commit.c
Normal file
|
|
@ -0,0 +1,684 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <pwd.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "clicon_backend_transaction.h"
|
||||
#include "backend_plugin.h"
|
||||
#include "backend_handle.h"
|
||||
#include "backend_commit.h"
|
||||
#include "backend_client.h"
|
||||
|
||||
/*! Key values are checked for validity independent of user-defined callbacks
|
||||
*
|
||||
* Key values are checked as follows:
|
||||
* 1. If no value and default value defined, add it.
|
||||
* 2. If no value and mandatory flag set in spec, report error.
|
||||
* 3. Validate value versus spec, and report error if no match. Currently only int ranges and
|
||||
* string regexp checked.
|
||||
* See also db_lv_set() where defaults are also filled in. The case here for defaults
|
||||
* are if code comes via XML/NETCONF.
|
||||
* @param yspec Yang spec
|
||||
* @param td Transaction data
|
||||
*/
|
||||
static int
|
||||
generic_validate(yang_spec *yspec,
|
||||
transaction_data_t *td)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *x1;
|
||||
cxobj *x2;
|
||||
int i;
|
||||
yang_stmt *ys;
|
||||
|
||||
/* changed entries */
|
||||
for (i=0; i<td->td_clen; i++){
|
||||
x1 = td->td_scvec[i]; /* source changed */
|
||||
x2 = td->td_tcvec[i]; /* target changed */
|
||||
ys = xml_spec(x1);
|
||||
if (xml_yang_validate(x2, ys) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* deleted entries */
|
||||
for (i=0; i<td->td_dlen; i++){
|
||||
x1 = td->td_dvec[i];
|
||||
ys = xml_spec(x1);
|
||||
if (yang_mandatory(ys)){
|
||||
clicon_err(OE_CFG, 0,"Removed mandatory variable: %s",
|
||||
xml_name(x1));
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
/* added entries */
|
||||
for (i=0; i<td->td_alen; i++){
|
||||
x2 = td->td_avec[i];
|
||||
if (xml_yang_validate(x2, xml_spec(x2)) < 0)
|
||||
goto done;
|
||||
if (xml_apply(x2, CX_ELMNT,
|
||||
(xml_applyfn_t*)xml_yang_validate, NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Do a diff between candidate and running, then start a commit transaction
|
||||
*
|
||||
* The code reverts changes if the commit fails. But if the revert
|
||||
* fails, we just ignore the errors and proceed. Maybe we should
|
||||
* do something more drastic?
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] running The current database. The original backend state
|
||||
* @param[in] candidate: The candidate database. The wanted backend state
|
||||
*/
|
||||
int
|
||||
candidate_commit(clicon_handle h,
|
||||
char *candidate,
|
||||
char *running)
|
||||
{
|
||||
int retval = -1;
|
||||
// int i, j;
|
||||
// int failed = 0;
|
||||
struct stat sb;
|
||||
void *firsterr = NULL;
|
||||
yang_spec *yspec;
|
||||
transaction_data_t *td = NULL;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||
goto done;
|
||||
}
|
||||
/* Sanity checks that databases exists. */
|
||||
if (stat(running, &sb) < 0){
|
||||
clicon_err(OE_DB, errno, "%s", running);
|
||||
goto done;
|
||||
}
|
||||
if (stat(candidate, &sb) < 0){
|
||||
clicon_err(OE_DB, errno, "%s", candidate);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 1. Start transaction */
|
||||
if ((td = transaction_new()) == NULL)
|
||||
goto done;
|
||||
|
||||
/* 2. Parse xml trees */
|
||||
if (xmldb_get(running, "/", yspec, &td->td_src) < 0)
|
||||
goto done;
|
||||
if (xmldb_get(candidate, "/", yspec, &td->td_target) < 0)
|
||||
goto done;
|
||||
|
||||
/* 3. Compute differences */
|
||||
if (xml_diff(yspec,
|
||||
td->td_src,
|
||||
td->td_target,
|
||||
&td->td_dvec, /* removed: only in running */
|
||||
&td->td_dlen,
|
||||
&td->td_avec, /* added: only in candidate */
|
||||
&td->td_alen,
|
||||
&td->td_scvec, /* changed: original values */
|
||||
&td->td_tcvec, /* changed: wanted values */
|
||||
&td->td_clen) < 0)
|
||||
goto done;
|
||||
if (debug)
|
||||
transaction_print(stderr, td);
|
||||
|
||||
/* 4. Call plugin transaction start callbacks */
|
||||
if (plugin_transaction_begin(h, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 5. Make generic validation on all new or changed data. */
|
||||
if (generic_validate(yspec, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 6. Call plugin transaction validate callbacks */
|
||||
if (plugin_transaction_validate(h, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 7. Call plugin transaction complete callbacks */
|
||||
if (plugin_transaction_complete(h, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 7. Call plugin transaction commit callbacks */
|
||||
if (plugin_transaction_commit(h, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 8. Copy running back to candidate in case end functions triggered
|
||||
updates in running */
|
||||
if (file_cp(running, candidate) < 0){
|
||||
/* ignore errors or signal major setback ? */
|
||||
clicon_err(OE_UNIX, errno, "file_cp(running, candidate)");
|
||||
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 9. Call plugin transaction end callbacks */
|
||||
plugin_transaction_end(h, td);
|
||||
|
||||
#ifdef OBSOLETE
|
||||
/* Find the differences between the two databases and store it in df vector. */
|
||||
memset(&df, 0, sizeof(df));
|
||||
if (db_diff(running, candidate,
|
||||
__FUNCTION__,
|
||||
clicon_dbspec_key(h),
|
||||
&df
|
||||
) < 0)
|
||||
goto done;
|
||||
|
||||
if (debug){
|
||||
struct dbdiff_ent *dfe;
|
||||
for (i=0; i<df.df_nr; i++) {
|
||||
dfe = &df.df_ents[i];
|
||||
clicon_debug(1, "%s op:%d key:%s",
|
||||
__FUNCTION__,
|
||||
dfe->dfe_op,
|
||||
cvec_name_get(dfe->dfe_vec1?dfe->dfe_vec1:dfe->dfe_vec2));
|
||||
}
|
||||
}
|
||||
/* 1. Get commit processing to dbdiff vector: one entry per key that changed.
|
||||
changes are registered as if they exist in the 1st(candidate) or
|
||||
2nd(running) dbs.
|
||||
*/
|
||||
if (dbdep_commitvec(h, &df, &nvec, &ddvec) < 0)
|
||||
goto done;
|
||||
|
||||
/* 2. Call transaction_begin hooks */
|
||||
if (plugin_transaction_begin(h) < 0)
|
||||
goto done;
|
||||
|
||||
/* call generic cv_validate() on all new or changed keys. */
|
||||
if (generic_validate(yspec, NULL) < 0)
|
||||
goto done;
|
||||
|
||||
/* user-defined validate callbacks registered in dbdep_validate */
|
||||
// if (validate_db(h, nvec, ddvec, running, candidate) < 0)
|
||||
// goto done;
|
||||
|
||||
/* Call plugin post-commit hooks */
|
||||
if (plugin_transaction_complete(h) < 0)
|
||||
goto done;
|
||||
|
||||
if (clicon_commit_order(h) == 0){
|
||||
for (i=0; i < nvec; i++){ /* revert in opposite order */
|
||||
dd = &ddvec[i];
|
||||
dp = dd->dd_dep; /* op, callback, arg */
|
||||
if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
|
||||
continue;
|
||||
dfe = dd->dd_dbdiff; /* key1/key2/op */
|
||||
op = dbdiff2commit_op(dfe->dfe_op);
|
||||
if (plugin_commit_callback(h,
|
||||
op, /* oper */
|
||||
running, /* db1 */
|
||||
candidate, /* db2 */
|
||||
dd->dd_mkey1, /* key1 */
|
||||
dd->dd_mkey2, /* key2 */
|
||||
dd->dd_dbdiff->dfe_vec1, /* vec1 */
|
||||
dd->dd_dbdiff->dfe_vec2, /* vec2 */
|
||||
dp /* callback */
|
||||
) < 0){
|
||||
firsterr = clicon_err_save(); /* save this error */
|
||||
failed++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!failed)
|
||||
if (file_cp(candidate, running) < 0){ /* Commit here in case cp fails */
|
||||
clicon_err(OE_UNIX, errno, "file_cp");
|
||||
failed++;
|
||||
}
|
||||
/* Failed operation, start error handling: rollback in opposite order */
|
||||
if (failed){
|
||||
for (j=i-1; j>=0; j--){ /* revert in opposite order */
|
||||
dd = &ddvec[j];
|
||||
dp = dd->dd_dep; /* op, callback, arg */
|
||||
if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
|
||||
continue;
|
||||
dfe = dd->dd_dbdiff; /* key1/key2/op */
|
||||
op = dbdiff2commit_op(dfe->dfe_op);
|
||||
switch (op){ /* reverse operation */
|
||||
case CO_ADD:
|
||||
op = CO_DELETE;
|
||||
break;
|
||||
case CO_DELETE:
|
||||
op = CO_ADD;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (plugin_commit_callback(h,
|
||||
op, /* oper */
|
||||
candidate, /* db1 */
|
||||
running, /* db2 */
|
||||
dd->dd_mkey2, /* key1 */
|
||||
dd->dd_mkey1, /* key2 */
|
||||
dd->dd_dbdiff->dfe_vec2, /* vec1 */
|
||||
dd->dd_dbdiff->dfe_vec1, /* vec2 */
|
||||
dp /* callback */
|
||||
) < 0){
|
||||
/* ignore errors or signal major setback ? */
|
||||
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
goto done;
|
||||
} /* error handling */
|
||||
}
|
||||
else { /* commit_order == 1 or 2 */
|
||||
/* Now follows commit rules in order.
|
||||
* 4. For all keys that are not in candidate but in running, delete key
|
||||
* in reverse prio order
|
||||
*/
|
||||
for (i = nvec-1; i >= 0; i--){
|
||||
dd = &ddvec[i];
|
||||
dp = dd->dd_dep; /* op, callback, arg */
|
||||
if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
|
||||
continue;
|
||||
dfe = dd->dd_dbdiff; /* key1/key2/op */
|
||||
op = dbdiff2commit_op(dfe->dfe_op);
|
||||
/* original mode 2 where CHANGE=DEL/ADD */
|
||||
if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
|
||||
op = CO_DELETE;
|
||||
if (op != CO_DELETE)
|
||||
continue;
|
||||
if (plugin_commit_callback(h,
|
||||
op, /* oper */
|
||||
running, /* db1 */
|
||||
candidate, /* db2 */
|
||||
dd->dd_mkey1, /* key1 */
|
||||
dd->dd_mkey2, /* key2 */
|
||||
dd->dd_dbdiff->dfe_vec1, /* vec1 */
|
||||
dd->dd_dbdiff->dfe_vec2, /* vec2 */
|
||||
dp /* callback */
|
||||
) < 0){
|
||||
firsterr = clicon_err_save(); /* save this error */
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* 5. Failed deletion, add the key value back to running */
|
||||
if (i >= 0){ /* failed */
|
||||
for (j=i+1; j<nvec; j++){ /* revert in opposite order */
|
||||
dd = &ddvec[j];
|
||||
dp = dd->dd_dep; /* op, callback, arg */
|
||||
if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
|
||||
continue;
|
||||
dfe = dd->dd_dbdiff; /* key1/key2/op */
|
||||
op = dbdiff2commit_op(dfe->dfe_op);
|
||||
/* original mode 2 where CHANGE=DEL/ADD */
|
||||
if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
|
||||
op = CO_DELETE;
|
||||
if (op != CO_DELETE)
|
||||
continue;
|
||||
if (plugin_commit_callback(h,
|
||||
op, /* oper */
|
||||
candidate, /* db1 */
|
||||
running, /* db2 */
|
||||
dd->dd_mkey2, /* key1 */
|
||||
dd->dd_mkey1, /* key2 */
|
||||
dd->dd_dbdiff->dfe_vec2, /* vec1 */
|
||||
dd->dd_dbdiff->dfe_vec1, /* vec2 */
|
||||
dp /* callback */
|
||||
) < 0){
|
||||
/* ignore errors or signal major setback ? */
|
||||
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
/*
|
||||
* 6. For all added or changed keys
|
||||
*/
|
||||
for (i=0; i < nvec; i++){
|
||||
dd = &ddvec[i];
|
||||
dp = dd->dd_dep; /* op, callback, arg */
|
||||
if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
|
||||
continue;
|
||||
dfe = dd->dd_dbdiff; /* key1/key2/op */
|
||||
op = dbdiff2commit_op(dfe->dfe_op);
|
||||
if (op != CO_CHANGE && op != CO_ADD)
|
||||
continue;
|
||||
/* original mode 2 where CHANGE=DEL/ADD */
|
||||
if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
|
||||
op = CO_ADD;
|
||||
if (plugin_commit_callback(h,
|
||||
op, /* oper */
|
||||
running, /* db1 */
|
||||
candidate, /* db2 */
|
||||
dd->dd_mkey1, /* key1 */
|
||||
dd->dd_mkey2, /* key2 */
|
||||
dd->dd_dbdiff->dfe_vec1, /* vec1 */
|
||||
dd->dd_dbdiff->dfe_vec2, /* vec2 */
|
||||
dp /* callback */
|
||||
) < 0){
|
||||
firsterr = clicon_err_save(); /* save this error */
|
||||
failed++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!failed) /* Commit here in case cp fails */
|
||||
if (file_cp(candidate, running) < 0){
|
||||
clicon_err(OE_UNIX, errno, "file_cp(candidate; running)");
|
||||
failed++;
|
||||
}
|
||||
/* 10. Failed setting keys in running, first remove the keys set */
|
||||
if (failed){ /* failed */
|
||||
for (j=i-1; j>=0; j--){ /* revert in opposite order */
|
||||
dd = &ddvec[j];
|
||||
dp = dd->dd_dep; /* op, callback, arg */
|
||||
if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
|
||||
continue;
|
||||
dfe = dd->dd_dbdiff; /* key1/key2/op */
|
||||
op = dbdiff2commit_op(dfe->dfe_op);
|
||||
if (op != CO_CHANGE && op != CO_ADD)
|
||||
continue;
|
||||
/* original mode 2 where CHANGE=DEL/ADD */
|
||||
if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
|
||||
op = CO_ADD;
|
||||
if (op == CO_ADD) /* reverse op */
|
||||
op = CO_DELETE;
|
||||
if (plugin_commit_callback(h,
|
||||
op, /* oper */
|
||||
candidate, /* db1 */
|
||||
running, /* db2 */
|
||||
dd->dd_mkey2, /* key1 */
|
||||
dd->dd_mkey1, /* key2 */
|
||||
dd->dd_dbdiff->dfe_vec2, /* vec1 */
|
||||
dd->dd_dbdiff->dfe_vec1, /* vec2 */
|
||||
dp /* callback */
|
||||
) < 0){
|
||||
/* ignore errors or signal major setback ? */
|
||||
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (j=0; j < nvec; j++){ /* revert in opposite order */
|
||||
dd = &ddvec[j];
|
||||
dp = dd->dd_dep; /* op, callback, arg */
|
||||
if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
|
||||
continue;
|
||||
dfe = dd->dd_dbdiff; /* key1/key2/op */
|
||||
op = dbdiff2commit_op(dfe->dfe_op);
|
||||
/* original mode 2 where CHANGE=DEL/ADD */
|
||||
if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
|
||||
op = CO_DELETE;
|
||||
if (op != CO_DELETE)
|
||||
continue;
|
||||
op = CO_ADD;
|
||||
if (plugin_commit_callback(h,
|
||||
op, /* oper */
|
||||
candidate, /* db1 */
|
||||
running, /* db2 */
|
||||
dd->dd_mkey2, /* key1 */
|
||||
dd->dd_mkey1, /* key2 */
|
||||
dd->dd_dbdiff->dfe_vec2, /* vec1 */
|
||||
dd->dd_dbdiff->dfe_vec1, /* vec2 */
|
||||
dp /* callback */
|
||||
) < 0){
|
||||
/* ignore errors or signal major setback ? */
|
||||
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
} /* commit_order */
|
||||
#endif /* OBSOLETE */
|
||||
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
/* In case of failure, call plugin transaction termination callbacks */
|
||||
if (retval < 0 && td)
|
||||
plugin_transaction_abort(h, td);
|
||||
if (td)
|
||||
transaction_free(td);
|
||||
if (firsterr)
|
||||
clicon_err_restore(firsterr);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Do a diff between candidate and running, then start a validate transaction
|
||||
*
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] running The current database. The original backend state
|
||||
* @param[in] candidate: The candidate database. The wanted backend state
|
||||
*/
|
||||
int
|
||||
candidate_validate(clicon_handle h,
|
||||
char *candidate,
|
||||
char *running)
|
||||
{
|
||||
int retval = -1;
|
||||
struct stat sb;
|
||||
yang_spec *yspec;
|
||||
transaction_data_t *td = NULL;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||
goto done;
|
||||
}
|
||||
/* Sanity checks that databases exists. */
|
||||
if (stat(running, &sb) < 0){
|
||||
clicon_err(OE_DB, errno, "%s", running);
|
||||
goto done;
|
||||
}
|
||||
if (stat(candidate, &sb) < 0){
|
||||
clicon_err(OE_DB, errno, "%s", candidate);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 1. Start transaction */
|
||||
if ((td = transaction_new()) == NULL)
|
||||
goto done;
|
||||
|
||||
/* 2. Parse xml trees */
|
||||
if (xmldb_get(running, "/", yspec, &td->td_src) < 0)
|
||||
goto done;
|
||||
if (xmldb_get(candidate, "/", yspec, &td->td_target) < 0)
|
||||
goto done;
|
||||
|
||||
/* 3. Compute differences */
|
||||
if (xml_diff(yspec,
|
||||
td->td_src,
|
||||
td->td_target,
|
||||
&td->td_dvec, /* removed: only in running */
|
||||
&td->td_dlen,
|
||||
&td->td_avec, /* added: only in candidate */
|
||||
&td->td_alen,
|
||||
&td->td_scvec, /* changed: original values */
|
||||
&td->td_tcvec, /* changed: wanted values */
|
||||
&td->td_clen) < 0)
|
||||
goto done;
|
||||
|
||||
if (debug)
|
||||
transaction_print(stderr, td);
|
||||
|
||||
/* 4. Call plugin start transaction callbacks */
|
||||
if (plugin_transaction_begin(h, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 5. Make generic validation on all new or changed data. */
|
||||
if (generic_validate(yspec, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 6. Call plugin validate transaction callbacks */
|
||||
if (plugin_transaction_validate(h, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 7. Call plugin complete transaction callbacks */
|
||||
if (plugin_transaction_complete(h, td) < 0)
|
||||
goto done;
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
/* In case of failure, call plugin transaction termination callbacks */
|
||||
if (retval < 0 && td)
|
||||
plugin_transaction_abort(h, td);
|
||||
if (td)
|
||||
transaction_free(td);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Handle an incoming commit message from a client.
|
||||
* XXX: If commit succeeds and snapshot/startup fails, we have strange state:
|
||||
* the commit has succeeded but an error message is returned.
|
||||
*/
|
||||
int
|
||||
from_client_commit(clicon_handle h,
|
||||
int s,
|
||||
struct clicon_msg *msg,
|
||||
const char *label)
|
||||
{
|
||||
int retval = -1;
|
||||
char *candidate;
|
||||
char *running;
|
||||
uint32_t snapshot;
|
||||
uint32_t startup;
|
||||
char *snapshot_0;
|
||||
char *archive_dir;
|
||||
char *startup_config;
|
||||
|
||||
if (clicon_msg_commit_decode(msg, &candidate, &running,
|
||||
&snapshot, &startup, label) < 0)
|
||||
goto err;
|
||||
|
||||
if (candidate_commit(h, candidate, running) < 0){
|
||||
clicon_debug(1, "Commit %s failed", candidate);
|
||||
retval = 0; /* We ignore errors from commit, but maybe
|
||||
we should fail on fatal errors? */
|
||||
goto err;
|
||||
}
|
||||
clicon_debug(1, "Commit %s", candidate);
|
||||
if (snapshot){
|
||||
if ((archive_dir = clicon_archive_dir(h)) == NULL){
|
||||
clicon_err(OE_PLUGIN, 0, "snapshot set and clicon_archive_dir not defined");
|
||||
goto err;
|
||||
}
|
||||
if (config_snapshot(h, running, archive_dir) < 0)
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (startup){
|
||||
if ((archive_dir = clicon_archive_dir(h)) == NULL){
|
||||
clicon_err(OE_PLUGIN, 0, "startup set but clicon_archive_dir not defined");
|
||||
goto err;
|
||||
}
|
||||
if ((startup_config = clicon_startup_config(h)) == NULL){
|
||||
clicon_err(OE_PLUGIN, 0, "startup set but startup_config not defined");
|
||||
goto err;
|
||||
}
|
||||
snapshot_0 = chunk_sprintf(__FUNCTION__, "%s/0", archive_dir);
|
||||
if (file_cp(snapshot_0, startup_config) < 0){
|
||||
clicon_err(OE_PROTO, errno, "%s: Error when creating startup",
|
||||
__FUNCTION__);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
if (send_msg_ok(s) < 0)
|
||||
goto done;
|
||||
goto done;
|
||||
err:
|
||||
/* XXX: more elaborate errstring? */
|
||||
if (send_msg_err(s, clicon_errno, clicon_suberrno, "%s", clicon_err_reason) < 0)
|
||||
retval = -1;
|
||||
done:
|
||||
unchunk_group(__FUNCTION__);
|
||||
|
||||
return retval; /* may be zero if we ignoring errors from commit */
|
||||
} /* from_client_commit */
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Call backend plugin
|
||||
*/
|
||||
int
|
||||
from_client_validate(clicon_handle h,
|
||||
int s,
|
||||
struct clicon_msg *msg,
|
||||
const char *label)
|
||||
{
|
||||
char *dbname;
|
||||
char *running_db;
|
||||
int retval = -1;
|
||||
|
||||
if (clicon_msg_validate_decode(msg, &dbname, label) < 0){
|
||||
send_msg_err(s, clicon_errno, clicon_suberrno,
|
||||
clicon_err_reason);
|
||||
goto err;
|
||||
}
|
||||
|
||||
clicon_debug(1, "Validate %s", dbname);
|
||||
if ((running_db = clicon_running_db(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "running db not set");
|
||||
goto err;
|
||||
}
|
||||
if (candidate_validate(h, dbname, running_db) < 0){
|
||||
clicon_debug(1, "Validate %s failed", dbname);
|
||||
retval = 0; /* We ignore errors from commit, but maybe
|
||||
we should fail on fatal errors? */
|
||||
goto err;
|
||||
}
|
||||
retval = 0;
|
||||
if (send_msg_ok(s) < 0)
|
||||
goto done;
|
||||
goto done;
|
||||
err:
|
||||
/* XXX: more elaborate errstring? */
|
||||
if (send_msg_err(s, clicon_errno, clicon_suberrno, "%s", clicon_err_reason) < 0)
|
||||
retval = -1;
|
||||
done:
|
||||
unchunk_group(__FUNCTION__);
|
||||
return retval;
|
||||
} /* from_client_validate */
|
||||
34
apps/backend/backend_commit.h
Normal file
34
apps/backend/backend_commit.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _BACKEND_COMMIT_H_
|
||||
#define _BACKEND_COMMIT_H_
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int from_client_validate(clicon_handle h, int s, struct clicon_msg *msg, const char *label);
|
||||
int from_client_commit(clicon_handle h, int s, struct clicon_msg *msg, const char *label);
|
||||
int candidate_commit(clicon_handle h, char *candidate, char *running);
|
||||
|
||||
#endif /* _BACKEND_COMMIT_H_ */
|
||||
43
apps/backend/backend_handle.h
Normal file
43
apps/backend/backend_handle.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _BACKEND_HANDLE_H_
|
||||
#define _BACKEND_HANDLE_H_
|
||||
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
* not exported.
|
||||
*/
|
||||
/* backend handles */
|
||||
clicon_handle backend_handle_init(void);
|
||||
|
||||
int backend_handle_exit(clicon_handle h);
|
||||
|
||||
struct client_entry *backend_client_add(clicon_handle h, struct sockaddr *addr);
|
||||
|
||||
struct client_entry *backend_client_list(clicon_handle h);
|
||||
|
||||
int backend_client_delete(clicon_handle h, struct client_entry *ce);
|
||||
|
||||
#endif /* _BACKEND_HANDLE_H_ */
|
||||
95
apps/backend/backend_lock.c
Normal file
95
apps/backend/backend_lock.c
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "backend_lock.h"
|
||||
|
||||
/*
|
||||
* Easy way out: store an integer for candidate db which contains
|
||||
* the session-id of the client holding the lock.
|
||||
* more general: any database
|
||||
* we dont make any sanity check on who is locking.
|
||||
*/
|
||||
static int _db_locked = 0;
|
||||
|
||||
/*
|
||||
* db_lock
|
||||
*/
|
||||
int
|
||||
db_lock(clicon_handle h, int id)
|
||||
{
|
||||
_db_locked = id;
|
||||
clicon_debug(1, "%s: lock db by %u", __FUNCTION__, id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* db_unlock
|
||||
*/
|
||||
int
|
||||
db_unlock(clicon_handle h)
|
||||
{
|
||||
if (!_db_locked )
|
||||
return 0;
|
||||
_db_locked = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* db_islocked
|
||||
* returns id of locker
|
||||
*/
|
||||
int
|
||||
db_islocked(clicon_handle h)
|
||||
{
|
||||
return _db_locked;
|
||||
}
|
||||
|
||||
37
apps/backend/backend_lock.h
Normal file
37
apps/backend/backend_lock.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* Database logical lock functions.
|
||||
* Only one lock (candidate_db)
|
||||
* Not persistent (needs another db)
|
||||
*/
|
||||
|
||||
#ifndef _BACKEND_LOCK_H_
|
||||
#define _BACKEND_LOCK_H_
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int db_lock(clicon_handle h, int id);
|
||||
int db_unlock(clicon_handle h);
|
||||
int db_islocked(clicon_handle h);
|
||||
|
||||
#endif /* _BACKEND_LOCK_H_ */
|
||||
617
apps/backend/backend_main.c
Normal file
617
apps/backend/backend_main.c
Normal file
|
|
@ -0,0 +1,617 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <syslog.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <grp.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "clicon_backend_handle.h"
|
||||
#include "backend_socket.h"
|
||||
#include "backend_client.h"
|
||||
#include "backend_commit.h"
|
||||
#include "backend_plugin.h"
|
||||
#include "backend_handle.h"
|
||||
|
||||
/* Command line options to be passed to getopt(3) */
|
||||
#define BACKEND_OPTS "hD:f:d:Fzu:P:1IRCc::rg:pt"
|
||||
|
||||
/* Cannot use h after this */
|
||||
static int
|
||||
config_terminate(clicon_handle h)
|
||||
{
|
||||
yang_spec *yspec;
|
||||
char *pidfile = clicon_backend_pidfile(h);
|
||||
char *sockpath = clicon_sock(h);
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
if ((yspec = clicon_dbspec_yang(h)) != NULL)
|
||||
yspec_free(yspec);
|
||||
plugin_finish(h);
|
||||
if (pidfile)
|
||||
unlink(pidfile);
|
||||
if (sockpath)
|
||||
unlink(sockpath);
|
||||
backend_handle_exit(h); /* Cannot use h after this */
|
||||
clicon_log_register_callback(NULL, NULL);
|
||||
clicon_debug(1, "%s done", __FUNCTION__);
|
||||
if (debug)
|
||||
chunk_check(stderr, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
config_sig_term
|
||||
Unlink pidfile and quit
|
||||
*/
|
||||
static void
|
||||
config_sig_term(int arg)
|
||||
{
|
||||
static int i=0;
|
||||
if (i++ == 0)
|
||||
clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d",
|
||||
__PROGRAM__, __FUNCTION__, getpid(), arg);
|
||||
clicon_exit_set(); /* checked in event_loop() */
|
||||
}
|
||||
|
||||
/*
|
||||
* usage
|
||||
*/
|
||||
static void
|
||||
usage(char *argv0, clicon_handle h)
|
||||
{
|
||||
char *plgdir = clicon_backend_dir(h);
|
||||
char *confsock = clicon_sock(h);
|
||||
char *confpid = clicon_backend_pidfile(h);
|
||||
char *startup = clicon_startup_config(h);
|
||||
char *group = clicon_sock_group(h);
|
||||
|
||||
fprintf(stderr, "usage:%s\n"
|
||||
"where options are\n"
|
||||
" -h\t\tHelp\n"
|
||||
" -D <level>\tdebug\n"
|
||||
" -f <file>\tCLICON config file (mandatory)\n"
|
||||
" -d <dir>\tSpecify backend plugin directory (default: %s)\n"
|
||||
" -z\t\tKill other config daemon and exit\n"
|
||||
" -F\t\tforeground\n"
|
||||
" -1\t\tonce (dont wait for events)\n"
|
||||
" -u <path>\tconfig UNIX domain path / ip address (default: %s)\n"
|
||||
" -P <file>\tPid filename (default: %s)\n"
|
||||
" -I\t\tInitialize running state database\n"
|
||||
" -R\t\tCall plugin_reset() in plugins to reset system state in running db (use with -I)\n"
|
||||
" -C\t\tCall plugin_reset() in plugins to reset system state in candidate db (use with -I)\n"
|
||||
" -c [<file>]\tLoad specified application config. Default is\n"
|
||||
" \t\"CLICON_STARTUP_CONFIG\" = %s\n"
|
||||
" -r\t\tReload running database\n"
|
||||
" -p \t\tPrint database yang specification\n"
|
||||
" -t \t\tPrint alternate spec translation (eg if YANG print KEY, if KEY print YANG)\n"
|
||||
" -g <group>\tClient membership required to this group (default: %s)\n",
|
||||
argv0,
|
||||
plgdir ? plgdir : "none",
|
||||
confsock ? confsock : "none",
|
||||
confpid ? confpid : "none",
|
||||
startup ? startup : "none",
|
||||
group ? group : "none"
|
||||
);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static int
|
||||
rundb_init(clicon_handle h, char *running_db)
|
||||
{
|
||||
if (unlink(running_db) != 0 && errno != ENOENT) {
|
||||
clicon_err(OE_UNIX, errno, "unlink");
|
||||
return -1;
|
||||
}
|
||||
if (db_init(running_db) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Initialize running-config from file application configuration
|
||||
*
|
||||
* @param[in] h clicon handle
|
||||
* @param[in] app_config_file clicon application configuration file
|
||||
* @param[in] running_db Name of running db
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error. clicon_err set
|
||||
*/
|
||||
static int
|
||||
rundb_main(clicon_handle h,
|
||||
char *app_config_file,
|
||||
char *running_db)
|
||||
{
|
||||
char *tmp = NULL;
|
||||
int retval = -1;
|
||||
int fd = -1;
|
||||
yang_spec *yspec;
|
||||
cxobj *xt = NULL;
|
||||
cxobj *xn;
|
||||
|
||||
if ((tmp = clicon_tmpfile(__FUNCTION__)) == NULL)
|
||||
goto done;
|
||||
if (file_cp(running_db, tmp) < 0){
|
||||
clicon_err(OE_UNIX, errno, "file copy");
|
||||
goto done;
|
||||
}
|
||||
if ((fd = open(app_config_file, O_RDONLY)) < 0){
|
||||
clicon_err(OE_UNIX, errno, "open(%s)", app_config_file);
|
||||
goto done;
|
||||
}
|
||||
if (clicon_xml_parse_file(fd, &xt, "</clicon>") < 0)
|
||||
goto done;
|
||||
yspec = clicon_dbspec_yang(h);
|
||||
if ((xn = xml_child_i(xt, 0)) != NULL)
|
||||
if (xmldb_put(tmp, xn, yspec, OP_MERGE) < 0)
|
||||
goto done;
|
||||
if (candidate_commit(h, tmp, running_db) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (tmp)
|
||||
unlink(tmp);
|
||||
if (xt)
|
||||
xml_free(xt);
|
||||
if (fd != -1)
|
||||
close(fd);
|
||||
unchunk_group(__FUNCTION__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
candb_reset(clicon_handle h, char *running_db)
|
||||
{
|
||||
int retval = -1;
|
||||
char *tmp = NULL;
|
||||
|
||||
if ((tmp = clicon_tmpfile(__FUNCTION__)) == NULL)
|
||||
goto done;
|
||||
if (file_cp(running_db, tmp) < 0){
|
||||
clicon_err(OE_UNIX, errno, "file copy");
|
||||
goto done;
|
||||
}
|
||||
/* Request plugins to reset system state, eg initiate running from system
|
||||
* -R
|
||||
*/
|
||||
if (plugin_reset_state(h, tmp) < 0)
|
||||
goto done;
|
||||
if (candidate_commit(h, tmp, running_db) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (tmp)
|
||||
unlink(tmp);
|
||||
unchunk_group(__FUNCTION__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Create backend server socket and register callback
|
||||
*/
|
||||
static int
|
||||
server_socket(clicon_handle h)
|
||||
{
|
||||
int ss;
|
||||
|
||||
/* Open control socket */
|
||||
if ((ss = config_socket_init(h)) < 0)
|
||||
return -1;
|
||||
/* ss is a server socket that the clients connect to. The callback
|
||||
therefore accepts clients on ss */
|
||||
if (event_reg_fd(ss, config_accept_client, h, "server socket") < 0) {
|
||||
close(ss);
|
||||
return -1;
|
||||
}
|
||||
return ss;
|
||||
}
|
||||
|
||||
/*! Callback for CLICON log events
|
||||
* If you make a subscription to CLICON stream, this function is called for every
|
||||
* log event.
|
||||
*/
|
||||
static int
|
||||
config_log_cb(int level, char *msg, void *arg)
|
||||
{
|
||||
size_t n;
|
||||
char *ptr;
|
||||
char *nptr;
|
||||
char *newmsg = NULL;
|
||||
int retval = -1;
|
||||
|
||||
/* backend_notify() will go through all clients and see if any has registered "CLICON",
|
||||
and if so make a clicon_proto notify message to those clients. */
|
||||
|
||||
|
||||
/* Sanitize '%' into "%%" to prevent segvfaults in vsnprintf later.
|
||||
At this stage all formatting is already done */
|
||||
n = 0;
|
||||
for(ptr=msg; *ptr; ptr++)
|
||||
if (*ptr == '%')
|
||||
n++;
|
||||
if ((newmsg = malloc(strlen(msg) + n + 1)) == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "malloc");
|
||||
return -1;
|
||||
}
|
||||
for(ptr=msg, nptr=newmsg; *ptr; ptr++) {
|
||||
*nptr++ = *ptr;
|
||||
if (*ptr == '%')
|
||||
*nptr++ = '%';
|
||||
}
|
||||
|
||||
retval = backend_notify(arg, "CLICON", level, newmsg);
|
||||
free(newmsg);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char c;
|
||||
int zap;
|
||||
int foreground;
|
||||
int once;
|
||||
int init_rundb;
|
||||
char *running_db;
|
||||
char *candidate_db;
|
||||
int reload_running;
|
||||
int reset_state_running;
|
||||
int reset_state_candidate;
|
||||
char *app_config_file = NULL;
|
||||
char *config_group;
|
||||
char *argv0 = argv[0];
|
||||
char *tmp;
|
||||
struct stat st;
|
||||
clicon_handle h;
|
||||
int help = 0;
|
||||
int printspec = 0;
|
||||
int printalt = 0;
|
||||
int pid;
|
||||
char *pidfile;
|
||||
char *sock;
|
||||
int sockfamily;
|
||||
|
||||
/* In the startup, logs to stderr & syslog and debug flag set later */
|
||||
clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG);
|
||||
/* Initiate CLICON handle */
|
||||
if ((h = backend_handle_init()) == NULL)
|
||||
return -1;
|
||||
if (config_plugin_init(h) != 0)
|
||||
return -1;
|
||||
foreground = 0;
|
||||
once = 0;
|
||||
zap = 0;
|
||||
init_rundb = 0;
|
||||
reload_running = 0;
|
||||
reset_state_running = 0;
|
||||
reset_state_candidate = 0;
|
||||
|
||||
/*
|
||||
* Command-line options for help, debug, and config-file
|
||||
*/
|
||||
opterr = 0;
|
||||
optind = 1;
|
||||
while ((c = getopt(argc, argv, BACKEND_OPTS)) != -1)
|
||||
switch (c) {
|
||||
case '?':
|
||||
case 'h':
|
||||
/* Defer the call to usage() to later. Reason is that for helpful
|
||||
text messages, default dirs, etc, are not set until later.
|
||||
But this measn that we need to check if 'help' is set before
|
||||
exiting, and then call usage() before exit.
|
||||
*/
|
||||
help = 1;
|
||||
break;
|
||||
case 'D' : /* debug */
|
||||
if (sscanf(optarg, "%d", &debug) != 1)
|
||||
usage(argv[0], h);
|
||||
break;
|
||||
case 'f': /* config file */
|
||||
if (!strlen(optarg))
|
||||
usage(argv[0], h);
|
||||
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Syslogs also to stderr, but later turn stderr off in daemon mode.
|
||||
* error only to syslog. debug to syslog
|
||||
*/
|
||||
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG);
|
||||
clicon_debug_init(debug, NULL);
|
||||
|
||||
/* Find and read configfile */
|
||||
if (clicon_options_main(h) < 0){
|
||||
if (help)
|
||||
usage(argv[0], h);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Now run through the operational args */
|
||||
opterr = 1;
|
||||
optind = 1;
|
||||
while ((c = getopt(argc, argv, BACKEND_OPTS)) != -1)
|
||||
switch (c) {
|
||||
case 'D' : /* debug */
|
||||
case 'f': /* config file */
|
||||
break; /* see above */
|
||||
case 'd': /* Plugin directory */
|
||||
if (!strlen(optarg))
|
||||
usage(argv[0], h);
|
||||
clicon_option_str_set(h, "CLICON_BACKEND_DIR", optarg);
|
||||
break;
|
||||
case 'F' : /* foreground */
|
||||
foreground = 1;
|
||||
break;
|
||||
case '1' : /* Quit after reading database once - dont wait for events */
|
||||
once = 1;
|
||||
break;
|
||||
case 'z': /* Zap other process */
|
||||
zap++;
|
||||
break;
|
||||
case 'u': /* config unix domain path / ip address */
|
||||
if (!strlen(optarg))
|
||||
usage(argv[0], h);
|
||||
clicon_option_str_set(h, "CLICON_SOCK", optarg);
|
||||
break;
|
||||
case 'P': /* pidfile */
|
||||
clicon_option_str_set(h, "CLICON_BACKEND_PIDFILE", optarg);
|
||||
break;
|
||||
case 'I': /* Initiate running db */
|
||||
init_rundb++;
|
||||
break;
|
||||
case 'R': /* Reset state directly into running */
|
||||
reset_state_running++;
|
||||
break;
|
||||
case 'C': /* Reset state into candidate and then commit it */
|
||||
reset_state_candidate++;
|
||||
break;
|
||||
case 'c': /* Load application config */
|
||||
app_config_file = optarg ? optarg : clicon_startup_config(h);
|
||||
if (app_config_file == NULL) {
|
||||
fprintf(stderr, "Option \"CLICON_STARTUP_CONFIG\" not set\n");
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case 'r': /* Reload running */
|
||||
reload_running++;
|
||||
break;
|
||||
case 'g': /* config socket group */
|
||||
clicon_option_str_set(h, "CLICON_SOCK_GROUP", optarg);
|
||||
break;
|
||||
case 'p' : /* Print spec */
|
||||
printspec++;
|
||||
break;
|
||||
case 't' : /* Print alternative dbspec format (eg if YANG, print KEY) */
|
||||
printalt++;
|
||||
break;
|
||||
default:
|
||||
usage(argv[0], h);
|
||||
break;
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
/* Defer: Wait to the last minute to print help message */
|
||||
if (help)
|
||||
usage(argv[0], h);
|
||||
|
||||
/* Check pid-file, if zap kil the old daemon, else return here */
|
||||
if ((pidfile = clicon_backend_pidfile(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "pidfile not set");
|
||||
goto done;
|
||||
}
|
||||
sockfamily = clicon_sock_family(h);
|
||||
if ((sock = clicon_sock(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "sock not set");
|
||||
goto done;
|
||||
}
|
||||
if (pidfile_get(pidfile, &pid) < 0)
|
||||
return -1;
|
||||
if (zap){
|
||||
if (pid && pidfile_zapold(pid) < 0)
|
||||
return -1;
|
||||
if (lstat(pidfile, &st) == 0)
|
||||
unlink(pidfile);
|
||||
if (sockfamily==AF_UNIX && lstat(sock, &st) == 0)
|
||||
unlink(sock);
|
||||
exit(0);
|
||||
}
|
||||
else
|
||||
if (pid){
|
||||
clicon_err(OE_DEMON, 0, "Daemon already running with pid %d\n(Try killing it with %s -z)",
|
||||
pid, argv0);
|
||||
return -1; /* goto done deletes pidfile */
|
||||
}
|
||||
|
||||
/* After this point we can goto done on error
|
||||
* Here there is either no old process or we have killed it,..
|
||||
*/
|
||||
if (lstat(pidfile, &st) == 0)
|
||||
unlink(pidfile);
|
||||
if (sockfamily==AF_UNIX && lstat(sock, &st) == 0)
|
||||
unlink(sock);
|
||||
|
||||
/* Sanity check: config group exists */
|
||||
if ((config_group = clicon_sock_group(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "clicon_sock_group option not set");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (group_name2gid(config_group, NULL) < 0){
|
||||
clicon_log(LOG_ERR, "'%s' does not seem to be a valid user group.\n"
|
||||
"The config demon requires a valid group to create a server UNIX socket\n"
|
||||
"Define a valid CLICON_SOCK_GROUP in %s or via the -g option\n"
|
||||
"or create the group and add the user to it. On linux for example:"
|
||||
" sudo groupadd %s\n"
|
||||
" sudo usermod -a -G %s user\n",
|
||||
config_group, clicon_configfile(h), config_group, config_group);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Parse db spec file */
|
||||
if (yang_spec_main(h, stdout, printspec) < 0)
|
||||
goto done;
|
||||
|
||||
if ((running_db = clicon_running_db(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "running db not set");
|
||||
goto done;
|
||||
}
|
||||
if ((candidate_db = clicon_candidate_db(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "candidate db not set");
|
||||
goto done;
|
||||
}
|
||||
/* If running exists and reload_running set, make a copy to candidate */
|
||||
if (reload_running){
|
||||
if (stat(running_db, &st) && errno == ENOENT){
|
||||
clicon_log(LOG_NOTICE, "%s: -r (reload running) option given but no running_db found, proceeding without", __PROGRAM__);
|
||||
reload_running = 0; /* void it, so we dont commit candidate below */
|
||||
}
|
||||
else
|
||||
if (file_cp(running_db, candidate_db) < 0){
|
||||
clicon_err(OE_UNIX, errno, "FATAL: file_cp");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
/* Init running db
|
||||
* -I
|
||||
*/
|
||||
if (init_rundb || (stat(running_db, &st) && errno == ENOENT))
|
||||
if (rundb_init(h, running_db) < 0)
|
||||
goto done;
|
||||
|
||||
/* Initialize plugins
|
||||
(also calls plugin_init() and plugin_start(argc,argv) in each plugin */
|
||||
if (plugin_initiate(h) != 0)
|
||||
goto done;
|
||||
|
||||
if (reset_state_candidate){
|
||||
if (candb_reset(h, running_db) < 0)
|
||||
goto done;
|
||||
}
|
||||
else
|
||||
if (reset_state_running){
|
||||
if (plugin_reset_state(h, running_db) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Call plugin_start */
|
||||
tmp = *(argv-1);
|
||||
*(argv-1) = argv0;
|
||||
if (plugin_start_hooks(h, argc+1, argv-1) < 0)
|
||||
goto done;
|
||||
*(argv-1) = tmp;
|
||||
|
||||
if (reload_running){
|
||||
if (candidate_commit(h, candidate_db, running_db) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Have we specified a config file to load? eg
|
||||
-c <file>
|
||||
-r replace running (obsolete)
|
||||
*/
|
||||
if (app_config_file)
|
||||
if (rundb_main(h, app_config_file, running_db) < 0)
|
||||
goto done;
|
||||
|
||||
/* Initiate the shared candidate. Maybe we should not do this? */
|
||||
if (file_cp(running_db, candidate_db) < 0){
|
||||
clicon_err(OE_UNIX, errno, "FATAL: file_cp");
|
||||
goto done;
|
||||
}
|
||||
/* XXX Hack for now. Change mode so that we all can write. Security issue*/
|
||||
chmod(candidate_db, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
|
||||
|
||||
if (once)
|
||||
goto done;
|
||||
|
||||
/* Daemonize and initiate logging. Note error is initiated here to make
|
||||
demonized errors OK. Before this stage, errors are logged on stderr
|
||||
also */
|
||||
if (foreground==0){
|
||||
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG);
|
||||
if (daemon(0, 0) < 0){
|
||||
fprintf(stderr, "config: daemon");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
/* Write pid-file */
|
||||
|
||||
if ((pid = pidfile_write(pidfile)) < 0)
|
||||
goto done;
|
||||
|
||||
/* Register log notifications */
|
||||
if (clicon_log_register_callback(config_log_cb, h) < 0)
|
||||
goto done;
|
||||
clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid());
|
||||
if (set_signal(SIGTERM, config_sig_term, NULL) < 0){
|
||||
clicon_err(OE_DEMON, errno, "Setting signal");
|
||||
goto done;
|
||||
}
|
||||
if (set_signal(SIGINT, config_sig_term, NULL) < 0){
|
||||
clicon_err(OE_DEMON, errno, "Setting signal");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Initialize server socket */
|
||||
if (server_socket(h) < 0)
|
||||
goto done;
|
||||
|
||||
if (debug)
|
||||
clicon_option_dump(h, debug);
|
||||
|
||||
if (event_loop() < 0)
|
||||
goto done;
|
||||
done:
|
||||
clicon_log(LOG_NOTICE, "%s: %u Terminated", __PROGRAM__, getpid());
|
||||
config_terminate(h); /* Cannot use h after this */
|
||||
|
||||
return 0;
|
||||
}
|
||||
751
apps/backend/backend_plugin.c
Normal file
751
apps/backend/backend_plugin.c
Normal file
|
|
@ -0,0 +1,751 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#define __USE_GNU /* strverscmp */
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <dlfcn.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "clicon_backend_transaction.h"
|
||||
#include "backend_plugin.h"
|
||||
#include "backend_commit.h"
|
||||
|
||||
/*
|
||||
* Types
|
||||
*/
|
||||
/* Following are specific to backend. For common see clicon_plugin.h
|
||||
* @note the following should match the prototypes in clicon_backend.h
|
||||
*/
|
||||
#define PLUGIN_RESET "plugin_reset"
|
||||
typedef int (plgreset_t)(clicon_handle h, char *dbname); /* Reset system status */
|
||||
|
||||
#define PLUGIN_TRANS_BEGIN "transaction_begin"
|
||||
#define PLUGIN_TRANS_VALIDATE "transaction_validate"
|
||||
#define PLUGIN_TRANS_COMPLETE "transaction_complete"
|
||||
#define PLUGIN_TRANS_COMMIT "transaction_commit"
|
||||
#define PLUGIN_TRANS_END "transaction_end"
|
||||
#define PLUGIN_TRANS_ABORT "transaction_abort"
|
||||
|
||||
typedef int (trans_cb_t)(clicon_handle h, transaction_data td); /* Transaction cbs */
|
||||
|
||||
/* Backend (config) plugins */
|
||||
struct plugin {
|
||||
char p_name[PATH_MAX]; /* Plugin name */
|
||||
void *p_handle; /* Dynamic object handle */
|
||||
plginit_t *p_init; /* Init */
|
||||
plgstart_t *p_start; /* Start */
|
||||
plgexit_t *p_exit; /* Exit */
|
||||
plgreset_t *p_reset; /* Reset state */
|
||||
trans_cb_t *p_trans_begin; /* Transaction start */
|
||||
trans_cb_t *p_trans_validate; /* Transaction validation */
|
||||
trans_cb_t *p_trans_complete; /* Transaction validation complete */
|
||||
trans_cb_t *p_trans_commit; /* Transaction commit */
|
||||
trans_cb_t *p_trans_end; /* Transaction completed */
|
||||
trans_cb_t *p_trans_abort; /* Transaction aborted */
|
||||
};
|
||||
|
||||
/*
|
||||
* Local variables
|
||||
*/
|
||||
static int nplugins = 0;
|
||||
static struct plugin *plugins = NULL;
|
||||
|
||||
/*! Find a plugin by name and return the dlsym handl
|
||||
* Used by libclicon code to find callback funcctions in plugins.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] h Name of plugin
|
||||
* @retval handle Plugin handle if found
|
||||
* @retval NULL Not found
|
||||
*/
|
||||
static void *
|
||||
config_find_plugin(clicon_handle h,
|
||||
char *name)
|
||||
{
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++){
|
||||
p = &plugins[i];
|
||||
if (strcmp(p->p_name, name) == 0)
|
||||
return p->p_handle;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*! Initialize plugin code (not the plugins themselves)
|
||||
* @param[in] h Clicon handle
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
config_plugin_init(clicon_handle h)
|
||||
{
|
||||
find_plugin_t *fp = config_find_plugin;
|
||||
clicon_hash_t *data = clicon_data(h);
|
||||
|
||||
/* Register CLICON_FIND_PLUGIN in data hash */
|
||||
if (hash_add(data, "CLICON_FIND_PLUGIN", &fp, sizeof(fp)) == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "failed to register CLICON_FIND_PLUGIN");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Unload a plugin
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] plg Plugin structure
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
plugin_unload(clicon_handle h,
|
||||
struct plugin *plg)
|
||||
{
|
||||
char *error;
|
||||
|
||||
/* Call exit function is it exists */
|
||||
if (plg->p_exit)
|
||||
plg->p_exit(h);
|
||||
|
||||
dlerror(); /* Clear any existing error */
|
||||
if (dlclose(plg->p_handle) != 0) {
|
||||
error = (char*)dlerror();
|
||||
clicon_err(OE_UNIX, 0, "dlclose: %s", error?error:"Unknown error");
|
||||
return -1;
|
||||
/* Just report */
|
||||
}
|
||||
else
|
||||
clicon_debug(1, "Plugin '%s' unloaded.", plg->p_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*! Load a dynamic plugin and call its init-function
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] file The plugin (.so) to load
|
||||
* @param[in] dlflags Arguments to dlopen(3)
|
||||
* @param[in] label Chunk label
|
||||
* @retval plugin Plugin struct
|
||||
* @retval NULL Error
|
||||
*/
|
||||
static struct plugin *
|
||||
plugin_load (clicon_handle h,
|
||||
char *file,
|
||||
int dlflags,
|
||||
const char *label)
|
||||
{
|
||||
char *error;
|
||||
void *handle;
|
||||
char *name;
|
||||
struct plugin *new;
|
||||
plginit_t *initfun;
|
||||
|
||||
dlerror(); /* Clear any existing error */
|
||||
if ((handle = dlopen (file, dlflags)) == NULL) {
|
||||
error = (char*)dlerror();
|
||||
clicon_err(OE_UNIX, 0, "dlopen: %s", error?error:"Unknown error");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
initfun = dlsym(handle, PLUGIN_INIT);
|
||||
if ((error = (char*)dlerror()) != NULL) {
|
||||
clicon_err(OE_UNIX, 0, "dlsym: %s", error);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (initfun(h) != 0) {
|
||||
dlclose(handle);
|
||||
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
||||
clicon_err(OE_DB, 0, "Unknown error: %s: plugin_init does not make clicon_err call on error",
|
||||
file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((new = chunk(sizeof(*new), label)) == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "dhunk: %s", strerror(errno));
|
||||
dlclose(handle);
|
||||
return NULL;
|
||||
}
|
||||
memset(new, 0, sizeof(*new));
|
||||
name = strrchr(file, '/') ? strrchr(file, '/')+1 : file;
|
||||
clicon_debug(2, "Loading plugin '%s'.", name);
|
||||
snprintf(new->p_name, sizeof(new->p_name), "%*s",
|
||||
(int)strlen(name)-2, name);
|
||||
new->p_handle = handle;
|
||||
new->p_init = initfun;
|
||||
if ((new->p_start = dlsym(handle, PLUGIN_START)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_START);
|
||||
if ((new->p_exit = dlsym(handle, PLUGIN_EXIT)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_EXIT);
|
||||
if ((new->p_reset = dlsym(handle, PLUGIN_RESET)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_RESET);
|
||||
if ((new->p_trans_begin = dlsym(handle, PLUGIN_TRANS_BEGIN)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_BEGIN);
|
||||
if ((new->p_trans_validate = dlsym(handle, PLUGIN_TRANS_VALIDATE)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_VALIDATE);
|
||||
if ((new->p_trans_complete = dlsym(handle, PLUGIN_TRANS_COMPLETE)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_COMPLETE);
|
||||
if ((new->p_trans_commit = dlsym(handle, PLUGIN_TRANS_COMMIT)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_COMMIT);
|
||||
if ((new->p_trans_end = dlsym(handle, PLUGIN_TRANS_END)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_END);
|
||||
if ((new->p_trans_abort = dlsym(handle, PLUGIN_TRANS_ABORT)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_ABORT);
|
||||
clicon_debug(2, "Plugin '%s' loaded.\n", name);
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
/*! Request plugins to reset system state
|
||||
* The system 'state' should be the same as the contents of running_db
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] dbname Name of database
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
plugin_reset_state(clicon_handle h,
|
||||
char *dbname)
|
||||
{
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
p = &plugins[i];
|
||||
if (p->p_reset) {
|
||||
clicon_debug(1, "Calling plugin_reset() for %s\n",
|
||||
p->p_name);
|
||||
if (((p->p_reset)(h, dbname)) < 0) {
|
||||
clicon_err(OE_FATAL, 0, "plugin_reset() failed for %s\n",
|
||||
p->p_name);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Call plugin_start in all plugins
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] argc Command-line arguments
|
||||
* @param[in] argv Command-line arguments
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
plugin_start_hooks(clicon_handle h,
|
||||
int argc,
|
||||
char **argv)
|
||||
{
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
p = &plugins[i];
|
||||
if (p->p_start) {
|
||||
optind = 0;
|
||||
if (((p->p_start)(h, argc, argv)) < 0) {
|
||||
clicon_err(OE_FATAL, 0, "plugin_start() failed for %s\n",
|
||||
p->p_name);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Append plugin to list
|
||||
* @param[in] p Plugin
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
plugin_append(struct plugin *p)
|
||||
{
|
||||
struct plugin *new;
|
||||
|
||||
if ((new = rechunk(plugins, (nplugins+1) * sizeof (*p), NULL)) == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "chunk");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset (&new[nplugins], 0, sizeof(new[nplugins]));
|
||||
memcpy (&new[nplugins], p, sizeof(new[nplugins]));
|
||||
plugins = new;
|
||||
nplugins++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Load backend plugins found in a directory
|
||||
* The plugins must have the '.so' suffix
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] dir Backend plugin directory
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
config_plugin_load_dir(clicon_handle h,
|
||||
const char *dir)
|
||||
{
|
||||
int retval = -1;
|
||||
int i;
|
||||
int np = 0;
|
||||
int ndp;
|
||||
struct stat st;
|
||||
char *filename;
|
||||
struct dirent *dp;
|
||||
struct plugin *new;
|
||||
struct plugin *p = NULL;
|
||||
char *master;
|
||||
char *master_plugin;
|
||||
|
||||
/* Format master plugin path */
|
||||
if ((master_plugin = clicon_master_plugin(h)) == NULL){
|
||||
clicon_err(OE_PLUGIN, 0, "clicon_master_plugin option not set");
|
||||
goto quit;
|
||||
}
|
||||
master = chunk_sprintf(__FUNCTION__, "%s.so", master_plugin);
|
||||
if (master == NULL) {
|
||||
clicon_err(OE_PLUGIN, errno, "chunk_sprintf master plugin");
|
||||
goto quit;
|
||||
}
|
||||
|
||||
/* Allocate plugin group object */
|
||||
/* Get plugin objects names from plugin directory */
|
||||
if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG, __FUNCTION__))<0)
|
||||
goto quit;
|
||||
|
||||
/* reset num plugins */
|
||||
np = 0;
|
||||
|
||||
/* Master plugin must be loaded first if it exists. */
|
||||
filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, master);
|
||||
if (filename == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "chunk");
|
||||
goto quit;
|
||||
}
|
||||
if (stat(filename, &st) == 0) {
|
||||
clicon_debug(1, "Loading master plugin '%.*s' ...",
|
||||
(int)strlen(filename), filename);
|
||||
|
||||
new = plugin_load(h, filename, RTLD_NOW|RTLD_GLOBAL, __FUNCTION__);
|
||||
if (new == NULL)
|
||||
goto quit;
|
||||
if (plugin_append(new) < 0)
|
||||
goto quit;
|
||||
}
|
||||
|
||||
/* Now load the rest */
|
||||
for (i = 0; i < ndp; i++) {
|
||||
if (strcmp(dp[i].d_name, master) == 0)
|
||||
continue; /* Skip master now */
|
||||
filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, dp[i].d_name);
|
||||
clicon_debug(1, "Loading plugin '%.*s' ...", (int)strlen(filename), filename);
|
||||
if (filename == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "chunk");
|
||||
goto quit;
|
||||
}
|
||||
new = plugin_load (h, filename, RTLD_NOW, __FUNCTION__);
|
||||
if (new == NULL)
|
||||
goto quit;
|
||||
if (plugin_append(new) < 0)
|
||||
goto quit;
|
||||
}
|
||||
|
||||
/* All good. */
|
||||
retval = 0;
|
||||
|
||||
quit:
|
||||
if (retval != 0) {
|
||||
if (p) {
|
||||
while (--np >= 0)
|
||||
plugin_unload (h, &p[np]);
|
||||
unchunk(p);
|
||||
}
|
||||
}
|
||||
unchunk_group(__FUNCTION__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Load a plugin group.
|
||||
* @param[in] h Clicon handle
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
plugin_initiate(clicon_handle h)
|
||||
{
|
||||
char *dir;
|
||||
|
||||
/* First load CLICON system plugins */
|
||||
if (config_plugin_load_dir(h, CLICON_BACKEND_SYSDIR) < 0)
|
||||
return -1;
|
||||
|
||||
/* Then load application plugins */
|
||||
if ((dir = clicon_backend_dir(h)) == NULL){
|
||||
clicon_err(OE_PLUGIN, 0, "backend_dir not defined");
|
||||
return -1;
|
||||
}
|
||||
if (config_plugin_load_dir(h, dir) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Unload and deallocate all backend plugins
|
||||
* @param[in] h Clicon handle
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
plugin_finish(clicon_handle h)
|
||||
{
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
p = &plugins[i];
|
||||
plugin_unload(h, p);
|
||||
}
|
||||
if (plugins)
|
||||
unchunk(plugins);
|
||||
nplugins = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Call from frontend to function 'func' in plugin 'plugin'.
|
||||
* Plugin function is supposed to populate 'retlen' and 'retarg' where
|
||||
* 'retarg' is malloc:ed data if non-NULL.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] req Clicon message containing information about the downcall
|
||||
* @param[out] retlen Length of return value
|
||||
* @param[out] ret Return value
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
plugin_downcall(clicon_handle h,
|
||||
struct clicon_msg_call_req *req,
|
||||
uint16_t *retlen,
|
||||
void **retarg)
|
||||
{
|
||||
int retval = -1;
|
||||
int i;
|
||||
downcall_cb funcp;
|
||||
char name[PATH_MAX];
|
||||
char *error;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
p = &plugins[i];
|
||||
strncpy(name, p->p_name, sizeof(name)-1);
|
||||
if (!strcmp(name+strlen(name)-3, ".so"))
|
||||
name[strlen(name)-3] = '\0';
|
||||
/* If no plugin is given or the plugin-name matches */
|
||||
if (req->cr_plugin == NULL || strlen(req->cr_plugin)==0 ||
|
||||
strcmp(name, req->cr_plugin) == 0) {
|
||||
funcp = dlsym(p->p_handle, req->cr_func);
|
||||
if ((error = (char*)dlerror()) != NULL) {
|
||||
clicon_err(OE_PROTO, ENOENT,
|
||||
"Function does not exist: %s()", req->cr_func);
|
||||
return -1;
|
||||
}
|
||||
retval = funcp(h, req->cr_op, req->cr_arglen, req->cr_arg, retlen, retarg);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
clicon_err(OE_PROTO, ENOENT,"%s: %s(): Plugin does not exist: %s",
|
||||
__FUNCTION__, req->cr_func, req->cr_plugin);
|
||||
return -1;
|
||||
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Create and initialize transaction */
|
||||
transaction_data_t *
|
||||
transaction_new(void)
|
||||
{
|
||||
transaction_data_t *td;
|
||||
static uint64_t id = 0; /* Global transaction id */
|
||||
|
||||
if ((td = malloc(sizeof(*td))) == NULL){
|
||||
clicon_err(OE_CFG, errno, "malloc");
|
||||
return NULL;
|
||||
}
|
||||
memset(td, 0, sizeof(*td));
|
||||
td->td_id = id++;
|
||||
return td;
|
||||
}
|
||||
|
||||
/*! Free transaction structure */
|
||||
int
|
||||
transaction_free(transaction_data_t *td)
|
||||
{
|
||||
if (td->td_src)
|
||||
xml_free(td->td_src);
|
||||
if (td->td_target)
|
||||
xml_free(td->td_target);
|
||||
if (td->td_dvec)
|
||||
free(td->td_dvec);
|
||||
if (td->td_avec)
|
||||
free(td->td_avec);
|
||||
if (td->td_scvec)
|
||||
free(td->td_scvec);
|
||||
if (td->td_tcvec)
|
||||
free(td->td_tcvec);
|
||||
free(td);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The plugin_transaction routines need access to struct plugin which is local to this file */
|
||||
|
||||
/*! Call transaction_begin() in all plugins before a validate/commit.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] td Transaction data
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error: one of the plugin callbacks returned error
|
||||
*/
|
||||
int
|
||||
plugin_transaction_begin(clicon_handle h,
|
||||
transaction_data_t *td)
|
||||
{
|
||||
int i;
|
||||
int retval = 0;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
p = &plugins[i];
|
||||
if (p->p_trans_begin)
|
||||
if ((retval = (p->p_trans_begin)(h, (transaction_data)td)) < 0){
|
||||
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
||||
clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
|
||||
__FUNCTION__, p->p_name, PLUGIN_TRANS_BEGIN);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Call transaction_validate callbacks in all backend plugins
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] td Transaction data
|
||||
* @retval 0 OK. Validation succeeded in all plugins
|
||||
* @retval -1 Error: one of the plugin callbacks returned validation fail
|
||||
*/
|
||||
int
|
||||
plugin_transaction_validate(clicon_handle h,
|
||||
transaction_data_t *td)
|
||||
{
|
||||
int retval = 0;
|
||||
int i;
|
||||
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++){
|
||||
p = &plugins[i];
|
||||
if (p->p_trans_validate)
|
||||
if ((retval = (p->p_trans_validate)(h, (transaction_data)td)) < 0){
|
||||
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
||||
clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
|
||||
__FUNCTION__, p->p_name, PLUGIN_TRANS_VALIDATE);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Call transaction_complete() in all plugins after validation (before commit)
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] td Transaction data
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error: one of the plugin callbacks returned error
|
||||
* @note Call plugins which have commit dependencies?
|
||||
* @note Rename to transaction_complete?
|
||||
*/
|
||||
int
|
||||
plugin_transaction_complete(clicon_handle h,
|
||||
transaction_data_t *td)
|
||||
{
|
||||
int i;
|
||||
int retval = 0;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++){
|
||||
p = &plugins[i];
|
||||
if (p->p_trans_complete)
|
||||
if ((retval = (p->p_trans_complete)(h, (transaction_data)td)) < 0){
|
||||
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
||||
clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
|
||||
__FUNCTION__, p->p_name, PLUGIN_TRANS_COMPLETE);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
plugin_transaction_revert(clicon_handle h,
|
||||
transaction_data_t *td,
|
||||
int nr)
|
||||
{
|
||||
int retval = 0;
|
||||
transaction_data_t tr; /* revert transaction */
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
/* Create a new reversed transaction from the original where src and target
|
||||
are swapped */
|
||||
memcpy(&tr, td, sizeof(tr));
|
||||
tr.td_src = td->td_target;
|
||||
tr.td_target = td->td_src;
|
||||
tr.td_dlen = td->td_alen;
|
||||
tr.td_dvec = td->td_avec;
|
||||
tr.td_alen = td->td_dlen;
|
||||
tr.td_avec = td->td_dvec;
|
||||
tr.td_scvec = td->td_tcvec;
|
||||
tr.td_tcvec = td->td_scvec;
|
||||
|
||||
for (i = nr-1; i; i--){
|
||||
p = &plugins[i];
|
||||
if (p->p_trans_commit)
|
||||
if ((p->p_trans_commit)(h, (transaction_data)&tr) < 0){
|
||||
clicon_log(LOG_NOTICE, "Plugin '%s' %s revert callback failed",
|
||||
p->p_name, PLUGIN_TRANS_COMMIT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retval; /* ignore errors */
|
||||
}
|
||||
|
||||
/*! Call transaction_commit callbacks in all backend plugins
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] td Transaction data
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error: one of the plugin callbacks returned error
|
||||
* If any of the commit callbacks fail by returning -1, a revert of the
|
||||
* transaction is tried by calling the commit callbacsk with reverse arguments
|
||||
* and in reverse order.
|
||||
*/
|
||||
int
|
||||
plugin_transaction_commit(clicon_handle h,
|
||||
transaction_data_t *td)
|
||||
{
|
||||
int retval = 0;
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++){
|
||||
p = &plugins[i];
|
||||
if (p->p_trans_commit)
|
||||
if ((retval = (p->p_trans_commit)(h, (transaction_data)td)) < 0){
|
||||
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
||||
clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
|
||||
__FUNCTION__, p->p_name, PLUGIN_TRANS_COMMIT);
|
||||
/* Make an effort to revert transaction */
|
||||
plugin_transaction_revert(h, td, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Call transaction_end() in all plugins after a successful commit.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] td Transaction data
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
plugin_transaction_end(clicon_handle h,
|
||||
transaction_data_t *td)
|
||||
{
|
||||
int retval = 0;
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
p = &plugins[i];
|
||||
if (p->p_trans_end)
|
||||
if ((retval = (p->p_trans_end)(h, (transaction_data)td)) < 0){
|
||||
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
||||
clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
|
||||
__FUNCTION__, p->p_name, PLUGIN_TRANS_END);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Call transaction_abort() in all plugins after a failed validation/commit.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] td Transaction data
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
plugin_transaction_abort(clicon_handle h,
|
||||
transaction_data_t *td)
|
||||
{
|
||||
int retval = 0;
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
p = &plugins[i];
|
||||
if (p->p_trans_abort)
|
||||
(p->p_trans_abort)(h, (transaction_data)td); /* dont abort on error */
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
72
apps/backend/backend_plugin.h
Normal file
72
apps/backend/backend_plugin.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _BACKEND_PLUGIN_H_
|
||||
#define _BACKEND_PLUGIN_H_
|
||||
|
||||
/*
|
||||
* Types
|
||||
*/
|
||||
|
||||
|
||||
/*! Transaction data
|
||||
* Clicon internal, presented as void* to app's callback in the 'transaction_data'
|
||||
* type in clicon_backend_api.h
|
||||
* XXX: move to .c file?
|
||||
*/
|
||||
typedef struct {
|
||||
uint64_t td_id; /* Transaction id */
|
||||
void *td_arg; /* Callback argument */
|
||||
cxobj *td_src; /* Source database xml tree */
|
||||
cxobj *td_target; /* Target database xml tree */
|
||||
cxobj **td_dvec; /* Delete xml vector */
|
||||
size_t td_dlen; /* Delete xml vector length */
|
||||
cxobj **td_avec; /* Add xml vector */
|
||||
size_t td_alen; /* Add xml vector length */
|
||||
cxobj **td_scvec; /* Source changed xml vector */
|
||||
cxobj **td_tcvec; /* Target changed xml vector */
|
||||
size_t td_clen; /* Changed xml vector length */
|
||||
} transaction_data_t;
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int config_plugin_init(clicon_handle h);
|
||||
int plugin_initiate(clicon_handle h);
|
||||
int plugin_finish(clicon_handle h);
|
||||
|
||||
int plugin_reset_state(clicon_handle h, char *dbname);
|
||||
int plugin_start_hooks(clicon_handle h, int argc, char **argv);
|
||||
int plugin_downcall(clicon_handle h, struct clicon_msg_call_req *req,
|
||||
uint16_t *retlen, void **retarg);
|
||||
|
||||
transaction_data_t * transaction_new(void);
|
||||
int transaction_free(transaction_data_t *);
|
||||
|
||||
int plugin_transaction_begin(clicon_handle h, transaction_data_t *td);
|
||||
int plugin_transaction_validate(clicon_handle h, transaction_data_t *td);
|
||||
int plugin_transaction_complete(clicon_handle h, transaction_data_t *td);
|
||||
int plugin_transaction_commit(clicon_handle h, transaction_data_t *td);
|
||||
int plugin_transaction_end(clicon_handle h, transaction_data_t *td);
|
||||
int plugin_transaction_abort(clicon_handle h, transaction_data_t *td);
|
||||
|
||||
#endif /* _BACKEND_PLUGIN_H_ */
|
||||
262
apps/backend/backend_socket.c
Normal file
262
apps/backend/backend_socket.c
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <syslog.h>
|
||||
#include <grp.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
#ifdef HAVE_SYS_UCRED_H
|
||||
#include <sys/types.h>
|
||||
#include <sys/ucred.h>
|
||||
#endif
|
||||
#define __USE_GNU
|
||||
#include <sys/socket.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "backend_socket.h"
|
||||
#include "backend_client.h"
|
||||
#include "backend_handle.h"
|
||||
|
||||
static int
|
||||
config_socket_init_ipv4(clicon_handle h, char *dst)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_in addr;
|
||||
uint16_t port;
|
||||
|
||||
port = clicon_sock_port(h);
|
||||
|
||||
/* create inet socket */
|
||||
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
||||
clicon_err(OE_UNIX, errno, "socket");
|
||||
return -1;
|
||||
}
|
||||
// setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&one, sizeof(one));
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
if (inet_pton(addr.sin_family, dst, &addr.sin_addr) != 1){
|
||||
clicon_err(OE_UNIX, errno, "inet_pton: %s (Expected IPv4 address. Check settings of CLICON_SOCK_FAMILY and CLICON_SOCK)", dst);
|
||||
goto err; /* Could check getaddrinfo */
|
||||
}
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0){
|
||||
clicon_err(OE_UNIX, errno, "%s: bind", __FUNCTION__);
|
||||
goto err;
|
||||
}
|
||||
clicon_debug(1, "Listen on server socket at %s:%hu", dst, port);
|
||||
if (listen(s, 5) < 0){
|
||||
clicon_err(OE_UNIX, errno, "%s: listen", __FUNCTION__);
|
||||
goto err;
|
||||
}
|
||||
return s;
|
||||
err:
|
||||
close(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*! Open a socket and bind it to a file descriptor
|
||||
*
|
||||
* The socket is accessed via CLICON_SOCK option, has 770 permissions
|
||||
* and group according to CLICON_SOCK_GROUP option.
|
||||
*/
|
||||
static int
|
||||
config_socket_init_unix(clicon_handle h, char *sock)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_un addr;
|
||||
mode_t old_mask;
|
||||
char *config_group;
|
||||
gid_t gid;
|
||||
struct stat st;
|
||||
|
||||
if (lstat(sock, &st) == 0 && unlink(sock) < 0){
|
||||
clicon_err(OE_UNIX, errno, "%s: unlink(%s)", __FUNCTION__, sock);
|
||||
return -1;
|
||||
}
|
||||
/* then find configuration group (for clients) and find its groupid */
|
||||
if ((config_group = clicon_sock_group(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "clicon_sock_group option not set");
|
||||
return -1;
|
||||
}
|
||||
if (group_name2gid(config_group, &gid) < 0)
|
||||
return -1;
|
||||
#if 0
|
||||
if (gid == 0)
|
||||
clicon_log(LOG_WARNING, "%s: No such group: %s\n", __FUNCTION__, config_group);
|
||||
#endif
|
||||
/* create unix socket */
|
||||
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
|
||||
clicon_err(OE_UNIX, errno, "%s: socket", __FUNCTION__);
|
||||
return -1;
|
||||
}
|
||||
// setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&one, sizeof(one));
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy(addr.sun_path, sock, sizeof(addr.sun_path)-1);
|
||||
old_mask = umask(S_IRWXO | S_IXGRP | S_IXUSR);
|
||||
if (bind(s, (struct sockaddr *)&addr, SUN_LEN(&addr)) < 0){
|
||||
clicon_err(OE_UNIX, errno, "%s: bind", __FUNCTION__);
|
||||
umask(old_mask);
|
||||
goto err;
|
||||
}
|
||||
umask(old_mask);
|
||||
/* change socket path file group */
|
||||
if (lchown(sock, -1, gid) < 0){
|
||||
clicon_err(OE_UNIX, errno, "%s: lchown(%s, %s)", __FUNCTION__,
|
||||
sock, config_group);
|
||||
goto err;
|
||||
}
|
||||
clicon_debug(1, "Listen on server socket at %s", addr.sun_path);
|
||||
if (listen(s, 5) < 0){
|
||||
clicon_err(OE_UNIX, errno, "%s: listen", __FUNCTION__);
|
||||
goto err;
|
||||
}
|
||||
return s;
|
||||
err:
|
||||
close(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
config_socket_init(clicon_handle h)
|
||||
{
|
||||
char *sock;
|
||||
|
||||
if ((sock = clicon_sock(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set");
|
||||
return -1;
|
||||
}
|
||||
switch (clicon_sock_family(h)){
|
||||
case AF_UNIX:
|
||||
return config_socket_init_unix(h, sock);
|
||||
break;
|
||||
case AF_INET:
|
||||
return config_socket_init_ipv4(h, sock);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* config_accept_client
|
||||
* XXX: credentials not properly implemented
|
||||
*/
|
||||
int
|
||||
config_accept_client(int fd, void *arg)
|
||||
{
|
||||
int retval = -1;
|
||||
clicon_handle h = (clicon_handle)arg;
|
||||
int s;
|
||||
struct sockaddr_un from;
|
||||
socklen_t len;
|
||||
struct client_entry *ce;
|
||||
#ifdef DONT_WORK /* XXX HAVE_SYS_UCRED_H */
|
||||
struct xucred credentials; /* FreeBSD. */
|
||||
socklen_t clen;
|
||||
#elif defined(SO_PEERCRED)
|
||||
struct ucred credentials; /* Linux. */
|
||||
socklen_t clen;
|
||||
#endif
|
||||
char *config_group;
|
||||
struct group *gr;
|
||||
char *mem;
|
||||
int i;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
len = sizeof(from);
|
||||
if ((s = accept(fd, (struct sockaddr*)&from, &len)) < 0){
|
||||
clicon_err(OE_UNIX, errno, "%s: accept", __FUNCTION__);
|
||||
goto done;
|
||||
}
|
||||
#if defined(SO_PEERCRED)
|
||||
/* fill in the user data structure */
|
||||
clen = sizeof(credentials);
|
||||
if(getsockopt(s, SOL_SOCKET, SO_PEERCRED/* XXX finns ej i freebsd*/, &credentials, &clen)){
|
||||
clicon_err(OE_UNIX, errno, "%s: getsockopt", __FUNCTION__);
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
if ((ce = backend_client_add(h, (struct sockaddr*)&from)) == NULL)
|
||||
goto done;
|
||||
#if defined(SO_PEERCRED)
|
||||
ce->ce_pid = credentials.pid;
|
||||
ce->ce_uid = credentials.uid;
|
||||
#endif
|
||||
ce->ce_handle = h;
|
||||
|
||||
/* check credentials of caller (not properly implemented yet) */
|
||||
if ((config_group = clicon_sock_group(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "clicon_sock_group option not set");
|
||||
goto done;
|
||||
}
|
||||
if ((gr = getgrnam(config_group)) != NULL){
|
||||
i = 0; /* one of mem should correspond to ce->ce_uid */
|
||||
while ((mem = gr->gr_mem[i++]) != NULL)
|
||||
;
|
||||
}
|
||||
|
||||
#if 0
|
||||
{ /* XXX */
|
||||
int ii;
|
||||
struct client_entry *c;
|
||||
for (c = ce_list, ii=0; c; c = c->ce_next, ii++);
|
||||
clicon_debug(1, "Open client socket (nr:%d pid:%d [Total: %d])",
|
||||
ce->ce_nr, ce->ce_pid, ii);
|
||||
}
|
||||
#endif
|
||||
ce->ce_s = s;
|
||||
|
||||
/*
|
||||
* Here we register callbacks for actual data socket
|
||||
*/
|
||||
if (event_reg_fd(s, from_client, (void*)ce, "client socket") < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
33
apps/backend/backend_socket.h
Normal file
33
apps/backend/backend_socket.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _BACKEND_SOCKET_H_
|
||||
#define _BACKEND_SOCKET_H_
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int config_socket_init(clicon_handle h);
|
||||
int config_accept_client(int fd, void *arg);
|
||||
|
||||
#endif /* _BACKEND_SOCKET_H_ */
|
||||
92
apps/backend/clicon_backend.h
Normal file
92
apps/backend/clicon_backend.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* The exported interface to plugins. External apps (eg backend plugins) should
|
||||
* only include this file.
|
||||
* Internal code should not include this file
|
||||
*/
|
||||
|
||||
#ifndef _CLICON_BACKEND_H_
|
||||
#define _CLICON_BACKEND_H_
|
||||
|
||||
/*
|
||||
* Use this constant to disable some prototypes that should not be visible outside the lib.
|
||||
* This is an alternative to use separate internal include files.
|
||||
*/
|
||||
|
||||
/* Common code (API and Backend daemon) */
|
||||
#include <clicon/clicon_backend_handle.h>
|
||||
#include <clicon/clicon_backend_transaction.h>
|
||||
|
||||
/*! Clicon Backend plugin callbacks: use these in your backend plugin code
|
||||
*/
|
||||
|
||||
/*! Called when plugin loaded. Only mandadory callback. All others optional
|
||||
* @see plginit_t
|
||||
*/
|
||||
int plugin_init(clicon_handle h);
|
||||
|
||||
/* Called when backend started with cmd-line arguments from daemon call.
|
||||
* @see plgstart_t
|
||||
*/
|
||||
int plugin_start(clicon_handle h, int argc, char **argv);
|
||||
|
||||
/* Called just before plugin unloaded.
|
||||
* @see plgexit_t
|
||||
*/
|
||||
int plugin_exit(clicon_handle h);
|
||||
|
||||
/*! Reset system state to original state. Eg at reboot before running thru config.
|
||||
* @see plgreset_t
|
||||
*/
|
||||
int plugin_reset(clicon_handle h, char *dbname);
|
||||
|
||||
/*! Called before a commit/validate sequence begins. Eg setup state before commit
|
||||
* @see trans_cb_t
|
||||
*/
|
||||
int transaction_begin(clicon_handle h, transaction_data td);
|
||||
|
||||
/*! Validate.
|
||||
* @see trans_cb_t
|
||||
*/
|
||||
int transaction_validate(clicon_handle h, transaction_data td);
|
||||
|
||||
/* Called after a validation completed succesfully (but before commit).
|
||||
* @see trans_cb_t
|
||||
*/
|
||||
int transaction_complete(clicon_handle h, transaction_data td);
|
||||
|
||||
/* Commit.
|
||||
* @see trans_cb_t
|
||||
*/
|
||||
int transaction_commit(clicon_handle h, transaction_data td);
|
||||
|
||||
/* Called after a commit sequence completed succesfully.
|
||||
* @see trans_cb_t
|
||||
*/
|
||||
int transaction_end(clicon_handle h, transaction_data td);
|
||||
|
||||
/* Called if commit or validate sequence fails. After eventual rollback.
|
||||
* @see trans_cb_t
|
||||
*/
|
||||
int transaction_abort(clicon_handle h, transaction_data td);
|
||||
|
||||
#endif /* _CLICON_BACKEND_H_ */
|
||||
353
apps/backend/clicon_backend_handle.c
Normal file
353
apps/backend/clicon_backend_handle.c
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <fnmatch.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <regex.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "clicon_backend_handle.h"
|
||||
#include "backend_client.h"
|
||||
#include "backend_handle.h"
|
||||
|
||||
/* header part is copied from struct clicon_handle in lib/src/clicon_handle.c */
|
||||
|
||||
#define CLICON_MAGIC 0x99aafabe
|
||||
|
||||
#define handle(h) (assert(clicon_handle_check(h)==0),(struct backend_handle *)(h))
|
||||
|
||||
/* Clicon_handle for backends.
|
||||
* First part of this is header, same for clicon_handle and cli_handle.
|
||||
* Access functions for common fields are found in clicon lib: clicon_options.[ch]
|
||||
* This file should only contain access functions for the _specific_
|
||||
* entries in the struct below.
|
||||
*/
|
||||
struct backend_handle {
|
||||
int cb_magic; /* magic (HDR)*/
|
||||
clicon_hash_t *cb_copt; /* clicon option list (HDR) */
|
||||
clicon_hash_t *cb_data; /* internal clicon data (HDR) */
|
||||
/* ------ end of common handle ------ */
|
||||
struct client_entry *cb_ce_list; /* The client list */
|
||||
int cb_ce_nr; /* Number of clients, just increment */
|
||||
struct handle_subscription *cb_subscription; /* Event subscription list */
|
||||
};
|
||||
|
||||
/*! Creates and returns a clicon config handle for other CLICON API calls
|
||||
*/
|
||||
clicon_handle
|
||||
backend_handle_init(void)
|
||||
{
|
||||
return clicon_handle_init0(sizeof(struct backend_handle));
|
||||
}
|
||||
|
||||
/*! Deallocates a backend handle, including all client structs
|
||||
* @Note: handle 'h' cannot be used in calls after this
|
||||
*/
|
||||
int
|
||||
backend_handle_exit(clicon_handle h)
|
||||
{
|
||||
struct client_entry *ce;
|
||||
|
||||
/* only delete client structs, not close sockets, etc, see backend_client_rm */
|
||||
while ((ce = backend_client_list(h)) != NULL)
|
||||
backend_client_delete(h, ce);
|
||||
clicon_handle_exit(h); /* frees h and options */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Notify event and distribute to all registered clients
|
||||
*
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] stream Name of event stream. CLICON is predefined as LOG stream
|
||||
* @param[in] level Event level (not used yet)
|
||||
* @param[in] event Actual message as text format
|
||||
*
|
||||
* Stream is a string used to qualify the event-stream. Distribute the
|
||||
* event to all clients registered to this backend.
|
||||
* XXX: event-log NYI.
|
||||
* @see also subscription_add()
|
||||
* @see also backend_notify_xml()
|
||||
*/
|
||||
int
|
||||
backend_notify(clicon_handle h, char *stream, int level, char *event)
|
||||
{
|
||||
struct client_entry *ce;
|
||||
struct client_subscription *su;
|
||||
struct handle_subscription *hs;
|
||||
int retval = -1;
|
||||
|
||||
/* First thru all clients(sessions), and all subscriptions and find matches */
|
||||
for (ce = backend_client_list(h); ce; ce = ce->ce_next)
|
||||
for (su = ce->ce_subscription; su; su = su->su_next)
|
||||
if (strcmp(su->su_stream, stream) == 0){
|
||||
if (fnmatch(su->su_filter, event, 0) == 0)
|
||||
if (send_msg_notify(ce->ce_s, level, event) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Then go thru all global (handle) subscriptions and find matches */
|
||||
hs = NULL;
|
||||
while ((hs = subscription_each(h, hs)) != NULL){
|
||||
if (hs->hs_format != MSG_NOTIFY_TXT)
|
||||
continue;
|
||||
if (strcmp(hs->hs_stream, stream))
|
||||
continue;
|
||||
if (fnmatch(hs->hs_filter, event, 0) == 0)
|
||||
if ((*hs->hs_fn)(h, event, hs->hs_arg) < 0)
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Notify event and distribute to all registered clients
|
||||
*
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] stream Name of event stream. CLICON is predefined as LOG stream
|
||||
* @param[in] level Event level (not used yet)
|
||||
* @param[in] event Actual message as xml tree
|
||||
*
|
||||
* Stream is a string used to qualify the event-stream. Distribute the
|
||||
* event to all clients registered to this backend.
|
||||
* XXX: event-log NYI.
|
||||
* @see also subscription_add()
|
||||
* @see also backend_notify()
|
||||
*/
|
||||
int
|
||||
backend_notify_xml(clicon_handle h, char *stream, int level, cxobj *x)
|
||||
{
|
||||
struct client_entry *ce;
|
||||
struct client_subscription *su;
|
||||
int retval = -1;
|
||||
cbuf *cb = NULL;
|
||||
struct handle_subscription *hs;
|
||||
|
||||
/* Now go thru all clients(sessions), and all subscriptions and find matches */
|
||||
for (ce = backend_client_list(h); ce; ce = ce->ce_next)
|
||||
for (su = ce->ce_subscription; su; su = su->su_next)
|
||||
if (strcmp(su->su_stream, stream) == 0){
|
||||
if (strlen(su->su_filter)==0 || xpath_first(x, su->su_filter) != NULL){
|
||||
if (cb==NULL){
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_PLUGIN, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
if (clicon_xml2cbuf(cb, x, 0, 0) < 0)
|
||||
goto done;
|
||||
}
|
||||
if (send_msg_notify(ce->ce_s, level, cbuf_get(cb)) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
/* Then go thru all global (handle) subscriptions and find matches */
|
||||
/* XXX: x contains name==dk-ore, but filter is
|
||||
id==/[userid=d2d5e46c-c6f9-42f3-9a69-fb52fe60940d] */
|
||||
hs = NULL;
|
||||
while ((hs = subscription_each(h, hs)) != NULL){
|
||||
if (hs->hs_format != MSG_NOTIFY_XML)
|
||||
continue;
|
||||
if (strcmp(hs->hs_stream, stream))
|
||||
continue;
|
||||
if (strlen(hs->hs_filter)==0 || xpath_first(x, hs->hs_filter) != NULL)
|
||||
if ((*hs->hs_fn)(h, x, hs->hs_arg) < 0)
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
struct client_entry *
|
||||
backend_client_add(clicon_handle h, struct sockaddr *addr)
|
||||
{
|
||||
struct backend_handle *cb = handle(h);
|
||||
struct client_entry *ce;
|
||||
|
||||
if ((ce = (struct client_entry *)malloc(sizeof(*ce))) == NULL){
|
||||
clicon_err(OE_PLUGIN, errno, "malloc");
|
||||
return NULL;
|
||||
}
|
||||
memset(ce, 0, sizeof(*ce));
|
||||
ce->ce_nr = cb->cb_ce_nr++;
|
||||
memcpy(&ce->ce_addr, addr, sizeof(*addr));
|
||||
ce->ce_next = cb->cb_ce_list;
|
||||
cb->cb_ce_list = ce;
|
||||
return ce;
|
||||
}
|
||||
|
||||
struct client_entry *
|
||||
backend_client_list(clicon_handle h)
|
||||
{
|
||||
struct backend_handle *cb = handle(h);
|
||||
|
||||
return cb->cb_ce_list;
|
||||
}
|
||||
|
||||
/*! Actually remove client from list
|
||||
* See also backend_client_rm()
|
||||
*/
|
||||
int
|
||||
backend_client_delete(clicon_handle h, struct client_entry *ce)
|
||||
{
|
||||
struct client_entry *c;
|
||||
struct client_entry **ce_prev;
|
||||
struct backend_handle *cb = handle(h);
|
||||
|
||||
ce_prev = &cb->cb_ce_list;
|
||||
for (c = *ce_prev; c; c = c->ce_next){
|
||||
if (c == ce){
|
||||
*ce_prev = c->ce_next;
|
||||
free(ce);
|
||||
break;
|
||||
}
|
||||
ce_prev = &c->ce_next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Add subscription given stream name, callback and argument
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] stream Name of event stream
|
||||
* @param[in] format Expected format of event, eg text or xml
|
||||
* @param[in] filter Filter to match event, depends on format, eg xpath for xml
|
||||
* @param[in] fn Callback when event occurs
|
||||
* @param[in] arg Argument to use with callback. Also handle when deleting
|
||||
* Note that arg is not a real handle.
|
||||
* @see subscription_delete
|
||||
* @see subscription_each
|
||||
*/
|
||||
struct handle_subscription *
|
||||
subscription_add(clicon_handle h,
|
||||
char *stream,
|
||||
enum format_enum format,
|
||||
char *filter,
|
||||
subscription_fn_t fn,
|
||||
void *arg)
|
||||
{
|
||||
struct backend_handle *cb = handle(h);
|
||||
struct handle_subscription *hs = NULL;
|
||||
|
||||
if ((hs = malloc(sizeof(*hs))) == NULL){
|
||||
clicon_err(OE_PLUGIN, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
memset(hs, 0, sizeof(*hs));
|
||||
hs->hs_stream = strdup(stream);
|
||||
hs->hs_format = format;
|
||||
hs->hs_filter = strdup(filter);
|
||||
hs->hs_next = cb->cb_subscription;
|
||||
hs->hs_fn = fn;
|
||||
hs->hs_arg = arg;
|
||||
cb->cb_subscription = hs;
|
||||
done:
|
||||
return hs;
|
||||
}
|
||||
|
||||
/*! Delete subscription given stream name, callback and argument
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] stream Name of event stream
|
||||
* @param[in] fn Callback when event occurs
|
||||
* @param[in] arg Argument to use with callback and handle
|
||||
* Note that arg is not a real handle.
|
||||
* @see subscription_add
|
||||
* @see subscription_each
|
||||
*/
|
||||
int
|
||||
subscription_delete(clicon_handle h,
|
||||
char *stream,
|
||||
subscription_fn_t fn,
|
||||
void *arg)
|
||||
{
|
||||
struct backend_handle *cb = handle(h);
|
||||
struct handle_subscription *hs;
|
||||
struct handle_subscription **hs_prev;
|
||||
|
||||
hs_prev = &cb->cb_subscription; /* this points to stack and is not real backpointer */
|
||||
for (hs = *hs_prev; hs; hs = hs->hs_next){
|
||||
/* XXX arg == hs->hs_arg */
|
||||
if (strcmp(hs->hs_stream, stream)==0 && hs->hs_fn == fn){
|
||||
*hs_prev = hs->hs_next;
|
||||
free(hs->hs_stream);
|
||||
if (hs->hs_filter)
|
||||
free(hs->hs_filter);
|
||||
if (hs->hs_arg)
|
||||
free(hs->hs_arg);
|
||||
free(hs);
|
||||
break;
|
||||
}
|
||||
hs_prev = &hs->hs_next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Iterator over subscriptions
|
||||
*
|
||||
* NOTE: Never manipulate the child-list during operation or using the
|
||||
* same object recursively, the function uses an internal field to remember the
|
||||
* index used. It works as long as the same object is not iterated concurrently.
|
||||
*
|
||||
* @param[in] h clicon handle
|
||||
* @param[in] hprev iterator, initialize with NULL
|
||||
* @code
|
||||
* clicon_handle h;
|
||||
* struct handle_subscription *hs = NULL;
|
||||
* while ((hs = subscription_each(h, hs)) != NULL) {
|
||||
* ...
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
struct handle_subscription *
|
||||
subscription_each(clicon_handle h,
|
||||
struct handle_subscription *hprev)
|
||||
{
|
||||
struct backend_handle *cb = handle(h);
|
||||
struct handle_subscription *hs = NULL;
|
||||
|
||||
if (hprev)
|
||||
hs = hprev->hs_next;
|
||||
else
|
||||
hs = cb->cb_subscription;
|
||||
return hs;
|
||||
}
|
||||
71
apps/backend/clicon_backend_handle.h
Normal file
71
apps/backend/clicon_backend_handle.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* Part of the external API to plugins. Applications should not include
|
||||
* this file directly (only via clicon_backend.h).
|
||||
* Internal code should include this
|
||||
*/
|
||||
|
||||
#ifndef _CLICON_BACKEND_HANDLE_H_
|
||||
#define _CLICON_BACKEND_HANDLE_H_
|
||||
|
||||
/*
|
||||
* Types
|
||||
*/
|
||||
|
||||
/*! Generic downcall registration.
|
||||
* Enables any function to be called from (cli) frontend
|
||||
* to backend. Like an RPC on application-level.
|
||||
*/
|
||||
typedef int (*downcall_cb)(clicon_handle h, uint16_t op, uint16_t len,
|
||||
void *arg, uint16_t *retlen, void **retarg);
|
||||
|
||||
/*
|
||||
* Log for netconf notify function (config_client.c)
|
||||
*/
|
||||
int backend_notify(clicon_handle h, char *stream, int level, char *txt);
|
||||
int backend_notify_xml(clicon_handle h, char *stream, int level, cxobj *x);
|
||||
|
||||
/* subscription callback */
|
||||
typedef int (*subscription_fn_t)(clicon_handle, void *filter, void *arg);
|
||||
|
||||
/* Notification subscription info
|
||||
* @see client_subscription in config_client.h
|
||||
*/
|
||||
struct handle_subscription{
|
||||
struct handle_subscription *hs_next;
|
||||
enum format_enum hs_format; /* format (enum format_enum) XXX not needed? */
|
||||
char *hs_stream; /* name of notify stream */
|
||||
char *hs_filter; /* filter, if format=xml: xpath, if text: fnmatch */
|
||||
subscription_fn_t hs_fn; /* Callback when event occurs */
|
||||
void *hs_arg; /* Callback argument */
|
||||
};
|
||||
|
||||
struct handle_subscription *subscription_add(clicon_handle h, char *stream,
|
||||
enum format_enum format, char *filter,
|
||||
subscription_fn_t fn, void *arg);
|
||||
|
||||
int subscription_delete(clicon_handle h, char *stream,
|
||||
subscription_fn_t fn, void *arg);
|
||||
|
||||
struct handle_subscription *subscription_each(clicon_handle h,
|
||||
struct handle_subscription *hprev);
|
||||
#endif /* _CLICON_BACKEND_HANDLE_H_ */
|
||||
206
apps/backend/clicon_backend_transaction.c
Normal file
206
apps/backend/clicon_backend_transaction.c
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
/*
|
||||
* Note that the functions in this file are accessible from the plugins
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/types.h>
|
||||
#include <regex.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "clicon_backend_transaction.h"
|
||||
#include "backend_plugin.h"
|
||||
|
||||
/* Access functions for transaction-data handle in callbacks
|
||||
* Expressed in a transition from an current -> wanted state.
|
||||
* For example, adding a database symbol 'a' in candidate and commiting
|
||||
* would give running in source and 'a' and candidate in 'target'.
|
||||
*/
|
||||
/*! Get transaction id
|
||||
* @param[in] td transaction_data
|
||||
* @retval id transaction id
|
||||
*/
|
||||
uint64_t
|
||||
transaction_id(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_id;
|
||||
}
|
||||
|
||||
/*! Get plugin/application specific callbackargument
|
||||
* @param[in] td transaction_data
|
||||
* @retval arg callback argument
|
||||
* @note NYI
|
||||
*/
|
||||
void *
|
||||
transaction_arg(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_arg;
|
||||
}
|
||||
|
||||
/*! Get Source database xml tree
|
||||
* @param[in] td transaction_data
|
||||
* @retval src source xml tree containing original state
|
||||
*/
|
||||
cxobj *
|
||||
transaction_src(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_src;
|
||||
}
|
||||
|
||||
/*! Get target database xml tree
|
||||
* @param[in] td transaction_data
|
||||
* @retval xml target xml tree containing wanted state
|
||||
*/
|
||||
cxobj *
|
||||
transaction_target(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_target;
|
||||
}
|
||||
|
||||
/*! Get delete xml vector, ie vector of xml nodes that are deleted src->target
|
||||
* @param[in] td transaction_data
|
||||
* @retval vec Vector of xml nodes
|
||||
*/
|
||||
cxobj **
|
||||
transaction_dvec(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_dvec;
|
||||
}
|
||||
|
||||
/*! Get length of delete xml vector
|
||||
* @param[in] td transaction_data
|
||||
* @retval len Length of vector of xml nodes
|
||||
* @see transaction_dvec
|
||||
*/
|
||||
size_t
|
||||
transaction_dlen(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_dlen;
|
||||
}
|
||||
|
||||
/*! Get add xml vector, ie vector of xml nodes that are added src->target
|
||||
* @param[in] td transaction_data
|
||||
* @retval vec Vector of xml nodes
|
||||
*/
|
||||
cxobj **
|
||||
transaction_avec(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_avec;
|
||||
}
|
||||
|
||||
/*! Get length of add xml vector
|
||||
* @param[in] td transaction_data
|
||||
* @retval len Length of vector of xml nodes
|
||||
* @see transaction_avec
|
||||
*/
|
||||
size_t
|
||||
transaction_alen(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_alen;
|
||||
}
|
||||
|
||||
/*! Get source changed xml vector, ie vector of xml nodes that changed
|
||||
* @param[in] td transaction_data
|
||||
* @retval vec Vector of xml nodes
|
||||
* These are only nodes of type LEAF.
|
||||
* For each node in this vector which contains the original value, there
|
||||
* is a node in tcvec with the changed value
|
||||
* @see transaction_dcvec
|
||||
*/
|
||||
cxobj **
|
||||
transaction_scvec(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_scvec;
|
||||
}
|
||||
|
||||
/*! Get target changed xml vector, ie vector of xml nodes that changed
|
||||
* @param[in] td transaction_data
|
||||
* @retval vec Vector of xml nodes
|
||||
* These are only nodes of type LEAF.
|
||||
* For each node in this vector which contains the original value, there
|
||||
* is a node in tcvec with the changed value
|
||||
* @see transaction_scvec
|
||||
*/
|
||||
cxobj **
|
||||
transaction_tcvec(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_dvec;
|
||||
}
|
||||
|
||||
/*! Get length of changed xml vector
|
||||
* @param[in] td transaction_data
|
||||
* @retval len Length of vector of xml nodes
|
||||
* This is the length of both the src change vector and the target change vector
|
||||
*/
|
||||
size_t
|
||||
transaction_clen(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_clen;
|
||||
}
|
||||
|
||||
int
|
||||
transaction_print(FILE *f,
|
||||
transaction_data th)
|
||||
{
|
||||
cxobj *xn;
|
||||
int i;
|
||||
transaction_data_t *td;
|
||||
|
||||
td = (transaction_data_t *)th;
|
||||
|
||||
fprintf(f, "Transaction id: 0x%llx\n", td->td_id);
|
||||
fprintf(f, "Removed\n=========\n");
|
||||
for (i=0; i<td->td_dlen; i++){
|
||||
xn = td->td_dvec[i];
|
||||
clicon_xml2file(f, xn, 0, 1);
|
||||
}
|
||||
fprintf(f, "Added\n=========\n");
|
||||
for (i=0; i<td->td_alen; i++){
|
||||
xn = td->td_avec[i];
|
||||
clicon_xml2file(f, xn, 0, 1);
|
||||
}
|
||||
fprintf(stderr, "Changed\n=========\n");
|
||||
for (i=0; i<td->td_clen; i++){
|
||||
xn = td->td_scvec[i];
|
||||
clicon_xml2file(f, xn, 0, 1);
|
||||
xn = td->td_tcvec[i];
|
||||
clicon_xml2file(f, xn, 0, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
60
apps/backend/clicon_backend_transaction.h
Normal file
60
apps/backend/clicon_backend_transaction.h
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* Part of the external API to plugins. Applications should not include
|
||||
* this file directly (only via clicon_backend.h).
|
||||
* Internal code should include this
|
||||
*/
|
||||
|
||||
#ifndef _CLICON_BACKEND_TRANSACTION_H_
|
||||
#define _CLICON_BACKEND_TRANSACTION_H_
|
||||
|
||||
/*
|
||||
* Types
|
||||
*/
|
||||
|
||||
/*! Generic downcall registration.
|
||||
* Enables any function to be called from (cli) frontend
|
||||
* to backend. Like an RPC on application-level.
|
||||
*/
|
||||
typedef int (*downcall_cb)(clicon_handle h, uint16_t op, uint16_t len,
|
||||
void *arg, uint16_t *retlen, void **retarg);
|
||||
|
||||
/* Transaction callback data accessors for client plugins
|
||||
* (defined in config_dbdep.c)
|
||||
* @see transaction_data_t internal structure
|
||||
*/
|
||||
typedef void *transaction_data;
|
||||
uint64_t transaction_id(transaction_data td);
|
||||
void *transaction_arg(transaction_data td);
|
||||
cxobj *transaction_src(transaction_data td);
|
||||
cxobj *transaction_target(transaction_data td);
|
||||
cxobj **transaction_dvec(transaction_data td);
|
||||
size_t transaction_dlen(transaction_data td);
|
||||
cxobj **transaction_avec(transaction_data td);
|
||||
size_t transaction_alen(transaction_data td);
|
||||
cxobj **transaction_scvec(transaction_data td);
|
||||
cxobj **transaction_tcvec(transaction_data td);
|
||||
size_t transaction_clen(transaction_data td);
|
||||
|
||||
int transaction_print(FILE *f, transaction_data th);
|
||||
|
||||
#endif /* _CLICON_BACKEND_TRANSACTION_H_ */
|
||||
BIN
apps/backend/clicon_transaction_api.o
Normal file
BIN
apps/backend/clicon_transaction_api.o
Normal file
Binary file not shown.
134
apps/cli/Makefile.in
Normal file
134
apps/cli/Makefile.in
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
#
|
||||
# Makefile
|
||||
#
|
||||
# Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
#
|
||||
# This file is part of CLICON.
|
||||
#
|
||||
# CLICON is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# CLICON is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with CLICON; see the file COPYING. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
|
||||
VPATH = @srcdir@
|
||||
srcdir = @srcdir@
|
||||
top_srcdir = @top_srcdir@
|
||||
CC = @CC@
|
||||
CFLAGS = @CFLAGS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
|
||||
prefix = @prefix@
|
||||
datarootdir = @datarootdir@
|
||||
exec_prefix = @exec_prefix@
|
||||
bindir = @bindir@
|
||||
libdir = @libdir@
|
||||
mandir = @mandir@
|
||||
libexecdir = @libexecdir@
|
||||
localstatedir = @localstatedir@
|
||||
sysconfdir = @sysconfdir@
|
||||
includedir = @includedir@
|
||||
|
||||
SH_SUFFIX = @SH_SUFFIX@
|
||||
CLICON_MAJOR = @CLICON_VERSION_MAJOR@
|
||||
CLICON_MINOR = @CLICON_VERSION_MINOR@
|
||||
|
||||
# Use this clicon lib for linking
|
||||
CLICON_LIB = libclicon.so.$(CLICON_MAJOR).$(CLICON_MINOR)
|
||||
# Location of system plugins
|
||||
CLICON_CLI_SYSDIR = $(libdir)/clicon/plugins/cli
|
||||
|
||||
# For dependency. A little strange that we rely on it being built in the src dir
|
||||
# even though it may exist in $(libdir). But the new version may not have been installed yet.
|
||||
LIBDEPS = $(top_srcdir)/lib/src/$(CLICON_LIB)
|
||||
|
||||
LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLICON_LIB) -lpthread
|
||||
CPPFLAGS = @CPPFLAGS@ -fPIC
|
||||
INCLUDES = -I. -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
|
||||
|
||||
APPL = clicon_cli
|
||||
SRC = cli_main.c
|
||||
OBJS = $(SRC:.c=.o)
|
||||
|
||||
MYNAME = clicon_cli
|
||||
MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX)
|
||||
MYLIB = $(MYLIBLINK).$(CLICON_MAJOR).$(CLICON_MINOR)
|
||||
MYLIBSO = $(MYLIBLINK).$(CLICON_MAJOR)
|
||||
|
||||
LIBSRC = cli_plugin.c cli_common.c cli_handle.c cli_generate.c
|
||||
LIBOBJS = $(LIBSRC:.c=.o)
|
||||
|
||||
all: $(MYLIB) $(APPL) test
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(LIBOBJS) *.core $(APPL) $(MYLIB) $(MYLIBSO) $(MYLIBLINK)
|
||||
|
||||
distclean: clean
|
||||
rm -f Makefile *~ .depend
|
||||
|
||||
# Put daemon in bin
|
||||
# Put other executables in libexec/
|
||||
# Also create a libexec/ directory for writeable/temporary files.
|
||||
# Put config file in etc/
|
||||
install: install-lib $(APPL)
|
||||
install -d $(DESTDIR)$(bindir)
|
||||
install $(APPL) $(DESTDIR)$(bindir)
|
||||
|
||||
install-lib: $(MYLIB)
|
||||
install -d $(DESTDIR)$(libdir)
|
||||
install $(MYLIB) $(DESTDIR)$(libdir)
|
||||
ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclicon_cli.so.2
|
||||
ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclicon_cli.so
|
||||
install -d $(DESTDIR)$(libdir)/clicon/plugins/cli
|
||||
|
||||
install-include: clicon_cli.h clicon_cli_api.h
|
||||
install -d $(DESTDIR)$(includedir)/clicon
|
||||
install -m 644 $^ $(DESTDIR)$(includedir)/clicon
|
||||
|
||||
uninstall:
|
||||
rm -f $(bindir)/$(APPL)
|
||||
rm -f $(libdir)/$(MYLIB)
|
||||
rm -f $(includedir)/clicon/*
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .c .o
|
||||
|
||||
.c.o:
|
||||
$(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" -DCLICON_CLI_SYSDIR=\"$(CLICON_CLI_SYSDIR)\" $(CFLAGS) -c $<
|
||||
|
||||
# Just link test programs
|
||||
test.c :
|
||||
echo "main(){}" > $@
|
||||
|
||||
test: test.c $(LIBOBJ)
|
||||
$(CC) $(INCLUDES) $(LDFLAGS) $< $(LIBOBJ) -L. -l:$(MYLIB) $(LIBS) -o $@
|
||||
|
||||
$(APPL): $(OBJS) $(MYLIBLINK) $(LIBDEPS)
|
||||
$(CC) $(LDFLAGS) $(OBJS) -L. -l:$(MYLIB) $(LIBS) -o $@
|
||||
|
||||
$(MYLIB) : $(LIBOBJS)
|
||||
$(CC) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJS) $(LIBS) -Wl,-soname=$(MYLIBSO)
|
||||
|
||||
# link-name is needed for application linking, eg for clicon_cli and clicon_config
|
||||
$(MYLIBLINK) : $(MYLIB)
|
||||
# ln -sf $(MYLIB) $(MYLIBSO)
|
||||
# ln -sf $(MYLIB) $@
|
||||
|
||||
TAGS:
|
||||
find . -name '*.[chyl]' -print | etags -
|
||||
|
||||
depend:
|
||||
$(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend
|
||||
|
||||
#include .depend
|
||||
|
||||
1930
apps/cli/cli_common.c
Normal file
1930
apps/cli/cli_common.c
Normal file
File diff suppressed because it is too large
Load diff
33
apps/cli/cli_common.h
Normal file
33
apps/cli/cli_common.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _CLI_COMMON_H_
|
||||
#define _CLI_COMMON_H_
|
||||
|
||||
void cli_signal_block(clicon_handle h);
|
||||
void cli_signal_unblock(clicon_handle h);
|
||||
|
||||
/* If you do not find a function here it may be in clicon_cli_api.h which is
|
||||
the external API */
|
||||
|
||||
#endif /* _CLI_COMMON_H_ */
|
||||
689
apps/cli/cli_generate.c
Normal file
689
apps/cli/cli_generate.c
Normal file
|
|
@ -0,0 +1,689 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* Translation between database specs
|
||||
* dbspec_key yang_spec CLIgen parse_tree
|
||||
* +-------------+ key2yang +-------------+ yang2cli +-------------+
|
||||
* | keyspec | -------------> | | ------------> | cli |
|
||||
* | A[].B !$a | yang2key | list{key A;}| | syntax |
|
||||
* +-------------+ <------------ +-------------+ +-------------+
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "clicon_cli_api.h"
|
||||
#include "cli_plugin.h"
|
||||
#include "cli_generate.h"
|
||||
|
||||
/* This is the default callback function. But this is typically overwritten */
|
||||
#define GENERATE_CALLBACK "cli_set"
|
||||
|
||||
/* variable expand function */
|
||||
#define GENERATE_EXPAND_LVEC "expand_dbvar_auto"
|
||||
#define GENERATE_EXPAND_XMLDB "expand_dbvar_dbxml"
|
||||
|
||||
/*=====================================================================
|
||||
* YANG generate CLI
|
||||
*=====================================================================*/
|
||||
#if 0 /* examples/ntp */
|
||||
ntp("Network Time Protocol"),cli_set("ntp");{
|
||||
logging("Configure NTP message logging"),cli_set("ntp.logging");{
|
||||
status (<status:bool>),cli_set("ntp.logging $status:bool");
|
||||
}
|
||||
server("Configure NTP Server") (<ipv4addr:ipv4addr>("IPv4 address of peer")),cli_set("ntp.server[] $!ipv4addr:ipv4addr");
|
||||
}
|
||||
#endif
|
||||
#if 0 /* examples/datamodel */
|
||||
|
||||
WITH COMPLETION:
|
||||
a (<x:number>|<x:number expand_dbvar_auto("candidate a[] $!x")>),cli_set("a[] $!x");{
|
||||
b,cli_set("a[].b $!x");{
|
||||
y (<y:string>|<y:string expand_dbvar_auto("candidate a[].b $!x $y")>),cli_set("a[].b $!x $y");
|
||||
}
|
||||
z (<z:string>|<z:string expand_dbvar_auto("candidate a[] $!x $z")>),cli_set("a[] $!x $z");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_CLIGEN_MAX2STR /* XXX cligen 3.6 feature */
|
||||
|
||||
/*! Print max value of a CLIgen variable type as string
|
||||
* @param[in] type CLIgen variable type
|
||||
* @param[out] str Max value printed in this string
|
||||
* @param[in] size Length of 'str'
|
||||
* @retval len How many bytes printed
|
||||
* @see cvtype_max2str_dup
|
||||
* You can use str=NULL to get the expected length.
|
||||
* The number of (potentially if str=NULL) written bytes is returned.
|
||||
*/
|
||||
static int
|
||||
cvtype_max2str(enum cv_type type, char *str, size_t size)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
switch (type){
|
||||
case CGV_INT8:
|
||||
len = snprintf(str, size, "%" PRId8, INT8_MAX);
|
||||
break;
|
||||
case CGV_INT16:
|
||||
len = snprintf(str, size, "%" PRId16, INT16_MAX);
|
||||
break;
|
||||
case CGV_INT32:
|
||||
len = snprintf(str, size, "%" PRId32, INT32_MAX);
|
||||
break;
|
||||
case CGV_INT64:
|
||||
len = snprintf(str, size, "%" PRId64, INT64_MAX);
|
||||
break;
|
||||
case CGV_UINT8:
|
||||
len = snprintf(str, size, "%" PRIu8, UINT8_MAX);
|
||||
break;
|
||||
case CGV_UINT16:
|
||||
len = snprintf(str, size, "%" PRIu16, UINT16_MAX);
|
||||
break;
|
||||
case CGV_UINT32:
|
||||
len = snprintf(str, size, "%" PRIu32, UINT32_MAX);
|
||||
break;
|
||||
case CGV_UINT64:
|
||||
len = snprintf(str, size, "%" PRIu64, UINT64_MAX);
|
||||
break;
|
||||
case CGV_DEC64:
|
||||
len = snprintf(str, size, "%" PRId64 ".0", INT64_MAX);
|
||||
break;
|
||||
case CGV_BOOL:
|
||||
len = snprintf(str, size, "true");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/*! Print max value of a CLIgen variable type as string
|
||||
*
|
||||
* The string should be freed after use.
|
||||
* @param[in] type CLIgen variable type
|
||||
* @retval str Malloced string containing value. Should be freed after use.
|
||||
* @see cvtype_max2str
|
||||
*/
|
||||
static char *
|
||||
cvtype_max2str_dup(enum cv_type type)
|
||||
{
|
||||
int len;
|
||||
char *str;
|
||||
|
||||
if ((len = cvtype_max2str(type, NULL, 0)) < 0)
|
||||
return NULL;
|
||||
if ((str = (char *)malloc (len+1)) == NULL)
|
||||
return NULL;
|
||||
memset (str, '\0', len+1);
|
||||
if ((cvtype_max2str(type, str, len+1)) < 0){
|
||||
free(str);
|
||||
return NULL;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
#endif /* HAVE_CLIGEN_MAX2STR */
|
||||
|
||||
/*! Create cligen variable expand entry with xmlkey format string as argument
|
||||
* @param[in] h clicon handle
|
||||
* @param[in] ys yang_stmt of the node at hand
|
||||
* @param[in] cvtype Type of the cligen variable
|
||||
* @param[in] cb0 The string where the result format string is inserted.
|
||||
* @see expand_dbvar_dbxml This is where the expand string is used
|
||||
*/
|
||||
static int
|
||||
cli_expand_var_generate(clicon_handle h,
|
||||
yang_stmt *ys,
|
||||
enum cv_type cvtype,
|
||||
cbuf *cb0)
|
||||
{
|
||||
int retval = -1;
|
||||
char *xkfmt = NULL;
|
||||
|
||||
if (yang2xmlkeyfmt(ys, &xkfmt) < 0)
|
||||
goto done;
|
||||
cprintf(cb0, "|<%s:%s %s(\"candidate %s\")>",
|
||||
ys->ys_argument,
|
||||
cv_type2str(cvtype),
|
||||
GENERATE_EXPAND_XMLDB,
|
||||
xkfmt);
|
||||
retval = 0;
|
||||
done:
|
||||
if (xkfmt)
|
||||
free(xkfmt);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Create callback with xmlkey format string as argument
|
||||
* @param[in] h clicon handle
|
||||
* @param[in] ys yang_stmt of the node at hand
|
||||
* @param[in] cb0 The string where the result format string is inserted.
|
||||
* @see cli_dbxml This is where the xmlkeyfmt string is used
|
||||
*/
|
||||
static int
|
||||
cli_callback_generate(clicon_handle h,
|
||||
yang_stmt *ys,
|
||||
cbuf *cb0)
|
||||
{
|
||||
int retval = -1;
|
||||
char *xkfmt = NULL;
|
||||
|
||||
if (yang2xmlkeyfmt(ys, &xkfmt) < 0)
|
||||
goto done;
|
||||
cprintf(cb0, ",%s(\"%s\")", GENERATE_CALLBACK, xkfmt);
|
||||
retval = 0;
|
||||
done:
|
||||
if (xkfmt)
|
||||
free(xkfmt);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int yang2cli_stmt(clicon_handle h, yang_stmt *ys,
|
||||
cbuf *cb0,
|
||||
enum genmodel_type gt,
|
||||
int level);
|
||||
|
||||
/*
|
||||
* Check for completion (of already existent values), ranges (eg range[min:max]) and
|
||||
* patterns, (eg regexp:"[0.9]*").
|
||||
*/
|
||||
static int
|
||||
yang2cli_var_sub(clicon_handle h,
|
||||
yang_stmt *ys,
|
||||
cbuf *cb0,
|
||||
char *description,
|
||||
enum cv_type cvtype,
|
||||
yang_stmt *ytype, /* resolved type */
|
||||
int options,
|
||||
cg_var *mincv,
|
||||
cg_var *maxcv,
|
||||
char *pattern,
|
||||
uint8_t fraction_digits
|
||||
)
|
||||
{
|
||||
int retval = -1;
|
||||
char *type;
|
||||
char *r;
|
||||
yang_stmt *yi = NULL;
|
||||
int i = 0;
|
||||
char *cvtypestr;
|
||||
int completion;
|
||||
|
||||
/* enumeration already gives completion */
|
||||
if (cvtype == CGV_VOID){
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
type = ytype?ytype->ys_argument:NULL;
|
||||
if (type)
|
||||
completion = clicon_cli_genmodel_completion(h) &&
|
||||
strcmp(type, "enumeration") != 0 &&
|
||||
strcmp(type, "bits") != 0;
|
||||
else
|
||||
completion = clicon_cli_genmodel_completion(h);
|
||||
|
||||
if (completion)
|
||||
cprintf(cb0, "(");
|
||||
cvtypestr = cv_type2str(cvtype);
|
||||
cprintf(cb0, "<%s:%s", ys->ys_argument, cvtypestr);
|
||||
#if 0
|
||||
if (type && (strcmp(type, "identityref") == 0)){
|
||||
yang_stmt *ybase;
|
||||
if ((ybase = yang_find((yang_node*)ytype, Y_BASE, NULL)) != NULL){
|
||||
cprintf(cb0, " choice:");
|
||||
i = 0;
|
||||
/* for every found identity derived from base-type , do: */
|
||||
{
|
||||
if (yi->ys_keyword != Y_ENUM && yi->ys_keyword != Y_BIT)
|
||||
continue;
|
||||
if (i)
|
||||
cprintf(cb0, "|");
|
||||
cprintf(cb0, "%s", yi->ys_argument);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
if (type && (strcmp(type, "enumeration") == 0 || strcmp(type, "bits") == 0)){
|
||||
cprintf(cb0, " choice:");
|
||||
i = 0;
|
||||
while ((yi = yn_each((yang_node*)ytype, yi)) != NULL){
|
||||
if (yi->ys_keyword != Y_ENUM && yi->ys_keyword != Y_BIT)
|
||||
continue;
|
||||
if (i)
|
||||
cprintf(cb0, "|");
|
||||
cprintf(cb0, "%s", yi->ys_argument);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (options & YANG_OPTIONS_FRACTION_DIGITS)
|
||||
cprintf(cb0, " fraction-digits:%u", fraction_digits);
|
||||
if (options & (YANG_OPTIONS_RANGE|YANG_OPTIONS_LENGTH)){
|
||||
cprintf(cb0, " %s[", (options&YANG_OPTIONS_RANGE)?"range":"length");
|
||||
if (mincv){
|
||||
if ((r = cv2str_dup(mincv)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cv2str_dup");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cb0, "%s:", r);
|
||||
free(r);
|
||||
}
|
||||
if (maxcv != NULL){
|
||||
if ((r = cv2str_dup(maxcv)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cv2str_dup");
|
||||
goto done;
|
||||
}
|
||||
|
||||
}
|
||||
else{ /* Cligen does not have 'max' keyword in range so need to find actual
|
||||
max value of type if yang range expression is 0..max */
|
||||
if ((r = cvtype_max2str_dup(cvtype)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cvtype_max2str");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
cprintf(cb0, "%s]", r);
|
||||
free(r);
|
||||
}
|
||||
if (options & YANG_OPTIONS_PATTERN)
|
||||
cprintf(cb0, " regexp:\"%s\"", pattern);
|
||||
|
||||
cprintf(cb0, ">");
|
||||
if (description)
|
||||
cprintf(cb0, "(\"%s\")", description);
|
||||
if (completion){
|
||||
if (cli_expand_var_generate(h, ys, cvtype, cb0) < 0)
|
||||
goto done;
|
||||
if (description)
|
||||
cprintf(cb0, "(\"%s\")", description);
|
||||
cprintf(cb0, ")");
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Translate a yang leaf to cligen variable
|
||||
* Make a type lookup and complete a cligen variable expression such as <a:string>.
|
||||
* One complication is yang union, that needs a recursion since it consists of sub-types.
|
||||
* eg type union{ type int32; type string } --> (<x:int32>| <x:string>)
|
||||
*/
|
||||
static int
|
||||
yang2cli_var(clicon_handle h,
|
||||
yang_stmt *ys,
|
||||
cbuf *cb0,
|
||||
char *description)
|
||||
{
|
||||
int retval = -1;
|
||||
char *type; /* orig type */
|
||||
yang_stmt *yrestype; /* resolved type */
|
||||
char *restype; /* resolved type */
|
||||
cg_var *mincv = NULL;
|
||||
cg_var *maxcv = NULL;
|
||||
char *pattern = NULL;
|
||||
yang_stmt *yt = NULL;
|
||||
yang_stmt *yrt;
|
||||
uint8_t fraction_digits = 0;
|
||||
enum cv_type cvtype;
|
||||
int options = 0;
|
||||
int i;
|
||||
|
||||
if (yang_type_get(ys, &type, &yrestype,
|
||||
&options, &mincv, &maxcv, &pattern, &fraction_digits) < 0)
|
||||
goto done;
|
||||
restype = yrestype?yrestype->ys_argument:NULL;
|
||||
if (clicon_type2cv(type, restype, &cvtype) < 0)
|
||||
goto done;
|
||||
/* Note restype can be NULL here for example with unresolved hardcoded uuid */
|
||||
if (restype && strcmp(restype, "union") == 0){
|
||||
/* Union: loop over resolved type's sub-types */
|
||||
cprintf(cb0, "(");
|
||||
yt = NULL;
|
||||
i = 0;
|
||||
while ((yt = yn_each((yang_node*)yrestype, yt)) != NULL){
|
||||
if (yt->ys_keyword != Y_TYPE)
|
||||
continue;
|
||||
if (i++)
|
||||
cprintf(cb0, "|");
|
||||
if (yang_type_resolve(ys, yt, &yrt,
|
||||
&options, &mincv, &maxcv, &pattern, &fraction_digits) < 0)
|
||||
goto done;
|
||||
restype = yrt?yrt->ys_argument:NULL;
|
||||
if (clicon_type2cv(type, restype, &cvtype) < 0)
|
||||
goto done;
|
||||
if ((retval = yang2cli_var_sub(h, ys, cb0, description, cvtype, yrt,
|
||||
options, mincv, maxcv, pattern, fraction_digits)) < 0)
|
||||
|
||||
goto done;
|
||||
|
||||
}
|
||||
cprintf(cb0, ")");
|
||||
}
|
||||
else
|
||||
if ((retval = yang2cli_var_sub(h, ys, cb0, description, cvtype, yrestype,
|
||||
options, mincv, maxcv, pattern, fraction_digits)) < 0)
|
||||
goto done;
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] callback If set, include a "; cli_set()" callback, otherwise not.
|
||||
*/
|
||||
static int
|
||||
yang2cli_leaf(clicon_handle h,
|
||||
yang_stmt *ys,
|
||||
cbuf *cbuf,
|
||||
enum genmodel_type gt,
|
||||
int level,
|
||||
int callback)
|
||||
{
|
||||
yang_stmt *yd; /* description */
|
||||
int retval = -1;
|
||||
char *description = NULL;
|
||||
|
||||
/* description */
|
||||
if ((yd = yang_find((yang_node*)ys, Y_DESCRIPTION, NULL)) != NULL)
|
||||
description = yd->ys_argument;
|
||||
cprintf(cbuf, "%*s", level*3, "");
|
||||
if (gt == GT_VARS|| gt == GT_ALL){
|
||||
cprintf(cbuf, "%s", ys->ys_argument);
|
||||
if (yd != NULL)
|
||||
cprintf(cbuf, "(\"%s\")", yd->ys_argument);
|
||||
cprintf(cbuf, " ");
|
||||
yang2cli_var(h, ys, cbuf, description);
|
||||
}
|
||||
else
|
||||
yang2cli_var(h, ys, cbuf, description);
|
||||
if (callback){
|
||||
if (cli_callback_generate(h, ys, cbuf) < 0)
|
||||
goto done;
|
||||
cprintf(cbuf, ";\n");
|
||||
}
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
yang2cli_container(clicon_handle h,
|
||||
yang_stmt *ys,
|
||||
cbuf *cbuf,
|
||||
enum genmodel_type gt,
|
||||
int level)
|
||||
{
|
||||
yang_stmt *yc;
|
||||
yang_stmt *yd;
|
||||
int i;
|
||||
int retval = -1;
|
||||
|
||||
cprintf(cbuf, "%*s%s", level*3, "", ys->ys_argument);
|
||||
if ((yd = yang_find((yang_node*)ys, Y_DESCRIPTION, NULL)) != NULL)
|
||||
cprintf(cbuf, "(\"%s\")", yd->ys_argument);
|
||||
if (cli_callback_generate(h, ys, cbuf) < 0)
|
||||
goto done;
|
||||
cprintf(cbuf, ";{\n");
|
||||
for (i=0; i<ys->ys_len; i++)
|
||||
if ((yc = ys->ys_stmt[i]) != NULL)
|
||||
if (yang2cli_stmt(h, yc, cbuf, gt, level+1) < 0)
|
||||
goto done;
|
||||
cprintf(cbuf, "%*s}\n", level*3, "");
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int
|
||||
yang2cli_list(clicon_handle h,
|
||||
yang_stmt *ys,
|
||||
cbuf *cbuf,
|
||||
enum genmodel_type gt,
|
||||
int level)
|
||||
{
|
||||
yang_stmt *yc;
|
||||
yang_stmt *yd;
|
||||
yang_stmt *ykey;
|
||||
yang_stmt *yleaf;
|
||||
int i;
|
||||
cg_var *cvi;
|
||||
char *keyname;
|
||||
cvec *cvk = NULL; /* vector of index keys */
|
||||
int retval = -1;
|
||||
|
||||
cprintf(cbuf, "%*s%s", level*3, "", ys->ys_argument);
|
||||
if ((yd = yang_find((yang_node*)ys, Y_DESCRIPTION, NULL)) != NULL)
|
||||
cprintf(cbuf, "(\"%s\")", yd->ys_argument);
|
||||
/* Loop over all key variables */
|
||||
if ((ykey = yang_find((yang_node*)ys, Y_KEY, NULL)) == NULL){
|
||||
clicon_err(OE_XML, 0, "List statement \"%s\" has no key", ys->ys_argument);
|
||||
goto done;
|
||||
}
|
||||
/* The value is a list of keys: <key>[ <key>]* */
|
||||
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
|
||||
goto done;
|
||||
cvi = NULL;
|
||||
/* Iterate over individual keys */
|
||||
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
|
||||
keyname = cv_string_get(cvi);
|
||||
if ((yleaf = yang_find((yang_node*)ys, Y_LEAF, keyname)) == NULL){
|
||||
clicon_err(OE_XML, 0, "List statement \"%s\" has no key leaf \"%s\"",
|
||||
ys->ys_argument, keyname);
|
||||
goto done;
|
||||
}
|
||||
/* Print key variable now, and skip it in loop below
|
||||
Note, only print callback on last statement
|
||||
*/
|
||||
if (yang2cli_leaf(h, yleaf, cbuf, gt==GT_VARS?GT_NONE:gt, level+1,
|
||||
cvec_next(cvk, cvi)?0:1) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
cprintf(cbuf, "{\n");
|
||||
for (i=0; i<ys->ys_len; i++)
|
||||
if ((yc = ys->ys_stmt[i]) != NULL){
|
||||
/* cvk is a cvec of strings containing variable names
|
||||
yc is a leaf that may match one of the values of cvk.
|
||||
*/
|
||||
cvi = NULL;
|
||||
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
|
||||
keyname = cv_string_get(cvi);
|
||||
if (strcmp(keyname, yc->ys_argument) == 0)
|
||||
break;
|
||||
}
|
||||
if (cvi != NULL)
|
||||
continue;
|
||||
if (yang2cli_stmt(h, yc, cbuf, gt, level+1) < 0)
|
||||
goto done;
|
||||
}
|
||||
cprintf(cbuf, "%*s}\n", level*3, "");
|
||||
retval = 0;
|
||||
done:
|
||||
if (cvk)
|
||||
cvec_free(cvk);
|
||||
return retval;
|
||||
}
|
||||
/*! Generate cli code for yang choice statement
|
||||
|
||||
Example:
|
||||
choice interface-type {
|
||||
container ethernet { ... }
|
||||
container fddi { ... }
|
||||
}
|
||||
@Note Removes 'meta-syntax' from cli syntax. They are not shown when xml is
|
||||
translated to cli. and therefore input-syntax != output syntax. Which is bad
|
||||
*/
|
||||
static int
|
||||
yang2cli_choice(clicon_handle h,
|
||||
yang_stmt *ys,
|
||||
cbuf *cbuf,
|
||||
enum genmodel_type gt,
|
||||
int level)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *yc;
|
||||
int i;
|
||||
|
||||
for (i=0; i<ys->ys_len; i++)
|
||||
if ((yc = ys->ys_stmt[i]) != NULL){
|
||||
switch (yc->ys_keyword){
|
||||
case Y_CASE:
|
||||
if (yang2cli_stmt(h, yc, cbuf, gt, level+2) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case Y_CONTAINER:
|
||||
case Y_LEAF:
|
||||
case Y_LEAF_LIST:
|
||||
case Y_LIST:
|
||||
default:
|
||||
if (yang2cli_stmt(h, yc, cbuf, gt, level+1) < 0)
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Translate yang-stmt to CLIgen syntax.
|
||||
*/
|
||||
static int
|
||||
yang2cli_stmt(clicon_handle h,
|
||||
yang_stmt *ys,
|
||||
cbuf *cbuf,
|
||||
enum genmodel_type gt,
|
||||
int level /* indentation level for pretty-print */
|
||||
)
|
||||
{
|
||||
yang_stmt *yc;
|
||||
int retval = -1;
|
||||
int i;
|
||||
|
||||
if (yang_config(ys)){
|
||||
switch (ys->ys_keyword){
|
||||
case Y_GROUPING:
|
||||
case Y_RPC:
|
||||
case Y_AUGMENT:
|
||||
return 0;
|
||||
break;
|
||||
case Y_CONTAINER:
|
||||
if (yang2cli_container(h, ys, cbuf, gt, level) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case Y_LIST:
|
||||
if (yang2cli_list(h, ys, cbuf, gt, level) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case Y_CHOICE:
|
||||
if (yang2cli_choice(h, ys, cbuf, gt, level) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case Y_LEAF_LIST:
|
||||
case Y_LEAF:
|
||||
if (yang2cli_leaf(h, ys, cbuf, gt, level, 1) < 0)
|
||||
goto done;
|
||||
break;
|
||||
default:
|
||||
for (i=0; i<ys->ys_len; i++)
|
||||
if ((yc = ys->ys_stmt[i]) != NULL)
|
||||
if (yang2cli_stmt(h, yc, cbuf, gt, level+1) < 0)
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
/*! Translate from a yang specification into a CLIgen syntax.
|
||||
*
|
||||
* Print a CLIgen syntax to cbuf string, then parse it.
|
||||
* @param gt - how to generate CLI:
|
||||
* VARS: generate keywords for regular vars only not index
|
||||
* ALL: generate keywords for all variables including index
|
||||
*/
|
||||
int
|
||||
yang2cli(clicon_handle h,
|
||||
yang_spec *yspec,
|
||||
parse_tree *ptnew,
|
||||
enum genmodel_type gt)
|
||||
{
|
||||
cbuf *cbuf;
|
||||
int i;
|
||||
int retval = -1;
|
||||
yang_stmt *ymod = NULL;
|
||||
cvec *globals; /* global variables from syntax */
|
||||
|
||||
if ((cbuf = cbuf_new()) == NULL){
|
||||
clicon_err(OE_XML, errno, "%s: cbuf_new", __FUNCTION__);
|
||||
goto done;
|
||||
}
|
||||
/* Traverse YANG specification: loop through statements */
|
||||
for (i=0; i<yspec->yp_len; i++)
|
||||
if ((ymod = yspec->yp_stmt[i]) != NULL){
|
||||
if (yang2cli_stmt(h, ymod, cbuf, gt, 0) < 0)
|
||||
goto done;
|
||||
}
|
||||
clicon_debug(1, "%s: buf\n%s\n", __FUNCTION__, cbuf_get(cbuf));
|
||||
/* Parse the buffer using cligen parser. XXX why this?*/
|
||||
if ((globals = cvec_new(0)) == NULL)
|
||||
goto done;
|
||||
/* load cli syntax */
|
||||
if (cligen_parse_str(cli_cligen(h), cbuf_get(cbuf),
|
||||
"yang2cli", ptnew, globals) < 0)
|
||||
goto done;
|
||||
cvec_free(globals);
|
||||
/* handle=NULL for global namespace, this means expand callbacks must be in
|
||||
CLICON namespace, not in a cli frontend plugin. */
|
||||
if (cligen_expand_str2fn(*ptnew, expand_str2fn, NULL) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
cbuf_free(cbuf);
|
||||
return retval;
|
||||
}
|
||||
32
apps/cli/cli_generate.h
Normal file
32
apps/cli/cli_generate.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _CLI_GENERATE_H_
|
||||
#define _CLI_GENERATE_H_
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int yang2cli(clicon_handle h, yang_spec *yspec, parse_tree *ptnew,
|
||||
enum genmodel_type gt);
|
||||
|
||||
#endif /* _CLI_GENERATE_H_ */
|
||||
295
apps/cli/cli_handle.c
Normal file
295
apps/cli/cli_handle.c
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <syslog.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <dirent.h>
|
||||
#include <grp.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "clicon_cli_api.h"
|
||||
#include "cli_plugin.h"
|
||||
#include "cli_handle.h"
|
||||
|
||||
#define CLICON_MAGIC 0x99aafabe
|
||||
|
||||
#define handle(h) (assert(clicon_handle_check(h)==0),(struct cli_handle *)(h))
|
||||
#define cligen(h) (handle(h)->cl_cligen)
|
||||
|
||||
/*
|
||||
* cli_handle
|
||||
* first part of this is header, same for clicon_handle and config_handle.
|
||||
* Access functions for common fields are found in clicon lib: clicon_options.[ch]
|
||||
* This file should only contain access functions for the _specific_
|
||||
* entries in the struct below.
|
||||
*/
|
||||
struct cli_handle {
|
||||
int cl_magic; /* magic (HDR)*/
|
||||
clicon_hash_t *cl_copt; /* clicon option list (HDR) */
|
||||
clicon_hash_t *cl_data; /* internal clicon data (HDR) */
|
||||
/* ------ end of common handle ------ */
|
||||
cligen_handle cl_cligen; /* cligen handle */
|
||||
|
||||
int cl_send2backend; /* Send changes to configuration daemon */
|
||||
enum candidate_db_type cl_candidate_type;
|
||||
cli_syntax_t *cl_stx; /* syntax structure */
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
* cli_handle_init
|
||||
* returns a clicon handle for other CLICON API calls
|
||||
*/
|
||||
clicon_handle
|
||||
cli_handle_init(void)
|
||||
{
|
||||
struct cli_handle *cl;
|
||||
cligen_handle clih = NULL;
|
||||
clicon_handle h = NULL;
|
||||
|
||||
if ((cl = (struct cli_handle *)clicon_handle_init0(sizeof(struct cli_handle))) == NULL)
|
||||
return NULL;
|
||||
|
||||
if ((clih = cligen_init()) == NULL){
|
||||
clicon_handle_exit((clicon_handle)cl);
|
||||
goto done;
|
||||
}
|
||||
cligen_userhandle_set(clih, cl);
|
||||
cl->cl_cligen = clih;
|
||||
cl->cl_candidate_type = CANDIDATE_DB_SHARED;
|
||||
h = (clicon_handle)cl;
|
||||
done:
|
||||
return h;
|
||||
}
|
||||
|
||||
/*
|
||||
* cli_handle_exit
|
||||
* frees clicon handle
|
||||
*/
|
||||
int
|
||||
cli_handle_exit(clicon_handle h)
|
||||
{
|
||||
cligen_handle ch = cligen(h);
|
||||
|
||||
clicon_handle_exit(h); /* frees h and options */
|
||||
cligen_exit(ch);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*----------------------------------------------------------
|
||||
* cli-specific handle access functions
|
||||
*----------------------------------------------------------*/
|
||||
/*! Send changes to configuration daemon or let client handle it itself. Default is 1 */
|
||||
int
|
||||
cli_set_send2backend(clicon_handle h, int send2backend)
|
||||
{
|
||||
struct cli_handle *cl = handle(h);
|
||||
|
||||
cl->cl_send2backend = send2backend;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Get status of whether to send changes to configuration daemon. */
|
||||
int
|
||||
cli_send2backend(clicon_handle h)
|
||||
{
|
||||
struct cli_handle *cl = handle(h);
|
||||
|
||||
return cl->cl_send2backend;
|
||||
}
|
||||
|
||||
enum candidate_db_type
|
||||
cli_candidate_type(clicon_handle h)
|
||||
{
|
||||
struct cli_handle *cl = handle(h);
|
||||
|
||||
return cl->cl_candidate_type;
|
||||
}
|
||||
|
||||
int
|
||||
cli_set_candidate_type(clicon_handle h, enum candidate_db_type type)
|
||||
{
|
||||
struct cli_handle *cl = handle(h);
|
||||
|
||||
cl->cl_candidate_type = type;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Current syntax-group */
|
||||
cli_syntax_t *
|
||||
cli_syntax(clicon_handle h)
|
||||
{
|
||||
struct cli_handle *cl = handle(h);
|
||||
return cl->cl_stx;
|
||||
}
|
||||
|
||||
int
|
||||
cli_syntax_set(clicon_handle h, cli_syntax_t *stx)
|
||||
{
|
||||
struct cli_handle *cl = handle(h);
|
||||
cl->cl_stx = stx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------
|
||||
* cligen access functions
|
||||
*----------------------------------------------------------*/
|
||||
cligen_handle
|
||||
cli_cligen(clicon_handle h)
|
||||
{
|
||||
return cligen(h);
|
||||
}
|
||||
|
||||
/*
|
||||
* cli_interactive and clicon_eval
|
||||
*/
|
||||
int
|
||||
cli_exiting(clicon_handle h)
|
||||
{
|
||||
cligen_handle ch = cligen(h);
|
||||
|
||||
return cligen_exiting(ch);
|
||||
}
|
||||
/*
|
||||
* cli_common.c: cli_quit
|
||||
* cli_interactive()
|
||||
*/
|
||||
int
|
||||
cli_set_exiting(clicon_handle h, int exiting)
|
||||
{
|
||||
cligen_handle ch = cligen(h);
|
||||
|
||||
return cligen_exiting_set(ch, exiting);
|
||||
}
|
||||
|
||||
char
|
||||
cli_comment(clicon_handle h)
|
||||
{
|
||||
cligen_handle ch = cligen(h);
|
||||
|
||||
return cligen_comment(ch);
|
||||
}
|
||||
|
||||
char
|
||||
cli_set_comment(clicon_handle h, char c)
|
||||
{
|
||||
cligen_handle ch = cligen(h);
|
||||
|
||||
return cligen_comment_set(ch, c);
|
||||
}
|
||||
|
||||
char
|
||||
cli_tree_add(clicon_handle h, char *tree, parse_tree pt)
|
||||
{
|
||||
cligen_handle ch = cligen(h);
|
||||
|
||||
return cligen_tree_add(ch, tree, pt);
|
||||
}
|
||||
|
||||
char *
|
||||
cli_tree_active(clicon_handle h)
|
||||
{
|
||||
cligen_handle ch = cligen(h);
|
||||
|
||||
return cligen_tree_active(ch);
|
||||
}
|
||||
|
||||
int
|
||||
cli_tree_active_set(clicon_handle h, char *treename)
|
||||
{
|
||||
cligen_handle ch = cligen(h);
|
||||
|
||||
return cligen_tree_active_set(ch, treename);
|
||||
}
|
||||
|
||||
parse_tree *
|
||||
cli_tree(clicon_handle h, char *name)
|
||||
{
|
||||
cligen_handle ch = cligen(h);
|
||||
|
||||
return cligen_tree(ch, name);
|
||||
}
|
||||
|
||||
int
|
||||
cli_parse_file(clicon_handle h,
|
||||
FILE *f,
|
||||
char *name, /* just for errs */
|
||||
parse_tree *pt,
|
||||
cvec *globals)
|
||||
{
|
||||
cligen_handle ch = cligen(h);
|
||||
|
||||
return cligen_parse_file(ch, f, name, pt, globals);
|
||||
}
|
||||
|
||||
int
|
||||
cli_susp_hook(clicon_handle h, cli_susphook_t *fn)
|
||||
{
|
||||
cligen_handle ch = cligen(h);
|
||||
|
||||
/* This assume first arg of fn can be treated as void* */
|
||||
return cligen_susp_hook(ch, (cligen_susp_cb_t*)fn);
|
||||
}
|
||||
|
||||
char *
|
||||
cli_nomatch(clicon_handle h)
|
||||
{
|
||||
cligen_handle ch = cligen(h);
|
||||
|
||||
return cligen_nomatch(ch);
|
||||
}
|
||||
|
||||
int
|
||||
cli_prompt_set(clicon_handle h, char *prompt)
|
||||
{
|
||||
cligen_handle ch = cligen(h);
|
||||
return cligen_prompt_set(ch, prompt);
|
||||
}
|
||||
|
||||
int
|
||||
cli_logsyntax_set(clicon_handle h, int status)
|
||||
{
|
||||
cligen_handle ch = cligen(h);
|
||||
return cligen_logsyntax_set(ch, status);
|
||||
}
|
||||
58
apps/cli/cli_handle.h
Normal file
58
apps/cli/cli_handle.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _CLI_HANDLE_H_
|
||||
#define _CLI_HANDLE_H_
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
* Internal prototypes. For exported functions see clicon_cli_api.h
|
||||
*/
|
||||
char cli_tree_add(clicon_handle h, char *tree, parse_tree pt);
|
||||
|
||||
int cli_parse_file(clicon_handle h,
|
||||
FILE *f,
|
||||
char *name, /* just for errs */
|
||||
parse_tree *pt,
|
||||
cvec *globals);
|
||||
|
||||
char *cli_tree_active(clicon_handle h);
|
||||
|
||||
int cli_tree_active_set(clicon_handle h, char *treename);
|
||||
|
||||
parse_tree *cli_tree(clicon_handle h, char *name);
|
||||
|
||||
int cli_susp_hook(clicon_handle h, cli_susphook_t *fn);
|
||||
|
||||
char *cli_nomatch(clicon_handle h);
|
||||
|
||||
int cli_prompt_set(clicon_handle h, char *prompt);
|
||||
|
||||
int cli_logsyntax_set(clicon_handle h, int status);
|
||||
|
||||
/* Internal functions for handling cli groups */
|
||||
|
||||
cli_syntax_t *cli_syntax(clicon_handle h);
|
||||
int cli_syntax_set(clicon_handle h, cli_syntax_t *stx);
|
||||
|
||||
#endif /* _CLI_HANDLE_H_ */
|
||||
407
apps/cli/cli_main.c
Normal file
407
apps/cli/cli_main.c
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#define __USE_GNU /* strverscmp */
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <dlfcn.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
#include <netinet/in.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "clicon_cli_api.h"
|
||||
|
||||
#include "cli_plugin.h"
|
||||
#include "cli_generate.h"
|
||||
#include "cli_common.h"
|
||||
#include "cli_handle.h"
|
||||
|
||||
/* Command line options to be passed to getopt(3) */
|
||||
#define CLI_OPTS "hD:f:F:1u:d:m:cP:qpGLl:"
|
||||
|
||||
static int
|
||||
cli_terminate(clicon_handle h)
|
||||
{
|
||||
yang_spec *yspec;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) != NULL)
|
||||
yspec_free(yspec);
|
||||
cli_plugin_finish(h);
|
||||
exit_candidate_db(h);
|
||||
cli_handle_exit(h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* cli_sig_term
|
||||
* Unlink pidfile and quit
|
||||
*/
|
||||
static void
|
||||
cli_sig_term(int arg)
|
||||
{
|
||||
clicon_log(LOG_NOTICE, "%s: %u Terminated (killed by sig %d)",
|
||||
__PROGRAM__, getpid(), arg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Setup signal handlers
|
||||
*/
|
||||
static void
|
||||
cli_signal_init (clicon_handle h)
|
||||
{
|
||||
cli_signal_block(h);
|
||||
set_signal(SIGTERM, cli_sig_term, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
cli_interactive(clicon_handle h)
|
||||
{
|
||||
int res;
|
||||
char *cmd;
|
||||
char *new_mode;
|
||||
int result;
|
||||
|
||||
/* Loop through all commands */
|
||||
while(!cli_exiting(h)) {
|
||||
// save_mode =
|
||||
new_mode = cli_syntax_mode(h);
|
||||
if ((cmd = clicon_cliread(h)) == NULL) {
|
||||
cli_set_exiting(h, 1); /* EOF */
|
||||
break;
|
||||
}
|
||||
if ((res = clicon_parse(h, cmd, &new_mode, &result)) < 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
usage(char *argv0, clicon_handle h)
|
||||
{
|
||||
char *confsock = clicon_sock(h);
|
||||
char *plgdir = clicon_cli_dir(h);
|
||||
|
||||
fprintf(stderr, "usage:%s [options] [commands]\n"
|
||||
"where commands is a CLI command or options passed to the main plugin\n"
|
||||
"where options are\n"
|
||||
"\t-h \t\tHelp\n"
|
||||
"\t-D <level> \tDebug\n"
|
||||
"\t-f <file> \tConfig-file (mandatory)\n"
|
||||
"\t-F <file> \tRead commands from file (default stdin)\n"
|
||||
"\t-1\t\tDo not enter interactive mode\n"
|
||||
"\t-u <sockpath>\tconfig UNIX domain path (default: %s)\n"
|
||||
"\t-d <dir>\tSpecify plugin directory (default: %s)\n"
|
||||
"\t-m <mode>\tSpecify plugin syntax mode\n"
|
||||
"\t-c \t\tWrite to candidate db directly, not via config backend\n"
|
||||
"\t-P <dbname> \tWrite to private database\n"
|
||||
"\t-q \t\tQuiet mode, dont print greetings or prompt, terminate on ctrl-C\n"
|
||||
"\t-p \t\tPrint database yang specification\n"
|
||||
"\t-G \t\tPrint CLI syntax generated from dbspec (if CLICON_CLI_GENMODEL enabled)\n"
|
||||
"\t-L \t\tDebug print dynamic CLI syntax including completions and expansions\n"
|
||||
"\t-l <s|e|o> \tLog on (s)yslog, std(e)rr or std(o)ut (stderr is default)\n",
|
||||
argv0,
|
||||
confsock ? confsock : "none",
|
||||
plgdir ? plgdir : "none"
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char c;
|
||||
enum candidate_db_type dbtype;
|
||||
char private_db[MAXPATHLEN];
|
||||
int once;
|
||||
char *tmp;
|
||||
char *argv0 = argv[0];
|
||||
clicon_handle h;
|
||||
int printspec = 0;
|
||||
int printgen = 0;
|
||||
int logclisyntax = 0;
|
||||
int help = 0;
|
||||
char *treename;
|
||||
char *running_db;
|
||||
int logdst = CLICON_LOG_STDERR;
|
||||
|
||||
/* Defaults */
|
||||
|
||||
/* In the startup, logs to stderr & debug flag set later */
|
||||
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
|
||||
/* Initiate CLICON handle */
|
||||
if ((h = cli_handle_init()) == NULL)
|
||||
goto done;
|
||||
if (cli_plugin_init(h) != 0)
|
||||
goto done;
|
||||
dbtype = CANDIDATE_DB_SHARED;
|
||||
once = 0;
|
||||
private_db[0] = '\0';
|
||||
cli_set_send2backend(h, 1); /* send changes to config daemon */
|
||||
cli_set_comment(h, '#'); /* Default to handle #! clicon_cli scripts */
|
||||
|
||||
/*
|
||||
* First-step command-line options for help, debug, config-file and log,
|
||||
*/
|
||||
optind = 1;
|
||||
opterr = 0;
|
||||
while ((c = getopt(argc, argv, CLI_OPTS)) != -1)
|
||||
switch (c) {
|
||||
case '?':
|
||||
case 'h':
|
||||
/* Defer the call to usage() to later. Reason is that for helpful
|
||||
text messages, default dirs, etc, are not set until later.
|
||||
But this means that we need to check if 'help' is set before
|
||||
exiting, and then call usage() before exit.
|
||||
*/
|
||||
help = 1;
|
||||
break;
|
||||
case 'D' : /* debug */
|
||||
if (sscanf(optarg, "%d", &debug) != 1)
|
||||
usage(argv[0], h);
|
||||
break;
|
||||
case 'f': /* config file */
|
||||
if (!strlen(optarg))
|
||||
usage(argv[0], h);
|
||||
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
|
||||
break;
|
||||
case 'l': /* Log destination: s|e|o */
|
||||
switch (optarg[0]){
|
||||
case 's':
|
||||
logdst = CLICON_LOG_SYSLOG;
|
||||
break;
|
||||
case 'e':
|
||||
logdst = CLICON_LOG_STDERR;
|
||||
break;
|
||||
case 'o':
|
||||
logdst = CLICON_LOG_STDOUT;
|
||||
break;
|
||||
default:
|
||||
usage(argv[0], h);
|
||||
}
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Logs, error and debug to stderr or syslog, set debug level
|
||||
*/
|
||||
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst);
|
||||
|
||||
clicon_debug_init(debug, NULL);
|
||||
|
||||
/* Find and read configfile */
|
||||
if (clicon_options_main(h) < 0){
|
||||
if (help)
|
||||
usage(argv[0], h);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Now rest of options */
|
||||
opterr = 0;
|
||||
optind = 1;
|
||||
while ((c = getopt(argc, argv, CLI_OPTS)) != -1){
|
||||
switch (c) {
|
||||
case 'D' : /* debug */
|
||||
case 'f': /* config file */
|
||||
case 'l': /* Log destination */
|
||||
break; /* see above */
|
||||
case 'F': /* read commands from file */
|
||||
if (freopen(optarg, "r", stdin) == NULL){
|
||||
cli_output(stderr, "freopen: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case '1' : /* Quit after reading database once - dont wait for events */
|
||||
once = 1;
|
||||
break;
|
||||
case 'u': /* config unix domain path/ ip host */
|
||||
if (!strlen(optarg))
|
||||
usage(argv[0], h);
|
||||
clicon_option_str_set(h, "CLICON_SOCK", optarg);
|
||||
break;
|
||||
case 'd': /* Plugin directory: overrides configfile */
|
||||
if (!strlen(optarg))
|
||||
usage(argv[0], h);
|
||||
clicon_option_str_set(h, "CLICON_CLI_DIR", optarg);
|
||||
break;
|
||||
case 'm': /* CLI syntax mode */
|
||||
if (!strlen(optarg))
|
||||
usage(argv[0], h);
|
||||
clicon_option_str_set(h, "CLICON_CLI_MODE", optarg);
|
||||
break;
|
||||
case 'c' : /* No config daemon (used in bootstrapping and file load) */
|
||||
cli_set_send2backend(h, 0);
|
||||
break;
|
||||
case 'P' : /* load to private database with given name */
|
||||
dbtype = CANDIDATE_DB_PRIVATE;
|
||||
clicon_option_str_set(h, "CLICON_CANDIDATE_DB", optarg); /* override default */
|
||||
break;
|
||||
case 'q' : /* Quiet mode */
|
||||
clicon_option_str_set(h, "CLICON_QUIET", "on");
|
||||
break;
|
||||
case 'p' : /* Print spec */
|
||||
printspec++;
|
||||
break;
|
||||
case 'G' : /* Print generated CLI syntax */
|
||||
printgen++;
|
||||
break;
|
||||
case 'L' : /* Debug print dynamic CLI syntax */
|
||||
logclisyntax++;
|
||||
break;
|
||||
default:
|
||||
usage(argv[0], h);
|
||||
break;
|
||||
}
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
/* Defer: Wait to the last minute to print help message */
|
||||
if (help)
|
||||
usage(argv[0], h);
|
||||
|
||||
/* Setup signal handlers */
|
||||
cli_signal_init(h);
|
||||
|
||||
/* Backward compatible mode, do not include keys in cgv-arrays in callbacks.
|
||||
Should be 0 but default is 1 since all legacy apps use 1
|
||||
Test legacy before shifting default to 0
|
||||
*/
|
||||
cv_exclude_keys(clicon_cli_varonly(h));
|
||||
|
||||
/* Parse db specification as cli*/
|
||||
if (yang_spec_main(h, stdout, printspec) < 0)
|
||||
goto done;
|
||||
|
||||
/* Check plugin directory */
|
||||
if (clicon_cli_dir(h) == NULL){
|
||||
clicon_err(OE_PLUGIN, 0, "clicon_cli_dir not defined");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Create tree generated from dataspec */
|
||||
if (clicon_cli_genmodel(h)){
|
||||
yang_spec *yspec; /* yang spec */
|
||||
parse_tree pt = {0,}; /* cli parse tree */
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "No YANG DB_SPEC");
|
||||
goto done;
|
||||
}
|
||||
/* Create cli command tree from dbspec */
|
||||
if (yang2cli(h, yspec, &pt, clicon_cli_genmodel_type(h)) < 0)
|
||||
goto done;
|
||||
|
||||
treename = chunk_sprintf(__FUNCTION__, "datamodel:%s", clicon_dbspec_name(h));
|
||||
cli_tree_add(h, treename, pt);
|
||||
if (printgen)
|
||||
cligen_print(stdout, pt, 1);
|
||||
}
|
||||
|
||||
/* Initialize cli syntax */
|
||||
if (cli_syntax_load(h) < 0)
|
||||
goto done;
|
||||
|
||||
/* Set syntax mode if specified from command-line or config-file. */
|
||||
if (clicon_option_exists(h, "CLICON_CLI_MODE"))
|
||||
if ((tmp = clicon_cli_mode(h)) != NULL)
|
||||
if (cli_set_syntax_mode(h, tmp) == 0) {
|
||||
fprintf(stderr, "FATAL: Failed to set syntax mode '%s'\n", tmp);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!cli_syntax_mode(h)){
|
||||
fprintf (stderr, "FATAL: No cli mode set (use -m or CLICON_CLI_MODE)\n");
|
||||
goto done;
|
||||
}
|
||||
if (cli_tree(h, cli_syntax_mode(h)) == NULL){
|
||||
fprintf (stderr, "FATAL: No such cli mode: %s\n", cli_syntax_mode(h));
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Initialize databases */
|
||||
if ((running_db = clicon_running_db(h)) == NULL)
|
||||
goto done;
|
||||
|
||||
if (strlen(private_db))
|
||||
clicon_option_str_set(h, "CLICON_CANDIDATE_DB", private_db);
|
||||
|
||||
if (!cli_send2backend(h))
|
||||
if (db_init(running_db) < 0){
|
||||
fprintf (stderr, "FATAL: Could not init running_db. (Run as root?)\n");
|
||||
goto done;
|
||||
}
|
||||
/* A client does not have access to the candidate (and running)
|
||||
databases if both these conditions are true:
|
||||
1. clicon_sock_family(h) == AF_INET[6]
|
||||
2. cli_send2backend(h) == 1
|
||||
*/
|
||||
if (clicon_sock_family(h) == AF_UNIX || cli_send2backend(h)==0)
|
||||
if (init_candidate_db(h, dbtype) < 0)
|
||||
return -1;
|
||||
|
||||
if (logclisyntax)
|
||||
cli_logsyntax_set(h, logclisyntax);
|
||||
|
||||
if (debug)
|
||||
clicon_option_dump(h, debug);
|
||||
|
||||
/* Call start function in all plugins before we go interactive
|
||||
Pass all args after the standard options to plugin_start
|
||||
*/
|
||||
|
||||
tmp = *(argv-1);
|
||||
*(argv-1) = argv0;
|
||||
cli_plugin_start(h, argc+1, argv-1);
|
||||
*(argv-1) = tmp;
|
||||
|
||||
/* Launch interfactive event loop, unless -1 */
|
||||
if (once == 0)
|
||||
cli_interactive(h);
|
||||
done:
|
||||
// Gets in your face if we log on stderr
|
||||
clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */
|
||||
clicon_log(LOG_NOTICE, "%s: %u Terminated\n", __PROGRAM__, getpid());
|
||||
cli_terminate(h);
|
||||
|
||||
return 0;
|
||||
}
|
||||
1153
apps/cli/cli_plugin.c
Normal file
1153
apps/cli/cli_plugin.c
Normal file
File diff suppressed because it is too large
Load diff
90
apps/cli/cli_plugin.h
Normal file
90
apps/cli/cli_plugin.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _CLI_PLUGIN_H_
|
||||
#define _CLI_PLUGIN_H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* clicon generic callback pointer */
|
||||
typedef void (clicon_callback_t)(clicon_handle h);
|
||||
|
||||
/* clicon_set value callback */
|
||||
typedef int (cli_valcb_t)(cvec *vars, cg_var *cgv, cg_var *arg);
|
||||
|
||||
/* specific to cli. For common see clicon_plugin.h */
|
||||
/* Hook to get prompt format before each getline */
|
||||
typedef char *(cli_prompthook_t)(clicon_handle, char *mode);
|
||||
|
||||
/* Ctrl-Z hook from getline() */
|
||||
typedef int (cli_susphook_t)(clicon_handle, char *, int, int *);
|
||||
|
||||
/* CLIgen parse failure hook. Retry other mode? */
|
||||
typedef char *(cli_parsehook_t)(clicon_handle, char *, char *);
|
||||
|
||||
typedef struct {
|
||||
qelem_t csm_qelem; /* List header */
|
||||
char csm_name[256]; /* Syntax mode name */
|
||||
char csm_prompt[CLI_PROMPT_LEN]; /* Prompt for mode */
|
||||
int csm_nsyntax; /* Num syntax specs registered by plugin */
|
||||
parse_tree csm_pt; /* CLIgen parse tree */
|
||||
|
||||
} cli_syntaxmode_t;
|
||||
|
||||
/* A plugin list object */
|
||||
struct cli_plugin {
|
||||
qelem_t cp_qelem; /* List header */
|
||||
char cp_name[256]; /* Plugin name */
|
||||
void *cp_handle; /* Dynamic object handle */
|
||||
};
|
||||
|
||||
/* Plugin group object */
|
||||
typedef struct {
|
||||
char stx_cnklbl[128]; /* Plugin group name */
|
||||
int stx_nplugins; /* Number of plugins */
|
||||
struct cli_plugin *stx_plugins; /* List of plugins */
|
||||
int stx_nmodes; /* Number of syntax modes */
|
||||
cli_syntaxmode_t *stx_active_mode; /* Current active syntax mode */
|
||||
cli_syntaxmode_t *stx_modes; /* List of syntax modes */
|
||||
cli_prompthook_t *stx_prompt_hook; /* Prompt hook */
|
||||
cli_parsehook_t *stx_parse_hook; /* Parse mode hook */
|
||||
cli_susphook_t *stx_susp_hook; /* Ctrl-Z hook from getline() */
|
||||
} cli_syntax_t;
|
||||
|
||||
|
||||
expand_cb *expand_str2fn(char *name, void *handle, char **error);
|
||||
|
||||
int cli_plugin_start(clicon_handle, int argc, char **argv);
|
||||
|
||||
int cli_plugin_init(clicon_handle h);
|
||||
|
||||
int clicon_eval(clicon_handle h, char *cmd, cg_obj *match_obj, cvec *vr);
|
||||
|
||||
int clicon_parse(clicon_handle h, char *cmd, char **mode, int *result);
|
||||
|
||||
char *clicon_cliread(clicon_handle h);
|
||||
|
||||
int cli_plugin_finish(clicon_handle h);
|
||||
|
||||
#endif /* _CLI_PLUGIN_H_ */
|
||||
59
apps/cli/clicon_cli.h
Normal file
59
apps/cli/clicon_cli.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _CLICON_CLI_H_
|
||||
#define _CLICON_CLI_H_
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#/* Common code (API and clicon_cli) */
|
||||
include <clicon/clicon_cli_api.h>
|
||||
|
||||
/*! Clicon Cli plugin callbacks: use these in your cli plugin code
|
||||
*/
|
||||
|
||||
/*! Called when plugin loaded. Only mandadory callback. All others optional
|
||||
* @see plginit_t
|
||||
*/
|
||||
int plugin_init(clicon_handle h);
|
||||
|
||||
/* Called when backend started with cmd-line arguments from daemon call.
|
||||
* @see plgstart_t
|
||||
*/
|
||||
int plugin_start(clicon_handle h, int argc, char **argv);
|
||||
|
||||
/* Called just before plugin unloaded.
|
||||
* @see plgexit_t
|
||||
*/
|
||||
int plugin_exit(clicon_handle h);
|
||||
|
||||
|
||||
/* Called before prompt is printed, return a customized prompt. */
|
||||
char *plugin_prompt_hook(clicon_handle h, char *mode);
|
||||
|
||||
/* Called if a command is not matched w current mode. Return name of next syntax mode to check until NULL */
|
||||
char *plugin_parse_hook(clicon_handle h, char *cmd, char *name);
|
||||
|
||||
/* Called if ^Z entered. Can modify cli command buffer and position */
|
||||
int plugin_susp_hook(clicon_handle h, char *buf, int prompt_width, int *cursor_loc);
|
||||
|
||||
#endif /* _CLICON_CLI_H_ */
|
||||
113
apps/cli/clicon_cli_api.h
Normal file
113
apps/cli/clicon_cli_api.h
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
* Note, this is a CLICON API file, only exprorted function prototypes should appear here
|
||||
*/
|
||||
|
||||
#ifndef _CLICON_CLI_API_H_
|
||||
#define _CLICON_CLI_API_H_
|
||||
|
||||
/*
|
||||
* Constants
|
||||
*/
|
||||
/* Max prompt length */
|
||||
#define CLI_PROMPT_LEN 64
|
||||
#define CLI_DEFAULT_PROMPT ">"
|
||||
|
||||
/*
|
||||
* Types
|
||||
*/
|
||||
//typedef void *cli_handle; /* clicon cli handle, see struct cli_handle */
|
||||
enum candidate_db_type{
|
||||
CANDIDATE_DB_NONE, /* No candidate */
|
||||
CANDIDATE_DB_PRIVATE, /* Create a private candidate_db */
|
||||
CANDIDATE_DB_SHARED, /* Share the candidate with everyone else */
|
||||
CANDIDATE_DB_CURRENT /* Dont create candidate, use current directly */
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Function Declarations
|
||||
*/
|
||||
/* cli_plugin.c */
|
||||
int cli_set_syntax_mode(clicon_handle h, const char *mode);
|
||||
char *cli_syntax_mode(clicon_handle h);
|
||||
int cli_syntax_load(clicon_handle h);
|
||||
int cli_handler_err(FILE *fd);
|
||||
int cli_set_prompt(clicon_handle h, const char *mode, const char *prompt);
|
||||
char *cli_prompt(char *fmt);
|
||||
int cli_exec(clicon_handle h, char *cmd, char **mode, int *result);
|
||||
int cli_ptpush(clicon_handle h, char *mode, char *string, char *op);
|
||||
int cli_ptpop(clicon_handle h, char *mode, char *op);
|
||||
|
||||
/* cli_handle.c */
|
||||
char cli_set_comment(clicon_handle h, char c);
|
||||
char cli_comment(clicon_handle h);
|
||||
int cli_set_exiting(clicon_handle h, int exiting);
|
||||
int cli_exiting(clicon_handle h);
|
||||
int cli_set_send2backend(clicon_handle h, int send2backend);
|
||||
int cli_send2backend(clicon_handle h);
|
||||
clicon_handle cli_handle_init(void);
|
||||
int cli_handle_exit(clicon_handle h);
|
||||
cligen_handle cli_cligen(clicon_handle h);
|
||||
enum candidate_db_type cli_candidate_type(clicon_handle h);
|
||||
int cli_set_candidate_type(clicon_handle h, enum candidate_db_type type);
|
||||
|
||||
/* cli_common.c */
|
||||
int init_candidate_db(clicon_handle h, enum candidate_db_type type);
|
||||
int exit_candidate_db(clicon_handle h);
|
||||
#define cli_output cligen_output
|
||||
int cli_set (clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int cli_merge (clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int cli_del(clicon_handle h, cvec *vars, cg_var *argv);
|
||||
int cli_debug(clicon_handle h, cvec *vars, cg_var *argv);
|
||||
int cli_record(clicon_handle h, cvec *vars, cg_var *argv);
|
||||
int isrecording(void);
|
||||
int record_command(char *str);
|
||||
int cli_set_mode(clicon_handle h, cvec *vars, cg_var *argv);
|
||||
int cli_start_shell(clicon_handle h, cvec *vars, cg_var *argv);
|
||||
int cli_quit(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int cli_commit(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int cli_validate(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int expand_dbvar(void *h, char *name, cvec *vars, cg_var *arg,
|
||||
int *nr, char ***commands, char ***helptexts);
|
||||
int expand_dbvar_auto(void *h, char *name, cvec *vars, cg_var *arg,
|
||||
int *nr, char ***commands, char ***helptexts);
|
||||
int expand_db_variable(clicon_handle h, char *dbname, char *basekey, char *variable, int *nr, char ***commands);
|
||||
int expand_db_symbol(clicon_handle h, char *symbol, int element, int *nr, char ***commands);
|
||||
int expand_dir(char *dir, int *nr, char ***commands, mode_t flags, int detail);
|
||||
int compare_dbs(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
|
||||
int load_config_file(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int save_config_file(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int delete_all(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int discard_changes(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int show_conf_as_xml(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int show_conf_as_netconf(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int show_conf_as_json(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int show_conf_as_text(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int show_conf_as_cli(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int show_conf_as_csv(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int show_yang(clicon_handle h, cvec *vars, cg_var *arg);
|
||||
int cli_notification_register(clicon_handle h, char *stream, enum format_enum format,
|
||||
char *filter, int status,
|
||||
int (*fn)(int, void*), void *arg);
|
||||
|
||||
#endif /* _CLICON_CLI_API_H_ */
|
||||
99
apps/dbctrl/Makefile.in
Normal file
99
apps/dbctrl/Makefile.in
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#
|
||||
# Makefile
|
||||
#
|
||||
#
|
||||
# Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
#
|
||||
# This file is part of CLICON.
|
||||
#
|
||||
# CLICON is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# CLICON is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with CLICON; see the file COPYING. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
VPATH = @srcdir@
|
||||
srcdir = @srcdir@
|
||||
top_srcdir = @top_srcdir@
|
||||
CC = @CC@
|
||||
CFLAGS = @CFLAGS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
|
||||
prefix = @prefix@
|
||||
exec_prefix = @exec_prefix@
|
||||
bindir = @bindir@
|
||||
libexecdir = @libexecdir@
|
||||
localstatedir = @localstatedir@
|
||||
sysconfdir = @sysconfdir@
|
||||
|
||||
SH_SUFFIX = @SH_SUFFIX@
|
||||
CLICON_MAJOR = @CLICON_VERSION_MAJOR@
|
||||
CLICON_MINOR = @CLICON_VERSION_MINOR@
|
||||
|
||||
# Use this clicon lib for linking
|
||||
CLICON_LIB = libclicon.so.$(CLICON_MAJOR).$(CLICON_MINOR)
|
||||
|
||||
# For dependency
|
||||
LIBDEPS = $(top_srcdir)/lib/src/$(CLICON_LIB)
|
||||
|
||||
LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLICON_LIB)
|
||||
|
||||
CPPFLAGS = @CPPFLAGS@
|
||||
|
||||
INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
|
||||
|
||||
SRC =
|
||||
|
||||
OBJS = $(SRC:.c=.o)
|
||||
|
||||
APPSRC = dbctrl_main.c
|
||||
APPOBJ = $(APPSRC:.c=.o)
|
||||
APPL = clicon_dbctrl
|
||||
|
||||
all: $(APPL)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) *.core $(APPL) $(APPOBJ)
|
||||
|
||||
distclean: clean
|
||||
rm -f Makefile *~ .depend
|
||||
|
||||
# Put demon in bin
|
||||
# Put other executables in libexec/
|
||||
# Also create a libexec/ directory for writeable/temporary files.
|
||||
# Put config file in etc/
|
||||
install: $(APPL)
|
||||
install -d $(DESTDIR)$(bindir)
|
||||
install $(APPL) $(DESTDIR)$(bindir)
|
||||
|
||||
install-include:
|
||||
|
||||
uninstall:
|
||||
rm -f $(bindir)/$(APPL)
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .c .o
|
||||
|
||||
.c.o:
|
||||
$(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" $(CPPFLAGS) $(CFLAGS) -c $<
|
||||
|
||||
$(APPL) : $(APPOBJ) $(OBJS) $(LIBDEPS)
|
||||
$(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) $(LIBS) -o $@
|
||||
|
||||
TAGS:
|
||||
find . -name '*.[chyl]' -print | etags -
|
||||
|
||||
depend:
|
||||
$(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend
|
||||
|
||||
#include .depend
|
||||
|
||||
241
apps/dbctrl/dbctrl_main.c
Normal file
241
apps/dbctrl/dbctrl_main.c
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
/* Command line options to be passed to getopt(3) */
|
||||
#define DBCTRL_OPTS "hDd:pbn:r:m:Zi"
|
||||
|
||||
/*! Write database contents to file, xml database variant
|
||||
* @param[in] dbspec If set make a sanity check if key dont match (just)
|
||||
*/
|
||||
static int
|
||||
dump_database(FILE *f,
|
||||
char *dbname,
|
||||
char *rxkey)
|
||||
{
|
||||
int retval = 0;
|
||||
int npairs;
|
||||
struct db_pair *pairs;
|
||||
|
||||
/* Default is match all */
|
||||
if (rxkey == NULL)
|
||||
rxkey = "^.*$";
|
||||
|
||||
/* Get all keys/values for vector */
|
||||
if ((npairs = db_regexp(dbname, rxkey, __FUNCTION__, &pairs, 0)) < 0)
|
||||
return -1;
|
||||
|
||||
for (npairs--; npairs >= 0; npairs--)
|
||||
fprintf(f, "%s %s\n", pairs[npairs].dp_key,
|
||||
pairs[npairs].dp_val?pairs[npairs].dp_val:"");
|
||||
unchunk_group(__FUNCTION__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* remove_entry
|
||||
*/
|
||||
static int
|
||||
remove_entry(char *dbname, char *key)
|
||||
{
|
||||
return db_del(dbname, key);
|
||||
}
|
||||
|
||||
/*
|
||||
* usage
|
||||
*/
|
||||
static void
|
||||
usage(char *argv0)
|
||||
{
|
||||
fprintf(stderr, "usage:%s\n"
|
||||
"where options are\n"
|
||||
"\t-h\t\tHelp\n"
|
||||
"\t-D\t\tDebug\n"
|
||||
"\t-d <dbname>\tDatabase name (default: running_db)\n"
|
||||
"\t-p\t\tDump database on stdout\n"
|
||||
"\t-b\t\tBrief output, just print keys. Combine with -p or -m\n"
|
||||
"\t-n \"<key> <val>\" Add database entry\n"
|
||||
"\t-r <key>\tRemove database entry\n"
|
||||
"\t-m <regexp key>\tMatch regexp key in database\n"
|
||||
"\t-Z\t\tDelete database\n"
|
||||
"\t-i\t\tInit database\n",
|
||||
argv0
|
||||
);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char c;
|
||||
int zapdb;
|
||||
int initdb;
|
||||
int dumpdb;
|
||||
int addent;
|
||||
int rment;
|
||||
char *matchkey = NULL;
|
||||
char *addstr;
|
||||
char rmkey[MAXPATHLEN];
|
||||
int brief;
|
||||
char dbname[MAXPATHLEN] = {0,};
|
||||
int use_syslog;
|
||||
yang_spec *yspec;
|
||||
clicon_handle h;
|
||||
|
||||
/* In the startup, logs to stderr & debug flag set later */
|
||||
clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR);
|
||||
|
||||
/* Defaults */
|
||||
zapdb = 0;
|
||||
initdb = 0;
|
||||
dumpdb = 0;
|
||||
addent = 0;
|
||||
rment = 0;
|
||||
brief = 0;
|
||||
use_syslog = 0;
|
||||
addstr = NULL;
|
||||
memset(rmkey, '\0', sizeof(rmkey));
|
||||
|
||||
if ((h = clicon_handle_init()) == NULL)
|
||||
goto done;
|
||||
/* getopt in two steps, first find config-file before over-riding options. */
|
||||
while ((c = getopt(argc, argv, DBCTRL_OPTS)) != -1)
|
||||
switch (c) {
|
||||
case '?' :
|
||||
case 'h' : /* help */
|
||||
usage(argv[0]);
|
||||
break;
|
||||
case 'D' : /* debug */
|
||||
debug = 1;
|
||||
break;
|
||||
case 'S': /* Log on syslog */
|
||||
use_syslog = 1;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Logs, error and debug to stderr or syslog, set debug level
|
||||
*/
|
||||
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO,
|
||||
use_syslog?CLICON_LOG_SYSLOG:CLICON_LOG_STDERR);
|
||||
clicon_debug_init(debug, NULL);
|
||||
|
||||
/* Now rest of options */
|
||||
optind = 1;
|
||||
while ((c = getopt(argc, argv, DBCTRL_OPTS)) != -1)
|
||||
switch (c) {
|
||||
case 'Z': /* Zap database */
|
||||
zapdb++;
|
||||
break;
|
||||
case 'i': /* Init database */
|
||||
initdb++;
|
||||
break;
|
||||
case 'p': /* Dump/print database */
|
||||
dumpdb++;
|
||||
break;
|
||||
case 'b': /* Dump/print/match database brief (combone w -p or -m) */
|
||||
brief++;
|
||||
break;
|
||||
case 'd': /* dbname */
|
||||
if (!optarg || sscanf(optarg, "%s", dbname) != 1)
|
||||
usage(argv[0]);
|
||||
break;
|
||||
case 'n': /* add database entry */
|
||||
if (!optarg || !strlen(optarg) || (addstr = strdup(optarg)) == NULL)
|
||||
usage(argv[0]);
|
||||
/* XXX addign both key and value, for now only key */
|
||||
addent++;
|
||||
break;
|
||||
case 'r':
|
||||
if (!optarg || sscanf(optarg, "%s", rmkey) != 1)
|
||||
usage(argv[0]);
|
||||
rment++;
|
||||
break;
|
||||
case 'm':
|
||||
if (!optarg || !strlen(optarg) || (matchkey = strdup(optarg)) == NULL)
|
||||
usage(argv[0]);
|
||||
dumpdb++;
|
||||
break;
|
||||
case 'D': /* Processed earlier, ignore now. */
|
||||
case 'S':
|
||||
break;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
break;
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (*dbname == '\0'){
|
||||
clicon_err(OE_FATAL, 0, "database not specified (with -d <db>): %s");
|
||||
goto done;
|
||||
}
|
||||
yspec = clicon_dbspec_yang(h);
|
||||
if (dumpdb){
|
||||
if (dump_database(stdout, dbname, matchkey)) {
|
||||
fprintf(stderr, "Match error\n");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
if (addent) /* add entry */
|
||||
if (xmldb_put_xkey(dbname, addstr, NULL, yspec, OP_REPLACE) < 0)
|
||||
goto done;
|
||||
if (rment)
|
||||
if (remove_entry(dbname, rmkey) < 0)
|
||||
goto done;
|
||||
if (zapdb) /* remove databases */
|
||||
unlink(dbname);
|
||||
if (initdb)
|
||||
if (db_init(dbname) < 0)
|
||||
goto done;
|
||||
|
||||
done:
|
||||
return 0;
|
||||
}
|
||||
122
apps/netconf/Makefile.in
Normal file
122
apps/netconf/Makefile.in
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
#
|
||||
# Makefile
|
||||
#
|
||||
# Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
#
|
||||
# This file is part of CLICON.
|
||||
#
|
||||
# CLICON is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# CLICON is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with CLICON; see the file COPYING. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
VPATH = @srcdir@
|
||||
srcdir = @srcdir@
|
||||
top_srcdir = @top_srcdir@
|
||||
CC = @CC@
|
||||
CFLAGS = @CFLAGS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
|
||||
prefix = @prefix@
|
||||
datarootdir = @datarootdir@
|
||||
exec_prefix = @exec_prefix@
|
||||
bindir = @bindir@
|
||||
libdir = @libdir@
|
||||
mandir = @mandir@
|
||||
libexecdir = @libexecdir@
|
||||
localstatedir = @localstatedir@
|
||||
sysconfdir = @sysconfdir@
|
||||
includedir = @includedir@
|
||||
|
||||
SH_SUFFIX = @SH_SUFFIX@
|
||||
CLICON_MAJOR = @CLICON_VERSION_MAJOR@
|
||||
CLICON_MINOR = @CLICON_VERSION_MINOR@
|
||||
|
||||
# Use this clicon lib for linking
|
||||
CLICON_LIB = libclicon.so.$(CLICON_MAJOR).$(CLICON_MINOR)
|
||||
|
||||
# For dependency
|
||||
LIBDEPS = $(top_srcdir)/lib/src/$(CLICON_LIB)
|
||||
|
||||
LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLICON_LIB)
|
||||
CPPFLAGS = @CPPFLAGS@ -fPIC
|
||||
INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
|
||||
|
||||
APPL = clicon_netconf
|
||||
SRC = netconf_main.c
|
||||
OBJS = $(SRC:.c=.o)
|
||||
|
||||
MYNAME = clicon_netconf
|
||||
MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX)
|
||||
MYLIB = $(MYLIBLINK).$(CLICON_MAJOR).$(CLICON_MINOR)
|
||||
MYLIBSO = $(MYLIBLINK).$(CLICON_MAJOR)
|
||||
|
||||
LIBSRC = netconf_hello.c netconf_rpc.c netconf_filter.c netconf_lib.c netconf_plugin.c
|
||||
LIBOBJS = $(LIBSRC:.c=.o)
|
||||
|
||||
all: $(MYLIB) $(APPL)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(LIBOBJS) *.core $(APPL) $(MYLIB) $(MYLIBSO) $(MYLIBLINK)
|
||||
|
||||
distclean: clean
|
||||
rm -f Makefile *~ .depend
|
||||
|
||||
# Put demon in bin
|
||||
# Put other executables in libexec/
|
||||
# Also create a libexec/ directory for writeable/temporary files.
|
||||
# Put config file in etc/
|
||||
install: install-lib $(APPL)
|
||||
install -d $(DESTDIR)$(bindir)
|
||||
install $(APPL) $(DESTDIR)$(bindir)
|
||||
|
||||
install-lib: $(MYLIB)
|
||||
install -d $(DESTDIR)$(libdir)
|
||||
install $(MYLIB) $(DESTDIR)$(libdir)
|
||||
ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclicon_netconf.so.2
|
||||
ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclicon_netconf.so
|
||||
|
||||
install-include: clicon_netconf.h
|
||||
install -d $(DESTDIR)$(includedir)/clicon
|
||||
install -m 644 $^ $(DESTDIR)$(includedir)/clicon
|
||||
|
||||
uninstall:
|
||||
rm -f $(bindir)/$(APPL)
|
||||
rm -f $(libdir)/$(MYLIB)
|
||||
rm -f $(includedir)/clicon/*
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .c .o
|
||||
|
||||
.c.o:
|
||||
$(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" $(CFLAGS) -c $<
|
||||
|
||||
$(APPL) : $(OBJS) $(MYLIBLINK) $(LIBDEPS)
|
||||
$(CC) $(LDFLAGS) $(OBJS) -L. -l:$(MYLIB) $(LIBS) -o $@
|
||||
|
||||
$(MYLIB) : $(LIBOBJS)
|
||||
$(CC) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJS) $(LIBS) -Wl,-soname=$(MYLIBSO)
|
||||
|
||||
# link-name is needed for application linking, eg for clicon_cli and clicon_config
|
||||
$(MYLIBLINK) : $(MYLIB)
|
||||
# ln -sf $(MYLIB) $(MYLIBSO)
|
||||
# ln -sf $(MYLIB) $@
|
||||
|
||||
TAGS:
|
||||
find . -name '*.[chyl]' -print | etags -
|
||||
|
||||
depend:
|
||||
$(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend
|
||||
|
||||
#include .depend
|
||||
|
||||
73
apps/netconf/clicon_netconf.h
Normal file
73
apps/netconf/clicon_netconf.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* The exported interface to plugins. External apps (eg frontend netconf plugins)
|
||||
* should only include this file (not the netconf_*.h)
|
||||
*/
|
||||
|
||||
#ifndef _CLICON_NETCONF_H_
|
||||
#define _CLICON_NETCONF_H_
|
||||
|
||||
/*
|
||||
* Types
|
||||
*/
|
||||
typedef int (*netconf_cb_t)(
|
||||
clicon_handle h,
|
||||
cxobj *xorig, /* Original request. */
|
||||
cxobj *xn, /* Sub-tree (under xorig) at child: <rpc><xn></rpc> */
|
||||
cbuf *cb, /* Output xml stream. For reply */
|
||||
cbuf *cb_err, /* Error xml stream. For error reply */
|
||||
void *arg /* Argument given at netconf_register_callback() */
|
||||
);
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
* (Duplicated. Also in netconf_*.h)
|
||||
*/
|
||||
int netconf_output(int s, cbuf *xf, char *msg);
|
||||
|
||||
int netconf_create_rpc_reply(cbuf *cb, /* msg buffer */
|
||||
cxobj *xr, /* orig request */
|
||||
char *body,
|
||||
int ok);
|
||||
|
||||
int netconf_register_callback(clicon_handle h,
|
||||
netconf_cb_t cb, /* Callback called */
|
||||
void *arg, /* Arg to send to callback */
|
||||
char *tag); /* Xml tag when callback is made */
|
||||
int netconf_create_rpc_error(cbuf *xf, /* msg buffer */
|
||||
cxobj *xr, /* orig request */
|
||||
char *tag,
|
||||
char *type,
|
||||
char *severity,
|
||||
char *message,
|
||||
char *info);
|
||||
|
||||
void netconf_ok_set(int ok);
|
||||
int netconf_ok_get(void);
|
||||
|
||||
int netconf_xpath(cxobj *xsearch,
|
||||
cxobj *xfilter,
|
||||
cbuf *xf, cbuf *xf_err,
|
||||
cxobj *xt);
|
||||
|
||||
|
||||
#endif /* _CLICON_NETCONF_H_ */
|
||||
633
apps/netconf/netconf_filter.c
Normal file
633
apps/netconf/netconf_filter.c
Normal file
|
|
@ -0,0 +1,633 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* netconf match & selection: get and edit operations
|
||||
*****************************************************************************/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/param.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "netconf_rpc.h"
|
||||
#include "netconf_lib.h"
|
||||
#include "netconf_filter.h"
|
||||
|
||||
/*
|
||||
* xml_filter
|
||||
* xf specifices a filter, and xn is an xml tree.
|
||||
* Select the part of xn that matches xf and return it.
|
||||
* Change xn destructively by removing the parts of the sub-tree that does
|
||||
* not match.
|
||||
* Match according to Section 6 of RFC 4741.
|
||||
NO_FILTER, select all
|
||||
EMPTY_FILTER, select nothing
|
||||
ATTRIBUTE_MATCH, select if attribute match
|
||||
SELECTION, select this node
|
||||
CONTENT_MATCH, select all siblings with matching content
|
||||
CONTAINMENT select
|
||||
*/
|
||||
|
||||
/* return a string containing leafs value, NULL if no leaf or no value */
|
||||
static char*
|
||||
leafstring(cxobj *x)
|
||||
{
|
||||
cxobj *c;
|
||||
|
||||
if (xml_type(x) != CX_ELMNT)
|
||||
return NULL;
|
||||
if (xml_child_nr(x) != 1)
|
||||
return NULL;
|
||||
c = xml_child_i(x, 0);
|
||||
if (xml_child_nr(c) != 0)
|
||||
return NULL;
|
||||
if (xml_type(c) != CX_BODY)
|
||||
return NULL;
|
||||
return xml_value(c);
|
||||
}
|
||||
|
||||
/*! Internal recursive part where configuration xml tree is pruned frim filter
|
||||
* assume parent has been selected and filter match (same name) as parent
|
||||
* parent is pruned according to selection.
|
||||
* @param[in] xfilter Filter xml
|
||||
* @param[out] xconf Configuration xml
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
xml_filter2(cxobj *xfilter,
|
||||
cxobj *xparent,
|
||||
int *remove_me)
|
||||
{
|
||||
cxobj *s;
|
||||
cxobj *sprev;
|
||||
cxobj *f;
|
||||
cxobj *attr;
|
||||
char *an;
|
||||
char *af;
|
||||
char *fstr;
|
||||
char *sstr;
|
||||
int containments;
|
||||
int remove_s;
|
||||
|
||||
*remove_me = 0;
|
||||
assert(xfilter && xparent && strcmp(xml_name(xfilter), xml_name(xparent))==0);
|
||||
/* 1. Check selection */
|
||||
if (xml_child_nr(xfilter) == 0)
|
||||
goto match;
|
||||
|
||||
/* Count containment/selection nodes in filter */
|
||||
f = NULL;
|
||||
containments = 0;
|
||||
while ((f = xml_child_each(xfilter, f, CX_ELMNT)) != NULL) {
|
||||
if (leafstring(f))
|
||||
continue;
|
||||
containments++;
|
||||
}
|
||||
|
||||
/* 2. Check attribute match */
|
||||
attr = NULL;
|
||||
while ((attr = xml_child_each(xfilter, attr, CX_ATTR)) != NULL) {
|
||||
af = xml_value(attr);
|
||||
an = xml_find_value(xfilter, xml_name(attr));
|
||||
if (af && an && strcmp(af, an)==0)
|
||||
; // match
|
||||
else
|
||||
goto nomatch;
|
||||
}
|
||||
/* 3. Check content match */
|
||||
f = NULL;
|
||||
while ((f = xml_child_each(xfilter, f, CX_ELMNT)) != NULL) {
|
||||
if ((fstr = leafstring(f)) == NULL)
|
||||
continue;
|
||||
if ((s = xml_find(xparent, xml_name(f))) == NULL)
|
||||
goto nomatch;
|
||||
if ((sstr = leafstring(s)) == NULL)
|
||||
continue;
|
||||
if (strcmp(fstr, sstr))
|
||||
goto nomatch;
|
||||
}
|
||||
/* If filter has no further specifiers, accept */
|
||||
if (!containments)
|
||||
goto match;
|
||||
/* Check recursively the rest of the siblings */
|
||||
sprev = s = NULL;
|
||||
while ((s = xml_child_each(xparent, s, CX_ELMNT)) != NULL) {
|
||||
if ((f = xml_find(xfilter, xml_name(s))) == NULL){
|
||||
xml_prune(xparent, s, 1);
|
||||
s = sprev;
|
||||
continue;
|
||||
}
|
||||
if (leafstring(f)){
|
||||
sprev = s;
|
||||
continue; // unsure?sk=lf
|
||||
}
|
||||
// XXX: s can be removed itself in the recursive call !
|
||||
remove_s = 0;
|
||||
if (xml_filter2(f, s, &remove_s) < 0)
|
||||
return -1;
|
||||
if (remove_s){
|
||||
xml_prune(xparent, s, 1);
|
||||
s = sprev;
|
||||
}
|
||||
sprev = s;
|
||||
}
|
||||
|
||||
match:
|
||||
return 0;
|
||||
nomatch: /* prune this parent node (maybe only children?) */
|
||||
*remove_me = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Remove parts of configuration xml tree that does not match filter xml tree
|
||||
* @param[in] xfilter Filter xml
|
||||
* @param[out] xconf Configuration xml
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* This is the top-level function, calls a recursive variant.
|
||||
*/
|
||||
int
|
||||
xml_filter(cxobj *xfilter,
|
||||
cxobj *xconfig)
|
||||
{
|
||||
int retval;
|
||||
int remove_s;
|
||||
|
||||
/* Call recursive variant */
|
||||
retval = xml_filter2(xfilter,
|
||||
xconfig,
|
||||
&remove_s);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* netconf_xpath
|
||||
* @param[in] xsearch where you search for xpath, grouped by a single top node which
|
||||
* is not significant and will not be returned in any result.
|
||||
* @param[in] xfilter the xml sub-tree, eg:
|
||||
* <filter type="xpath" select="/t:top/t:users/t:user[t:name='fred']"/>
|
||||
* @param[out] cb Output xml stream. For reply
|
||||
* @param[out] cb_err Error xml stream. For error reply
|
||||
* @param[in] xorig Original tree
|
||||
*
|
||||
*/
|
||||
int
|
||||
netconf_xpath(cxobj *xsearch,
|
||||
cxobj *xfilter,
|
||||
cbuf *cb,
|
||||
cbuf *cb_err,
|
||||
cxobj *xorig)
|
||||
{
|
||||
cxobj *x;
|
||||
int retval = -1;
|
||||
char *selector;
|
||||
cxobj **xv;
|
||||
int xlen;
|
||||
int i;
|
||||
|
||||
if ((selector = xml_find_value(xfilter, "select")) == NULL){
|
||||
netconf_create_rpc_error(cb_err, xorig,
|
||||
"operation-failed",
|
||||
"application",
|
||||
"error",
|
||||
NULL,
|
||||
"select");
|
||||
goto done;
|
||||
}
|
||||
|
||||
x = NULL;
|
||||
|
||||
clicon_errno = 0;
|
||||
if ((xv = xpath_vec(xsearch, selector, &xlen)) != NULL) {
|
||||
for (i=0; i<xlen; i++){
|
||||
x = xv[i];
|
||||
clicon_xml2cbuf(cb, x, 0, 1);
|
||||
}
|
||||
free(xv);
|
||||
}
|
||||
/* XXX: NULL means error sometimes */
|
||||
if (clicon_errno){
|
||||
netconf_create_rpc_error(cb_err, xorig,
|
||||
"operation-failed",
|
||||
"application",
|
||||
"error",
|
||||
clicon_err_reason,
|
||||
"select");
|
||||
goto done;
|
||||
}
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
#ifdef NOTUSED
|
||||
/*
|
||||
* in edit_config, we copy a tree to the config. But some wthings shouldbe
|
||||
* cleaned:
|
||||
* - operation attribute
|
||||
*/
|
||||
static int
|
||||
netconf_clean(cxobj *xn)
|
||||
{
|
||||
cxobj *xa;
|
||||
cxobj *x;
|
||||
|
||||
if ((xa = xml_find(xn, "operation")) != NULL &&
|
||||
xml_type(xa) == CX_ATTR)
|
||||
xml_prune(xn, xa, 1);
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(xn, x, CX_ELMNT)) != NULL)
|
||||
netconf_clean(x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_operation
|
||||
* get the value of the "operation" attribute and change op if given
|
||||
*/
|
||||
static int
|
||||
get_operation(cxobj *xn,
|
||||
enum operation_type *op,
|
||||
cbuf *xf_err,
|
||||
cxobj *xorig)
|
||||
{
|
||||
char *opstr;
|
||||
|
||||
if ((opstr = xml_find_value(xn, "operation")) != NULL){
|
||||
if (strcmp("merge", opstr) == 0)
|
||||
*op = OP_MERGE;
|
||||
else
|
||||
if (strcmp("replace", opstr) == 0)
|
||||
*op = OP_REPLACE;
|
||||
else
|
||||
if (strcmp("create", opstr) == 0)
|
||||
*op = OP_CREATE;
|
||||
else
|
||||
if (strcmp("delete", opstr) == 0)
|
||||
*op = OP_DELETE;
|
||||
else
|
||||
if (strcmp("remove", opstr) == 0)
|
||||
*op = OP_REMOVE;
|
||||
else{
|
||||
netconf_create_rpc_error(xf_err, xorig,
|
||||
"bad-attribute",
|
||||
"protocol",
|
||||
"error",
|
||||
NULL,
|
||||
"<bad-attribute>operation</bad-attribute>");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* forward */
|
||||
static int
|
||||
xml_edit(cxobj *filter,
|
||||
cxobj *parent,
|
||||
enum operation_type op,
|
||||
cbuf *xf_err,
|
||||
cxobj *xorig);
|
||||
|
||||
|
||||
/*! Merge two XML trees according to RFC4741/Junos
|
||||
* 1. in configuration(parent) but not in new(filter) -> remain in configuration
|
||||
* 2. not in configuration but in new -> add to configuration
|
||||
* 3. Both in configuration and new: Do 1.2.3 with children.
|
||||
* A key is: the configuration data identified by the element
|
||||
*/
|
||||
static int
|
||||
edit_selection(cxobj *filter,
|
||||
cxobj *parent,
|
||||
enum operation_type op,
|
||||
cbuf *xf_err,
|
||||
cxobj *xorig)
|
||||
{
|
||||
int retval = -1;
|
||||
|
||||
assert(filter && parent && strcmp(xml_name(filter), xml_name(parent))==0);
|
||||
fprintf(stderr, "%s: %s\n", __FUNCTION__, xml_name(filter));
|
||||
switch (op){
|
||||
case OP_MERGE:
|
||||
break;
|
||||
case OP_REPLACE:
|
||||
xml_prune(xml_parent(parent), parent, 1);
|
||||
break;
|
||||
case OP_CREATE:
|
||||
netconf_create_rpc_error(xf_err, xorig,
|
||||
NULL,
|
||||
"protocol",
|
||||
"error",
|
||||
"statement creation failed",
|
||||
"<bad-element></bad-element>");
|
||||
goto done;
|
||||
break;
|
||||
case OP_DELETE:
|
||||
fprintf(stderr, "%s: %s DELETE\n", __FUNCTION__, xml_name(filter));
|
||||
if (xml_child_nr(parent) == 0){
|
||||
fprintf(stderr, "%s: %s ERROR\n", __FUNCTION__, xml_name(filter));
|
||||
netconf_create_rpc_error(xf_err, xorig,
|
||||
NULL,
|
||||
"protocol",
|
||||
"error",
|
||||
"statement not found",
|
||||
"<bad-element></bad-element>");
|
||||
goto done;
|
||||
}
|
||||
/* fall through */
|
||||
case OP_REMOVE:
|
||||
fprintf(stderr, "%s: %s REMOVE\n", __FUNCTION__, xml_name(filter));
|
||||
xml_prune(xml_parent(parent), parent, 1);
|
||||
break;
|
||||
case OP_NONE:
|
||||
break;
|
||||
}
|
||||
retval = 0;
|
||||
netconf_ok_set(1); /* maybe cc_ok shouldnt be set if we continue? */
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX: not called from external?
|
||||
*/
|
||||
static int
|
||||
edit_match(cxobj *filter,
|
||||
cxobj *parent,
|
||||
enum operation_type op,
|
||||
cbuf *xf_err,
|
||||
cxobj *xorig,
|
||||
int match
|
||||
)
|
||||
{
|
||||
cxobj *f;
|
||||
cxobj *s;
|
||||
cxobj *copy;
|
||||
int retval = -1;
|
||||
|
||||
clicon_debug(1, "%s: %s op:%d", __FUNCTION__, xml_name(filter), op);
|
||||
if (match)
|
||||
switch (op){
|
||||
case OP_REPLACE:
|
||||
case OP_CREATE:
|
||||
s = NULL;
|
||||
while ((s = xml_child_each(parent, s, -1)) != NULL){
|
||||
xml_prune(parent, s, 1);
|
||||
s = NULL;
|
||||
}
|
||||
if (xml_copy(filter, parent) < 0)
|
||||
goto done;
|
||||
netconf_clean(parent);
|
||||
retval = 0;
|
||||
netconf_ok_set(1);
|
||||
goto done;
|
||||
break;
|
||||
case OP_DELETE:
|
||||
case OP_REMOVE:
|
||||
xml_prune(xml_parent(parent), parent, 1);
|
||||
netconf_ok_set(1);
|
||||
goto done;
|
||||
break;
|
||||
case OP_MERGE:
|
||||
case OP_NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
f = NULL;
|
||||
while ((f = xml_child_each(filter, f, CX_ELMNT)) != NULL) {
|
||||
s = xml_find(parent, xml_name(f));
|
||||
switch (op){
|
||||
case OP_MERGE:
|
||||
/* things in filter:
|
||||
not in conf should be added
|
||||
in conf go down recursive
|
||||
*/
|
||||
if (s == NULL && match){
|
||||
if ((copy = xml_new(xml_name(f), parent)) == NULL)
|
||||
goto done;
|
||||
if (xml_copy(f, copy) < 0)
|
||||
goto done;
|
||||
netconf_clean(copy);
|
||||
}
|
||||
else{
|
||||
s = NULL;
|
||||
while ((s = xml_child_each(parent, s, CX_ELMNT)) != NULL) {
|
||||
if (strcmp(xml_name(f), xml_name(s)))
|
||||
continue;
|
||||
if ((retval = xml_edit(f, s, op, xf_err, xorig)) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OP_REPLACE:
|
||||
#if 1
|
||||
/* things in filter
|
||||
in conf: remove from conf and
|
||||
add to conf
|
||||
*/
|
||||
// if (!match)
|
||||
// break;
|
||||
if (s != NULL)
|
||||
xml_prune(parent, s, 1);
|
||||
if ((copy = xml_new(xml_name(f), parent)) == NULL)
|
||||
goto done;
|
||||
if (xml_copy(f, copy) < 0)
|
||||
goto done;
|
||||
netconf_clean(copy);
|
||||
#endif
|
||||
break;
|
||||
case OP_CREATE:
|
||||
#if 0
|
||||
/* things in filter
|
||||
in conf: error
|
||||
else add to conf
|
||||
*/
|
||||
if (!match)
|
||||
break;
|
||||
if (s != NULL){
|
||||
netconf_create_rpc_error(xf_err, xorig,
|
||||
NULL,
|
||||
"protocol",
|
||||
"error",
|
||||
"statement creation failed",
|
||||
"<bad-element></bad-element>");
|
||||
goto done;
|
||||
}
|
||||
if ((copy = xml_new(xml_name(f), parent)) == NULL)
|
||||
goto done;
|
||||
if (xml_copy(f, copy) < 0)
|
||||
goto done;
|
||||
netconf_clean(copy);
|
||||
#endif
|
||||
break;
|
||||
case OP_DELETE:
|
||||
/* things in filter
|
||||
if not in conf: error
|
||||
else remove from conf
|
||||
*/
|
||||
#if 0
|
||||
if (!match)
|
||||
break;
|
||||
if (s == NULL){
|
||||
netconf_create_rpc_error(xf_err, xorig,
|
||||
NULL,
|
||||
"protocol",
|
||||
"error",
|
||||
"statement not found",
|
||||
"<bad-element></bad-element>");
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
/* fall through */
|
||||
case OP_REMOVE:
|
||||
/* things in filter
|
||||
remove from conf
|
||||
*/
|
||||
#if 0
|
||||
if (!match)
|
||||
break;
|
||||
xml_prune(parent, s, 1);
|
||||
#endif
|
||||
break;
|
||||
case OP_NONE:
|
||||
/* recursive descent */
|
||||
s = NULL;
|
||||
while ((s = xml_child_each(parent, s, CX_ELMNT)) != NULL) {
|
||||
if (strcmp(xml_name(f), xml_name(s)))
|
||||
continue;
|
||||
if ((retval = xml_edit(f, s, op, xf_err, xorig)) < 0)
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} /* while f */
|
||||
retval = 0;
|
||||
netconf_ok_set(1); /* maybe cc_ok shouldnt be set if we continue? */
|
||||
done:
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* xml_edit
|
||||
* merge filter into parent
|
||||
* XXX: not called from external?
|
||||
*/
|
||||
static int
|
||||
xml_edit(cxobj *filter,
|
||||
cxobj *parent,
|
||||
enum operation_type op,
|
||||
cbuf *xf_err,
|
||||
cxobj *xorig)
|
||||
{
|
||||
cxobj *attr;
|
||||
cxobj *f;
|
||||
cxobj *s;
|
||||
int retval = -1;
|
||||
char *an, *af;
|
||||
char *fstr, *sstr;
|
||||
int keymatch = 0;
|
||||
|
||||
get_operation(filter, &op, xf_err, xorig);
|
||||
/* 1. First try selection: filter is empty */
|
||||
if (xml_child_nr(filter) == 0){ /* no elements? */
|
||||
retval = edit_selection(filter, parent, op, xf_err, xorig);
|
||||
goto done;
|
||||
}
|
||||
if (xml_child_nr(filter) == 1 && /* same as above */
|
||||
xpath_first(filter, "/[@operation]")){
|
||||
retval = edit_selection(filter, parent, op, xf_err, xorig);
|
||||
goto done;
|
||||
}
|
||||
/* 2. Check attribute match */
|
||||
attr = NULL;
|
||||
while ((attr = xml_child_each(filter, attr, CX_ATTR)) != NULL) {
|
||||
af = xml_value(attr);
|
||||
an = xml_find_value(filter, xml_name(attr));
|
||||
if (af && an && strcmp(af, an)==0)
|
||||
; // match
|
||||
else
|
||||
goto nomatch;
|
||||
}
|
||||
/* 3. Check content match */
|
||||
/*
|
||||
* For content-match we do a somewhat strange thing, we find
|
||||
* a match in first content-node and assume that is unique
|
||||
* and then we remove/replace that
|
||||
* For merge we just continue
|
||||
*/
|
||||
f = NULL;
|
||||
while ((f = xml_child_each(filter, f, CX_ELMNT)) != NULL) {
|
||||
if ((fstr = leafstring(f)) == NULL)
|
||||
continue;
|
||||
/* we found a filter leaf-match: no return we say it should match*/
|
||||
if ((s = xml_find(parent, xml_name(f))) == NULL)
|
||||
goto nomatch;
|
||||
if ((sstr = leafstring(s)) == NULL)
|
||||
goto nomatch;
|
||||
if (strcmp(fstr, sstr))
|
||||
goto nomatch;
|
||||
keymatch++;
|
||||
break; /* match */
|
||||
}
|
||||
|
||||
if (debug && keymatch){
|
||||
fprintf(stderr, "%s: match\n", __FUNCTION__);
|
||||
fprintf(stderr, "%s: filter:\n", __FUNCTION__);
|
||||
clicon_xml2file(stdout, filter, 0, 1);
|
||||
fprintf(stderr, "%s: config:\n", __FUNCTION__);
|
||||
clicon_xml2file(stdout, parent, 0, 1);
|
||||
}
|
||||
|
||||
retval = edit_match(filter, parent, op, xf_err, xorig, keymatch);
|
||||
/* match */
|
||||
netconf_ok_set(1);
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
nomatch:
|
||||
return 0;
|
||||
}
|
||||
#endif /* NOTUSED */
|
||||
36
apps/netconf/netconf_filter.h
Normal file
36
apps/netconf/netconf_filter.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* netconf match & selection: get and edit operations
|
||||
*****************************************************************************/
|
||||
#ifndef _NETCONF_FILTER_H_
|
||||
#define _NETCONF_FILTER_H_
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int xml_filter(cxobj *xf, cxobj *xn);
|
||||
int netconf_xpath(cxobj *xsearch,
|
||||
cxobj *xfilter,
|
||||
cbuf *xf, cbuf *xf_err,
|
||||
cxobj *xt);
|
||||
|
||||
#endif /* _NETCONF_FILTER_H_ */
|
||||
118
apps/netconf/netconf_hello.c
Normal file
118
apps/netconf/netconf_hello.c
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* Code for handling netconf hello messages
|
||||
*****************************************************************************/
|
||||
/*
|
||||
Capabilities are advertised in messages sent by each peer during
|
||||
session establishment. When the NETCONF session is opened, each peer
|
||||
(both client and server) MUST send a <hello> element containing a
|
||||
list of that peer's capabilities. Each peer MUST send at least the
|
||||
base NETCONF capability, "urn:ietf:params:netconf:base:1.0".
|
||||
<hello>
|
||||
<capabilities>
|
||||
<capability>URI</capability>
|
||||
</capabilities>
|
||||
|
||||
</hello>
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/param.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "netconf_lib.h"
|
||||
#include "netconf_hello.h"
|
||||
|
||||
static int
|
||||
netconf_hello(cxobj *xn)
|
||||
{
|
||||
cxobj *x;
|
||||
|
||||
x = NULL;
|
||||
while ((x = xpath_each(xn, "//capability", x)) != NULL) {
|
||||
//fprintf(stderr, "cap: %s\n", xml_body(x));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
netconf_hello_dispatch(cxobj *xn)
|
||||
{
|
||||
cxobj *xp;
|
||||
int retval = -1;
|
||||
|
||||
if ((xp = xpath_first(xn, "//hello")) != NULL)
|
||||
retval = netconf_hello(xp);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* netconf_create_hello
|
||||
* create capability string (once)
|
||||
*/
|
||||
int
|
||||
netconf_create_hello(cbuf *xf, /* msg buffer */
|
||||
int session_id)
|
||||
{
|
||||
int retval = 0;
|
||||
|
||||
add_preamble(xf);
|
||||
cprintf(xf, "<hello>");
|
||||
cprintf(xf, "<capabilities>");
|
||||
cprintf(xf, "<capability>urn:ietf:params:xml:ns:netconf:base:1.0</capability>\n");
|
||||
cprintf(xf, "<capability>urn:ietf:params:xml:ns:netconf:capability:candidate:1:0</capability>\n");
|
||||
cprintf(xf, "<capability>urn:ietf:params:xml:ns:netconf:capability:validate:1.0</capability>\n");
|
||||
cprintf(xf, "<capability>urn:ietf:params:netconf:capability:xpath:1.0</capability>\n");
|
||||
cprintf(xf, "<capability>urn:ietf:params:netconf:capability:notification:1.0</capability>\n");
|
||||
|
||||
|
||||
// cprintf(xf, "<capability>urn:rnr:rnrapi:1:0</capability>");
|
||||
cprintf(xf, "</capabilities>");
|
||||
cprintf(xf, "<session-id>%lu</session-id>", 42+session_id);
|
||||
cprintf(xf, "</hello>");
|
||||
add_postamble(xf);
|
||||
return retval;
|
||||
}
|
||||
34
apps/netconf/netconf_hello.h
Normal file
34
apps/netconf/netconf_hello.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* Code for handling netconf hello messages
|
||||
*****************************************************************************/
|
||||
#ifndef _NETCONF_HELLO_H_
|
||||
#define _NETCONF_HELLO_H_
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int netconf_create_hello(cbuf *xf, int session_id);
|
||||
|
||||
int netconf_hello_dispatch(cxobj *xn);
|
||||
|
||||
#endif /* _NETCONF_HELLO_H_ */
|
||||
261
apps/netconf/netconf_lib.c
Normal file
261
apps/netconf/netconf_lib.c
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* netconf lib
|
||||
*****************************************************************************/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <syslog.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/param.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "netconf_rpc.h"
|
||||
#include "netconf_lib.h"
|
||||
|
||||
/*
|
||||
* Exported variables
|
||||
*/
|
||||
enum transport_type transport = NETCONF_SSH;
|
||||
int cc_closed = 0;
|
||||
|
||||
static int cc_ok = 0;
|
||||
|
||||
void
|
||||
netconf_ok_set(int ok)
|
||||
{
|
||||
cc_ok = ok;
|
||||
}
|
||||
|
||||
int
|
||||
netconf_ok_get(void)
|
||||
{
|
||||
return cc_ok;
|
||||
}
|
||||
|
||||
int
|
||||
add_preamble(cbuf *xf)
|
||||
{
|
||||
if (transport == NETCONF_SOAP)
|
||||
cprintf(xf, "\n<soapenv:Envelope\n xmlns:soapenv=\"http://www.w3.org/2003/05/soap-envelope\">\n"
|
||||
"<soapenv:Body>");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* add_postamble
|
||||
* add netconf xml postamble of message. That is, xml after the body of the message.
|
||||
* for soap this is the envelope stuff, for ssh this is ]]>]]>
|
||||
*/
|
||||
int
|
||||
add_postamble(cbuf *xf)
|
||||
{
|
||||
switch (transport){
|
||||
case NETCONF_SSH:
|
||||
cprintf(xf, "]]>]]>"); /* Add RFC4742 end-of-message marker */
|
||||
break;
|
||||
case NETCONF_SOAP:
|
||||
cprintf(xf, "\n</soapenv:Body>" "</soapenv:Envelope>");
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* add_error_preamble
|
||||
* compared to regular messages (see add_preamble), error message differ in some
|
||||
* protocols (eg soap) by adding a longer and deeper header.
|
||||
*/
|
||||
int
|
||||
add_error_preamble(cbuf *xf, char *reason)
|
||||
{
|
||||
switch (transport){
|
||||
case NETCONF_SOAP:
|
||||
cprintf(xf, "<soapenv:Envelope xmlns:soapenv=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:xml=\"http://www.w3.org/XML/1998/namespace\">"
|
||||
"<soapenv:Body>"
|
||||
"<soapenv:Fault>"
|
||||
"<soapenv:Code>"
|
||||
"<soapenv:Value>env:Receiver</soapenv:Value>"
|
||||
"</soapenv:Code>"
|
||||
"<soapenv:Reason>"
|
||||
"<soapenv:Text xml:lang=\"en\">%s</soapenv:Text>"
|
||||
"</soapenv:Reason>"
|
||||
"<detail>", reason);
|
||||
break;
|
||||
default:
|
||||
if (add_preamble(xf) < 0)
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* add_error_postamble
|
||||
* compared to regular messages (see add_postamble), error message differ in some
|
||||
* protocols (eg soap) by adding a longer and deeper header.
|
||||
*/
|
||||
int
|
||||
add_error_postamble(cbuf *xf)
|
||||
{
|
||||
switch (transport){
|
||||
case NETCONF_SOAP:
|
||||
cprintf(xf, "</detail>" "</soapenv:Fault>");
|
||||
default: /* fall through */
|
||||
if (add_postamble(xf) < 0)
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Look for a text pattern in an input string, one char at a time
|
||||
* @param[in] tag What to look for
|
||||
* @param[in] ch New input character
|
||||
* @param[in,out] state A state integer holding how far we have parsed.
|
||||
* @retval 0 No, we havent detected end tag
|
||||
* @retval 1 Yes, we have detected end tag!
|
||||
* XXX: move to clicon_xml?
|
||||
*/
|
||||
int
|
||||
detect_endtag(char *tag, char ch, int *state)
|
||||
{
|
||||
int retval = 0;
|
||||
|
||||
if (tag[*state] == ch){
|
||||
(*state)++;
|
||||
if (*state == strlen(tag)){
|
||||
*state = 0;
|
||||
retval = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
*state = 0;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* target_locked
|
||||
* return 1 if locked, 0 if not.
|
||||
* return clientid if locked.
|
||||
*/
|
||||
int
|
||||
target_locked(enum target_type target, int *client)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* unlock_target
|
||||
*/
|
||||
int
|
||||
unlock_target(enum target_type target)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
lock_target(enum target_type target)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Get "target" attribute, return actual database given candidate or running
|
||||
* Caller must do error handling
|
||||
* @retval dbname Actual database file name
|
||||
*/
|
||||
char *
|
||||
netconf_get_target(clicon_handle h, cxobj *xn, char *path)
|
||||
{
|
||||
cxobj *x;
|
||||
char *target = NULL;
|
||||
char *running_db;
|
||||
char *candidate_db;
|
||||
|
||||
if ((running_db = clicon_running_db(h)) == NULL)
|
||||
goto done;
|
||||
if ((candidate_db = clicon_candidate_db(h)) == NULL)
|
||||
goto done;
|
||||
if ((x = xpath_first(xn, path)) != NULL){
|
||||
if (xpath_first(x, "candidate") != NULL)
|
||||
target = candidate_db;
|
||||
else
|
||||
if (xpath_first(x, "running") != NULL)
|
||||
target = running_db;
|
||||
}
|
||||
done:
|
||||
return target;
|
||||
|
||||
}
|
||||
|
||||
/*! Send netconf message from cbuf on socket
|
||||
* @param[in] s
|
||||
* @param[in] cb Cligen buffer that contains the XML message
|
||||
* @param[in] msg Only for debug
|
||||
*/
|
||||
int
|
||||
netconf_output(int s, cbuf *xf, char *msg)
|
||||
{
|
||||
char *buf = cbuf_get(xf);
|
||||
int len = cbuf_len(xf);
|
||||
int retval = -1;
|
||||
|
||||
clicon_debug(1, "SEND %s", msg);
|
||||
if (debug > 1){ /* XXX: below only works to stderr, clicon_debug may log to syslog */
|
||||
cxobj *xt = NULL;
|
||||
if (clicon_xml_parse_string(&buf, &xt) == 0){
|
||||
clicon_xml2file(stderr, xml_child_i(xt, 0), 0, 0);
|
||||
fprintf(stderr, "\n");
|
||||
xml_free(xt);
|
||||
}
|
||||
}
|
||||
if (write(s, buf, len) < 0){
|
||||
if (errno == EPIPE)
|
||||
;
|
||||
else
|
||||
clicon_log(LOG_ERR, "%s: write: %s", __FUNCTION__, strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
75
apps/netconf/netconf_lib.h
Normal file
75
apps/netconf/netconf_lib.h
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* Netconf lib
|
||||
*****************************************************************************/
|
||||
#ifndef _NETCONF_LIB_H_
|
||||
#define _NETCONF_LIB_H_
|
||||
|
||||
/*
|
||||
* Types
|
||||
*/
|
||||
enum target_type{ /* netconf */
|
||||
RUNNING,
|
||||
CANDIDATE
|
||||
};
|
||||
enum transport_type{
|
||||
NETCONF_SSH, /* RFC 4742 */
|
||||
NETCONF_SOAP, /* RFC 4743 */
|
||||
};
|
||||
|
||||
enum test_option{ /* edit-config */
|
||||
SET,
|
||||
TEST_THEN_SET,
|
||||
TEST_ONLY
|
||||
};
|
||||
|
||||
enum error_option{ /* edit-config */
|
||||
STOP_ON_ERROR,
|
||||
CONTINUE_ON_ERROR
|
||||
};
|
||||
|
||||
enum filter_option{ /* get-config/filter */
|
||||
FILTER_SUBTREE,
|
||||
FILTER_XPATH
|
||||
};
|
||||
|
||||
/*
|
||||
* Variables
|
||||
*/
|
||||
extern enum transport_type transport;
|
||||
extern int cc_closed;
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
void netconf_ok_set(int ok);
|
||||
int netconf_ok_get(void);
|
||||
|
||||
int add_preamble(cbuf *xf);
|
||||
int add_postamble(cbuf *xf);
|
||||
int add_error_preamble(cbuf *xf, char *reason);
|
||||
int detect_endtag(char *tag, char ch, int *state);
|
||||
char *netconf_get_target(clicon_handle h, cxobj *xn, char *path);
|
||||
int add_error_postamble(cbuf *xf);
|
||||
int netconf_output(int s, cbuf *xf, char *msg);
|
||||
|
||||
#endif /* _NETCONF_LIB_H_ */
|
||||
425
apps/netconf/netconf_main.c
Normal file
425
apps/netconf/netconf_main.c
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "clicon_netconf.h"
|
||||
#include "netconf_lib.h"
|
||||
#include "netconf_hello.h"
|
||||
#include "netconf_plugin.h"
|
||||
#include "netconf_rpc.h"
|
||||
|
||||
/* Command line options to be passed to getopt(3) */
|
||||
#define NETCONF_OPTS "hDqf:d:S"
|
||||
|
||||
/*! Process incoming packet
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xf Packet buffer
|
||||
*/
|
||||
static int
|
||||
process_incoming_packet(clicon_handle h,
|
||||
cbuf *xf)
|
||||
{
|
||||
char *str;
|
||||
char *str0;
|
||||
cxobj *xml_req = NULL; /* Request (in) */
|
||||
int isrpc = 0; /* either hello or rpc */
|
||||
cbuf *xf_out;
|
||||
cbuf *xf_err;
|
||||
cbuf *xf1;
|
||||
|
||||
clicon_debug(1, "RECV");
|
||||
clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(xf));
|
||||
if ((str0 = strdup(cbuf_get(xf))) == NULL){
|
||||
clicon_log(LOG_ERR, "%s: strdup: %s", __FUNCTION__, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
str = str0;
|
||||
/* Parse incoming XML message */
|
||||
if (clicon_xml_parse_string(&str, &xml_req) < 0){
|
||||
if ((xf = cbuf_new()) == NULL){
|
||||
netconf_create_rpc_error(xf, NULL,
|
||||
"operation-failed",
|
||||
"rpc", "error",
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
netconf_output(1, xf, "rpc-error");
|
||||
cbuf_free(xf);
|
||||
}
|
||||
else
|
||||
clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__);
|
||||
free(str0);
|
||||
goto done;
|
||||
}
|
||||
free(str0);
|
||||
if (xpath_first(xml_req, "//rpc") != NULL){
|
||||
isrpc++;
|
||||
}
|
||||
else
|
||||
if (xpath_first(xml_req, "//hello") != NULL)
|
||||
;
|
||||
else{
|
||||
clicon_log(LOG_WARNING, "Invalid netconf msg: neither rpc or hello: dropp\
|
||||
ed");
|
||||
goto done;
|
||||
}
|
||||
/* Initialize response buffers */
|
||||
if ((xf_out = cbuf_new()) == NULL){
|
||||
clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Create error buf */
|
||||
if ((xf_err = cbuf_new()) == NULL){
|
||||
clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__);
|
||||
goto done;
|
||||
}
|
||||
netconf_ok_set(0);
|
||||
if (isrpc){
|
||||
if (netconf_rpc_dispatch(h,
|
||||
xml_req,
|
||||
xpath_first(xml_req, "//rpc"),
|
||||
xf_out, xf_err) < 0){
|
||||
assert(cbuf_len(xf_err));
|
||||
clicon_debug(1, "%s", cbuf_get(xf_err));
|
||||
if (isrpc){
|
||||
if (netconf_output(1, xf_err, "rpc-error") < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else{
|
||||
if ((xf1 = cbuf_new()) != NULL){
|
||||
if (netconf_create_rpc_reply(xf1, xml_req, cbuf_get(xf_out), netconf_ok_get()) < 0){
|
||||
cbuf_free(xf_out);
|
||||
cbuf_free(xf_err);
|
||||
cbuf_free(xf1);
|
||||
goto done;
|
||||
}
|
||||
if (netconf_output(1, xf1, "rpc-reply") < 0){
|
||||
cbuf_reset(xf1);
|
||||
netconf_create_rpc_error(xf1, xml_req, "operation-failed",
|
||||
"protocol", "error",
|
||||
NULL, cbuf_get(xf_err));
|
||||
netconf_output(1, xf1, "rpc-error");
|
||||
cbuf_free(xf_out);
|
||||
cbuf_free(xf_err);
|
||||
cbuf_free(xf1);
|
||||
goto done;
|
||||
}
|
||||
cbuf_free(xf1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
netconf_hello_dispatch(xml_req); /* XXX: return-value */
|
||||
}
|
||||
cbuf_free(xf_out);
|
||||
cbuf_free(xf_err);
|
||||
done:
|
||||
if (xml_req)
|
||||
xml_free(xml_req);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Get netconf message: detect end-of-msg
|
||||
* @param[in] s Socket where input arrived. read from this.
|
||||
* @param[in] arg Clicon handle.
|
||||
*/
|
||||
static int
|
||||
netconf_input_cb(int s,
|
||||
void *arg)
|
||||
{
|
||||
clicon_handle h = arg;
|
||||
unsigned char buf[BUFSIZ];
|
||||
int i;
|
||||
int len;
|
||||
static cbuf *xf; /* XXX: should use ce state? */
|
||||
int xml_state = 0;
|
||||
int retval = -1;
|
||||
|
||||
if (xf == NULL)
|
||||
if ((xf = cbuf_new()) == NULL){
|
||||
clicon_err(OE_XML, errno, "%s: cbuf_new", __FUNCTION__);
|
||||
return retval;
|
||||
}
|
||||
memset(buf, 0, sizeof(buf));
|
||||
if ((len = read(s, buf, sizeof(buf))) < 0){
|
||||
if (errno == ECONNRESET)
|
||||
len = 0; /* emulate EOF */
|
||||
else{
|
||||
clicon_log(LOG_ERR, "%s: read: %s", __FUNCTION__, strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
} /* read */
|
||||
if (len == 0){ /* EOF */
|
||||
cc_closed++;
|
||||
close(s);
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (i=0; i<len; i++){
|
||||
if (buf[i] == 0)
|
||||
continue; /* Skip NULL chars (eg from terminals) */
|
||||
cprintf(xf, "%c", buf[i]);
|
||||
if (detect_endtag("]]>]]>",
|
||||
buf[i],
|
||||
&xml_state)) {
|
||||
/* OK, we have an xml string from a client */
|
||||
if (process_incoming_packet(h, xf) < 0){
|
||||
goto done;
|
||||
}
|
||||
if (cc_closed)
|
||||
break;
|
||||
cbuf_reset(xf);
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
// cbuf_free(xf);
|
||||
if (cc_closed)
|
||||
retval = -1;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* send_hello
|
||||
* args: s file descriptor to write on (eg 1 - stdout)
|
||||
*/
|
||||
static int
|
||||
send_hello(int s)
|
||||
{
|
||||
cbuf *xf;
|
||||
int retval = -1;
|
||||
|
||||
if ((xf = cbuf_new()) == NULL){
|
||||
clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__);
|
||||
goto done;
|
||||
}
|
||||
if (netconf_create_hello(xf, getpid()) < 0)
|
||||
goto done;
|
||||
if (netconf_output(s, xf, "hello") < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (xf)
|
||||
cbuf_free(xf);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* from init_candidate_db() and clicon_rpc_copy() */
|
||||
static int
|
||||
init_candidate_db(clicon_handle h, char *running_db, char *candidate_db)
|
||||
{
|
||||
struct stat sb;
|
||||
int retval = -1;
|
||||
|
||||
/* init shared candidate */
|
||||
if (lstat(candidate_db, &sb) < 0){
|
||||
if (clicon_rpc_copy(h, running_db, candidate_db) < 0)
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
unchunk_group(__FUNCTION__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int
|
||||
terminate(clicon_handle h)
|
||||
{
|
||||
yang_spec *yspec;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) != NULL)
|
||||
yspec_free(yspec);
|
||||
clicon_handle_exit(h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* usage
|
||||
*/
|
||||
static void
|
||||
usage(char *argv0, clicon_handle h)
|
||||
{
|
||||
char *netconfdir = clicon_netconf_dir(h);
|
||||
|
||||
fprintf(stderr, "usage:%s\n"
|
||||
"where options are\n"
|
||||
"\t-h\t\tHelp\n"
|
||||
"\t-D\t\tDebug\n"
|
||||
"\t-q\t\tQuiet: dont send hello prompt\n"
|
||||
"\t-f <file>\tConfiguration file (mandatory)\n"
|
||||
"\t-d <dir>\tSpecify netconf plugin directory dir (default: %s)\n"
|
||||
"\t-S\t\tLog on syslog\n",
|
||||
argv0,
|
||||
netconfdir
|
||||
);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char c;
|
||||
char *tmp;
|
||||
char *argv0 = argv[0];
|
||||
int quiet = 0;
|
||||
clicon_handle h;
|
||||
int use_syslog;
|
||||
char *running_db;
|
||||
char *candidate_db;
|
||||
|
||||
/* Defaults */
|
||||
use_syslog = 0;
|
||||
|
||||
/* In the startup, logs to stderr & debug flag set later */
|
||||
clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR);
|
||||
/* Create handle */
|
||||
if ((h = clicon_handle_init()) == NULL)
|
||||
return -1;
|
||||
|
||||
while ((c = getopt(argc, argv, NETCONF_OPTS)) != -1)
|
||||
switch (c) {
|
||||
case 'h' : /* help */
|
||||
usage(argv[0], h);
|
||||
break;
|
||||
case 'D' : /* debug */
|
||||
debug = 1;
|
||||
break;
|
||||
case 'f': /* override config file */
|
||||
if (!strlen(optarg))
|
||||
usage(argv[0], h);
|
||||
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
|
||||
break;
|
||||
case 'S': /* Log on syslog */
|
||||
use_syslog = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Logs, error and debug to stderr or syslog, set debug level
|
||||
*/
|
||||
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO,
|
||||
use_syslog?CLICON_LOG_SYSLOG:CLICON_LOG_STDERR);
|
||||
clicon_debug_init(debug, NULL);
|
||||
|
||||
/* Find and read configfile */
|
||||
if (clicon_options_main(h) < 0)
|
||||
return -1;
|
||||
|
||||
/* Now rest of options */
|
||||
optind = 1;
|
||||
opterr = 0;
|
||||
while ((c = getopt(argc, argv, NETCONF_OPTS)) != -1)
|
||||
switch (c) {
|
||||
case 'h' : /* help */
|
||||
case 'D' : /* debug */
|
||||
case 'f': /* config file */
|
||||
case 'S': /* Log on syslog */
|
||||
break; /* see above */
|
||||
case 'q': /* quiet: dont write hello */
|
||||
quiet++;
|
||||
break;
|
||||
case 'd': /* Plugin directory */
|
||||
if (!strlen(optarg))
|
||||
usage(argv[0], h);
|
||||
clicon_option_str_set(h, "CLICON_NETCONF_DIR", optarg);
|
||||
break;
|
||||
default:
|
||||
usage(argv[0], h);
|
||||
break;
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
/* Parse db spec file */
|
||||
if (yang_spec_main(h, stdout, 0) < 0)
|
||||
goto done;
|
||||
|
||||
/* Initialize plugins group */
|
||||
if (netconf_plugin_load(h) < 0)
|
||||
return -1;
|
||||
|
||||
if ((running_db = clicon_running_db(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "running db not set");
|
||||
goto done;
|
||||
}
|
||||
if ((candidate_db = clicon_candidate_db(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "candidate db not set");
|
||||
goto done;
|
||||
}
|
||||
if (init_candidate_db(h, running_db, candidate_db) < 0)
|
||||
return -1;
|
||||
/* Call start function is all plugins before we go interactive */
|
||||
tmp = *(argv-1);
|
||||
*(argv-1) = argv0;
|
||||
netconf_plugin_start(h, argc+1, argv-1);
|
||||
*(argv-1) = tmp;
|
||||
|
||||
if (!quiet)
|
||||
send_hello(1);
|
||||
if (event_reg_fd(0, netconf_input_cb, h, "netconf socket") < 0)
|
||||
goto done;
|
||||
if (debug)
|
||||
clicon_option_dump(h, debug);
|
||||
|
||||
if (event_loop() < 0)
|
||||
goto done;
|
||||
done:
|
||||
|
||||
netconf_plugin_unload(h);
|
||||
terminate(h);
|
||||
clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */
|
||||
clicon_log(LOG_NOTICE, "%s: %u Terminated\n", __PROGRAM__, getpid());
|
||||
return 0;
|
||||
}
|
||||
277
apps/netconf/netconf_plugin.c
Normal file
277
apps/netconf/netconf_plugin.c
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* handling netconf plugins
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <syslog.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <dirent.h>
|
||||
#include <grp.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
/* clicon netconf*/
|
||||
#include "clicon_netconf.h"
|
||||
#include "netconf_lib.h"
|
||||
#include "netconf_plugin.h"
|
||||
|
||||
/*
|
||||
* Unload a plugin
|
||||
*/
|
||||
static int
|
||||
plugin_unload(clicon_handle h, void *handle)
|
||||
{
|
||||
int retval = 0;
|
||||
char *error;
|
||||
plgexit_t *exitfn;
|
||||
|
||||
/* Call exit function is it exists */
|
||||
exitfn = dlsym(handle, PLUGIN_EXIT);
|
||||
if (dlerror() == NULL)
|
||||
exitfn(h);
|
||||
|
||||
dlerror(); /* Clear any existing error */
|
||||
if (dlclose(handle) != 0) {
|
||||
error = (char*)dlerror();
|
||||
clicon_err(OE_PLUGIN, errno, "dlclose: %s\n", error ? error : "Unknown error");
|
||||
/* Just report */
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Load a dynamic plugin object and call it's init-function
|
||||
* Note 'file' may be destructively modified
|
||||
*/
|
||||
static plghndl_t
|
||||
plugin_load (clicon_handle h, char *file, int dlflags, const char *cnklbl)
|
||||
{
|
||||
char *error;
|
||||
void *handle = NULL;
|
||||
plginit_t *initfn;
|
||||
|
||||
dlerror(); /* Clear any existing error */
|
||||
if ((handle = dlopen (file, dlflags)) == NULL) {
|
||||
error = (char*)dlerror();
|
||||
clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error");
|
||||
goto quit;
|
||||
}
|
||||
/* call plugin_init() if defined */
|
||||
if ((initfn = dlsym(handle, PLUGIN_INIT)) != NULL) {
|
||||
if (initfn(h) != 0) {
|
||||
clicon_err(OE_PLUGIN, errno, "Failed to initiate %s\n", strrchr(file,'/')?strchr(file, '/'):file);
|
||||
goto quit;
|
||||
}
|
||||
}
|
||||
quit:
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
static int nplugins = 0;
|
||||
static plghndl_t *plugins = NULL;
|
||||
static netconf_reg_t *deps = NULL;
|
||||
|
||||
/*
|
||||
* netconf_plugin_load
|
||||
* Load allplugins you can find in CLICON_NETCONF_DIR
|
||||
*/
|
||||
int
|
||||
netconf_plugin_load(clicon_handle h)
|
||||
{
|
||||
int retval = -1;
|
||||
char *dir;
|
||||
int ndp;
|
||||
struct dirent *dp;
|
||||
int i;
|
||||
char *filename;
|
||||
plghndl_t *handle;
|
||||
|
||||
if ((dir = clicon_netconf_dir(h)) == NULL){
|
||||
clicon_err(OE_PLUGIN, 0, "clicon_netconf_dir not defined");
|
||||
goto quit;
|
||||
}
|
||||
|
||||
/* Get plugin objects names from plugin directory */
|
||||
if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG, __FUNCTION__))<0)
|
||||
goto quit;
|
||||
|
||||
/* Load all plugins */
|
||||
for (i = 0; i < ndp; i++) {
|
||||
filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, dp[i].d_name);
|
||||
clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...",
|
||||
(int)strlen(filename), filename);
|
||||
if (filename == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "chunk");
|
||||
goto quit;
|
||||
}
|
||||
if ((handle = plugin_load (h, filename, RTLD_NOW, __FUNCTION__)) == NULL)
|
||||
goto quit;
|
||||
if ((plugins = rechunk(plugins, (nplugins+1) * sizeof (*plugins), NULL)) == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "chunk");
|
||||
goto quit;
|
||||
}
|
||||
plugins[nplugins++] = handle;
|
||||
unchunk (filename);
|
||||
}
|
||||
retval = 0;
|
||||
quit:
|
||||
unchunk_group(__FUNCTION__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
netconf_plugin_unload(clicon_handle h)
|
||||
{
|
||||
int i;
|
||||
netconf_reg_t *nr;
|
||||
|
||||
while((nr = deps) != NULL) {
|
||||
DELQ(nr, deps, netconf_reg_t *);
|
||||
if (nr->nr_tag)
|
||||
free(nr->nr_tag);
|
||||
free(nr);
|
||||
}
|
||||
for (i = 0; i < nplugins; i++)
|
||||
plugin_unload(h, plugins[i]);
|
||||
if (plugins)
|
||||
unchunk(plugins);
|
||||
nplugins = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Call plugin_start in all plugins
|
||||
*/
|
||||
int
|
||||
netconf_plugin_start(clicon_handle h, int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
plgstart_t *startfn;
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
/* Call exit function is it exists */
|
||||
if ((startfn = dlsym(plugins[i], PLUGIN_START)) == NULL)
|
||||
break;
|
||||
optind = 0;
|
||||
if (startfn(h, argc, argv) < 0) {
|
||||
clicon_debug(1, "plugin_start() failed\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* netconf_register_callback
|
||||
* Called from plugin to register a callback for a specific netconf XML tag.
|
||||
*/
|
||||
int
|
||||
netconf_register_callback(clicon_handle h,
|
||||
netconf_cb_t cb, /* Callback called */
|
||||
void *arg, /* Arg to send to callback */
|
||||
char *tag) /* Xml tag when callback is made */
|
||||
{
|
||||
netconf_reg_t *nr;
|
||||
|
||||
if ((nr = malloc(sizeof(netconf_reg_t))) == NULL) {
|
||||
clicon_err(OE_DB, errno, "malloc: %s", strerror(errno));
|
||||
goto catch;
|
||||
}
|
||||
memset (nr, 0, sizeof (*nr));
|
||||
nr->nr_callback = cb;
|
||||
nr->nr_arg = arg;
|
||||
nr->nr_tag = strdup(tag); /* strdup */
|
||||
INSQ(nr, deps);
|
||||
return 0;
|
||||
catch:
|
||||
if (nr){
|
||||
if (nr->nr_tag)
|
||||
free(nr->nr_tag);
|
||||
free(nr);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*! See if there is any callback registered for this tag
|
||||
*
|
||||
* @param xn Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>.
|
||||
* @param xf Output xml stream. For reply
|
||||
* @param xf_err Error xml stream. For error reply
|
||||
* @param xorig Original request.
|
||||
*
|
||||
* @retval -1 Error
|
||||
* @retval 0 OK, not found handler.
|
||||
* @retval 1 OK, handler called
|
||||
*/
|
||||
int
|
||||
netconf_plugin_callbacks(clicon_handle h,
|
||||
cxobj *xn,
|
||||
cbuf *xf,
|
||||
cbuf *xf_err,
|
||||
cxobj *xorig)
|
||||
{
|
||||
netconf_reg_t *nr;
|
||||
int retval;
|
||||
|
||||
if (deps == NULL)
|
||||
return 0;
|
||||
nr = deps;
|
||||
do {
|
||||
if (strcmp(nr->nr_tag, xml_name(xn)) == 0){
|
||||
if ((retval = nr->nr_callback(h,
|
||||
xorig,
|
||||
xn,
|
||||
xf,
|
||||
xf_err,
|
||||
nr->nr_arg)) < 0)
|
||||
return -1;
|
||||
else
|
||||
return 1; /* handled */
|
||||
}
|
||||
nr = NEXTQ(netconf_reg_t *, nr);
|
||||
} while (nr != deps);
|
||||
return 0;
|
||||
}
|
||||
|
||||
57
apps/netconf/netconf_plugin.h
Normal file
57
apps/netconf/netconf_plugin.h
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* handling netconf plugins
|
||||
*****************************************************************************/
|
||||
#ifndef _NETCONF_PLUGIN_H_
|
||||
#define _NETCONF_PLUGIN_H_
|
||||
|
||||
/*
|
||||
* Types
|
||||
*/
|
||||
|
||||
/* Database dependency description */
|
||||
struct netconf_reg {
|
||||
qelem_t nr_qelem; /* List header */
|
||||
netconf_cb_t nr_callback; /* Validation/Commit Callback */
|
||||
void *nr_arg; /* Application specific argument to cb */
|
||||
char *nr_tag; /* Xml tag when matched, callback called */
|
||||
};
|
||||
typedef struct netconf_reg netconf_reg_t;
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int netconf_plugin_load(clicon_handle h);
|
||||
|
||||
int netconf_plugin_start(clicon_handle h, int argc, char **argv);
|
||||
|
||||
int netconf_plugin_unload(clicon_handle h);
|
||||
|
||||
|
||||
int netconf_plugin_callbacks(clicon_handle h,
|
||||
// dbspec_key *dbspec,
|
||||
cxobj *xn,
|
||||
cbuf *xf,
|
||||
cbuf *xf_err,
|
||||
cxobj *xt);
|
||||
|
||||
#endif /* _NETCONF_PLUGIN_H_ */
|
||||
1258
apps/netconf/netconf_rpc.c
Normal file
1258
apps/netconf/netconf_rpc.c
Normal file
File diff suppressed because it is too large
Load diff
48
apps/netconf/netconf_rpc.h
Normal file
48
apps/netconf/netconf_rpc.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* Code for handling netconf rpc messages
|
||||
*****************************************************************************/
|
||||
#ifndef _NETCONF_RPC_H_
|
||||
#define _NETCONF_RPC_H_
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int
|
||||
netconf_rpc_dispatch(clicon_handle h,
|
||||
cxobj *xorig,
|
||||
cxobj *xn,
|
||||
cbuf *xf,
|
||||
cbuf *xf_err);
|
||||
|
||||
int netconf_create_rpc_reply(cbuf *xf, /* msg buffer */
|
||||
cxobj *xr, /* orig request */
|
||||
char *body, int ok);
|
||||
int netconf_create_rpc_error(cbuf *xf, /* msg buffer */
|
||||
cxobj *xr, /* orig request */
|
||||
char *tag,
|
||||
char *type,
|
||||
char *severity,
|
||||
char *message,
|
||||
char *info);
|
||||
|
||||
#endif /* _NETCONF_RPC_H_ */
|
||||
Loading…
Add table
Add a link
Reference in a new issue