example-system.c

The following example code can be found in the libsystem/src directory.

/*
 * Copyright (c) 2012 - 2024, YumaWorks, Inc., All Rights Reserved.
 *
 * 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.
 */
/*  FILE: example-system.c

  Example External System Library

*********************************************************************
*                                                                   *
*                     I N C L U D E    F I L E S                    *
*                                                                   *
*********************************************************************/

#include <stdlib.h>
#include <xmlstring.h>

#include "procdefs.h"
#include "agt.h"
#include "agt_acm.h"
#include "agt_acm_ietf.h"
#include "agt_acm_extern.h"
#include "agt_util.h"
#include "agt_cfg.h"
#include "agt_cb.h"
#include "agt_commit_complete.h"
#include "agt_glob.h"
#include "agt_hook_util.h"
#include "dlq.h"
#include "example-system.h"
#include "getcb.h"
#include "log.h"
#include "log_vendor.h"
#include "log_vendor_extern.h"
#include "ncx.h"
#include "ncxconst.h"
#include "ncxmod.h"
#include "ncxtypes.h"
#include "obj.h"
#include "ses.h"
#include "status.h"
#include "typ_userdef.h"
#include "val.h"
#include "val_util.h"
#include "xml_util.h"


/********************************************************************
*                                                                   *
*                       C O N S T A N T S                           *
*                                                                   *
*********************************************************************/

/* this flag is used to enable the NACM external group examples */
// #define ADD_NACM_GROUP 1

/* this flag is used to enable the Session Hook callback examples */
// #define ADD_SESSION_HOOK 1

/* this flag is used to enable the canonical callback for an IPv6
 * address examples
 */
// #define ADD_IPV6_FN 1

/* this flag is used to enable the External ACM Hooks examples */
// #define ADD_ACM_EXTERN 1

/* this flag is used to enable Vendor log functions examples */
// #define ADD_VENDOR_LOG 1

/* this flag is used to enable External NV-Storage Handler Hooks examples */
// #define ADD_NV_STORE_HOOK 1

/* this flag is used to enable Transaction Management Hooks examples */
// #define ADD_TRANS_HOOK 1

/* this flag is used to enable Commit Completeness Hooks examples */
// #define ADD_CC_HOOK 1

/* this flag is used to enable Edit Phase Complete Callback examples */
// #define ADD_EPC_HOOK 1

/* this flag is used to enable YANG Object Template Callback examples */
// #define ADD_YANG_OBJ_TEMP 1

/* this flag is used to enable YANG Extension Handler Callback examples */
// #define ADD_YANG_EXTEN 1

/* this flag is used to enable RPC Command Complete examples */
// #define ADD_RPC_COMPLETE 1

/* this flag is used to enable the user-type callback examples */
// #define ADD_USER_TYPE 1
#define USERTYPE_MODULE (const xmlChar *)"iana-crypt-hash"
#define USERTYPE_TYPE   (const xmlChar *)"crypt-hash"

/* The Commit Complete Callback needs a module name
 * It just needs to be the same in the register and unregister call
 */
#define CC_HOOK_MODULE (const xmlChar *)"my-module"


/* this flag is used to enable Global EDIT3 callback examples */
// #define ADD_GLOBAL_EDIT 1

/* this flag is used to enable Global GET2 callback examples */
// #define ADD_GLOBAL_GET 1

/* this flag is used to enable Global RPC callback examples */
// #define ADD_GLOBAL_RPC 1

/* this flag is used to enable Global Action callback examples */
// #define ADD_GLOBAL_ACTION 1


/********************************************************************
*                                                                   *
*                        F U N C T I O N S                          *
*                                                                   *
*********************************************************************/

static uint32 cb_trace = 0;


/****************  Example Callback for Global EDIT3 *******************/

#ifdef ADD_GLOBAL_EDIT

/*
 * @brief Edit database object callback (agt_edit3_fn_t)\n
 *
 * @param editcb Edit Control Block that contains
 * EDIT3 callback information
 *
 * @return return status for the phase.
 */
status_t
    sample_global_edit3 (agt_editcb_t *editcb)
{
    status_t res = NO_ERR;

    /* Callback information available in the callback */
    ses_cb_t *scb = editcb->scb;
    rpc_msg_t *msg = editcb->msg;
    agt_cbtyp_t cbtyp = editcb->cbtyp;
    op_editop_t editop = editcb->editop;

    /* Node related information available in the callback */
    val_value_t *newval = editcb->newval;
    val_value_t *curval = editcb->curval;
    val_value_t *update = editcb->update;
    obj_template_t *obj = editcb->obj;
    const xmlChar *modname = editcb->modname;

    /* Transaction information available in the callback */
    agt_cfg_transaction_t *txcb = editcb->txcb;
    boolean isvalidate = editcb->isvalidate;
    boolean isrunning = editcb->isrunning;
    const xmlChar *user_id = editcb->user_id;
    const xmlChar *client_addr = editcb->client_addr;
    const xmlChar *target = editcb->target;
    const xmlChar *txid_str = editcb->txid_str;
    const xmlChar *instance_id = editcb->instance_id;

    val_value_t *errorval = (curval) ? curval : newval;

    /* Invoke callbacks only for specific nodes from a
     * specific module. Skip callback for any nodes
     * that are not from 'sample' YANG module
     */
    if (xml_strcmp(modname, (const xmlChar *)"sample")) {
        if (LOGDEBUG) {
            log_debug("\n Skipping callback for module '%s'",
                      modname);
        }

        return res;
    } else {
        if (LOGDEBUG) {
            log_debug("\n (%s) Enter GLOBAL EDIT-3 callback"
                      "\n ------PHASE:%s;EDITOP:%s"
                      "\n ------Instance ID:%s\n",
                      errorval ? VAL_NAME(errorval) : NCX_NT_NONE,
                      agt_cbtype_name(cbtyp),
                      op_editop_name(editop),
                      instance_id);
        }
    }

    switch (cbtyp) {
    case AGT_CB_VALIDATE:
        /* description-stmt validation here */
        break;
    case AGT_CB_APPLY:
        /* database manipulation are performed by the server here */
        break;
    case AGT_CB_COMMIT:
        /* device instrumentation done here */
        if (op_editop_is_delete(editop)) {
            /* Delete all data from the device */

        } else {
            /* Use update value to update your device data
             *
             * The technique here is to replace the existing data
             * with this composite Update value.
             */

        }
        break;
    case AGT_CB_ROLLBACK:
        /* undo device instrumentation done in Apply Phase */
        break;
    default:
        FLAG_INT_ERROR;
        res = ERR_INTERNAL_VAL;
    }
    return res;

} /* sample_global_edit3 */

#endif   // ADD_GLOBAL_EDIT


/****************  Example Callback for Global GET2 *******************/

#ifdef ADD_GLOBAL_GET

/* GET2 TEST
 *
 * see ncx/getcb.h (getcb_fn2_t)
 */
static status_t
    sample_global_get2 (ses_cb_t *scb,
                        xml_msg_hdr_t *msg,
                        getcb_get2_t *get2cb)
{
    (void)scb;
    (void)msg;

    /* check the callback mode type */
    getcb_mode_t cbmode = GETCB_GET2_CBMODE(get2cb);
    switch (cbmode) {
    case GETCB_GET_VALUE:
        break;
    case GETCB_GETNEXT_VALUE:
        return ERR_NCX_NO_INSTANCE;
    default:
        return SET_ERROR(ERR_INTERNAL_VAL);
    }
    status_t res = NO_ERR;

    obj_template_t *obj = GETCB_GET2_OBJ(get2cb);
    const xmlChar *modname = obj_get_mod_name(obj);

    /* Invoke callbacks only for specific nodes from a
     * specific module. Skip callback for any nodes
     * that are not from 'sample' YANG module
     */
    if (xml_strcmp(modname, (const xmlChar *)"sample")) {
        if (LOGDEBUG) {
            log_debug("\n Skipping callback for module '%s'",
                      modname);
        }

        return res;
    } else {
        getcb_dump_get2cb(get2cb);
    }

    return res;

}  /* sample_global_get2 */

#endif   // ADD_GLOBAL_GET


/****************  Example Callback for Global RPC *******************/

#ifdef ADD_GLOBAL_RPC

/*
 * @brief Validation phase callback for Global RPC\n
 *
 * All YANG constraints have passed at this point.
 * Add description-stmt checks in this function.
 *
 * @param scb session invoking the RPC operation.
 * @param msg message in progress for this <rpc> request.
 * The msg->rpc_input value node contains the input (if any).
 * It is a container matching the rpc/input node for the YANG rpc.
 * @param methnode XML node for the operation, which can be used
 * in error reporting (or ignored).
 * @return return status for the phase.
 *  - An error in validate phase will cancel invoke phase
 *  - An rpc-error will be added if an error is returned and
 * the msg error Q is empty
 */
static status_t
    global_rpc_validate (ses_cb_t *scb,
                         rpc_msg_t *msg,
                         xml_node_t *methnode)
{
    status_t res = NO_ERR;

    /* YANG module found for this node */
    const xmlChar *modname = methnode->module;

    /* qualified name of element */
    const xmlChar *qname = methnode->qname;

    /* element name without any prefix */
    const xmlChar *elname = methnode->elname;

    /* Invoke Global RPC callbacks only for module with a name sample */
    if (xml_strcmp(modname, (const xmlChar *)"sample")) {
        if (LOGDEBUG) {
            log_debug("\n Skipping VALIDATE callback for module '%s'",
                      modname);
        }

        return res;
    } else {
        if (LOGDEBUG) {
            log_debug("\n\nEnter GLOBAL RPC VALIDATE callback"
                      "\n modname=%s"
                      "\n RPC Qualified Name=%s"
                      "\n RPC Name=%s",
                      modname,
                      qname ? qname : NCX_NT_NONE,
                      elname ? elname : NCX_NT_NONE);
        }
    }

    /* Check RPC input to ensure it is required */
    val_value_t *inputval = agt_get_rpc_input(msg);
    if (inputval == NULL) {
        return ERR_NCX_OPERATION_FAILED;
    }

    /* Assign an error value in case any errors occur */
    val_value_t *errorval = NULL;


    /* Handle Input parameter here */


    if (res != NO_ERR) {
        agt_record_error(scb,
                         &msg->mhdr,
                         NCX_LAYER_OPERATION,
                         res,
                         methnode,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval);
    }
    return res;

} /* global_rpc_validate */


/*
 * @brief Invocation phase callback for GLOBAL RPC\n
 *
 * Validation callback has passed at this point.
 * Call device instrumentation code in this function.
 *
 * @param scb session invoking the RPC operation.
 * @param msg message in progress for this <rpc> request.
 * The msg->rpc_input value node contains the input (if any).
 * It is a container matching the rpc/input node for the YANG rpc.
 * @param methnode XML node for the operation, which can be used
 * in error reporting (or ignored).
 * @return return status for the phase.
 *  - An error in validate phase will cancel invoke phase
 *  - An rpc-error will be added if an error is returned and
 * the msg error Q is empty
 */
static status_t
    global_rpc_invoke (ses_cb_t *scb,
                       rpc_msg_t *msg,
                       xml_node_t *methnode)
{
    status_t res = NO_ERR;

    /* YANG module found for this node */
    const xmlChar *modname = methnode->module;

    /* qualified name of element */
    const xmlChar *qname = methnode->qname;

    /* element name without any prefix */
    const xmlChar *elname = methnode->elname;

    /* Invoke Global RPC callbacks only for module with a name sample */
    if (xml_strcmp(modname, (const xmlChar *)"sample")) {
        if (LOGDEBUG) {
            log_debug("\n Skipping INVOKE callback for module '%s'",
                      modname);
        }

        return res;
    } else {
        if (LOGDEBUG) {
            log_debug("\n\nEnter GLOBAL RPC INVOKE callback"
                      "\n modname=%s"
                      "\n RPC Qualified Name=%s"
                      "\n RPC Name=%s",
                      modname,
                      qname ? qname : NCX_NT_NONE,
                      elname ? elname : NCX_NT_NONE);
        }
    }

    /* Check RPC input to ensure it is required */
    val_value_t *inputval = agt_get_rpc_input(msg);
    if (inputval == NULL) {
        return ERR_NCX_OPERATION_FAILED;
    }

    /* remove the next line if scb is used */
    (void)scb;

    /* invoke your device instrumentation code here */

    obj_template_t *outputobj = agt_get_rpc_output_obj(msg);
    if (outputobj == NULL) {
        return ERR_NCX_OPERATION_FAILED;
    }

    /* Handle Output parameters here */

    return res;

} /* global_rpc_invoke */

#endif   // ADD_GLOBAL_RPC


/****************  Example Callback for Global Action *******************/

#ifdef ADD_GLOBAL_ACTION

/*
 * @brief YANG 1.1 global action validate callback. (agt_action_cb_t)\n
 *
 * @param scb session invoking the "<action>" RPC
 * @param msg message in progress for this "<rpc>" request
 * @param methnode XML node for the operation, which can be used
 * in error reporting (or ignored).
 * @param actionval the nested 'action-method-name' node that was parsed
 * within the topval subtree, in the RPC "<action>" request.
 * This is used to help derive the list keys.
 * @return return status for the phase.
 *  - An error in validate phase will cancel invoke phase
 *  - An rpc-error will be added if an error is returned and
 * the msg error Q is empty
 */
static status_t
    global_action_validate (ses_cb_t *scb,
                            rpc_msg_t *msg,
                            xml_node_t *methnode,
                            val_value_t *actionval)
{
    status_t res = NO_ERR;

    /* YANG module found for this node */
    const xmlChar *modname = methnode->module;

    /* qualified name of element */
    const xmlChar *qname = methnode->qname;

    /* element name without any prefix */
    const xmlChar *elname = methnode->elname;

    /* Invoke Global Action callbacks only for module with a name sample */
    if (xml_strcmp(modname, (const xmlChar *)"sample")) {
        if (LOGDEBUG) {
            log_debug("\n Skipping VALIDATE callback for module '%s'",
                      modname);
        }

        return res;
    } else {
        if (LOGDEBUG) {
            log_debug("\n\nEnter GLOBAL ACTION VALIDATE callback"
                      "\n modname=%s"
                      "\n Action Qualified Name=%s"
                      "\n Action Name=%s",
                      modname,
                      qname ? qname : NCX_NT_NONE,
                      elname ? elname : NCX_NT_NONE);
        }
    }

    /* Assign an error value in case any errors occur */
    val_value_t *errorval = NULL;


    /* Handle Action Input parameter here */
    (void)actionval;

    if (res != NO_ERR) {
        agt_record_error(scb,
                         &msg->mhdr,
                         NCX_LAYER_OPERATION,
                         res,
                         methnode,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval,
                         (errorval) ? NCX_NT_VAL : NCX_NT_NONE,
                         errorval);
    }
    return res;

} /* global_action_validate */


/*
 * @brief YANG 1.1 global action invoke callback. (agt_action_cb_t)\n
 *
 * @param scb session invoking the "<action>" RPC
 * @param msg message in progress for this "<rpc>" request
 * @param methnode XML node for the operation, which can be used
 * in error reporting (or ignored).
 * @param actionval the nested 'action-method-name' node that was parsed
 * within the topval subtree, in the RPC "<action>" request.
 * This is used to help derive the list keys.
 * @return return status for the phase.
 *  - An error in validate phase will cancel invoke phase
 *  - An rpc-error will be added if an error is returned and
 * the msg error Q is empty
 */
static status_t
    global_action_invoke (ses_cb_t *scb,
                          rpc_msg_t *msg,
                          xml_node_t *methnode,
                          val_value_t *actionval)
{
    status_t res = NO_ERR;

    /* YANG module found for this node */
    const xmlChar *modname = methnode->module;

    /* qualified name of element */
    const xmlChar *qname = methnode->qname;

    /* element name without any prefix */
    const xmlChar *elname = methnode->elname;

    /* Invoke Global Action callbacks only for module with a name sample */
    if (xml_strcmp(modname, (const xmlChar *)"sample")) {
        if (LOGDEBUG) {
            log_debug("\n Skipping INVOKE callback for module '%s'",
                      modname);
        }

        return res;
    } else {
        if (LOGDEBUG) {
            log_debug("\n\nEnter GLOBAL ACTION INVOKE callback"
                      "\n modname=%s"
                      "\n Action Qualified Name=%s"
                      "\n Action Name=%s",
                      modname,
                      qname ? qname : NCX_NT_NONE,
                      elname ? elname : NCX_NT_NONE);
        }
    }

    /* Handle Action Input parameter here */
    (void)actionval;

    /* remove the next line if msg is used */
    (void)msg;

    /* remove the next line if scb is used */
    (void)scb;

    /* remove the next line if methnode is used */
    (void)methnode;

    /* invoke your device instrumentation code here */

    /* Handle Output parameters here */

    return res;

} /* global_action_invoke */

#endif   // ADD_GLOBAL_ACTION


/****************  Example Callback for an IPv6 address *******************/

#ifdef ADD_IPV6_FN
/********************************************************************
* FUNCTION my_ipv6_fn
*
* Canonical callback for an IPv6 address.
* Convert the inval to the canonical format for the type
*
* INPUTS:
*   typdef == type definition for the user defined type
*   val == input value to convert
*   cookie == cookie value passed to register function
*
* OUTPUTS:
*   val == can be changed to canonical format
*
* RETURNS:
*   status
*
*********************************************************************/
static status_t
    my_ipv6_fn (typ_def_t *typdef,
                val_value_t *val,
                void *cookie)
{
    (void)typdef;
    (void)cookie;

    log_debug("\nmy_ipv6_fn called ******************************");
    return NO_ERR;

}  /* my_ipv6_fn */

#endif   // ADD_IPV6_FN



#ifdef ADD_USER_TYPE
/********************************************************************
* FUNCTION dummy1_canonical_fn
*
* Canonical callback for a data type.
* Convert the inval to the canonical format for the type (not really)
*
* INPUTS:
*   typdef == type definition for the user defined type
*   val == input value to convert
*   cookie == cookie value passed to register function
*
* OUTPUTS:
*   val == can be changed to canonical format
*
* RETURNS:
*   status
*
*********************************************************************/
static status_t
    dummy1_canonical_fn (typ_def_t *typdef,
                         val_value_t *val,
                         void *cookie)
{
    (void)typdef;
    (void)cookie;

    log_debug("\ndummy1_canonical(%s) called *****", VAL_NAME(val));

    /* real function would change the value to canonical format */
    return NO_ERR;

}  /* dummy1_canonical_fn */


/********************************************************************
* FUNCTION dummy1_compare_fn
*
* Compare callback for a data type.
*
 * INPUTS:
 *   typdef == type definition for the user defined type
 *   val1 == input value 1 to comapre
 *   val2 == input value 2 to comapre
 *   cookie == cookie value passed to register function
 *   retval == address of return compare value
 * OUTPUTS:
 *   *retval == return compare value
 * RETURNS:
 *   status (ERR_NCX_SKIPPED if no compare done)
 */
static status_t
    dummy1_compare_fn (const typ_def_t *typdef,
                       const val_value_t *val1,
                       const val_value_t *val2,
                       void *cookie,
                       int *retval)
{
    (void)typdef;
    (void)cookie;

    log_debug("\ndummy1_compare(%s) called *****", VAL_NAME(val1));

    /* This must be a real compare function!
     * just use the default compare function
     * real function would compare the 2 values with different function
     */
    *retval = val_compare(val1, val2);

    return NO_ERR;

}  /* dummy1_compare_fn */


/********************************************************************
* FUNCTION dummy1_validate_fn
* user validation callback for a userdef type
* Invoked when the input data is checked for field validation
* such as xml_parse. Applies to RPC input and datastore nodes
* INPUTS:
*   typdef == type definition for the user defined type
*   inval == input value to convert
*   cookie == cookie value passed to register function
* RETURNS:
*   the validation status
*/
static status_t
    dummy1_validate_fn (typ_def_t *typdef,
                        val_value_t *val,
                        void *cookie)
{
    (void)typdef;
    (void)cookie;

    log_debug("\ndummy1_validate(%s) called *****", VAL_NAME(val));

    /* real function would test the value */

    return NO_ERR;

}  /* dummy1_validate_fn */

#endif   // ADD_USER_TYPE


/****************  Example External ACM Hooks *******************/

#ifdef ADD_ACM_EXTERN
/********************************************************************
* FUNCTION acm_extern_rpc
*
* Check if the specified user is allowed to invoke an RPC
*
* INPUTS:
*   msg == XML header in incoming message in progress
*   user == user name string
*   rpcobj == obj_template_t for the RPC method to check
*
* RETURNS:
*   TRUE if user allowed invoke this RPC; FALSE otherwise
*
*********************************************************************/
static boolean
    acm_extern_rpc (xml_msg_hdr_t *msg,
                    const xmlChar *user,
                    const obj_template_t *rpcobj)
{
    (void)msg;

    const xmlChar *modname = obj_get_mod_name(rpcobj);
    const xmlChar *objname = obj_get_name(rpcobj);

    log_debug("\nChecking RPC access for user %s to operation '%s:%s'",
              user, modname, objname);

    // check access here

    log_debug("\nacm_extern_rpc: return OK\n");
    return TRUE;

} /* acm_extern_rpc */


/********************************************************************
* FUNCTION acm_extern_notif
*
* Check if the specified user is allowed to receive
* a notification event
*
* INPUTS:
*   user == user name string
*   notifobj == obj_template_t for the notification event to check
*
* RETURNS:
*   TRUE if user allowed receive this notification event;
*   FALSE otherwise
*********************************************************************/
static boolean
    acm_extern_notif (const xmlChar *user,
                      const obj_template_t *notifobj)
{
    const xmlChar *modname = obj_get_mod_name(rpcobj);
    const xmlChar *objname = obj_get_name(rpcobj);

    log_debug("\nChecking Notification access for "
              "user %s to event '%s:%s'",
              user, modname, objname);

    // check access here

    log_debug("\nacm_extern_notif: return OK\n");
    return TRUE;

} /* acm_extern_notif */


/********************************************************************
* FUNCTION acm_extern_write_fn
*
* Check if the specified user is allowed to access a value node
* The val->obj template will be checked against the val->editop
* requested access and the user's configured max-access
*
* INPUTS:
*   msg == XML header from incoming message in progress
*   newval  == val_value_t in progress to check
*                (may be NULL, if curval set)
*   curval  == val_value_t in progress to check
*                (may be NULL, if newval set)
*   val  == val_value_t in progress to check
*   editop == requested CRUD operation
*
* RETURNS:
*   TRUE if user allowed this level of access to the value node
*********************************************************************/
static boolean
    acm_extern_write (xml_msg_hdr_t *msg,
                      const xmlChar *user,
                      const val_value_t *newval,
                      const val_value_t *curval,
                      op_editop_t editop)
{
    (void)msg;
    (void)user;
    (void)newval;
    (void)curval;
    (void)editop;
    log_debug("\nacm_extern_write: return OK\n");
    return TRUE;

} /* acm_extern_write */


/********************************************************************
* FUNCTION acm_extern_read_fn
*
* Check if the specified user is allowed to read a value node
*
* INPUTS:
*   msg == XML header from incoming message in progress
*   user == user name string
*   val  == val_value_t in progress to check
*
* RETURNS:
*   TRUE if user allowed read access to the value node
*********************************************************************/
static boolean
    acm_extern_read (xml_msg_hdr_t *msg,
                     const xmlChar *user,
                     const val_value_t *val)
{
    (void)msg;
    (void)user;
    (void)val;
    log_debug("\nacm_extern_read: return OK\n");
    return TRUE;

} /* acm_extern_read */

#endif   // ADD_ACM_EXTERN


/****************  Example External NACM Group Hook *******************/

#ifdef ADD_NACM_GROUP
/********************************************************************
* FUNCTION nacm_external_group_cbfn
*
 * Get the list of group names for this username
 * These groups are added to the usergroup cache for the session
 * INPUTS:
 *   username: return the list of group names that this username
 *             is a member
 *   retgroups == address of return malloced string
 * OUTPUTS:
 *   *retgroups is set to a malloced string that will be parsed.
 *         It contains a whitespace delimited list of group named
 *            ' group1 group2 group3'
 *         The caller will free this string with m__free
 * RETURNS:
 *   status: if an error occurs the session will only use NACM groups
*********************************************************************/
static status_t
    nacm_external_group_cbfn (const xmlChar *username,
                              xmlChar **retgroups)
{
    if (retgroups == NULL) {
        return ERR_NCX_INVALID_VALUE;
    }

    (void)username;

    /* MUST use a function that allocates memory with m__getMem
     * Will be freed by the caller with m__free macro
     */
    *retgroups = xml_strdup("group1 group2 group5");
    if (*retgroups == NULL) {
        return ERR_INTERNAL_MEM;
    }

    return NO_ERR;

} /* nacm_external_group_cbfn */

#endif   // ADD_NACM_GROUP


/**************  Example External Log Vendor Hooks ***************/

#ifdef ADD_VENDOR_LOG
/********************************************************************
* FUNCTION log_vendor_send_fn
*
* Vendor function to consume log output (mandatory)
*
* INPUTS:
*   app == "Facility" (Yangcli or Netconfd)
*   level == Logging level (error, warn, info, debug, ..., debug4)
*   fstr == format string (like printf)
*   args == variable argument list
*
* RETURNS:
*   void
*********************************************************************/
static void
    log_vendor_send_fn (log_debug_app_t app,
                        log_debug_t level,
                        const char *fstr,
                        va_list args)
{
    (void)app;
    (void)level;
    (void)fstr;
    (void)args;
    return;

} /* log_vendor_send_fn */
#endif   // ADD_VENDOR_LOG


/***********  Example External NV-Storage Handler Hooks **********/

#ifdef ADD_NV_STORE_HOOK
#define EXAMPLE_CONFIG_SPEC (const xmlChar *)"/tmp/example-config.xml"
/********************************************************************
* FUNCTION example_nvsave
*
* Nvsave callback is invoked when some config needs to be saved
* to non-volatile storage
*
* INPUTS:
*    encoding == encoding format for the config (xml only allowed value)
*    filespec == filespec containing the config to save
*
* RETURNS:
*    status; error indicates NV-save failed somehow
*
*********************************************************************/
static status_t
  example_nvsave (ncx_display_mode_t encoding,
                  const xmlChar *filespec)
{
    status_t res = NO_ERR;

    if (filespec == NULL || *filespec == 0) {
        res = ERR_NCX_INVALID_VALUE;
    } else if (encoding != NCX_DISPLAY_MODE_XML) {
        res = ERR_NCX_INVALID_VALUE;
    } else {
        res = ncxmod_copy_text_file(filespec, EXAMPLE_CONFIG_SPEC);
    }

    return res;

}  /* example_nvsave */


/********************************************************************
* FUNCTION example_nvload
*
* Nvload callback invoked when some config needs to be read
* from non-volatile storage
*
* INPUTS:
*    encoding == address of encoding for the config
*    filespec == address of filespec containing the config that was loaded
*
* OUTPUTS:
*    *encoding == set to the enum for the encoding used in the config
*    *filespec == malloced filespec containing the config that was loaded
*
* RETURNS:
*    status; error indicates NV-load failed somehow
*    If return NO_ERR and *filespec == NULL then use the factory config
*
*********************************************************************/
static status_t
    example_nvload (ncx_display_mode_t *encoding,
                    xmlChar **filespec)
{
    log_debug("\nEnter example_nvload");

    *filespec = NULL;
    *encoding = NCX_DISPLAY_MODE_XML;

    status_t res = NO_ERR;

    if (ncxmod_test_filespec(EXAMPLE_CONFIG_SPEC)) {
        /* file exists so copy the filespec */
        *filespec = xml_strdup(EXAMPLE_CONFIG_SPEC);
        if (*filespec == NULL) {
            res = ERR_INTERNAL_MEM;
        }
    }

    return res;

}  /* example_nvload */

#endif   // ADD_NV_STORE_HOOK


/***********  Example Transaction Management Hooks **********/

#ifdef ADD_TRANS_HOOK
/********************************************************************
* FUNCTION example_transaction_start
*
* The Start Transaction function is the user/system
* callback that is invoked before any changes to the
* candidate database will be committed.
*
* INPUTS:
*    txcb == transaction control block in progress
*
* RETURNS:
*    status
*
*********************************************************************/
static status_t
    example_transaction_start (agt_cfg_transaction_t *txcb)
{
    (void)txcb;
    log_debug("\nEnter transaction_start example");
    return NO_ERR;

}  /* example_transaction_start */


/********************************************************************
* FUNCTION example_transaction_complete
*
* The Transaction Complete function is the
* user/system callback that is invoked after
* the transactions has been processed.
*
* INPUTS:
*    txcb == transaction control block in progress
*
* RETURNS:
*    none
*
*********************************************************************/
static void
    example_transaction_complete (agt_cfg_transaction_t *txcb)
{
    (void)txcb;
    log_debug("\nEnter transaction_complete example");
    return;

}  /* example_transaction_complete */

#endif   // ADD_TRANS_HOOK



#ifdef ADD_CC_HOOK
/********************************************************************
* FUNCTION example_validate_complete
*
* The Validate Complete callback is the user/system callback
* that is invoked after the Validate Phase has been processed
* during the <commit> operation.
*
* The Validate Complete is object independent and module
* independent which means you don't have to link them to
* the specific object as it's done for EDIT or GET callbacks,
* and you don't have to link them to any specific module
*
* Max Callbacks: No limit (except available heap memory)
*
* @param scb session control block making the request
* @param msg incoming rpc_msg_t in progress
* @param candidate candidate val_value_t for the config database to use
* @param running running val_value_t for the config database to use
* @return status
*/
static status_t
    example_validate_complete (ses_cb_t *scb,
                               rpc_msg_t *msg,
                               val_value_t *candidate,
                               val_value_t *running)
{
    if (LOGDEBUG) {
        log_debug("\n*** Enter Validate Complete Callback (%u)",
                  ++cb_trace);
    }
    status_t res = NO_ERR;
    (void)scb;
    (void)msg;
    (void)candidate;
    (void)running;
    return res;

}  /* example_validate_complete */


/********************************************************************
* FUNCTION example_apply_complete
*
* The Apply Complete callback is the user/system callback
* that is invoked after the Validate Phase has been processed
* during the <commit> operation.
*
* The Validate Complete is object independent and module
* independent which means you don't have to link them to
* the specific object as it's done for EDIT or GET callbacks,
* and you don't have to link them to any specific module
*
* Max Callbacks: No limit (except available heap memory)
*
* @param scb session control block making the request
* @param msg incoming rpc_msg_t in progress
* @param candidate candidate val_value_t for the config database to use
* @param running running val_value_t for the config database to use
* @return status
*/
static status_t
    example_apply_complete (ses_cb_t *scb,
                            rpc_msg_t *msg,
                            val_value_t *candidate,
                            val_value_t *running)
{
    if (LOGDEBUG) {
        log_debug("\n*** Enter Apply Complete Callback (%u)",
                  ++cb_trace);
    }
    status_t res = NO_ERR;
    (void)scb;
    (void)msg;
    (void)candidate;
    (void)running;
    return res;

}  /* example_apply_complete */


/********************************************************************
* FUNCTION example_commit_complete
*
 *   - Specific to the <commit> operation
 *   - Invoked if the validate, apply, and commit all return NO_ERR
 *   - This is a legacy API from yuma
 *
 * NOTE: IF RETURN VALUE IS NOT NO_ERR:
 *  -  PRIOR TO 22.10T-10: This function causes an error to be returned
 *     but no Rollback phase is done
 *  -  Starting with 22.10T-10: This function causes an error to be returned
 *     and also a Rollback phase is done
 *
 * @param commit_type
 *   - AGT_COMMIT_TYPE_NORMAL is a <commit> operation
 *   - AGT_COMMIT_TYPE_REPLAY is a replay-commit procedure
 * @return status
 *   - Need to return NO_ERR or else end of transaction can be incompleted
 */
static status_t
    example_commit_complete (agt_commit_type_t commit_type)
{
    if (LOGDEBUG) {
        log_debug("\n*** Enter Commit Complete Callback (%u)",
                  ++cb_trace);
    }
    status_t res = NO_ERR;
    (void)commit_type;

    if (cb_trace > 5) {
        //res = ERR_NCX_RESOURCE_DENIED;
    }

    return res;

}  /* example_commit_complete */


/********************************************************************
* FUNCTION example_rollback_complete
*
* The Rollback Complete callback is the user/system callback
* that is invoked after and if the Rollback Phase has been processed
* during the <commit> operation.
*
* The Rollback Complete is object independent and module
* independent which means you don't have to link them to
* the specific object as it's done for EDIT or GET callbacks,
* and you don't have to link them to any specific module.
*
* Max Callbacks: No limit (except available heap memory)
*
* @param scb session control block making the request
* @param msg incoming rpc_msg_t in progress
* @param candidate candidate val_value_t for the config database to use
* @param running running val_value_t for the config database to use
* @return status
*/
static status_t
    example_rollback_complete (ses_cb_t *scb,
                               rpc_msg_t *msg,
                               val_value_t *candidate,
                               val_value_t *running)
{
    if (LOGDEBUG) {
        log_debug("\n*** Enter Rollback Complete Callback (%u)",
                  ++cb_trace);

    }
    status_t res = NO_ERR;
    (void)scb;
    (void)msg;
    (void)candidate;
    (void)running;
    return res;

}  /* example_rollback_complete */

#endif   // ADD_CC_HOOK


/***********  Example Edit Phase Complete Template Callback **********/

#ifdef ADD_EPC_HOOK
/*
* @brief Example agt_cb_edit_phase_complete_t callback
*
* The Edit Phase Complete callback is the user/system callback
* that is invoked after each edit transaction phase has been processed
* during any edit transaction operation.
*
* Edit phases are defined in agt_cbtyp_t enumeration
*
* Callbacks for the AGT_CB_VALIDATE phase may be invoked
* for the <candidate> or <running> datastore.
*
* Callbacks for the other phases may be invoked
* for the <running> datastore.
*
* If the return value is not NO_ERR then the edit transaction
* will be terminated and a rollback will begin.
*
* Max Callbacks: No limit (except available heap memory)
*
* @param edit_phase edit phase enumeration that has just been completed
* @param scb session control block making the request
* @param msg incoming rpc_msg_t in progress
* @param source val_value_t for the source config database to use
* @param target val_value_t for the target config database to use
* @return status
*/
static status_t
    example_edit_phase_complete (agt_cbtyp_t edit_phase,
                                 ses_cb_t *scb,
                                 rpc_msg_t *msg,
                                 val_value_t *source,
                                 val_value_t *target)
{
    if (LOGDEBUG) {
        log_debug("\n*** Enter Edit Phase Complete Callback "
                  "for '%s' (%u)",
                  agt_cbtype_name(edit_phase),
                  ++cb_trace);
    }

    status_t res = NO_ERR;
    (void)scb;
    (void)msg;
    (void)source;
    (void)target;

    /* check the specific phase that has just completed
     * and set res to some error if the edit transaction
     * should be rejected. (validate, apply, commit)
     * For rollback phase this function can only change the
     * status of the rollback printed in log messages
     */
    switch (edit_phase) {
    case AGT_CB_VALIDATE:
    case AGT_CB_APPLY:
    case AGT_CB_COMMIT:
    case AGT_CB_ROLLBACK:
        break;
    default:
        res = ERR_NCX_INVALID_VALUE;
    }

    return res;

}
#endif  // ADD_EPC_HOOK


/***********  Example YANG Object Template Callback **********/

#ifdef ADD_YANG_OBJ_TEMP
#define FL_ACME_1  0x1
/********************************************************************
* FUNCTION example_transaction_complete
*
* User function callback template when a YANG object is
* parsed by yang_obj.c. This API is invoked at the
* end of the resolve phase if the status is NO_ERR
* It is skipped if the object has errors detected at the time
*
* Callback is invoked to check extensions in an obj_template_t
* representing YANG data nodes
*
* Assume YANG module foo exists with extension acme1
*
* module foo {
*   prefix f;
*   ...
*   extension acme1 { ... }
*
*
* The extension is used inside object definitions. e.g:
*
*    leaf X {
*      f:acme1;
*      type string;
*    }
*
* Assume there is a vendor bit defined for the user flags field
* ncx_yang_obj_cbfn_t
*
*  Run an instrumentation-defined function
*  for a 'object parsed' event
*
* INPUTS:
*   mod == module that is being parsed now
*   obj == object being parsed
*
*********************************************************************/
static void
    example_obj_template_cbfn (ncx_module_t *mod,
                               struct obj_template_t_ *obj)
{
    /* optional: use the module to check cetain module names to
     * pre-filter the callback
     */
    (void)mod;

    /* get the appinfoQ for the object */
    dlq_hdr_t *appinfoQ = obj_get_appinfoQ(obj);
    if (appinfoQ == NULL) {
        return;   // error!
    }

    /* check the object template appinfoQ to see if the vendor
     * extensions are present
     */
    ncx_appinfo_t *appinfo =
        ncx_find_appinfo(appinfoQ,
                         (const xmlChar *)"f",
                         (const xmlChar *)"acme1");
    if (appinfo) {
        OBJ_USER_FLAGS(obj) |= FL_ACME_1;
    }

} /* example_obj_template_cbfn */

#endif   // ADD_YANG_OBJ_TEMP


/***********  Example YANG Extension Handler Callback **********/

#ifdef ADD_YANG_EXTEN
/********************************************************************
* FUNCTION example_ext_cbfn
*
* One YANG Extension Handler Callback
*
* Callback is invoked to check a specific extension in an
* obj_template_t, typ_template_t, typ_def_t
*
* Assume the same YANG module foo exists with extension acme1
*
* The example callback does the same task as the
* example_obj_template_cbfn, using the per-callback approach
*
* Handle the parsing and processing of an external statement
* using the associated YANG extension statement
*
* This callback is invoked when the external statement is
* first encountered. The current token is the argument string
* if any or the identifier token if none.
* The next token is expected to be a semi-colon or a left brace
* The callback is expected to parse the closing semi-colon or
* entire sub-section including starting brace
*
* INPUTS:
*   rawpcb == parser control block in progress (cast as void *)
*   mod == module being processed
*   tkc == token chain of module tokens parse in progress
*   ext == extension definition record (allows a handler to
*         process multiple extension types)
*   cookie == cbfn_cookie from the extension 'ext'
*   arg == argument string used in the external statement (if any)
*   node_type == type of node being processed; direct parent
*         statement of the external statement using the extension
*         If NULL, then the parent statement is the module itself,
*         and 'mod' should be used as the 'node' pointer
*   node == pointer to node indicated by node_type
* OUTPUTS:
*
* RETURNS:
*   status of processing
*
*********************************************************************/
static status_t
    example_ext_cbfn (void *rawpcb,  // struct yang_pcb_t_ *pcb
                      ncx_module_t *mod,
                      tk_chain_t *tkc,
                      struct ext_template_t_ *ext,
                      void *cookie,
                      const xmlChar *arg,
                      ncx_node_t node_type,
                      void *node)
{
    (void)rawpcb;
    (void)mod;
    (void)tkc;
    (void)ext;
    (void)cookie;
    (void)arg;

    /* ignore this extension in all contexts except object template */
    if (node_type != NCX_NT_OBJ) {
        return NO_ERR;
    }

    /* get the object template */
    obj_template_t *obj = (obj_template_t *)node;

    /* set the acme1 bit */
    OBJ_USER_FLAGS(obj) |= FL_ACME_1;

    return NO_ERR;

} /* example_ext_cbfn */

#endif   // ADD_YANG_EXTEN


/***********  Example RPC Command Complete Callback **********/

#ifdef ADD_RPC_COMPLETE
/********************************************************************
* FUNCTION example_command_complete_cbfn
*
* One RPC Command Complete Handler Callback
*
* The Command Complete callback is the user/system callback
* that is invoked after each client command is executed
* for the NETCONF or RESTCONF protocols.
*
* The Command Complete is typically used for retrieval
* operations (get, get-config, get-bulk) to release resources
* used during GET2 callbacks invoked during the operation.
*
* Max Callbacks: limited by memory only
*
* INPUTS:
*   scb == session control block making the request
*   msg == incoming rpc_msg_t in progress
*   command_modname == YANG module name of command that is completed
*   command_name == YANG RPC object name of command that is completed
*
* OUTPUTS:
*    none
*
* RETURNS:
*    none
*
*********************************************************************/
static void
    example_command_complete_cbfn (ses_cb_t *scb,
                                   rpc_msg_t *msg,
                                   const xmlChar *command_modname,
                                   const xmlChar *command_name)
{
    if (!xml_strcmp(command_modname, (const xmlChar *)"RESTCONF")) {
        /* RESTCONF specific handling */

        if (!xml_stricmp(command_name, (const xmlChar *)"POST") ||
            !xml_stricmp(command_name, (const xmlChar *)"PUT") ||
            !xml_stricmp(command_name, (const xmlChar *)"PATCH")) {

            /* post, put or patch handling */
        }
    } else {
        /* NETCONF operations handling */
        if (!xml_strcmp(command_name, (const xmlChar *)"get") ||
            !xml_strcmp(command_name, (const xmlChar *)"get-config") ||
            !xml_strcmp(command_name, (const xmlChar *)"get-bulk")) {

            /* cleanup our internal get cache data
             * example_clean_get2_cache(scb, msg);
             */
        }
    }
} /* example_command_complete_cbfn */

#endif   // ADD_RPC_COMPLETE


/***********  Example Session Hook Callback **********/

#ifdef ADD_SESSION_HOOK
/********************************************************************
* FUNCTION example_session_hook
*
* The Session Hook callback is invoked when a session starts
* and ends.
* Use ses.h macros like SES_USERNAME(scb) to get
* data like client username
* Max Callbacks: No limit (except available heap memory)
*
* INPUTS:
*    ses_event == session event type
*    scb == session control block for the session event
*
* OUTPUTS:
*    none
*
* RETURNS:
*    none
*
*********************************************************************/
static void
    example_session_hook (ncx_ses_event_t ses_event,
                          const ses_cb_t *scb)
{
    if (!scb) {
        FLAG_INT_ERROR;
        return;
    }

    /* event name is either session-start or session-end */
    const xmlChar *event_name = ncx_get_ses_event_str(ses_event);

    /* peer_addr should be set */
    const xmlChar *peer_addr = SES_PEERADDR(scb);
    if (peer_addr == NULL) {
        peer_addr = NCX_EL_LOCALHOST;
    }


    /* user_name should be set */
    const xmlChar *user_name = SES_MY_USERNAME(scb);
    if (user_name == NULL) {
        user_name = NCX_EL_NOBODY;
    }

    /* protocol name should be set */
    ncx_protocol_t protocol = ses_get_protocol(scb);
    const xmlChar *protocol_name = ncx_get_protocol_name(protocol);
    if (protocol_name == NULL) {
        protocol_name = NCX_EL_NONE;
    }

    log_info("\nGot %s event from host %s, user %s, protocol %s\n",
             event_name, peer_addr, user_name, protocol_name);

}  /* example_session_hook */

#endif  // ADD_SESSION_HOOK


/****************  Required System Library Hooks *******************/

/********************************************************************
* FUNCTION yp_system_init_profile
*
* system init server profile callback
*
* Initialize the server profile if needed
*
* INPUTS:
*  profile == server profile to change if needed
*
*********************************************************************/
void
    yp_system_init_profile (agt_profile_t *profile)
{
    (void) profile;
    log_debug("\nyp_system init profile\n");

    /* example: use an external ACM module */
    //profile->agt_acm_model = AGT_ACM_MODEL_EXTERNAL;

    /* example: set the with-defaults also-supported bits
     * from agt/agt.h:
     * bitmask of the with-defaults enumerations that should be
     * enabled in the server
     *    explicit: bit0
     *    trim: bit1
     *    report-all: bit2
     *    report-all-tagged: bit3
     *
     * uint8 agt_withdef_enabled;
     *
     * The basic-mode (agt_defaultStyle and agt_defaultStyleEnum)
     * will be added by the server automatically, so a value of 0
     * will not enable any 'also-supported' retrieval modes
     * for the 'with-defaults' leaf
     */
    //profile->agt_withdef_enabled = bit1 | bit3;

    /* add to enable callhome reconnect after a close-session */
    //profile->agt_callhome_reconnect = TRUE;

} /* yp_system_init_profile */


/********************************************************************
* FUNCTION yp_system_init1
*
* init1 system call
* this callback is invoked twice; before and after CLI processing
*
* INPUTS:
*   pre_cli == TRUE if this call is before the CLI parameters
*              have been read
*              FALSE if this call is after the CLI parameters
*              have been read
* RETURNS:
*   status
*
*********************************************************************/
status_t yp_system_init1 (boolean pre_cli)
{
    status_t res = NO_ERR;
    log_debug("\nyp_system init1 (%s)\n",
              pre_cli ? "pre-CLI" : "post-CLI");

    if (pre_cli) {

#ifdef ADD_VENDOR_LOG
        // example -- register vendor callback to consume logging output.
        /* Note that output will not be re-directed to the vendor stream
         * until AFTER --log-vendor is parsed by CLI processing or log_vendor
         * is parsed by config file processing.
         * Uncomment the following 2 lines to enable!!
         */
        log_vendor_extern_register_send_fn(log_vendor_send_fn);
#endif   // ADD_VENDOR_LOG

    } else {

#ifdef ADD_ACM_EXTERN
        /* example -- external NACM callbacks
         * load module for external module
         * with ncxmod_load_module
         * register the external ACM callbacks
         * this will have no affect unless the
         * yp_system_init_profile fn sets the
         * agt_acm_model to AGT_ACM_MODEL_EXTERNAL
         */
        agt_acm_extern_register_callbacks(acm_extern_rpc,
                                         acm_extern_notif,
                                         acm_extern_write,
                                         acm_extern_read);
#endif   // ADD_ACM_EXTERN


#ifdef ADD_NV_STORE_HOOK
        /* example -- register NV-storage handler to load/save config */
        res = agt_register_local_nv_handler(example_nvload,
                                            example_nvsave);
        if (res != NO_ERR) {
            return res;
        }
#endif   // ADD_NV_STORE_HOOK


#ifdef ADD_YANG_OBJ_TEMP
        /* example -- Register a YANG Object Template Callback */
        res = ncx_set_yang_obj_callback(example_obj_template_cbfn);
        if (res != NO_ERR) {
            return res;
        }
#endif   // ADD_YANG_OBJ_TEMP


#ifdef ADD_YANG_EXTEN
        /* example -- Register an Extension Handler Callback */
        res = ext_register_cbfn((const xmlChar *)"acme-ext",
                                (const xmlChar *)"acme1",
                                example_ext_cbfn,
                                NULL);   // cookie
        if (res != NO_ERR) {
            return res;
        }
#endif   // ADD_YANG_EXTEN


#ifdef ADD_IPV6_FN
        /* Example: register a type-specific callback
         * In real usage at least 1 of the 3 callback functions
         * must be set
         */
        res = typ_userdef_register(NCXMOD_IETF_INET_TYPES,
                                   (const xmlChar *)"ipv6-address-no-zone",
                                   NULL,         // validate_fn
                                   my_ipv6_fn,   // canonical_fn
                                   NULL,         // compare_fn
                                   NULL);        // cookie
        if (res != NO_ERR) {
            return res;
        }
#endif  // ADD_IPV6_FN

#ifdef ADD_USER_TYPE
        /* Example: register a type-specific callback
         * Show all 3 callbacks being used
         */
        res = typ_userdef_register(USERTYPE_MODULE,
                                   USERTYPE_TYPE,
                                   dummy1_validate_fn,
                                   dummy1_canonical_fn,
                                   dummy1_compare_fn,
                                   NULL);        // cookie
        if (res != NO_ERR) {
            return res;
        }
#endif   // ADD_USER_TPYE

#ifdef ADD_SESSION_HOOK
        /* Example: register a Session Hook callback
         * uncomment following to enable
         */
        res = agt_cb_session_hook_register(example_session_hook);
        if (res != NO_ERR) {
            return res;
        }
#endif  // ADD_SESSION_HOOK

#ifdef ADD_GLOBAL_EDIT
        /* Example: register a Global EDIT3 callback
         * uncomment following to enable
         */
        res = agt_glob_register_edit3(sample_global_edit3);
        if (res != NO_ERR) {
            return res;
        }
#endif  // ADD_GLOBAL_EDIT

#ifdef ADD_GLOBAL_GET
        /* Example: register a Global GET2 callback
         * uncomment following to enable
         */
        res = agt_glob_register_get(sample_global_get2);
        if (res != NO_ERR) {
            return res;
        }
#endif  // ADD_GLOBAL_GET


#ifdef ADD_GLOBAL_RPC
        /* Example: register a Global RPC callback
         * uncomment following to enable
         */
        res =
            agt_glob_register_rpc(AGT_RPC_PH_VALIDATE,
                                  global_rpc_validate);
        if (res != NO_ERR) {
            return res;
        }

        res =
            agt_glob_register_rpc(AGT_RPC_PH_INVOKE,
                                  global_rpc_invoke);
        if (res != NO_ERR) {
            return res;
        }
#endif  // ADD_GLOBAL_RPC

#ifdef ADD_GLOBAL_ACTION
        /* Example: register a Global RPC callback
         * uncomment following to enable
         */
        res =
            agt_glob_register_action(AGT_RPC_PH_VALIDATE,
                                     global_action_validate);
        if (res != NO_ERR) {
            return res;
        }

        res =
            agt_glob_register_action(AGT_RPC_PH_INVOKE,
                                     global_action_invoke);
#endif  // ADD_GLOBAL_ACTION

    }
    return res;

}  /* yp_system_init1 */


/********************************************************************
* FUNCTION yp_system_init2
*
* init2 system call
* this callback is invoked twice; before and after
* load_running_config processing
*
* INPUTS:
* pre_load == TRUE if this call is before the running config
*            has been loaded
*            FALSE if this call is after the running config
*            has been loaded
* RETURNS:
*  status
*
*********************************************************************/
status_t
    yp_system_init2 (boolean pre_load)
{
    status_t res = NO_ERR;

    log_debug("\nyp_system init2 (%s)\n",
              pre_load ? "pre-load-config" : "post-load-config");

    if (pre_load) {
#ifdef ADD_NACM_GROUP
        /* example -- Register a NACM External Groups Callback
         * uncomment following to enable
         */
        agt_acm_ietf_register_group_cbfn(nacm_external_group_cbfn);
#endif  // ADD_NACM_GROUP


#ifdef ADD_RPC_COMPLETE
        /* initialize a Command Complete callback */
        res = agt_cb_command_complete_register(example_command_complete_cbfn);
        if (res != NO_ERR) {
            return res;
        }
#endif  // ADD_RPC_COMPLETE


#ifdef ADD_TRANS_HOOK
        /* example -- Register a Transaction Start callback */
        res = agt_cb_trans_start_register(example_transaction_start);
        if (res != NO_ERR) {
            return res;
        }

        /* example -- Register a Transaction Complete callback */
        res = agt_cb_trans_complete_register(example_transaction_complete);
        if (res != NO_ERR) {
            return res;
        }
#endif   // ADD_TRANS_HOOK

#ifdef ADD_CC_HOOK
        /* example -- Register a Validate Complete callback */
        res = agt_cb_validate_complete_register(example_validate_complete);
        if (res != NO_ERR) {
            return res;
        }

        /* example -- Register an Apply Complete callback */
        res = agt_cb_apply_complete_register(example_apply_complete);
        if (res != NO_ERR) {
            return res;
        }

        /* example -- Register an Commit Complete callback */
        res = agt_commit_complete_register(CC_HOOK_MODULE,
                                           example_commit_complete);
        if (res != NO_ERR) {
            return res;
        }

        /* example -- Register a Rollback Complete callback */
        res = agt_cb_rollback_complete_register(example_rollback_complete);
        if (res != NO_ERR) {
            return res;
        }
#endif   // ADD_CC_HOOK

#ifdef ADD_EPC_HOOK
        /* example -- Register an Edit Phase Complete callback */
        res = agt_cb_edit_phase_complete_register(example_edit_phase_complete);
        if (res != NO_ERR) {
            return res;
        }
#endif   // ADD_EPC_HOOK

    } else {
        ;
    }

    return res;

}  /* yp_system_init2 */


/********************************************************************
* FUNCTION yp_system_cleanup
*
* System cleanup callback
* this callback is invoked once during agt_cleanup
*
* INPUTS:
*   none
*
* RETURNS:
*   none
*
*********************************************************************/
void yp_system_cleanup (void)
{
    log_debug("\nyp_system cleanup\n");

#ifdef ADD_SESSION_HOOK
    /* Example: Unregister a Session Hook callback.
     * This is optional; will get deleted at shutdown
     * uncomment following to enable
     */
    agt_cb_session_hook_unregister(example_session_hook);
#endif  // ADD_SESSION_HOOK


#ifdef ADD_TRANS_HOOK
    /* example -- Unregister a Transaction Start callback */
    agt_cb_trans_start_unregister(example_transaction_start);

    /* example -- Unregister a Transaction Complete callback */
    agt_cb_trans_complete_unregister(example_transaction_complete);
#endif   // ADD_TRANS_HOOK

#ifdef ADD_CC_HOOK
    /* example -- UnRegister a Validate Complete callback */
    agt_cb_validate_complete_unregister(example_validate_complete);

    /* example -- UnRegister an Apply Complete callback */
    agt_cb_apply_complete_unregister(example_apply_complete);

    /* example -- UnRegister an Commit Complete callback */
    agt_commit_complete_unregister(CC_HOOK_MODULE);

    /* example -- Register a Rollback Complete callback */
    agt_cb_rollback_complete_unregister(example_rollback_complete);
#endif   // ADD_CC_HOOK

#ifdef ADD_EPC_HOOK
    /* example -- UnRegister a Validate Complete callback */
    agt_cb_edit_phase_complete_unregister(example_edit_phase_complete);
#endif   // ADD_EPC_HOOK

#ifdef ADD_GLOBAL_EDIT
    /* example -- UnRegister a Global EDIT3 callback */
    agt_glob_unregister_edit();
#endif  // ADD_GLOBAL_EDIT

#ifdef ADD_GLOBAL_GET
    /* example -- UnRegister a Global GET2 callback */
    agt_glob_unregister_get();
#endif  // ADD_GLOBAL_GET

#ifdef ADD_GLOBAL_RPC
    /* example -- UnRegister a Global RPC callback */
    agt_glob_unregister_rpc();
#endif  // ADD_GLOBAL_RPC

#ifdef ADD_GLOBAL_ACTION
    /* example -- UnRegister a Global Action callback */
    agt_glob_unregister_action();
#endif  // ADD_GLOBAL_ACTION

} /* yp_system_cleanup */


/* END example-system.c */