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

#if HAVE_LIMITS_H
#include <limits.h>
#endif
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <ctype.h>
#include <signal.h>
#if HAVE_MACHINE_PARAM_H
#include <machine/param.h>
#endif
#if HAVE_SYS_VMMETER_H
#if !defined(bsdi2) && !defined(netbsd1)
#include <sys/vmmeter.h>
#endif
#endif
#if HAVE_SYS_CONF_H
#include <sys/conf.h>
#endif
#if HAVE_SYS_FS_H
#include <sys/fs.h>
#else
#if HAVE_UFS_FS_H
#include <ufs/fs.h>
#else
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_SYS_VNODE_H
#include <sys/vnode.h>
#endif
#ifdef HAVE_UFS_UFS_QUOTA_H
#include <ufs/ufs/quota.h>
#endif
#ifdef HAVE_UFS_UFS_INODE_H
#include <ufs/ufs/inode.h>
#endif
#if HAVE_UFS_FFS_FS_H
#include <ufs/ffs/fs.h>
#endif
#endif
#endif
#if HAVE_MTAB_H
#include <mtab.h>
#endif
#include <sys/stat.h>
#include <errno.h>
#if HAVE_FSTAB_H
#include <fstab.h>
#endif
#if HAVE_SYS_STATVFS_H
#include <sys/statvfs.h>
#endif
#if HAVE_SYS_VFS_H
#include <sys/vfs.h>
#endif
#if (!defined(HAVE_STATVFS)) && defined(HAVE_STATFS)
#if HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#if HAVE_SYS_MOUNT_H
#include <sys/mount.h>
#endif
#if HAVE_SYS_SYSCTL_H
#include <sys/sysctl.h>
#endif
#define statvfs statfs
#endif
#if HAVE_VM_SWAP_PAGER_H
#include <vm/swap_pager.h>
#endif
#if HAVE_SYS_FIXPOINT_H
#include <sys/fixpoint.h>
#endif
#if HAVE_MALLOC_H
#include <malloc.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#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 <sys/utsname.h>

#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include <net-snmp/agent/auto_nlist.h>
#include <net-snmp/agent/hardware/cpu.h>

#include "mibdefs.h"
#include "struct.h"
#include "util_funcs.h"
#include "vmstat.h"

FindVarMethod var_extensible_vmstat;

static int has_vmstat = 1;
static int has_cpu_26 = 1;
static time_t cache_time;
#define CACHE_TIMEOUT	5
#define MAX_INT32 0x7fffffff
#define MAX_COUNTER 0xffffffff

#define STAT_FILE	"/proc/stat"
#define VMSTAT_FILE	"/proc/vmstat"


void
init_vmstat(void)
{
    struct variable2 extensible_vmstat_variables[] = {
        {MIBINDEX, ASN_INTEGER, RONLY, var_extensible_vmstat, 1,
         {MIBINDEX}},
        {ERRORNAME, ASN_OCTET_STR, RONLY, var_extensible_vmstat, 1,
         {ERRORNAME}},
        {SWAPIN, ASN_INTEGER, RONLY, var_extensible_vmstat, 1, {SWAPIN}},
        {SWAPOUT, ASN_INTEGER, RONLY, var_extensible_vmstat, 1, {SWAPOUT}},
        {RAWSWAPIN, ASN_COUNTER, RONLY, var_extensible_vmstat, 1, {RAWSWAPIN}},
        {RAWSWAPOUT, ASN_COUNTER, RONLY, var_extensible_vmstat, 1, {RAWSWAPOUT}},
        {IOSENT, ASN_INTEGER, RONLY, var_extensible_vmstat, 1, {IOSENT}},
        {IORECEIVE, ASN_INTEGER, RONLY, var_extensible_vmstat, 1,
         {IORECEIVE}},
        {IORAWSENT, ASN_COUNTER, RONLY, var_extensible_vmstat, 1, {IORAWSENT}},
        {IORAWRECEIVE, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {IORAWRECEIVE}},
        {SYSINTERRUPTS, ASN_INTEGER, RONLY, var_extensible_vmstat, 1,
         {SYSINTERRUPTS}},
        {SYSCONTEXT, ASN_INTEGER, RONLY, var_extensible_vmstat, 1,
         {SYSCONTEXT}},
        {CPUUSER, ASN_INTEGER, RONLY, var_extensible_vmstat, 1, {CPUUSER}},
        {CPUSYSTEM, ASN_INTEGER, RONLY, var_extensible_vmstat, 1,
         {CPUSYSTEM}},
        {CPUIDLE, ASN_INTEGER, RONLY, var_extensible_vmstat, 1, {CPUIDLE}},
        {CPURAWUSER, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {CPURAWUSER}},
        {CPURAWNICE, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {CPURAWNICE}},
        {CPURAWSYSTEM, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {CPURAWSYSTEM}},
        {CPURAWKERNEL, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {CPURAWKERNEL}},
        {CPURAWIDLE, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {CPURAWIDLE}},
        {SYSRAWINTERRUPTS, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {SYSRAWINTERRUPTS}},
        {SYSRAWCONTEXT, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {SYSRAWCONTEXT}},
        {CPURAWWAIT, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {CPURAWWAIT}},
        {CPURAWINTR, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {CPURAWINTR}},
        {CPURAWSOFTIRQ, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {CPURAWSOFTIRQ}},
        /*
         * Future use: 
         */
        /*
         * {ERRORFLAG, ASN_INTEGER, RONLY, var_extensible_vmstat, 1, {ERRORFLAG }},
         * {ERRORMSG, ASN_OCTET_STR, RONLY, var_extensible_vmstat, 1, {ERRORMSG }}
         */
    };

    /*
     * Define the OID pointer to the top of the mib tree that we're
     * registering underneath 
     */
    oid             vmstat_variables_oid[] = { NETSNMP_UCDAVIS_MIB, 11 };

    /*
     * register ourselves with the agent to handle our mib tree 
     */
    REGISTER_MIB("ucd-snmp/vmstat", extensible_vmstat_variables, variable2,
                 vmstat_variables_oid);
}


static void
getstat(unsigned long *cuse, unsigned long *cice, unsigned long *csys,
        unsigned long *cide, unsigned *pin, unsigned *pout,
        unsigned *swpin, unsigned *swpout, unsigned *itot, unsigned *i1,
        unsigned *ct, unsigned long *ciow, unsigned long *cirq,
	unsigned long *csoft)
{
    int             statfd, vmstatfd;
    static int      first = 1;
    static char    *buff = NULL, *vmbuff = NULL;
    static int      bsize = 0, vmbsize = 0;
    char           *b, *c;
    time_t          now;
    unsigned long long cpunum;
    unsigned long long cusell = 0, cicell = 0, csysll = 0, cidell = 0,
        ciowll = 0, cirqll = 0, csoftll = 0, ctll = 0, itotll = 0, i1ll = 0;

    time(&now);
    if (cache_time + CACHE_TIMEOUT < now) {
	if ((statfd = open(STAT_FILE, O_RDONLY, 0)) == -1) {
	    snmp_log_perror(STAT_FILE);
	    return;
	}
        if (bsize == 0) {
            bsize = 256;
            buff = malloc(bsize);
        }
        while (read(statfd, buff, bsize) == bsize) {
            bsize += 256;
            buff = realloc(buff, bsize);
            DEBUGMSGTL(("vmstat", "/proc/stat buffer increased to %d\n", bsize));
            close(statfd);
            statfd = open(STAT_FILE, O_RDONLY, 0);
        }
        close(statfd);

	if (has_vmstat) {
          vmstatfd = open(VMSTAT_FILE, O_RDONLY, 0);
          if (vmstatfd == -1) {
                snmp_log(LOG_ERR, "cannot open %s\n", VMSTAT_FILE);
                has_vmstat = 0;
          } else {
	    if (vmbsize == 0) {
		vmbsize = 256;
		vmbuff = malloc(vmbsize);
	    }
	    while (read(vmstatfd, vmbuff, vmbsize) == vmbsize) {
		vmbsize += 256;
		vmbuff = realloc(vmbuff, vmbsize);
		close(vmstatfd);
		vmstatfd = open(VMSTAT_FILE, O_RDONLY, 0);
	    }
	    close(vmstatfd);
          }
	}
	cache_time = now;
    }

    *itot = 0;
    *i1 = 1;
    c = buff;
    while ((c = strstr(c+1, "cpu")) != NULL)
        b = c;
    sscanf(b, "cpu%llu", &cpunum);
    cpunum++;
    b = strstr(buff, "cpu ");
    if (b) {
	if (!has_cpu_26 ||
            sscanf(b, "cpu  %llu %llu %llu %llu %llu %llu %llu", &cusell,
                   &cicell, &csysll, &cidell, &ciowll, &cirqll, &csoftll) != 7) {
	    has_cpu_26 = 0;
	    sscanf(b, "cpu  %llu %llu %llu %llu", &cusell, &cicell, &csysll,
                   &cidell);
	    *ciow = *cirq = *csoft = 0;
	} else {
  	    *ciow = (unsigned long)(ciowll/cpunum);
  	    *cirq = (unsigned long)(cirqll/cpunum);
  	    *csoft = (unsigned long)(csoftll/cpunum);
 	}
	*cuse = (unsigned long)(cusell/cpunum);
	*cice = (unsigned long)(cicell/cpunum);
	*csys = (unsigned long)(csysll/cpunum);
	*cide = (unsigned long)(cidell/cpunum);

    }
    else {
	if (first)
	    snmp_log(LOG_ERR, "No cpu line in %s\n", STAT_FILE);
	*cuse = *cice = *csys = *cide = *ciow = *cirq = *csoft = 0;
    }
    if (has_vmstat) {
	b = strstr(vmbuff, "pgpgin ");
	if (b)
	    sscanf(b, "pgpgin %u", pin);
	else {
	    if (first)
		snmp_log(LOG_ERR, "No pgpgin line in %s\n", VMSTAT_FILE);
	    *pin = 0;
	}
	b = strstr(vmbuff, "pgpgout ");
	if (b)
	    sscanf(b, "pgpgout %u", pout);
	else {
	    if (first)
		snmp_log(LOG_ERR, "No pgpgout line in %s\n", VMSTAT_FILE);
	    *pout = 0;
	}
	b = strstr(vmbuff, "pswpin ");
	if (b)
	    sscanf(b, "pswpin %u", swpin);
	else {
	    if (first)
		snmp_log(LOG_ERR, "No pswpin line in %s\n", VMSTAT_FILE);
	    *swpin = 0;
	}
	b = strstr(vmbuff, "pswpout ");
	if (b)
	    sscanf(b, "pswpout %u", swpout);
	else {
	    if (first)
		snmp_log(LOG_ERR, "No pswpout line in %s\n", VMSTAT_FILE);
	    *swpout = 0;
	}
    }
    else {
	b = strstr(buff, "page ");
	if (b)
	    sscanf(b, "page %u %u", pin, pout);
	else {
	    if (first)
		snmp_log(LOG_ERR, "No page line in %s\n", STAT_FILE);
	    *pin = *pout = 0;
	}
	b = strstr(buff, "swap ");
	if (b)
	    sscanf(b, "swap %u %u", swpin, swpout);
	else {
	    if (first)
		snmp_log(LOG_ERR, "No swap line in %s\n", STAT_FILE);
	    *swpin = *swpout = 0;
	}
    }
    b = strstr(buff, "intr ");
    if (b) {
	sscanf(b, "intr %llu %llu", &itotll, &i1ll);
        *itot = (unsigned)itotll;
        *i1 = (unsigned)i1ll;
    }
    else {
	if (first)
	    snmp_log(LOG_ERR, "No intr line in %s\n", STAT_FILE);
	*itot = 0;
    }
    b = strstr(buff, "ctxt ");
    if (b) {
	sscanf(b, "ctxt %llu", &ctll);
        *ct = (unsigned long)ctll;
    }
    else {
	if (first)
	    snmp_log(LOG_ERR, "No ctxt line in %s\n", STAT_FILE);
	*ct = 0;
    }
    first = 0;
}

enum vmstat_index { swapin = 0, swapout,
    rawswapin, rawswapout,
    iosent, ioreceive,
    rawiosent, rawioreceive,
    sysinterrupts, syscontext,
    cpuuser, cpusystem, cpuidle,
    cpurawuser, cpurawnice,
    cpurawsystem, cpurawidle,
    cpurawinter, cpurawsoft, cpurawwait,
    rawinterrupts, rawcontext
};

static unsigned
vmstat(int iindex)
{
    double          duse, dsys, didl, ddiv, divo2;
    double          druse, drnic, drsys, dridl;
    unsigned int    hertz;
    double          ddiv2;

    netsnmp_cpu_info *cpu;
    netsnmp_cpu_load();
    cpu = netsnmp_cpu_get_byIdx( -1, 0 );

    duse = cpu->user_ticks + cpu->nice_ticks;
    dsys = cpu->sys_ticks;
    didl = cpu->idle_ticks;
    ddiv = duse + dsys + didl;
    hertz = sysconf(_SC_CLK_TCK);  /* get ticks/s from system */
    divo2 = ddiv / 2;
    druse = cpu->user_ticks;
    drnic = cpu->nice_ticks;
    drsys = cpu->sys_ticks;
    dridl = cpu->idle_ticks;

    ddiv2 = ddiv + cpu->wait_ticks
                 + cpu->intrpt_ticks
                 + cpu->sirq_ticks;
    if (cpu->history) {
        duse  -= (cpu->history[0].user_hist + cpu->history[0].nice_hist);
        dsys  -=  cpu->history[0].sys_hist;
        didl  -=  cpu->history[0].idle_hist;
        ddiv2 -=  cpu->history[0].total_hist;
    }
    if (!ddiv) ddiv=1;   /* Protect against division-by-0 */
 
    switch (iindex) {
    case swapin:
        return (cpu->swapIn  * 4 * hertz + divo2) / ddiv;
    case swapout:
        return (cpu->swapOut * 4 * hertz + divo2) / ddiv;
    case iosent:
        return (cpu->pageIn      * hertz + divo2) / ddiv;
    case ioreceive:
        return (cpu->pageOut     * hertz + divo2) / ddiv;
    case sysinterrupts:
        return (cpu->nInterrupts  * hertz + divo2) / ddiv;
    case syscontext:
        return (cpu->nCtxSwitches * hertz + divo2) / ddiv;
    case cpuuser:
        return (ddiv2 ? 100 * duse / ddiv2 : 0);
    case cpusystem:
        return (ddiv2 ? 100 * dsys / ddiv2 : 0);
    case cpuidle:
        return (ddiv2 ? 100 * didl / ddiv2 : 0);
    case cpurawuser:
        return druse;
    case cpurawnice:
        return drnic;
    case cpurawsystem:
        return drsys;
    case cpurawidle:
        return dridl;
    case rawinterrupts:
        return cpu->nInterrupts;
    case rawcontext:
        return cpu->nCtxSwitches;
    case cpurawwait:
	return cpu->wait_ticks;
    case cpurawinter:
	return cpu->intrpt_ticks;
    case cpurawsoft:
	return cpu->sirq_ticks;
    case rawiosent:
	return cpu->pageOut*2;
    case rawioreceive:
	return cpu->pageIn*2;
    case rawswapin:
	return cpu->swapIn;
    case rawswapout:
	return cpu->swapOut;
    default:
        return -1;
    }
}

unsigned char  *
var_extensible_vmstat(struct variable *vp,
                      oid * name,
                      size_t * length,
                      int exact,
                      size_t * var_len, WriteMethod ** write_method)
{

    static long     long_ret;
    static char     errmsg[300];

    long_ret = 0;               /* set to 0 as default */

    if (header_generic(vp, name, length, exact, var_len, write_method))
        return (NULL);
    switch (vp->magic) {
    case MIBINDEX:
        long_ret = 1;
        return ((u_char *) (&long_ret));
    case ERRORNAME:            /* dummy name */
        sprintf(errmsg, "systemStats");
        *var_len = strlen(errmsg);
        return ((u_char *) (errmsg));
    case SWAPIN:
        long_ret = vmstat(swapin) & MAX_INT32;
        return ((u_char *) (&long_ret));
    case SWAPOUT:
        long_ret = vmstat(swapout) & MAX_INT32;
        return ((u_char *) (&long_ret));
    case RAWSWAPIN:
        long_ret = vmstat(rawswapin) & MAX_COUNTER;
        return ((u_char *) (&long_ret));
    case RAWSWAPOUT:
        long_ret = vmstat(rawswapout) & MAX_COUNTER;
        return ((u_char *) (&long_ret));
    case IOSENT:
        long_ret = vmstat(iosent) & MAX_INT32;
        return ((u_char *) (&long_ret));
    case IORECEIVE:
        long_ret = vmstat(ioreceive) & MAX_INT32;
        return ((u_char *) (&long_ret));
    case IORAWSENT:
        long_ret = vmstat(rawiosent) & MAX_COUNTER;
        return ((u_char *) (&long_ret));
    case IORAWRECEIVE:
        long_ret = vmstat(rawioreceive) & MAX_COUNTER;
        return ((u_char *) (&long_ret));
    case SYSINTERRUPTS:
        long_ret = vmstat(sysinterrupts) & MAX_INT32;
        return ((u_char *) (&long_ret));
    case SYSCONTEXT:
        long_ret = vmstat(syscontext) & MAX_INT32;
        return ((u_char *) (&long_ret));
    case CPUUSER:
        long_ret = vmstat(cpuuser) & MAX_INT32;
        return ((u_char *) (&long_ret));
    case CPUSYSTEM:
        long_ret = vmstat(cpusystem) & MAX_INT32;
        return ((u_char *) (&long_ret));
    case CPUIDLE:
        long_ret = vmstat(cpuidle) & MAX_INT32;
        return ((u_char *) (&long_ret));
    case CPURAWUSER:
        long_ret = vmstat(cpurawuser) & MAX_COUNTER;
        return ((u_char *) (&long_ret));
    case CPURAWNICE:
        long_ret = vmstat(cpurawnice) & MAX_COUNTER;
        return ((u_char *) (&long_ret));
    case CPURAWSYSTEM:
        long_ret = (vmstat(cpurawsystem)+vmstat(cpurawinter)+vmstat(cpurawsoft)) & MAX_COUNTER;
        return ((u_char *) (&long_ret));
    case CPURAWKERNEL:
        long_ret = vmstat(cpurawsystem) & MAX_COUNTER;
        return ((u_char *) (&long_ret));
    case CPURAWIDLE:
        long_ret = vmstat(cpurawidle) & MAX_COUNTER;
        return ((u_char *) (&long_ret));
    case SYSRAWINTERRUPTS:
	long_ret = vmstat(rawinterrupts) & MAX_COUNTER;
	return (u_char *)&long_ret;
    case SYSRAWCONTEXT:
	long_ret = vmstat(rawcontext) & MAX_COUNTER;
	return (u_char *)&long_ret;
    case CPURAWWAIT:
	if (!has_cpu_26) return NULL;
        long_ret = vmstat(cpurawwait) & MAX_COUNTER;
        return ((u_char *) (&long_ret));
    case CPURAWINTR:
	if (!has_cpu_26) return NULL;
        long_ret = vmstat(cpurawinter) & MAX_COUNTER;
        return ((u_char *) (&long_ret));
    case CPURAWSOFTIRQ:
	if (!has_cpu_26) return NULL;
        long_ret = vmstat(cpurawsoft) & MAX_COUNTER;
        return ((u_char *) (&long_ret));
		
        /*
         * reserved for future use 
         */
        /*
         * case ERRORFLAG:
         * return((u_char *) (&long_ret));
         * case ERRORMSG:
         * return((u_char *) (&long_ret));
         */
    default:
	snmp_log(LOG_ERR, "vmstat.c: don't know how to handle %d request\n",
		vp->magic);
    }
    return NULL;
}
