/*
 *  Host Resources MIB - storage group implementation - hrh_storage.c
 *
 */

#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/hardware/memory.h>
#include <net-snmp/agent/hardware/fsys.h>
#include "host_res.h"
#include "hrh_filesys.h"
#include "hrh_storage.h"
#include "hr_disk.h"
#include "hr_filesys.h"
#include <net-snmp/utilities.h>


#include <sys/types.h>
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef TIME_WITH_SYS_TIME
# ifdef WIN32
#  include <windows.h>
#  include <errno.h>
# else
#  include <sys/time.h>
# endif
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

#include <net-snmp/output_api.h>

#include <net-snmp/agent/agent_read_config.h>
#include <net-snmp/library/read_config.h>

#define HRSTORE_MONOTONICALLY_INCREASING

        /*********************
	 *
	 *  Kernel & interface information,
	 *   and internal forward declarations
	 *
	 *********************/


static void parse_storage_config(const char *, char *);

        /*********************
	 *
	 *  Initialisation & common implementation functions
	 *
	 *********************/
int             Get_Next_HR_Store(const struct netsnmp_fsys_info_s **entry);
void            Init_HR_Store(void);

Netsnmp_Node_Handler handle_memsize;


#define	HRSTORE_MEMSIZE		1
#define	HRSTORE_INDEX		2
#define	HRSTORE_TYPE		3
#define	HRSTORE_DESCR		4
#define	HRSTORE_UNITS		5
#define	HRSTORE_SIZE		6
#define	HRSTORE_USED		7
#define	HRSTORE_FAILS		8

struct variable2 hrstore_variables[] = {
    {HRSTORE_INDEX, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
     var_hrstore, 1, {1}},
    {HRSTORE_TYPE, ASN_OBJECT_ID, NETSNMP_OLDAPI_RONLY,
     var_hrstore, 1, {2}},
    {HRSTORE_DESCR, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
     var_hrstore, 1, {3}},
    {HRSTORE_UNITS, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
     var_hrstore, 1, {4}},
    {HRSTORE_SIZE, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
     var_hrstore, 1, {5}},
    {HRSTORE_USED, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
     var_hrstore, 1, {6}},
    {HRSTORE_FAILS, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
     var_hrstore, 1, {7}}
};
oid             hrstore_variables_oid[] = { 1, 3, 6, 1, 2, 1, 25, 2 };
oid             hrMemorySize_oid[]   = { 1, 3, 6, 1, 2, 1, 25, 2, 2 };
oid             hrStorageTable_oid[] = { 1, 3, 6, 1, 2, 1, 25, 2, 3, 1 };


void
init_hrh_storage(void)
{
    char *appname;

    netsnmp_register_scalar(
        netsnmp_create_handler_registration("host/hrMemorySize", handle_memsize,
                           hrMemorySize_oid, OID_LENGTH(hrMemorySize_oid),
                                             HANDLER_CAN_RONLY));
    REGISTER_MIB("host/hr_storage", hrstore_variables, variable2,
                 hrStorageTable_oid);

    appname = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
                                    NETSNMP_DS_LIB_APPTYPE);
    netsnmp_ds_register_config(ASN_BOOLEAN, appname, "skipNFSInHostResources", 
			       NETSNMP_DS_APPLICATION_ID,
			       NETSNMP_DS_AGENT_SKIPNFSINHOSTRESOURCES);

    netsnmp_ds_register_config(ASN_BOOLEAN, appname, "realStorageUnits",
                   NETSNMP_DS_APPLICATION_ID,
                   NETSNMP_DS_AGENT_REALSTORAGEUNITS);

    snmpd_register_config_handler("storageUseNFS", parse_storage_config, NULL,
	"1 | 2\t\t(1 = enable, 2 = disable)");
}

static int storageUseNFS = 1;	/* Default to reporting NFS mounts as NetworkDisk */

static void
parse_storage_config(const char *token, char *cptr)
{
    char *val;
    int ival;
    char *st;

    val = strtok_r(cptr, " \t", &st);
    if (!val) {
        config_perror("Missing FLAG parameter in storageUseNFS");
        return;
    }
    ival = atoi(val);
    if (ival < 1 || ival > 2) {
        config_perror("storageUseNFS must be 1 or 2");
        return;
    }
    storageUseNFS = (ival == 1) ? 1 : 0;
}

/*
 * header_hrstoreEntry(...
 * Arguments:
 * vp     IN      - pointer to variable entry that points here
 * name    IN/OUT  - IN/name requested, OUT/name found
 * length  IN/OUT  - length of IN/OUT oid's 
 * exact   IN      - TRUE if an exact match was requested
 * var_len OUT     - length of variable or 0 if function returned
 * write_method
 * 
 */
static void *
header_hrstoreEntry(struct variable *vp, oid *name, size_t *length, int exact,
                    size_t *var_len, WriteMethod **write_method,
                    const netsnmp_fsys_info **entry)
{
#define HRSTORE_ENTRY_NAME_LENGTH	11
    oid             newname[MAX_OID_LEN];
    int             storage_idx, LowIndex = -1;
    int             result;
    int                  idx = -1;
    netsnmp_memory_info *mem  = NULL;

    DEBUGMSGTL(("host/hr_storage", "var_hrstoreEntry: request "));
    DEBUGMSGOID(("host/hr_storage", name, *length));
    DEBUGMSG(("host/hr_storage", " exact=%d\n", exact));

    memcpy((char *) newname, (char *) vp->name,
           (int) vp->namelen * sizeof(oid));
    result = snmp_oid_compare(name, *length, vp->name, vp->namelen);

    DEBUGMSGTL(("host/hr_storage", "var_hrstoreEntry: compare "));
    DEBUGMSGOID(("host/hr_storage", vp->name, vp->namelen));
    DEBUGMSG(("host/hr_storage", " => %d\n", result));


    if (result < 0 ||
        *length <= HRSTORE_ENTRY_NAME_LENGTH ) {
       /*
        * Requested OID too early or too short to refer
        *   to a valid row (for the current column object).
        * GET requests should fail, GETNEXT requests
        *   should use the first row.
        */
        if ( exact )
            return NULL;
        netsnmp_memory_load();
        mem = netsnmp_memory_get_first( 0 );
    }
    else {
        /*
         * Otherwise, retrieve the requested
         *  (or following) row as appropriate.
         */
        if ( exact && *length > HRSTORE_ENTRY_NAME_LENGTH+1 )
            return NULL;   /* Too long for a valid instance */
        idx = name[ HRSTORE_ENTRY_NAME_LENGTH ];
        if ( idx < NETSNMP_MEM_TYPE_MAX ) {
            netsnmp_memory_load();
            mem = ( exact ? netsnmp_memory_get_byIdx( idx, 0 ) :
                       netsnmp_memory_get_next_byIdx( idx, 0 ));
        }
    }

    /*
     * If this matched a memory-based entry, then
     *    update the OID parameter(s) for GETNEXT requests.
     */
    if ( mem ) {
        if ( !exact ) {
            newname[ HRSTORE_ENTRY_NAME_LENGTH ] = mem->idx;
            memcpy((char *) name, (char *) newname,
                   ((int) vp->namelen + 1) * sizeof(oid));
            *length = vp->namelen + 1;
        }
    }
    /*
     * If this didn't match a memory-based entry,
     *   then consider the disk-based storage.
     */
    else {
        *entry = NULL;
        Init_HR_Store();
        for (;;) {
            storage_idx = Get_Next_HR_Store(entry);
            DEBUGMSG(("host/hr_storage", "(index %d ....", storage_idx));
            if (storage_idx == -1)
                break;
            newname[HRSTORE_ENTRY_NAME_LENGTH] = storage_idx;
            DEBUGMSGOID(("host/hr_storage", newname, *length));
            DEBUGMSG(("host/hr_storage", "\n"));
            result = snmp_oid_compare(name, *length, newname, vp->namelen + 1);
            if (exact && (result == 0)) {
                LowIndex = storage_idx;
                /*
                 * Save storage status information 
                 */
                break;
            }
            if ((!exact && (result < 0)) &&
                (LowIndex == -1 || storage_idx < LowIndex)) {
                LowIndex = storage_idx;
                /*
                 * Save storage status information 
                 */
#ifdef HRSTORE_MONOTONICALLY_INCREASING
                break;
#endif
            }
        }
        if ( LowIndex != -1 ) {
            if ( !exact ) {
                newname[ HRSTORE_ENTRY_NAME_LENGTH ] = LowIndex;
                memcpy((char *) name, (char *) newname,
                       ((int) vp->namelen + 1) * sizeof(oid));
                *length = vp->namelen + 1;
            }
            mem = (netsnmp_memory_info*)0xffffffff;   /* To indicate 'success' */
        }
    }

    *write_method = (WriteMethod*)0;
    *var_len = sizeof(long);    /* default to 'long' results */

    /*
     *  ... and return the appropriate row
     */
    DEBUGMSGTL(("host/hr_storage", "var_hrstoreEntry: process "));
    DEBUGMSGOID(("host/hr_storage", name, *length));
    DEBUGMSG(("host/hr_storage", " (%p)\n", mem));
    return (void*)mem;
}

oid             storage_type_id[] = { 1, 3, 6, 1, 2, 1, 25, 2, 1, 1 };  /* hrStorageOther */
int             storage_type_len =
    sizeof(storage_type_id) / sizeof(storage_type_id[0]);

        /*********************
	 *
	 *  System specific implementation functions
	 *
	 *********************/

int
handle_memsize(netsnmp_mib_handler *handler,
                netsnmp_handler_registration *reginfo,
                netsnmp_agent_request_info *reqinfo,
                netsnmp_request_info *requests)
{
    netsnmp_memory_info *mem_info;
    int val;

    /*
     * We just need to handle valid GET requests, as invalid instances
     *   are rejected automatically, and (valid) GETNEXT requests are
     *   converted into the appropriate GET request.
     *
     * We also only ever receive one request at a time.
     */
    switch (reqinfo->mode) {
    case MODE_GET:
        netsnmp_memory_load();
        mem_info = netsnmp_memory_get_byIdx( NETSNMP_MEM_TYPE_PHYSMEM, 0 );
        if ( !mem_info || mem_info->size == -1 || mem_info->units == -1 )
            netsnmp_set_request_error( reqinfo, requests, SNMP_NOSUCHOBJECT );
	else {
            val  =  mem_info->size;     /* memtotal */
            val *= (mem_info->units/1024);
            snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER,
                                     (u_char *)&val, sizeof(val));
        }
        return SNMP_ERR_NOERROR;

    default:
        /*
         * we should never get here, so this is a really bad error 
         */
        snmp_log(LOG_ERR, "unknown mode (%d) in handle_memsize\n",
                 reqinfo->mode);
        return SNMP_ERR_GENERR;
    }

    return SNMP_ERR_NOERROR;
}


u_char         *
var_hrstore(struct variable *vp,
            oid * name,
            size_t * length,
            int exact, size_t * var_len, WriteMethod ** write_method)
{
    int             store_idx = 0;
    static char     string[1024];
    void                *ptr;
    const netsnmp_fsys_info *entry = NULL;
    netsnmp_memory_info *mem = NULL;

really_try_next:
    ptr = header_hrstoreEntry(vp, name, length, exact, var_len, write_method,
                              &entry);
    if (ptr == NULL)
        return NULL;

    store_idx = name[ HRSTORE_ENTRY_NAME_LENGTH ];
    if (entry &&
        store_idx > NETSNMP_MEM_TYPE_MAX &&
        netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
                               NETSNMP_DS_AGENT_SKIPNFSINHOSTRESOURCES) &&
        Check_HR_FileSys_NFS(entry))
        return NULL;
    if (entry && Check_HR_FileSys_AutoFs(entry))
        return NULL;
    if (store_idx <= NETSNMP_MEM_TYPE_MAX ) {
        mem = (netsnmp_memory_info*)ptr;
    }



    switch (vp->magic) {
    case HRSTORE_INDEX:
        long_return = store_idx;
        return (u_char *) & long_return;
    case HRSTORE_TYPE:
        if (store_idx > NETSNMP_MEM_TYPE_MAX)
            if (entry->flags & NETSNMP_FS_FLAG_REMOTE && storageUseNFS)
                storage_type_id[storage_type_len - 1] = 10;     /* Network Disk */
            else if (entry->flags & NETSNMP_FS_FLAG_REMOVE )
                storage_type_id[storage_type_len - 1] = 5;      /* Removable Disk */
            else
                storage_type_id[storage_type_len - 1] = 4;      /* Assume fixed */
        else
            switch (store_idx) {
            case NETSNMP_MEM_TYPE_PHYSMEM:
            case NETSNMP_MEM_TYPE_USERMEM:
                storage_type_id[storage_type_len - 1] = 2;      /* RAM */
                break;
            case NETSNMP_MEM_TYPE_VIRTMEM:
            case NETSNMP_MEM_TYPE_SWAP:
                storage_type_id[storage_type_len - 1] = 3;      /* Virtual Mem */
                break;
            default:
                storage_type_id[storage_type_len - 1] = 1;      /* Other */
                break;
            }
        *var_len = sizeof(storage_type_id);
        return (u_char *) storage_type_id;
    case HRSTORE_DESCR:
        if (store_idx > NETSNMP_MEM_TYPE_MAX) {
            strlcpy(string, entry->path, sizeof(string));
            *var_len = strlen(string);
            return (u_char *) string;
        } else {
            if ( !mem || !mem->descr )
                goto try_next;
            *var_len = strlen(mem->descr);
            return (u_char *) mem->descr;
        }
    case HRSTORE_UNITS:
        if (store_idx > NETSNMP_MEM_TYPE_MAX) {
            if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
                                       NETSNMP_DS_AGENT_REALSTORAGEUNITS))
                long_return = entry->units & 0x7fffffff;
            else
                long_return = entry->units_32;
        } else {
            if ( !mem || mem->units == -1 )
                goto try_next;
            long_return = mem->units & 0x7fffffff;
        }
        return (u_char *) & long_return;
    case HRSTORE_SIZE:
        if (store_idx > NETSNMP_MEM_TYPE_MAX) {
            if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
                                       NETSNMP_DS_AGENT_REALSTORAGEUNITS))
                long_return = entry->size & 0x7fffffff;
            else
                long_return = entry->size_32;
        } else {
            if ( !mem || mem->size == -1 )
                goto try_next;
            long_return = mem->size & 0x7fffffff;
        }
        return (u_char *) & long_return;
    case HRSTORE_USED:
        if (store_idx > NETSNMP_MEM_TYPE_MAX) {
            if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
                                       NETSNMP_DS_AGENT_REALSTORAGEUNITS))
                long_return = entry->used & 0x7fffffff;
            else
                long_return = entry->used_32;
        } else {
            if ( !mem || mem->size == -1 || mem->free == -1 )
                goto try_next;
            long_return = (mem->size - mem->free) & 0x7fffffff;
        }
        return (u_char *) & long_return;
    case HRSTORE_FAILS:
        if (store_idx > NETSNMP_MEM_TYPE_MAX)
#ifdef NETSNMP_NO_DUMMY_VALUES
	    goto try_next;
#else
        long_return = 0;
#endif
        else {
            if ( !mem || mem->other == -1 )
                goto try_next;
            long_return = mem->other;
        }
        return (u_char *) & long_return;
    default:
        DEBUGMSGTL(("snmpd", "unknown sub-id %d in var_hrstore\n",
                    vp->magic));
    }
    return NULL;

try_next:
    if (!exact)
        goto really_try_next;

    return NULL;
}


        /*********************
	 *
	 *  Internal implementation functions
	 *
	 *********************/

void
Init_HR_Store(void)
{
    Init_HR_FileSys();
}

int
Get_Next_HR_Store(const struct netsnmp_fsys_info_s **entry)
{
    /*
     * File-based storage 
     */
    for (;;) {
	Get_Next_HR_FileSys(entry);
        if (!*entry)
            return -1;
        if (!(netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, 
                                  NETSNMP_DS_AGENT_SKIPNFSINHOSTRESOURCES) && 
              Check_HR_FileSys_NFS(*entry)) &&
            !Check_HR_FileSys_AutoFs(*entry)) {
            return (*entry)->idx.oids[0] + NETSNMP_MEM_TYPE_MAX;	
        }
    }
}

