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

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <signal.h>
#ifdef HAVE_MACHINE_PARAM_H
#include <machine/param.h>
#endif
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_SYS_VMMETER_H
#if !(defined(bsdi2) || defined(netbsd1))
#include <sys/vmmeter.h>
#endif
#endif
#ifdef HAVE_SYS_CONF_H
#include <sys/conf.h>
#endif
#ifdef HAVE_ASM_PAGE_H
#include <asm/page.h>
#endif
#ifdef HAVE_SYS_SWAP_H
#include <sys/swap.h>
#endif
#ifdef HAVE_SYS_FS_H
#include <sys/fs.h>
#else
#ifdef HAVE_UFS_FS_H
#include <ufs/fs.h>
#else
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#if !defined(dragonfly)
#ifdef HAVE_SYS_VNODE_H
#include <sys/vnode.h>
#endif
#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
#ifdef HAVE_UFS_FFS_FS_H
#include <ufs/ffs/fs.h>
#endif
#endif
#endif
#ifdef HAVE_MTAB_H
#include <mtab.h>
#endif
#include <errno.h>
#ifdef HAVE_FSTAB_H
#include <fstab.h>
#endif
#ifdef HAVE_SYS_STATFS_H
#include <sys/statfs.h>
#endif
#ifdef HAVE_SYS_STATVFS_H
#include <sys/statvfs.h>
#endif
#ifdef HAVE_SYS_VFS_H
#include <sys/vfs.h>
#endif
#if (!defined(HAVE_STATVFS)) && defined(HAVE_STATFS)
#ifdef HAVE_SYS_MOUNT_H
#include <sys/mount.h>
#endif
#ifdef HAVE_SYS_SYSCTL_H
#include <sys/sysctl.h>
#endif
#define statvfs statfs
#endif
#ifdef HAVE_VM_VM_H
#include <vm/vm.h>
#endif
#ifdef HAVE_VM_SWAP_PAGER_H
#include <vm/swap_pager.h>
#endif
#ifdef HAVE_SYS_FIXPOINT_H
#include <sys/fixpoint.h>
#endif
#ifdef HAVE_SYS_LOADAVG_H
#include <sys/loadavg.h>
#endif
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#ifdef dynix
#include <sys/mc_vmparam.h>
#endif
#if defined(hpux10) || defined(hpux11)
#include <sys/pstat.h>
#endif
#if defined(aix4) || defined(aix5) || defined(aix6) || defined(aix7)
#ifdef HAVE_SYS_PROTOSW_H
#include <sys/protosw.h>
#endif
#include <libperfstat.h>
#endif
#ifdef HAVE_SYS_SYSGET_H
#include <sys/sysget.h>
#endif

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

#include "struct.h"
#include "loadave.h"
#include "util_funcs/header_simple_table.h"
#include "kernel.h"

static double maxload[3];
static int laConfigSet = 0;

static int
loadave_store_config(int a, int b, void *c, void *d)
{
    char line[SNMP_MAXBUF_SMALL];
    if (laConfigSet > 0) {
        snprintf(line, SNMP_MAXBUF_SMALL, "pload %.02f %.02f %.02f", maxload[0], maxload[1], maxload[2]);
        snmpd_store_config(line);
    }
    return SNMPERR_SUCCESS;
}

void
init_loadave(void)
{

    /*
     * define the structure we're going to ask the agent to register our
     * information at 
     */
    struct variable2 extensible_loadave_variables[] = {
        {MIBINDEX, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_extensible_loadave, 1, {MIBINDEX}},
        {ERRORNAME, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
         var_extensible_loadave, 1, {ERRORNAME}},
        {LOADAVE, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
         var_extensible_loadave, 1, {LOADAVE}},
        {LOADMAXVAL, ASN_OCTET_STR, NETSNMP_OLDAPI_RWRITE,
         var_extensible_loadave, 1, {LOADMAXVAL}},
        {LOADAVEINT, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_extensible_loadave, 1, {LOADAVEINT}},
#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES
        {LOADAVEFLOAT, ASN_OPAQUE_FLOAT, NETSNMP_OLDAPI_RONLY,
         var_extensible_loadave, 1, {LOADAVEFLOAT}},
#endif
        {ERRORFLAG, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_extensible_loadave, 1, {ERRORFLAG}},
        {ERRORMSG, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
         var_extensible_loadave, 1, {ERRORMSG}}
    };

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

    /*
     * register ourselves with the agent to handle our mib tree 
     */
    REGISTER_MIB("ucd-snmp/loadave", extensible_loadave_variables,
                 variable2, loadave_variables_oid);

    laConfigSet = 0;

    snmpd_register_config_handler("load", loadave_parse_config,
                                  loadave_free_config,
                                  "max1 [max5] [max15]");

    snmpd_register_config_handler("pload",
                                  loadave_parse_config, NULL, NULL);


    /*
     * we need to be called back later
     */
    snmp_register_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_STORE_DATA,
                                       loadave_store_config, NULL);

}

void
loadave_parse_config(const char *token, char *cptr)
{
    int             i;

    if (strcmp(token, "pload") == 0) {
        if (laConfigSet < 0) {
            snmp_log(LOG_WARNING,
                     "ignoring attempted override of read-only load\n");
            return;
        } else {
            laConfigSet++;
        }
    } else {
        if (laConfigSet > 0) {
            snmp_log(LOG_WARNING,
                     "ignoring attempted override of read-only load\n");
            /*
             * Fall through and copy in this value.
             */
        }
        laConfigSet = -1;
    }

    for (i = 0; i <= 2; i++) {
        if (cptr != NULL)
            maxload[i] = atof(cptr);
        else
            if (i > 0)
                maxload[i] = maxload[i - 1];
            else
                maxload[i] = NETSNMP_DEFMAXLOADAVE;
        cptr = skip_not_white(cptr);
        cptr = skip_white(cptr);
    }
}

void
loadave_free_config(void)
{
    int             i;

    for (i = 0; i <= 2; i++)
        maxload[i] = NETSNMP_DEFMAXLOADAVE;
}

/*
 * try to get load average
 * Inputs: pointer to array of doubles, number of elements in array
 * Returns: 0=array has values, -1=error occurred.
 */
int
try_getloadavg(double *r_ave, size_t s_ave)
{
#ifndef HAVE_GETLOADAVG
#ifdef HAVE_SYS_FIXPOINT_H
    fix             favenrun[3];
#endif
#if (defined(ultrix) || defined(sun) || defined(__alpha) || defined(dynix))
    int             i;
#if (defined(sun) || defined(__alpha) || defined(dynix))
    long            favenrun[3];
    if (s_ave > 3)              /* bounds check */
        return (-1);
#define FIX_TO_DBL(_IN) (((double) _IN)/((double) FSCALE))
#endif
#endif
#if defined(aix4) || defined(aix5) || defined(aix6) || defined(aix7)
    perfstat_cpu_total_t cs;
#endif
#if defined(hpux10) || defined(hpux11)
    struct pst_dynamic pst_buf;
#endif
#ifdef irix6
    int             i, favenrun[3];
    sgt_cookie_t    cookie;
#endif
#endif	/* !HAVE_GETLOADAVG */

#ifdef HAVE_GETLOADAVG
    if (getloadavg(r_ave, s_ave) == -1)
        return (-1);
#elif defined(linux)
    {
        FILE           *in = fopen("/proc/loadavg", "r");
        if (!in) {
            NETSNMP_LOGONCE((LOG_ERR, "snmpd: cannot open /proc/loadavg\n"));
            return (-1);
        }
        fscanf(in, "%lf %lf %lf", r_ave, (r_ave + 1), (r_ave + 2));
        fclose(in);
    }
#elif (defined(ultrix) || defined(sun) || defined(__alpha) || defined(dynix))
    if (auto_nlist(LOADAVE_SYMBOL, (char *) favenrun, sizeof(favenrun)) ==
        0)
        return (-1);
    for (i = 0; i < s_ave; i++)
        *(r_ave + i) = FIX_TO_DBL(favenrun[i]);
#elif defined(hpux10) || defined(hpux11)
    if (pstat_getdynamic(&pst_buf, sizeof(struct pst_dynamic), 1, 0) < 0)
        return(-1);
    r_ave[0] = pst_buf.psd_avg_1_min;
    r_ave[1] = pst_buf.psd_avg_5_min;
    r_ave[2] = pst_buf.psd_avg_15_min;
#elif defined(aix4) || defined(aix5) || defined(aix6) || defined(aix7)
    if(perfstat_cpu_total((perfstat_id_t *)NULL, &cs, sizeof(perfstat_cpu_total_t), 1) > 0) {
        r_ave[0] = cs.loadavg[0] / 65536.0;
        r_ave[1] = cs.loadavg[1] / 65536.0;
        r_ave[2] = cs.loadavg[2] / 65536.0;
    }
#elif defined(irix6)
    SGT_COOKIE_INIT(&cookie);
    SGT_COOKIE_SET_KSYM(&cookie, "avenrun");
    sysget(SGT_KSYM, (char*)favenrun, sizeof(favenrun), SGT_READ, &cookie);
    for (i = 0; i < s_ave; i++)
      r_ave[i] = favenrun[i] / 1000.0;
    DEBUGMSGTL(("ucd-snmp/loadave", "irix6: %d %d %d\n", favenrun[0], favenrun[1], favenrun[2]));
#elif !defined(cygwin)
#if defined(NETSNMP_CAN_USE_NLIST) && defined(LOADAVE_SYMBOL)
    if (auto_nlist(LOADAVE_SYMBOL, (char *) r_ave, sizeof(double) * s_ave)
        == 0)
#endif
        return (-1);
#endif
    /*
     * XXX
     *   To calculate this, we need to compare
     *   successive values of the kernel array
     *   '_cp_times', and calculate the resulting
     *   percentage changes.
     *     This calculation needs to be performed
     *   regularly - perhaps as a background process.
     *
     *   See the source to 'top' for full details.
     *
     * The linux SNMP HostRes implementation
     *   uses 'avenrun[0]*100' as an approximation.
     *   This is less than accurate, but has the
     *   advantage of being simple to implement!
     *
     * I'm also assuming a single processor
     */
    return 0;
}

static int
write_laConfig(int action,
                          u_char * var_val,
                          u_char var_val_type,
                          size_t var_val_len,
                          u_char * statP, oid * name, size_t name_len)
{
    static double laConfig = 0;

    switch (action) {
    case RESERVE1: /* Check values for acceptability */
        if (var_val_type != ASN_OCTET_STR) {
            DEBUGMSGTL(("ucd-snmp/loadave",
                        "write to laConfig not ASN_OCTET_STR\n"));
            return SNMP_ERR_WRONGTYPE;
        }
        if (var_val_len > 8 || var_val_len <= 0) {
            DEBUGMSGTL(("ucd-snmp/loadave",
                        "write to laConfig: bad length\n"));
            return SNMP_ERR_WRONGLENGTH;
        }

        if (laConfigSet < 0) {
            /*
             * The object is set in a read-only configuration file.
             */
            return SNMP_ERR_NOTWRITABLE;
        }
        break;

    case RESERVE2: /* Allocate memory and similar resources */
        {
            char buf[8];
            int old_errno = errno;
            double val;
            char *endp;

            sprintf(buf, "%.*s", (int) var_val_len, (char *)var_val);
            val = strtod(buf, &endp);

            if (errno == ERANGE || *endp != '\0' || val < 0 || val > 65536.00) {
                errno = old_errno;
                DEBUGMSGTL(("ucd-snmp/loadave",
                            "write to laConfig: invalid value\n"));
                return SNMP_ERR_WRONGVALUE;
            }

            errno = old_errno;

            laConfig = val;
        }
        break;

    case COMMIT:
        {
            int idx = name[name_len - 1] - 1;
            maxload[idx] = laConfig;
            laConfigSet = 1;
        }
    }

    return SNMP_ERR_NOERROR;
}

u_char         *
var_extensible_loadave(struct variable * vp,
                       oid * name,
                       size_t * length,
                       int exact,
                       size_t * var_len, WriteMethod ** write_method)
{

    static long     long_ret;
    static float    float_ret;
    static char     errmsg[300];
    double          avenrun[3];
    if (header_simple_table
        (vp, name, length, exact, var_len, write_method, 3))
        return (NULL);
    switch (vp->magic) {
    case MIBINDEX:
        long_ret = name[*length - 1];
        return ((u_char *) (&long_ret));
    case LOADMAXVAL:
        /* setup write method, but don't return yet */
        *write_method = write_laConfig;
        break;
    case ERRORNAME:
        sprintf(errmsg, "Load-%d", ((name[*length - 1] == 1) ? 1 :
                                    ((name[*length - 1] == 2) ? 5 : 15)));
        *var_len = strlen(errmsg);
        return ((u_char *) (errmsg));
    }
    if (try_getloadavg(&avenrun[0], sizeof(avenrun) / sizeof(avenrun[0]))
        == -1)
        return NULL;
    switch (vp->magic) {
    case LOADAVE:

        sprintf(errmsg, "%.2f", avenrun[name[*length - 1] - 1]);
        *var_len = strlen(errmsg);
        return ((u_char *) (errmsg));
    case LOADMAXVAL:
        sprintf(errmsg, "%.2f", maxload[name[*length - 1] - 1]);
        *var_len = strlen(errmsg);
        return ((u_char *) (errmsg));
    case LOADAVEINT:
        long_ret = (u_long) (avenrun[name[*length - 1] - 1] * 100);
        return ((u_char *) (&long_ret));
#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES
    case LOADAVEFLOAT:
        float_ret = (float) avenrun[name[*length - 1] - 1];
        *var_len = sizeof(float_ret);
        return ((u_char *) (&float_ret));
#endif
    case ERRORFLAG:
        long_ret = (maxload[name[*length - 1] - 1] != 0 &&
                    avenrun[name[*length - 1] - 1] >=
                    maxload[name[*length - 1] - 1]) ? 1 : 0;
        return ((u_char *) (&long_ret));
    case ERRORMSG:
        if (maxload[name[*length - 1] - 1] != 0 &&
            avenrun[name[*length - 1] - 1] >=
            maxload[name[*length - 1] - 1]) {
            snprintf(errmsg, sizeof(errmsg),
                     "%d min Load Average too high (= %.2f)",
                     name[*length - 1] == 1 ? 1 :
                     name[*length - 1] == 2 ? 5 : 15,
                     avenrun[name[*length - 1] - 1]);
        } else {
            errmsg[0] = 0;
        }
        *var_len = strlen(errmsg);
        return ((u_char *) errmsg);
    }
    return NULL;
}
