Inital commit
This commit is contained in:
parent
edc5e091bb
commit
d6e393ea58
145 changed files with 58117 additions and 0 deletions
137
apps/backend/Makefile.in
Normal file
137
apps/backend/Makefile.in
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#
|
||||
# Makefile
|
||||
#
|
||||
# Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
#
|
||||
# This file is part of CLICON.
|
||||
#
|
||||
# CLICON is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# CLICON is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with CLICON; see the file COPYING. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
VPATH = @srcdir@
|
||||
srcdir = @srcdir@
|
||||
top_srcdir = @top_srcdir@
|
||||
CC = @CC@
|
||||
CFLAGS = @CFLAGS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
|
||||
prefix = @prefix@
|
||||
exec_prefix = @exec_prefix@
|
||||
bindir = @bindir@
|
||||
libdir = @libdir@
|
||||
sbindir = @sbindir@
|
||||
libexecdir = @libexecdir@
|
||||
localstatedir = @localstatedir@
|
||||
sysconfdir = @sysconfdir@
|
||||
includedir = @includedir@
|
||||
|
||||
SH_SUFFIX = @SH_SUFFIX@
|
||||
CLICON_MAJOR = @CLICON_VERSION_MAJOR@
|
||||
CLICON_MINOR = @CLICON_VERSION_MINOR@
|
||||
|
||||
# Use this clicon lib for linking
|
||||
CLICON_LIB = libclicon.so.$(CLICON_MAJOR).$(CLICON_MINOR)
|
||||
# Location of system plugins
|
||||
CLICON_BACKEND_SYSDIR = $(libdir)/clicon/plugins/backend
|
||||
|
||||
# For dependency. A little strange that we rely on it being built in the src dir
|
||||
# even though it may exist in $(libdir). But the new version may not have been installed yet.
|
||||
LIBDEPS = $(top_srcdir)/lib/src/$(CLICON_LIB)
|
||||
|
||||
LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLICON_LIB) -lpthread
|
||||
CPPFLAGS = @CPPFLAGS@ -fPIC
|
||||
INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
|
||||
|
||||
# Not accessible from plugin
|
||||
APPSRC = backend_main.c backend_socket.c backend_client.c \
|
||||
backend_lock.c backend_commit.c backend_plugin.c
|
||||
|
||||
APPOBJ = $(APPSRC:.c=.o)
|
||||
APPL = clicon_backend
|
||||
|
||||
#SHLIB = clicon_backend
|
||||
MYNAME = clicon_backend
|
||||
MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX)
|
||||
MYLIB = $(MYLIBLINK).$(CLICON_MAJOR).$(CLICON_MINOR)
|
||||
MYLIBSO = $(MYLIBLINK).$(CLICON_MAJOR)
|
||||
|
||||
# Accessible from plugin
|
||||
LIBSRC = clicon_backend_transaction.c clicon_backend_handle.c
|
||||
LIBOBJ = $(LIBSRC:.c=.o)
|
||||
|
||||
all: $(MYLIB) $(APPL) test
|
||||
|
||||
clean:
|
||||
rm -f *.core $(APPL) $(APPOBJ) $(LIBOBJ) $(MYLIB) $(MYLIBSO) $(MYLIBLINK)
|
||||
|
||||
distclean: clean
|
||||
rm -f Makefile *~ .depend
|
||||
|
||||
# Put demon in bin
|
||||
# Put other executables in libexec/
|
||||
# Also create a libexec/ directory for writeable/temporary files.
|
||||
# Put config file in etc/
|
||||
install: install-lib $(APPL)
|
||||
install -d $(DESTDIR)$(sbindir)
|
||||
install $(APPL) $(DESTDIR)$(sbindir)
|
||||
|
||||
install-lib: $(MYLIB)
|
||||
install -d $(DESTDIR)$(libdir)
|
||||
install $(MYLIB) $(DESTDIR)$(libdir)
|
||||
ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclicon_config.so.2
|
||||
ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclicon_config.so
|
||||
install -d $(DESTDIR)$(libdir)/clicon/plugins/backend
|
||||
|
||||
uninstall:
|
||||
rm -f $(sbindir)/$(APPL)
|
||||
rm -f $(libdir)/$(MYLIB)
|
||||
rm -f $(includedir)/clicon/*
|
||||
|
||||
install-include: clicon_backend.h clicon_backend_api.h
|
||||
install -d $(DESTDIR)$(includedir)/clicon
|
||||
install -m 644 $^ $(DESTDIR)$(includedir)/clicon
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .c .o
|
||||
|
||||
.c.o:
|
||||
$(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" -DCLICON_BACKEND_SYSDIR=\"$(CLICON_BACKEND_SYSDIR)\" $(CPPFLAGS) $(CFLAGS) -c $<
|
||||
|
||||
# Just link test programs
|
||||
test.c :
|
||||
echo "main(){}" > $@
|
||||
|
||||
test: test.c $(LIBOBJ)
|
||||
$(CC) $(INCLUDES) $(LDFLAGS) $< $(LIBOBJ) -L. -l:$(MYLIB) $(LIBS) -o $@
|
||||
|
||||
$(APPL) : $(APPOBJ) $(MYLIBLINK) $(LIBDEPS)
|
||||
$(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) -L. -l:$(MYLIB) $(LIBS) -o $@
|
||||
|
||||
$(MYLIB): $(LIBOBJ)
|
||||
$(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ -lc $(LIBOBJ) -Wl,-soname=$(MYLIBSO)
|
||||
|
||||
# link-name is needed for application linking, eg for clicon_cli and clicon_backend
|
||||
$(MYLIBLINK) : $(MYLIB)
|
||||
# ln -sf $(MYLIB) $(MYLIBSO)
|
||||
# ln -sf $(MYLIB) $@
|
||||
|
||||
TAGS:
|
||||
find . -name '*.[chyl]' -print | etags -
|
||||
|
||||
depend:
|
||||
$(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend
|
||||
|
||||
#include .depend
|
||||
|
||||
1066
apps/backend/backend_client.c
Normal file
1066
apps/backend/backend_client.c
Normal file
File diff suppressed because it is too large
Load diff
64
apps/backend/backend_client.h
Normal file
64
apps/backend/backend_client.h
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _BACKEND_CLIENT_H_
|
||||
#define _BACKEND_CLIENT_H_
|
||||
|
||||
/*
|
||||
* Types
|
||||
*/
|
||||
/*
|
||||
* Client entry.
|
||||
* Keep state about every connected client.
|
||||
*/
|
||||
struct client_entry{
|
||||
struct client_entry *ce_next; /* The clients linked list */
|
||||
struct sockaddr ce_addr; /* The clients (UNIX domain) address */
|
||||
int ce_s; /* stream socket to client */
|
||||
int ce_nr; /* Client number (for dbg/tracing) */
|
||||
int ce_stat_in; /* Nr of received msgs from client */
|
||||
int ce_stat_out;/* Nr of sent msgs to client */
|
||||
int ce_pid; /* Process id */
|
||||
int ce_uid; /* User id of calling process */
|
||||
clicon_handle ce_handle; /* clicon config handle (all clients have same?) */
|
||||
struct client_subscription *ce_subscription; /* notification subscriptions */
|
||||
};
|
||||
|
||||
/* Notification subscription info
|
||||
* @see subscription in config_handle.c
|
||||
*/
|
||||
struct client_subscription{
|
||||
struct client_subscription *su_next;
|
||||
int su_s; /* stream socket */
|
||||
enum format_enum su_format; /* format of notification stream */
|
||||
char *su_stream;
|
||||
char *su_filter;
|
||||
};
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int backend_client_rm(clicon_handle h, struct client_entry *ce);
|
||||
int config_snapshot(clicon_handle h, char *dbname, char *dir);
|
||||
|
||||
int from_client(int fd, void *arg);
|
||||
|
||||
#endif /* _BACKEND_CLIENT_H_ */
|
||||
684
apps/backend/backend_commit.c
Normal file
684
apps/backend/backend_commit.c
Normal file
|
|
@ -0,0 +1,684 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <pwd.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "clicon_backend_transaction.h"
|
||||
#include "backend_plugin.h"
|
||||
#include "backend_handle.h"
|
||||
#include "backend_commit.h"
|
||||
#include "backend_client.h"
|
||||
|
||||
/*! Key values are checked for validity independent of user-defined callbacks
|
||||
*
|
||||
* Key values are checked as follows:
|
||||
* 1. If no value and default value defined, add it.
|
||||
* 2. If no value and mandatory flag set in spec, report error.
|
||||
* 3. Validate value versus spec, and report error if no match. Currently only int ranges and
|
||||
* string regexp checked.
|
||||
* See also db_lv_set() where defaults are also filled in. The case here for defaults
|
||||
* are if code comes via XML/NETCONF.
|
||||
* @param yspec Yang spec
|
||||
* @param td Transaction data
|
||||
*/
|
||||
static int
|
||||
generic_validate(yang_spec *yspec,
|
||||
transaction_data_t *td)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *x1;
|
||||
cxobj *x2;
|
||||
int i;
|
||||
yang_stmt *ys;
|
||||
|
||||
/* changed entries */
|
||||
for (i=0; i<td->td_clen; i++){
|
||||
x1 = td->td_scvec[i]; /* source changed */
|
||||
x2 = td->td_tcvec[i]; /* target changed */
|
||||
ys = xml_spec(x1);
|
||||
if (xml_yang_validate(x2, ys) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* deleted entries */
|
||||
for (i=0; i<td->td_dlen; i++){
|
||||
x1 = td->td_dvec[i];
|
||||
ys = xml_spec(x1);
|
||||
if (yang_mandatory(ys)){
|
||||
clicon_err(OE_CFG, 0,"Removed mandatory variable: %s",
|
||||
xml_name(x1));
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
/* added entries */
|
||||
for (i=0; i<td->td_alen; i++){
|
||||
x2 = td->td_avec[i];
|
||||
if (xml_yang_validate(x2, xml_spec(x2)) < 0)
|
||||
goto done;
|
||||
if (xml_apply(x2, CX_ELMNT,
|
||||
(xml_applyfn_t*)xml_yang_validate, NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Do a diff between candidate and running, then start a commit transaction
|
||||
*
|
||||
* The code reverts changes if the commit fails. But if the revert
|
||||
* fails, we just ignore the errors and proceed. Maybe we should
|
||||
* do something more drastic?
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] running The current database. The original backend state
|
||||
* @param[in] candidate: The candidate database. The wanted backend state
|
||||
*/
|
||||
int
|
||||
candidate_commit(clicon_handle h,
|
||||
char *candidate,
|
||||
char *running)
|
||||
{
|
||||
int retval = -1;
|
||||
// int i, j;
|
||||
// int failed = 0;
|
||||
struct stat sb;
|
||||
void *firsterr = NULL;
|
||||
yang_spec *yspec;
|
||||
transaction_data_t *td = NULL;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||
goto done;
|
||||
}
|
||||
/* Sanity checks that databases exists. */
|
||||
if (stat(running, &sb) < 0){
|
||||
clicon_err(OE_DB, errno, "%s", running);
|
||||
goto done;
|
||||
}
|
||||
if (stat(candidate, &sb) < 0){
|
||||
clicon_err(OE_DB, errno, "%s", candidate);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 1. Start transaction */
|
||||
if ((td = transaction_new()) == NULL)
|
||||
goto done;
|
||||
|
||||
/* 2. Parse xml trees */
|
||||
if (xmldb_get(running, "/", yspec, &td->td_src) < 0)
|
||||
goto done;
|
||||
if (xmldb_get(candidate, "/", yspec, &td->td_target) < 0)
|
||||
goto done;
|
||||
|
||||
/* 3. Compute differences */
|
||||
if (xml_diff(yspec,
|
||||
td->td_src,
|
||||
td->td_target,
|
||||
&td->td_dvec, /* removed: only in running */
|
||||
&td->td_dlen,
|
||||
&td->td_avec, /* added: only in candidate */
|
||||
&td->td_alen,
|
||||
&td->td_scvec, /* changed: original values */
|
||||
&td->td_tcvec, /* changed: wanted values */
|
||||
&td->td_clen) < 0)
|
||||
goto done;
|
||||
if (debug)
|
||||
transaction_print(stderr, td);
|
||||
|
||||
/* 4. Call plugin transaction start callbacks */
|
||||
if (plugin_transaction_begin(h, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 5. Make generic validation on all new or changed data. */
|
||||
if (generic_validate(yspec, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 6. Call plugin transaction validate callbacks */
|
||||
if (plugin_transaction_validate(h, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 7. Call plugin transaction complete callbacks */
|
||||
if (plugin_transaction_complete(h, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 7. Call plugin transaction commit callbacks */
|
||||
if (plugin_transaction_commit(h, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 8. Copy running back to candidate in case end functions triggered
|
||||
updates in running */
|
||||
if (file_cp(running, candidate) < 0){
|
||||
/* ignore errors or signal major setback ? */
|
||||
clicon_err(OE_UNIX, errno, "file_cp(running, candidate)");
|
||||
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 9. Call plugin transaction end callbacks */
|
||||
plugin_transaction_end(h, td);
|
||||
|
||||
#ifdef OBSOLETE
|
||||
/* Find the differences between the two databases and store it in df vector. */
|
||||
memset(&df, 0, sizeof(df));
|
||||
if (db_diff(running, candidate,
|
||||
__FUNCTION__,
|
||||
clicon_dbspec_key(h),
|
||||
&df
|
||||
) < 0)
|
||||
goto done;
|
||||
|
||||
if (debug){
|
||||
struct dbdiff_ent *dfe;
|
||||
for (i=0; i<df.df_nr; i++) {
|
||||
dfe = &df.df_ents[i];
|
||||
clicon_debug(1, "%s op:%d key:%s",
|
||||
__FUNCTION__,
|
||||
dfe->dfe_op,
|
||||
cvec_name_get(dfe->dfe_vec1?dfe->dfe_vec1:dfe->dfe_vec2));
|
||||
}
|
||||
}
|
||||
/* 1. Get commit processing to dbdiff vector: one entry per key that changed.
|
||||
changes are registered as if they exist in the 1st(candidate) or
|
||||
2nd(running) dbs.
|
||||
*/
|
||||
if (dbdep_commitvec(h, &df, &nvec, &ddvec) < 0)
|
||||
goto done;
|
||||
|
||||
/* 2. Call transaction_begin hooks */
|
||||
if (plugin_transaction_begin(h) < 0)
|
||||
goto done;
|
||||
|
||||
/* call generic cv_validate() on all new or changed keys. */
|
||||
if (generic_validate(yspec, NULL) < 0)
|
||||
goto done;
|
||||
|
||||
/* user-defined validate callbacks registered in dbdep_validate */
|
||||
// if (validate_db(h, nvec, ddvec, running, candidate) < 0)
|
||||
// goto done;
|
||||
|
||||
/* Call plugin post-commit hooks */
|
||||
if (plugin_transaction_complete(h) < 0)
|
||||
goto done;
|
||||
|
||||
if (clicon_commit_order(h) == 0){
|
||||
for (i=0; i < nvec; i++){ /* revert in opposite order */
|
||||
dd = &ddvec[i];
|
||||
dp = dd->dd_dep; /* op, callback, arg */
|
||||
if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
|
||||
continue;
|
||||
dfe = dd->dd_dbdiff; /* key1/key2/op */
|
||||
op = dbdiff2commit_op(dfe->dfe_op);
|
||||
if (plugin_commit_callback(h,
|
||||
op, /* oper */
|
||||
running, /* db1 */
|
||||
candidate, /* db2 */
|
||||
dd->dd_mkey1, /* key1 */
|
||||
dd->dd_mkey2, /* key2 */
|
||||
dd->dd_dbdiff->dfe_vec1, /* vec1 */
|
||||
dd->dd_dbdiff->dfe_vec2, /* vec2 */
|
||||
dp /* callback */
|
||||
) < 0){
|
||||
firsterr = clicon_err_save(); /* save this error */
|
||||
failed++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!failed)
|
||||
if (file_cp(candidate, running) < 0){ /* Commit here in case cp fails */
|
||||
clicon_err(OE_UNIX, errno, "file_cp");
|
||||
failed++;
|
||||
}
|
||||
/* Failed operation, start error handling: rollback in opposite order */
|
||||
if (failed){
|
||||
for (j=i-1; j>=0; j--){ /* revert in opposite order */
|
||||
dd = &ddvec[j];
|
||||
dp = dd->dd_dep; /* op, callback, arg */
|
||||
if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
|
||||
continue;
|
||||
dfe = dd->dd_dbdiff; /* key1/key2/op */
|
||||
op = dbdiff2commit_op(dfe->dfe_op);
|
||||
switch (op){ /* reverse operation */
|
||||
case CO_ADD:
|
||||
op = CO_DELETE;
|
||||
break;
|
||||
case CO_DELETE:
|
||||
op = CO_ADD;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (plugin_commit_callback(h,
|
||||
op, /* oper */
|
||||
candidate, /* db1 */
|
||||
running, /* db2 */
|
||||
dd->dd_mkey2, /* key1 */
|
||||
dd->dd_mkey1, /* key2 */
|
||||
dd->dd_dbdiff->dfe_vec2, /* vec1 */
|
||||
dd->dd_dbdiff->dfe_vec1, /* vec2 */
|
||||
dp /* callback */
|
||||
) < 0){
|
||||
/* ignore errors or signal major setback ? */
|
||||
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
goto done;
|
||||
} /* error handling */
|
||||
}
|
||||
else { /* commit_order == 1 or 2 */
|
||||
/* Now follows commit rules in order.
|
||||
* 4. For all keys that are not in candidate but in running, delete key
|
||||
* in reverse prio order
|
||||
*/
|
||||
for (i = nvec-1; i >= 0; i--){
|
||||
dd = &ddvec[i];
|
||||
dp = dd->dd_dep; /* op, callback, arg */
|
||||
if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
|
||||
continue;
|
||||
dfe = dd->dd_dbdiff; /* key1/key2/op */
|
||||
op = dbdiff2commit_op(dfe->dfe_op);
|
||||
/* original mode 2 where CHANGE=DEL/ADD */
|
||||
if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
|
||||
op = CO_DELETE;
|
||||
if (op != CO_DELETE)
|
||||
continue;
|
||||
if (plugin_commit_callback(h,
|
||||
op, /* oper */
|
||||
running, /* db1 */
|
||||
candidate, /* db2 */
|
||||
dd->dd_mkey1, /* key1 */
|
||||
dd->dd_mkey2, /* key2 */
|
||||
dd->dd_dbdiff->dfe_vec1, /* vec1 */
|
||||
dd->dd_dbdiff->dfe_vec2, /* vec2 */
|
||||
dp /* callback */
|
||||
) < 0){
|
||||
firsterr = clicon_err_save(); /* save this error */
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* 5. Failed deletion, add the key value back to running */
|
||||
if (i >= 0){ /* failed */
|
||||
for (j=i+1; j<nvec; j++){ /* revert in opposite order */
|
||||
dd = &ddvec[j];
|
||||
dp = dd->dd_dep; /* op, callback, arg */
|
||||
if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
|
||||
continue;
|
||||
dfe = dd->dd_dbdiff; /* key1/key2/op */
|
||||
op = dbdiff2commit_op(dfe->dfe_op);
|
||||
/* original mode 2 where CHANGE=DEL/ADD */
|
||||
if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
|
||||
op = CO_DELETE;
|
||||
if (op != CO_DELETE)
|
||||
continue;
|
||||
if (plugin_commit_callback(h,
|
||||
op, /* oper */
|
||||
candidate, /* db1 */
|
||||
running, /* db2 */
|
||||
dd->dd_mkey2, /* key1 */
|
||||
dd->dd_mkey1, /* key2 */
|
||||
dd->dd_dbdiff->dfe_vec2, /* vec1 */
|
||||
dd->dd_dbdiff->dfe_vec1, /* vec2 */
|
||||
dp /* callback */
|
||||
) < 0){
|
||||
/* ignore errors or signal major setback ? */
|
||||
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
/*
|
||||
* 6. For all added or changed keys
|
||||
*/
|
||||
for (i=0; i < nvec; i++){
|
||||
dd = &ddvec[i];
|
||||
dp = dd->dd_dep; /* op, callback, arg */
|
||||
if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
|
||||
continue;
|
||||
dfe = dd->dd_dbdiff; /* key1/key2/op */
|
||||
op = dbdiff2commit_op(dfe->dfe_op);
|
||||
if (op != CO_CHANGE && op != CO_ADD)
|
||||
continue;
|
||||
/* original mode 2 where CHANGE=DEL/ADD */
|
||||
if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
|
||||
op = CO_ADD;
|
||||
if (plugin_commit_callback(h,
|
||||
op, /* oper */
|
||||
running, /* db1 */
|
||||
candidate, /* db2 */
|
||||
dd->dd_mkey1, /* key1 */
|
||||
dd->dd_mkey2, /* key2 */
|
||||
dd->dd_dbdiff->dfe_vec1, /* vec1 */
|
||||
dd->dd_dbdiff->dfe_vec2, /* vec2 */
|
||||
dp /* callback */
|
||||
) < 0){
|
||||
firsterr = clicon_err_save(); /* save this error */
|
||||
failed++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!failed) /* Commit here in case cp fails */
|
||||
if (file_cp(candidate, running) < 0){
|
||||
clicon_err(OE_UNIX, errno, "file_cp(candidate; running)");
|
||||
failed++;
|
||||
}
|
||||
/* 10. Failed setting keys in running, first remove the keys set */
|
||||
if (failed){ /* failed */
|
||||
for (j=i-1; j>=0; j--){ /* revert in opposite order */
|
||||
dd = &ddvec[j];
|
||||
dp = dd->dd_dep; /* op, callback, arg */
|
||||
if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
|
||||
continue;
|
||||
dfe = dd->dd_dbdiff; /* key1/key2/op */
|
||||
op = dbdiff2commit_op(dfe->dfe_op);
|
||||
if (op != CO_CHANGE && op != CO_ADD)
|
||||
continue;
|
||||
/* original mode 2 where CHANGE=DEL/ADD */
|
||||
if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
|
||||
op = CO_ADD;
|
||||
if (op == CO_ADD) /* reverse op */
|
||||
op = CO_DELETE;
|
||||
if (plugin_commit_callback(h,
|
||||
op, /* oper */
|
||||
candidate, /* db1 */
|
||||
running, /* db2 */
|
||||
dd->dd_mkey2, /* key1 */
|
||||
dd->dd_mkey1, /* key2 */
|
||||
dd->dd_dbdiff->dfe_vec2, /* vec1 */
|
||||
dd->dd_dbdiff->dfe_vec1, /* vec2 */
|
||||
dp /* callback */
|
||||
) < 0){
|
||||
/* ignore errors or signal major setback ? */
|
||||
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (j=0; j < nvec; j++){ /* revert in opposite order */
|
||||
dd = &ddvec[j];
|
||||
dp = dd->dd_dep; /* op, callback, arg */
|
||||
if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
|
||||
continue;
|
||||
dfe = dd->dd_dbdiff; /* key1/key2/op */
|
||||
op = dbdiff2commit_op(dfe->dfe_op);
|
||||
/* original mode 2 where CHANGE=DEL/ADD */
|
||||
if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
|
||||
op = CO_DELETE;
|
||||
if (op != CO_DELETE)
|
||||
continue;
|
||||
op = CO_ADD;
|
||||
if (plugin_commit_callback(h,
|
||||
op, /* oper */
|
||||
candidate, /* db1 */
|
||||
running, /* db2 */
|
||||
dd->dd_mkey2, /* key1 */
|
||||
dd->dd_mkey1, /* key2 */
|
||||
dd->dd_dbdiff->dfe_vec2, /* vec1 */
|
||||
dd->dd_dbdiff->dfe_vec1, /* vec2 */
|
||||
dp /* callback */
|
||||
) < 0){
|
||||
/* ignore errors or signal major setback ? */
|
||||
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
} /* commit_order */
|
||||
#endif /* OBSOLETE */
|
||||
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
/* In case of failure, call plugin transaction termination callbacks */
|
||||
if (retval < 0 && td)
|
||||
plugin_transaction_abort(h, td);
|
||||
if (td)
|
||||
transaction_free(td);
|
||||
if (firsterr)
|
||||
clicon_err_restore(firsterr);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Do a diff between candidate and running, then start a validate transaction
|
||||
*
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] running The current database. The original backend state
|
||||
* @param[in] candidate: The candidate database. The wanted backend state
|
||||
*/
|
||||
int
|
||||
candidate_validate(clicon_handle h,
|
||||
char *candidate,
|
||||
char *running)
|
||||
{
|
||||
int retval = -1;
|
||||
struct stat sb;
|
||||
yang_spec *yspec;
|
||||
transaction_data_t *td = NULL;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||
goto done;
|
||||
}
|
||||
/* Sanity checks that databases exists. */
|
||||
if (stat(running, &sb) < 0){
|
||||
clicon_err(OE_DB, errno, "%s", running);
|
||||
goto done;
|
||||
}
|
||||
if (stat(candidate, &sb) < 0){
|
||||
clicon_err(OE_DB, errno, "%s", candidate);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 1. Start transaction */
|
||||
if ((td = transaction_new()) == NULL)
|
||||
goto done;
|
||||
|
||||
/* 2. Parse xml trees */
|
||||
if (xmldb_get(running, "/", yspec, &td->td_src) < 0)
|
||||
goto done;
|
||||
if (xmldb_get(candidate, "/", yspec, &td->td_target) < 0)
|
||||
goto done;
|
||||
|
||||
/* 3. Compute differences */
|
||||
if (xml_diff(yspec,
|
||||
td->td_src,
|
||||
td->td_target,
|
||||
&td->td_dvec, /* removed: only in running */
|
||||
&td->td_dlen,
|
||||
&td->td_avec, /* added: only in candidate */
|
||||
&td->td_alen,
|
||||
&td->td_scvec, /* changed: original values */
|
||||
&td->td_tcvec, /* changed: wanted values */
|
||||
&td->td_clen) < 0)
|
||||
goto done;
|
||||
|
||||
if (debug)
|
||||
transaction_print(stderr, td);
|
||||
|
||||
/* 4. Call plugin start transaction callbacks */
|
||||
if (plugin_transaction_begin(h, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 5. Make generic validation on all new or changed data. */
|
||||
if (generic_validate(yspec, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 6. Call plugin validate transaction callbacks */
|
||||
if (plugin_transaction_validate(h, td) < 0)
|
||||
goto done;
|
||||
|
||||
/* 7. Call plugin complete transaction callbacks */
|
||||
if (plugin_transaction_complete(h, td) < 0)
|
||||
goto done;
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
/* In case of failure, call plugin transaction termination callbacks */
|
||||
if (retval < 0 && td)
|
||||
plugin_transaction_abort(h, td);
|
||||
if (td)
|
||||
transaction_free(td);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Handle an incoming commit message from a client.
|
||||
* XXX: If commit succeeds and snapshot/startup fails, we have strange state:
|
||||
* the commit has succeeded but an error message is returned.
|
||||
*/
|
||||
int
|
||||
from_client_commit(clicon_handle h,
|
||||
int s,
|
||||
struct clicon_msg *msg,
|
||||
const char *label)
|
||||
{
|
||||
int retval = -1;
|
||||
char *candidate;
|
||||
char *running;
|
||||
uint32_t snapshot;
|
||||
uint32_t startup;
|
||||
char *snapshot_0;
|
||||
char *archive_dir;
|
||||
char *startup_config;
|
||||
|
||||
if (clicon_msg_commit_decode(msg, &candidate, &running,
|
||||
&snapshot, &startup, label) < 0)
|
||||
goto err;
|
||||
|
||||
if (candidate_commit(h, candidate, running) < 0){
|
||||
clicon_debug(1, "Commit %s failed", candidate);
|
||||
retval = 0; /* We ignore errors from commit, but maybe
|
||||
we should fail on fatal errors? */
|
||||
goto err;
|
||||
}
|
||||
clicon_debug(1, "Commit %s", candidate);
|
||||
if (snapshot){
|
||||
if ((archive_dir = clicon_archive_dir(h)) == NULL){
|
||||
clicon_err(OE_PLUGIN, 0, "snapshot set and clicon_archive_dir not defined");
|
||||
goto err;
|
||||
}
|
||||
if (config_snapshot(h, running, archive_dir) < 0)
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (startup){
|
||||
if ((archive_dir = clicon_archive_dir(h)) == NULL){
|
||||
clicon_err(OE_PLUGIN, 0, "startup set but clicon_archive_dir not defined");
|
||||
goto err;
|
||||
}
|
||||
if ((startup_config = clicon_startup_config(h)) == NULL){
|
||||
clicon_err(OE_PLUGIN, 0, "startup set but startup_config not defined");
|
||||
goto err;
|
||||
}
|
||||
snapshot_0 = chunk_sprintf(__FUNCTION__, "%s/0", archive_dir);
|
||||
if (file_cp(snapshot_0, startup_config) < 0){
|
||||
clicon_err(OE_PROTO, errno, "%s: Error when creating startup",
|
||||
__FUNCTION__);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
if (send_msg_ok(s) < 0)
|
||||
goto done;
|
||||
goto done;
|
||||
err:
|
||||
/* XXX: more elaborate errstring? */
|
||||
if (send_msg_err(s, clicon_errno, clicon_suberrno, "%s", clicon_err_reason) < 0)
|
||||
retval = -1;
|
||||
done:
|
||||
unchunk_group(__FUNCTION__);
|
||||
|
||||
return retval; /* may be zero if we ignoring errors from commit */
|
||||
} /* from_client_commit */
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Call backend plugin
|
||||
*/
|
||||
int
|
||||
from_client_validate(clicon_handle h,
|
||||
int s,
|
||||
struct clicon_msg *msg,
|
||||
const char *label)
|
||||
{
|
||||
char *dbname;
|
||||
char *running_db;
|
||||
int retval = -1;
|
||||
|
||||
if (clicon_msg_validate_decode(msg, &dbname, label) < 0){
|
||||
send_msg_err(s, clicon_errno, clicon_suberrno,
|
||||
clicon_err_reason);
|
||||
goto err;
|
||||
}
|
||||
|
||||
clicon_debug(1, "Validate %s", dbname);
|
||||
if ((running_db = clicon_running_db(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "running db not set");
|
||||
goto err;
|
||||
}
|
||||
if (candidate_validate(h, dbname, running_db) < 0){
|
||||
clicon_debug(1, "Validate %s failed", dbname);
|
||||
retval = 0; /* We ignore errors from commit, but maybe
|
||||
we should fail on fatal errors? */
|
||||
goto err;
|
||||
}
|
||||
retval = 0;
|
||||
if (send_msg_ok(s) < 0)
|
||||
goto done;
|
||||
goto done;
|
||||
err:
|
||||
/* XXX: more elaborate errstring? */
|
||||
if (send_msg_err(s, clicon_errno, clicon_suberrno, "%s", clicon_err_reason) < 0)
|
||||
retval = -1;
|
||||
done:
|
||||
unchunk_group(__FUNCTION__);
|
||||
return retval;
|
||||
} /* from_client_validate */
|
||||
34
apps/backend/backend_commit.h
Normal file
34
apps/backend/backend_commit.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _BACKEND_COMMIT_H_
|
||||
#define _BACKEND_COMMIT_H_
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int from_client_validate(clicon_handle h, int s, struct clicon_msg *msg, const char *label);
|
||||
int from_client_commit(clicon_handle h, int s, struct clicon_msg *msg, const char *label);
|
||||
int candidate_commit(clicon_handle h, char *candidate, char *running);
|
||||
|
||||
#endif /* _BACKEND_COMMIT_H_ */
|
||||
43
apps/backend/backend_handle.h
Normal file
43
apps/backend/backend_handle.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _BACKEND_HANDLE_H_
|
||||
#define _BACKEND_HANDLE_H_
|
||||
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
* not exported.
|
||||
*/
|
||||
/* backend handles */
|
||||
clicon_handle backend_handle_init(void);
|
||||
|
||||
int backend_handle_exit(clicon_handle h);
|
||||
|
||||
struct client_entry *backend_client_add(clicon_handle h, struct sockaddr *addr);
|
||||
|
||||
struct client_entry *backend_client_list(clicon_handle h);
|
||||
|
||||
int backend_client_delete(clicon_handle h, struct client_entry *ce);
|
||||
|
||||
#endif /* _BACKEND_HANDLE_H_ */
|
||||
95
apps/backend/backend_lock.c
Normal file
95
apps/backend/backend_lock.c
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "backend_lock.h"
|
||||
|
||||
/*
|
||||
* Easy way out: store an integer for candidate db which contains
|
||||
* the session-id of the client holding the lock.
|
||||
* more general: any database
|
||||
* we dont make any sanity check on who is locking.
|
||||
*/
|
||||
static int _db_locked = 0;
|
||||
|
||||
/*
|
||||
* db_lock
|
||||
*/
|
||||
int
|
||||
db_lock(clicon_handle h, int id)
|
||||
{
|
||||
_db_locked = id;
|
||||
clicon_debug(1, "%s: lock db by %u", __FUNCTION__, id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* db_unlock
|
||||
*/
|
||||
int
|
||||
db_unlock(clicon_handle h)
|
||||
{
|
||||
if (!_db_locked )
|
||||
return 0;
|
||||
_db_locked = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* db_islocked
|
||||
* returns id of locker
|
||||
*/
|
||||
int
|
||||
db_islocked(clicon_handle h)
|
||||
{
|
||||
return _db_locked;
|
||||
}
|
||||
|
||||
37
apps/backend/backend_lock.h
Normal file
37
apps/backend/backend_lock.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* Database logical lock functions.
|
||||
* Only one lock (candidate_db)
|
||||
* Not persistent (needs another db)
|
||||
*/
|
||||
|
||||
#ifndef _BACKEND_LOCK_H_
|
||||
#define _BACKEND_LOCK_H_
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int db_lock(clicon_handle h, int id);
|
||||
int db_unlock(clicon_handle h);
|
||||
int db_islocked(clicon_handle h);
|
||||
|
||||
#endif /* _BACKEND_LOCK_H_ */
|
||||
617
apps/backend/backend_main.c
Normal file
617
apps/backend/backend_main.c
Normal file
|
|
@ -0,0 +1,617 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <syslog.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <grp.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "clicon_backend_handle.h"
|
||||
#include "backend_socket.h"
|
||||
#include "backend_client.h"
|
||||
#include "backend_commit.h"
|
||||
#include "backend_plugin.h"
|
||||
#include "backend_handle.h"
|
||||
|
||||
/* Command line options to be passed to getopt(3) */
|
||||
#define BACKEND_OPTS "hD:f:d:Fzu:P:1IRCc::rg:pt"
|
||||
|
||||
/* Cannot use h after this */
|
||||
static int
|
||||
config_terminate(clicon_handle h)
|
||||
{
|
||||
yang_spec *yspec;
|
||||
char *pidfile = clicon_backend_pidfile(h);
|
||||
char *sockpath = clicon_sock(h);
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
if ((yspec = clicon_dbspec_yang(h)) != NULL)
|
||||
yspec_free(yspec);
|
||||
plugin_finish(h);
|
||||
if (pidfile)
|
||||
unlink(pidfile);
|
||||
if (sockpath)
|
||||
unlink(sockpath);
|
||||
backend_handle_exit(h); /* Cannot use h after this */
|
||||
clicon_log_register_callback(NULL, NULL);
|
||||
clicon_debug(1, "%s done", __FUNCTION__);
|
||||
if (debug)
|
||||
chunk_check(stderr, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
config_sig_term
|
||||
Unlink pidfile and quit
|
||||
*/
|
||||
static void
|
||||
config_sig_term(int arg)
|
||||
{
|
||||
static int i=0;
|
||||
if (i++ == 0)
|
||||
clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d",
|
||||
__PROGRAM__, __FUNCTION__, getpid(), arg);
|
||||
clicon_exit_set(); /* checked in event_loop() */
|
||||
}
|
||||
|
||||
/*
|
||||
* usage
|
||||
*/
|
||||
static void
|
||||
usage(char *argv0, clicon_handle h)
|
||||
{
|
||||
char *plgdir = clicon_backend_dir(h);
|
||||
char *confsock = clicon_sock(h);
|
||||
char *confpid = clicon_backend_pidfile(h);
|
||||
char *startup = clicon_startup_config(h);
|
||||
char *group = clicon_sock_group(h);
|
||||
|
||||
fprintf(stderr, "usage:%s\n"
|
||||
"where options are\n"
|
||||
" -h\t\tHelp\n"
|
||||
" -D <level>\tdebug\n"
|
||||
" -f <file>\tCLICON config file (mandatory)\n"
|
||||
" -d <dir>\tSpecify backend plugin directory (default: %s)\n"
|
||||
" -z\t\tKill other config daemon and exit\n"
|
||||
" -F\t\tforeground\n"
|
||||
" -1\t\tonce (dont wait for events)\n"
|
||||
" -u <path>\tconfig UNIX domain path / ip address (default: %s)\n"
|
||||
" -P <file>\tPid filename (default: %s)\n"
|
||||
" -I\t\tInitialize running state database\n"
|
||||
" -R\t\tCall plugin_reset() in plugins to reset system state in running db (use with -I)\n"
|
||||
" -C\t\tCall plugin_reset() in plugins to reset system state in candidate db (use with -I)\n"
|
||||
" -c [<file>]\tLoad specified application config. Default is\n"
|
||||
" \t\"CLICON_STARTUP_CONFIG\" = %s\n"
|
||||
" -r\t\tReload running database\n"
|
||||
" -p \t\tPrint database yang specification\n"
|
||||
" -t \t\tPrint alternate spec translation (eg if YANG print KEY, if KEY print YANG)\n"
|
||||
" -g <group>\tClient membership required to this group (default: %s)\n",
|
||||
argv0,
|
||||
plgdir ? plgdir : "none",
|
||||
confsock ? confsock : "none",
|
||||
confpid ? confpid : "none",
|
||||
startup ? startup : "none",
|
||||
group ? group : "none"
|
||||
);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static int
|
||||
rundb_init(clicon_handle h, char *running_db)
|
||||
{
|
||||
if (unlink(running_db) != 0 && errno != ENOENT) {
|
||||
clicon_err(OE_UNIX, errno, "unlink");
|
||||
return -1;
|
||||
}
|
||||
if (db_init(running_db) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Initialize running-config from file application configuration
|
||||
*
|
||||
* @param[in] h clicon handle
|
||||
* @param[in] app_config_file clicon application configuration file
|
||||
* @param[in] running_db Name of running db
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error. clicon_err set
|
||||
*/
|
||||
static int
|
||||
rundb_main(clicon_handle h,
|
||||
char *app_config_file,
|
||||
char *running_db)
|
||||
{
|
||||
char *tmp = NULL;
|
||||
int retval = -1;
|
||||
int fd = -1;
|
||||
yang_spec *yspec;
|
||||
cxobj *xt = NULL;
|
||||
cxobj *xn;
|
||||
|
||||
if ((tmp = clicon_tmpfile(__FUNCTION__)) == NULL)
|
||||
goto done;
|
||||
if (file_cp(running_db, tmp) < 0){
|
||||
clicon_err(OE_UNIX, errno, "file copy");
|
||||
goto done;
|
||||
}
|
||||
if ((fd = open(app_config_file, O_RDONLY)) < 0){
|
||||
clicon_err(OE_UNIX, errno, "open(%s)", app_config_file);
|
||||
goto done;
|
||||
}
|
||||
if (clicon_xml_parse_file(fd, &xt, "</clicon>") < 0)
|
||||
goto done;
|
||||
yspec = clicon_dbspec_yang(h);
|
||||
if ((xn = xml_child_i(xt, 0)) != NULL)
|
||||
if (xmldb_put(tmp, xn, yspec, OP_MERGE) < 0)
|
||||
goto done;
|
||||
if (candidate_commit(h, tmp, running_db) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (tmp)
|
||||
unlink(tmp);
|
||||
if (xt)
|
||||
xml_free(xt);
|
||||
if (fd != -1)
|
||||
close(fd);
|
||||
unchunk_group(__FUNCTION__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
candb_reset(clicon_handle h, char *running_db)
|
||||
{
|
||||
int retval = -1;
|
||||
char *tmp = NULL;
|
||||
|
||||
if ((tmp = clicon_tmpfile(__FUNCTION__)) == NULL)
|
||||
goto done;
|
||||
if (file_cp(running_db, tmp) < 0){
|
||||
clicon_err(OE_UNIX, errno, "file copy");
|
||||
goto done;
|
||||
}
|
||||
/* Request plugins to reset system state, eg initiate running from system
|
||||
* -R
|
||||
*/
|
||||
if (plugin_reset_state(h, tmp) < 0)
|
||||
goto done;
|
||||
if (candidate_commit(h, tmp, running_db) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (tmp)
|
||||
unlink(tmp);
|
||||
unchunk_group(__FUNCTION__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Create backend server socket and register callback
|
||||
*/
|
||||
static int
|
||||
server_socket(clicon_handle h)
|
||||
{
|
||||
int ss;
|
||||
|
||||
/* Open control socket */
|
||||
if ((ss = config_socket_init(h)) < 0)
|
||||
return -1;
|
||||
/* ss is a server socket that the clients connect to. The callback
|
||||
therefore accepts clients on ss */
|
||||
if (event_reg_fd(ss, config_accept_client, h, "server socket") < 0) {
|
||||
close(ss);
|
||||
return -1;
|
||||
}
|
||||
return ss;
|
||||
}
|
||||
|
||||
/*! Callback for CLICON log events
|
||||
* If you make a subscription to CLICON stream, this function is called for every
|
||||
* log event.
|
||||
*/
|
||||
static int
|
||||
config_log_cb(int level, char *msg, void *arg)
|
||||
{
|
||||
size_t n;
|
||||
char *ptr;
|
||||
char *nptr;
|
||||
char *newmsg = NULL;
|
||||
int retval = -1;
|
||||
|
||||
/* backend_notify() will go through all clients and see if any has registered "CLICON",
|
||||
and if so make a clicon_proto notify message to those clients. */
|
||||
|
||||
|
||||
/* Sanitize '%' into "%%" to prevent segvfaults in vsnprintf later.
|
||||
At this stage all formatting is already done */
|
||||
n = 0;
|
||||
for(ptr=msg; *ptr; ptr++)
|
||||
if (*ptr == '%')
|
||||
n++;
|
||||
if ((newmsg = malloc(strlen(msg) + n + 1)) == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "malloc");
|
||||
return -1;
|
||||
}
|
||||
for(ptr=msg, nptr=newmsg; *ptr; ptr++) {
|
||||
*nptr++ = *ptr;
|
||||
if (*ptr == '%')
|
||||
*nptr++ = '%';
|
||||
}
|
||||
|
||||
retval = backend_notify(arg, "CLICON", level, newmsg);
|
||||
free(newmsg);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char c;
|
||||
int zap;
|
||||
int foreground;
|
||||
int once;
|
||||
int init_rundb;
|
||||
char *running_db;
|
||||
char *candidate_db;
|
||||
int reload_running;
|
||||
int reset_state_running;
|
||||
int reset_state_candidate;
|
||||
char *app_config_file = NULL;
|
||||
char *config_group;
|
||||
char *argv0 = argv[0];
|
||||
char *tmp;
|
||||
struct stat st;
|
||||
clicon_handle h;
|
||||
int help = 0;
|
||||
int printspec = 0;
|
||||
int printalt = 0;
|
||||
int pid;
|
||||
char *pidfile;
|
||||
char *sock;
|
||||
int sockfamily;
|
||||
|
||||
/* In the startup, logs to stderr & syslog and debug flag set later */
|
||||
clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG);
|
||||
/* Initiate CLICON handle */
|
||||
if ((h = backend_handle_init()) == NULL)
|
||||
return -1;
|
||||
if (config_plugin_init(h) != 0)
|
||||
return -1;
|
||||
foreground = 0;
|
||||
once = 0;
|
||||
zap = 0;
|
||||
init_rundb = 0;
|
||||
reload_running = 0;
|
||||
reset_state_running = 0;
|
||||
reset_state_candidate = 0;
|
||||
|
||||
/*
|
||||
* Command-line options for help, debug, and config-file
|
||||
*/
|
||||
opterr = 0;
|
||||
optind = 1;
|
||||
while ((c = getopt(argc, argv, BACKEND_OPTS)) != -1)
|
||||
switch (c) {
|
||||
case '?':
|
||||
case 'h':
|
||||
/* Defer the call to usage() to later. Reason is that for helpful
|
||||
text messages, default dirs, etc, are not set until later.
|
||||
But this measn that we need to check if 'help' is set before
|
||||
exiting, and then call usage() before exit.
|
||||
*/
|
||||
help = 1;
|
||||
break;
|
||||
case 'D' : /* debug */
|
||||
if (sscanf(optarg, "%d", &debug) != 1)
|
||||
usage(argv[0], h);
|
||||
break;
|
||||
case 'f': /* config file */
|
||||
if (!strlen(optarg))
|
||||
usage(argv[0], h);
|
||||
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Syslogs also to stderr, but later turn stderr off in daemon mode.
|
||||
* error only to syslog. debug to syslog
|
||||
*/
|
||||
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG);
|
||||
clicon_debug_init(debug, NULL);
|
||||
|
||||
/* Find and read configfile */
|
||||
if (clicon_options_main(h) < 0){
|
||||
if (help)
|
||||
usage(argv[0], h);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Now run through the operational args */
|
||||
opterr = 1;
|
||||
optind = 1;
|
||||
while ((c = getopt(argc, argv, BACKEND_OPTS)) != -1)
|
||||
switch (c) {
|
||||
case 'D' : /* debug */
|
||||
case 'f': /* config file */
|
||||
break; /* see above */
|
||||
case 'd': /* Plugin directory */
|
||||
if (!strlen(optarg))
|
||||
usage(argv[0], h);
|
||||
clicon_option_str_set(h, "CLICON_BACKEND_DIR", optarg);
|
||||
break;
|
||||
case 'F' : /* foreground */
|
||||
foreground = 1;
|
||||
break;
|
||||
case '1' : /* Quit after reading database once - dont wait for events */
|
||||
once = 1;
|
||||
break;
|
||||
case 'z': /* Zap other process */
|
||||
zap++;
|
||||
break;
|
||||
case 'u': /* config unix domain path / ip address */
|
||||
if (!strlen(optarg))
|
||||
usage(argv[0], h);
|
||||
clicon_option_str_set(h, "CLICON_SOCK", optarg);
|
||||
break;
|
||||
case 'P': /* pidfile */
|
||||
clicon_option_str_set(h, "CLICON_BACKEND_PIDFILE", optarg);
|
||||
break;
|
||||
case 'I': /* Initiate running db */
|
||||
init_rundb++;
|
||||
break;
|
||||
case 'R': /* Reset state directly into running */
|
||||
reset_state_running++;
|
||||
break;
|
||||
case 'C': /* Reset state into candidate and then commit it */
|
||||
reset_state_candidate++;
|
||||
break;
|
||||
case 'c': /* Load application config */
|
||||
app_config_file = optarg ? optarg : clicon_startup_config(h);
|
||||
if (app_config_file == NULL) {
|
||||
fprintf(stderr, "Option \"CLICON_STARTUP_CONFIG\" not set\n");
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case 'r': /* Reload running */
|
||||
reload_running++;
|
||||
break;
|
||||
case 'g': /* config socket group */
|
||||
clicon_option_str_set(h, "CLICON_SOCK_GROUP", optarg);
|
||||
break;
|
||||
case 'p' : /* Print spec */
|
||||
printspec++;
|
||||
break;
|
||||
case 't' : /* Print alternative dbspec format (eg if YANG, print KEY) */
|
||||
printalt++;
|
||||
break;
|
||||
default:
|
||||
usage(argv[0], h);
|
||||
break;
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
/* Defer: Wait to the last minute to print help message */
|
||||
if (help)
|
||||
usage(argv[0], h);
|
||||
|
||||
/* Check pid-file, if zap kil the old daemon, else return here */
|
||||
if ((pidfile = clicon_backend_pidfile(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "pidfile not set");
|
||||
goto done;
|
||||
}
|
||||
sockfamily = clicon_sock_family(h);
|
||||
if ((sock = clicon_sock(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "sock not set");
|
||||
goto done;
|
||||
}
|
||||
if (pidfile_get(pidfile, &pid) < 0)
|
||||
return -1;
|
||||
if (zap){
|
||||
if (pid && pidfile_zapold(pid) < 0)
|
||||
return -1;
|
||||
if (lstat(pidfile, &st) == 0)
|
||||
unlink(pidfile);
|
||||
if (sockfamily==AF_UNIX && lstat(sock, &st) == 0)
|
||||
unlink(sock);
|
||||
exit(0);
|
||||
}
|
||||
else
|
||||
if (pid){
|
||||
clicon_err(OE_DEMON, 0, "Daemon already running with pid %d\n(Try killing it with %s -z)",
|
||||
pid, argv0);
|
||||
return -1; /* goto done deletes pidfile */
|
||||
}
|
||||
|
||||
/* After this point we can goto done on error
|
||||
* Here there is either no old process or we have killed it,..
|
||||
*/
|
||||
if (lstat(pidfile, &st) == 0)
|
||||
unlink(pidfile);
|
||||
if (sockfamily==AF_UNIX && lstat(sock, &st) == 0)
|
||||
unlink(sock);
|
||||
|
||||
/* Sanity check: config group exists */
|
||||
if ((config_group = clicon_sock_group(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "clicon_sock_group option not set");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (group_name2gid(config_group, NULL) < 0){
|
||||
clicon_log(LOG_ERR, "'%s' does not seem to be a valid user group.\n"
|
||||
"The config demon requires a valid group to create a server UNIX socket\n"
|
||||
"Define a valid CLICON_SOCK_GROUP in %s or via the -g option\n"
|
||||
"or create the group and add the user to it. On linux for example:"
|
||||
" sudo groupadd %s\n"
|
||||
" sudo usermod -a -G %s user\n",
|
||||
config_group, clicon_configfile(h), config_group, config_group);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Parse db spec file */
|
||||
if (yang_spec_main(h, stdout, printspec) < 0)
|
||||
goto done;
|
||||
|
||||
if ((running_db = clicon_running_db(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "running db not set");
|
||||
goto done;
|
||||
}
|
||||
if ((candidate_db = clicon_candidate_db(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "candidate db not set");
|
||||
goto done;
|
||||
}
|
||||
/* If running exists and reload_running set, make a copy to candidate */
|
||||
if (reload_running){
|
||||
if (stat(running_db, &st) && errno == ENOENT){
|
||||
clicon_log(LOG_NOTICE, "%s: -r (reload running) option given but no running_db found, proceeding without", __PROGRAM__);
|
||||
reload_running = 0; /* void it, so we dont commit candidate below */
|
||||
}
|
||||
else
|
||||
if (file_cp(running_db, candidate_db) < 0){
|
||||
clicon_err(OE_UNIX, errno, "FATAL: file_cp");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
/* Init running db
|
||||
* -I
|
||||
*/
|
||||
if (init_rundb || (stat(running_db, &st) && errno == ENOENT))
|
||||
if (rundb_init(h, running_db) < 0)
|
||||
goto done;
|
||||
|
||||
/* Initialize plugins
|
||||
(also calls plugin_init() and plugin_start(argc,argv) in each plugin */
|
||||
if (plugin_initiate(h) != 0)
|
||||
goto done;
|
||||
|
||||
if (reset_state_candidate){
|
||||
if (candb_reset(h, running_db) < 0)
|
||||
goto done;
|
||||
}
|
||||
else
|
||||
if (reset_state_running){
|
||||
if (plugin_reset_state(h, running_db) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Call plugin_start */
|
||||
tmp = *(argv-1);
|
||||
*(argv-1) = argv0;
|
||||
if (plugin_start_hooks(h, argc+1, argv-1) < 0)
|
||||
goto done;
|
||||
*(argv-1) = tmp;
|
||||
|
||||
if (reload_running){
|
||||
if (candidate_commit(h, candidate_db, running_db) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Have we specified a config file to load? eg
|
||||
-c <file>
|
||||
-r replace running (obsolete)
|
||||
*/
|
||||
if (app_config_file)
|
||||
if (rundb_main(h, app_config_file, running_db) < 0)
|
||||
goto done;
|
||||
|
||||
/* Initiate the shared candidate. Maybe we should not do this? */
|
||||
if (file_cp(running_db, candidate_db) < 0){
|
||||
clicon_err(OE_UNIX, errno, "FATAL: file_cp");
|
||||
goto done;
|
||||
}
|
||||
/* XXX Hack for now. Change mode so that we all can write. Security issue*/
|
||||
chmod(candidate_db, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
|
||||
|
||||
if (once)
|
||||
goto done;
|
||||
|
||||
/* Daemonize and initiate logging. Note error is initiated here to make
|
||||
demonized errors OK. Before this stage, errors are logged on stderr
|
||||
also */
|
||||
if (foreground==0){
|
||||
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG);
|
||||
if (daemon(0, 0) < 0){
|
||||
fprintf(stderr, "config: daemon");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
/* Write pid-file */
|
||||
|
||||
if ((pid = pidfile_write(pidfile)) < 0)
|
||||
goto done;
|
||||
|
||||
/* Register log notifications */
|
||||
if (clicon_log_register_callback(config_log_cb, h) < 0)
|
||||
goto done;
|
||||
clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid());
|
||||
if (set_signal(SIGTERM, config_sig_term, NULL) < 0){
|
||||
clicon_err(OE_DEMON, errno, "Setting signal");
|
||||
goto done;
|
||||
}
|
||||
if (set_signal(SIGINT, config_sig_term, NULL) < 0){
|
||||
clicon_err(OE_DEMON, errno, "Setting signal");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Initialize server socket */
|
||||
if (server_socket(h) < 0)
|
||||
goto done;
|
||||
|
||||
if (debug)
|
||||
clicon_option_dump(h, debug);
|
||||
|
||||
if (event_loop() < 0)
|
||||
goto done;
|
||||
done:
|
||||
clicon_log(LOG_NOTICE, "%s: %u Terminated", __PROGRAM__, getpid());
|
||||
config_terminate(h); /* Cannot use h after this */
|
||||
|
||||
return 0;
|
||||
}
|
||||
751
apps/backend/backend_plugin.c
Normal file
751
apps/backend/backend_plugin.c
Normal file
|
|
@ -0,0 +1,751 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#define __USE_GNU /* strverscmp */
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <dlfcn.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "clicon_backend_transaction.h"
|
||||
#include "backend_plugin.h"
|
||||
#include "backend_commit.h"
|
||||
|
||||
/*
|
||||
* Types
|
||||
*/
|
||||
/* Following are specific to backend. For common see clicon_plugin.h
|
||||
* @note the following should match the prototypes in clicon_backend.h
|
||||
*/
|
||||
#define PLUGIN_RESET "plugin_reset"
|
||||
typedef int (plgreset_t)(clicon_handle h, char *dbname); /* Reset system status */
|
||||
|
||||
#define PLUGIN_TRANS_BEGIN "transaction_begin"
|
||||
#define PLUGIN_TRANS_VALIDATE "transaction_validate"
|
||||
#define PLUGIN_TRANS_COMPLETE "transaction_complete"
|
||||
#define PLUGIN_TRANS_COMMIT "transaction_commit"
|
||||
#define PLUGIN_TRANS_END "transaction_end"
|
||||
#define PLUGIN_TRANS_ABORT "transaction_abort"
|
||||
|
||||
typedef int (trans_cb_t)(clicon_handle h, transaction_data td); /* Transaction cbs */
|
||||
|
||||
/* Backend (config) plugins */
|
||||
struct plugin {
|
||||
char p_name[PATH_MAX]; /* Plugin name */
|
||||
void *p_handle; /* Dynamic object handle */
|
||||
plginit_t *p_init; /* Init */
|
||||
plgstart_t *p_start; /* Start */
|
||||
plgexit_t *p_exit; /* Exit */
|
||||
plgreset_t *p_reset; /* Reset state */
|
||||
trans_cb_t *p_trans_begin; /* Transaction start */
|
||||
trans_cb_t *p_trans_validate; /* Transaction validation */
|
||||
trans_cb_t *p_trans_complete; /* Transaction validation complete */
|
||||
trans_cb_t *p_trans_commit; /* Transaction commit */
|
||||
trans_cb_t *p_trans_end; /* Transaction completed */
|
||||
trans_cb_t *p_trans_abort; /* Transaction aborted */
|
||||
};
|
||||
|
||||
/*
|
||||
* Local variables
|
||||
*/
|
||||
static int nplugins = 0;
|
||||
static struct plugin *plugins = NULL;
|
||||
|
||||
/*! Find a plugin by name and return the dlsym handl
|
||||
* Used by libclicon code to find callback funcctions in plugins.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] h Name of plugin
|
||||
* @retval handle Plugin handle if found
|
||||
* @retval NULL Not found
|
||||
*/
|
||||
static void *
|
||||
config_find_plugin(clicon_handle h,
|
||||
char *name)
|
||||
{
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++){
|
||||
p = &plugins[i];
|
||||
if (strcmp(p->p_name, name) == 0)
|
||||
return p->p_handle;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*! Initialize plugin code (not the plugins themselves)
|
||||
* @param[in] h Clicon handle
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
config_plugin_init(clicon_handle h)
|
||||
{
|
||||
find_plugin_t *fp = config_find_plugin;
|
||||
clicon_hash_t *data = clicon_data(h);
|
||||
|
||||
/* Register CLICON_FIND_PLUGIN in data hash */
|
||||
if (hash_add(data, "CLICON_FIND_PLUGIN", &fp, sizeof(fp)) == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "failed to register CLICON_FIND_PLUGIN");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Unload a plugin
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] plg Plugin structure
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
plugin_unload(clicon_handle h,
|
||||
struct plugin *plg)
|
||||
{
|
||||
char *error;
|
||||
|
||||
/* Call exit function is it exists */
|
||||
if (plg->p_exit)
|
||||
plg->p_exit(h);
|
||||
|
||||
dlerror(); /* Clear any existing error */
|
||||
if (dlclose(plg->p_handle) != 0) {
|
||||
error = (char*)dlerror();
|
||||
clicon_err(OE_UNIX, 0, "dlclose: %s", error?error:"Unknown error");
|
||||
return -1;
|
||||
/* Just report */
|
||||
}
|
||||
else
|
||||
clicon_debug(1, "Plugin '%s' unloaded.", plg->p_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*! Load a dynamic plugin and call its init-function
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] file The plugin (.so) to load
|
||||
* @param[in] dlflags Arguments to dlopen(3)
|
||||
* @param[in] label Chunk label
|
||||
* @retval plugin Plugin struct
|
||||
* @retval NULL Error
|
||||
*/
|
||||
static struct plugin *
|
||||
plugin_load (clicon_handle h,
|
||||
char *file,
|
||||
int dlflags,
|
||||
const char *label)
|
||||
{
|
||||
char *error;
|
||||
void *handle;
|
||||
char *name;
|
||||
struct plugin *new;
|
||||
plginit_t *initfun;
|
||||
|
||||
dlerror(); /* Clear any existing error */
|
||||
if ((handle = dlopen (file, dlflags)) == NULL) {
|
||||
error = (char*)dlerror();
|
||||
clicon_err(OE_UNIX, 0, "dlopen: %s", error?error:"Unknown error");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
initfun = dlsym(handle, PLUGIN_INIT);
|
||||
if ((error = (char*)dlerror()) != NULL) {
|
||||
clicon_err(OE_UNIX, 0, "dlsym: %s", error);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (initfun(h) != 0) {
|
||||
dlclose(handle);
|
||||
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
||||
clicon_err(OE_DB, 0, "Unknown error: %s: plugin_init does not make clicon_err call on error",
|
||||
file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((new = chunk(sizeof(*new), label)) == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "dhunk: %s", strerror(errno));
|
||||
dlclose(handle);
|
||||
return NULL;
|
||||
}
|
||||
memset(new, 0, sizeof(*new));
|
||||
name = strrchr(file, '/') ? strrchr(file, '/')+1 : file;
|
||||
clicon_debug(2, "Loading plugin '%s'.", name);
|
||||
snprintf(new->p_name, sizeof(new->p_name), "%*s",
|
||||
(int)strlen(name)-2, name);
|
||||
new->p_handle = handle;
|
||||
new->p_init = initfun;
|
||||
if ((new->p_start = dlsym(handle, PLUGIN_START)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_START);
|
||||
if ((new->p_exit = dlsym(handle, PLUGIN_EXIT)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_EXIT);
|
||||
if ((new->p_reset = dlsym(handle, PLUGIN_RESET)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_RESET);
|
||||
if ((new->p_trans_begin = dlsym(handle, PLUGIN_TRANS_BEGIN)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_BEGIN);
|
||||
if ((new->p_trans_validate = dlsym(handle, PLUGIN_TRANS_VALIDATE)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_VALIDATE);
|
||||
if ((new->p_trans_complete = dlsym(handle, PLUGIN_TRANS_COMPLETE)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_COMPLETE);
|
||||
if ((new->p_trans_commit = dlsym(handle, PLUGIN_TRANS_COMMIT)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_COMMIT);
|
||||
if ((new->p_trans_end = dlsym(handle, PLUGIN_TRANS_END)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_END);
|
||||
if ((new->p_trans_abort = dlsym(handle, PLUGIN_TRANS_ABORT)) != NULL)
|
||||
clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_ABORT);
|
||||
clicon_debug(2, "Plugin '%s' loaded.\n", name);
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
/*! Request plugins to reset system state
|
||||
* The system 'state' should be the same as the contents of running_db
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] dbname Name of database
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
plugin_reset_state(clicon_handle h,
|
||||
char *dbname)
|
||||
{
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
p = &plugins[i];
|
||||
if (p->p_reset) {
|
||||
clicon_debug(1, "Calling plugin_reset() for %s\n",
|
||||
p->p_name);
|
||||
if (((p->p_reset)(h, dbname)) < 0) {
|
||||
clicon_err(OE_FATAL, 0, "plugin_reset() failed for %s\n",
|
||||
p->p_name);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Call plugin_start in all plugins
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] argc Command-line arguments
|
||||
* @param[in] argv Command-line arguments
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
plugin_start_hooks(clicon_handle h,
|
||||
int argc,
|
||||
char **argv)
|
||||
{
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
p = &plugins[i];
|
||||
if (p->p_start) {
|
||||
optind = 0;
|
||||
if (((p->p_start)(h, argc, argv)) < 0) {
|
||||
clicon_err(OE_FATAL, 0, "plugin_start() failed for %s\n",
|
||||
p->p_name);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Append plugin to list
|
||||
* @param[in] p Plugin
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
plugin_append(struct plugin *p)
|
||||
{
|
||||
struct plugin *new;
|
||||
|
||||
if ((new = rechunk(plugins, (nplugins+1) * sizeof (*p), NULL)) == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "chunk");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset (&new[nplugins], 0, sizeof(new[nplugins]));
|
||||
memcpy (&new[nplugins], p, sizeof(new[nplugins]));
|
||||
plugins = new;
|
||||
nplugins++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Load backend plugins found in a directory
|
||||
* The plugins must have the '.so' suffix
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] dir Backend plugin directory
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
config_plugin_load_dir(clicon_handle h,
|
||||
const char *dir)
|
||||
{
|
||||
int retval = -1;
|
||||
int i;
|
||||
int np = 0;
|
||||
int ndp;
|
||||
struct stat st;
|
||||
char *filename;
|
||||
struct dirent *dp;
|
||||
struct plugin *new;
|
||||
struct plugin *p = NULL;
|
||||
char *master;
|
||||
char *master_plugin;
|
||||
|
||||
/* Format master plugin path */
|
||||
if ((master_plugin = clicon_master_plugin(h)) == NULL){
|
||||
clicon_err(OE_PLUGIN, 0, "clicon_master_plugin option not set");
|
||||
goto quit;
|
||||
}
|
||||
master = chunk_sprintf(__FUNCTION__, "%s.so", master_plugin);
|
||||
if (master == NULL) {
|
||||
clicon_err(OE_PLUGIN, errno, "chunk_sprintf master plugin");
|
||||
goto quit;
|
||||
}
|
||||
|
||||
/* Allocate plugin group object */
|
||||
/* Get plugin objects names from plugin directory */
|
||||
if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG, __FUNCTION__))<0)
|
||||
goto quit;
|
||||
|
||||
/* reset num plugins */
|
||||
np = 0;
|
||||
|
||||
/* Master plugin must be loaded first if it exists. */
|
||||
filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, master);
|
||||
if (filename == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "chunk");
|
||||
goto quit;
|
||||
}
|
||||
if (stat(filename, &st) == 0) {
|
||||
clicon_debug(1, "Loading master plugin '%.*s' ...",
|
||||
(int)strlen(filename), filename);
|
||||
|
||||
new = plugin_load(h, filename, RTLD_NOW|RTLD_GLOBAL, __FUNCTION__);
|
||||
if (new == NULL)
|
||||
goto quit;
|
||||
if (plugin_append(new) < 0)
|
||||
goto quit;
|
||||
}
|
||||
|
||||
/* Now load the rest */
|
||||
for (i = 0; i < ndp; i++) {
|
||||
if (strcmp(dp[i].d_name, master) == 0)
|
||||
continue; /* Skip master now */
|
||||
filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, dp[i].d_name);
|
||||
clicon_debug(1, "Loading plugin '%.*s' ...", (int)strlen(filename), filename);
|
||||
if (filename == NULL) {
|
||||
clicon_err(OE_UNIX, errno, "chunk");
|
||||
goto quit;
|
||||
}
|
||||
new = plugin_load (h, filename, RTLD_NOW, __FUNCTION__);
|
||||
if (new == NULL)
|
||||
goto quit;
|
||||
if (plugin_append(new) < 0)
|
||||
goto quit;
|
||||
}
|
||||
|
||||
/* All good. */
|
||||
retval = 0;
|
||||
|
||||
quit:
|
||||
if (retval != 0) {
|
||||
if (p) {
|
||||
while (--np >= 0)
|
||||
plugin_unload (h, &p[np]);
|
||||
unchunk(p);
|
||||
}
|
||||
}
|
||||
unchunk_group(__FUNCTION__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Load a plugin group.
|
||||
* @param[in] h Clicon handle
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
plugin_initiate(clicon_handle h)
|
||||
{
|
||||
char *dir;
|
||||
|
||||
/* First load CLICON system plugins */
|
||||
if (config_plugin_load_dir(h, CLICON_BACKEND_SYSDIR) < 0)
|
||||
return -1;
|
||||
|
||||
/* Then load application plugins */
|
||||
if ((dir = clicon_backend_dir(h)) == NULL){
|
||||
clicon_err(OE_PLUGIN, 0, "backend_dir not defined");
|
||||
return -1;
|
||||
}
|
||||
if (config_plugin_load_dir(h, dir) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Unload and deallocate all backend plugins
|
||||
* @param[in] h Clicon handle
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
plugin_finish(clicon_handle h)
|
||||
{
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
p = &plugins[i];
|
||||
plugin_unload(h, p);
|
||||
}
|
||||
if (plugins)
|
||||
unchunk(plugins);
|
||||
nplugins = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Call from frontend to function 'func' in plugin 'plugin'.
|
||||
* Plugin function is supposed to populate 'retlen' and 'retarg' where
|
||||
* 'retarg' is malloc:ed data if non-NULL.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] req Clicon message containing information about the downcall
|
||||
* @param[out] retlen Length of return value
|
||||
* @param[out] ret Return value
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
plugin_downcall(clicon_handle h,
|
||||
struct clicon_msg_call_req *req,
|
||||
uint16_t *retlen,
|
||||
void **retarg)
|
||||
{
|
||||
int retval = -1;
|
||||
int i;
|
||||
downcall_cb funcp;
|
||||
char name[PATH_MAX];
|
||||
char *error;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
p = &plugins[i];
|
||||
strncpy(name, p->p_name, sizeof(name)-1);
|
||||
if (!strcmp(name+strlen(name)-3, ".so"))
|
||||
name[strlen(name)-3] = '\0';
|
||||
/* If no plugin is given or the plugin-name matches */
|
||||
if (req->cr_plugin == NULL || strlen(req->cr_plugin)==0 ||
|
||||
strcmp(name, req->cr_plugin) == 0) {
|
||||
funcp = dlsym(p->p_handle, req->cr_func);
|
||||
if ((error = (char*)dlerror()) != NULL) {
|
||||
clicon_err(OE_PROTO, ENOENT,
|
||||
"Function does not exist: %s()", req->cr_func);
|
||||
return -1;
|
||||
}
|
||||
retval = funcp(h, req->cr_op, req->cr_arglen, req->cr_arg, retlen, retarg);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
clicon_err(OE_PROTO, ENOENT,"%s: %s(): Plugin does not exist: %s",
|
||||
__FUNCTION__, req->cr_func, req->cr_plugin);
|
||||
return -1;
|
||||
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Create and initialize transaction */
|
||||
transaction_data_t *
|
||||
transaction_new(void)
|
||||
{
|
||||
transaction_data_t *td;
|
||||
static uint64_t id = 0; /* Global transaction id */
|
||||
|
||||
if ((td = malloc(sizeof(*td))) == NULL){
|
||||
clicon_err(OE_CFG, errno, "malloc");
|
||||
return NULL;
|
||||
}
|
||||
memset(td, 0, sizeof(*td));
|
||||
td->td_id = id++;
|
||||
return td;
|
||||
}
|
||||
|
||||
/*! Free transaction structure */
|
||||
int
|
||||
transaction_free(transaction_data_t *td)
|
||||
{
|
||||
if (td->td_src)
|
||||
xml_free(td->td_src);
|
||||
if (td->td_target)
|
||||
xml_free(td->td_target);
|
||||
if (td->td_dvec)
|
||||
free(td->td_dvec);
|
||||
if (td->td_avec)
|
||||
free(td->td_avec);
|
||||
if (td->td_scvec)
|
||||
free(td->td_scvec);
|
||||
if (td->td_tcvec)
|
||||
free(td->td_tcvec);
|
||||
free(td);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The plugin_transaction routines need access to struct plugin which is local to this file */
|
||||
|
||||
/*! Call transaction_begin() in all plugins before a validate/commit.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] td Transaction data
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error: one of the plugin callbacks returned error
|
||||
*/
|
||||
int
|
||||
plugin_transaction_begin(clicon_handle h,
|
||||
transaction_data_t *td)
|
||||
{
|
||||
int i;
|
||||
int retval = 0;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
p = &plugins[i];
|
||||
if (p->p_trans_begin)
|
||||
if ((retval = (p->p_trans_begin)(h, (transaction_data)td)) < 0){
|
||||
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
||||
clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
|
||||
__FUNCTION__, p->p_name, PLUGIN_TRANS_BEGIN);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Call transaction_validate callbacks in all backend plugins
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] td Transaction data
|
||||
* @retval 0 OK. Validation succeeded in all plugins
|
||||
* @retval -1 Error: one of the plugin callbacks returned validation fail
|
||||
*/
|
||||
int
|
||||
plugin_transaction_validate(clicon_handle h,
|
||||
transaction_data_t *td)
|
||||
{
|
||||
int retval = 0;
|
||||
int i;
|
||||
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++){
|
||||
p = &plugins[i];
|
||||
if (p->p_trans_validate)
|
||||
if ((retval = (p->p_trans_validate)(h, (transaction_data)td)) < 0){
|
||||
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
||||
clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
|
||||
__FUNCTION__, p->p_name, PLUGIN_TRANS_VALIDATE);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Call transaction_complete() in all plugins after validation (before commit)
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] td Transaction data
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error: one of the plugin callbacks returned error
|
||||
* @note Call plugins which have commit dependencies?
|
||||
* @note Rename to transaction_complete?
|
||||
*/
|
||||
int
|
||||
plugin_transaction_complete(clicon_handle h,
|
||||
transaction_data_t *td)
|
||||
{
|
||||
int i;
|
||||
int retval = 0;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++){
|
||||
p = &plugins[i];
|
||||
if (p->p_trans_complete)
|
||||
if ((retval = (p->p_trans_complete)(h, (transaction_data)td)) < 0){
|
||||
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
||||
clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
|
||||
__FUNCTION__, p->p_name, PLUGIN_TRANS_COMPLETE);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
plugin_transaction_revert(clicon_handle h,
|
||||
transaction_data_t *td,
|
||||
int nr)
|
||||
{
|
||||
int retval = 0;
|
||||
transaction_data_t tr; /* revert transaction */
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
/* Create a new reversed transaction from the original where src and target
|
||||
are swapped */
|
||||
memcpy(&tr, td, sizeof(tr));
|
||||
tr.td_src = td->td_target;
|
||||
tr.td_target = td->td_src;
|
||||
tr.td_dlen = td->td_alen;
|
||||
tr.td_dvec = td->td_avec;
|
||||
tr.td_alen = td->td_dlen;
|
||||
tr.td_avec = td->td_dvec;
|
||||
tr.td_scvec = td->td_tcvec;
|
||||
tr.td_tcvec = td->td_scvec;
|
||||
|
||||
for (i = nr-1; i; i--){
|
||||
p = &plugins[i];
|
||||
if (p->p_trans_commit)
|
||||
if ((p->p_trans_commit)(h, (transaction_data)&tr) < 0){
|
||||
clicon_log(LOG_NOTICE, "Plugin '%s' %s revert callback failed",
|
||||
p->p_name, PLUGIN_TRANS_COMMIT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retval; /* ignore errors */
|
||||
}
|
||||
|
||||
/*! Call transaction_commit callbacks in all backend plugins
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] td Transaction data
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error: one of the plugin callbacks returned error
|
||||
* If any of the commit callbacks fail by returning -1, a revert of the
|
||||
* transaction is tried by calling the commit callbacsk with reverse arguments
|
||||
* and in reverse order.
|
||||
*/
|
||||
int
|
||||
plugin_transaction_commit(clicon_handle h,
|
||||
transaction_data_t *td)
|
||||
{
|
||||
int retval = 0;
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++){
|
||||
p = &plugins[i];
|
||||
if (p->p_trans_commit)
|
||||
if ((retval = (p->p_trans_commit)(h, (transaction_data)td)) < 0){
|
||||
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
||||
clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
|
||||
__FUNCTION__, p->p_name, PLUGIN_TRANS_COMMIT);
|
||||
/* Make an effort to revert transaction */
|
||||
plugin_transaction_revert(h, td, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Call transaction_end() in all plugins after a successful commit.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] td Transaction data
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
plugin_transaction_end(clicon_handle h,
|
||||
transaction_data_t *td)
|
||||
{
|
||||
int retval = 0;
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
p = &plugins[i];
|
||||
if (p->p_trans_end)
|
||||
if ((retval = (p->p_trans_end)(h, (transaction_data)td)) < 0){
|
||||
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
||||
clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
|
||||
__FUNCTION__, p->p_name, PLUGIN_TRANS_END);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Call transaction_abort() in all plugins after a failed validation/commit.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] td Transaction data
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
plugin_transaction_abort(clicon_handle h,
|
||||
transaction_data_t *td)
|
||||
{
|
||||
int retval = 0;
|
||||
int i;
|
||||
struct plugin *p;
|
||||
|
||||
for (i = 0; i < nplugins; i++) {
|
||||
p = &plugins[i];
|
||||
if (p->p_trans_abort)
|
||||
(p->p_trans_abort)(h, (transaction_data)td); /* dont abort on error */
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
72
apps/backend/backend_plugin.h
Normal file
72
apps/backend/backend_plugin.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _BACKEND_PLUGIN_H_
|
||||
#define _BACKEND_PLUGIN_H_
|
||||
|
||||
/*
|
||||
* Types
|
||||
*/
|
||||
|
||||
|
||||
/*! Transaction data
|
||||
* Clicon internal, presented as void* to app's callback in the 'transaction_data'
|
||||
* type in clicon_backend_api.h
|
||||
* XXX: move to .c file?
|
||||
*/
|
||||
typedef struct {
|
||||
uint64_t td_id; /* Transaction id */
|
||||
void *td_arg; /* Callback argument */
|
||||
cxobj *td_src; /* Source database xml tree */
|
||||
cxobj *td_target; /* Target database xml tree */
|
||||
cxobj **td_dvec; /* Delete xml vector */
|
||||
size_t td_dlen; /* Delete xml vector length */
|
||||
cxobj **td_avec; /* Add xml vector */
|
||||
size_t td_alen; /* Add xml vector length */
|
||||
cxobj **td_scvec; /* Source changed xml vector */
|
||||
cxobj **td_tcvec; /* Target changed xml vector */
|
||||
size_t td_clen; /* Changed xml vector length */
|
||||
} transaction_data_t;
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int config_plugin_init(clicon_handle h);
|
||||
int plugin_initiate(clicon_handle h);
|
||||
int plugin_finish(clicon_handle h);
|
||||
|
||||
int plugin_reset_state(clicon_handle h, char *dbname);
|
||||
int plugin_start_hooks(clicon_handle h, int argc, char **argv);
|
||||
int plugin_downcall(clicon_handle h, struct clicon_msg_call_req *req,
|
||||
uint16_t *retlen, void **retarg);
|
||||
|
||||
transaction_data_t * transaction_new(void);
|
||||
int transaction_free(transaction_data_t *);
|
||||
|
||||
int plugin_transaction_begin(clicon_handle h, transaction_data_t *td);
|
||||
int plugin_transaction_validate(clicon_handle h, transaction_data_t *td);
|
||||
int plugin_transaction_complete(clicon_handle h, transaction_data_t *td);
|
||||
int plugin_transaction_commit(clicon_handle h, transaction_data_t *td);
|
||||
int plugin_transaction_end(clicon_handle h, transaction_data_t *td);
|
||||
int plugin_transaction_abort(clicon_handle h, transaction_data_t *td);
|
||||
|
||||
#endif /* _BACKEND_PLUGIN_H_ */
|
||||
262
apps/backend/backend_socket.c
Normal file
262
apps/backend/backend_socket.c
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <syslog.h>
|
||||
#include <grp.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
#ifdef HAVE_SYS_UCRED_H
|
||||
#include <sys/types.h>
|
||||
#include <sys/ucred.h>
|
||||
#endif
|
||||
#define __USE_GNU
|
||||
#include <sys/socket.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "backend_socket.h"
|
||||
#include "backend_client.h"
|
||||
#include "backend_handle.h"
|
||||
|
||||
static int
|
||||
config_socket_init_ipv4(clicon_handle h, char *dst)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_in addr;
|
||||
uint16_t port;
|
||||
|
||||
port = clicon_sock_port(h);
|
||||
|
||||
/* create inet socket */
|
||||
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
||||
clicon_err(OE_UNIX, errno, "socket");
|
||||
return -1;
|
||||
}
|
||||
// setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&one, sizeof(one));
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
if (inet_pton(addr.sin_family, dst, &addr.sin_addr) != 1){
|
||||
clicon_err(OE_UNIX, errno, "inet_pton: %s (Expected IPv4 address. Check settings of CLICON_SOCK_FAMILY and CLICON_SOCK)", dst);
|
||||
goto err; /* Could check getaddrinfo */
|
||||
}
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0){
|
||||
clicon_err(OE_UNIX, errno, "%s: bind", __FUNCTION__);
|
||||
goto err;
|
||||
}
|
||||
clicon_debug(1, "Listen on server socket at %s:%hu", dst, port);
|
||||
if (listen(s, 5) < 0){
|
||||
clicon_err(OE_UNIX, errno, "%s: listen", __FUNCTION__);
|
||||
goto err;
|
||||
}
|
||||
return s;
|
||||
err:
|
||||
close(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*! Open a socket and bind it to a file descriptor
|
||||
*
|
||||
* The socket is accessed via CLICON_SOCK option, has 770 permissions
|
||||
* and group according to CLICON_SOCK_GROUP option.
|
||||
*/
|
||||
static int
|
||||
config_socket_init_unix(clicon_handle h, char *sock)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_un addr;
|
||||
mode_t old_mask;
|
||||
char *config_group;
|
||||
gid_t gid;
|
||||
struct stat st;
|
||||
|
||||
if (lstat(sock, &st) == 0 && unlink(sock) < 0){
|
||||
clicon_err(OE_UNIX, errno, "%s: unlink(%s)", __FUNCTION__, sock);
|
||||
return -1;
|
||||
}
|
||||
/* then find configuration group (for clients) and find its groupid */
|
||||
if ((config_group = clicon_sock_group(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "clicon_sock_group option not set");
|
||||
return -1;
|
||||
}
|
||||
if (group_name2gid(config_group, &gid) < 0)
|
||||
return -1;
|
||||
#if 0
|
||||
if (gid == 0)
|
||||
clicon_log(LOG_WARNING, "%s: No such group: %s\n", __FUNCTION__, config_group);
|
||||
#endif
|
||||
/* create unix socket */
|
||||
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
|
||||
clicon_err(OE_UNIX, errno, "%s: socket", __FUNCTION__);
|
||||
return -1;
|
||||
}
|
||||
// setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&one, sizeof(one));
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy(addr.sun_path, sock, sizeof(addr.sun_path)-1);
|
||||
old_mask = umask(S_IRWXO | S_IXGRP | S_IXUSR);
|
||||
if (bind(s, (struct sockaddr *)&addr, SUN_LEN(&addr)) < 0){
|
||||
clicon_err(OE_UNIX, errno, "%s: bind", __FUNCTION__);
|
||||
umask(old_mask);
|
||||
goto err;
|
||||
}
|
||||
umask(old_mask);
|
||||
/* change socket path file group */
|
||||
if (lchown(sock, -1, gid) < 0){
|
||||
clicon_err(OE_UNIX, errno, "%s: lchown(%s, %s)", __FUNCTION__,
|
||||
sock, config_group);
|
||||
goto err;
|
||||
}
|
||||
clicon_debug(1, "Listen on server socket at %s", addr.sun_path);
|
||||
if (listen(s, 5) < 0){
|
||||
clicon_err(OE_UNIX, errno, "%s: listen", __FUNCTION__);
|
||||
goto err;
|
||||
}
|
||||
return s;
|
||||
err:
|
||||
close(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
config_socket_init(clicon_handle h)
|
||||
{
|
||||
char *sock;
|
||||
|
||||
if ((sock = clicon_sock(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set");
|
||||
return -1;
|
||||
}
|
||||
switch (clicon_sock_family(h)){
|
||||
case AF_UNIX:
|
||||
return config_socket_init_unix(h, sock);
|
||||
break;
|
||||
case AF_INET:
|
||||
return config_socket_init_ipv4(h, sock);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* config_accept_client
|
||||
* XXX: credentials not properly implemented
|
||||
*/
|
||||
int
|
||||
config_accept_client(int fd, void *arg)
|
||||
{
|
||||
int retval = -1;
|
||||
clicon_handle h = (clicon_handle)arg;
|
||||
int s;
|
||||
struct sockaddr_un from;
|
||||
socklen_t len;
|
||||
struct client_entry *ce;
|
||||
#ifdef DONT_WORK /* XXX HAVE_SYS_UCRED_H */
|
||||
struct xucred credentials; /* FreeBSD. */
|
||||
socklen_t clen;
|
||||
#elif defined(SO_PEERCRED)
|
||||
struct ucred credentials; /* Linux. */
|
||||
socklen_t clen;
|
||||
#endif
|
||||
char *config_group;
|
||||
struct group *gr;
|
||||
char *mem;
|
||||
int i;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
len = sizeof(from);
|
||||
if ((s = accept(fd, (struct sockaddr*)&from, &len)) < 0){
|
||||
clicon_err(OE_UNIX, errno, "%s: accept", __FUNCTION__);
|
||||
goto done;
|
||||
}
|
||||
#if defined(SO_PEERCRED)
|
||||
/* fill in the user data structure */
|
||||
clen = sizeof(credentials);
|
||||
if(getsockopt(s, SOL_SOCKET, SO_PEERCRED/* XXX finns ej i freebsd*/, &credentials, &clen)){
|
||||
clicon_err(OE_UNIX, errno, "%s: getsockopt", __FUNCTION__);
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
if ((ce = backend_client_add(h, (struct sockaddr*)&from)) == NULL)
|
||||
goto done;
|
||||
#if defined(SO_PEERCRED)
|
||||
ce->ce_pid = credentials.pid;
|
||||
ce->ce_uid = credentials.uid;
|
||||
#endif
|
||||
ce->ce_handle = h;
|
||||
|
||||
/* check credentials of caller (not properly implemented yet) */
|
||||
if ((config_group = clicon_sock_group(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "clicon_sock_group option not set");
|
||||
goto done;
|
||||
}
|
||||
if ((gr = getgrnam(config_group)) != NULL){
|
||||
i = 0; /* one of mem should correspond to ce->ce_uid */
|
||||
while ((mem = gr->gr_mem[i++]) != NULL)
|
||||
;
|
||||
}
|
||||
|
||||
#if 0
|
||||
{ /* XXX */
|
||||
int ii;
|
||||
struct client_entry *c;
|
||||
for (c = ce_list, ii=0; c; c = c->ce_next, ii++);
|
||||
clicon_debug(1, "Open client socket (nr:%d pid:%d [Total: %d])",
|
||||
ce->ce_nr, ce->ce_pid, ii);
|
||||
}
|
||||
#endif
|
||||
ce->ce_s = s;
|
||||
|
||||
/*
|
||||
* Here we register callbacks for actual data socket
|
||||
*/
|
||||
if (event_reg_fd(s, from_client, (void*)ce, "client socket") < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
33
apps/backend/backend_socket.h
Normal file
33
apps/backend/backend_socket.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _BACKEND_SOCKET_H_
|
||||
#define _BACKEND_SOCKET_H_
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int config_socket_init(clicon_handle h);
|
||||
int config_accept_client(int fd, void *arg);
|
||||
|
||||
#endif /* _BACKEND_SOCKET_H_ */
|
||||
92
apps/backend/clicon_backend.h
Normal file
92
apps/backend/clicon_backend.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* The exported interface to plugins. External apps (eg backend plugins) should
|
||||
* only include this file.
|
||||
* Internal code should not include this file
|
||||
*/
|
||||
|
||||
#ifndef _CLICON_BACKEND_H_
|
||||
#define _CLICON_BACKEND_H_
|
||||
|
||||
/*
|
||||
* Use this constant to disable some prototypes that should not be visible outside the lib.
|
||||
* This is an alternative to use separate internal include files.
|
||||
*/
|
||||
|
||||
/* Common code (API and Backend daemon) */
|
||||
#include <clicon/clicon_backend_handle.h>
|
||||
#include <clicon/clicon_backend_transaction.h>
|
||||
|
||||
/*! Clicon Backend plugin callbacks: use these in your backend plugin code
|
||||
*/
|
||||
|
||||
/*! Called when plugin loaded. Only mandadory callback. All others optional
|
||||
* @see plginit_t
|
||||
*/
|
||||
int plugin_init(clicon_handle h);
|
||||
|
||||
/* Called when backend started with cmd-line arguments from daemon call.
|
||||
* @see plgstart_t
|
||||
*/
|
||||
int plugin_start(clicon_handle h, int argc, char **argv);
|
||||
|
||||
/* Called just before plugin unloaded.
|
||||
* @see plgexit_t
|
||||
*/
|
||||
int plugin_exit(clicon_handle h);
|
||||
|
||||
/*! Reset system state to original state. Eg at reboot before running thru config.
|
||||
* @see plgreset_t
|
||||
*/
|
||||
int plugin_reset(clicon_handle h, char *dbname);
|
||||
|
||||
/*! Called before a commit/validate sequence begins. Eg setup state before commit
|
||||
* @see trans_cb_t
|
||||
*/
|
||||
int transaction_begin(clicon_handle h, transaction_data td);
|
||||
|
||||
/*! Validate.
|
||||
* @see trans_cb_t
|
||||
*/
|
||||
int transaction_validate(clicon_handle h, transaction_data td);
|
||||
|
||||
/* Called after a validation completed succesfully (but before commit).
|
||||
* @see trans_cb_t
|
||||
*/
|
||||
int transaction_complete(clicon_handle h, transaction_data td);
|
||||
|
||||
/* Commit.
|
||||
* @see trans_cb_t
|
||||
*/
|
||||
int transaction_commit(clicon_handle h, transaction_data td);
|
||||
|
||||
/* Called after a commit sequence completed succesfully.
|
||||
* @see trans_cb_t
|
||||
*/
|
||||
int transaction_end(clicon_handle h, transaction_data td);
|
||||
|
||||
/* Called if commit or validate sequence fails. After eventual rollback.
|
||||
* @see trans_cb_t
|
||||
*/
|
||||
int transaction_abort(clicon_handle h, transaction_data td);
|
||||
|
||||
#endif /* _CLICON_BACKEND_H_ */
|
||||
353
apps/backend/clicon_backend_handle.c
Normal file
353
apps/backend/clicon_backend_handle.c
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <fnmatch.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <regex.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "clicon_backend_handle.h"
|
||||
#include "backend_client.h"
|
||||
#include "backend_handle.h"
|
||||
|
||||
/* header part is copied from struct clicon_handle in lib/src/clicon_handle.c */
|
||||
|
||||
#define CLICON_MAGIC 0x99aafabe
|
||||
|
||||
#define handle(h) (assert(clicon_handle_check(h)==0),(struct backend_handle *)(h))
|
||||
|
||||
/* Clicon_handle for backends.
|
||||
* First part of this is header, same for clicon_handle and cli_handle.
|
||||
* Access functions for common fields are found in clicon lib: clicon_options.[ch]
|
||||
* This file should only contain access functions for the _specific_
|
||||
* entries in the struct below.
|
||||
*/
|
||||
struct backend_handle {
|
||||
int cb_magic; /* magic (HDR)*/
|
||||
clicon_hash_t *cb_copt; /* clicon option list (HDR) */
|
||||
clicon_hash_t *cb_data; /* internal clicon data (HDR) */
|
||||
/* ------ end of common handle ------ */
|
||||
struct client_entry *cb_ce_list; /* The client list */
|
||||
int cb_ce_nr; /* Number of clients, just increment */
|
||||
struct handle_subscription *cb_subscription; /* Event subscription list */
|
||||
};
|
||||
|
||||
/*! Creates and returns a clicon config handle for other CLICON API calls
|
||||
*/
|
||||
clicon_handle
|
||||
backend_handle_init(void)
|
||||
{
|
||||
return clicon_handle_init0(sizeof(struct backend_handle));
|
||||
}
|
||||
|
||||
/*! Deallocates a backend handle, including all client structs
|
||||
* @Note: handle 'h' cannot be used in calls after this
|
||||
*/
|
||||
int
|
||||
backend_handle_exit(clicon_handle h)
|
||||
{
|
||||
struct client_entry *ce;
|
||||
|
||||
/* only delete client structs, not close sockets, etc, see backend_client_rm */
|
||||
while ((ce = backend_client_list(h)) != NULL)
|
||||
backend_client_delete(h, ce);
|
||||
clicon_handle_exit(h); /* frees h and options */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Notify event and distribute to all registered clients
|
||||
*
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] stream Name of event stream. CLICON is predefined as LOG stream
|
||||
* @param[in] level Event level (not used yet)
|
||||
* @param[in] event Actual message as text format
|
||||
*
|
||||
* Stream is a string used to qualify the event-stream. Distribute the
|
||||
* event to all clients registered to this backend.
|
||||
* XXX: event-log NYI.
|
||||
* @see also subscription_add()
|
||||
* @see also backend_notify_xml()
|
||||
*/
|
||||
int
|
||||
backend_notify(clicon_handle h, char *stream, int level, char *event)
|
||||
{
|
||||
struct client_entry *ce;
|
||||
struct client_subscription *su;
|
||||
struct handle_subscription *hs;
|
||||
int retval = -1;
|
||||
|
||||
/* First thru all clients(sessions), and all subscriptions and find matches */
|
||||
for (ce = backend_client_list(h); ce; ce = ce->ce_next)
|
||||
for (su = ce->ce_subscription; su; su = su->su_next)
|
||||
if (strcmp(su->su_stream, stream) == 0){
|
||||
if (fnmatch(su->su_filter, event, 0) == 0)
|
||||
if (send_msg_notify(ce->ce_s, level, event) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Then go thru all global (handle) subscriptions and find matches */
|
||||
hs = NULL;
|
||||
while ((hs = subscription_each(h, hs)) != NULL){
|
||||
if (hs->hs_format != MSG_NOTIFY_TXT)
|
||||
continue;
|
||||
if (strcmp(hs->hs_stream, stream))
|
||||
continue;
|
||||
if (fnmatch(hs->hs_filter, event, 0) == 0)
|
||||
if ((*hs->hs_fn)(h, event, hs->hs_arg) < 0)
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Notify event and distribute to all registered clients
|
||||
*
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] stream Name of event stream. CLICON is predefined as LOG stream
|
||||
* @param[in] level Event level (not used yet)
|
||||
* @param[in] event Actual message as xml tree
|
||||
*
|
||||
* Stream is a string used to qualify the event-stream. Distribute the
|
||||
* event to all clients registered to this backend.
|
||||
* XXX: event-log NYI.
|
||||
* @see also subscription_add()
|
||||
* @see also backend_notify()
|
||||
*/
|
||||
int
|
||||
backend_notify_xml(clicon_handle h, char *stream, int level, cxobj *x)
|
||||
{
|
||||
struct client_entry *ce;
|
||||
struct client_subscription *su;
|
||||
int retval = -1;
|
||||
cbuf *cb = NULL;
|
||||
struct handle_subscription *hs;
|
||||
|
||||
/* Now go thru all clients(sessions), and all subscriptions and find matches */
|
||||
for (ce = backend_client_list(h); ce; ce = ce->ce_next)
|
||||
for (su = ce->ce_subscription; su; su = su->su_next)
|
||||
if (strcmp(su->su_stream, stream) == 0){
|
||||
if (strlen(su->su_filter)==0 || xpath_first(x, su->su_filter) != NULL){
|
||||
if (cb==NULL){
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_PLUGIN, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
if (clicon_xml2cbuf(cb, x, 0, 0) < 0)
|
||||
goto done;
|
||||
}
|
||||
if (send_msg_notify(ce->ce_s, level, cbuf_get(cb)) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
/* Then go thru all global (handle) subscriptions and find matches */
|
||||
/* XXX: x contains name==dk-ore, but filter is
|
||||
id==/[userid=d2d5e46c-c6f9-42f3-9a69-fb52fe60940d] */
|
||||
hs = NULL;
|
||||
while ((hs = subscription_each(h, hs)) != NULL){
|
||||
if (hs->hs_format != MSG_NOTIFY_XML)
|
||||
continue;
|
||||
if (strcmp(hs->hs_stream, stream))
|
||||
continue;
|
||||
if (strlen(hs->hs_filter)==0 || xpath_first(x, hs->hs_filter) != NULL)
|
||||
if ((*hs->hs_fn)(h, x, hs->hs_arg) < 0)
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
struct client_entry *
|
||||
backend_client_add(clicon_handle h, struct sockaddr *addr)
|
||||
{
|
||||
struct backend_handle *cb = handle(h);
|
||||
struct client_entry *ce;
|
||||
|
||||
if ((ce = (struct client_entry *)malloc(sizeof(*ce))) == NULL){
|
||||
clicon_err(OE_PLUGIN, errno, "malloc");
|
||||
return NULL;
|
||||
}
|
||||
memset(ce, 0, sizeof(*ce));
|
||||
ce->ce_nr = cb->cb_ce_nr++;
|
||||
memcpy(&ce->ce_addr, addr, sizeof(*addr));
|
||||
ce->ce_next = cb->cb_ce_list;
|
||||
cb->cb_ce_list = ce;
|
||||
return ce;
|
||||
}
|
||||
|
||||
struct client_entry *
|
||||
backend_client_list(clicon_handle h)
|
||||
{
|
||||
struct backend_handle *cb = handle(h);
|
||||
|
||||
return cb->cb_ce_list;
|
||||
}
|
||||
|
||||
/*! Actually remove client from list
|
||||
* See also backend_client_rm()
|
||||
*/
|
||||
int
|
||||
backend_client_delete(clicon_handle h, struct client_entry *ce)
|
||||
{
|
||||
struct client_entry *c;
|
||||
struct client_entry **ce_prev;
|
||||
struct backend_handle *cb = handle(h);
|
||||
|
||||
ce_prev = &cb->cb_ce_list;
|
||||
for (c = *ce_prev; c; c = c->ce_next){
|
||||
if (c == ce){
|
||||
*ce_prev = c->ce_next;
|
||||
free(ce);
|
||||
break;
|
||||
}
|
||||
ce_prev = &c->ce_next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Add subscription given stream name, callback and argument
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] stream Name of event stream
|
||||
* @param[in] format Expected format of event, eg text or xml
|
||||
* @param[in] filter Filter to match event, depends on format, eg xpath for xml
|
||||
* @param[in] fn Callback when event occurs
|
||||
* @param[in] arg Argument to use with callback. Also handle when deleting
|
||||
* Note that arg is not a real handle.
|
||||
* @see subscription_delete
|
||||
* @see subscription_each
|
||||
*/
|
||||
struct handle_subscription *
|
||||
subscription_add(clicon_handle h,
|
||||
char *stream,
|
||||
enum format_enum format,
|
||||
char *filter,
|
||||
subscription_fn_t fn,
|
||||
void *arg)
|
||||
{
|
||||
struct backend_handle *cb = handle(h);
|
||||
struct handle_subscription *hs = NULL;
|
||||
|
||||
if ((hs = malloc(sizeof(*hs))) == NULL){
|
||||
clicon_err(OE_PLUGIN, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
memset(hs, 0, sizeof(*hs));
|
||||
hs->hs_stream = strdup(stream);
|
||||
hs->hs_format = format;
|
||||
hs->hs_filter = strdup(filter);
|
||||
hs->hs_next = cb->cb_subscription;
|
||||
hs->hs_fn = fn;
|
||||
hs->hs_arg = arg;
|
||||
cb->cb_subscription = hs;
|
||||
done:
|
||||
return hs;
|
||||
}
|
||||
|
||||
/*! Delete subscription given stream name, callback and argument
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] stream Name of event stream
|
||||
* @param[in] fn Callback when event occurs
|
||||
* @param[in] arg Argument to use with callback and handle
|
||||
* Note that arg is not a real handle.
|
||||
* @see subscription_add
|
||||
* @see subscription_each
|
||||
*/
|
||||
int
|
||||
subscription_delete(clicon_handle h,
|
||||
char *stream,
|
||||
subscription_fn_t fn,
|
||||
void *arg)
|
||||
{
|
||||
struct backend_handle *cb = handle(h);
|
||||
struct handle_subscription *hs;
|
||||
struct handle_subscription **hs_prev;
|
||||
|
||||
hs_prev = &cb->cb_subscription; /* this points to stack and is not real backpointer */
|
||||
for (hs = *hs_prev; hs; hs = hs->hs_next){
|
||||
/* XXX arg == hs->hs_arg */
|
||||
if (strcmp(hs->hs_stream, stream)==0 && hs->hs_fn == fn){
|
||||
*hs_prev = hs->hs_next;
|
||||
free(hs->hs_stream);
|
||||
if (hs->hs_filter)
|
||||
free(hs->hs_filter);
|
||||
if (hs->hs_arg)
|
||||
free(hs->hs_arg);
|
||||
free(hs);
|
||||
break;
|
||||
}
|
||||
hs_prev = &hs->hs_next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Iterator over subscriptions
|
||||
*
|
||||
* NOTE: Never manipulate the child-list during operation or using the
|
||||
* same object recursively, the function uses an internal field to remember the
|
||||
* index used. It works as long as the same object is not iterated concurrently.
|
||||
*
|
||||
* @param[in] h clicon handle
|
||||
* @param[in] hprev iterator, initialize with NULL
|
||||
* @code
|
||||
* clicon_handle h;
|
||||
* struct handle_subscription *hs = NULL;
|
||||
* while ((hs = subscription_each(h, hs)) != NULL) {
|
||||
* ...
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
struct handle_subscription *
|
||||
subscription_each(clicon_handle h,
|
||||
struct handle_subscription *hprev)
|
||||
{
|
||||
struct backend_handle *cb = handle(h);
|
||||
struct handle_subscription *hs = NULL;
|
||||
|
||||
if (hprev)
|
||||
hs = hprev->hs_next;
|
||||
else
|
||||
hs = cb->cb_subscription;
|
||||
return hs;
|
||||
}
|
||||
71
apps/backend/clicon_backend_handle.h
Normal file
71
apps/backend/clicon_backend_handle.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* Part of the external API to plugins. Applications should not include
|
||||
* this file directly (only via clicon_backend.h).
|
||||
* Internal code should include this
|
||||
*/
|
||||
|
||||
#ifndef _CLICON_BACKEND_HANDLE_H_
|
||||
#define _CLICON_BACKEND_HANDLE_H_
|
||||
|
||||
/*
|
||||
* Types
|
||||
*/
|
||||
|
||||
/*! Generic downcall registration.
|
||||
* Enables any function to be called from (cli) frontend
|
||||
* to backend. Like an RPC on application-level.
|
||||
*/
|
||||
typedef int (*downcall_cb)(clicon_handle h, uint16_t op, uint16_t len,
|
||||
void *arg, uint16_t *retlen, void **retarg);
|
||||
|
||||
/*
|
||||
* Log for netconf notify function (config_client.c)
|
||||
*/
|
||||
int backend_notify(clicon_handle h, char *stream, int level, char *txt);
|
||||
int backend_notify_xml(clicon_handle h, char *stream, int level, cxobj *x);
|
||||
|
||||
/* subscription callback */
|
||||
typedef int (*subscription_fn_t)(clicon_handle, void *filter, void *arg);
|
||||
|
||||
/* Notification subscription info
|
||||
* @see client_subscription in config_client.h
|
||||
*/
|
||||
struct handle_subscription{
|
||||
struct handle_subscription *hs_next;
|
||||
enum format_enum hs_format; /* format (enum format_enum) XXX not needed? */
|
||||
char *hs_stream; /* name of notify stream */
|
||||
char *hs_filter; /* filter, if format=xml: xpath, if text: fnmatch */
|
||||
subscription_fn_t hs_fn; /* Callback when event occurs */
|
||||
void *hs_arg; /* Callback argument */
|
||||
};
|
||||
|
||||
struct handle_subscription *subscription_add(clicon_handle h, char *stream,
|
||||
enum format_enum format, char *filter,
|
||||
subscription_fn_t fn, void *arg);
|
||||
|
||||
int subscription_delete(clicon_handle h, char *stream,
|
||||
subscription_fn_t fn, void *arg);
|
||||
|
||||
struct handle_subscription *subscription_each(clicon_handle h,
|
||||
struct handle_subscription *hprev);
|
||||
#endif /* _CLICON_BACKEND_HANDLE_H_ */
|
||||
206
apps/backend/clicon_backend_transaction.c
Normal file
206
apps/backend/clicon_backend_transaction.c
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
/*
|
||||
* Note that the functions in this file are accessible from the plugins
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clicon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/types.h>
|
||||
#include <regex.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clicon/clicon.h>
|
||||
|
||||
#include "clicon_backend_transaction.h"
|
||||
#include "backend_plugin.h"
|
||||
|
||||
/* Access functions for transaction-data handle in callbacks
|
||||
* Expressed in a transition from an current -> wanted state.
|
||||
* For example, adding a database symbol 'a' in candidate and commiting
|
||||
* would give running in source and 'a' and candidate in 'target'.
|
||||
*/
|
||||
/*! Get transaction id
|
||||
* @param[in] td transaction_data
|
||||
* @retval id transaction id
|
||||
*/
|
||||
uint64_t
|
||||
transaction_id(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_id;
|
||||
}
|
||||
|
||||
/*! Get plugin/application specific callbackargument
|
||||
* @param[in] td transaction_data
|
||||
* @retval arg callback argument
|
||||
* @note NYI
|
||||
*/
|
||||
void *
|
||||
transaction_arg(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_arg;
|
||||
}
|
||||
|
||||
/*! Get Source database xml tree
|
||||
* @param[in] td transaction_data
|
||||
* @retval src source xml tree containing original state
|
||||
*/
|
||||
cxobj *
|
||||
transaction_src(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_src;
|
||||
}
|
||||
|
||||
/*! Get target database xml tree
|
||||
* @param[in] td transaction_data
|
||||
* @retval xml target xml tree containing wanted state
|
||||
*/
|
||||
cxobj *
|
||||
transaction_target(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_target;
|
||||
}
|
||||
|
||||
/*! Get delete xml vector, ie vector of xml nodes that are deleted src->target
|
||||
* @param[in] td transaction_data
|
||||
* @retval vec Vector of xml nodes
|
||||
*/
|
||||
cxobj **
|
||||
transaction_dvec(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_dvec;
|
||||
}
|
||||
|
||||
/*! Get length of delete xml vector
|
||||
* @param[in] td transaction_data
|
||||
* @retval len Length of vector of xml nodes
|
||||
* @see transaction_dvec
|
||||
*/
|
||||
size_t
|
||||
transaction_dlen(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_dlen;
|
||||
}
|
||||
|
||||
/*! Get add xml vector, ie vector of xml nodes that are added src->target
|
||||
* @param[in] td transaction_data
|
||||
* @retval vec Vector of xml nodes
|
||||
*/
|
||||
cxobj **
|
||||
transaction_avec(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_avec;
|
||||
}
|
||||
|
||||
/*! Get length of add xml vector
|
||||
* @param[in] td transaction_data
|
||||
* @retval len Length of vector of xml nodes
|
||||
* @see transaction_avec
|
||||
*/
|
||||
size_t
|
||||
transaction_alen(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_alen;
|
||||
}
|
||||
|
||||
/*! Get source changed xml vector, ie vector of xml nodes that changed
|
||||
* @param[in] td transaction_data
|
||||
* @retval vec Vector of xml nodes
|
||||
* These are only nodes of type LEAF.
|
||||
* For each node in this vector which contains the original value, there
|
||||
* is a node in tcvec with the changed value
|
||||
* @see transaction_dcvec
|
||||
*/
|
||||
cxobj **
|
||||
transaction_scvec(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_scvec;
|
||||
}
|
||||
|
||||
/*! Get target changed xml vector, ie vector of xml nodes that changed
|
||||
* @param[in] td transaction_data
|
||||
* @retval vec Vector of xml nodes
|
||||
* These are only nodes of type LEAF.
|
||||
* For each node in this vector which contains the original value, there
|
||||
* is a node in tcvec with the changed value
|
||||
* @see transaction_scvec
|
||||
*/
|
||||
cxobj **
|
||||
transaction_tcvec(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_dvec;
|
||||
}
|
||||
|
||||
/*! Get length of changed xml vector
|
||||
* @param[in] td transaction_data
|
||||
* @retval len Length of vector of xml nodes
|
||||
* This is the length of both the src change vector and the target change vector
|
||||
*/
|
||||
size_t
|
||||
transaction_clen(transaction_data td)
|
||||
{
|
||||
return ((transaction_data_t *)td)->td_clen;
|
||||
}
|
||||
|
||||
int
|
||||
transaction_print(FILE *f,
|
||||
transaction_data th)
|
||||
{
|
||||
cxobj *xn;
|
||||
int i;
|
||||
transaction_data_t *td;
|
||||
|
||||
td = (transaction_data_t *)th;
|
||||
|
||||
fprintf(f, "Transaction id: 0x%llx\n", td->td_id);
|
||||
fprintf(f, "Removed\n=========\n");
|
||||
for (i=0; i<td->td_dlen; i++){
|
||||
xn = td->td_dvec[i];
|
||||
clicon_xml2file(f, xn, 0, 1);
|
||||
}
|
||||
fprintf(f, "Added\n=========\n");
|
||||
for (i=0; i<td->td_alen; i++){
|
||||
xn = td->td_avec[i];
|
||||
clicon_xml2file(f, xn, 0, 1);
|
||||
}
|
||||
fprintf(stderr, "Changed\n=========\n");
|
||||
for (i=0; i<td->td_clen; i++){
|
||||
xn = td->td_scvec[i];
|
||||
clicon_xml2file(f, xn, 0, 1);
|
||||
xn = td->td_tcvec[i];
|
||||
clicon_xml2file(f, xn, 0, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
60
apps/backend/clicon_backend_transaction.h
Normal file
60
apps/backend/clicon_backend_transaction.h
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
*
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLICON.
|
||||
|
||||
CLICON is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CLICON is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CLICON; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*
|
||||
* Part of the external API to plugins. Applications should not include
|
||||
* this file directly (only via clicon_backend.h).
|
||||
* Internal code should include this
|
||||
*/
|
||||
|
||||
#ifndef _CLICON_BACKEND_TRANSACTION_H_
|
||||
#define _CLICON_BACKEND_TRANSACTION_H_
|
||||
|
||||
/*
|
||||
* Types
|
||||
*/
|
||||
|
||||
/*! Generic downcall registration.
|
||||
* Enables any function to be called from (cli) frontend
|
||||
* to backend. Like an RPC on application-level.
|
||||
*/
|
||||
typedef int (*downcall_cb)(clicon_handle h, uint16_t op, uint16_t len,
|
||||
void *arg, uint16_t *retlen, void **retarg);
|
||||
|
||||
/* Transaction callback data accessors for client plugins
|
||||
* (defined in config_dbdep.c)
|
||||
* @see transaction_data_t internal structure
|
||||
*/
|
||||
typedef void *transaction_data;
|
||||
uint64_t transaction_id(transaction_data td);
|
||||
void *transaction_arg(transaction_data td);
|
||||
cxobj *transaction_src(transaction_data td);
|
||||
cxobj *transaction_target(transaction_data td);
|
||||
cxobj **transaction_dvec(transaction_data td);
|
||||
size_t transaction_dlen(transaction_data td);
|
||||
cxobj **transaction_avec(transaction_data td);
|
||||
size_t transaction_alen(transaction_data td);
|
||||
cxobj **transaction_scvec(transaction_data td);
|
||||
cxobj **transaction_tcvec(transaction_data td);
|
||||
size_t transaction_clen(transaction_data td);
|
||||
|
||||
int transaction_print(FILE *f, transaction_data th);
|
||||
|
||||
#endif /* _CLICON_BACKEND_TRANSACTION_H_ */
|
||||
BIN
apps/backend/clicon_transaction_api.o
Normal file
BIN
apps/backend/clicon_transaction_api.o
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue