/* Portions of this file are subject to the following copyright(s).  See
 * the Net-SNMP's COPYING file for more details and other copyrights
 * that may apply:
 */
/*
 * Portions of this file are copyrighted by:
 * Copyright © 2003 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms specified in the COPYING file
 * distributed with the Net-SNMP package.
 */

#include <net-snmp/net-snmp-config.h>

/*
 * needed by util_funcs.h 
 */
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include <math.h>

#if defined (linux)
/* for stat() */
#include <ctype.h>
#include <sys/stat.h>
#endif

#ifdef HAVE_SYS_SYSMACROS_H
#include <sys/sysmacros.h> /* major() */
#endif

#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include <net-snmp/agent/agent_callbacks.h>

#include "util_funcs/header_simple_table.h"

#include "struct.h"
/*
 * include our .h file 
 */
#include "diskio.h"

#define CACHE_TIMEOUT 1
static time_t   cache_time = 0;

#ifdef solaris2
#include <kstat.h>

#define MAX_DISKS 128

static kstat_ctl_t *kc;
static kstat_t *ksp;
static kstat_io_t kio;
static int      cache_disknr = -1;
#endif                          /* solaris2 */

#if defined(aix4) || defined(aix5) || defined(aix6) || defined(aix7)
/*
 * handle disk statistics via libperfstat
 */
#ifdef HAVE_SYS_PROTOSW_H
#include <sys/protosw.h>
#endif
#include <libperfstat.h>
static perfstat_disk_t *ps_disk;	/* storage for all disk values */
static int ps_numdisks;			/* number of disks in system, may change while running */
#endif

#if defined(bsdi3) || defined(bsdi4) || defined(openbsd4)
#include <string.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#ifdef openbsd4
#include <sys/disk.h>
#else
#include <sys/diskstats.h>
#endif
#endif                          /* bsdi */

#if defined(HAVE_GETDEVS) || defined(HAVE_DEVSTAT_GETDEVS)
#include <sys/param.h>
#if HAVE_DEVSTAT_GETDEVS
#include <sys/resource.h>       /* for CPUSTATES in devstat.h */
#elif HAVE_SYS_DKSTAT_H
#include <sys/dkstat.h>
#endif
#include <devstat.h>
#include <net-snmp/utilities.h>

#include <math.h>
/* sampling interval, in seconds */
#define DISKIO_SAMPLE_INTERVAL 5

#endif                          /* freebsd */

#if HAVE_DEVSTAT_GETDEVS
  #define GETDEVS(x) devstat_getdevs(NULL, (x))
#else
  #define GETDEVS(x) getdevs((x))
#endif

#if defined (linux)
#define DISKIO_SAMPLE_INTERVAL 5
void devla_getstats(unsigned int regno, void * dummy);
static void diskio_parse_config_disks(const char *token, char *cptr);
static int diskio_pre_update_config(int, int, void *, void *);
static void diskio_free_config(void);

#define DISK_INCR 2

typedef struct linux_diskio
{
    int major;
    int  minor;
    unsigned long  blocks;
    char name[256];
    unsigned long  rio;
    unsigned long  rmerge;
    unsigned long  rsect;
    unsigned long  ruse;
    unsigned long  wio;
    unsigned long  wmerge;
    unsigned long  wsect;
    unsigned long  wuse;
    unsigned long  running;
    unsigned long  use;
    unsigned long  aveq;
} linux_diskio;

/* disk load averages */
typedef struct linux_diskio_la
{
    unsigned long use_prev;
    double la1, la5, la15;
} linux_diskio_la;

typedef struct linux_diskio_header
{
    linux_diskio* indices;
    int length;
    int alloc;
} linux_diskio_header;

typedef struct linux_diskio_la_header
{
    linux_diskio_la * indices;
    int length;
} linux_diskio_la_header;

static linux_diskio_header head;
static linux_diskio_la_header la_head;

#endif /* linux */

#if defined (darwin)
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/storage/IOBlockStorageDriver.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/IOBSD.h>

static mach_port_t masterPort;		/* to communicate with I/O Kit	*/
#endif                          /* darwin */

#if !defined(solaris2) && !(defined(aix4) || defined(aix5) || defined(aix6) || defined(aix7))
static int      getstats(void);
#endif

#if defined (HAVE_GETDEVS) || defined(HAVE_DEVSTAT_GETDEVS)
void		devla_getstats(unsigned int regno, void *dummy);
#endif

#ifdef linux
struct diskiopart {
    char            syspath[STRMAX];	/* full stat path */
    char            name[STRMAX];	/* name as provided */
    char            shortname[STRMAX];	/* short name for output */
    int             major;
    int             minor;
};

static int             numdisks;
static int             maxdisks = 0;
static struct diskiopart *disks;
#endif

         /*********************
	 *
	 *  Initialisation & common implementation functions
	 *
	 *********************/


/*
 * this is an optional function called at the time the agent starts up
 * to do any initilizations you might require.  You don't have to
 * create it, as it is optional. 
 */

/*
 * IMPORTANT: If you add or remove this function, you *must* re-run
 * the configure script as it checks for its existance. 
 */

void
init_diskio(void)
{
    /*
     * Define a 'variable' structure that is a representation of our mib. 
     */

    /*
     * first, we have to pick the variable type.  They are all defined in
     * the var_struct.h file in the agent subdirectory.  I'm picking the
     * variable2 structure since the longest sub-component of the oid I
     * want to load is .2.1 and .2.2 so I need at most 2 spaces in the
     * last entry. 
     */

    struct variable2 diskio_variables[] = {
        {DISKIO_INDEX, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_diskio, 1, {1}},
        {DISKIO_DEVICE, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
         var_diskio, 1, {2}},
        {DISKIO_NREAD, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
         var_diskio, 1, {3}},
        {DISKIO_NWRITTEN, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
         var_diskio, 1, {4}},
        {DISKIO_READS, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
         var_diskio, 1, {5}},
        {DISKIO_WRITES, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
         var_diskio, 1, {6}},
#if defined(HAVE_GETDEVS) || defined(HAVE_DEVSTAT_GETDEVS) || defined(linux)
        {DISKIO_LA1, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_diskio, 1, {9}},
        {DISKIO_LA5, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_diskio, 1, {10}},
        {DISKIO_LA15, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_diskio, 1, {11}},
#endif
        {DISKIO_NREADX, ASN_COUNTER64, NETSNMP_OLDAPI_RONLY,
         var_diskio, 1, {12}},
        {DISKIO_NWRITTENX, ASN_COUNTER64, NETSNMP_OLDAPI_RONLY,
         var_diskio, 1, {13}},
        {DISKIO_BUSYTIME, ASN_COUNTER64, NETSNMP_OLDAPI_RONLY,
         var_diskio, 1, {14}},
    };

    /*
     * Define the OID pointer to the top of the mib tree that we're
     * registering underneath. 
     */
    oid             diskio_variables_oid[] =
        { 1, 3, 6, 1, 4, 1, 2021, 13, 15, 1, 1 };

    /*
     * register ourselves with the agent to handle our mib tree
     * 
     * This is a macro defined in ../../snmp_vars.h.  The arguments are:
     * 
     * descr:   A short description of the mib group being loaded.
     * var:     The variable structure to load.
     * vartype: The variable structure used to define it (variable2, variable4, ...)
     * theoid:  A *initialized* *exact length* oid pointer.
     * (sizeof(theoid) *must* return the number of elements!)  
     */
    REGISTER_MIB("diskio", diskio_variables, variable2,
                 diskio_variables_oid);

#ifdef solaris2
    kc = kstat_open();

    if (kc == NULL)
        snmp_log(LOG_ERR, "diskio: Couldn't open kstat\n");
#endif

#ifdef darwin
    /*
     * Get the I/O Kit communication handle.
     */
    IOMasterPort(bootstrap_port, &masterPort);
#endif

#if defined(aix4) || defined(aix5) || defined(aix6) || defined(aix7)
    /*
     * initialize values to gather information on first request
     */
    ps_numdisks = 0;
    ps_disk = NULL;
#endif

#if defined (HAVE_GETDEVS) || defined(HAVE_DEVSTAT_GETDEVS) || defined(linux)
    devla_getstats(0, NULL);
    /* collect LA data regularly */
    snmp_alarm_register(DISKIO_SAMPLE_INTERVAL, SA_REPEAT, devla_getstats, NULL);
#endif


#ifdef linux
    char *app = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
                                      NETSNMP_DS_LIB_APPTYPE);
    netsnmp_ds_register_config(ASN_BOOLEAN, app, "diskio_exclude_fd",
                               NETSNMP_DS_APPLICATION_ID,
                               NETSNMP_DS_AGENT_DISKIO_NO_FD);
    netsnmp_ds_register_config(ASN_BOOLEAN, app, "diskio_exclude_loop",
                               NETSNMP_DS_APPLICATION_ID,
                               NETSNMP_DS_AGENT_DISKIO_NO_LOOP);
    netsnmp_ds_register_config(ASN_BOOLEAN, app, "diskio_exclude_ram",
                               NETSNMP_DS_APPLICATION_ID,
                               NETSNMP_DS_AGENT_DISKIO_NO_RAM);

    snmpd_register_config_handler("diskio", diskio_parse_config_disks,
        diskio_free_config, "path | device");
    

    snmp_register_callback(SNMP_CALLBACK_APPLICATION,
	                   SNMPD_CALLBACK_PRE_UPDATE_CONFIG,
	                   diskio_pre_update_config, NULL);

#endif
}

#ifdef linux
/* to do: make sure diskio_free_config() gets invoked upon SIGHUP. */
static int
diskio_pre_update_config(int major, int minor, void *serverarg, void *clientarg)
{
    diskio_free_config();
    return 0;
}

static void
diskio_free_config(void)
{
    int i;

    DEBUGMSGTL(("diskio", "free config %d\n",
		netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
				       NETSNMP_DS_AGENT_DISKIO_NO_RAM)));
    netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, 
			   NETSNMP_DS_AGENT_DISKIO_NO_FD,   0);
    netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, 
			   NETSNMP_DS_AGENT_DISKIO_NO_LOOP, 0);
    netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, 
			   NETSNMP_DS_AGENT_DISKIO_NO_RAM,  0);

    if (la_head.length) {
        /* reset any usage stats, we may get different list of devices from config */
        free(la_head.indices);
        la_head.length = 0;
        la_head.indices = NULL;
    }
    if (numdisks > 0) {
        head.length = 0;
        numdisks = 0;
        for (i = 0; i < maxdisks; i++) {    /* init/erase disk db */
            disks[i].syspath[0] = 0;
            disks[i].name[0] = 0;
            disks[i].shortname[0] = 0;
            disks[i].major = -1;
            disks[i].minor = -1;
        }
    }
}

static int
disk_exists(char *path) 
{
    int index;
    for(index = 0; index < numdisks; index++) {
        DEBUGMSGTL(("ucd-snmp/disk", "Checking for %s. Found %s at %d\n", path, disks[index].syspath, index));
        if(strcmp(path, disks[index].syspath) == 0) {
            return index;
        }
    }
    return -1;
}

static void
add_device(char *path, int addNewDisks ) 
{
    int index;
    char device[STRMAX];
    char syspath[STRMAX];
    char *basename;
    struct stat stbuf;

    if (!path || !strcmp(path, "none")) {
        DEBUGMSGTL(("ucd-snmp/diskio", "Skipping null path device (%s)\n", path));
        return;
    }
    if (numdisks == maxdisks) {
        if (maxdisks == 0) {
            maxdisks = 50;
            disks = malloc(maxdisks * sizeof(struct diskiopart));
            if (!disks) {
                config_perror("malloc failed for new disko allocation.");
	            netsnmp_config_error("\tignoring:  %s", path);
                return;
            }
            memset(disks, 0, maxdisks * sizeof(struct diskiopart));
        } else {
            struct diskiopart *newdisks;
            maxdisks *= 2;
            newdisks = realloc(disks, maxdisks * sizeof(struct diskiopart));
            if (!newdisks) {
                free(disks);
                disks = NULL;
                config_perror("malloc failed for new disko allocation.");
	            netsnmp_config_error("\tignoring:  %s", path);
                return;
            }
            disks = newdisks;
            memset(disks + maxdisks/2, 0, maxdisks/2 * sizeof(struct diskiopart));
        }
    }

    /* first find the path for this device */
    device[0]='\0';
    if ( *path != '/' ) {
        strlcpy(device, "/dev/", STRMAX - 1 );
    }
    strncat(device, path, STRMAX - 1 );

    /* check for /dev existence */
    if ( stat(device,&stbuf)!=0 ) { /* ENOENT */
        config_perror("diskio path does not exist.");
        netsnmp_config_error("\tignoring:  %s", path);
        return;
    }
    else if ( ! S_ISBLK(stbuf.st_mode) ) { /* ENODEV */
        config_perror("diskio path is not a device.");
        netsnmp_config_error("\tignoring:  %s", path);
        return;
    }

    /* either came with a slash or we just put one there, so the following always works */
    basename = strrchr(device, '/' )+1;
    /* construct a sys path using the device numbers to avoid having to disambiguate the various text forms */
    snprintf( syspath, STRMAX - 1, "/sys/dev/block/%d:%d/stat", major(stbuf.st_rdev), minor(stbuf.st_rdev) );
    DEBUGMSGTL(("ucd-snmp/diskio", " monitoring sys path (%s)\n", syspath));

    index = disk_exists(syspath);

    if(index == -1 && addNewDisks){
        /* The following buffers are cleared above, no need to add '\0' */
        strlcpy(disks[numdisks].syspath, syspath, sizeof(disks[numdisks].syspath) - 1);
        strlcpy(disks[numdisks].name, path, sizeof(disks[numdisks].name) - 1);
        strlcpy(disks[numdisks].shortname, basename, sizeof(disks[numdisks].shortname) - 1);
        disks[numdisks].major = major(stbuf.st_rdev);
        disks[numdisks].minor = minor(stbuf.st_rdev);
        numdisks++;  
    }
}

static void 
diskio_parse_config_disks(const char *token, char *cptr)
{
#if HAVE_FSTAB_H || HAVE_GETMNTENT || HAVE_STATFS
    char path[STRMAX];


    /*
     * read disk path (eg, /1 or /usr) 
     */
    copy_nword(cptr, path, sizeof(path));

    /* TODO: we may include regular expressions in future */
    /*
     * check if the disk already exists, if so then modify its
     * parameters. if it does not exist then add it
     */
    add_device(path, 1);
#endif /* HAVE_FSTAB_H || HAVE_GETMNTENT || HAVE_STATFS */
}

#endif /* linux */


#ifdef solaris2
int
get_disk(int disknr)
{
    time_t          now;
    int             i = 0;
    kstat_t *tksp;

    now = time(NULL);
    if (disknr == cache_disknr && cache_time + CACHE_TIMEOUT > now) {
        return 1;
    }

    /*
     * could be optimiced by checking if cache_disknr<=disknr
     * if so, just reread the data - not going through the whole chain
     * from kc->kc_chain 
     */

    for (tksp = kc->kc_chain; tksp != NULL; tksp = tksp->ks_next) {
        if (tksp->ks_type == KSTAT_TYPE_IO
            && !strcmp(tksp->ks_class, "disk")) {
            if (i == disknr) {
                if (kstat_read(kc, tksp, &kio) == -1)
                    snmp_log(LOG_ERR, "diskio: kstat_read failed\n");
		ksp = tksp;
                cache_time = now;
                cache_disknr = disknr;
                return 1;
            } else {
                i++;
            }
        }
    }
    return 0;
}


u_char         *
var_diskio(struct variable * vp,
           oid * name,
           size_t * length,
           int exact, size_t * var_len, WriteMethod ** write_method)
{
    /*
     * define any variables we might return as static! 
     */
    static long     long_ret;
    static struct counter64 c64_ret;

    if (header_simple_table
        (vp, name, length, exact, var_len, write_method, MAX_DISKS))
        return NULL;


    if (get_disk(name[*length - 1] - 1) == 0)
        return NULL;


    /*
     * We can now simply test on vp's magic number, defined in diskio.h 
     */
    switch (vp->magic) {
    case DISKIO_INDEX:
        long_ret = (long) name[*length - 1];
        return (u_char *) & long_ret;
    case DISKIO_DEVICE:
        *var_len = strlen(ksp->ks_name);
        return (u_char *) ksp->ks_name;
    case DISKIO_NREAD:
        long_ret = (uint32_t) kio.nread;
        return (u_char *) & long_ret;
    case DISKIO_NWRITTEN:
        long_ret = (uint32_t) kio.nwritten;
        return (u_char *) & long_ret;
    case DISKIO_NREADX:
        *var_len = sizeof(struct counter64);
        c64_ret.low = kio.nread & 0xffffffff;
        c64_ret.high = kio.nread >> 32;
        return (u_char *) & c64_ret;
    case DISKIO_NWRITTENX:
        *var_len = sizeof(struct counter64);
        c64_ret.low = kio.nwritten & 0xffffffff;
        c64_ret.high = kio.nwritten >> 32;
        return (u_char *) & c64_ret;
    case DISKIO_READS:
        long_ret = (uint32_t) kio.reads;
        return (u_char *) & long_ret;
    case DISKIO_WRITES:
        long_ret = (uint32_t) kio.writes;
        return (u_char *) & long_ret;

    default:
        ERROR_MSG("diskio.c: don't know how to handle this request.");
    }
    /*
     * if we fall to here, fail by returning NULL 
     */
    return NULL;
}
#endif                          /* solaris2 */

#if defined(bsdi3) || defined(bsdi4)
static int      ndisk;
static struct diskstats *dk;
static char   **dkname;

static int
getstats(void)
{
    time_t          now;
    int             mib[2];
    char           *t, *tp;
    size_t          size, dkn_size;
    int             i;

    now = time(NULL);
    if (cache_time + CACHE_TIMEOUT > now) {
        return 1;
    }
    mib[0] = CTL_HW;
    mib[1] = HW_DISKSTATS;
    size = 0;
    if (sysctl(mib, 2, NULL, &size, NULL, 0) < 0) {
        perror("Can't get size of HW_DISKSTATS mib");
        return 0;
    }
    if (ndisk != size / sizeof(*dk)) {
        if (dk)
            free(dk);
        if (dkname) {
            for (i = 0; i < ndisk; i++)
                if (dkname[i])
                    free(dkname[i]);
            free(dkname);
        }
        ndisk = size / sizeof(*dk);
        if (ndisk == 0)
            return 0;
        dkname = malloc(ndisk * sizeof(char *));
        mib[0] = CTL_HW;
        mib[1] = HW_DISKNAMES;
        if (sysctl(mib, 2, NULL, &dkn_size, NULL, 0) < 0) {
            perror("Can't get size of HW_DISKNAMES mib");
            return 0;
        }
        tp = t = malloc(dkn_size);
        if (sysctl(mib, 2, t, &dkn_size, NULL, 0) < 0) {
            perror("Can't get size of HW_DISKNAMES mib");
            return 0;
        }
        for (i = 0; i < ndisk; i++) {
            dkname[i] = strdup(tp);
            tp += strlen(tp) + 1;
        }
        free(t);
        dk = malloc(ndisk * sizeof(*dk));
    }
    mib[0] = CTL_HW;
    mib[1] = HW_DISKSTATS;
    if (sysctl(mib, 2, dk, &size, NULL, 0) < 0) {
        perror("Can't get HW_DISKSTATS mib");
        return 0;
    }
    cache_time = now;
    return 1;
}

u_char         *
var_diskio(struct variable * vp,
           oid * name,
           size_t * length,
           int exact, size_t * var_len, WriteMethod ** write_method)
{
    static long     long_ret;
    unsigned int    indx;

    if (getstats() == 0)
        return 0;

    if (header_simple_table
        (vp, name, length, exact, var_len, write_method, ndisk))
        return NULL;

    indx = (unsigned int) (name[*length - 1] - 1);
    if (indx >= ndisk)
        return NULL;

    switch (vp->magic) {
    case DISKIO_INDEX:
        long_ret = (long) indx + 1;
        return (u_char *) & long_ret;
    case DISKIO_DEVICE:
        *var_len = strlen(dkname[indx]);
        return (u_char *) dkname[indx];
    case DISKIO_NREAD:
        long_ret =
            (signed long) (dk[indx].dk_sectors * dk[indx].dk_secsize);
        return (u_char *) & long_ret;
    case DISKIO_NWRITTEN:
        return NULL;            /* Sigh... BSD doesn't keep seperate track */
    case DISKIO_READS:
        long_ret = (signed long) dk[indx].dk_xfers;
        return (u_char *) & long_ret;
    case DISKIO_WRITES:
        return NULL;            /* Sigh... BSD doesn't keep seperate track */

    default:
        ERROR_MSG("diskio.c: don't know how to handle this request.");
    }
    return NULL;
}
#endif                          /* bsdi */

#if defined(openbsd4)
static int      ndisk;
static struct diskstats *dk;
static char   **dkname;

static int
getstats(void)
{
    time_t          now;
    int             mib[2];
    char           *t, *tp,*te;
    size_t          size, dkn_size;
    int             i;

    now = time(NULL);
    if (cache_time + CACHE_TIMEOUT > now) {
        return 1;
    }
    mib[0] = CTL_HW;
    mib[1] = HW_DISKSTATS;
    size = 0;
    if (sysctl(mib, 2, NULL, &size, NULL, 0) < 0) {
        perror("Can't get size of HW_DISKSTATS mib");
        return 0;
    }
    if (ndisk != size / sizeof(*dk)) {
        if (dk)
            free(dk);
        if (dkname) {
            for (i = 0; i < ndisk; i++)
                if (dkname[i])
                    free(dkname[i]);
            free(dkname);
        }
        ndisk = size / sizeof(*dk);
        if (ndisk == 0)
            return 0;
        dkname = malloc(ndisk * sizeof(char *));
        mib[0] = CTL_HW;
        mib[1] = HW_DISKNAMES;
        if (sysctl(mib, 2, NULL, &dkn_size, NULL, 0) < 0) {
            perror("Can't get size of HW_DISKNAMES mib");
            return 0;
        }
        te = tp = t = malloc(dkn_size);
        if (sysctl(mib, 2, t, &dkn_size, NULL, 0) < 0) {
            perror("Can't get size of HW_DISKNAMES mib");
            return 0;
        }
        for (i = 0; i < ndisk; i++) {
	    while (te-t < dkn_size && *te != ',') te++;
	    *te++ = '\0';
            dkname[i] = strdup(tp);
            tp = te;
        }
        free(t);
        dk = malloc(ndisk * sizeof(*dk));
    }
    mib[0] = CTL_HW;
    mib[1] = HW_DISKSTATS;
    if (sysctl(mib, 2, dk, &size, NULL, 0) < 0) {
        perror("Can't get HW_DISKSTATS mib");
        return 0;
    }
    cache_time = now;
    return 1;
}

u_char         *
var_diskio(struct variable * vp,
           oid * name,
           size_t * length,
           int exact, size_t * var_len, WriteMethod ** write_method)
{
    static long     long_ret;
    static long long        longlong_ret;
    static struct counter64 c64_ret;
    unsigned int    indx;

    if (getstats() == 0)
        return 0;

    if (header_simple_table
        (vp, name, length, exact, var_len, write_method, ndisk))
        return NULL;

    indx = (unsigned int) (name[*length - 1] - 1);
    if (indx >= ndisk)
        return NULL;

    switch (vp->magic) {
    case DISKIO_INDEX:
        long_ret = (long) indx + 1;
        return (u_char *) & long_ret;
    case DISKIO_DEVICE:
        *var_len = strlen(dkname[indx]);
        return (u_char *) dkname[indx];
    case DISKIO_NREAD:
        long_ret = (unsigned long) (dk[indx].ds_rbytes) & 0xffffffff;
        return (u_char *) & long_ret;
    case DISKIO_NWRITTEN:
        long_ret = (unsigned long) (dk[indx].ds_wbytes) & 0xffffffff;
        return (u_char *) & long_ret;
    case DISKIO_READS:
        long_ret = (unsigned long) dk[indx].ds_rxfer & 0xffffffff;
        return (u_char *) & long_ret;
    case DISKIO_WRITES:
        long_ret = (unsigned long) dk[indx].ds_wxfer & 0xffffffff;
        return (u_char *) & long_ret;
    case DISKIO_NREADX:
        *var_len = sizeof(struct counter64);
        c64_ret.low = dk[indx].ds_rbytes & 0xffffffff;
        c64_ret.high = dk[indx].ds_rbytes >> 32;
        return (u_char *) & c64_ret;
    case DISKIO_NWRITTENX:
        *var_len = sizeof(struct counter64);
        c64_ret.low = dk[indx].ds_rbytes & 0xffffffff;
        c64_ret.high = dk[indx].ds_rbytes >> 32;
        return (u_char *) & c64_ret;
    case DISKIO_BUSYTIME:
        *var_len = sizeof(struct counter64);
	longlong_ret = dk[indx].ds_time.tv_sec*1000000 + dk[indx].ds_time.tv_usec;
        c64_ret.low = longlong_ret & 0xffffffff;
        c64_ret.high = longlong_ret >> 32;
	return (u_char *) &c64_ret;
    default:
        ERROR_MSG("diskio.c: don't know how to handle this request.");
    }
    return NULL;
}
#endif                          /* openbsd */

#ifdef __NetBSD__
#include <sys/sysctl.h>
static int      ndisk;
#ifdef HW_IOSTATNAMES
static int nmib[2] = {CTL_HW, HW_IOSTATNAMES};
#else
static int nmib[2] = {CTL_HW, HW_DISKNAMES};
#endif
#ifdef HW_DISKSTATS
#include <sys/disk.h>
static int dmib[3] = {CTL_HW, HW_DISKSTATS, sizeof(struct disk_sysctl)};
static struct disk_sysctl *dk;
#endif
#ifdef HW_IOSTATS
#include <sys/iostat.h>
static int dmib[3] = {CTL_HW, HW_IOSTATS, sizeof(struct io_sysctl)};
static struct io_sysctl *dk;
#endif
static char   **dkname;

static int
getstats(void)
{
    time_t          now;
    char           *t, *tp;
    size_t          size, dkn_size;
    int             i;

    now = time(NULL);
    if (cache_time + CACHE_TIMEOUT > now) {
        return 1;
    }
    size = 0;
    if (sysctl(dmib, 3, NULL, &size, NULL, 0) < 0) {
        perror("Can't get size of HW_DISKSTATS/HW_IOSTATS mib");
        return 0;
    }
    if (ndisk != size / dmib[2]) {
        if (dk)
            free(dk);
        if (dkname) {
            for (i = 0; i < ndisk; i++)
                if (dkname[i])
                    free(dkname[i]);
            free(dkname);
        }
        ndisk = size / dmib[2];
        if (ndisk == 0)
            return 0;
        dkname = malloc(ndisk * sizeof(char *));
        dkn_size = 0;
        if (sysctl(nmib, 2, NULL, &dkn_size, NULL, 0) < 0) {
            perror("Can't get size of HW_DISKNAMES mib");
            return 0;
        }
        t = malloc(dkn_size);
        if (sysctl(nmib, 2, t, &dkn_size, NULL, 0) < 0) {
            perror("Can't get size of HW_DISKNAMES mib");
            return 0;
        }
        for (i = 0, tp = strtok(t, " "); tp && i < ndisk; i++,
	    tp = strtok(NULL, " ")) {
            dkname[i] = strdup(tp);
        }
        free(t);
        dk = malloc(ndisk * sizeof(*dk));
    }
    if (sysctl(dmib, 3, dk, &size, NULL, 0) < 0) {
        perror("Can't get HW_DISKSTATS/HW_IOSTATS mib");
        return 0;
    }
    cache_time = now;
    return 1;
}

u_char *
var_diskio(struct variable * vp,
           oid * name,
           size_t * length,
           int exact, size_t * var_len, WriteMethod ** write_method)
{
    static long     long_ret;
    static long long        longlong_ret;
    static struct counter64 c64_ret;
    unsigned int    indx;

    if (getstats() == 0)
        return 0;

    if (header_simple_table
        (vp, name, length, exact, var_len, write_method, ndisk))
        return NULL;

    indx = (unsigned int) (name[*length - 1] - 1);
    if (indx >= ndisk)
        return NULL;

    switch (vp->magic) {
    case DISKIO_INDEX:
        long_ret = (long) indx + 1;
        return (u_char *) & long_ret;

    case DISKIO_DEVICE:
        *var_len = strlen(dkname[indx]);
        return (u_char *) dkname[indx];

    case DISKIO_NREAD:
#ifdef HW_DISKSTATS
     	long_ret = dk[indx].dk_rbytes;
#endif
#ifdef HW_IOSTATS
	if (dk[indx].type == IOSTAT_DISK)
	    long_ret = dk[indx].rbytes;
#endif
        return (u_char *) & long_ret;

    case DISKIO_NWRITTEN:
#ifdef HW_DISKSTATS
     	long_ret = dk[indx].dk_wbytes;
#endif
#ifdef HW_IOSTATS
	if (dk[indx].type == IOSTAT_DISK)
	    long_ret = dk[indx].wbytes;
#endif
        return (u_char *) & long_ret;

    case DISKIO_NREADX:
        *var_len = sizeof(struct counter64);
        longlong_ret = dk[indx].rbytes;
        c64_ret.low = longlong_ret & 0xffffffff;
        c64_ret.high = longlong_ret >> 32;
        return (u_char *) & c64_ret;

    case DISKIO_NWRITTENX:
        *var_len = sizeof(struct counter64);
        longlong_ret = dk[indx].wbytes;
        c64_ret.low = longlong_ret & 0xffffffff;
        c64_ret.high = longlong_ret >> 32;
        return (u_char *) & c64_ret;

    case DISKIO_READS:
#ifdef HW_DISKSTATS
     	long_ret = dk[indx].dk_rxfer;
#endif
#ifdef HW_IOSTATS
	if (dk[indx].type == IOSTAT_DISK)
	    long_ret = dk[indx].rxfer;
#endif
        return (u_char *) & long_ret;

    case DISKIO_WRITES:
#ifdef HW_DISKSTATS
     	long_ret = dk[indx].dk_wxfer;
#endif
#ifdef HW_IOSTATS
	if (dk[indx].type == IOSTAT_DISK)
	    long_ret = dk[indx].wxfer;
#endif
        return (u_char *) & long_ret;

    case DISKIO_BUSYTIME:
#ifdef HW_IOSTATS
        *var_len = sizeof(struct counter64);
	if (dk[indx].type == IOSTAT_DISK) {
	    longlong_ret = dk[indx].time_sec*1000 + dk[indx].time_usec/1000;
	    c64_ret.low = longlong_ret & 0xffffffff;
	    c64_ret.high = longlong_ret >> 32;
	    return (u_char *) & c64_ret;
	}
	else
	    return NULL;
#else
	return NULL;
#endif

    default:
        ERROR_MSG("diskio.c: don't know how to handle this request.");
    }
    return NULL;
}
#endif /* __NetBSD__ */

#if defined(HAVE_GETDEVS) || defined(HAVE_DEVSTAT_GETDEVS)

/* disk load average patch by Rojer */

struct dev_la {
#if HAVE_DEVSTAT_GETDEVS
        struct bintime prev;
#else
        struct timeval prev;
#endif
        double la1,la5,la15;
        char name[DEVSTAT_NAME_LEN+5];
        };

static struct dev_la *devloads = NULL;
static int ndevs = 0;

#if ! HAVE_DEVSTAT_GETDEVS
double devla_timeval_diff(struct timeval *t1, struct timeval *t2) {

        double dt1 = (double) t1->tv_sec + (double) t1->tv_usec * 0.000001;
        double dt2 = (double) t2->tv_sec + (double) t2->tv_usec * 0.000001;

        return dt2-dt1;

        }
#endif

void devla_getstats(unsigned int regno, void *dummy) {

        static struct statinfo *lastat = NULL;
        int i;
        double busy_time, busy_percent;
        static double expon1, expon5, expon15;
        char current_name[DEVSTAT_NAME_LEN+5];

	if (lastat == NULL) {
	    lastat = (struct statinfo *) malloc(sizeof(struct statinfo));
	    if (lastat != NULL)
		lastat->dinfo = (struct devinfo *) calloc(sizeof(struct devinfo), 1);
	    if (lastat == NULL || lastat->dinfo == NULL) {
		    SNMP_FREE(lastat);
		    ERROR_MSG("Memory alloc failure - devla_getstats()\n");
		    return;
	    }
	}

        if ((GETDEVS(lastat)) == -1) {
                ERROR_MSG("can't do getdevs()\n");
                return;
                }

        if (ndevs != 0) {
                for (i=0; i < ndevs; i++) {
                        snprintf(current_name, sizeof(current_name), "%s%d",
                                lastat->dinfo->devices[i].device_name, lastat->dinfo->devices[i].unit_number);
                        if (strcmp(current_name, devloads[i].name)) {
                                ndevs = 0;
                                free(devloads);
                                }
                        }
                }

        if (ndevs == 0) {
                ndevs = lastat->dinfo->numdevs;
                devloads = (struct dev_la *) malloc(ndevs * sizeof(struct dev_la));
                memset(devloads, '\0', ndevs * sizeof(struct dev_la));
                for (i=0; i < ndevs; i++) {
                        devloads[i].la1 = devloads[i].la5 = devloads[i].la15 = 0;
                        memcpy(&devloads[i].prev, &lastat->dinfo->devices[i].busy_time, sizeof(devloads[i].prev));
                        snprintf(devloads[i].name, sizeof(devloads[i].name), "%s%d",
                                lastat->dinfo->devices[i].device_name, lastat->dinfo->devices[i].unit_number);
                        }
                expon1  = exp(-(((double)DISKIO_SAMPLE_INTERVAL) / ((double)60)));
                expon5  = exp(-(((double)DISKIO_SAMPLE_INTERVAL) / ((double)300)));
                expon15 = exp(-(((double)DISKIO_SAMPLE_INTERVAL) / ((double)900)));
                }

        for (i=0; i<ndevs; i++) {
#if HAVE_DEVSTAT_GETDEVS
                busy_time = devstat_compute_etime(&lastat->dinfo->devices[i].busy_time, &devloads[i].prev);
#else
                busy_time = devla_timeval_diff(&devloads[i].prev, &lastat->dinfo->devices[i].busy_time);
#endif
                if ( busy_time < 0 )
                    busy_time = 0;   /* Account for possible FP loss of precision near zero */
                busy_percent = busy_time * 100 / DISKIO_SAMPLE_INTERVAL;
                devloads[i].la1 = devloads[i].la1 * expon1 + busy_percent * (1 - expon1);
/*		fprintf(stderr, "(%d) %s: update la1=%.2lf%%\n", i, devloads[i].name, expon1); */
                devloads[i].la5 = devloads[i].la5 * expon5 + busy_percent * (1 - expon5);
                devloads[i].la15 = devloads[i].la15 * expon15 + busy_percent * (1 - expon15);
                memcpy(&devloads[i].prev, &lastat->dinfo->devices[i].busy_time, sizeof(devloads[i].prev));
                }

        }

/* end of disk LA patch */

static int      ndisk;
static struct statinfo *stat;
FILE           *file;

static int
getstats(void)
{
    time_t          now;
    int             i;

    now = time(NULL);
    if (cache_time + CACHE_TIMEOUT > now) {
        return 0;
    }
    if (stat == NULL) {
        stat = (struct statinfo *) malloc(sizeof(struct statinfo));
        if (stat != NULL)
            stat->dinfo = (struct devinfo *) calloc(sizeof(struct devinfo), 1);
        if (stat == NULL || stat->dinfo == NULL) {
		SNMP_FREE(stat);
        	ERROR_MSG("Memory alloc failure - getstats\n");
		return 1;
	}
    }

    if (GETDEVS(stat) == -1) {
        fprintf(stderr, "Can't get devices:%s\n", devstat_errbuf);
        return 1;
    }
    ndisk = stat->dinfo->numdevs;
    /* Gross hack to include device numbers in the device name array */
    for (i = 0; i < ndisk; i++) {
      char *cp = stat->dinfo->devices[i].device_name;
      int len = strlen(cp);
      if (len > DEVSTAT_NAME_LEN - 3)
        len -= 3;
      cp += len;
      sprintf(cp, "%d", stat->dinfo->devices[i].unit_number);
    }
    cache_time = now;
    return 0;
}

u_char         *
var_diskio(struct variable * vp,
           oid * name,
           size_t * length,
           int exact, size_t * var_len, WriteMethod ** write_method)
{
    static long     long_ret;
    static struct   counter64 c64_ret;
    long long       longlong_ret;
    unsigned int    indx;

    if (getstats() == 1) {
        return NULL;
    }


    if (header_simple_table
        (vp, name, length, exact, var_len, write_method, ndisk)) {
        return NULL;
    }

    indx = (unsigned int) (name[*length - 1] - 1);

    if (indx >= ndisk)
        return NULL;

    switch (vp->magic) {
    case DISKIO_INDEX:
        long_ret = (long) indx + 1;
        return (u_char *) & long_ret;
    case DISKIO_DEVICE:
        *var_len = strlen(stat->dinfo->devices[indx].device_name);
        return (u_char *) stat->dinfo->devices[indx].device_name;
    case DISKIO_NREAD:
#if HAVE_DEVSTAT_GETDEVS
        long_ret = (signed long) stat->dinfo->devices[indx].bytes[DEVSTAT_READ] & 0xFFFFFFFF;
#else
        long_ret = (signed long) stat->dinfo->devices[indx].bytes_read;
#endif
        return (u_char *) & long_ret;
    case DISKIO_NWRITTEN:
#if HAVE_DEVSTAT_GETDEVS
        long_ret = (signed long) stat->dinfo->devices[indx].bytes[DEVSTAT_WRITE] & 0xFFFFFFFF;
#else
        long_ret = (signed long) stat->dinfo->devices[indx].bytes_written;
#endif
        return (u_char *) & long_ret;
    case DISKIO_NREADX:
        *var_len = sizeof(struct counter64);
#if HAVE_DEVSTAT_GETDEVS
        longlong_ret = stat->dinfo->devices[indx].bytes[DEVSTAT_READ];
#else
        longlong_ret = stat->dinfo->devices[indx].bytes_read;
#endif
        c64_ret.low = longlong_ret & 0xffffffff;
        c64_ret.high = longlong_ret >> 32;
        return (u_char *) & c64_ret;
    case DISKIO_NWRITTENX:
        *var_len = sizeof(struct counter64);
#if HAVE_DEVSTAT_GETDEVS
        longlong_ret = stat->dinfo->devices[indx].bytes[DEVSTAT_WRITE];
#else
        longlong_ret = stat->dinfo->devices[indx].bytes_written;
#endif
        c64_ret.low = longlong_ret & 0xffffffff;
        c64_ret.high = longlong_ret >> 32;
        return (u_char *) & c64_ret;
    case DISKIO_READS:
#if HAVE_DEVSTAT_GETDEVS
        long_ret = (signed long) stat->dinfo->devices[indx].operations[DEVSTAT_READ] & 0xFFFFFFFF;
#else
        long_ret = (signed long) stat->dinfo->devices[indx].num_reads;
#endif
        return (u_char *) & long_ret;
    case DISKIO_WRITES:
#if HAVE_DEVSTAT_GETDEVS
        long_ret = (signed long) stat->dinfo->devices[indx].operations[DEVSTAT_WRITE] & 0xFFFFFFFF;
#else
        long_ret = (signed long) stat->dinfo->devices[indx].num_writes;
#endif
        return (u_char *) & long_ret;
    case DISKIO_LA1:
	long_ret = devloads[indx].la1;
	return (u_char *) & long_ret;
    case DISKIO_LA5:
        long_ret = devloads[indx].la5;
        return (u_char *) & long_ret;
    case DISKIO_LA15:
        long_ret = devloads[indx].la15;
        return (u_char *) & long_ret;

    default:
        ERROR_MSG("diskio.c: don't know how to handle this request.");
    }
    return NULL;
}
#endif                          /* freebsd4 */


#ifdef linux


void devla_getstats(unsigned int regno, void * dummy) {

    static double expon1, expon5, expon15;
    double busy_time, busy_percent;
    int idx;

    if (getstats() == 1) {
        ERROR_MSG("can't do diskio getstats()\n");
        return;
    }

    if (!la_head.length) {
        la_head.indices = (linux_diskio_la *) malloc(head.length * sizeof(linux_diskio_la));
        for (idx=0; idx<head.length; idx++) {
            la_head.indices[idx].la1 = la_head.indices[idx].la5 = la_head.indices[idx].la15 = 0.; 
            la_head.indices[idx].use_prev = head.indices[idx].use;
        }
        la_head.length = head.length;
        expon1 = exp(-(((double)DISKIO_SAMPLE_INTERVAL) / ((double)60)));
        expon5 = exp(-(((double)DISKIO_SAMPLE_INTERVAL) / ((double)300)));
        expon15 = exp(-(((double)DISKIO_SAMPLE_INTERVAL) / ((double)900)));
    }
    else if (head.length - la_head.length) {
        la_head.indices = (linux_diskio_la *) realloc(la_head.indices, head.length * sizeof(linux_diskio_la));
        for (idx=la_head.length; idx<head.length; idx++) {
            la_head.indices[idx].la1 = la_head.indices[idx].la5 = la_head.indices[idx].la15 = 0.; 
            la_head.indices[idx].use_prev = head.indices[idx].use;
        }
        la_head.length = head.length;
    }

    for (idx=0; idx<head.length; idx++) {
        busy_time = head.indices[idx].use - la_head.indices[idx].use_prev;
        busy_percent = busy_time * 100. / ((double) DISKIO_SAMPLE_INTERVAL) / 1000.;
        la_head.indices[idx].la1 = la_head.indices[idx].la1 * expon1 + busy_percent * (1. - expon1);
        la_head.indices[idx].la5 = la_head.indices[idx].la5 * expon5 + busy_percent * (1. - expon5);
        la_head.indices[idx].la15 = la_head.indices[idx].la15 * expon15 + busy_percent * (1. - expon15);
        /*
          fprintf(stderr, "(%d) update la1=%f la5=%f la15=%f\n",
          idx, la_head.indices[idx].la1, la_head.indices[idx].la5, la_head.indices[idx].la15);   
        */
        la_head.indices[idx].use_prev = head.indices[idx].use;
    }
}

int is_excluded(const char *name)
{
    if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
                               NETSNMP_DS_AGENT_DISKIO_NO_FD)
                           && !(strncmp(name, "fd", 2)))
        return 1;
    if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
                               NETSNMP_DS_AGENT_DISKIO_NO_LOOP)
                           && !(strncmp(name, "loop", 4)))
        return 1;
    if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
                               NETSNMP_DS_AGENT_DISKIO_NO_RAM)
                           && !(strncmp(name, "ram", 3)))
        return 1;
    return 0;
}

static int get_sysfs_stats(void)
{
    int i;
    char buffer[1024];

    head.length  = 0;

    for(i = 0; i < numdisks; i++) {
        FILE *f = fopen(disks[i].syspath, "r");
        if ( f == NULL ) {
            DEBUGMSGTL(("ucd-snmp/diskio", "Can't open %s, skipping", disks[i].syspath));
            continue;
        }

        if (fgets(buffer, sizeof(buffer), f) == NULL) {
            DEBUGMSGTL(("ucd-snmp/diskio", "Can't read %s, skipping", disks[i].syspath));
            fclose(f);
            continue;
        }

        linux_diskio* pTemp;
        if (head.length == head.alloc) {
            head.alloc += DISK_INCR;
            head.indices = (linux_diskio *) realloc(head.indices, head.alloc*sizeof(linux_diskio));
        }
        pTemp = &head.indices[head.length];
        pTemp->major = disks[i].major;
        pTemp->minor = disks[i].minor;
        strlcpy( pTemp->name, disks[i].shortname, sizeof(pTemp->name) - 1 );
        if (sscanf (buffer, "%*[ \n\t]%lu%*[ \n\t]%lu%*[ \n\t]%lu%*[ \n\t]%lu%*[ \n\t]%lu%*[ \n\t]%lu%*[ \n\t]%lu%*[ \n\t]%lu%*[ \n\t]%lu%*[ \n\t]%lu%*[ \n\t]%lu\n",
                &pTemp->rio, &pTemp->rmerge, &pTemp->rsect, &pTemp->ruse,
                &pTemp->wio, &pTemp->wmerge, &pTemp->wsect, &pTemp->wuse,
                &pTemp->running, &pTemp->use, &pTemp->aveq) != 11)
            sscanf (buffer, "%*[ \n\t]%lu%*[ \n\t]%lu%*[ \n\t]%lu%*[ \n\t]%lu\n",
                &pTemp->rio, &pTemp->rsect,
                &pTemp->wio, &pTemp->wsect);
        head.length++;
        fclose(f);
    }
    return 0;
}

static int
getstats(void)
{
    struct stat stbuf;
    FILE* parts;
    time_t now;
    
    now = time(NULL);
    if (cache_time + CACHE_TIMEOUT > now) {
        return 0;
    }

    if (!head.indices) {
	head.alloc = DISK_INCR;
	head.indices = (linux_diskio *)malloc(head.alloc*sizeof(linux_diskio));
    }
    head.length  = 0;

    memset(head.indices, 0, head.alloc*sizeof(linux_diskio));

    if (numdisks>0) {
        /* 'diskio' configuration is used - go through the whitelist only and
         * read /sys/dev/block/xxx */
        cache_time = now;
        return get_sysfs_stats();
    }
    /* 'diskio' configuration is not used - report all devices */
    /* Is this a 2.6 kernel? */
    parts = fopen("/proc/diskstats", "r");
    if (parts) {
	char buffer[1024];
	while (fgets(buffer, sizeof(buffer), parts)) {
	    linux_diskio* pTemp;
	    if (head.length == head.alloc) {
		head.alloc += DISK_INCR;
		head.indices = (linux_diskio *)realloc(head.indices, head.alloc*sizeof(linux_diskio));
	    }
	    pTemp = &head.indices[head.length];
	    sscanf (buffer, "%d %d", &pTemp->major, &pTemp->minor);
 	    if (sscanf (buffer, "%d %d %s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu\n",
		    &pTemp->major, &pTemp->minor, pTemp->name,
		    &pTemp->rio, &pTemp->rmerge, &pTemp->rsect, &pTemp->ruse,
		    &pTemp->wio, &pTemp->wmerge, &pTemp->wsect, &pTemp->wuse,
 		    &pTemp->running, &pTemp->use, &pTemp->aveq) != 14)
		sscanf (buffer, "%d %d %s %lu %lu %lu %lu\n",
		    &pTemp->major, &pTemp->minor, pTemp->name,
		    &pTemp->rio, &pTemp->rsect,
		    &pTemp->wio, &pTemp->wsect);
            if (!is_excluded(pTemp->name))
	        head.length++;
	}
    }
    else if (stat("/proc/vz", &stbuf) == 0) {
        // OpenVZ / Virtuozzo containers do not have /proc/diskstats
        goto update_cache_time;
    }
    else {
	/* See if a 2.4 kernel */
	char buffer[1024];
	int rc;
	parts = fopen("/proc/partitions", "r");
	if (!parts) {
	    snmp_log_perror("/proc/partitions");
	    return 1;
	}

	/*
	 * first few fscanfs are garbage we don't care about. skip it.
	 */
	NETSNMP_IGNORE_RESULT(fgets(buffer, sizeof(buffer), parts));
	NETSNMP_IGNORE_RESULT(fgets(buffer, sizeof(buffer), parts));

	while (! feof(parts)) {
	    linux_diskio* pTemp;

	    if (head.length == head.alloc) {
		head.alloc += DISK_INCR;
		head.indices = (linux_diskio *)realloc(head.indices, head.alloc*sizeof(linux_diskio));
	    }
	    pTemp = &head.indices[head.length];

	    rc = fscanf(parts, "%d %d %lu %255s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu\n",
		    &pTemp->major, &pTemp->minor, &pTemp->blocks, pTemp->name,
		    &pTemp->rio, &pTemp->rmerge, &pTemp->rsect, &pTemp->ruse,
		    &pTemp->wio, &pTemp->wmerge, &pTemp->wsect, &pTemp->wuse,
		    &pTemp->running, &pTemp->use, &pTemp->aveq);
            if (rc != 15) {
               snmp_log(LOG_ERR, "diskio.c: cannot find statistics in /proc/partitions\n");
               fclose(parts);
               return 1;
            }
            if (!is_excluded(pTemp->name))
	        head.length++;
	}
    }

    fclose(parts);

update_cache_time:
    cache_time = now;
    return 0;
}

u_char *
var_diskio(struct variable * vp,
	   oid * name,
	   size_t * length,
	   int exact,
	   size_t * var_len,
	   WriteMethod ** write_method)
{
    unsigned int indx;
    static unsigned long long_ret;
    static struct counter64 c64_ret;

    if (getstats() == 1) {
	return NULL;
    }

 if (header_simple_table(vp, name, length, exact, var_len, write_method, head.length))
    {
	return NULL;
    }

  indx = (unsigned int) (name[*length - 1] - 1);

  if (indx >= head.length)
    return NULL;

  switch (vp->magic) {
    case DISKIO_INDEX:
      long_ret = indx+1;
      return (u_char *) &long_ret;
    case DISKIO_DEVICE:
      *var_len = strlen(head.indices[indx].name);
      return (u_char *) head.indices[indx].name;
    case DISKIO_NREAD:
      long_ret = (head.indices[indx].rsect*512) & 0xffffffff;
      return (u_char *) & long_ret;
    case DISKIO_NWRITTEN:
      long_ret = (head.indices[indx].wsect*512) & 0xffffffff;
      return (u_char *) & long_ret;
    case DISKIO_READS:
      long_ret = head.indices[indx].rio & 0xffffffff;
      return (u_char *) & long_ret;
    case DISKIO_WRITES:
      long_ret = head.indices[indx].wio & 0xffffffff;
      return (u_char *) & long_ret;
    case DISKIO_LA1:
      if (la_head.length > indx)
          long_ret = la_head.indices[indx].la1;
      else
          long_ret = 0; /* we don't have the load yet */
      return (u_char *) & long_ret;
    case DISKIO_LA5:
      if (la_head.length > indx)
          long_ret = la_head.indices[indx].la5;
      else
          long_ret = 0; /* we don't have the load yet */
      return (u_char *) & long_ret;
    case DISKIO_LA15:
      if (la_head.length > indx)
          long_ret = la_head.indices[indx].la15;
      else
          long_ret = 0;
      return (u_char *) & long_ret;
    case DISKIO_BUSYTIME:
      *var_len = sizeof(struct counter64);
      c64_ret.low = head.indices[indx].use*1000 & 0xffffffff;
      c64_ret.high = head.indices[indx].use*1000 >> 32;
      return (u_char *) & c64_ret;
    case DISKIO_NREADX:
      *var_len = sizeof(struct counter64);
      c64_ret.low = head.indices[indx].rsect * 512 & 0xffffffff;
      c64_ret.high = head.indices[indx].rsect >> (32 - 9);
      return (u_char *) & c64_ret;
    case DISKIO_NWRITTENX:
      *var_len = sizeof(struct counter64);
      c64_ret.low = head.indices[indx].wsect * 512 & 0xffffffff;
      c64_ret.high = head.indices[indx].wsect >> (32 - 9);
      return (u_char *) & c64_ret;
    default:
	snmp_log(LOG_ERR, "don't know how to handle %d request\n", vp->magic);
  }
  return NULL;
}
#endif  /* linux */

#if defined(darwin)

#define MAXDRIVES	16	/* most drives we will record */
#define MAXDRIVENAME	31	/* largest drive name we allow */

#define kIDXBytesRead		0	/* used as index into the stats array in a drivestats struct */
#define kIDXBytesWritten	1
#define kIDXNumReads		2
#define kIDXNumWrites		3
#define kIDXBytesReadXhi	4
#define kIDXBytesReadXlo	5
#define kIDXBytesWrittenXhi	6
#define kIDXBytesWrittenXlo	7
#define kIDXLast		7

struct drivestats {
    char name[MAXDRIVENAME + 1];
    long bsd_unit_number;
    long stats[kIDXLast+1];
};

static struct drivestats drivestat[MAXDRIVES];

static mach_port_t masterPort;		/* to communicate with I/O Kit	*/

static int num_drives;			/* number of drives detected	*/

static int
collect_drive_stats(io_registry_entry_t driver, long *stats)
{
    CFNumberRef     number;
    CFDictionaryRef properties;
    CFDictionaryRef statistics;
    long            value;
    SInt64          value64;
    kern_return_t   status;
    int             i;


    /*
     * If the drive goes away, we may not get any properties
     * for it.  So take some defaults. Nb: use memset ??
     */
    for (i = 0; i < kIDXLast; i++) {
	stats[i] = 0;
    }

    /* retrieve the properties */
    status = IORegistryEntryCreateCFProperties(driver, (CFMutableDictionaryRef *)&properties,
					       kCFAllocatorDefault, kNilOptions);
    if (status != KERN_SUCCESS) {
	snmp_log(LOG_ERR, "diskio: device has no properties\n");
/*	fprintf(stderr, "device has no properties\n"); */
	return (1);
    }

    /* retrieve statistics from properties */
    statistics = (CFDictionaryRef)CFDictionaryGetValue(properties,
						       CFSTR(kIOBlockStorageDriverStatisticsKey));
    if (statistics) {

	/* Now hand me the crystals. */
	if ((number = (CFNumberRef)CFDictionaryGetValue(statistics,
						 CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey)))) {
	    CFNumberGetValue(number, kCFNumberSInt32Type, &value);
	    stats[kIDXBytesRead] = value;
	}

	if ((number = (CFNumberRef)CFDictionaryGetValue(statistics,
						 CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey)))) {
	    CFNumberGetValue(number, kCFNumberSInt32Type, &value);
	    stats[kIDXBytesWritten] = value;
	}

	if ((number = (CFNumberRef)CFDictionaryGetValue(statistics,
						 CFSTR(kIOBlockStorageDriverStatisticsReadsKey)))) {
	    CFNumberGetValue(number, kCFNumberSInt32Type, &value);
	    stats[kIDXNumReads] = value;
	}
	if ((number = (CFNumberRef)CFDictionaryGetValue(statistics,
						 CFSTR(kIOBlockStorageDriverStatisticsWritesKey)))) {
	    CFNumberGetValue(number, kCFNumberSInt32Type, &value);
	    stats[kIDXNumWrites] = value;
	}
	/* grab the 64 bit versions of the bytes read */
	if ((number = (CFNumberRef)CFDictionaryGetValue(statistics,
						 CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey)))) {
	    CFNumberGetValue(number, kCFNumberSInt64Type, &value64);
	    stats[kIDXBytesReadXhi] = (long)(value64 >> 32);
	    stats[kIDXBytesReadXlo] = (long)(value64 & 0xffffffff);	
	}
		
	/* grab the 64 bit versions of the bytes written */
	if ((number = (CFNumberRef)CFDictionaryGetValue(statistics,
						 CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey)))) {
	    CFNumberGetValue(number, kCFNumberSInt64Type, &value64);
	    stats[kIDXBytesWrittenXhi] = (long)(value64 >> 32);
	    stats[kIDXBytesWrittenXlo] = (long)(value64 & 0xffffffff);	
	}
    }
    /* we're done with the properties, release them */
    CFRelease(properties);
    return (0);
}

/*
 * Check whether an IORegistryEntry refers to a valid
 * I/O device, and if so, collect the information.
 */
static int
handle_drive(io_registry_entry_t drive, struct drivestats * dstat)
{
    io_registry_entry_t parent;
    CFMutableDictionaryRef     properties;
    CFStringRef         name;
    CFNumberRef         number;
    kern_return_t       status;

    /* get drive's parent */
    status = IORegistryEntryGetParentEntry(drive, kIOServicePlane, &parent);
    if (status != KERN_SUCCESS) {
	snmp_log(LOG_ERR, "diskio: device has no parent\n");
/*	fprintf(stderr, "device has no parent\n"); */
	return(1);
    }

    if (IOObjectConformsTo(parent, "IOBlockStorageDriver")) {

	/* get drive properties */
	status = IORegistryEntryCreateCFProperties(drive, &properties,
					    kCFAllocatorDefault, kNilOptions);
	if (status != KERN_SUCCESS) {
	    snmp_log(LOG_ERR, "diskio: device has no properties\n");
/*	    fprintf(stderr, "device has no properties\n"); */
	    return(1);
	}

	/* get BSD name and unitnumber from properties */
	name = (CFStringRef)CFDictionaryGetValue(properties,
					  CFSTR(kIOBSDNameKey));
	number = (CFNumberRef)CFDictionaryGetValue(properties,
					    CFSTR(kIOBSDUnitKey));

	/* Collect stats and if succesful store them with the name and unitnumber */
	if (name && number && !collect_drive_stats(parent, dstat->stats)) {

	    CFStringGetCString(name, dstat->name, MAXDRIVENAME, CFStringGetSystemEncoding());
	    CFNumberGetValue(number, kCFNumberSInt32Type, &dstat->bsd_unit_number);
	    num_drives++;
	}

	/* clean up, return success */
	CFRelease(properties);
	return(0);
    }

    /* failed, don't keep parent */
    IOObjectRelease(parent);
    return(1);
}

static int
getstats(void)
{
    time_t                 now;
    io_iterator_t          drivelist;
    io_registry_entry_t    drive;
    CFMutableDictionaryRef match;
    kern_return_t          status;

    now = time(NULL);	/* register current time and check wether cache can be used */
    if (cache_time + CACHE_TIMEOUT > now) {
        return 0;
    }

    /*  Retrieve a list of drives. */
    match = IOServiceMatching("IOMedia");
    CFDictionaryAddValue(match, CFSTR(kIOMediaWholeKey), kCFBooleanTrue);
    status = IOServiceGetMatchingServices(masterPort, match, &drivelist);
    if (status != KERN_SUCCESS) {
	snmp_log(LOG_ERR, "diskio: couldn't match whole IOMedia devices\n");
/*	fprintf(stderr,"Couldn't match whole IOMedia devices\n"); */
	return -1;
    }

    num_drives = 0;  /* NB: Incremented by handle_drive */
    while ((drive = IOIteratorNext(drivelist)) && (num_drives < MAXDRIVES)) {
	handle_drive(drive, &drivestat[num_drives]);
	IOObjectRelease(drive);
    }
    IOObjectRelease(drivelist);

    cache_time = now;
    return 0;
}

u_char         *
var_diskio(struct variable * vp,
           oid * name,
           size_t * length,
           int exact, size_t * var_len, WriteMethod ** write_method)
{
    static long     long_ret;
    static struct   counter64 c64_ret;
    unsigned int    indx;

    if (getstats() == 1) {
        return NULL;
    }


    if (header_simple_table
        (vp, name, length, exact, var_len, write_method, num_drives)) {
        return NULL;
    }

    indx = (unsigned int) (name[*length - 1] - 1);

    if (indx >= num_drives)
        return NULL;

    switch (vp->magic) {
	case DISKIO_INDEX:
	    long_ret = (long) drivestat[indx].bsd_unit_number;
	    return (u_char *) & long_ret;
	case DISKIO_DEVICE:
	    *var_len = strlen(drivestat[indx].name);
	    return (u_char *) drivestat[indx].name;
	case DISKIO_NREAD:
	    long_ret = (signed long) drivestat[indx].stats[kIDXBytesRead];
	    return (u_char *) & long_ret;
	case DISKIO_NWRITTEN:
	    long_ret = (signed long) drivestat[indx].stats[kIDXBytesWritten];
	    return (u_char *) & long_ret;
	case DISKIO_READS:
	    long_ret = (signed long) drivestat[indx].stats[kIDXNumReads];
	    return (u_char *) & long_ret;
	case DISKIO_WRITES:
	    long_ret = (signed long) drivestat[indx].stats[kIDXNumWrites];
	    return (u_char *) & long_ret;
	case DISKIO_NREADX:
	    *var_len = 8;
	    c64_ret.low = (signed long) drivestat[indx].stats[kIDXBytesReadXlo];
	    c64_ret.high = (signed long) drivestat[indx].stats[kIDXBytesReadXhi];
	    return (u_char *) & c64_ret;
	case DISKIO_NWRITTENX:
	    *var_len = 8;
	    c64_ret.low = (signed long) drivestat[indx].stats[kIDXBytesWrittenXlo];
	    c64_ret.high = (signed long) drivestat[indx].stats[kIDXBytesWrittenXhi];
	    return (u_char *) & c64_ret;
	default:
	    ERROR_MSG("diskio.c: don't know how to handle this request.");
    }
    return NULL;
}
#endif                          /* darwin */


#if defined(aix4) || defined(aix5) || defined(aix6) || defined(aix7)
/*
 * collect statistics for all disks
 */
int
collect_disks(void)
{
    time_t          now;
    int             i;
    perfstat_id_t   first;

    /* cache valid? if yes, just return */
    now = time(NULL);
    if (ps_disk != NULL && cache_time + CACHE_TIMEOUT > now) {
        return 0;
    }

    /* get number of disks we have */
    i = perfstat_disk(NULL, NULL, sizeof(perfstat_disk_t), 0);
    if(i <= 0) return 1;

    /* if number of disks differs or structures are uninitialized, init them */
    if(i != ps_numdisks || ps_disk == NULL) {
        if(ps_disk != NULL) free(ps_disk);
        ps_numdisks = i;
        ps_disk = malloc(sizeof(perfstat_disk_t) * ps_numdisks);
        if(ps_disk == NULL) return 1;
    }

    /* gather statistics about all disks we have */
    strcpy(first.name, "");
    i = perfstat_disk(&first, ps_disk, sizeof(perfstat_disk_t), ps_numdisks);
    if(i != ps_numdisks) return 1;

    cache_time = now;
    return 0;
}


u_char         *
var_diskio(struct variable * vp,
           oid * name,
           size_t * length,
           int exact, size_t * var_len, WriteMethod ** write_method)
{
    static long     long_ret;
    static struct counter64 c64_ret;
    unsigned int    indx;

    /* get disk statistics */
    if (collect_disks())
        return NULL;

    if (header_simple_table
        (vp, name, length, exact, var_len, write_method, ps_numdisks))
        return NULL;

    indx = (unsigned int) (name[*length - 1] - 1);
    if (indx >= ps_numdisks)
        return NULL;

    /* deliver requested data on requested disk */
    switch (vp->magic) {
    case DISKIO_INDEX:
        long_ret = (long) indx;
        return (u_char *) & long_ret;
    case DISKIO_DEVICE:
        *var_len = strlen(ps_disk[indx].name);
        return (u_char *) ps_disk[indx].name;
    case DISKIO_NREAD:
        long_ret = (signed long) ps_disk[indx].rblks * ps_disk[indx].bsize;
        return (u_char *) & long_ret;
    case DISKIO_NWRITTEN:
        long_ret = (signed long) ps_disk[indx].wblks * ps_disk[indx].bsize;
        return (u_char *) & long_ret;
    case DISKIO_READS:
        long_ret = (signed long) ps_disk[indx].xfers;
        return (u_char *) & long_ret;
    case DISKIO_WRITES:
        long_ret = (signed long) 0;	/* AIX has just one value for read/write transfers */
        return (u_char *) & long_ret;
    case DISKIO_NREADX:
        *var_len = sizeof(struct counter64);
        c64_ret.low = (ps_disk[indx].rblks * ps_disk[indx].bsize) & 0xffffffff;
        c64_ret.high = (ps_disk[indx].rblks * ps_disk[indx].bsize) >> 32;
        return (u_char *) & c64_ret;
    case DISKIO_NWRITTENX:
        *var_len = sizeof(struct counter64);
        c64_ret.low = (ps_disk[indx].wblks * ps_disk[indx].bsize) & 0xffffffff;
        c64_ret.high = (ps_disk[indx].wblks * ps_disk[indx].bsize) >> 32;
        return (u_char *) & c64_ret;

    default:
        ERROR_MSG("diskio.c: don't know how to handle this request.");
    }

    /* return NULL in case of error */
    return NULL;
}
#endif                          /* aix 4/5 */
