## -*- c -*-
## Portions of this file ar Copyright (C) Apple, Inc.
## Use is subject to license terms specified in the COPYING file
## distributed with the Net-SNMP package.
######################################################################
## Do the .h file
######################################################################
@open ${name}.h@
/*
 * Note: this file originally auto-generated by mib2c using
 *  $Id$
 */
#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;
@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
 *  $Id$
 */

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

#ifdef ${i.uc}_USE_CACHE
static void _cache_free(netsnmp_cache * cache, void *magic);
static int _cache_load(netsnmp_cache * cache, void *vmagic);
#endif

/** 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 = NULL;
    netsnmp_mib_handler             *handler = NULL;
    netsnmp_container               *container = NULL;
    netsnmp_table_registration_info *table_info = NULL;
#ifdef ${i.uc}_USE_CACHE
    netsnmp_cache  *cache = NULL;
#endif

    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@
              );
    if (NULL == reg) {
        snmp_log(LOG_ERR,"error creating handler registration for $i\n");
        goto bail;
    }
@if $i.settable@
    /** should a set on a non-existent row create a new one? */
    /** reg->modes |= HANDLER_CAN_NOT_CREATE; */
@end@

    container  = netsnmp_container_find( "$i:table_container" );
    if (NULL == container) {
        snmp_log(LOG_ERR,"error creating container for $i\n");
        goto bail;
    }

    table_info = SNMP_MALLOC_TYPEDEF( netsnmp_table_registration_info );
    if (NULL == table_info) {
        snmp_log(LOG_ERR,"error allocating table registration for $i\n");
        goto bail;
    }
    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;
    
    /*************************************************
     *
     * inject container_table helper
     */
    handler = netsnmp_container_table_handler_get(table_info, container,
                                                  TABLE_CONTAINER_KEY_NETSNMP_INDEX);
    if (NULL == handler) {
        snmp_log(LOG_ERR,"error allocating table registration for $i\n");
        goto bail;
    }
    if (SNMPERR_SUCCESS != netsnmp_inject_handler(reg, handler)) {
        snmp_log(LOG_ERR,"error injecting container_table handler for $i\n");
        goto bail;
    }
    handler = NULL; /* reg has it, will reuse below */

#ifdef ${i.uc}_USE_CACHE
    /*************************************************
     *
     * inject cache helper
     */
    cache = netsnmp_cache_create(30,    /* timeout in seconds */
                                 _cache_load, _cache_free,
                                 ${i}_oid, ${i}_oid_len);

    if (NULL == cache) {
        snmp_log(LOG_ERR, "error creating cache for $i\n");
        goto bail;
    }
    cache->flags = NETSNMP_CACHE_DONT_INVALIDATE_ON_SET;
    cache->magic = container;

    handler = netsnmp_cache_handler_get(cache);
    if (NULL == handler) {
        snmp_log(LOG_ERR, "error creating cache handler for $i\n");
        goto bail;
    }

    if (SNMPERR_SUCCESS != netsnmp_inject_handler(reg, handler)) {
        snmp_log(LOG_ERR,"error injecting cache handler for $i\n");
        goto bail;
    }
    handler = NULL; /* reg has it*/
#endif

    /*
     * register the table
     */
    if (SNMPERR_SUCCESS != netsnmp_register_table(reg, table_info)) {
        snmp_log(LOG_ERR,"error registering table handler for $i\n");
        goto bail;
    }

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

    
    return; /* ok */

    /*
     * Some error occurred during registration. Clean up and bail.
     */
  bail: /* not ok */
    
    if (handler)
        netsnmp_handler_free(handler);

#ifdef ${i.uc}_USE_CACHE
    if (cache)
        netsnmp_cache_free(cache);
#endif
    
    if (table_info)
        netsnmp_table_registration_info_free(table_info);

    if (container)
        CONTAINER_FREE(container);

    if (reg) 
        netsnmp_handler_registration_free(reg);
}

/** Typical data structure for a row entry */
typedef struct ${i}_entry_s {
    netsnmp_index oid_index;

    /* 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;
} ${i}_entry;

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

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

  @foreach $idx index@
   @if $idx.needlength@
    memcpy(entry->$idx, $idx, ${idx}_len);
    entry->${idx}_len = ${idx}_len;
   @else@
    entry->$idx = $idx;
   @end@
  @end@
    entry->oid_index.len  = XXX;
    entry->oid_index.oids = YYY;
    CONTAINER_INSERT( container, entry );
    return entry;
}

/** remove a row from the table */
void
${i}_removeEntry(netsnmp_container *container, 
                 ${i}_entry *entry) {

    if (!entry)
        return;    /* Nothing to remove */
    CONTAINER_REMOVE( container, entry );
    if (entry)
        SNMP_FREE( entry );   /* XXX - release any other internal resources */
}

/** remove a row from the table */
void
${i}_freeEntry(${i}_entry *entry) {

    if (!entry)
        return;    /* Nothing to remove */
    SNMP_FREE( entry );   /* XXX - release any other internal resources */
}

/** 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_container          *container;
    ${i}_entry                 *table_entry;
@if $i.settable@
    int                         ret;
@end@

    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 = (${i}_entry *)
                              netsnmp_container_table_extract_context(request);
            table_info  =     netsnmp_extract_table_info(request);
            if ((NULL == table_entry) || (NULL == table_info)) {
                snmp_log(LOG_ERR,
                         "could not extract table entry or info for $i\n");
                snmp_set_var_typed_value(request->requestvb, SNMP_ERR_GENERR,
                                         NULL, 0);
                continue;
            }
    
            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 = (${i}_entry *)
                              netsnmp_container_table_extract_context(request);
            table_info  =     netsnmp_extract_table_info(request);
            if ((NULL == table_entry) || (NULL == table_info)) {
                snmp_log(LOG_ERR,
                         "could not extract table entry or info for $i\n");
                snmp_set_var_typed_value(request->requestvb, SNMP_ERR_GENERR,
                                         NULL, 0);
                continue;
            }
    
            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_entry = (${i}_entry *)
                              netsnmp_container_table_extract_context(request);
            table_info  =     netsnmp_extract_table_info(request);
            if ((NULL == table_entry) || (NULL == table_info)) {
                snmp_log(LOG_ERR,
                         "could not extract table entry or info for $i\n");
                snmp_set_var_typed_value(request->requestvb, SNMP_ERR_GENERR,
                                         NULL, 0);
                continue;
            }
    
            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:
                    container   =     netsnmp_container_table_extract(request);
                    if (NULL == container) {
                        snmp_log(LOG_ERR,
                                 "could not extract table container for $i\n");
                        snmp_set_var_typed_value(request->requestvb,
                                                 SNMP_ERR_GENERR, NULL, 0);
                        continue;
                    }
                    table_entry = ${i}_createEntry(container
  @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_entry) {
                        netsnmp_container_table_insert_row( request, table_entry );
                    } 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_entry ) {
                    container   =     netsnmp_container_table_extract(request);
                    if (NULL == container) {
                        snmp_log(LOG_ERR,
                                 "could not extract table container for $i\n");
                        snmp_set_var_typed_value(request->requestvb,
                                                 SNMP_ERR_GENERR, NULL, 0);
                        continue;
                    }
                    table_entry = ${i}_createEntry(container
  @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_entry) {
                        netsnmp_container_table_insert_row( request, table_entry );
                    } 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;
            container   =     netsnmp_container_table_extract(request);
            if (NULL == container) {
                snmp_log(LOG_ERR,
                         "could not extract table container for $i\n");
                snmp_set_var_typed_value(request->requestvb,
                                         SNMP_ERR_GENERR, NULL, 0);
                continue;
            }
            table_entry = (${i}_entry *)
                              netsnmp_container_table_extract_context(request);
            table_info  =     netsnmp_extract_table_info(request);
            if ((NULL == table_entry) || (NULL == table_info)) {
                snmp_log(LOG_ERR,
                         "could not extract table entry or info for $i\n");
                snmp_set_var_typed_value(request->requestvb, SNMP_ERR_GENERR,
                                         NULL, 0);
                continue;
            }
    
            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(container, table_entry );
                    }
                }
            @end@
            @end@
@else@
            @foreach $c column@
            @if $c.creatable@
            case COLUMN_$c.uc:
            @end@
            @end@
                if ( table_entry && !table_entry->valid ) {
                    ${i}_removeEntry(container, table_entry );
                }
                break;
@end@
            }
        }
@end@
        break;

    case MODE_SET_ACTION:
        for (request=requests; request; request=request->next) {
            if (request->processed)
               continue;
            table_entry = (${i}_entry *)
                              netsnmp_container_table_extract_context(request);
            table_info  =     netsnmp_extract_table_info(request);
            if ((NULL == table_entry) || (NULL == table_info)) {
                snmp_log(LOG_ERR,
                         "could not extract table entry or info for $i\n");
                snmp_set_var_typed_value(request->requestvb, SNMP_ERR_GENERR,
                                         NULL, 0);
                continue;
            }
    
            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) {
            if (request->processed)
               continue;
            table_entry = (${i}_entry *)
                              netsnmp_container_table_extract_context(request);
            table_info  =     netsnmp_extract_table_info(request);
            if ((NULL == table_entry) || (NULL == table_info)) {
                snmp_log(LOG_ERR,
                         "could not extract table entry or info for $i\n");
                snmp_set_var_typed_value(request->requestvb, SNMP_ERR_GENERR,
                                         NULL, 0);
                continue;
            }
    
            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;
            container   =     netsnmp_container_table_extract(request);
            if (NULL == container) {
                snmp_log(LOG_ERR,
                         "could not extract table container for $i\n");
                snmp_set_var_typed_value(request->requestvb,
                                         SNMP_ERR_GENERR, NULL, 0);
                continue;
            }
            table_entry = (${i}_entry *)
                              netsnmp_container_table_extract_context(request);
            table_info  =     netsnmp_extract_table_info(request);
            if ((NULL == table_entry) || (NULL == table_info)) {
                snmp_log(LOG_ERR,
                         "could not extract table entry or info for $i\n");
                snmp_set_var_typed_value(request->requestvb, SNMP_ERR_GENERR,
                                         NULL, 0);
                continue;
            }
    
            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(container, table_entry );
                    }
                }
  @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(container, 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 = (${i}_entry *)
                              netsnmp_container_table_extract_context(request);
            table_info  =     netsnmp_extract_table_info(request);
            if ((NULL == table_entry) || (NULL == table_info)) {
                snmp_log(LOG_ERR,
                         "could not extract table entry or info for $i\n");
                snmp_set_var_typed_value(request->requestvb, SNMP_ERR_GENERR,
                                         NULL, 0);
                continue;
            }
    
            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:
                    container = netsnmp_container_table_extract(request);
                    if (NULL == container) {
                        snmp_log(LOG_ERR,
                                 "could not extract table container for $i\n");
                        snmp_set_var_typed_value(request->requestvb,
                                                 SNMP_ERR_GENERR, NULL, 0);
                        continue;
                    }
                    ${i}_removeEntry(container, table_entry );
                }
            @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;
}

#ifdef ${i.uc}_USE_CACHE
/**
 * @internal
 */
static int
_cache_load(netsnmp_cache * cache, void *vmagic)
{
    netsnmp_container *container;
    
    DEBUGMSGTL(("internal:${i}:_cache_load", "called\n"));

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

    /** should only be called for an invalid or expired cache */
    netsnmp_assert((0 == cache->valid) || (1 == cache->expired));

    /*
     * load cache here (or call function to do it)
     */
    /** CONTAINER_INSERT(container, record); */

    return 0;
}                               /* _cache_load */

/**
 * @Internal
 */
/** remove a row from the table */
static void
${i}_freeEntry_cb(${i}_entry *entry, void *magic) {

    ${i}_freeEntry(entry);
}

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

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

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

    /*
     * empty (but don't free) cache here
     */
    CONTAINER_CLEAR(container,
                    (netsnmp_container_obj_func*)${i}_freeEntry_cb,
                    NULL);
}                               /* _cache_free */
#endif /* ${i.uc}_USE_CACHE */
@end@
