/* Sysctl.xs -- XS component of BSD-Sysctl
 *
 * Copyright (C) 2006-2014 David Landgren
 */

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

/* define _FreeBSD_version where applicable */
#if __FreeBSD__ >= 2
#include <osreldate.h>
#endif

#include <stdio.h>
#include <sys/types.h>
#include <sys/sysctl.h>

#include <sys/time.h>       /* struct clockinfo */
#include <sys/vmmeter.h>    /* struct vmtotal */
#include <sys/resource.h>   /* struct loadavg */
#if __FreeBSD_version < 1000000
#include <sys/mbuf.h>       /* struct mbstat (opaque mib) */
#endif
#include <sys/timex.h>      /* struct ntptimeval (opaque mib) */
#include <sys/devicestat.h> /* struct devstat (opaque mib) */
#include <sys/mount.h>      /* struct xvfsconf (opaque mib) */

/* prerequisites for TCP/IP-related structs */
#include <arpa/inet.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#if __FreeBSD_version < 500000
#include <netinet/tcp.h>  /* struct tcpstat prerequisite */
#endif

#include <netinet/icmp_var.h> /* struct icmpstat */
#include <netinet/igmp_var.h> /* struct igmpstat */
#include <netinet/tcp_var.h>  /* struct tcpstat */

/* prerequisites for struct udpstat */
#include <netinet/in.h>
#include <netinet/ip_var.h>
#include <netinet/udp.h>
#include <netinet/udp_var.h>

#include <netinet6/raw_ip6.h>
#include "bsd-sysctl.h"

int
_init_iterator(HV *self, int *mib, int *miblenp, int valid) {
    SV **headp;
    int qoid[CTL_MAXNAME];
    u_int qoidlen;
    SV *clen;
    SV **clenp;
    int cmplen;
    int j;

    qoid[0] = 0;
    qoid[1] = 2;
    if (valid) {
        memcpy(qoid+2, mib, (*miblenp) * sizeof(int));
        qoidlen = *miblenp + 2;
        *miblenp = (CTL_MAXNAME+2) * sizeof(int);
        clenp = hv_fetch(self, "_len", 4, 0);
        cmplen = SvIV(*clenp);
    }
    else {
        headp = hv_fetch(self, "head", 4, 0);
        if (!(headp && *headp)) {
            croak( "failed to get some head in _init_iterator()\n" );
        }
        if (SvPOK(*headp)) {
            /* begin where asked */
            qoidlen = sizeof(qoid);
            if (sysctlnametomib( SvPV_nolen(*headp), qoid+2, (size_t*)&qoidlen) == -1) {
                warn( "_init_iterator(%s): sysctlnametomib lookup failed\n",
                    SvPV_nolen(*headp)
                );
                return 0;
            }
            cmplen = qoidlen;
            qoidlen += 2;
        }
        else {
            /* begin at the beginning */
            qoid[2] = 1;
            cmplen  = 0;
            qoidlen = 3;
        }
        clen = newSViv(cmplen);
        SvREFCNT_inc(clen);
        hv_store(self, "_len", 4, clen, 0);
    }

    /*
    printf( "next: " );
    for (j = 0; j < qoidlen; ++j) {
        if (j) printf("."); printf("%d", qoid[j]);
    }
    printf("\n");
    */

    /* load the mib */
    if (sysctl(qoid, qoidlen, mib, (size_t*)miblenp, 0, 0) == -1) {
        return 0;
    }
    *miblenp /= sizeof(int);
    if (*miblenp < cmplen) {
        return 0 ;
    }

    for (j = 0; j < cmplen; ++j) {
        if (mib[j] != qoid[j+2]) {
            return 0;
        }
    }
    return 1;
}

MODULE = BSD::Sysctl   PACKAGE = BSD::Sysctl

PROTOTYPES: ENABLE

SV *
next (SV *refself)
    INIT:
        int mib[CTL_MAXNAME+2];
        size_t miblen;
        int qoid[CTL_MAXNAME+2];
        size_t qoidlen;
        char name[BUFSIZ];
        size_t namelen;
        HV *self;
        SV **ctxp;
        SV *ctx;
        SV *cname;
        int j;
        int *p;

    CODE:
        self = (HV *)SvRV(refself);
        if ((ctxp = hv_fetch(self, "_ctx", 4, 0))) {
            p = (int *)SvPVX(*ctxp);
            miblen = *p++;
            memcpy(mib, p, miblen * sizeof(int));

            if (!_init_iterator(self, mib, (int*)&miblen, 1)) {
                XSRETURN_UNDEF;
            }
        }
        else {
            miblen = sizeof(mib)/sizeof(mib[0]);
            if (!_init_iterator(self, mib, (int*)&miblen, 0)) {
                XSRETURN_UNDEF;
            }
        }

        qoid[0] = 0;
        qoid[1] = 1;
        memcpy(qoid+2, mib, miblen * sizeof(int));
        qoidlen = miblen + 2;

        bzero(name, BUFSIZ);
        namelen = sizeof(name);
        j = sysctl(qoid, qoidlen, name, &namelen, 0, 0);
        if (j || !namelen) {
            warn("next(): sysctl name failure %d %zu %d", j, namelen, errno);
            XSRETURN_UNDEF;
        }
        cname = newSVpvn(name, namelen-1);
        SvREFCNT_inc(cname);
        hv_store(self, "_name", 5, cname, 0);
        RETVAL = cname;

        /* reuse qoid to build context store
         *  - the length of the mib
         *  - followed by the mib values
         * and copy to an SV to save in the self hash
         */
        p = qoid;
        memcpy(p++, (const void *)&miblen, sizeof(int));
        memcpy(p, (const void *)mib, miblen * sizeof(int));

        ctx = newSVpvn((const char *)qoid, (miblen+1) * sizeof(int));
        SvREFCNT_inc(ctx);
        hv_store(self, "_ctx", 4, ctx, 0);

    OUTPUT:
        RETVAL

int
_mib_exists(const char *arg)
    CODE:
        int mib[CTL_MAXNAME];
        size_t miblen = (sizeof(mib)/sizeof(mib[0]));
        RETVAL = (sysctlnametomib(arg, mib, &miblen) != -1);
    OUTPUT:
        RETVAL

SV *
_mib_info(const char *arg)
    INIT:
        int mib[CTL_MAXNAME+2];
        size_t miblen;
        int nr_octets;
        int size;
        char fmt[BUFSIZ];
        size_t len = sizeof(fmt);
        int fmt_type;
        char *f = fmt + sizeof(int);
        char res[BUFSIZ];
        char *resp = res;
        SV *cache;
        SV **store;

    CODE:
        /* see if the mib exists */
        miblen = (sizeof(mib) / sizeof(mib[0])) - 2;
        if (sysctlnametomib(arg, mib+2, &miblen) == -1) {
            XSRETURN_UNDEF;
        }
        nr_octets = miblen;

        /* determine how to format the results */
        mib[0] = 0;
        mib[1] = 4;
        if (sysctl(mib, nr_octets+2, fmt, &len, NULL, 0) == -1) {
            XSRETURN_UNDEF;
        }

        switch (*f) {
        case 'A':
            fmt_type = FMT_A;
            break;
        case 'I':
            ++f;
            fmt_type = *f == 'U' ? FMT_UINT : FMT_INT;
            break;
        case 'L':
            ++f;
            fmt_type = *f == 'U' ? FMT_ULONG : FMT_LONG;
            break;
        case 'Q':
            ++f;
            fmt_type = *f == 'U' ? FMT_U64 : FMT_64;
            break;
        case 'S': {
            if (strcmp(f,"S,clockinfo") == 0)    { fmt_type = FMT_CLOCKINFO; }
            else if (strcmp(f,"S,loadavg") == 0) { fmt_type = FMT_LOADAVG; }
            else if (strcmp(f,"S,timeval") == 0) { fmt_type = FMT_TIMEVAL; }
            else if (strcmp(f,"S,vmtotal") == 0) { fmt_type = FMT_VMTOTAL; }
            /* now the opaque OIDs */
            else if (strcmp(f,"S,bootinfo") == 0)   { fmt_type = FMT_BOOTINFO; }
            else if (strcmp(f,"S,devstat") == 0)    { fmt_type = FMT_DEVSTAT; }
            else if (strcmp(f,"S,icmpstat") == 0)   { fmt_type = FMT_ICMPSTAT; }
            else if (strcmp(f,"S,igmpstat") == 0)   { fmt_type = FMT_IGMPSTAT; }
            else if (strcmp(f,"S,ipstat") == 0)     { fmt_type = FMT_IPSTAT; }
            else if (strcmp(f,"S,mbstat") == 0)     { fmt_type = FMT_MBSTAT; } /* removed in FreeBSD 10 */
            else if (strcmp(f,"S,nfsrvstats") == 0) { fmt_type = FMT_NFSRVSTATS; }
            else if (strcmp(f,"S,nfsstats") == 0)   { fmt_type = FMT_NFSSTATS; }
            else if (strcmp(f,"S,ntptimeval") == 0) { fmt_type = FMT_NTPTIMEVAL; }
            else if (strcmp(f,"S,rip6stat") == 0)   { fmt_type = FMT_RIP6STAT; }
            else if (strcmp(f,"S,tcpstat") == 0)    { fmt_type = FMT_TCPSTAT; }
            else if (strcmp(f,"S,udpstat") == 0)    { fmt_type = FMT_UDPSTAT; }
            else if (strcmp(f,"S,xinpcb") == 0)     { fmt_type = FMT_XINPCB; }
            else if (strcmp(f,"S,xvfsconf") == 0)   { fmt_type = FMT_XVFSCONF; }
            else {
                /* bleah */
            }
            break;
        }
        case 'T': {
            if (strcmp(f,"T,struct cdev *") == 0) {
                fmt_type = FMT_STRUCT_CDEV;
            }
            else {
                /* bleah */
            }
            break;
        }
        case 'N':
            fmt_type = FMT_N;
            break;
        default:
            fmt_type = FMT_A;
            break;
        }

        /* first two bytes indicate format type */
        memcpy(resp, (void *)&fmt_type, sizeof(int));
        resp += sizeof(int);
        len = sizeof(int);

        /* reuse len to measure cached info */
        /* next two bytes indicate the length of the oid */
        memcpy(resp, (void *)&nr_octets, sizeof(int));
        resp += sizeof(int);
        len += sizeof(int);

        /* following bytes are the numeric oid (step past 0, 4) */
        size = (nr_octets) * sizeof(int);
        memcpy(resp, (void *)(mib+2), size);
        len += size;

        cache = newSVpvn(res, len);
        store = hv_store(
            get_hv("BSD::Sysctl::MIB_CACHE", 0),
            arg, strlen(arg), cache, 0
        );
        SvREFCNT_inc(cache);
        RETVAL = cache;
    OUTPUT:
        RETVAL

SV *
_mib_description(const char *arg)
    INIT:
        int mib[CTL_MAXNAME];
        size_t miblen = (sizeof(mib)/sizeof(mib[0]));
        int qmib[CTL_MAXNAME+2];
        char desc[BUFSIZ];
        size_t len = sizeof(desc);
    CODE:
        /* see if the mib exists */
        if (sysctlnametomib(arg, mib, &miblen) == -1) {
            XSRETURN_UNDEF;
        }
        /* fetch the description */
        qmib[0] = 0;
        qmib[1] = 5;
        memcpy(qmib+2, mib, miblen * sizeof(int));
        if (sysctl(qmib, miblen+2, desc, &len, NULL, 0) == -1) {
            XSRETURN_UNDEF;
        }
        RETVAL = newSVpvn(desc, len-1);
    OUTPUT:
        RETVAL

SV *
_mib_lookup(const char *arg)
    INIT:
        HV *cache;
        SV **oidp;
        SV *oid;
        int mib[CTL_MAXNAME];
        size_t miblen = (sizeof(mib)/sizeof(mib[0]));
        char *oid_data;
        int oid_fmt;
        int oid_len;
        SV *sv_buf;
        char *buf;
        size_t buflen = sizeof(buf);

    CODE:
        /* see if the mib exists */
        cache = get_hv("BSD::Sysctl::MIB_CACHE", 0);

        if((oidp = hv_fetch(cache, arg, strlen(arg), 0))) {
            oid = *oidp;
        }
        else {
            /* else use the cache
            * How do you call an XS sub from C?
            */
            warn("uncached mib: %s\n", arg);
            XSRETURN_UNDEF;
        }

        oid_data = SvPVX(oid);
        oid_fmt  = (int)(*oid_data);
        oid_data += sizeof(int);

        oid_len  = (int)(*oid_data);
        oid_data += sizeof(int);
        
        memcpy(mib, oid_data, oid_len * sizeof(int));

        /* determine buffer size */
        if (sysctl(mib, oid_len, NULL, &buflen, NULL, 0) == -1) {
            XSRETURN_UNDEF;
        }
        if (0 == buflen) {
            XSRETURN_UNDEF;
        }

        sv_buf = newSV(buflen);
        buf    = SvPVX(sv_buf);
        if (sysctl(mib, oid_len, buf, &buflen, NULL, 0) == -1) {
            XSRETURN_UNDEF;
        }

        switch(oid_fmt) {
        case FMT_A:
            SvPOK_on(sv_buf);
            SvCUR_set(sv_buf, buflen);
            RETVAL = sv_buf;
            break;
        case FMT_INT:
            if (buflen == sizeof(int)) {
                RETVAL = newSViv(*(int *)buf);
            }
            else {
                AV *c = (AV *)sv_2mortal((SV *)newAV());
                char *bptr = buf;
                while (buflen >= sizeof(int)) {
                    av_push(c, newSViv(*(int *)bptr));
                    buflen -= sizeof(int);
                    bptr   += sizeof(int);
                }
                RETVAL = newRV((SV *)c);
            }
            break;
        case FMT_UINT:
            if (buflen == sizeof(unsigned int)) {
                RETVAL = newSViv(*(unsigned int *)buf);
            }
            else {
                AV *c = (AV *)sv_2mortal((SV *)newAV());
                char *bptr = buf;
                while (buflen >= sizeof(unsigned int)) {
                    av_push(c, newSViv(*(unsigned int *)bptr));
                    buflen -= sizeof(unsigned int);
                    bptr   += sizeof(unsigned int);
                }
                RETVAL = newRV((SV *)c);
            }
            break;
        case FMT_LONG:
            if (buflen == sizeof(long)) {
                RETVAL = newSVuv(*(long *)buf);
            }
            else {
                AV *c = (AV *)sv_2mortal((SV *)newAV());
                char *bptr = buf;
                while (buflen >= sizeof(long)) {
                    av_push(c, newSVuv(*(long *)bptr));
                    buflen -= sizeof(long);
                    bptr   += sizeof(long);
                }
                RETVAL = newRV((SV *)c);
            }
            break;
        case FMT_ULONG:
            if (buflen == sizeof(unsigned long)) {
                RETVAL = newSVuv(*(unsigned long *)buf);
            }
            else {
                AV *c = (AV *)sv_2mortal((SV *)newAV());
                char *bptr = buf;
                while (buflen >= sizeof(unsigned long)) {
                    av_push(c, newSVuv(*(unsigned long *)bptr));
                    buflen -= sizeof(unsigned long);
                    bptr   += sizeof(unsigned long);
                }
                RETVAL = newRV((SV *)c);
            }
            break;
        case FMT_64:
            if (buflen == sizeof(int64_t)) {
                RETVAL = newSVuv(*(int64_t *)buf);
            }
            else {
                AV *c = (AV *)sv_2mortal((SV *)newAV());
                char *bptr = buf;
                while (buflen >= sizeof(int64_t)) {
                    av_push(c, newSVuv(*(int64_t *)bptr));
                    buflen -= sizeof(int64_t);
                    bptr   += sizeof(int64_t);
                }
                RETVAL = newRV((SV *)c);
            }
            break;
        case FMT_U64:
            if (buflen == sizeof(uint64_t)) {
                RETVAL = newSVuv(*(uint64_t *)buf);
            }
            else {
                AV *c = (AV *)sv_2mortal((SV *)newAV());
                char *bptr = buf;
                while (buflen >= sizeof(uint64_t)) {
                    av_push(c, newSVuv(*(uint64_t *)bptr));
                    buflen -= sizeof(uint64_t);
                    bptr   += sizeof(uint64_t);
                }
                RETVAL = newRV((SV *)c);
            }
            break;
        case FMT_CLOCKINFO: {
            HV *c = (HV *)sv_2mortal((SV *)newHV());
            struct clockinfo *inf = (struct clockinfo *)buf;
            RETVAL = newRV((SV *)c);
            hv_store(c, "hz",     2, newSViv(inf->hz), 0);
            hv_store(c, "tick",   4, newSViv(inf->tick), 0);
            hv_store(c, "profhz", 6, newSViv(inf->profhz), 0);
            hv_store(c, "stathz", 6, newSViv(inf->stathz), 0);
            break;
        }
        case FMT_VMTOTAL: {
            HV *c = (HV *)sv_2mortal((SV *)newHV());
            struct vmtotal *inf = (struct vmtotal *)buf;
            RETVAL = newRV((SV *)c);
            hv_store(c, "runqueue",          8, newSViv(inf->t_rq), 0);
            hv_store(c, "diskwait",          8, newSViv(inf->t_dw), 0);
            hv_store(c, "pagewait",          8, newSViv(inf->t_pw), 0);
            hv_store(c, "sleeping",          8, newSViv(inf->t_sl), 0);
            hv_store(c, "pagesize",          8, newSViv(getpagesize()), 0);
            hv_store(c, "vmtotal",           7, newSVuv(inf->t_vm), 0);
            hv_store(c, "vmactive",          8, newSVuv(inf->t_avm), 0);
            hv_store(c, "realtotal",         9, newSVuv(inf->t_rm), 0);
            hv_store(c, "realactive",       10, newSVuv(inf->t_arm), 0);
            hv_store(c, "vmshared",          8, newSVuv(inf->t_vmshr), 0);
            hv_store(c, "vmsharedactive",   14, newSVuv(inf->t_avmshr), 0);
            hv_store(c, "realshared",       10, newSVuv(inf->t_rmshr), 0);
            hv_store(c, "realsharedactive", 16, newSVuv(inf->t_armshr), 0);
            hv_store(c, "pagefree",          8, newSViv(inf->t_free), 0);
            break;
        }
        case FMT_LOADAVG: {
            AV *c = (AV *)sv_2mortal((SV *)newAV());
            struct loadavg *inf = (struct loadavg *)buf;
            double scale = inf->fscale;
            RETVAL = newRV((SV *)c);
            av_extend(c, 3);
            av_store(c, 0, newSVnv((double)inf->ldavg[0]/scale));
            av_store(c, 1, newSVnv((double)inf->ldavg[1]/scale));
            av_store(c, 2, newSVnv((double)inf->ldavg[2]/scale));
            break;
        }
        case FMT_TIMEVAL: {
            struct timeval *inf = (struct timeval *)buf;
            RETVAL = newSVnv(
                (double)inf->tv_sec + ((double)inf->tv_usec/1000000)
            );
            break;
        }
        /* the remaining custom formats are for opaque mibs */
#if __FreeBSD_version < 1000000
        case FMT_MBSTAT: {
            HV *c = (HV *)sv_2mortal((SV *)newHV());
            struct mbstat *inf = (struct mbstat *)buf;
            RETVAL = newRV((SV *)c);
            hv_store(c, "copymfail",      9, newSVuv(inf->m_mcfail), 0);
            hv_store(c, "pullupfail",    10, newSVuv(inf->m_mpfail), 0);
            hv_store(c, "mbufsize",       8, newSVuv(inf->m_msize), 0);
            hv_store(c, "mclustsize",    10, newSVuv(inf->m_mclbytes), 0);
            hv_store(c, "minclsize",      9, newSVuv(inf->m_minclsize), 0);
            hv_store(c, "mbuflen",        7, newSVuv(inf->m_mlen), 0);
            hv_store(c, "mbufhead",       8, newSVuv(inf->m_mhlen), 0);
            hv_store(c, "drain",          5, newSVuv(inf->m_drain), 0);
#if __FreeBSD_version < 500000
            hv_store(c, "numtypes",       8, newSVpvn("", 0), 0);
#else
            hv_store(c, "numtypes",       8, newSViv(inf->m_numtypes), 0);
#endif
#if __FreeBSD_version < 600000
            hv_store(c, "mbufs",          5, newSVpvn("", 0), 0);
            hv_store(c, "mclusts",        7, newSVpvn("", 0), 0);
            hv_store(c, "sfallocwait",   11, newSVpvn("", 0), 0);
            hv_store(c, "sfiocnt",        7, newSVpvn("", 0), 0);
#else
            hv_store(c, "mbufs",          5, newSVuv(inf->m_mbufs), 0);
            hv_store(c, "mclusts",        7, newSVuv(inf->m_mclusts), 0);
            hv_store(c, "sfallocwait",   11, newSVuv(inf->sf_allocwait), 0);
            hv_store(c, "sfiocnt",        7, newSVuv(inf->sf_iocnt), 0);
#endif
            break;
        }
#endif
        case FMT_NTPTIMEVAL: {
            HV *c = (HV *)sv_2mortal((SV *)newHV());
            struct ntptimeval *inf = (struct ntptimeval *)buf;
            RETVAL = newRV((SV *)c);
            hv_store(c, "sec",        3, newSVuv(inf->time.tv_sec), 0);
            hv_store(c, "nanosec",    7, newSViv(inf->time.tv_nsec), 0);
            hv_store(c, "maxerror",   8, newSViv(inf->maxerror), 0);
            hv_store(c, "esterror",   8, newSViv(inf->esterror), 0);
            hv_store(c, "taioffset",  9, newSViv(inf->tai), 0);
            hv_store(c, "timestate",  9, newSViv(inf->time_state), 0);
            break;
        }
        case FMT_DEVSTAT: {
            HV *c = (HV *)sv_2mortal((SV *)newHV());
            struct devstat *inf = (struct devstat *)buf;
            RETVAL = newRV((SV *)c);
            hv_store(c, "devno",           5, newSViv(inf->device_number), 0);
            hv_store(c, "unitno",          6, newSViv(inf->unit_number), 0);
#if __FreeBSD_version < 500000
            hv_store(c, "sequence",        8, newSVpvn("", 0), 0);
            hv_store(c, "allocated",       9, newSVpvn("", 0), 0);
            hv_store(c, "startcount",     10, newSVpvn("", 0), 0);
            hv_store(c, "endcount",        8, newSVpvn("", 0), 0);
            hv_store(c, "busyfromsec",    11, newSVpvn("", 0), 0);
            hv_store(c, "busyfromfrac",   12, newSVpvn("", 0), 0);
#else
            hv_store(c, "sequence",        8, newSVuv(inf->sequence0), 0);
            hv_store(c, "allocated",       9, newSViv(inf->allocated), 0);
            hv_store(c, "startcount",     10, newSViv(inf->start_count), 0);
            hv_store(c, "endcount",        8, newSViv(inf->end_count), 0);
            hv_store(c, "busyfromsec",    11, newSViv(inf->busy_from.sec), 0);
            hv_store(c, "busyfromfrac",   12, newSVuv(inf->busy_from.frac), 0);
#endif
            break;
        }
#if __FreeBSD_version >= 500000
        case FMT_XVFSCONF: {
            HV *c = (HV *)sv_2mortal((SV *)newHV());
            struct xvfsconf *inf = (struct xvfsconf *)buf;
            RETVAL = newRV((SV *)c);
            hv_store(c, "name",         4, newSVpv(inf->vfc_name, 0), 0);
            hv_store(c, "typenum",      7, newSViv(inf->vfc_typenum), 0);
            hv_store(c, "refcount",     8, newSViv(inf->vfc_refcount), 0);
            hv_store(c, "flags",        5, newSViv(inf->vfc_flags), 0);
            break;
        }
#endif
        case FMT_ICMPSTAT: {
            HV *c = (HV *)sv_2mortal((SV *)newHV());
            struct icmpstat *inf = (struct icmpstat *)buf;
            RETVAL = newRV((SV *)c);
            hv_store(c, "error",         5, newSViv(inf->icps_error), 0);
            hv_store(c, "badcode",       7, newSViv(inf->icps_badcode), 0);
            hv_store(c, "tooshort",      8, newSViv(inf->icps_tooshort), 0);
            hv_store(c, "checksum",      8, newSViv(inf->icps_checksum), 0);
            hv_store(c, "badlen",        6, newSViv(inf->icps_badlen), 0);
            hv_store(c, "reflect",       7, newSViv(inf->icps_reflect), 0);
            hv_store(c, "bmcastecho",   10, newSViv(inf->icps_bmcastecho), 0);
            hv_store(c, "bmcasttstamp", 12, newSViv(inf->icps_bmcasttstamp), 0);
            hv_store(c, "badaddr",       7, newSViv(inf->icps_badaddr), 0);
            hv_store(c, "noroute",       7, newSViv(inf->icps_noroute), 0);
            break;
        }
        case FMT_IGMPSTAT: {
            HV *c = (HV *)sv_2mortal((SV *)newHV());
            struct igmpstat *inf = (struct igmpstat *)buf;
            RETVAL = newRV((SV *)c);
#if __FreeBSD_version < 800070
            hv_store(c, "total",       5, newSVuv(inf->igps_rcv_total), 0);
            hv_store(c, "tooshort",    8, newSVuv(inf->igps_rcv_tooshort), 0);
            hv_store(c, "badsum",      6, newSVuv(inf->igps_rcv_badsum), 0);
            hv_store(c, "queries",     7, newSVuv(inf->igps_rcv_queries), 0);
            hv_store(c, "badqueries", 10, newSVuv(inf->igps_rcv_badqueries), 0);
            hv_store(c, "reports",     7, newSVuv(inf->igps_rcv_reports), 0);
            hv_store(c, "badreports", 10, newSVuv(inf->igps_rcv_badreports), 0);
            hv_store(c, "ourreports", 10, newSVuv(inf->igps_rcv_ourreports), 0);
            hv_store(c, "sent",        4, newSVuv(inf->igps_snd_reports), 0);
#else
            /* Message statistics */
            hv_store(c, "total",             5, newSVuv(inf->igps_rcv_total), 0);
            hv_store(c, "tooshort",          8, newSVuv(inf->igps_rcv_tooshort), 0);
            hv_store(c, "badttl",            6, newSVuv(inf->igps_rcv_badttl), 0);
            hv_store(c, "badsum",            6, newSVuv(inf->igps_rcv_badsum), 0);
            /* Query statistics */
            hv_store(c, "queries",           7, newSVuv(inf->igps_rcv_v1v2_queries + inf->igps_rcv_v3_queries), 0);
            hv_store(c, "v1v2_queries",     12, newSVuv(inf->igps_rcv_v1v2_queries), 0);
            hv_store(c, "v3_queries",       10, newSVuv(inf->igps_rcv_v3_queries), 0);
            hv_store(c, "badqueries",       10, newSVuv(inf->igps_rcv_badqueries), 0);
            hv_store(c, "gen_queries",      11, newSVuv(inf->igps_rcv_gen_queries), 0);
            hv_store(c, "group_queries",    13, newSVuv(inf->igps_rcv_group_queries), 0);
            hv_store(c, "gsr_queries",      11, newSVuv(inf->igps_rcv_gsr_queries), 0);
            hv_store(c, "drop_gsr_queries", 16, newSVuv(inf->igps_drop_gsr_queries), 0);
            /* Report statistics */
            hv_store(c, "reports",           7, newSVuv(inf->igps_rcv_reports), 0);
            hv_store(c, "badreports",       10, newSVuv(inf->igps_rcv_badreports), 0);
            hv_store(c, "ourreports",       10, newSVuv(inf->igps_rcv_ourreports), 0);
            hv_store(c, "nore",              4, newSVuv(inf->igps_rcv_nora), 0);
            hv_store(c, "sent",              4, newSVuv(inf->igps_snd_reports), 0);
#endif
            break;
        }
        case FMT_TCPSTAT: {
            HV *c = (HV *)sv_2mortal((SV *)newHV());
            struct tcpstat *inf = (struct tcpstat *)buf;
            RETVAL = newRV((SV *)c);
            hv_store(c, "connattempt",      11, newSVuv(inf->tcps_connattempt), 0);
            hv_store(c, "accepts",           7, newSVuv(inf->tcps_accepts), 0);
            hv_store(c, "connects",          8, newSVuv(inf->tcps_connects), 0);
            hv_store(c, "drops",             5, newSVuv(inf->tcps_drops), 0);
            hv_store(c, "conndrops",         9, newSVuv(inf->tcps_conndrops), 0);
            hv_store(c, "closed",            6, newSVuv(inf->tcps_closed), 0);
            hv_store(c, "segstimed",         9, newSVuv(inf->tcps_segstimed), 0);
            hv_store(c, "rttupdated",       10, newSVuv(inf->tcps_rttupdated), 0);
            hv_store(c, "delack",            6, newSVuv(inf->tcps_delack), 0);
            hv_store(c, "timeoutdrop",      11, newSVuv(inf->tcps_timeoutdrop), 0);
            hv_store(c, "rexmttimeo",       10, newSVuv(inf->tcps_rexmttimeo), 0);
            hv_store(c, "persisttimeo",     12, newSVuv(inf->tcps_persisttimeo), 0);
            hv_store(c, "keeptimeo",         9, newSVuv(inf->tcps_keeptimeo), 0);
            hv_store(c, "keepprobe",         9, newSVuv(inf->tcps_keepprobe), 0);
            hv_store(c, "keepdrops",         9, newSVuv(inf->tcps_keepdrops), 0);
            hv_store(c, "sendtotal",         9, newSVuv(inf->tcps_sndtotal), 0);
            hv_store(c, "sendpack",          8, newSVuv(inf->tcps_sndpack), 0);
            hv_store(c, "sendbyte",          8, newSVuv(inf->tcps_sndbyte), 0);
            hv_store(c, "sendrexmitpack",   14, newSVuv(inf->tcps_sndrexmitpack), 0);
            hv_store(c, "sendrexmitbyte",   14, newSVuv(inf->tcps_sndrexmitbyte), 0);
            hv_store(c, "sendacks",          8, newSVuv(inf->tcps_sndacks), 0);
            hv_store(c, "sendprobe",         9, newSVuv(inf->tcps_sndprobe), 0);
            hv_store(c, "sendurgent",       10, newSVuv(inf->tcps_sndurg), 0);
            hv_store(c, "sendwinup",         9, newSVuv(inf->tcps_sndwinup), 0);
            hv_store(c, "sendctrl",          8, newSVuv(inf->tcps_sndctrl), 0);
            hv_store(c, "recvtotal",         9, newSVuv(inf->tcps_rcvtotal), 0);
            hv_store(c, "recvpack",          8, newSVuv(inf->tcps_rcvpack), 0);
            hv_store(c, "recvbyte",          8, newSVuv(inf->tcps_rcvbyte), 0);
            hv_store(c, "recvbadsum",       10, newSVuv(inf->tcps_rcvbadsum), 0);
            hv_store(c, "recvbadoff",       10, newSVuv(inf->tcps_rcvbadoff), 0);
            hv_store(c, "recvmemdrop",      11, newSVuv(inf->tcps_rcvmemdrop), 0);
            hv_store(c, "recvshort",         9, newSVuv(inf->tcps_rcvshort), 0);
            hv_store(c, "recvduppack",      11, newSVuv(inf->tcps_rcvduppack), 0);
            hv_store(c, "recvdupbyte",      11, newSVuv(inf->tcps_rcvdupbyte), 0);
            hv_store(c, "recvpartduppack",  15, newSVuv(inf->tcps_rcvduppack), 0);
            hv_store(c, "recvpartdupbyte",  15, newSVuv(inf->tcps_rcvdupbyte), 0);
            hv_store(c, "recvoopack",       10, newSVuv(inf->tcps_rcvoopack), 0);
            hv_store(c, "recvoobyte",       10, newSVuv(inf->tcps_rcvoobyte), 0);
            hv_store(c, "recvpackafterwin", 16, newSVuv(inf->tcps_rcvpackafterwin), 0);
            hv_store(c, "recvbyteafterwin", 16, newSVuv(inf->tcps_rcvbyteafterwin), 0);
            hv_store(c, "recvafterclose",   14, newSVuv(inf->tcps_rcvafterclose), 0);
            hv_store(c, "recvwinprobe",     12, newSVuv(inf->tcps_rcvwinprobe), 0);
            hv_store(c, "recvdupack",       10, newSVuv(inf->tcps_rcvdupack), 0);
            hv_store(c, "recvacktoomuch",   14, newSVuv(inf->tcps_rcvacktoomuch), 0);
            hv_store(c, "recvackpack",      11, newSVuv(inf->tcps_rcvackpack), 0);
            hv_store(c, "recvackbyte",      11, newSVuv(inf->tcps_rcvackbyte), 0);
            hv_store(c, "recvwinupd",       10, newSVuv(inf->tcps_rcvwinupd), 0);
            hv_store(c, "pawsdrop",          8, newSVuv(inf->tcps_pawsdrop), 0);
            hv_store(c, "predack",           7, newSVuv(inf->tcps_predack), 0);
            hv_store(c, "preddat",           7, newSVuv(inf->tcps_preddat), 0);
            hv_store(c, "pcbcachemiss",     12, newSVuv(inf->tcps_pcbcachemiss), 0);
            hv_store(c, "cachedrtt",         9, newSVuv(inf->tcps_cachedrtt), 0);
            hv_store(c, "cachedrttvar",     12, newSVuv(inf->tcps_cachedrttvar), 0);
            hv_store(c, "cachedssthresh",   14, newSVuv(inf->tcps_cachedssthresh), 0);
            hv_store(c, "usedrtt",           7, newSVuv(inf->tcps_usedrtt), 0);
            hv_store(c, "usedrttvar",       10, newSVuv(inf->tcps_usedrttvar), 0);
            hv_store(c, "usedssthresh",     12, newSVuv(inf->tcps_usedssthresh), 0);
            hv_store(c, "persistdrop",      11, newSVuv(inf->tcps_persistdrop), 0);
            hv_store(c, "badsyn",            6, newSVuv(inf->tcps_badsyn), 0);
            hv_store(c, "mturesent",         9, newSVuv(inf->tcps_mturesent), 0);
            hv_store(c, "listendrop",       10, newSVuv(inf->tcps_listendrop), 0);
            hv_store(c, "listendrop",       10, newSVuv(inf->tcps_listendrop), 0);
            hv_store(c, "added",             5, newSVuv(inf->tcps_sc_added), 0);
            hv_store(c, "rexmit",            6, newSVuv(inf->tcps_sc_retransmitted), 0);
            hv_store(c, "dupsyn",            6, newSVuv(inf->tcps_sc_dupsyn), 0);
            hv_store(c, "dropped",           7, newSVuv(inf->tcps_sc_dropped), 0);
            hv_store(c, "completed",         9, newSVuv(inf->tcps_sc_completed), 0);
            hv_store(c, "bucketoverflow",   14, newSVuv(inf->tcps_sc_bucketoverflow), 0);
            hv_store(c, "cacheoverflow",    13, newSVuv(inf->tcps_sc_cacheoverflow), 0);
            hv_store(c, "reset",             5, newSVuv(inf->tcps_sc_reset), 0);
            hv_store(c, "stale",             5, newSVuv(inf->tcps_sc_stale), 0);
            hv_store(c, "aborted",           7, newSVuv(inf->tcps_sc_aborted), 0);
            hv_store(c, "badack",            6, newSVuv(inf->tcps_sc_badack), 0);
            hv_store(c, "unreach",           7, newSVuv(inf->tcps_sc_unreach), 0);
            hv_store(c, "zonefail",          8, newSVuv(inf->tcps_sc_zonefail), 0);
            hv_store(c, "sendcookie",       10, newSVuv(inf->tcps_sc_sendcookie), 0);
            hv_store(c, "recvcookie",       10, newSVuv(inf->tcps_sc_recvcookie), 0);
#if __FreeBSD_version < 500000
            hv_store(c, "minmssdrops",      11, newSVpvn("", 0), 0);
            hv_store(c, "sendrexmitbad",    13, newSVpvn("", 0), 0);
            hv_store(c, "hostcacheadd",     12, newSVpvn("", 0), 0);
            hv_store(c, "hostcacheover",    13, newSVpvn("", 0), 0);
#else
            hv_store(c, "minmssdrops",      11, newSVuv(inf->tcps_minmssdrops), 0);
            hv_store(c, "sendrexmitbad",    13, newSVuv(inf->tcps_sndrexmitbad), 0);
            hv_store(c, "hostcacheadd",     12, newSVuv(inf->tcps_hc_added), 0);
            hv_store(c, "hostcacheover",    13, newSVuv(inf->tcps_hc_bucketoverflow), 0);
#endif
#if __FreeBSD_version < 600000
            hv_store(c, "badrst",            6, newSVpvn("", 0), 0);
            hv_store(c, "sackrecover",      11, newSVpvn("", 0), 0);
            hv_store(c, "sackrexmitsegs",   14, newSVpvn("", 0), 0);
            hv_store(c, "sackrexmitbytes",  15, newSVpvn("", 0), 0);
            hv_store(c, "sackrecv",          8, newSVpvn("", 0), 0);
            hv_store(c, "sacksend",          8, newSVpvn("", 0), 0);
            hv_store(c, "sackscorebover",   14, newSVpvn("", 0), 0);
#else
            hv_store(c, "badrst",            6, newSVuv(inf->tcps_badrst), 0);
            hv_store(c, "sackrecover",      11, newSVuv(inf->tcps_sack_recovery_episode), 0);
            hv_store(c, "sackrexmitsegs",   14, newSVuv(inf->tcps_sack_rexmits), 0);
            hv_store(c, "sackrexmitbytes",  15, newSVuv(inf->tcps_sack_rexmit_bytes), 0);
            hv_store(c, "sackrecv",          8, newSVuv(inf->tcps_sack_rcv_blocks), 0);
            hv_store(c, "sacksend",          8, newSVuv(inf->tcps_sack_send_blocks), 0);
            hv_store(c, "sackscorebover",   14, newSVuv(inf->tcps_sack_sboverflow), 0);
#endif
            break;
        }
        case FMT_UDPSTAT: {
            HV *c = (HV *)sv_2mortal((SV *)newHV());
            struct udpstat *inf = (struct udpstat *)buf;
            RETVAL = newRV((SV *)c);
            hv_store(c, "inpackets",       9, newSVuv(inf->udps_ipackets), 0);
            hv_store(c, "headdrops",       9, newSVuv(inf->udps_hdrops), 0);
            hv_store(c, "badsum",          6, newSVuv(inf->udps_badsum), 0);
            hv_store(c, "nosum",           5, newSVuv(inf->udps_nosum), 0);
            hv_store(c, "badlen",          6, newSVuv(inf->udps_badlen), 0);
            hv_store(c, "noport",          6, newSVuv(inf->udps_noport), 0);
            hv_store(c, "noportbcast",    11, newSVuv(inf->udps_noportbcast), 0);
            hv_store(c, "pcbcachemiss",   12, newSVuv(inf->udpps_pcbcachemiss), 0);
            hv_store(c, "pcbhashmiss",    11, newSVuv(inf->udpps_pcbhashmiss), 0);
            hv_store(c, "outpackets",     10, newSVuv(inf->udps_opackets), 0);
            hv_store(c, "fastout",         7, newSVuv(inf->udps_fastout), 0);
            hv_store(c, "noportmcast",    11, newSVuv(inf->udps_noportmcast), 0);
            break;
        }
        case FMT_RIP6STAT: {
            HV *c = (HV *)sv_2mortal((SV *)newHV());
            struct rip6stat *inf = (struct rip6stat *)buf;
            RETVAL = newRV((SV *)c);
            /* these values are of type u_quad_t */
            hv_store(c, "inpackets",       9, newSVnv(inf->rip6s_ipackets), 0);
            hv_store(c, "insum",           5, newSVnv(inf->rip6s_isum), 0);
            hv_store(c, "badsum",          6, newSVnv(inf->rip6s_badsum), 0);
            hv_store(c, "nosock",          6, newSVnv(inf->rip6s_nosock), 0);
            hv_store(c, "nosockmcast",    11, newSVnv(inf->rip6s_nosockmcast), 0);
            hv_store(c, "sockfull",        8, newSVnv(inf->rip6s_fullsock), 0);
            hv_store(c, "outpackets",     10, newSVnv(inf->rip6s_opackets), 0);
            break;
        }
#ifdef BOOTINFO_VERSION
        case FMT_BOOTINFO: {
            HV *c = (HV *)sv_2mortal((SV *)newHV());
            struct bootinfo *inf = (struct bootinfo *)buf;
            RETVAL = newRV((SV *)c);
            /* ignore the following fields for the time being:
             * bi_bios_geom
             * bi_kernelname
             * bi_nfs_diskless
             * bi_symtab
             * bi_esymtab
             */
            hv_store(c, "version",        7, newSVuv(inf->bi_version), 0);
            /* don't know if any IA64 fields are useful,
             * (as per /usr/src/sys/ia64/include/bootinfo.h)
             */
#ifdef __ia64
            hv_store(c, "biosused",       8, newSVpvn("", 0), 0);
            hv_store(c, "size",           4, newSVpvn("", 0), 0);
            hv_store(c, "msizevalid",    10, newSVpvn("", 0), 0);
            hv_store(c, "biosdev",        7, newSVpvn("", 0), 0);
            hv_store(c, "basemem",        7, newSVpvn("", 0), 0);
            hv_store(c, "extmem",         6, newSVpvn("", 0), 0);
#else
            hv_store(c, "biosused",       8, newSVuv(inf->bi_n_bios_used), 0);
            hv_store(c, "size",           4, newSVuv(inf->bi_size), 0);
            hv_store(c, "msizevalid",    10, newSVuv(inf->bi_memsizes_valid), 0);
            hv_store(c, "biosdev",        7, newSVuv(inf->bi_bios_dev), 0);
            hv_store(c, "basemem",        7, newSVuv(inf->bi_basemem), 0);
            hv_store(c, "extmem",         6, newSVuv(inf->bi_extmem), 0);
#endif
            break;
        }
#endif
        case FMT_N:
        case FMT_IPSTAT:
        case FMT_NFSRVSTATS:
        case FMT_NFSSTATS:
        case FMT_XINPCB:
        case FMT_STRUCT_CDEV:
            /* don't know how to interpret the results */
            SvREFCNT_dec(sv_buf);
            XSRETURN_IV(0);
            break;
        default:
            warn("%s: unhandled format type=%d\n", arg, oid_fmt);
            SvREFCNT_dec(sv_buf);
            XSRETURN_IV(0);
            break;
        }

        if (oid_fmt != FMT_A) {
            SvREFCNT_dec(sv_buf);
        }

    OUTPUT:
        RETVAL

SV *
_mib_set(const char *arg, const char *value)
    INIT:
        HV *cache;
        SV **oidp;
        SV *oid;
        char *oid_data;
        int oid_fmt;
        int oid_len;
        int intval;
        unsigned int uintval;
        long longval;
        unsigned long ulongval;
        void *newval = 0;
        size_t newsize = 0;
        char *endconvptr;

    CODE:
        /* see if the mib exists */
        cache = get_hv("BSD::Sysctl::MIB_CACHE", 0);

        if((oidp = hv_fetch(cache, arg, strlen(arg), 0))) {
            oid = *oidp;
        }
        else {
            /* else use the cache
            * How do you call an XS sub from C?
            */
            warn("uncached mib: %s\n", arg);
            XSRETURN_UNDEF;
        }

        oid_data = SvPVX(oid);
        oid_fmt  = (int)(*oid_data);
        oid_data += sizeof(int);

        oid_len  = (int)(*oid_data);
        oid_data += sizeof(int);
        
        switch(oid_fmt) {
        case FMT_A:
            newval  = (void *)value;
            newsize = strlen(value);
            break;

        case FMT_INT:
            intval = (int)strtol(value, &endconvptr, 0);
            if (endconvptr == value || *endconvptr != '\0') {
                warn("invalid integer: '%s'", value);
                XSRETURN_UNDEF;
            }
            newval  = &intval;
            newsize = sizeof(intval);
            break;

        case FMT_UINT:
            uintval = (unsigned int)strtoul(value, &endconvptr, 0);
            if (endconvptr == value || *endconvptr != '\0') {
                warn("invalid unsigned integer: '%s'", value);
                XSRETURN_UNDEF;
            }
            newval  = &uintval;
            newsize = sizeof(uintval);
            break;

        case FMT_LONG:
            longval = strtol(value, &endconvptr, 0);
            if (endconvptr == value || *endconvptr != '\0') {
                warn("invalid long integer: '%s'", value);
                XSRETURN_UNDEF;
            }
            newval  = &longval;
            newsize = sizeof(longval);
            break;

        case FMT_ULONG:
            ulongval = strtoul(value, &endconvptr, 0);
            if (endconvptr == value || *endconvptr != '\0') {
                warn("invalid unsigned long integer: '%s'", value);
                XSRETURN_UNDEF;
            }
            newval  = &ulongval;
            newsize = sizeof(ulongval);
            break;
        }
        
        if (sysctl((int *)oid_data, oid_len, 0, 0, newval, newsize) == -1) {
            warn("set sysctl %s failed\n", arg);
            XSRETURN_UNDEF;
        }
        RETVAL = newSViv(1);

    OUTPUT:
        RETVAL