/*
 *
  ***** BEGIN LICENSE BLOCK *****
 
  Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
  Copyright (C) 2017-2019 Olof Hagsand
  Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)

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

#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <dlfcn.h>
#include <dirent.h>
#include <syslog.h>
#include <unistd.h>
#include <termios.h>
#include <sys/stat.h>
#include <sys/param.h>

/* cligen */
#include <cligen/cligen.h>

#include "clixon_err.h"
#include "clixon_queue.h"
#include "clixon_string.h"
#include "clixon_hash.h"
#include "clixon_log.h"
#include "clixon_file.h"
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_options.h"
#include "clixon_xml.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xml_map.h"
#include "clixon_yang_module.h"
#include "clixon_netconf_lib.h"
#include "clixon_validate.h"
#include "clixon_plugin.h"

/*
 * Private types
 */
/*! Structure for checking status before and after a plugin call
 * Currently signal settings: blocked and handlers, and termios
 * @see plugin_context_check
 */
struct plugin_context {
    sigset_t         pc_sigset;            /* See sigprocmask(2) */
    struct sigaction pc_sigaction_vec[32]; /* See sigaction(2) */
    struct termios   pc_termios;           /* See termios(3) */
};

/* Internal plugin structure with dlopen() handle and plugin_api
 * This is an internal type, not exposed in the API
 * The external type is "clixon_plugin_t" defined in clixon_plugin.h
 */
struct clixon_plugin{
    qelem_t           cp_q;                /* queue header */
    char              cp_name[MAXPATHLEN]; /* Plugin filename. Note api ca_name is given by plugin itself */
    plghndl_t         cp_handle;  /* Handle to plugin using dlopen(3) */
    clixon_plugin_api cp_api;
};

/*
 * Upgrade callbacks for backend upgrade of datastore
 * Register upgrade callbacks in plugin_init() with a module and a "from" and "to"
 * revision.
 */
typedef struct {
    qelem_t           uc_qelem;     /* List header */
    clicon_upgrade_cb uc_callback;  /* RPC Callback */
    const char       *uc_fnstr;     /* Stringified fn name for debug */
    void             *uc_arg;       /* Application specific argument to cb */
    char             *uc_namespace; /* Module namespace */
} upgrade_callback_t;

/* Internal struct for accessing plugin list and rpc list. This handle is accessed
 * via clixon-handle "cdata" structure (see clixon_data.h) using the key "clixon-plugin-handle"."
 * It is just a way to avoid using global variables
 */
struct plugin_module_struct {
    clixon_plugin_t    *ms_plugin_list;
    rpc_callback_t     *ms_rpc_callbacks;
    upgrade_callback_t *ms_upgrade_callbacks;
};
typedef struct plugin_module_struct plugin_module_struct;

/*! Get plugin handle containing plugin and callback lists
 * @param[in]  h     Clicon handle
 */
static plugin_module_struct *
plugin_module_struct_get(clicon_handle h)
{
    clicon_hash_t *cdat = clicon_data(h);
    size_t         len;
    void          *p;

    if ((p = clicon_hash_value(cdat, "plugin-module-struct", &len)) != NULL)
        return *(plugin_module_struct **)p;
    return NULL;
}

/*! Set plugin handle containing plugin and callback lists
 * @param[in]  h     Clicon handle
 * @param[in]  pl    Clixon plugin handle 
 */
static int
plugin_module_struct_set(clicon_handle         h,
                         plugin_module_struct *ms)
{
    clicon_hash_t  *cdat = clicon_data(h);

    /* It is the pointer to ys that should be copied by hash,
       so we send a ptr to the ptr to indicate what to copy.
     */
    if (clicon_hash_add(cdat, "plugin-module-struct", &ms, sizeof(ms)) == NULL)
        return -1;
    return 0;
}

/* Access functions */

/*! Get plugin api 
 * @param[in]  cp   Clixon plugin handle
 */ 
clixon_plugin_api *
clixon_plugin_api_get(clixon_plugin_t *cp)
{
    return &cp->cp_api;
}

/*! Get plugin name 
 * @param[in]  cp   Clixon plugin handle
 */ 
char *
clixon_plugin_name_get(clixon_plugin_t *cp)
{
    return cp->cp_name;
}

/*! Get plugin handle
 * @param[in]  cp   Clixon plugin handle
 */ 
plghndl_t
clixon_plugin_handle_get(clixon_plugin_t *cp)
{
    return cp->cp_handle;
}

/*! Iterator over clixon 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 = clixon_plugin_each(h, cp)) != NULL) {
 *     ...
 *   }
 * @endcode
 * @note Not optimized, always iterates from the start of the list
 */
clixon_plugin_t *
clixon_plugin_each(clicon_handle    h,
                   clixon_plugin_t *cpprev)
{
    clixon_plugin_t      *cpnext = NULL; 
    plugin_module_struct *ms = plugin_module_struct_get(h);

    /* ms == NULL means plugins are not yet initialized */
    if (ms == NULL || ms->ms_plugin_list == NULL)
        cpnext = NULL;
    else if (cpprev == NULL)
        cpnext = ms->ms_plugin_list;
    else{
        cpnext = NEXTQ(clixon_plugin_t *, cpprev);
        if (cpnext == ms->ms_plugin_list)
            cpnext = NULL;
    }
    return cpnext;
}

/*! Reverse iterator over clixon plugins, iterater from nr to 0
 *
 * @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
 * @param[in]  nr      Start from this nr <= lngth of list 
 * @code
 *   clicon_plugin_t *cp = NULL;
 *   while ((cp = clixon_plugin_each_revert(h, cp, nr)) != NULL) {
 *     ...
 *   }
 * @endcode
 * @note Not optimized, always iterates from the start of the list
 */
clixon_plugin_t *
clixon_plugin_each_revert(clicon_handle    h,
                          clixon_plugin_t *cpprev,
                          int              nr)
{
    int            i;
    clixon_plugin_t *cpnext = NULL; 
    plugin_module_struct *ms = plugin_module_struct_get(h);

    if (ms == NULL){
        clicon_err(OE_PLUGIN, EINVAL, "plugin module not initialized");
        return NULL;
    }
    if (ms->ms_plugin_list == NULL)
        cpnext = NULL;
    else if (cpprev == NULL){
        cpnext = ms->ms_plugin_list;
        for (i = nr-1; i > 0; i--) {
            cpnext = NEXTQ(clixon_plugin_t *, cpnext);
            if (cpnext == ms->ms_plugin_list){
                cpnext = NULL;
                break;
            }
        }
    }
    else{
        if (cpprev == ms->ms_plugin_list)
            cpnext = NULL;
        else
            cpnext = PREVQ(clixon_plugin_t *, 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
 * @code
 *    clixon_plugin_t *cp;
 *    cp = clixon_plugin_find(h, "plugin-name");
 * @endcode
 */
clixon_plugin_t *
clixon_plugin_find(clicon_handle h,
                   const char   *name)
{
    clixon_plugin_t      *cp = NULL;
    plugin_module_struct *ms = plugin_module_struct_get(h);

    if (ms == NULL){
        clicon_err(OE_PLUGIN, EINVAL, "plugin module not initialized");
        return NULL;
    }
    if ((cp = ms->ms_plugin_list) != NULL){
        do {
            if (strcmp(cp->cp_name, name) == 0)
                return cp;
            cp = NEXTQ(clixon_plugin_t *, cp);
        } while (cp && cp != ms->ms_plugin_list);
    }
    return NULL;
}

/*! Load a dynamic plugin object and call its init-function
 *
 * @param[in]  h        Clicon handle
 * @param[in]  file     Which plugin to load
 * @param[in]  function Which function symbol to load and call
 * @param[in]  dlflags  See man(3) dlopen
 * @param[out] cpp      Clixon plugin structure (if retval is 1)
 * @retval     1        OK
 * @retval     0        Failed load, log, skip and continue with other plugins
 * @retval    -1        Error
 * @see clixon_plugins_load  Load all plugins
 */
static int
plugin_load_one(clicon_handle     h, 
                char             *file, /* note modified */
                const char       *function,
                int               dlflags,
                clixon_plugin_t **cpp)
{
    int                retval = -1;
    char              *error;
    void              *handle = NULL;
    plginit2_t        *initfn;
    clixon_plugin_api *api = NULL;
    clixon_plugin_t   *cp = NULL;
    char              *name;
    char              *p;
    void              *wh = NULL;

    clicon_debug(1, "%s file:%s function:%s", __FUNCTION__, file, function);
    dlerror();    /* Clear any existing error */
    if ((handle = dlopen(file, dlflags)) == NULL) {
        error = (char*)dlerror();
        clicon_err(OE_PLUGIN, errno, "dlopen(%s): %s", file, error ? error : "Unknown error");
        goto done;
    }
    /* call plugin_init() if defined, eg CLIXON_PLUGIN_INIT or CLIXON_BACKEND_INIT */
    if ((initfn = dlsym(handle, function)) == NULL){
        clicon_err(OE_PLUGIN, errno, "Failed to find %s when loading clixon plugin %s", CLIXON_PLUGIN_INIT, file);
        goto done;
    }
    if ((error = (char*)dlerror()) != NULL) {
        clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error);
        goto done;
    }
    clicon_err_reset();
    wh = NULL;
    if (plugin_context_check(h, &wh, file, __FUNCTION__) < 0)
        goto done;
    if ((api = initfn(h)) == NULL) {
        if (!clicon_errno){     /* if clicon_err() is not called then log and continue */
            clicon_log(LOG_DEBUG, "Warning: failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
            retval = 0;
            goto done;
        }
        else{
            clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
            goto done;
        }
    }
    if (plugin_context_check(h, &wh, file, __FUNCTION__) < 0)
        goto done;

    /* Note: sizeof clixon_plugin_api which is largest of clixon_plugin_api:s */
    if ((cp = (clixon_plugin_t *)malloc(sizeof(struct clixon_plugin))) == NULL){
        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 */
    snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s",
             (int)strlen(name), name);
    cp->cp_api = *api;
    if (cp){
        *cpp = cp;
        cp = NULL;
    }
    retval = 1;
 done:
    clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
    if (wh != NULL)
        free(wh);
    if (retval != 1 && handle)
        dlclose(handle);
    if (cp)
        free(cp);
    return retval;
}

/*! Load a set of plugin objects from a directory and and call their init-function
 * @param[in]  h        Clicon handle
 * @param[in]  function Which function symbol to load and call (eg CLIXON_PLUGIN_INIT)
 * @param[in]  dir      Directory. .so files in this dir will be loaded.
 * @param[in]  regexp   Regexp for matching files in plugin directory. Default *.so.
 * @retval     0        OK
 * @retval     -1       Error
 */
int
clixon_plugins_load(clicon_handle h,
                    const char   *function,
                    const char   *dir,
                    const char   *regexp)
{
    int                   retval = -1;
    int                   ndp;
    struct dirent        *dp = NULL;
    int                   i;
    char                  filename[MAXPATHLEN];
    clixon_plugin_t      *cp = NULL;
    int                   ret;
    plugin_module_struct *ms = plugin_module_struct_get(h);
    int                   dlflags;
        
    clicon_debug(CLIXON_DBG_DETAIL, "%s", __FUNCTION__); 
    if (ms == NULL){
        clicon_err(OE_PLUGIN, EINVAL, "plugin module not initialized");
        goto done;
    }
    /* Get plugin objects names from plugin directory */
    if((ndp = clicon_file_dirent(dir, &dp, regexp?regexp:"(.so)$", S_IFREG)) < 0)
        goto done;
    /* Load all plugins */
    for (i = 0; i < ndp; i++) {
        snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);
        clicon_debug(CLIXON_DBG_DEFAULT, "Loading plugin '%s'", filename);
        dlflags = RTLD_NOW;
        if (clicon_option_bool(h, "CLICON_PLUGIN_DLOPEN_GLOBAL"))
            dlflags |= RTLD_GLOBAL;
        else
            dlflags |= RTLD_LOCAL;
        if ((ret = plugin_load_one(h, filename, function, dlflags, &cp)) < 0)
            goto done;
        if (ret == 0)
            continue;
        ADDQ(cp, ms->ms_plugin_list);
    }
    retval = 0;
done:
    if (dp)
        free(dp);
    return retval;
}

/*! Create a pseudo plugin so that a main function can register callbacks
 * @param[in]  h     Clicon handle
 * @param[in]  name  Plugin name
 * @param[out] cpp   Clixon plugin structure (direct pointer)
 * @retval     0     OK, with cpp set
 * @retval    -1     Error
 * @code
 *   clixon_plugin_t *cp = NULL;
 *   if (clixon_pseudo_plugin(h, "pseudo plugin", &cp) < 0)
 *     err;
 *   cp->cp_api.ca_extension = my_ext_cb;
 * @endcode
 */
int
clixon_pseudo_plugin(clicon_handle     h,
                     const char       *name,
                     clixon_plugin_t **cpp)
{
    int              retval = -1;
    clixon_plugin_t *cp = NULL;
    plugin_module_struct *ms = plugin_module_struct_get(h);

    clicon_debug(1, "%s %s", __FUNCTION__, name); 
    if (ms == NULL){
        clicon_err(OE_PLUGIN, EINVAL, "plugin module not initialized");
        goto done;
    }
    /* Create a pseudo plugins */
    /* Note: sizeof clixon_plugin_api which is largest of clixon_plugin_api:s */
    if ((cp = (clixon_plugin_t *)malloc(sizeof(struct clixon_plugin))) == NULL){
        clicon_err(OE_UNIX, errno, "malloc");
        goto done;
    }
    memset(cp, 0, sizeof(struct clixon_plugin));
    snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s", (int)strlen(name), name);
    ADDQ(cp, ms->ms_plugin_list);
    *cpp = cp;
    cp = NULL;
    retval = 0;
done:
    if (cp)
        free(cp);
    return retval;
}

/*! Get system context, eg signal procmask (for blocking) and sigactions
 * Call this before a plugin
 * @retval     pc      Plugin context structure, use free() to deallocate
 * @retval     NULL    Error
 * @see plugin_context_check
 * */
static void *
plugin_context_get(void)
{
    int                    i;
    struct plugin_context *pc = NULL;

    if ((pc = malloc(sizeof(*pc))) == NULL){
        clicon_err(OE_UNIX, errno, "malloc");
        goto done;
    }
    memset(pc, 0, sizeof(*pc));
    if (sigprocmask(0, NULL, &pc->pc_sigset) < 0){
        clicon_err(OE_UNIX, errno, "sigprocmask");
        goto done;
    }
    for (i=1; i<32; i++){
        if (sigaction(i, NULL, &pc->pc_sigaction_vec[i]) < 0){
            clicon_err(OE_UNIX, errno, "sigaction");
            goto done;
        }
        /* Mask SA_RESTORER: Not intended for application use.
         * Note that it may not be included in user space so may be hardcoded below
         */
#ifdef SA_RESTORER
        pc->pc_sigaction_vec[i].sa_flags &= ~SA_RESTORER;
#else
        pc->pc_sigaction_vec[i].sa_flags &= ~0x04000000;
#endif
    }
    if (isatty(0) && tcgetattr(0, &pc->pc_termios) < 0){
        clicon_err(OE_UNIX, errno, "tcgetattr %d", errno);
        goto done;
    }
    return pc;
 done:
    if (pc)
        free(pc);
    return NULL;
}

/*! Given an existing, old plugin context, check if anything has changed
 *
 * Called twice:
 * 1) Make a check of resources
 * 2) Make a new check and compare with the old check, return 1 on success, 0 on fail
 * Log if there is a difference at loglevel WARNING.
 * Controlled by CLICON_PLUGIN_CALLBACK_CHECK:
 * 0 : No checks
 * 1 : warning logs on failure
 * 2 : log and abort on failure
 *
 * @param[in]     h      Clixon handle
 * @param[in,out] wh     Either: NULL for init, will be assigned, OR previous handle (will be freed)
 * @param[in]     name   Name of plugin for logging. Can be other name, context dependent
 * @param[in]     fn     Typically name of callback, or caller function
 * @retval        1      OK
 * @retval        0      Fail, log on syslog using LOG_WARNING
 * @retval       -1      Error
 * @note Only logs error, does not generate error
 * @note name and fn are context dependent, since the env of callback calls are very different
 * @see plugin_context_get
 * @see CLICON_PLUGIN_CALLBACK_CHECK  Enable to activate these checks
 */
int
plugin_context_check(clicon_handle h,
                     void        **wh,
                     const char   *name,
                     const char   *fn)
{
    int                    retval = -1;
    int                    failed = 0;
    int                    i;
    struct plugin_context *oldpc;
    struct plugin_context *newpc = NULL;
    int                    option;

    if (h == NULL){
        errno = EINVAL;
        return -1;
    }
    option = clicon_option_int(h, "CLICON_PLUGIN_CALLBACK_CHECK");
    /* Check if plugion checks are enabled */
    if (option == 0)
        return 1;
    if (wh == NULL){
        errno = EINVAL;
        return -1;
    }
    if (*wh == NULL){
        *wh = plugin_context_get();
        return 1;
    }
    oldpc = (struct plugin_context *)*wh;
    if ((newpc = plugin_context_get()) == NULL)
        goto done;
    if (oldpc->pc_termios.c_iflag != newpc->pc_termios.c_iflag){
        clicon_log(LOG_WARNING, "%s Plugin context %s %s: Changed termios input modes from 0x%x to 0x%x", __FUNCTION__,
                   name, fn,
                   oldpc->pc_termios.c_iflag,
                   newpc->pc_termios.c_iflag);
        failed++;
    }
    if (oldpc->pc_termios.c_oflag != newpc->pc_termios.c_oflag){
        clicon_log(LOG_WARNING, "%s Plugin context %s %s: Changed termios output modes from 0x%x to 0x%x", __FUNCTION__,
                   name, fn,
                   oldpc->pc_termios.c_oflag,
                   newpc->pc_termios.c_oflag);
        failed++;
    }
    if (oldpc->pc_termios.c_cflag != newpc->pc_termios.c_cflag){
        clicon_log(LOG_WARNING, "%s Plugin context %s %s: Changed termios control modes from 0x%x to 0x%x", __FUNCTION__,
                   name, fn,
                   oldpc->pc_termios.c_cflag,
                   newpc->pc_termios.c_cflag);
        failed++;
    }
    if (oldpc->pc_termios.c_lflag != newpc->pc_termios.c_lflag){
        clicon_log(LOG_WARNING, "%s Plugin context %s %s: Changed termios local modes from 0x%x to 0x%x", __FUNCTION__,
                   name, fn,
                   oldpc->pc_termios.c_lflag,
                   newpc->pc_termios.c_lflag);
        failed++;
    }
    /* XXX pc_termios.cc_t  c_cc[NCCS] not checked */
    /* Abort if option is 2 or above on failure
     */
    if (option > 1 && failed)
        abort();
    for (i=1; i<32; i++){
        if (sigismember(&oldpc->pc_sigset, i) != sigismember(&newpc->pc_sigset, i)){
            clicon_log(LOG_WARNING, "%s Plugin context %s %s: Changed blocking of signal %s(%d) from %d to %d", __FUNCTION__,
                       name, fn, strsignal(i), i,
                       sigismember(&oldpc->pc_sigset, i),
                       sigismember(&newpc->pc_sigset, i)
                       );
            failed++;
        }
        if (oldpc->pc_sigaction_vec[i].sa_flags != newpc->pc_sigaction_vec[i].sa_flags){
            clicon_log(LOG_WARNING, "%s Plugin context %s %s: Changed flags of signal %s(%d) from 0x%x to 0x%x", __FUNCTION__,
                       name, fn, strsignal(i), i,
                       oldpc->pc_sigaction_vec[i].sa_flags,
                       newpc->pc_sigaction_vec[i].sa_flags);;
            failed++;
        }
        if (oldpc->pc_sigaction_vec[i].sa_sigaction != newpc->pc_sigaction_vec[i].sa_sigaction){
            clicon_log(LOG_WARNING, "%s Plugin context %s %s: Changed action of signal %s(%d) from %p to %p", __FUNCTION__,
                       name, fn, strsignal(i), i,
                       oldpc->pc_sigaction_vec[i].sa_sigaction,
                       newpc->pc_sigaction_vec[i].sa_sigaction);
            failed++;
        }
        /* Abort if option is 2 or above on failure
         */
        if (option > 1 && failed)
            abort();
    }
    if (failed)
        goto fail;
    retval = 1; /* OK */
 done:
    if (newpc)
        free(newpc);
    if (oldpc)
        free(oldpc);
    if (wh && *wh)
        *wh = NULL;
    return retval;
 fail:
    retval = 0;
    goto done;
}

/*! Call single plugin start callback
 * @param[in]  cp      Plugin handle
 * @param[in]  h       Clixon handle
 * @retval     0       OK
 * @retval    -1       Error
 */
int
clixon_plugin_start_one(clixon_plugin_t *cp,
                        clicon_handle  h)
{
    int         retval = -1;
    plgstart_t *fn;          /* Plugin start */
    void       *wh = NULL;

    if ((fn = cp->cp_api.ca_start) != NULL){
        wh = NULL;
        if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0)
            goto done;
        if (fn(h) < 0) {
            if (clicon_errno < 0) 
                clicon_log(LOG_WARNING, "%s: Internal error: Start callback in plugin: %s returned -1 but did not make a clicon_err call",
                           __FUNCTION__, cp->cp_name);
            goto done;
        }
        if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0)
            goto done;
    }
    retval = 0;
 done:
    return retval;
}

/*! Call plugin_start in all plugins
 * @param[in]  h       Clixon handle
 * Call plugin start functions (if defined)
 * @note  Start functions can use clicon_argv_get() to get -- command line options
 */
int
clixon_plugin_start_all(clicon_handle h)
{
    int            retval = -1;
    clixon_plugin_t *cp = NULL;

    while ((cp = clixon_plugin_each(h, cp)) != NULL) {
        if (clixon_plugin_start_one(cp, h) < 0)
            goto done;
    }
    retval = 0;
 done:
    return retval;
}

/*! Unload all plugins: call exit function and close shared handle
 * @param[in]  h       Clicon handle
 * @param[in]  cp      Plugin handle
 * @param[in]  h       Clixon handle
 * @retval     0       OK
 * @retval    -1       Error
 */
static int
clixon_plugin_exit_one(clixon_plugin_t *cp,
                       clicon_handle  h)
{
    int         retval = -1;
    char       *error;
    plgexit_t  *fn;
    void       *wh = NULL;
        
    if ((fn = cp->cp_api.ca_exit) != NULL){
        wh = NULL;
        if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0)
            goto done;
        if (fn(h) < 0) {
            if (clicon_errno < 0) 
                clicon_log(LOG_WARNING, "%s: Internal error: Exit callback in plugin: %s returned -1 but did not make a clicon_err call",
                           __FUNCTION__, cp->cp_name);
            goto done;
        }
        if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0)
            goto done;
        if (dlclose(cp->cp_handle) != 0) {
            error = (char*)dlerror();
            clicon_err(OE_PLUGIN, errno, "dlclose: %s", error ? error : "Unknown error");
        }
    }
    retval = 0;
 done:
    return retval;
}

/*! Unload all plugins: call exit function and close shared handle
 * @param[in]  h       Clixon handle
 * @retval     0       OK
 * @retval    -1       Error
 */
static int
clixon_plugin_exit_all(clicon_handle h)
{
    int            retval = -1;
    clixon_plugin_t *cp = NULL;
    plugin_module_struct *ms = plugin_module_struct_get(h);

    if (ms != NULL){
        while ((cp = ms->ms_plugin_list) != NULL){
            DELQ(cp, ms->ms_plugin_list, clixon_plugin_t *);
            if (clixon_plugin_exit_one(cp, h) < 0)
                goto done;
            free(cp);
        }
    }
    retval = 0;
 done:
    return retval;
}

/*! Run the restconf user-defined credentials callback 
 * @param[in]  h         Clicon handle
 * @param[in]  req       Per-message request www handle to use with restconf_api.h
 * @param[in]  auth_type Authentication type: none, user-defined, or client-cert
 * @param[out] authp     NULL: Credentials failed, no user set (401 returned). 
 *                       String: Credentials OK, the associated user, must be mallloc:ed
 *                       Parameter signtificant only if retval is 1/OK
 * @retval     1         OK, see authp parameter on result.
 * @retval     0         Ignore, undecided, not handled, same as no callback
 * @retval    -1         Fatal error
 * @note If authenticated either a callback was called and clicon_username_set() 
 *       Or no callback was found.
 */
static int
clixon_plugin_auth_one(clixon_plugin_t     *cp,
                       clicon_handle      h, 
                       void              *req,
                       clixon_auth_type_t auth_type,
                       char             **authp)
{
    int        retval = -1; 
    plgauth_t *fn;          /* Plugin auth */
    void      *wh = NULL;

    clicon_debug(1, "%s", __FUNCTION__);
    if ((fn = cp->cp_api.ca_auth) != NULL){
        wh = NULL;
        if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0)
            goto done;
        if ((retval = fn(h, req, auth_type, authp)) < 0) {
            if (clicon_errno < 0) 
                clicon_log(LOG_WARNING, "%s: Internal error: Auth callback in plugin: %s returned -1 but did not make a clicon_err call",
                           __FUNCTION__, cp->cp_name);
            goto done;
        }
        if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0)
            goto done;
    }
    else
        retval = 0; /* Ignored / no callback */
 done:
    clicon_debug(1, "%s retval:%d auth:%s", __FUNCTION__, retval, *authp);
    return retval;
}

/*! Run the restconf user-defined credentials callback for all plugins
 * Find first authentication callback and call that, then return.
 * The callback is to set the authenticated user
 * @param[in]  h         Clicon handle
 * @param[in]  req       Per-message request www handle to use with restconf_api.h
 * @param[in]  auth_type Authentication type: none, user-defined, or client-cert
 * @param[out] authp     NULL: Credentials failed, no user set (401 returned). 
 *                       String: Credentials OK, the associated user, must be mallloc:ed
 *                       Parameter signtificant only if retval is 1/OK
 * @retval     1         OK, see authp parameter for result.
 * @retval     0         Ignore, undecided, not handled, same as no callback
 * @retval    -1         Fatal error
 * @note If authp returns string, it should be malloced
 */
int
clixon_plugin_auth_all(clicon_handle      h, 
                       void              *req,
                       clixon_auth_type_t auth_type,
                       char             **authp)
{
    int              retval = -1;
    clixon_plugin_t *cp = NULL;
    int              ret = 0; 
    
    clicon_debug(1, "%s", __FUNCTION__);
    if (authp == NULL){
        clicon_err(OE_PLUGIN, EINVAL, "Authp output parameter is NULL");
        goto done;
    }    
    *authp = NULL;
    ret = 0; /* ignore */
    while ((cp = clixon_plugin_each(h, cp)) != NULL) {
        if ((ret = clixon_plugin_auth_one(cp, h, req, auth_type, authp)) < 0)
            goto done;
        if (ret == 1)
            break; /* result, not ignored */
        /* ret == 0, ignore try next */
    }
    retval = ret;
 done:
    clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
    return retval;
}

/*! Callback for a yang extension (unknown) statement single plugin
 * extension can be made.
 * @param[in] cp   Plugin handle
 * @param[in] h    Clixon handle
 * @param[in] yext Yang node of extension 
 * @param[in] ys   Yang node of (unknown) statement belonging to extension
 * @retval    0    OK, 
 * @retval   -1    Error 
 */
int
clixon_plugin_extension_one(clixon_plugin_t *cp,
                            clicon_handle  h, 
                            yang_stmt     *yext,
                            yang_stmt     *ys)
{
    int             retval = 1;
    plgextension_t *fn;          /* Plugin extension fn */
    void           *wh = NULL;
    
    if ((fn = cp->cp_api.ca_extension) != NULL){
        wh = NULL;
        if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0)
            goto done;
        if (fn(h, yext, ys) < 0) {
            if (clicon_errno < 0) 
                clicon_log(LOG_WARNING, "%s: Internal error: Extension callback in plugin: %s returned -1 but did not make a clicon_err call",
                           __FUNCTION__, cp->cp_name);
            goto done;
        }
        if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0)
            goto done;
    }
    retval = 0;
 done:
    return retval;
}

/*! Callback for a yang extension (unknown) statement in all plugins
 * Called at parsing of yang module containing a statement of an extension.
 * A plugin may identify the extension and perform actions
 * on the yang statement, such as transforming the yang.
 * A callback is made for every statement, which means that several calls per
 * extension can be made.
 * @param[in] h    Clixon handle
 * @param[in] yext Yang node of extension 
 * @param[in] ys   Yang node of (unknown) statement belonging to extension
 * @retval     0   OK, all callbacks executed OK
 * @retval    -1   Error in one callback
 */
int
clixon_plugin_extension_all(clicon_handle h,
                            yang_stmt    *yext,
                            yang_stmt    *ys)
{
    int            retval = -1;
    clixon_plugin_t *cp = NULL;
    
    while ((cp = clixon_plugin_each(h, cp)) != NULL) {
        if (clixon_plugin_extension_one(cp, h, yext, ys) < 0)
            goto done;
    }
    retval = 0;
 done:
    return retval;
}

/*! Call plugin general-purpose datastore upgrade in one plugin
 *
 * @param[in] cp   Plugin handle
 * @param[in] h    Clicon handle
 * @param[in] db   Name of datastore, eg "running", "startup" or "tmp"
 * @param[in] xt   XML tree. Upgrade this "in place"
 * @param[in] msd  Module-state diff, info on datastore module-state
 * @retval   -1    Error
 * @retval    0    OK
 * Upgrade datastore on load before or as an alternative to module-specific upgrading mechanism
 */
int
clixon_plugin_datastore_upgrade_one(clixon_plugin_t   *cp,
                                    clicon_handle    h,
                                    const char      *db,
                                    cxobj           *xt,
                                    modstate_diff_t *msd)

{
    int                  retval = -1;
    datastore_upgrade_t *fn;
    void                *wh = NULL;
    
    if ((fn = cp->cp_api.ca_datastore_upgrade) != NULL){
        wh = NULL;
        if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0)
            goto done;
        if (fn(h, db, xt, msd) < 0) {
            if (clicon_errno < 0) 
                clicon_log(LOG_WARNING, "%s: Internal error: Datastore upgrade callback in plugin: %s returned -1 but did not make a clicon_err call",
                           __FUNCTION__, cp->cp_name);
            goto done;
        }
        if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0)
            goto done;
    }
    retval = 0;
 done:
    return retval;
}

/*! Call plugin general-purpose datastore upgrade in all plugins
 *
 * @param[in] h    Clicon handle
 * @param[in] db   Name of datastore, eg "running", "startup" or "tmp"
 * @param[in] xt   XML tree. Upgrade this "in place"
 * @param[in] msd  Module-state diff, info on datastore module-state
 * @retval    0    OK
 * @retval   -1    Error
 * Upgrade datastore on load before or as an alternative to module-specific upgrading mechanism
 */
int
clixon_plugin_datastore_upgrade_all(clicon_handle    h,
                                    const char      *db,
                                    cxobj           *xt,
                                    modstate_diff_t *msd)
{
    int            retval = -1;
    clixon_plugin_t *cp = NULL;
    
    while ((cp = clixon_plugin_each(h, cp)) != NULL) {
        if (clixon_plugin_datastore_upgrade_one(cp, h, db, xt, msd) < 0)
            goto done;
    }
    retval = 0;
 done:
    return retval;
}

/*! Call plugin YANG schema mount
 *

 * (No need to be yang bound?
 * @param[in]  cp      Plugin handle
 * @param[in]  h       Clixon handle
 * @param[in]  xt      XML mount-point in XML tree
 * @param[out] config  If '0' all data nodes in the mounted schema are read-only
 * @param[out] validate Do or dont do full RFC 7950 validation
 * @param[out] yanglib XML yang-lib module-set tree
 * @retval     0       OK
 * @retval    -1       Error
 */
int
clixon_plugin_yang_mount_one(clixon_plugin_t *cp,
                             clicon_handle    h,
                             cxobj           *xt,
                             int             *config,
                             validate_level  *vl,
                             cxobj          **yanglib)
{
    int           retval = -1;
    yang_mount_t *fn;
    void         *wh = NULL;
    
    if ((fn = cp->cp_api.ca_yang_mount) != NULL){
        wh = NULL;
        if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0)
            goto done;
        if (fn(h, xt, config, vl, yanglib) < 0) {
            if (clicon_errno < 0) 
                clicon_log(LOG_WARNING, "%s: Internal error: Yang mount callback in plugin: %s returned -1 but did not make a clicon_err call",
                           __FUNCTION__, cp->cp_name);
            goto done;
        }
        if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0)
            goto done;
    }
    retval = 0;
 done:
    return retval;
}

/*! Call plugin YANG schema mount in all plugins, break at first return
 *
 * @param[in]  h       Clixon handle
 * @param[in]  xt      XML mount-point in XML tree
 * @param[out] config  If '0' all data nodes in the mounted schema are read-only
 * @param[out] validate Do or dont do full RFC 7950 validation
 * @param[out] yanglib XML yang-lib module-set tree (freed by caller)
 * @retval     0       OK
 * @retval    -1       Error
 */
int
clixon_plugin_yang_mount_all(clicon_handle   h,
                             cxobj          *xt,
                             int            *config,
                             validate_level *vl,
                             cxobj         **yanglib)
{
    int            retval = -1;
    clixon_plugin_t *cp = NULL;
    
    while ((cp = clixon_plugin_each(h, cp)) != NULL) {
        if (clixon_plugin_yang_mount_one(cp, h, xt, config, vl, yanglib) < 0)
            goto done;
        /* Break if find yanglib */
        if (yanglib && *yanglib)
            break;
    }
    retval = 0;
 done:
    return retval;
}

/*! Call plugin YANG schema patch
 *

 * @param[in]  cp      Plugin handle
 * @param[in]  h       Clixon handle
 * @param[in]  ymod    YANG module
 * @retval     0       OK
 * @retval    -1       Error
 */
int
clixon_plugin_yang_patch_one(clixon_plugin_t *cp,
                             clicon_handle    h,
                             yang_stmt       *ymod)
{
    int           retval = -1;
    yang_patch_t *fn;
    void         *wh = NULL;
    
    if ((fn = cp->cp_api.ca_yang_patch) != NULL){
        wh = NULL;
        if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0)
            goto done;
        if (fn(h, ymod) < 0) {
            if (clicon_errno < 0) 
                clicon_log(LOG_WARNING, "%s: Internal error: Yang patch callback in plugin: %s returned -1 but did not make a clicon_err call",
                           __FUNCTION__, cp->cp_name);
            goto done;
        }
        if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0)
            goto done;
    }
    retval = 0;
 done:
    return retval;
}

/*! Call plugin YANG schema patch in all plugins.
 *
 * @param[in]  h       Clixon handle
 * @param[in]  ymod    YANG module
 * @retval     0       OK
 * @retval    -1       Error
 */
int
clixon_plugin_yang_patch_all(clicon_handle h,
                             yang_stmt    *ymod)
{
    int            retval = -1;
    clixon_plugin_t *cp = NULL;
    
    while ((cp = clixon_plugin_each(h, cp)) != NULL) {
        if (clixon_plugin_yang_patch_one(cp, h, ymod) < 0)
            goto done;
    }
    retval = 0;
 done:
    return retval;
}

/*--------------------------------------------------------------------
 * RPC callbacks for both client/frontend and backend plugins.
 */

#if 0 /* Debugging */
static int
rpc_callback_dump(clicon_handle h)
{
    rpc_callback_t *rc;
    plugin_module_struct *ms = plugin_module_struct_get(h);

    clicon_debug(1, "%s--------------", __FUNCTION__);
    if ((rc = ms->ms_rpc_callbacks) != NULL)
        do {
            clicon_debug(1, "%s %s", __FUNCTION__, rc->rc_name);
            rc = NEXTQ(rpc_callback_t *, rc);
        } while (rc != ms->ms_rpc_callbacks);
    return 0;
}
#endif

/*! Register a RPC callback by appending a new RPC to a global list
 *
 * @param[in]  h         clicon handle
 * @param[in]  cb        Callback called 
 * @param[in]  arg       Domain-specific argument to send to callback 
 * @param[in]  ns        namespace of rpc
 * @param[in]  name      RPC name
 * @retval     0         OK
 * @retval    -1         Error
 * @see rpc_callback_call  which makes the actual callback
 */
int
rpc_callback_register(clicon_handle  h,
                      clicon_rpc_cb  cb,
                      void          *arg,       
                      const char    *ns,
                      const char    *name)
{
    rpc_callback_t *rc = NULL;
    plugin_module_struct *ms = plugin_module_struct_get(h);

    clicon_debug(1, "%s %s", __FUNCTION__, name);
    if (ms == NULL){
        clicon_err(OE_PLUGIN, EINVAL, "plugin module not initialized");
        goto done;
    }
    if (name == NULL || ns == NULL){
        clicon_err(OE_DB, EINVAL, "name or namespace NULL");
        goto done;
    }
    if ((rc = malloc(sizeof(rpc_callback_t))) == NULL) {
        clicon_err(OE_DB, errno, "malloc");
        goto done;
    }
    memset(rc, 0, sizeof(*rc));
    rc->rc_callback = cb;
    rc->rc_arg  = arg;
    rc->rc_namespace  = strdup(ns);
    rc->rc_name  = strdup(name);
    ADDQ(rc, ms->ms_rpc_callbacks);
    return 0;
 done:
    if (rc){
        if (rc->rc_namespace)
            free(rc->rc_namespace);
        if (rc->rc_name)
            free(rc->rc_name);
        free(rc);
    }
    return -1;
}

/*! Delete all RPC callbacks
 */
static int
rpc_callback_delete_all(clicon_handle h)
{
    rpc_callback_t *rc;
    plugin_module_struct *ms = plugin_module_struct_get(h);

    if (ms != NULL)
        while((rc = ms->ms_rpc_callbacks) != NULL) {
            DELQ(rc, ms->ms_rpc_callbacks, rpc_callback_t *);
            if (rc->rc_namespace)
                free(rc->rc_namespace);
            if (rc->rc_name)
                free(rc->rc_name);
            free(rc);
        }
    return 0;
}

/*! Search RPC callbacks and invoke if XML match with tag
 *
 * @param[in]   h       clicon handle
 * @param[in]   xn      Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>.
 * @param[in]   arg     Domain-speific arg (eg client_entry)
 * @param[out]  nrp     Number of callbacks handled: 0, 1, n (retval = 1) or NULL
 * @param[out]  cbret   Return XML (as string in CLIgen buffer), error or OK
 * @retval      1       OK, see nr
 * @retval      0       Failed, error return in cbret
 * @retval     -1       Error
 * @see rpc_callback_register  which register a callback function
 * @note that several callbacks can be registered. They need to cooperate on
 * return values, ie if one writes cbret, the other needs to handle that by
 * leaving it, replacing it or amending it.
 */
int
rpc_callback_call(clicon_handle h,
                  cxobj        *xe, 
                  void         *arg,
                  int          *nrp,
                  cbuf         *cbret)
{
    int                   retval = -1;
    rpc_callback_t       *rc;
    char                 *name;
    char                 *prefix;
    char                 *ns;
    int                   nr = 0; /* How many callbacks */
    plugin_module_struct *ms = plugin_module_struct_get(h);
    void                 *wh;
    int                   ret;

    if (ms == NULL){
        clicon_err(OE_PLUGIN, EINVAL, "plugin module not initialized");
        goto done;
    }
    name = xml_name(xe);
    prefix = xml_prefix(xe);
    xml2ns(xe, prefix, &ns);
    if ((rc = ms->ms_rpc_callbacks) != NULL)
        do {
            if (strcmp(rc->rc_name, name) == 0 &&
                ns && rc->rc_namespace &&
                strcmp(rc->rc_namespace, ns) == 0){
                wh = NULL;
                if (plugin_context_check(h, &wh, rc->rc_name, __FUNCTION__) < 0)
                    goto done;
                if (rc->rc_callback(h, xe, cbret, arg, rc->rc_arg) < 0){
                    clicon_debug(1, "%s Error in: %s", __FUNCTION__, rc->rc_name);
                    if (plugin_context_check(h, &wh, rc->rc_name, __FUNCTION__) < 0)
                        goto done;
                    goto done;
                }
                nr++;
                if (plugin_context_check(h, &wh, rc->rc_name, __FUNCTION__) < 0)
                    goto done;
            }
            rc = NEXTQ(rpc_callback_t *, rc);
        } while (rc != ms->ms_rpc_callbacks);
    /* action reply checked in action_callback_call */
    if (nr && !xml_rpc_isaction(xe)){
        if ((ret = rpc_reply_check(h, name, cbret)) < 0)
            goto done;
        if (ret == 0)
            goto fail;
    }
    if (nrp)
        *nrp = nr;
    retval = 1; /* 0: none found, >0 nr of handlers called */
 done:
    clicon_debug(CLIXON_DBG_DETAIL, "%s retval:%d", __FUNCTION__, retval);
    return retval;
 fail:
    retval = 0;
    goto done;
}

/*--------------------------------------------------------------------
 * Action callback API. Reuse many of the same data structures as RPC, but
 * context is given by a yang node
 */

/*! Register a RPC action callback by appending a new RPC to a yang action node
 * @param[in]  h     clicon handle
 * @param[in]  ys    YANG node where action resides
 * @param[in]  cb    Callback
 * @param[in]  arg   Domain-specific argument to send to callback 
 * @retval     0     OK
 * @retval    -1     Error
 * @see rpc_callback_register  which registers global callbacks
 */
int
action_callback_register(clicon_handle  h,
                         yang_stmt     *ya,
                         clicon_rpc_cb  cb,
                         void          *arg)
{
    int             retval = -1;
    rpc_callback_t *rc = NULL;
    char           *name;

    clicon_debug(1, "%s", __FUNCTION__);
    if (ya == NULL){
        clicon_err(OE_DB, EINVAL, "yang node is NULL");
        goto done;
    }
    name = yang_argument_get(ya);
    if ((rc = malloc(sizeof(rpc_callback_t))) == NULL) {
        clicon_err(OE_DB, errno, "malloc");
        goto done;
    }
    memset(rc, 0, sizeof(*rc));
    rc->rc_callback = cb;
    rc->rc_arg  = arg;
    rc->rc_namespace  = strdup(YANG_XML_NAMESPACE); // XXX
    rc->rc_name  = strdup(name);
    if (yang_action_cb_add(ya, rc) < 0)
        goto done;
    retval = 0;
 done:
    return retval;
}

/*! Search Action callbacks and invoke if XML match with tag
 *
 * @param[in]   h       clicon handle
 * @param[in]   xn      Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>.
 * @param[in]   arg     Domain-speific arg (eg client_entry)
 * @param[out]  nrp     Number of callbacks handled: 0, 1, n (retval = 1) or NULL
 * @param[out]  cbret   Return XML (as string in CLIgen buffer), error or OK
 * @retval      1       OK, see nr
 * @retval      0       Failed, error return in cbret
 * @retval     -1       Error
 * @see rpc_callback_register  which register a callback function
 * @note that several callbacks can be registered. They need to cooperate on
 * return values, ie if one writes cbret, the other needs to handle that by
 * leaving it, replacing it or amending it.
 */
int
action_callback_call(clicon_handle h,
                     cxobj        *xe, 
                     cbuf         *cbret,
                     void         *arg,
                     void         *regarg)
{
    int             retval = -1;
    cxobj          *xa = NULL;
    yang_stmt      *ya = NULL;
    char           *name;
    int             nr = 0; /* How many callbacks */
    void           *wh = NULL;
    rpc_callback_t *rc;
    
    clicon_debug(1, "%s", __FUNCTION__);
    if (xml_find_action(xe, 1, &xa) < 0)
        goto done;
    if (xa == NULL){
        if (netconf_operation_not_supported(cbret, "application", "Action not found") < 0)
            goto done;
        goto ok;                
    }
    if ((ya = xml_spec(xa)) == NULL){
        if (netconf_operation_not_supported(cbret, "application", "Action spec not found") < 0)
            goto done;
        goto ok;                
    }
    name = xml_name(xa);
    /* Action callback */
    if ((rc = (rpc_callback_t *)yang_action_cb_get(ya)) != NULL){
        do {
            if (strcmp(rc->rc_name, name) == 0){
                if (plugin_context_check(h, &wh, rc->rc_name, __FUNCTION__) < 0)
                    goto done;
                if (rc->rc_callback(h, xa, cbret, arg, rc->rc_arg) < 0){
                    clicon_debug(1, "%s Error in: %s", __FUNCTION__, rc->rc_name);
                    if (plugin_context_check(h, &wh, rc->rc_name, __FUNCTION__) < 0)
                        goto done;
                    goto done;
                }
                nr++;
                if (plugin_context_check(h, &wh, rc->rc_name, __FUNCTION__) < 0)
                    goto done;
            }
            rc = NEXTQ(rpc_callback_t *, rc);
        } while (rc != yang_action_cb_get(ya));
    }
    if (nr){
#ifdef NYI
        if ((ret = rpc_action_reply_check(h, xe, cbret)) < 0)
            goto done;
        if (ret == 0)
            goto fail;
#endif
    }
 ok:
    retval = 1;
 done:
    return retval;
}

/*--------------------------------------------------------------------
 * Upgrade callbacks for backend upgrade of datastore
 */

/*! Register an upgrade callback by appending the new callback to the list
 *
 * @param[in]  h         clicon handle
 * @param[in]  cb        Callback called 
 * @param[in]  fnstr     Stringified function for debug
 * @param[in]  arg       Domain-specific argument to send to callback 
 * @param[in]  ns        Module namespace (if NULL all modules)
 * @retval     0         OK
 * @retval    -1         Error
 * @see upgrade_callback_call  which makes the actual callback
 */
int
upgrade_callback_reg_fn(clicon_handle     h,
                        clicon_upgrade_cb cb,
                        const char       *fnstr,
                        const char       *ns,
                        void             *arg)
{
    upgrade_callback_t *uc = NULL;
    plugin_module_struct *ms = plugin_module_struct_get(h);

    if (ms == NULL){
        clicon_err(OE_PLUGIN, EINVAL, "plugin module not initialized");
        goto done;
    }
    if ((uc = malloc(sizeof(upgrade_callback_t))) == NULL) {
        clicon_err(OE_DB, errno, "malloc");
        goto done;
    }
    memset(uc, 0, sizeof(*uc));
    uc->uc_callback = cb;
    uc->uc_fnstr = fnstr;
    uc->uc_arg  = arg;
    if (ns)
        uc->uc_namespace  = strdup(ns);
    ADDQ(uc, ms->ms_upgrade_callbacks);
    return 0;
 done:
    if (uc){
        if (uc->uc_namespace)
            free(uc->uc_namespace);
        free(uc);
    }
    return -1;
}

/*! Delete all Upgrade callbacks
 */
static int
upgrade_callback_delete_all(clicon_handle h)
{
    upgrade_callback_t *uc;
    plugin_module_struct *ms = plugin_module_struct_get(h);

    if (ms != NULL)
        while((uc = ms->ms_upgrade_callbacks) != NULL) {
            DELQ(uc, ms->ms_upgrade_callbacks, upgrade_callback_t *);
            if (uc->uc_namespace)
                free(uc->uc_namespace);
            free(uc);
        }
    return 0;
}

/*! Upgrade specific module identified by namespace, search matching callbacks
 *
 * @param[in]  h       clicon handle
 * @param[in]  xt      Top-level XML tree to be updated (includes other ns as well)
 * @param[in]  ns      Namespace of module
 * @param[in]  op      One of XML_FLAG_ADD, _DEL or _CHANGE
 * @param[in]  from    From revision on the form YYYYMMDD (if DEL or CHANGE)
 * @param[in]  to      To revision on the form YYYYMMDD (if ADD or CHANGE)
 * @param[out] cbret   Return XML (as string in CLIgen buffer), on invalid
 * @retval  1  OK
 * @retval  0  Invalid - cbret contains reason as netconf
 * @retval -1  Error
 * @see upgrade_callback_reg_fn  which registers the callbacks
 */
int
upgrade_callback_call(clicon_handle h,
                      cxobj        *xt,
                      char         *ns,
                      uint16_t      op,
                      uint32_t      from,
                      uint32_t      to,
                      cbuf         *cbret)
{
    int                 retval = -1;
    upgrade_callback_t *uc;
    int                 nr = 0; /* How many callbacks */
    int                 ret;
    plugin_module_struct *ms = plugin_module_struct_get(h);

    if (ms == NULL){
        clicon_err(OE_PLUGIN, EINVAL, "plugin module not initialized");
        goto done;
    }
    if ((uc = ms->ms_upgrade_callbacks) != NULL)
        do {
            /* For matching an upgrade callback:
             * - No module name registered (matches all modules) OR
             * - Names match
             * AND
             * - No registered from revision (matches all revisions) OR
             *   - Registered from revision >= from AND
             *   - Registered to revision <= to (which includes case both 0)
             */
            if (uc->uc_namespace == NULL || strcmp(uc->uc_namespace, ns)==0){
                if ((ret = uc->uc_callback(h, xt, ns, op, from, to, uc->uc_arg, cbret)) < 0){
                    clicon_debug(1, "%s Error in: %s", __FUNCTION__, uc->uc_namespace);
                    goto done;
                }
                if (ret == 0){
                    if (cbuf_len(cbret)==0){    
                        clicon_err(OE_CFG, 0, "Validation fail %s(%s): cbret not set",
                                   uc->uc_fnstr, ns);
                        goto done;
                    }
                    goto fail;
                }
                nr++;
            }
            uc = NEXTQ(upgrade_callback_t *, uc);
        } while (uc != ms->ms_upgrade_callbacks);
    retval = 1;
 done:
    clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
    return retval;
 fail:
    retval =0;
    goto done;
}

/* Authentication type
 * @see http-auth-type in clixon-restconf.yang
 * @see restconf_media_str2int
 */
static const map_str2int clixon_auth_type[] = {
    {"none",               CLIXON_AUTH_NONE},
    {"client-certificate", CLIXON_AUTH_CLIENT_CERTIFICATE},
    {"user",               CLIXON_AUTH_USER},
    {NULL,                 -1}
};

/*! Translate from string to auth-type
 */
const int
clixon_auth_type_str2int(char *auth_type)
{
    return clicon_str2int(clixon_auth_type, auth_type);
}

/*! Translate from auth-type to string
 */
const char *
clixon_auth_type_int2str(clixon_auth_type_t auth_type)
{
    return clicon_int2str(clixon_auth_type, auth_type);
}

/*! Initialize plugin module by creating a handle holding plugin and callback lists
 * This should be called once at start by every application
 * @param[in]  h   Clixon handle
 * @see clixon_plugin_module_exit
 */
int
clixon_plugin_module_init(clicon_handle h)
{
    int                          retval = -1;
    struct plugin_module_struct *ph;

    if (plugin_module_struct_get(h) != NULL){
        clicon_err(OE_PLUGIN, EFAULT, "Already initialized");
        goto done;
    }
    if ((ph = malloc(sizeof(*ph))) == NULL){
        clicon_err(OE_UNIX, errno, "malloc");
        goto done;
    }
    memset(ph, 0, sizeof(*ph));
    if (plugin_module_struct_set(h, ph) < 0)
        goto done;
    retval = 0;
 done:
    return retval;
}

/*! Delete plugin module
 * This should be called once at exit by every application
 * @param[in]  h   Clixon handle
 */
int
clixon_plugin_module_exit(clicon_handle h)
{
    struct plugin_module_struct *ph;

    /* Delete all plugins */
    clixon_plugin_exit_all(h);
    /* Delete all RPC callbacks */
    rpc_callback_delete_all(h);
    /* Delete all backend plugin upgrade callbacks (only backend) */
    upgrade_callback_delete_all(h);
    /* Delete plugin_module itself */
    if ((ph = plugin_module_struct_get(h)) != NULL){
        free(ph);
        plugin_module_struct_set(h, NULL);
    }
    return 0;
}
