## -*- c -*-
#
@if "$cache" eq "" @
@open -@
This framework can work in one of two ways:

  1)  Hold a local cached copy of some external data
      which is then used to service incoming requests.

  2)  Hold the data for the table solely within the agent

Which is more appropriate for your needs?
    @prompt $ans Select your choice : @
    @if $ans == 1 @
      @eval $cache = 1@
    @elsif $ans == 2@
      @eval $cache = 0@
    @else@
Invalid answer - generating caching code
      @eval $cache = 1@
    @end@
@else@       #  -S cache={something}
    @if "$cache" ne "1" && "$cache" ne "0" @
@open -@
Invalid value for 'cache' ($cache) - generating caching code
      @eval $cache = 1@
    @end@
@end@

######################################################################
## Do the .h file
######################################################################
@open ${name}.h@
/*
 * Note: this file originally auto-generated by mib2c
 * using mib2c.table_data.conf
 */
#ifndef $name.uc_H
#define $name.uc_H

/* function declarations */
void init_$name(void);
@foreach $i table@
void initialize_table_$i(void);
Netsnmp_Node_Handler ${i}_handler;
@if "$cache" == 1 @
NetsnmpCacheLoad ${i}_load;
NetsnmpCacheFree ${i}_free;
#define $i.uc_TIMEOUT  60
@end@
@end@
@foreach $i table@

/* column number definitions for table $i */
    @foreach $c column@
       #define COLUMN_$c.uc		$c.subid
    @end@
@end@
#endif /* $name.uc_H */
######################################################################
## Do the .c file
######################################################################
@open ${name}.c@
/*
 * Note: this file originally auto-generated by mib2c
 * using mib2c.table_data.conf
 */

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "${name}.h"

/** Initializes the $name module */
void
init_$name(void)
{
  /* here we initialize all the tables we're planning on supporting */
  @foreach $i table@
    initialize_table_$i();
  @end@
}

@foreach $i table@
  ## Determine the first/last column names
  @eval $first_column = "-"@
  @eval $last_column = "-"@
  @foreach $c column@
    @if $c.readable@
      @if "$first_column" eq "-"@
        @eval $first_column = $c@
      @end@
      @eval $last_column = $c@
    @end@
  @end@

/** Initialize the $i table by defining its contents and how it's structured */
void
initialize_table_$i(void)
{
    const oid ${i}_oid[] = {$i.commaoid};
    const size_t ${i}_oid_len   = OID_LENGTH(${i}_oid);
    netsnmp_handler_registration    *reg;
    netsnmp_tdata                   *table_data;
    netsnmp_table_registration_info *table_info;
@if "$cache" == 1 @
    netsnmp_cache                   *cache;
@end@

    DEBUGMSGTL(("${name}:init", "initializing table $i\n"));

    reg = netsnmp_create_handler_registration(
              "$i",     ${i}_handler,
              ${i}_oid, ${i}_oid_len,
@if $i.settable@
              HANDLER_CAN_RWRITE
@else@
              HANDLER_CAN_RONLY
@end@
              );

    table_data = netsnmp_tdata_create_table( "$i", 0 );
    if (NULL == table_data) {
        snmp_log(LOG_ERR,"error creating tdata table for $i\n");
        return;
    }
@if "$cache" == 1 @
    cache = netsnmp_cache_create($i.uc_TIMEOUT,
                                  ${i}_load, ${i}_free,
                                  ${i}_oid, ${i}_oid_len);
    if (NULL == cache) {
        snmp_log(LOG_ERR,"error creating cache for $i\n");
    }
    else
        cache->magic = (void *)table_data;
@end@
    table_info = SNMP_MALLOC_TYPEDEF( netsnmp_table_registration_info );
    if (NULL == table_info) {
        snmp_log(LOG_ERR,"error creating table info for $i\n");
        return;
    }
    netsnmp_table_helper_add_indexes(table_info,
    @foreach $idx index@
                           $idx.type,  /* index: $idx */
    @end@
                           0);

    table_info->min_column = COLUMN_$first_column.uc;
    table_info->max_column = COLUMN_$last_column.uc;
    
    netsnmp_tdata_register( reg, table_data, table_info );
@if "$cache" == 1 @
    if (cache) 
        netsnmp_inject_handler_before( reg, netsnmp_cache_handler_GET(cache),
                                       TABLE_TDATA_NAME);
@end@

    /* Initialise the contents of the table here */
}

    /* Typical data structure for a row entry */
struct ${i}_entry {
    /* Index values */
    @foreach $idx index@
     @if $idx.needlength@
    $idx.decl $idx[NNN];
    size_t ${idx}_len;
     @else@
    $idx.decl $idx;
     @end@
    @end@

    /* Column values */
    @foreach $c column@
    @if $c.readable@
     @if $c.needlength@
    $c.decl $c[NNN];
    size_t ${c}_len;
     @else@
    $c.decl $c;
     @end@
     @if $c.settable@
      @if !$c.rowstatus@
       @if $c.needlength@
    $c.decl old_$c[NNN];
    size_t old_${c}_len;
       @else@
    $c.decl old_$c;
       @end@
      @end@
     @end@
    @end@
    @end@

    int   valid;
};

/* create a new row in the table */
netsnmp_tdata_row *
${i}_createEntry(netsnmp_tdata *table_data
  @foreach $idx index@
    @if $idx.needlength@
                 , $idx.decl* $idx
                 , size_t ${idx}_len
    @else@
                 , $idx.decl  $idx
    @end@
  @end@
                ) {
    struct ${i}_entry *entry;
    netsnmp_tdata_row *row;

    entry = SNMP_MALLOC_TYPEDEF(struct ${i}_entry);
    if (!entry)
        return NULL;

    row = netsnmp_tdata_create_row();
    if (!row) {
        SNMP_FREE(entry);
        return NULL;
    }
    row->data = entry;

    DEBUGMSGT(("${i}:entry:create", "row 0x%x\n", (uintptr_t)row));
  @foreach $idx index@
   @if $idx.needlength@
    memcpy(entry->$idx, $idx, ${idx}_len);
    entry->${idx}_len = ${idx}_len;
    netsnmp_tdata_row_add_index( row, $idx.type,
                                 entry->$idx, ${idx}_len);
   @else@
    entry->$idx = $idx;
    netsnmp_tdata_row_add_index( row, $idx.type,
                                 &(entry->$idx),
                                 sizeof(entry->$idx));
   @end@
  @end@
    if (table_data)
        netsnmp_tdata_add_row( table_data, row );
    return row;
}

/* remove a row from the table */
void
${i}_removeEntry(netsnmp_tdata     *table_data, 
                 netsnmp_tdata_row *row) {
    struct ${i}_entry *entry;

    if (!row)
        return;    /* Nothing to remove */

    DEBUGMSGT(("${i}:entry:remove", "row 0x%x\n", (uintptr_t)row));

    entry = (struct ${i}_entry *)row->data;
    SNMP_FREE( entry );   /* XXX - release any other internal resources */

    if (table_data)
        netsnmp_tdata_remove_and_delete_row( table_data, row );
    else
        netsnmp_tdata_delete_row( row );    
}

@if "$cache" == 1 @
/* Example cache handling - set up table_data list from a suitable file */
int
${i}_load( netsnmp_cache *cache, void *vmagic ) {
    netsnmp_tdata     *table = (netsnmp_tdata *)vmagic;
    netsnmp_tdata_row *row;
    struct ${i}_entry *this;
    FILE *fp;
    char buf[STRMAX];
  @foreach $idx index@
    @if $idx.needlength@
    $idx.decl* $idx;
    size_t ${idx}_len;
    @else@
    $idx.decl  $idx;
    @end@
  @end@

    /* The basic load routine template assumes that the data to
       be reported is held in a file - with one row of the file
       for each row of the table.
          If your data is available via a different API, you
       should amend this initial block (and the control of the
       'while' loop) accordingly.
          'XXX' marks places where the template is incomplete and
       code will definitely need to be added. */

    fp = fopen( "/data/for/${i}", "r" );
    if ( !fp ) {
        return -1;
    }
    while ( fgets( buf, STRMAX, fp )) {
        /* XXX - Unpick 'buf' to extract the individual field values
                 (or at least the index values for this row) ... */
        row = ${i}_createEntry(table
  @foreach $idx index@
    @if $idx.needlength@
                         , $idx
                         , ${idx}_len
    @else@
                         , $idx
    @end@
  @end@
                        );
        if (row == NULL)
            continue;
        this = (struct ${i}_entry *)row->entry;
        /* XXX - ... and then populate the 'this' data structure with
                 column values (typically) extracted from 'buf' above */
    }
    fclose(fp);
    return 0;  /* OK */
}

void
${i}_free( netsnmp_cache *cache, void *vmagic ) {
    netsnmp_tdata     *table = (netsnmp_tdata *)vmagic;
    netsnmp_tdata_row *this;

    while ((this = netsnmp_tdata_get_first_row(table))) {
        netsnmp_tdata_remove_and_delete_row(table, this);
    }
}
@end@

/** handles requests for the $i table */
int
${i}_handler(
    netsnmp_mib_handler               *handler,
    netsnmp_handler_registration      *reginfo,
    netsnmp_agent_request_info        *reqinfo,
    netsnmp_request_info              *requests) {

    netsnmp_request_info       *request;
    netsnmp_table_request_info *table_info;
    netsnmp_tdata              *table_data;
    netsnmp_tdata_row          *table_row;
    struct ${i}_entry          *table_entry;
    int                         ret;

    DEBUGMSGTL(("${name}:handler", "Processing request (%d)\n", reqinfo->mode));

    switch (reqinfo->mode) {
        /*
         * Read-support (also covers GetNext requests)
         */
    case MODE_GET:
        for (request=requests; request; request=request->next) {
            if (request->processed)
                continue;

            table_entry = (struct ${i}_entry *)
                              netsnmp_tdata_extract_entry(request);
            table_info  =     netsnmp_extract_table_info( request);
    
            switch (table_info->colnum) {
            @foreach $c column@
            @if $c.readable@
            case COLUMN_$c.uc:
                if ( !table_entry ) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
            @if $c.needlength@
                snmp_set_var_typed_value( request->requestvb, $c.type,
                                          table_entry->$c,
                                          table_entry->${c}_len);
            @else@
                snmp_set_var_typed_integer( request->requestvb, $c.type,
                                            table_entry->$c);
            @end@
                break;
            @end@
            @end@
            default:
                netsnmp_set_request_error(reqinfo, request,
                                          SNMP_NOSUCHOBJECT);
                break;
            }
        }
        break;

@if $i.settable@
        /*
         * Write-support
         */
    case MODE_SET_RESERVE1:
        for (request=requests; request; request=request->next) {
            if (request->processed)
                continue;

            table_entry = (struct ${i}_entry *)
                              netsnmp_tdata_extract_entry(request);
            table_info  =     netsnmp_extract_table_info( request);
    
            switch (table_info->colnum) {
            @foreach $c column@
            @if $c.settable@
            case COLUMN_$c.uc:
            @if $c.rowstatus@
                ret = netsnmp_check_vb_rowstatus(request->requestvb,
                         (table_entry ? RS_ACTIVE : RS_NONEXISTENT ));
            @else@
            @if $c.needlength@
	        /* or possibly 'netsnmp_check_vb_type_and_size' */
                ret = netsnmp_check_vb_type_and_max_size(
                          request->requestvb, $c.type, sizeof(table_entry->$c));
            @else@
                /* or possibly 'netsnmp_check_vb_int_range' */
                ret = netsnmp_check_vb_int( request->requestvb );
            @end@
            @end@
                if ( ret != SNMP_ERR_NOERROR ) {
                    netsnmp_set_request_error( reqinfo, request, ret );
                    return SNMP_ERR_NOERROR;
                }
                break;
            @end@
            @end@
            default:
                netsnmp_set_request_error( reqinfo, request,
                                           SNMP_ERR_NOTWRITABLE );
                return SNMP_ERR_NOERROR;
            }
        }
        break;

    case MODE_SET_RESERVE2:
@if $i.creatable@
        for (request=requests; request; request=request->next) {
            if (request->processed)
                continue;

            table_row  = netsnmp_tdata_extract_row(  request);
            table_data = netsnmp_tdata_extract_table(request);
            table_info = netsnmp_extract_table_info( request);
    
            switch (table_info->colnum) {
@if $i.rowstatus@
            @foreach $c column@
            @if $c.rowstatus@
            case COLUMN_$c.uc:
                switch (*request->requestvb->val.integer) {
                case RS_CREATEANDGO:
                case RS_CREATEANDWAIT:
                    table_row = ${i}_createEntry(table_data
  @foreach $idx index@
    @if $idx.needlength@
                        ,  table_info->indexes->val.string
                        ,  table_info->indexes->val_len
    @else@
                        , *table_info->indexes->val.integer
    @end@
  @end@
                        );
                    if (table_row) {
                        netsnmp_insert_tdata_row( request, table_row );
                    } else {
                        netsnmp_set_request_error( reqinfo, request,
                                                   SNMP_ERR_RESOURCEUNAVAILABLE );
                        return SNMP_ERR_NOERROR;
                    }
                }
            @end@
            @end@
@else@
            @foreach $c column@
            @if $c.creatable@
            case COLUMN_$c.uc:
            @end@
            @end@
                if ( !table_row ) {
                    table_row = ${i}_createEntry(table_data
  @foreach $idx index@
    @if $idx.needlength@
                        ,  table_info->indexes->val.string
                        ,  table_info->indexes->val_len
    @else@
                        , *table_info->indexes->val.integer
    @end@
  @end@
                        );
                    if (table_row) {
                        netsnmp_insert_tdata_row( request, table_row );
                    } else {
                        netsnmp_set_request_error( reqinfo, request,
                                                   SNMP_ERR_RESOURCEUNAVAILABLE );
                        return SNMP_ERR_NOERROR;
                    }
                }
                break;
@end@
            }
        }
@end@
        break;

    case MODE_SET_FREE:
@if $i.creatable@
        for (request=requests; request; request=request->next) {
            if (request->processed)
                continue;

            table_entry = (struct ${i}_entry *)
                              netsnmp_tdata_extract_entry(request);
            table_row   =     netsnmp_tdata_extract_row(  request);
            table_data  =     netsnmp_tdata_extract_table(request);
            table_info  =     netsnmp_extract_table_info( request);
    
            switch (table_info->colnum) {
@if $i.rowstatus@
            @foreach $c column@
            @if $c.rowstatus@
            case COLUMN_$c.uc:
                switch (*request->requestvb->val.integer) {
                case RS_CREATEANDGO:
                case RS_CREATEANDWAIT:
                    if (table_entry && !table_entry->valid) {
                        ${i}_removeEntry(table_data, table_row );
                    }
                }
            @end@
            @end@
@else@
            @foreach $c column@
            @if $c.creatable@
            case COLUMN_$c.uc:
            @end@
            @end@
                if ( table_entry && !table_entry->valid ) {
                    ${i}_removeEntry(table_data, table_row );
                }
                break;
@end@
            }
        }
@end@
        break;

    case MODE_SET_ACTION:
        for (request=requests; request; request=request->next) {
            if (request->processed)
                continue;

            table_entry = (struct ${i}_entry *)
                              netsnmp_tdata_extract_entry(request);
            table_info  =     netsnmp_extract_table_info( request);
    
            switch (table_info->colnum) {
            @foreach $c column@
            @if $c.settable@
            @if !$c.rowstatus@
            case COLUMN_$c.uc:
                @if $c.needlength@
                memcpy( table_entry->old_$c,
                        table_entry->$c,
                        sizeof(table_entry->$c));
                table_entry->old_${c}_len =
                        table_entry->${c}_len;
                memset( table_entry->$c, 0,
                        sizeof(table_entry->$c));
                memcpy( table_entry->$c,
                        request->requestvb->val.string,
                        request->requestvb->val_len);
                table_entry->${c}_len =
                        request->requestvb->val_len;
                @else@
                table_entry->old_$c = table_entry->$c;
                table_entry->$c     = *request->requestvb->val.integer;
                @end@
                break;
            @end@
            @end@
            @end@
            }
        }
@if $i.rowstatus@
        /* Check the internal consistency of an active row */
        for (request=requests; request; request=request->next) {
            table_entry = (struct ${i}_entry *)
                              netsnmp_tdata_extract_entry(request);
            table_info  =     netsnmp_extract_table_info( request);
    
            switch (table_info->colnum) {
            @foreach $c column@
            @if $c.rowstatus@
            case COLUMN_$c.uc:
                switch (*request->requestvb->val.integer) {
                case RS_ACTIVE:
                case RS_CREATEANDGO:
                    if (/* XXX */) {
                        netsnmp_set_request_error( reqinfo, request,
                                                   SNMP_ERR_INCONSISTENTVALUE );
                        return SNMP_ERR_NOERROR;
                    }
                }
            @end@
            @end@
            }
        }
@end@
        break;

    case MODE_SET_UNDO:
        for (request=requests; request; request=request->next) {
            if (request->processed)
                continue;

            table_entry = (struct ${i}_entry *)
                              netsnmp_tdata_extract_entry(request);
            table_row   =     netsnmp_tdata_extract_row(  request);
            table_data  =     netsnmp_tdata_extract_table(request);
            table_info  =     netsnmp_extract_table_info( request);
    
            switch (table_info->colnum) {
            @foreach $c column@
            @if $c.settable@
            case COLUMN_$c.uc:
@if $i.rowstatus@
  @if $c.rowstatus@
                switch (*request->requestvb->val.integer) {
                case RS_CREATEANDGO:
                case RS_CREATEANDWAIT:
                    if (table_entry && !table_entry->valid) {
                        ${i}_removeEntry(table_data, table_row );
                    }
                }
  @else@
                @if $c.needlength@
                memcpy( table_entry->$c,
                        table_entry->old_$c,
                        sizeof(table_entry->$c));
                memset( table_entry->old_$c, 0,
                        sizeof(table_entry->$c));
                table_entry->${c}_len =
                        table_entry->old_${c}_len;
                @else@
                table_entry->$c     = table_entry->old_$c;
                table_entry->old_$c = 0;
                @end@
  @end@
@else@
  @if $c.creatable@
                if ( table_entry && !table_entry->valid ) {
                    ${i}_removeEntry(table_data, table_row );
                } else {
                    @if $c.needlength@
                    memcpy( table_entry->$c,
                            table_entry->old_$c,
                            sizeof(table_entry->$c));
                    memset( table_entry->old_$c, 0,
                            sizeof(table_entry->$c));
                    table_entry->${c}_len =
                            table_entry->old_${c}_len;
                    @else@
                    table_entry->$c     = table_entry->old_$c;
                    table_entry->old_$c = 0;
                    @end@
                }
  @else@
                @if $c.needlength@
                memcpy( table_entry->$c,
                        table_entry->old_$c,
                        sizeof(table_entry->$c));
                memset( table_entry->old_$c, 0,
                        sizeof(table_entry->$c));
                table_entry->${c}_len =
                        table_entry->old_${c}_len;
                @else@
                table_entry->$c     = table_entry->old_$c;
                table_entry->old_$c = 0;
                @end@
  @end@
@end@
                break;
            @end@
            @end@
            }
        }
        break;

    case MODE_SET_COMMIT:
@if $i.creatable@
        for (request=requests; request; request=request->next) {
            if (request->processed)
                continue;

            table_entry = (struct ${i}_entry *)
                              netsnmp_tdata_extract_entry(request);
@if $i.rowstatus@
            table_row   =     netsnmp_tdata_extract_row(  request);
            table_data  =     netsnmp_tdata_extract_table(request);
@end@
            table_info  =     netsnmp_extract_table_info(    request);
    
            switch (table_info->colnum) {
@if $i.rowstatus@
            @foreach $c column@
            @if $c.rowstatus@
            case COLUMN_$c.uc:
                switch (*request->requestvb->val.integer) {
                case RS_CREATEANDGO:
                    table_entry->valid = 1;
                    /* Fall-through */
                case RS_ACTIVE:
                    table_entry->$c = RS_ACTIVE;
                    break;

                case RS_CREATEANDWAIT:
                    table_entry->valid = 1;
                    /* Fall-through */
                case RS_NOTINSERVICE:
                    table_entry->$c = RS_NOTINSERVICE;
                    break;

                case RS_DESTROY:
                    ${i}_removeEntry(table_data, table_row );
                }
            @end@
            @end@
@else@
            @foreach $c column@
            @if $c.creatable@
            case COLUMN_$c.uc:
            @end@
            @end@
                if ( table_entry && !table_entry->valid ) {
                    table_entry->valid = 1;
                }
@end@
            }
        }
@end@
        break;
@end@
    }
    return SNMP_ERR_NOERROR;
}
@end@
