#######################################################################
###generic include for XXX. Do not use directly.
###
########################################################################
@if $m2c_mark_boundary == 1@
/** START code generated by mfd-access-container-cached-defines.m2i */
@end@
##//####################################################################
##//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
##//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@if $m2c_processing_type eq 'h'@

@   if $m2c_data_cache != 1@
void ${context}_container_init(netsnmp_container **container_ptr_ptr);
@   else@
    /*
     * TODO:180:o: Review ${context} cache timeout.
     * The number of seconds before the cache times out
     */
#define $context.uc_CACHE_TIMEOUT   60

void ${context}_container_init(netsnmp_container **container_ptr_ptr,
                             netsnmp_cache *cache);
@   end@ # data cache
void ${context}_container_shutdown(netsnmp_container *container_ptr);

int ${context}_container_load(netsnmp_container *container);
void ${context}_container_free(netsnmp_container *container);

@   if $m2c_data_cache == 1@
int ${context}_cache_load(netsnmp_container *container);
void ${context}_cache_free(netsnmp_container *container);

@   end@
@   if $m2c_include_examples == 1@
$example_start
/* *********************************************************************
 * Since we have no idea how you really access your data, we'll go with
 * a worst case example: a flat text file.
 */
#define MAX_LINE_SIZE 256
$example_end
@   end@ // example
@end@ // m2c_processing_type eq 'h'
##//####################################################################
##//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
##//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@if $m2c_processing_type eq 'c'@
/**
 * container overview
 *
 */

/**
 * container initialization
 *
 * @param container_ptr_ptr A pointer to a container pointer. If you
 *        create a custom container, use this parameter to return it
 *        to the MFD helper. If set to NULL, the MFD helper will
 *        allocate a container for you.
@   if $m2c_data_cache == 1@
 * @param  cache A pointer to a cache structure. You can set the timeout
 *         and other cache flags using this pointer.
@   end@
 *
 *  This function is called at startup to allow you to customize certain
 *  aspects of the access method. For the most part, it is for advanced
 *  users. The default code should suffice for most cases. If no custom
 *  container is allocated, the MFD code will create one for your.
 *
@   if $m2c_data_cache == 1@
 *  This is also the place to set up cache behavior. The default, to
 *  simply set the cache timeout, will work well with the default
 *  container. If you are using a custom container, you may want to
 *  look at the cache helper documentation to see if there are any
 *  flags you want to set.
 *
@   end@
 * @remark
 *  This would also be a good place to do any initialization needed
 *  for you data source. For example, opening a connection to another
 *  process that will supply the data, opening a database, etc.
 */
void
@   if $m2c_data_cache != 1@
${context}_container_init(netsnmp_container **container_ptr_ptr)
@   else@
${context}_container_init(netsnmp_container **container_ptr_ptr,
                             netsnmp_cache *cache)
@   end@
{
    DEBUGMSGTL(("verbose:${context}:${context}_container_init","called\n"));
    
    if (NULL == container_ptr_ptr) {
        snmp_log(LOG_ERR,"bad container param to ${context}_container_init\n");
        return;
    }

    /*
     * For advanced users, you can use a custom container. If you
     * do not create one, one will be created for you.
     */
    *container_ptr_ptr = NULL;

@if $m2c_data_cache == 1@
    if (NULL == cache) {
        snmp_log(LOG_ERR,"bad cache param to ${context}_container_init\n");
        return;
    }

    /*
     * TODO:345:A: Set up $context cache properties.
     *
     * Also for advanced users, you can set parameters for the
     * cache. Do not change the magic pointer, as it is used
     * by the MFD helper. To completely disable caching, set
     * cache->enabled to 0.
     */
    cache->timeout = $context.uc_CACHE_TIMEOUT; /* seconds */
@end@
} /* ${context}_container_init */

/**
 * container shutdown
 *
 * @param container_ptr A pointer to the container.
 *
 *  This function is called at shutdown to allow you to customize certain
 *  aspects of the access method. For the most part, it is for advanced
 *  users. The default code should suffice for most cases.
 *
 *  This function is called before ${context}_container_free().
 *
 * @remark
 *  This would also be a good place to do any cleanup needed
 *  for you data source. For example, closing a connection to another
 *  process that supplied the data, closing a database, etc.
 */
void
${context}_container_shutdown(netsnmp_container *container_ptr)
{
    DEBUGMSGTL(("verbose:${context}:${context}_container_shutdown","called\n"));
    
    if (NULL == container_ptr) {
        snmp_log(LOG_ERR,"bad params to ${context}_container_shutdown\n");
        return;
    }

} /* ${context}_container_shutdown */

/**
 * load initial data
 *
 * TODO:350:M: Implement $context data load
@   if $m2c_data_cache == 1@
 * This function will also be called by the cache helper to load
 * the container again (after the container free function has been
 * called to free the previous contents).
@   end@
 *
 * @param container container to which items should be inserted
 *
 * @retval MFD_SUCCESS              : success.
 * @retval MFD_RESOURCE_UNAVAILABLE : Can't access data source
 * @retval MFD_ERROR                : other error.
 *
 *  This function is called to load the index(es) (and data, optionally)
 *  for the every row in the data set.
 *
 * @remark
 *  While loading the data, the only important thing is the indexes.
 *  If access to your data is cheap/fast (e.g. you have a pointer to a
 *  structure in memory), it would make sense to update the data here.
 *  If, however, the accessing the data involves more work (e.g. parsing
 *  some other existing data, or performing calculations to derive the data),
 *  then you can limit yourself to setting the indexes and saving any
 *  information you will need later. Then use the saved information in
 *  ${context}_row_prep() for populating data.
 *
 * @note
 *  If you need consistency between rows (like you want statistics
 *  for each row to be from the same time frame), you should set all
 *  data here.
 *
 */
int
${context}_container_load(netsnmp_container *container)
{
    ${context}_rowreq_ctx *rowreq_ctx;
    size_t                 count = 0;

    /*
     * storage for each column
     */
@  foreach $node index@
@    include m2c_setup_node.m2i@
@    include node-storage.m2i@
@  end@ // foreach

    /*
     * temporary storage for index values
     */
@  foreach $node index@
@    include m2c_setup_node.m2i@
        /*
         * $m2c_node_summary
         */
@    if $m2c_node_needlength == 1@
@        eval $m2c_gi_maxlen = (126 - $node.oidlength - $m2c_gi_others)@
@        if $m2c_node_maxlen > $m2c_gi_maxlen@
@            eval $m2c_node_maxlen = $m2c_gi_maxlen@
        /** 128 - 1(entry) - 1(col) - $m2c_gi_others(other indexes) = $m2c_node_maxlen */
@        end@
@    end@ # needlength
@    include node-storage.m2i@
@  end@ // foreach

@if $m2c_include_examples == 1@
    
    /*
     * this example code is based on a data source that is a
     * text file to be read and parsed.
     */
    FILE *filep;
    char line[MAX_LINE_SIZE];
@end@ // examples

    DEBUGMSGTL(("verbose:${context}:${context}_container_load","called\n"));

@if $m2c_include_examples == 1@
$example_start
    /*
     * open our data file.
     */
    filep = fopen("/etc/dummy.conf", "r");
    if(NULL ==  filep) {
        return MFD_RESOURCE_UNAVAILABLE;
    }

$example_end
@end@ // example
    /*
     * TODO:351:M: |-> Load/update data in the $context container.
     * loop over your $context data, allocate a rowreq context,
     * set the index(es) [and data, optionally] and insert into
     * the container.
     */
    while( 1 ) {
@   if $m2c_include_examples == 0@
        /*
         * check for end of data; bail out if there is no more data
         */
        if( 1 )
            break;
@   else@
$example_start
    /*
     * get a line (skip blank lines)
     */
    do {
        if (!fgets(line, sizeof(line), filep)) {
            /* we're done */
            fclose(filep);
            filep = NULL;
        }
    } while (filep && (line[0] == '\n'));

    /*
     * check for end of data
     */
    if(NULL == filep)
        break;

    /*
     * parse line into variables
     */
$example_end
@    end@ # example

        /*
         * TODO:352:M: |   |-> set indexes in new $context rowreq context.
@   eval $m2c_tmp = ""@
@   if ($m2c_data_allocate == 1) || ($m2c_data_init == 1)@
@      eval $m2c_tmp = "NULL"@
@      if ($m2c_data_allocate == 1) && ($m2c_data_init == 1)@
@         eval $m2c_tmp = "$m2c_tmp, NULL"@
         * data context will be set from the first param (unless NULL,
         *      in which case a new data context will be allocated)
         * the second param will be passed, with the row context, to
         *      ${context}rowreq_ctx_init.
@      else@
         * data context will be set from the param (unless NULL,
         *      in which case a new data context will be allocated)
@      @end@
@   end@
         */
        rowreq_ctx = ${context}_allocate_rowreq_ctx($m2c_tmp);
        if (NULL == rowreq_ctx) {
            snmp_log(LOG_ERR, "memory allocation failed\n");
            return MFD_RESOURCE_UNAVAILABLE;
        }
        if(MFD_SUCCESS != ${context}_indexes_set(rowreq_ctx
@   foreach $node index@
@      include m2c_setup_node.m2i@
@        if $m2c_node_needlength == 1@
                               , $node, ${node}_len
@        else@
                               , $node
@        end@
@   end@ # foreach index
               )) {
            snmp_log(LOG_ERR,"error setting index while loading "
                     "${context} data.\n");
            ${context}_release_rowreq_ctx(rowreq_ctx);
            continue;
        }

        /*
         * TODO:352:r: |   |-> populate $context data context.
         * Populate data context here. (optionally, delay until row prep)
         */
@if $m2c_data_transient == 0@ # persistent
    /* non-TRANSIENT data: no need to copy. set pointer to data */
@else@
    /*
     * TRANSIENT or semi-TRANSIENT data:
     * copy data or save any info needed to do it in row_prep.
     */
@   foreach $node nonindex@
@      include m2c_setup_node.m2i@
    /*
     * setup/save data for $node
     * $m2c_node_summary
     */
@      if "$m2c_data_context" eq "generated"@
@         eval $m2c_ctx_lh = "$m2c_ctx_rh"@
@         eval $m2c_ctx_lhs = "$m2c_ctx_rhs"@
@         eval $m2c_ctx_rh = "$node"@
@         eval $m2c_ctx_rhs = "${node}_len"@
@         include generic-value-map.m2i@
    
@      end@ # data_context ! generated
@   end@ // for each
@end@ # transient
        
        /*
         * insert into table container
         */
        CONTAINER_INSERT(container, rowreq_ctx);
        ++count;
    }
@if $m2c_include_examples == 1@

$example_start
    if(NULL != filep)
        fclose(filep);
$example_end
@end@ # example

    DEBUGMSGT(("verbose:${context}:${context}_container_load",
               "inserted %" NETSNMP_PRIz "u records\n", count));

    return MFD_SUCCESS;
} /* ${context}_container_load */

/**
 * container clean up
 *
 * @param container container with all current items
 *
 *  This optional callback is called prior to all
 *  item's being removed from the container. If you
 *  need to do any processing before that, do it here.
 *
 * @note
 *  The MFD helper will take care of releasing all the row contexts.
@   if ($m2c_data_allocate == 1) && ($m2c_data_transient == 0)@
 *  If you did not pass a data context pointer when allocating
 *  the rowreq context, the one that was allocated will be deleted.
 *  If you did pass one in, it will not be deleted and that memory
 *  is your responsibility.
@   end@
 *
 */
void
${context}_container_free(netsnmp_container *container)
{
    DEBUGMSGTL(("verbose:${context}:${context}_container_free","called\n"));

    /*
     * TODO:380:M: Free $context container data.
     */
} /* ${context}_container_free */

@end@ // m2c_processing_type eq 'c'
########################################################################
##//####################################################################
##//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
##//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@if $m2c_processing_type eq 'i'@
@   if $m2c_data_cache == 1@
static void _container_free(netsnmp_container *container);

/**
 * @internal
 */
static int
_cache_load(netsnmp_cache *cache, void *vmagic)
{
    DEBUGMSGTL(("internal:${context}:_cache_load","called\n"));

    if((NULL == cache) || (NULL == cache->magic)) {
        snmp_log(LOG_ERR, "invalid cache for ${context}_cache_load\n");
        return -1;
    }

    /** should only be called for an invalid or expired cache */
    netsnmp_assert((0 == cache->valid) || (1 == cache->expired));
    
    /*
     * call user code
     */
    return ${context}_container_load((netsnmp_container*)cache->magic);
} /* _cache_load */

/**
 * @internal
 */
static void
_cache_free(netsnmp_cache *cache, void *magic)
{
    netsnmp_container *container;

    DEBUGMSGTL(("internal:${context}:_cache_free","called\n"));

    if((NULL == cache) || (NULL == cache->magic)) {
        snmp_log(LOG_ERR, "invalid cache in ${context}_cache_free\n");
        return;
    }

    container = (netsnmp_container*)cache->magic;

    _container_free(container);
} /* _cache_free */

@   end@ # cache
/**
 * @internal
 */
static void
_container_item_free(${context}_rowreq_ctx *rowreq_ctx, void *context)
{
    DEBUGMSGTL(("internal:${context}:_container_item_free","called\n"));

    if(NULL == rowreq_ctx)
        return;

    ${context}_release_rowreq_ctx(rowreq_ctx);
} /* _container_item_free */

/**
 * @internal
 */
static void
_container_free(netsnmp_container *container)
{
    DEBUGMSGTL(("internal:${context}:_container_free","called\n"));

    if (NULL == container) {
        snmp_log(LOG_ERR, "invalid container in ${context}_container_free\n");
        return;
    }

    /*
     * call user code
     */
    ${context}_container_free(container);
    
    /*
     * free all items. inefficient, but easy.
     */
    CONTAINER_CLEAR(container, _container_item_free, NULL);
} /* _container_free */

/**
 * @internal
 * initialize the container with functions or wrappers
 */
void
_${context}_container_init(${context}_interface_ctx *if_ctx)
{
    DEBUGMSGTL(("internal:${context}:_${context}_container_init","called\n"));

@   if $m2c_data_cache == 1@
    /*
     * cache init
     */
@    if 0@
    if_ctx->cache =
        netsnmp_cache_find_by_oid(PARTNER_oid, OID_LENGTH(PARTNER_oid));
@    else@
    if_ctx->cache = netsnmp_cache_create(30, /* timeout in seconds */
                                         _cache_load, _cache_free,
                                         ${context}_oid,
                                         ${context}_oid_size);
@    end@ // shared cache

    if(NULL == if_ctx->cache) {
        snmp_log(LOG_ERR, "error creating cache for ${context}\n");
        return;
    }

    if_ctx->cache->flags = NETSNMP_CACHE_DONT_INVALIDATE_ON_SET;

    ${context}_container_init(&if_ctx->container, if_ctx->cache);
@   else@
    /*
     * container init
     */    
    ${context}_container_init(&if_ctx->container);
@   end@ data cache
    if(NULL == if_ctx->container) {
        if_ctx->container = netsnmp_container_find("${context}:table_container");
        if(NULL != if_ctx->container) {
            /*
             * When reporting container management errors, we log the container's
             * name, so set it here.
             */
            if_ctx->container->container_name = strdup("${context}");
        }
    }
    if(NULL == if_ctx->container) {
        snmp_log(LOG_ERR,"error creating container in "
                 "${context}_container_init\n");
        return;
    }

@   if $m2c_data_cache == 1@
    if (NULL != if_ctx->cache)
        if_ctx->cache->magic = (void*)if_ctx->container;
@   end@
} /* _${context}_container_init */

/**
 * @internal
 * shutdown the container with functions or wrappers
 */
void
_${context}_container_shutdown(${context}_interface_ctx *if_ctx)
{
    DEBUGMSGTL(("internal:${context}:_${context}_container_shutdown","called\n"));

    ${context}_container_shutdown(if_ctx->container);

    _container_free(if_ctx->container);

} /* _${context}_container_shutdown */

@end@ // m2c_processing_type eq 'i'
########################################################################
##//####################################################################
##//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
##//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@if $m2c_processing_type eq 'r'@
##
  container summary
  ------------------------
    The container data access code is for cases when you want to
    store your data in the agent/sub-agent.

    ... to be continued...


@   if $m2c_data_cache == 1@
  cache summary
  ------------------------
    The container-cached data access code is for cases when you want to
    cache your data in the agent/sub-agent.

    ... to be continued...


@   end@
@end@ // m2c_processing_type eq 'r'
########################################################################
##//####################################################################
@if $m2c_mark_boundary == 1@
/** END code generated by mfd-access-container-cached-defines.m2i */
@end@
