CLI plugin API restructuring completed. Now all plugin APIs have the generic form

documented in README and FAQ.
This commit is contained in:
Olof hagsand 2018-04-08 11:32:43 +02:00
parent afb6aa31db
commit 2e00411621
19 changed files with 317 additions and 551 deletions

View file

@ -17,7 +17,8 @@
* CLI parse hook
* CLICON_FIND_PLUGIN
* clicon_valcb()
* backend system plugins (CLIXON_BACKEND_SYSDIR)
* CLIXON_BACKEND_SYSDIR
* CLIXON_CLI_SYSDIR
* CLICON_MASTER_PLUGIN config variable
* Example of migrating a backend plugin module:
* Add all callbacks in a clixon_plugin_api struct

View file

@ -83,7 +83,7 @@ Extending
=========
Clixon provides a core system and can be used as-is using available
Yang specifications. However, an application very quickly needs to
specialize functions. Clixon is extended by (most commonly) writing
specialize functions. Clixon is extended by writing
plugins for cli and backend. Extensions for netconf and restconf
are also available.

View file

@ -106,7 +106,7 @@ clixon_plugin_reset(clicon_handle h,
plgreset_t *resetfn; /* Plugin auth */
int retval = 1;
while ((cp = plugin_each(cp)) != NULL) {
while ((cp = plugin_each(h, cp)) != NULL) {
if ((resetfn = cp->cp_api.ca_reset) == NULL)
continue;
if ((retval = resetfn(h, db)) < 0) {
@ -150,7 +150,7 @@ clixon_plugin_statedata(clicon_handle h,
clicon_err(OE_CFG, ENOENT, "XML tree expected");
goto done;
}
while ((cp = plugin_each(cp)) != NULL) {
while ((cp = plugin_each(h, cp)) != NULL) {
if ((fn = cp->cp_api.ca_statedata) == NULL)
continue;
if ((x = xml_new("config", NULL, NULL)) == NULL)
@ -246,7 +246,7 @@ plugin_transaction_begin(clicon_handle h,
clixon_plugin *cp = NULL;
trans_cb_t *fn;
while ((cp = plugin_each(cp)) != NULL) {
while ((cp = plugin_each(h, cp)) != NULL) {
if ((fn = cp->cp_api.ca_trans_begin) == NULL)
continue;
if ((retval = fn(h, (transaction_data)td)) < 0){
@ -274,7 +274,7 @@ plugin_transaction_validate(clicon_handle h,
clixon_plugin *cp = NULL;
trans_cb_t *fn;
while ((cp = plugin_each(cp)) != NULL) {
while ((cp = plugin_each(h, cp)) != NULL) {
if ((fn = cp->cp_api.ca_trans_validate) == NULL)
continue;
if ((retval = fn(h, (transaction_data)td)) < 0){
@ -303,7 +303,7 @@ plugin_transaction_complete(clicon_handle h,
clixon_plugin *cp = NULL;
trans_cb_t *fn;
while ((cp = plugin_each(cp)) != NULL) {
while ((cp = plugin_each(h, cp)) != NULL) {
if ((fn = cp->cp_api.ca_trans_complete) == NULL)
continue;
if ((retval = fn(h, (transaction_data)td)) < 0){
@ -349,7 +349,7 @@ plugin_transaction_revert(clicon_handle h,
tr.td_scvec = td->td_tcvec;
tr.td_tcvec = td->td_scvec;
while ((cp = plugin_each_revert(cp, nr)) != NULL) {
while ((cp = plugin_each_revert(h, cp, nr)) != NULL) {
if ((fn = cp->cp_api.ca_trans_commit) == NULL)
continue;
if ((retval = fn(h, (transaction_data)td)) < 0){
@ -379,7 +379,7 @@ plugin_transaction_commit(clicon_handle h,
trans_cb_t *fn;
int i=0;
while ((cp = plugin_each(cp)) != NULL) {
while ((cp = plugin_each(h, cp)) != NULL) {
i++;
if ((fn = cp->cp_api.ca_trans_commit) == NULL)
continue;
@ -409,7 +409,7 @@ plugin_transaction_end(clicon_handle h,
clixon_plugin *cp = NULL;
trans_cb_t *fn;
while ((cp = plugin_each(cp)) != NULL) {
while ((cp = plugin_each(h, cp)) != NULL) {
if ((fn = cp->cp_api.ca_trans_end) == NULL)
continue;
if ((retval = fn(h, (transaction_data)td)) < 0){
@ -436,7 +436,7 @@ plugin_transaction_abort(clicon_handle h,
clixon_plugin *cp = NULL;
trans_cb_t *fn;
while ((cp = plugin_each(cp)) != NULL) {
while ((cp = plugin_each(h, cp)) != NULL) {
if ((fn = cp->cp_api.ca_trans_abort) == NULL)
continue;
fn(h, (transaction_data)td); /* dont abort on error */

View file

@ -56,8 +56,6 @@ CLIXON_MINOR = @CLIXON_VERSION_MINOR@
# Use this clixon lib for linking
CLIXON_LIB = libclixon.so.$(CLIXON_MAJOR).$(CLIXON_MINOR)
# Location of system plugins
CLIXON_CLI_SYSDIR = $(libdir)/clixon/plugins/cli
# For dependency. A little strange that we rely on it being built in the src dir
# even though it may exist in $(libdir). But the new version may not have been installed yet.
@ -127,7 +125,7 @@ uninstall:
.SUFFIXES: .c .o
.c.o:
$(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" -DCLIXON_CLI_SYSDIR=\"$(CLIXON_CLI_SYSDIR)\" $(CFLAGS) -c $<
$(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" $(CFLAGS) -c $<
# Just link test programs
test.c :

View file

@ -172,12 +172,22 @@ cli_parse_file(clicon_handle h,
}
int
cli_susp_hook(clicon_handle h, cli_susphook_t *fn)
cli_susp_hook(clicon_handle h,
cligen_susp_cb_t *fn)
{
cligen_handle ch = cligen(h);
/* This assume first arg of fn can be treated as void* */
return cligen_susp_hook(ch, (cligen_susp_cb_t*)fn);
return cligen_susp_hook(ch, fn);
}
int
cli_interrupt_hook(clicon_handle h,
cligen_interrupt_cb_t *fn)
{
cligen_handle ch = cligen(h);
/* This assume first arg of fn can be treated as void* */
return cligen_interrupt_hook(ch, fn);
}
char *

View file

@ -39,7 +39,7 @@
/*
* Prototypes
* Internal prototypes. For exported functions see clicon_cli_api.h
* Internal prototypes. For exported functions see clixon_cli_api.h
*/
int cli_parse_file(clicon_handle h,
FILE *f,
@ -47,7 +47,9 @@ int cli_parse_file(clicon_handle h,
parse_tree *pt,
cvec *globals);
int cli_susp_hook(clicon_handle h, cli_susphook_t *fn);
int cli_susp_hook(clicon_handle h, cligen_susp_cb_t *fn);
int cli_interrupt_hook(clicon_handle h, cligen_interrupt_cb_t *fn);
char *cli_nomatch(clicon_handle h);
@ -56,8 +58,8 @@ int cli_prompt_set(clicon_handle h, char *prompt);
int cli_logsyntax_set(clicon_handle h, int status);
/* Internal functions for handling cli groups */
cli_syntax_t *cli_syntax(clicon_handle h);
int cli_syntax_set(clicon_handle h, cli_syntax_t *stx);
#endif /* _CLI_HANDLE_H_ */

View file

@ -460,7 +460,7 @@ main(int argc, char **argv)
*/
tmp = *(argv-1);
*(argv-1) = argv0;
cli_plugin_start(h, argc+1, argv-1);
clixon_plugin_start(h, argc+1, argv-1);
*(argv-1) = tmp;
cligen_line_scrolling_set(cli_cligen(h), clicon_option_int(h,"CLICON_CLI_LINESCROLLING"));

View file

@ -66,13 +66,6 @@
#include "cli_plugin.h"
#include "cli_handle.h"
/*! Name of master plugin functions
* More in clicon_plugin.h
* @note not really used consider documenting or remove
*/
#define PLUGIN_PROMPT_HOOK "plugin_prompt_hook"
#define PLUGIN_SUSP_HOOK "plugin_susp_hook"
/*
*
* CLI PLUGIN INTERFACE, INTERNAL SECTION
@ -82,7 +75,9 @@
/*! Find syntax mode named 'mode'. Create if specified
*/
static cli_syntaxmode_t *
syntax_mode_find(cli_syntax_t *stx, const char *mode, int create)
syntax_mode_find(cli_syntax_t *stx,
const char *mode,
int create)
{
cli_syntaxmode_t *m;
@ -111,34 +106,20 @@ syntax_mode_find(cli_syntax_t *stx, const char *mode, int create)
return m;
}
/*! Find plugin by name
*/
static struct cli_plugin *
plugin_find_cli(cli_syntax_t *stx, char *plgnam)
{
struct cli_plugin *p;
if ((p = stx->stx_plugins) != NULL)
do {
if (strcmp (p->cp_name, plgnam) == 0)
return p;
p = NEXTQ(struct cli_plugin *, p);
} while (p && p != stx->stx_plugins);
return NULL;
}
/*! Generate parse tree for syntax mode
* @param[in] h Clicon handle
* @param[in] m Syntax mode struct
*/
static int
gen_parse_tree(clicon_handle h, cli_syntaxmode_t *m)
gen_parse_tree(clicon_handle h,
cli_syntaxmode_t *m)
{
cligen_tree_add(cli_cligen(h), m->csm_name, m->csm_pt);
return 0;
}
/*! Append syntax
* @param[in] h Clicon handle
*/
static int
syntax_append(clicon_handle h,
@ -157,27 +138,18 @@ syntax_append(clicon_handle h,
return 0;
}
/*! Unload all plugins in a group
/*! Remove all cligen syntax modes
* @param[in] h Clicon handle
*/
static int
cli_syntax_unload(clicon_handle h)
{
cli_syntax_t *stx = cli_syntax(h);
struct cli_plugin *p;
cli_syntaxmode_t *m;
if (stx == NULL)
return 0;
while (stx->stx_nplugins > 0) {
p = stx->stx_plugins;
plugin_unload(h, p->cp_handle);
clicon_debug(1, "DEBUG: Plugin '%s' unloaded.", p->cp_name);
DELQ(p, stx->stx_plugins, struct cli_plugin *);
if (p)
free(p);
stx->stx_nplugins--;
}
while (stx->stx_nmodes > 0) {
m = stx->stx_modes;
DELQ(m, stx->stx_modes, cli_syntaxmode_t *);
@ -237,34 +209,6 @@ clixon_str2fn(char *name,
return NULL;
}
/*! Load a dynamic plugin object and call it's init-function
* Note 'file' may be destructively modified
* @retval plugin-handle should be freed after use
*/
static plghndl_t
cli_plugin_load(clicon_handle h,
char *file,
int dlflags)
{
char *name;
plghndl_t handle = NULL;
struct cli_plugin *cp = NULL;
if ((handle = plugin_load(h, file, dlflags)) == NULL)
goto quit;
if ((cp = malloc(sizeof (struct cli_plugin))) == NULL) {
perror("malloc");
goto quit;
}
memset (cp, 0, sizeof(*cp));
name = basename(file);
snprintf(cp->cp_name, sizeof(cp->cp_name), "%.*s", (int)strlen(name)-3, name);
cp->cp_handle = handle;
quit:
return cp;
}
/*! Append to syntax mode from file
* @param[in] h Clixon handle
* @param[in] filename Name of file where syntax is specified (in syntax-group dir)
@ -286,7 +230,7 @@ cli_load_syntax(clicon_handle h,
char **vec = NULL;
int i, nvec;
char *plgnam;
struct cli_plugin *p;
clixon_plugin *cp;
if (dir)
snprintf(filepath, MAXPATHLEN-1, "%s/%s", dir, filename);
@ -316,8 +260,8 @@ cli_load_syntax(clicon_handle h,
mode = cvec_find_str(cvv, "CLICON_MODE");
if (plgnam != NULL) { /* Find plugin for callback resolving */
if ((p = plugin_find_cli (cli_syntax(h), plgnam)) != NULL)
handle = p->cp_handle;
if ((cp = plugin_find(h, plgnam)) != NULL)
handle = cp->cp_handle;
if (handle == NULL){
clicon_err(OE_PLUGIN, 0, "CLICON_PLUGIN set to '%s' in %s but plugin %s.so not found in %s\n",
plgnam, filename, plgnam,
@ -325,7 +269,6 @@ cli_load_syntax(clicon_handle h,
goto done;
}
}
/* Resolve callback names to function pointers. */
if (cligen_callbackv_str2fn(pt, (cgv_str2fn_t*)clixon_str2fn, handle) < 0){
clicon_err(OE_PLUGIN, 0, "Mismatch between CLIgen file '%s' and CLI plugin file '%s'. Some possible errors:\n\t1. A function given in the CLIgen file does not exist in the plugin (ie link error)\n\t2. The CLIgen spec does not point to the correct plugin .so file (CLICON_PLUGIN=\"%s\" is wrong)",
@ -343,7 +286,10 @@ cli_load_syntax(clicon_handle h,
if ((vec = clicon_strsep(mode, ":", &nvec)) == NULL)
goto done;
for (i = 0; i < nvec; i++) {
if (syntax_append(h, cli_syntax(h), vec[i], pt) < 0) {
if (syntax_append(h,
cli_syntax(h),
vec[i],
pt) < 0) {
goto done;
}
if (prompt)
@ -361,74 +307,7 @@ done:
return retval;
}
/*! Load plugins within a directory
*/
static int
cli_plugin_load_dir(clicon_handle h,
char *dir,
cli_syntax_t *stx)
{
int i;
int ndp;
struct dirent *dp = NULL;
char *master_plugin;
char master[MAXPATHLEN];
char filename[MAXPATHLEN];
struct cli_plugin *cp;
struct stat st;
int retval = -1;
/* 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;
}
snprintf(master, MAXPATHLEN-1, "%s.so", master_plugin);
/* Get plugin objects names from plugin directory */
ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG);
if (ndp < 0)
goto quit;
/* Load master plugin first */
snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, master);
if (stat(filename, &st) == 0) {
clicon_debug(1, "DEBUG: Loading master plugin '%s'", master);
cp = cli_plugin_load(h, filename, RTLD_NOW|RTLD_GLOBAL);
if (cp == NULL)
goto quit;
/* Look up certain call-backs in master plugin */
stx->stx_prompt_hook =
dlsym(cp->cp_handle, PLUGIN_PROMPT_HOOK);
stx->stx_susp_hook =
dlsym(cp->cp_handle, PLUGIN_SUSP_HOOK);
INSQ(cp, stx->stx_plugins);
stx->stx_nplugins++;
}
/* Load the rest */
for (i = 0; i < ndp; i++) {
if (strcmp (dp[i].d_name, master) == 0)
continue; /* Skip master now */
snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);
clicon_debug(1, "DEBUG: Loading plugin '%s'", dp[i].d_name);
if ((cp = cli_plugin_load (h, filename, RTLD_NOW)) == NULL)
goto quit;
INSQ(cp, stx->stx_plugins);
stx->stx_nplugins++;
}
retval = 0;
quit:
if (dp)
free(dp);
return retval;
}
/*! Load a syntax group.
/*! Load a syntax group. Includes both CLI plugin and CLIgen spec syntax files.
* @param[in] h Clicon handle
*/
int
@ -443,6 +322,9 @@ cli_syntax_load (clicon_handle h)
struct dirent *dp = NULL;
cli_syntax_t *stx;
cli_syntaxmode_t *m;
cligen_susp_cb_t *fns = NULL;
cligen_interrupt_cb_t *fni = NULL;
clixon_plugin *cp;
/* Syntax already loaded. XXX should we re-load?? */
if ((stx = cli_syntax(h)) != NULL)
@ -456,59 +338,62 @@ cli_syntax_load (clicon_handle h)
/* Allocate plugin group object */
if ((stx = malloc(sizeof(*stx))) == NULL) {
clicon_err(OE_UNIX, errno, "malloc");
goto quit;
goto done;
}
memset (stx, 0, sizeof (*stx)); /* Zero out all */
cli_syntax_set(h, stx);
/* First load CLICON system plugins. CLIXON_CLI_SYSDIR is defined
in Makefile*/
if (cli_plugin_load_dir(h, CLIXON_CLI_SYSDIR, stx) < 0)
goto quit;
/* Then load application plugins */
if (plugin_dir && cli_plugin_load_dir(h, plugin_dir, stx) < 0)
goto quit;
/* Load cli plugins */
if (plugin_dir &&
clixon_plugins_load(h, CLIXON_PLUGIN_INIT, plugin_dir)< 0)
goto done;
if (clispec_file){
if (cli_load_syntax(h, clispec_file, NULL) < 0)
goto quit;
goto done;
}
if (clispec_dir){
/* load syntaxfiles */
if ((ndp = clicon_file_dirent(clispec_dir, &dp, "(.cli)$", S_IFREG)) < 0)
goto quit;
goto done;
/* Load the rest */
for (i = 0; i < ndp; i++) {
clicon_debug(1, "DEBUG: Loading syntax '%.*s'",
(int)strlen(dp[i].d_name)-4, dp[i].d_name);
if (cli_load_syntax(h, dp[i].d_name, clispec_dir) < 0)
goto quit;
goto done;
}
}
/* Did we successfully load any syntax modes? */
if (stx->stx_nmodes <= 0) {
retval = 0;
goto quit;
goto done;
}
/* Parse syntax tree for all modes */
m = stx->stx_modes;
do {
if (gen_parse_tree(h, m) != 0)
goto quit;
goto done;
m = NEXTQ(cli_syntaxmode_t *, m);
} while (m && m != stx->stx_modes);
/* Set callbacks into CLIgen */
cli_susp_hook(h, cli_syntax(h)->stx_susp_hook);
/* Set susp and interrupt callbacks into CLIgen */
cp = NULL;
while ((cp = plugin_each(h, cp)) != NULL) {
if (fns==NULL && (fns = cp->cp_api.ca_suspend) != NULL)
if (cli_susp_hook(h, fns) < 0)
goto done;
if (fni==NULL && (fni = cp->cp_api.ca_interrupt) != NULL)
if (cli_susp_hook(h, fns) < 0)
goto done;
}
/* All good. We can now proudly return a new group */
retval = 0;
quit:
done:
if (retval != 0) {
clixon_plugin_exit(h);
cli_syntax_unload(h);
cli_syntax_set(h, NULL);
}
@ -517,34 +402,15 @@ quit:
return retval;
}
/*! Call plugin_start() in all plugins
*/
int
cli_plugin_start(clicon_handle h, int argc, char **argv)
{
struct cli_plugin *p;
cli_syntax_t *stx;
plgstart_t *startfun;
// XXX int (*startfun)(clicon_handle, int, char **);
stx = cli_syntax(h);
if ((p = stx->stx_plugins) != NULL)
do {
startfun = dlsym(p->cp_handle, PLUGIN_START);
if (dlerror() == NULL)
startfun(h, argc, argv);
p = NEXTQ(struct cli_plugin *, p);
} while (p && p != stx->stx_plugins);
return 0;
}
/*
/*! Remove syntax modes and remove syntax
* @param[in] h Clicon handle
*/
int
cli_plugin_finish(clicon_handle h)
{
/* Remove all CLI plugins */
clixon_plugin_exit(h);
/* Remove all cligen syntax modes */
cli_syntax_unload(h);
cli_syntax_set(h, NULL);
return 0;
@ -553,6 +419,7 @@ cli_plugin_finish(clicon_handle h)
/*! Help function to print a meaningful error string.
* Sometimes the libraries specify an error string, if so print that.
* Otherwise just print 'command error'.
* @param[in] f File handler to write error to.
*/
int
cli_handler_err(FILE *f)
@ -704,6 +571,7 @@ done:
}
/*! Read command from CLIgen's cliread() using current syntax mode.
* @param[in] h Clicon handle
* @retval string char* buffer containing CLIgen command
* @retval NULL Fatal error
*/
@ -714,12 +582,19 @@ clicon_cliread(clicon_handle h)
char *pfmt = NULL;
cli_syntaxmode_t *mode;
cli_syntax_t *stx;
cli_prompthook_t *fn;
clixon_plugin *cp;
stx = cli_syntax(h);
mode = stx->stx_active_mode;
if (stx->stx_prompt_hook)
pfmt = stx->stx_prompt_hook(h, mode->csm_name);
/* Get prompt from plugin callback? */
cp = NULL;
while ((cp = plugin_each(h, cp)) != NULL) {
if ((fn = cp->cp_api.ca_prompt) == NULL)
continue;
pfmt = fn(h, mode->csm_name);
break;
}
if (clicon_quiet_mode(h))
cli_prompt_set(h, "");
else
@ -732,6 +607,7 @@ clicon_cliread(clicon_handle h)
}
/*! Initialize plugin code (not the plugins themselves)
* @param[in] h Clicon handle
*/
int
cli_plugin_init(clicon_handle h)
@ -746,11 +622,12 @@ cli_plugin_init(clicon_handle h)
*/
/*
* Set syntax mode mode for existing current plugin group.
/*! Set syntax mode mode for existing current plugin group.
* @param[in] h Clicon handle
*/
int
cli_set_syntax_mode(clicon_handle h, const char *name)
cli_set_syntax_mode(clicon_handle h,
const char *name)
{
cli_syntaxmode_t *mode;
@ -761,8 +638,8 @@ cli_set_syntax_mode(clicon_handle h, const char *name)
return 1;
}
/*
* Get syntax mode name
/*! Get syntax mode name
* @param[in] h Clicon handle
*/
char *
cli_syntax_mode(clicon_handle h)
@ -774,14 +651,15 @@ cli_syntax_mode(clicon_handle h)
return csm->csm_name;
}
/*
* Callback from cli_set_prompt(). Set prompt format for syntax mode
* Arguments:
* name : Name of syntax mode
* prompt : Prompt format
/*! Callback from cli_set_prompt(). Set prompt format for syntax mode
* @param[in] h Clicon handle
* @param[in] name Name of syntax mode
* @param[in] prompt Prompt format
*/
int
cli_set_prompt(clicon_handle h, const char *name, const char *prompt)
cli_set_prompt(clicon_handle h,
const char *name,
const char *prompt)
{
cli_syntaxmode_t *m;
@ -793,9 +671,14 @@ cli_set_prompt(clicon_handle h, const char *name, const char *prompt)
}
/*! Format prompt
* @param[out] prompt Prompt string to be written
* @param[in] plen Length of prompt string
* @param[in] fmt Stdarg fmt string
*/
static int
prompt_fmt (char *prompt, size_t plen, char *fmt, ...)
prompt_fmt (char *prompt,
size_t plen,
char *fmt, ...)
{
va_list ap;
char *s = fmt;
@ -849,6 +732,7 @@ done:
}
/*! Return a formatted prompt string
* @param[in] fmt Format string
*/
char *
cli_prompt(char *fmt)

View file

@ -43,19 +43,7 @@
/* clicon generic callback pointer */
typedef void (clicon_callback_t)(clicon_handle h);
/* clicon_set value callback */
typedef int (cli_valcb_t)(cvec *vars, cg_var *cgv, cg_var *arg);
/* specific to cli. For common see clicon_plugin.h */
/* Hook to get prompt format before each getline */
typedef char *(cli_prompthook_t)(clicon_handle, char *mode);
/* Ctrl-Z hook from getline() */
typedef int (cli_susphook_t)(clicon_handle, char *, int, int *);
/* CLIgen parse failure hook. Retry other mode? */
typedef char *(cli_parsehook_t)(clicon_handle, char *, char *);
/* List of syntax modes */
typedef struct {
qelem_t csm_qelem; /* List header */
char csm_name[256]; /* Syntax mode name */
@ -65,29 +53,16 @@ typedef struct {
} cli_syntaxmode_t;
/* A plugin list object */
struct cli_plugin {
qelem_t cp_qelem; /* List header */
char cp_name[256]; /* Plugin name */
void *cp_handle; /* Dynamic object handle */
};
/* Plugin group object */
/* Plugin group object. Just a single object, not list. part of cli_handle */
typedef struct {
int stx_nplugins; /* Number of plugins */
struct cli_plugin *stx_plugins; /* List of plugins */
int stx_nmodes; /* Number of syntax modes */
cli_syntaxmode_t *stx_active_mode; /* Current active syntax mode */
cli_syntaxmode_t *stx_modes; /* List of syntax modes */
cli_prompthook_t *stx_prompt_hook; /* Prompt hook */
cli_susphook_t *stx_susp_hook; /* Ctrl-Z hook from getline() */
} cli_syntax_t;
void *clixon_str2fn(char *name, void *handle, char **error);
int cli_plugin_start(clicon_handle, int argc, char **argv);
int cli_plugin_init(clicon_handle h);
int clicon_eval(clicon_handle h, char *cmd, cg_obj *match_obj, cvec *vr);

View file

@ -1,129 +0,0 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Alternatively, the contents of this file may be used under the terms of
# the GNU General Public License Version 3 or later (the "GPL"),
# in which case the provisions of the GPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of the GPL, and not to allow others to
# use your version of this file under the terms of Apache License version 2,
# indicate your decision by deleting the provisions above and replace them with
# the notice and other provisions required by the GPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the Apache License version 2 or the GPL.
#
# ***** END LICENSE BLOCK *****
#
#
# CLIXON options - Default values
# The origin of this file is run a _first_ time through a pre-processor at
# clixon make install time causing autoconf constants (such as "prefix" and
# "localstatedir") to be replaced with their installed values.
# It should be run a _second_ time as a part of installation of the application,
# in case clixon.mk is included in the application include file, and
# "$(APPNAME).conf" rule is accessed.
#
# See clicon_tutorial for more documentation
# Location of configuration-file for default values (this file)
CLICON_CONFIGFILE sysconfdir/APPNAME.conf
# Location of YANG module and submodule files.
CLICON_YANG_DIR prefix/share/APPNAME/yang
# Main yang module or absolute filename. If module then search as follows:
# <yangdir>/<module>[@<revision>]
# CLICON_YANG_MODULE_MAIN clicon
# Option used to construct initial yang file:
# <module>[@<revision>]
CLICON_YANG_MODULE_REVISION
# Location of backend .so plugins
CLICON_BACKEND_DIR libdir/APPNAME/backend
# Location of netconf (frontend) .so plugins
CLICON_NETCONF_DIR libdir/APPNAME/netconf
# Location of restconf (frontend) .so plugins
CLICON_RESTCONF_DIR libdir/APPNAME/restconf
# Location of cli frontend .so plugins
CLICON_CLI_DIR libdir/APPNAME/cli
# Location of frontend .cli cligen spec files
CLICON_CLISPEC_DIR libdir/APPNAME/clispec
# Enabled uses "startup" configuration on boot
CLICON_USE_STARTUP_CONFIG 0
# Address family for communicating with clixon_backend (UNIX|IPv4|IPv6)
CLICON_SOCK_FAMILY UNIX
# If family above is AF_UNIX: Unix socket for communicating with clixon_backend
# If family above is AF_INET: IPv4 address
CLICON_SOCK localstatedir/APPNAME/APPNAME.sock
# Inet socket port for communicating with clixon_backend (only IPv4|IPv6)
CLICON_SOCK_PORT 4535
# Process-id file
CLICON_BACKEND_PIDFILE localstatedir/APPNAME/APPNAME.pidfile
# Group membership to access clixon_backend unix socket
# CLICON_SOCK_GROUP clicon
# Set if all configuration changes are committed directly, commit command unnecessary
# CLICON_AUTOCOMMIT 0
# Name of master plugin (both frontend and backend). Master plugin has special
# callbacks for frontends. See clicon user manual for more info.
# CLICON_MASTER_PLUGIN master
# Startup CLI mode. This should match the CLICON_MODE in your startup clispec file
# CLICON_CLI_MODE base
# Generate code for CLI completion of existing db symbols. Add name="myspec" in
# datamodel spec and reference as @myspec.
# CLICON_CLI_GENMODEL 1
# Generate code for CLI completion of existing db symbols
# CLICON_CLI_GENMODEL_COMPLETION 1
# How to generate and show CLI syntax: VARS|ALL
# CLICON_CLI_GENMODEL_TYPE VARS
# Directory where "running", "candidate" and "startup" are placed
CLICON_XMLDB_DIR localstatedir/APPNAME
# XMLDB datastore plugin filename (see datastore/ and clixon_xml_db.[ch])
CLICON_XMLDB_PLUGIN libdir/xmldb/text.so
# Dont include keys in cvec in cli vars callbacks, ie a & k in 'a <b> k <c>' ignored
# CLICON_CLI_VARONLY 1
# Set to 0 if you want CLI to wrap to next line.
# Set to 1 if you want CLI to scroll sideways when approaching right margin
# CLICON_CLI_LINESCROLLING 1
# FastCGI unix socket. Should be specified in webserver
# Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock;
CLICON_RESTCONF_PATH /www-data/fastcgi_restconf.sock

View file

@ -1,4 +1,4 @@
# Clixon FAQ
i# Clixon FAQ
## What is Clixon?
@ -197,6 +197,7 @@ The second way is by programming the plugin_reset() in the backend
plugin. The example code contains an example on how to do this (see plugin_reset() in example_backend.c).
## I want to program. How do I extend the example?
See [../apps/example]
- example.xml - Change the configuration file
- The yang specifications - This is the central part. It changes the XML, database and the config cli.
- example_cli.cli - Change the fixed part of the CLI commands
@ -205,6 +206,25 @@ plugin. The example code contains an example on how to do this (see plugin_reset
- example_netconf.c - Netconf plugin
- example_restconf.c - Add restconf authentication, etc.
## How is a plugin initiated?
Each plugin is initiated with an API struct followed by a plugin init function as follows:
```
static clixon_plugin_api api = {
"example", /* name */
clixon_plugin_init,
plugin_start,
... /* more functions here */
}
clixon_plugin_api *
clixon_plugin_init(clicon_handle h)
{
...
return &api; /* Return NULL on error */
}
```
For more info see [../example/README.md]
## How do I write a commit function?
In the example, you write a commit function in example_backend.c.
Every time a commit is made, transaction_commit() is called in the
@ -284,10 +304,10 @@ implement the RFC, you need to register an RPC callback in the backend plugin:
Example:
```
int
plugin_init(clicon_handle h)
clixon_plugin_init(clicon_handle h)
{
...
backend_rpc_cb_register(h, fib_route, NULL, "fib-route");
rpc_callback_register(h, fib_route, NULL, "fib-route");
...
}
```
@ -296,9 +316,9 @@ And then define the callback itself:
static int
fib_route(clicon_handle h, /* Clicon handle */
cxobj *xe, /* Request: <rpc><xn></rpc> */
struct client_entry *ce, /* Client session */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg) /* Argument given at register */
void *arg, /* Client session */
void *regarg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
return 0;
@ -313,13 +333,14 @@ You can specify an authentication callback for restconf as follows:
```
int
plugin_credentials(clicon_handle h,
FCGX_Request *r,
char **username)
void *arg)
{
FCGX_Request *r = (FCGX_Request *)arg;
...
clicon_username_set(h, user);
```
If a plugin is provided, it needs to supply a username. If not, the
request is unauthorized. the function mallocs a username and returns
it.
To authenticate, the callback needs to return the value 1 and supply a username.
See (../apps/example/example_restconf.c) plugin_credentials() for
See [../apps/example/example_restconf.c] plugin_credentials() for
an example of HTTP basic auth.

View file

@ -89,6 +89,43 @@ Routing notification
...
```
## Initializing a plugin
The example includes a restonf, netconf, CLI and two backend plugins.
Each plugin is initiated with an API struct followed by a plugin init function.
The content of the API struct is different depending on what kind of plugin it is. Some fields are
meaningful only for some plugins.
The plugin init function may also include registering RPC functions.
```
static clixon_plugin_api api = {
"example", /* name */
clixon_plugin_init,
plugin_start,
plugin_exit,
NULL, /* cli prompt N/A for backend */
NULL, /* cli suspend N/A for backend */
NULL, /* cli interrupt N/A for backend */
NULL, /* auth N/A for backend */
plugin_reset,
plugin_statedata,
transaction_begin,
transaction_validate,
transaction_complete,
transaction_commit,
transaction_end,
transaction_abort
};
clixon_plugin_api *
clixon_plugin_init(clicon_handle h)
{
/* Optional callback registration for RPC calls */
rpc_callback_register(h, fib_route, NULL, "fib-route");
/* Return plugin API */
return &api; /* Return NULL on error */
}
```
## Operation data
Clixon implements Yang RPC operations by an extension mechanism. The
@ -119,18 +156,18 @@ In the backend, a callback is registered (fib_route()) which handles the RPC.
static int
fib_route(clicon_handle h,
cxobj *xe, /* Request: <rpc><xn></rpc> */
struct client_entry *ce, /* Client session */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg) /* Argument given at register */
void *arg, /* Client session */
void *regarg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
return 0;
}
int
plugin_init(clicon_handle h)
clixon_plugin_init(clicon_handle h)
{
...
backend_rpc_cb_register(h, fib_route, NULL, "fib-route");
rpc_callback_register(h, fib_route, NULL, "fib-route");
...
}
```

View file

@ -256,6 +256,9 @@ static clixon_plugin_api api = {
plugin_start, /* start */
NULL, /* exit */
NULL, /* auth */
NULL, /* cli prompt */
NULL, /* cli suspend */
NULL, /* cli interrupt */
plugin_reset, /* reset */
plugin_statedata, /* statedata */
NULL, /* trans begin */
@ -293,6 +296,7 @@ clixon_plugin_init(clicon_handle h)
"empty"/* Xml tag when callback is made */
) < 0)
goto done;
/* Return plugin API */
return &api;
done:
return NULL;

View file

@ -52,19 +52,6 @@
#include <clixon/clixon.h>
#include <clixon/clixon_cli.h>
/*
* Plugin initialization
*/
int
plugin_init(clicon_handle h)
{
struct timeval tv;
gettimeofday(&tv, NULL);
srandom(tv.tv_usec);
return 0;
}
/*! Example cli function */
int
@ -125,3 +112,29 @@ fib_route_rpc(clicon_handle h,
return retval;
}
static clixon_plugin_api api = {
"example", /* name */
clixon_plugin_init, /* init */
NULL, /* start */
NULL, /* exit */
NULL, /* auth */
NULL, /* cli_prompthook_t */
NULL, /* cligen_susp_cb_t */
NULL, /* cligen_interrupt_cb_t */
};
/*! CLI plugin initialization
* @param[in] h Clixon handle
* @retval NULL Error with clicon_err set
* @retval api Pointer to API struct
*/
clixon_plugin_api *
clixon_plugin_init(clicon_handle h)
{
struct timeval tv;
gettimeofday(&tv, NULL);
srandom(tv.tv_usec);
return &api;
}

View file

@ -268,7 +268,8 @@ plugin_credentials(clicon_handle h,
/*! Local example restconf rpc callback
*/
int restconf_client_rpc(clicon_handle h,
int
restconf_client_rpc(clicon_handle h,
cxobj *xn,
cbuf *cbret,
void *arg,

View file

@ -43,8 +43,6 @@
*/
/* default group membership to access config unix socket */
#define CLICON_SOCK_GROUP "clicon"
/* Default name of master plugin */
#define CLICON_MASTER_PLUGIN "master"
/*
* Types
@ -137,9 +135,6 @@ static inline char *clicon_sock_group(clicon_handle h){
static inline char *clicon_backend_pidfile(clicon_handle h){
return clicon_option_str(h, "CLICON_BACKEND_PIDFILE");
}
static inline char *clicon_master_plugin(clicon_handle h){
return clicon_option_str(h, "CLICON_MASTER_PLUGIN");
}
static inline char *clicon_xmldb_dir(clicon_handle h){
return clicon_option_str(h, "CLICON_XMLDB_DIR");
}

View file

@ -38,10 +38,16 @@
#ifndef _CLIXON_PLUGIN_H_
#define _CLIXON_PLUGIN_H_
/*
* Constants
*/
/* Hardcoded plugin symbol. Must exist in all plugins to kickstart */
#define CLIXON_PLUGIN_INIT "clixon_plugin_init"
/*
* Types
*/
/* The dynamicically loadable plugin object handle */
/* Dynamicically loadable plugin object handle. @see return value of dlopen(3) */
typedef void *plghndl_t;
/* Registered RPC callback function */
@ -62,22 +68,13 @@ typedef int (*clicon_rpc_cb)(
* Backend see config_plugin.c
*/
/*! Called when plugin loaded. Only mandadory callback. All others optional
* @see plginit_t
*/
#define PLUGIN_INIT "plugin_init"
typedef void * (plginit_t)(clicon_handle); /* Clixon plugin Init */
/* Called when backend started with cmd-line arguments from daemon call.
* @see plgstart_t
*/
#define PLUGIN_START "plugin_start"
typedef int (plgstart_t)(clicon_handle, int, char **); /* Plugin start */
/* Called just before plugin unloaded.
*/
#define PLUGIN_EXIT "plugin_exit"
typedef int (plgexit_t)(clicon_handle); /* Plugin exit */
/*! Called by restconf to check credentials and return username
@ -99,6 +96,14 @@ typedef void *transaction_data;
/* Transaction callbacks */
typedef int (trans_cb_t)(clicon_handle h, transaction_data td);
/* Hook to override default prompt with explicit function
* Format prompt before each getline
* @param[in] h Clicon handle
* @param[in] mode Cligen syntax mode
* @retval prompt Prompt to prepend all CLigen command lines
*/
typedef char *(cli_prompthook_t)(clicon_handle, char *mode);
/* plugin init struct for the api
* Note: Implicit init function
*/
@ -106,14 +111,20 @@ struct clixon_plugin_api;
typedef struct clixon_plugin_api* (plginit2_t)(clicon_handle); /* Clixon plugin Init */
struct clixon_plugin_api{
/*--- Common fields. ---*/
char ca_name[PATH_MAX]; /* Name of plugin (given by plugin) */
plginit2_t *ca_init; /* Clixon plugin Init (implicit) */
plgstart_t *ca_start; /* Plugin start */
plgexit_t *ca_exit; /* Plugin exit */
plgauth_t *ca_auth; /* Auth credentials */
/*--Above here common fields w clixon_backend_api ----------*/
plgreset_t *ca_reset; /* Reset system status (backend only) */
/*--- CLI plugin-only ---*/
cli_prompthook_t *ca_prompt; /* Prompt hook */
cligen_susp_cb_t *ca_suspend; /* Ctrl-Z hook, see cligen getline */
cligen_interrupt_cb_t *ca_interrupt; /* Ctrl-C, see cligen getline */
/*--- Backend plugin only ---*/
plgreset_t *ca_reset; /* Reset system status (backend only) */
plgstatedata_t *ca_statedata; /* Get state data from plugin (backend only) */
trans_cb_t *ca_trans_begin; /* Transaction start */
trans_cb_t *ca_trans_validate; /* Transaction validation */
@ -124,46 +135,34 @@ struct clixon_plugin_api{
};
typedef struct clixon_plugin_api clixon_plugin_api;
/*! Called when plugin loaded. Only mandadory callback. All others optional
* @see plginit_t
*/
/* Internal plugin structure with dlopen() handle and plugin_api
*/
struct clixon_plugin{
char cp_name[PATH_MAX]; /* Plugin filename. Note api ca_name is given by plugin itself */
plghndl_t cp_handle; /* Handle to plugin using dlopen(3) */
struct clixon_plugin_api cp_api;
clixon_plugin_api cp_api;
};
typedef struct clixon_plugin clixon_plugin;
/*
* Pseudo-Prototypes
* User-defineed plugins, not in library code
* Prototypes
*/
#define CLIXON_PLUGIN_INIT "clixon_plugin_init" /* Nextgen */
/*! Plugin initialization
/*! Plugin initialization function. Must appear in all plugins
* @param[in] h Clixon handle
* @retval NULL Error with clicon_err set
* @retval api Pointer to API struct
* @see CLIXON_PLUGIN_INIT default symbol
*/
clixon_plugin_api *clixon_plugin_init(clicon_handle h);
/*
* Prototypes
*/
clixon_plugin *plugin_each(clixon_plugin *cpprev);
clixon_plugin *plugin_each_revert(clixon_plugin *cpprev, int nr);
clixon_plugin *plugin_each(clicon_handle h, clixon_plugin *cpprev);
clixon_plugin *plugin_each_revert(clicon_handle h, clixon_plugin *cpprev, int nr);
clixon_plugin *plugin_find(clicon_handle h, char *name);
int clixon_plugins_load(clicon_handle h, char *function, char *dir);
/* obsolete */
plghndl_t plugin_load (clicon_handle h, char *file, int dlflags);
/* obsolete */
int plugin_unload(clicon_handle h, plghndl_t *handle);
int clixon_plugin_start(clicon_handle h, int argc, char **argv);
int clixon_plugin_exit(clicon_handle h);

View file

@ -60,7 +60,10 @@
#include "clixon_xml.h"
#include "clixon_plugin.h"
/* XXX The below should be placed in clixon handle when done */
/* List of plugins XXX
* 1. Place in clixon handle not global variables
* 2. Use qelem circular lists
*/
static clixon_plugin *_clixon_plugins = NULL; /* List of plugins (of client) */
static int _clixon_nplugins = 0; /* Number of plugins */
@ -69,17 +72,19 @@ static int _clixon_nplugins = 0; /* Number of plugins */
* @note Never manipulate the plugin during operation or using the
* same object recursively
*
* @param[in] h Clicon handle
* @param[in] plugin previous plugin, or NULL on init
* @code
* clicon_plugin *cp = NULL;
* while ((cp = plugin_each(cp)) != NULL) {
* while ((cp = plugin_each(h, cp)) != NULL) {
* ...
* }
* @endcode
* @note Not optimized, alwasy iterates from the start of the list
*/
clixon_plugin *
plugin_each(clixon_plugin *cpprev)
plugin_each(clicon_handle h,
clixon_plugin *cpprev)
{
int i;
clixon_plugin *cp;
@ -105,17 +110,19 @@ plugin_each(clixon_plugin *cpprev)
* @note Never manipulate the plugin during operation or using the
* same object recursively
*
* @param[in] h Clicon handle
* @param[in] plugin previous plugin, or NULL on init
* @code
* clicon_plugin *cp = NULL;
* while ((cp = plugin_each_revert(cp, nr)) != NULL) {
* while ((cp = plugin_each_revert(h, cp, nr)) != NULL) {
* ...
* }
* @endcode
* @note Not optimized, alwasy iterates from the start of the list
*/
clixon_plugin *
plugin_each_revert(clixon_plugin *cpprev,
plugin_each_revert(clicon_handle h,
clixon_plugin *cpprev,
int nr)
{
int i;
@ -137,6 +144,27 @@ plugin_each_revert(clixon_plugin *cpprev,
return cpnext;
}
/*! Find plugin by name
* @param[in] h Clicon handle
* @param[in] name Plugin name
* @retval p Plugin if found
* @retval NULL Not found
*/
clixon_plugin *
plugin_find(clicon_handle h,
char *name)
{
int i;
clixon_plugin *cp = NULL;
for (i = 0; i < _clixon_nplugins; i++) {
cp = &_clixon_plugins[i];
if (strcmp(cp->cp_name, name) == 0)
return cp;
}
return NULL;
}
/*! Load a dynamic plugin object and call its init-function
* @param[in] h Clicon handle
* @param[in] file Which plugin to load
@ -158,6 +186,7 @@ plugin_load_one(clicon_handle h,
clixon_plugin_api *api = NULL;
clixon_plugin *cp = NULL;
char *name;
char *p;
clicon_debug(1, "%s", __FUNCTION__);
dlerror(); /* Clear any existing error */
@ -187,10 +216,18 @@ plugin_load_one(clicon_handle h,
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(cp, 0, sizeof(struct clixon_plugin));
cp->cp_handle = handle;
/* Extract string after last '/' in filename, if any */
name = strrchr(file, '/') ? strrchr(file, '/')+1 : file;
/* strip extension, eg .so from name */
if ((p=strrchr(name, '.')) != NULL)
*p = '\0';
/* Copy name to struct */
memcpy(cp->cp_name, name, strlen(name)+1);
snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s",
(int)strlen(name)-2, name);
(int)strlen(name), name);
cp->cp_api = *api;
clicon_debug(1, "%s", __FUNCTION__);
done:
@ -246,80 +283,6 @@ done:
return retval;
}
/*! Load a dynamic plugin object and call its init-function
* Note 'file' may be destructively modified
* @param[in] h Clicon handle
* @param[in] file Which plugin to load
* @param[in] dlflags See man(3) dlopen
* @note OBSOLETE
*/
plghndl_t
plugin_load(clicon_handle h,
char *file,
int dlflags)
{
char *error;
void *handle = NULL;
plginit_t *initfn;
clicon_debug(1, "%s", __FUNCTION__);
dlerror(); /* Clear any existing error */
if ((handle = dlopen(file, dlflags)) == NULL) {
error = (char*)dlerror();
clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error");
goto done;
}
/* call plugin_init() if defined */
if ((initfn = dlsym(handle, PLUGIN_INIT)) == NULL){
clicon_err(OE_PLUGIN, errno, "Failed to find plugin_init when loading clixon plugin %s", file);
goto err;
}
if ((error = (char*)dlerror()) != NULL) {
clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error);
goto done;
}
if (initfn(h) != 0) {
clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
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);
goto err;
}
done:
return handle;
err:
if (handle)
dlclose(handle);
return NULL;
}
/*! Unload a plugin
* @param[in] h Clicon handle
* @param[in] handle Clicon handle
* @note OBSOLETE
*/
int
plugin_unload(clicon_handle h,
plghndl_t *handle)
{
int retval = 0;
char *error;
plgexit_t *exitfn;
/* Call exit function is it exists */
exitfn = dlsym(handle, PLUGIN_EXIT);
if (dlerror() == NULL)
exitfn(h);
dlerror(); /* Clear any existing error */
if (dlclose(handle) != 0) {
error = (char*)dlerror();
clicon_err(OE_PLUGIN, errno, "dlclose: %s\n", error ? error : "Unknown error");
/* Just report */
}
return retval;
}
/*! Call plugin_start in all plugins
* @param[in] h Clicon handle
*/

View file

@ -237,14 +237,6 @@ module clixon-config {
"Set if all configuration changes are committed automatically
on every edit change. Explicit commit commands unnecessary";
}
leaf CLICON_MASTER_PLUGIN {
type string;
default "master";
description
"Name of master plugin (cli, netconf, restconf and backend).
Master plugin has special callbacks for frontends.
See clicon user manual for more info. (Obsolete?)";
}
leaf CLICON_XMLDB_DIR {
type string;
mandatory true;