Inital commit

This commit is contained in:
Olof hagsand 2016-02-22 22:17:30 +01:00
parent edc5e091bb
commit d6e393ea58
145 changed files with 58117 additions and 0 deletions

68
apps/Makefile.in Normal file
View 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
View 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

File diff suppressed because it is too large Load diff

View 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_ */

View 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 */

View 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_ */

View 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_ */

View 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;
}

View 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
View 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;
}

View 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;
}

View 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_ */

View 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;
}

View 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_ */

View 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_ */

View 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;
}

View 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_ */

View 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;
}

View 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_ */

Binary file not shown.

134
apps/cli/Makefile.in Normal file
View 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

File diff suppressed because it is too large Load diff

33
apps/cli/cli_common.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

90
apps/cli/cli_plugin.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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

View 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_ */

View 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 */

View 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_ */

View 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;
}

View 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
View 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;
}

View 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
View 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;
}

View 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;
}

View 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

File diff suppressed because it is too large Load diff

View 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_ */