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

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.