/*
 * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * Functions specific to Sun OS Solaris platforms.
 *
 * Thanks to Justin Venus who originally wrote a consistent part of
 * this in Cython which I later on translated in C.
 */


#include <Python.h>

// fix for "Cannot use procfs in the large file compilation environment"
// error, see:
// http://sourceware.org/ml/gdb-patches/2010-11/msg00336.html
#undef _FILE_OFFSET_BITS
#define _STRUCTURED_PROC 1

// fix compilation issue on SunOS 5.10, see:
// https://github.com/giampaolo/psutil/issues/421
#define NEW_MIB_COMPLIANT

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/proc.h>
#include <sys/swap.h>
#include <sys/sysinfo.h>
#include <sys/mntent.h>  // for MNTTAB
#include <sys/mnttab.h>
#include <sys/procfs.h>
#include <sys/sockio.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <utmpx.h>
#include <kstat.h>
#include <sys/ioctl.h>
#include <sys/tihdr.h>
#include <stropts.h>
#include <inet/tcp.h>
#include <arpa/inet.h>
#include <net/if.h>

#include "_psutil_sunos.h"


#define TV2DOUBLE(t)   (((t).tv_nsec * 0.000000001) + (t).tv_sec)

/*
 * Read a file content and fills a C structure with it.
 */
int
psutil_file_to_struct(char *path, void *fstruct, size_t size)
{
    int fd;
    size_t nbytes;
    fd = open(path, O_RDONLY);
    if (fd == -1) {
        PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
        return 0;
    }
    nbytes = read(fd, fstruct, size);
    if (nbytes <= 0) {
        close(fd);
        PyErr_SetFromErrno(PyExc_OSError);
        return 0;
    }
    if (nbytes != size) {
        close(fd);
        PyErr_SetString(PyExc_RuntimeError, "structure size mismatch");
        return 0;
    }
    close(fd);
    return nbytes;
}


/*
 * Return process ppid, rss, vms, ctime, nice, nthreads, status and tty
 * as a Python tuple.
 */
static PyObject *
psutil_proc_basic_info(PyObject *self, PyObject *args)
{
    int pid;
    char path[100];
    psinfo_t info;

    if (! PyArg_ParseTuple(args, "i", &pid))
        return NULL;
    sprintf(path, "/proc/%i/psinfo", pid);
    if (! psutil_file_to_struct(path, (void *)&info, sizeof(info)))
        return NULL;
    return Py_BuildValue("ikkdiiik",
                         info.pr_ppid,              // parent pid
                         info.pr_rssize,            // rss
                         info.pr_size,              // vms
                         TV2DOUBLE(info.pr_start),  // create time
                         info.pr_lwp.pr_nice,       // nice
                         info.pr_nlwp,              // no. of threads
                         info.pr_lwp.pr_state,      // status code
                         info.pr_ttydev             // tty nr
                        );
}


/*
 * Return process name and args as a Python tuple.
 */
static PyObject *
psutil_proc_name_and_args(PyObject *self, PyObject *args)
{
    int pid;
    char path[100];
    psinfo_t info;

    if (! PyArg_ParseTuple(args, "i", &pid))
        return NULL;
    sprintf(path, "/proc/%i/psinfo", pid);
    if (! psutil_file_to_struct(path, (void *)&info, sizeof(info)))
        return NULL;
    return Py_BuildValue("ss", info.pr_fname, info.pr_psargs);
}


/*
 * Return process user and system CPU times as a Python tuple.
 */
static PyObject *
psutil_proc_cpu_times(PyObject *self, PyObject *args)
{
    int pid;
    char path[100];
    pstatus_t info;

    if (! PyArg_ParseTuple(args, "i", &pid))
        return NULL;
    sprintf(path, "/proc/%i/status", pid);
    if (! psutil_file_to_struct(path, (void *)&info, sizeof(info)))
        return NULL;
    // results are more precise than os.times()
    return Py_BuildValue("dd",
                         TV2DOUBLE(info.pr_utime),
                         TV2DOUBLE(info.pr_stime));
}


/*
 * Return process uids/gids as a Python tuple.
 */
static PyObject *
psutil_proc_cred(PyObject *self, PyObject *args)
{
    int pid;
    char path[100];
    prcred_t info;

    if (! PyArg_ParseTuple(args, "i", &pid))
        return NULL;
    sprintf(path, "/proc/%i/cred", pid);
    if (! psutil_file_to_struct(path, (void *)&info, sizeof(info)))
        return NULL;
    return Py_BuildValue("iiiiii",
                         info.pr_ruid, info.pr_euid, info.pr_suid,
                         info.pr_rgid, info.pr_egid, info.pr_sgid);
}


/*
 * Return process uids/gids as a Python tuple.
 */
static PyObject *
psutil_proc_num_ctx_switches(PyObject *self, PyObject *args)
{
    int pid;
    char path[100];
    prusage_t info;

    if (! PyArg_ParseTuple(args, "i", &pid))
        return NULL;
    sprintf(path, "/proc/%i/usage", pid);
    if (! psutil_file_to_struct(path, (void *)&info, sizeof(info)))
        return NULL;
    return Py_BuildValue("kk", info.pr_vctx, info.pr_ictx);
}


/*
 * Process IO counters.
 *
 * Commented out and left here as a reminder.  Apparently we cannot
 * retrieve process IO stats because:
 * - 'pr_ioch' is a sum of chars read and written, with no distinction
 * - 'pr_inblk' and 'pr_oublk', which should be the number of bytes
 *    read and written, hardly increase and according to:
 *    http://www.brendangregg.com/Perf/paper_diskubyp1.pdf
 *    ...they should be meaningless anyway.
 *
static PyObject*
proc_io_counters(PyObject* self, PyObject* args)
{
    int pid;
    char path[100];
    prusage_t info;

    if (! PyArg_ParseTuple(args, "i", &pid))
        return NULL;
    sprintf(path, "/proc/%i/usage", pid);
    if (! psutil_file_to_struct(path, (void *)&info, sizeof(info)))
        return NULL;

    // On Solaris we only have 'pr_ioch' which accounts for bytes read
    // *and* written.
    // 'pr_inblk' and 'pr_oublk' should be expressed in blocks of
    // 8KB according to:
    // http://www.brendangregg.com/Perf/paper_diskubyp1.pdf  (pag. 8)
    return Py_BuildValue("kkkk",
                         info.pr_ioch,
                         info.pr_ioch,
                         info.pr_inblk,
                         info.pr_oublk);
}
 */


/*
 * Return information about a given process thread.
 */
static PyObject *
psutil_proc_query_thread(PyObject *self, PyObject *args)
{
    int pid, tid;
    char path[100];
    lwpstatus_t info;

    if (! PyArg_ParseTuple(args, "ii", &pid, &tid))
        return NULL;
    sprintf(path, "/proc/%i/lwp/%i/lwpstatus", pid, tid);
    if (! psutil_file_to_struct(path, (void *)&info, sizeof(info)))
        return NULL;
    return Py_BuildValue("dd",
                         TV2DOUBLE(info.pr_utime),
                         TV2DOUBLE(info.pr_stime));
}


/*
 * Return information about system virtual memory.
 */
static PyObject *
psutil_swap_mem(PyObject *self, PyObject *args)
{
// XXX (arghhh!)
// total/free swap mem: commented out as for some reason I can't
// manage to get the same results shown by "swap -l", despite the
// code below is exactly the same as:
// http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/
//    cmd/swap/swap.c
// We're going to parse "swap -l" output from Python (sigh!)

/*
    struct swaptable     *st;
    struct swapent    *swapent;
    int    i;
    struct stat64 statbuf;
    char *path;
    char fullpath[MAXPATHLEN+1];
    int    num;

    if ((num = swapctl(SC_GETNSWP, NULL)) == -1) {
        PyErr_SetFromErrno(PyExc_OSError);
        return NULL;
    }
    if (num == 0) {
        PyErr_SetString(PyExc_RuntimeError, "no swap devices configured");
        return NULL;
    }
    if ((st = malloc(num * sizeof(swapent_t) + sizeof (int))) == NULL) {
        PyErr_SetString(PyExc_RuntimeError, "malloc failed");
        return NULL;
    }
    if ((path = malloc(num * MAXPATHLEN)) == NULL) {
        PyErr_SetString(PyExc_RuntimeError, "malloc failed");
        return NULL;
    }
    swapent = st->swt_ent;
    for (i = 0; i < num; i++, swapent++) {
        swapent->ste_path = path;
        path += MAXPATHLEN;
    }
    st->swt_n = num;
    if ((num = swapctl(SC_LIST, st)) == -1) {
        PyErr_SetFromErrno(PyExc_OSError);
        return NULL;
    }

    swapent = st->swt_ent;
    long t = 0, f = 0;
    for (i = 0; i < num; i++, swapent++) {
        int diskblks_per_page =(int)(sysconf(_SC_PAGESIZE) >> DEV_BSHIFT);
        t += (long)swapent->ste_pages;
        f += (long)swapent->ste_free;
    }

    free(st);
    return Py_BuildValue("(kk)", t, f);
*/

    kstat_ctl_t *kc;
    kstat_t     *k;
    cpu_stat_t  *cpu;
    int         cpu_count = 0;
    int         flag = 0;
    uint_t      sin = 0;
    uint_t      sout = 0;

    kc = kstat_open();
    if (kc == NULL)
        return PyErr_SetFromErrno(PyExc_OSError);;

    k = kc->kc_chain;
    while (k != NULL) {
        if ((strncmp(k->ks_name, "cpu_stat", 8) == 0) && \
                (kstat_read(kc, k, NULL) != -1) )
        {
            flag = 1;
            cpu = (cpu_stat_t *) k->ks_data;
            sin += cpu->cpu_vminfo.pgswapin;    // num pages swapped in
            sout += cpu->cpu_vminfo.pgswapout;  // num pages swapped out
        }
        cpu_count += 1;
        k = k->ks_next;
    }
    kstat_close(kc);
    if (!flag) {
        PyErr_SetString(PyExc_RuntimeError, "no swap device was found");
        return NULL;
    }
    return Py_BuildValue("(II)", sin, sout);
}


/*
 * Return users currently connected on the system.
 */
static PyObject *
psutil_users(PyObject *self, PyObject *args)
{
    struct utmpx *ut;
    PyObject *ret_list = PyList_New(0);
    PyObject *tuple = NULL;
    PyObject *user_proc = NULL;

    if (ret_list == NULL)
        return NULL;

    while (NULL != (ut = getutxent())) {
        if (ut->ut_type == USER_PROCESS)
            user_proc = Py_True;
        else
            user_proc = Py_False;
        tuple = Py_BuildValue(
            "(sssfO)",
            ut->ut_user,              // username
            ut->ut_line,              // tty
            ut->ut_host,              // hostname
            (float)ut->ut_tv.tv_sec,  // tstamp
            user_proc);               // (bool) user process
        if (tuple == NULL)
            goto error;
        if (PyList_Append(ret_list, tuple))
            goto error;
        Py_DECREF(tuple);
    }
    endutent();

    return ret_list;

error:
    Py_XDECREF(tuple);
    Py_DECREF(ret_list);
    if (ut != NULL)
        endutent();
    return NULL;
}


/*
 * Return disk mounted partitions as a list of tuples including device,
 * mount point and filesystem type.
 */
static PyObject *
psutil_disk_partitions(PyObject *self, PyObject *args)
{
    FILE *file;
    struct mnttab mt;
    PyObject *py_retlist = PyList_New(0);
    PyObject *py_tuple = NULL;

    if (py_retlist == NULL)
        return NULL;

    file = fopen(MNTTAB, "rb");
    if (file == NULL) {
        PyErr_SetFromErrno(PyExc_OSError);
        goto error;
    }

    while (getmntent(file, &mt) == 0) {
        py_tuple = Py_BuildValue(
            "(ssss)",
            mt.mnt_special,   // device
            mt.mnt_mountp,    // mount point
            mt.mnt_fstype,    // fs type
            mt.mnt_mntopts);  // options
        if (py_tuple == NULL)
            goto error;
        if (PyList_Append(py_retlist, py_tuple))
            goto error;
        Py_DECREF(py_tuple);

    }
    fclose(file);
    return py_retlist;

error:
    Py_XDECREF(py_tuple);
    Py_DECREF(py_retlist);
    if (file != NULL)
        fclose(file);
    return NULL;
}


/*
 * Return system-wide CPU times.
 */
static PyObject *
psutil_per_cpu_times(PyObject *self, PyObject *args)
{
    kstat_ctl_t *kc;
    kstat_t *ksp;
    cpu_stat_t cs;
    PyObject *py_retlist = PyList_New(0);
    PyObject *py_cputime = NULL;

    if (py_retlist == NULL)
        return NULL;

    kc = kstat_open();
    if (kc == NULL) {
        PyErr_SetFromErrno(PyExc_OSError);
        goto error;
    }

    for (ksp = kc->kc_chain; ksp != NULL; ksp = ksp->ks_next) {
        if (strcmp(ksp->ks_module, "cpu_stat") == 0) {
            if (kstat_read(kc, ksp, &cs) == -1) {
                PyErr_SetFromErrno(PyExc_OSError);
                goto error;
            }
            py_cputime = Py_BuildValue("ffff",
                                       (float)cs.cpu_sysinfo.cpu[CPU_USER],
                                       (float)cs.cpu_sysinfo.cpu[CPU_KERNEL],
                                       (float)cs.cpu_sysinfo.cpu[CPU_IDLE],
                                       (float)cs.cpu_sysinfo.cpu[CPU_WAIT]);
            if (py_cputime == NULL)
                goto error;
            if (PyList_Append(py_retlist, py_cputime))
                goto error;
            Py_DECREF(py_cputime);
            py_cputime = NULL;
        }
    }

    kstat_close(kc);
    return py_retlist;

error:
    Py_XDECREF(py_cputime);
    Py_DECREF(py_retlist);
    if (kc != NULL)
        kstat_close(kc);
    return NULL;
}


/*
 * Return disk IO statistics.
 */
static PyObject *
psutil_disk_io_counters(PyObject *self, PyObject *args)
{
    kstat_ctl_t *kc;
    kstat_t *ksp;
    kstat_io_t kio;
    PyObject *py_retdict = PyDict_New();
    PyObject *py_disk_info = NULL;

    if (py_retdict == NULL)
        return NULL;
    kc = kstat_open();
    if (kc == NULL) {
        PyErr_SetFromErrno(PyExc_OSError);;
        goto error;
    }
    ksp = kc->kc_chain;
    while (ksp != NULL) {
        if (ksp->ks_type == KSTAT_TYPE_IO) {
            if (strcmp(ksp->ks_class, "disk") == 0) {
                if (kstat_read(kc, ksp, &kio) == -1) {
                    kstat_close(kc);
                    return PyErr_SetFromErrno(PyExc_OSError);;
                }
                py_disk_info = Py_BuildValue(
                    "(IIKKLL)",
                    kio.reads,
                    kio.writes,
                    kio.nread,
                    kio.nwritten,
                    kio.rtime / 1000 / 1000,  // from nano to milli secs
                    kio.wtime / 1000 / 1000   // from nano to milli secs
                );
                if (!py_disk_info)
                    goto error;
                if (PyDict_SetItemString(py_retdict, ksp->ks_name,
                                         py_disk_info))
                    goto error;
                Py_DECREF(py_disk_info);
            }
        }
        ksp = ksp->ks_next;
    }
    kstat_close(kc);

    return py_retdict;

error:
    Py_XDECREF(py_disk_info);
    Py_DECREF(py_retdict);
    if (kc != NULL)
        kstat_close(kc);
    return NULL;
}


/*
 * Return process memory mappings.
 */
static PyObject *
psutil_proc_memory_maps(PyObject *self, PyObject *args)
{
    int pid;
    int fd = -1;
    char path[100];
    char perms[10];
    char *name;
    struct stat st;
    pstatus_t status;

    prxmap_t *xmap = NULL, *p;
    off_t size;
    size_t nread;
    int nmap;
    uintptr_t pr_addr_sz;
    uintptr_t stk_base_sz, brk_base_sz;

    PyObject *pytuple = NULL;
    PyObject *py_retlist = PyList_New(0);

    if (py_retlist == NULL)
        return NULL;
    if (! PyArg_ParseTuple(args, "i", &pid))
        goto error;

    sprintf(path, "/proc/%i/status", pid);
    if (! psutil_file_to_struct(path, (void *)&status, sizeof(status)))
        goto error;

    sprintf(path, "/proc/%i/xmap", pid);
    if (stat(path, &st) == -1) {
        PyErr_SetFromErrno(PyExc_OSError);
        goto error;
    }

    size = st.st_size;

    fd = open(path, O_RDONLY);
    if (fd == -1) {
        PyErr_SetFromErrno(PyExc_OSError);
        goto error;
    }

    xmap = (prxmap_t *)malloc(size);
    if (xmap == NULL) {
        PyErr_NoMemory();
        goto error;
    }

    nread = pread(fd, xmap, size, 0);
    nmap = nread / sizeof(prxmap_t);
    p = xmap;

    while (nmap) {
        nmap -= 1;
        if (p == NULL) {
            p += 1;
            continue;
        }

        perms[0] = '\0';
        pr_addr_sz = p->pr_vaddr + p->pr_size;

        // perms
        sprintf(perms, "%c%c%c%c%c%c", p->pr_mflags & MA_READ ? 'r' : '-',
                p->pr_mflags & MA_WRITE ? 'w' : '-',
                p->pr_mflags & MA_EXEC ? 'x' : '-',
                p->pr_mflags & MA_SHARED ? 's' : '-',
                p->pr_mflags & MA_NORESERVE ? 'R' : '-',
                p->pr_mflags & MA_RESERVED1 ? '*' : ' ');

        // name
        if (strlen(p->pr_mapname) > 0) {
            name = p->pr_mapname;
        }
        else {
            if ((p->pr_mflags & MA_ISM) || (p->pr_mflags & MA_SHM)) {
                name = "[shmid]";
            }
            else {
                stk_base_sz = status.pr_stkbase + status.pr_stksize;
                brk_base_sz = status.pr_brkbase + status.pr_brksize;

                if ((pr_addr_sz > status.pr_stkbase) &&
                        (p->pr_vaddr < stk_base_sz)) {
                    name = "[stack]";
                }
                else if ((p->pr_mflags & MA_ANON) && \
                         (pr_addr_sz > status.pr_brkbase) && \
                         (p->pr_vaddr < brk_base_sz)) {
                    name = "[heap]";
                }
                else {
                    name = "[anon]";
                }
            }
        }

        pytuple = Py_BuildValue("iisslll",
                                p->pr_vaddr,
                                pr_addr_sz,
                                perms,
                                name,
                                (long)p->pr_rss * p->pr_pagesize,
                                (long)p->pr_anon * p->pr_pagesize,
                                (long)p->pr_locked * p->pr_pagesize);
        if (!pytuple)
            goto error;
        if (PyList_Append(py_retlist, pytuple))
            goto error;
        Py_DECREF(pytuple);

        // increment pointer
        p += 1;
    }

    close(fd);
    free(xmap);
    return py_retlist;

error:
    if (fd != -1)
        close(fd);
    Py_XDECREF(pytuple);
    Py_DECREF(py_retlist);
    if (xmap != NULL)
        free(xmap);
    return NULL;
}


/*
 * Return a list of tuples for network I/O statistics.
 */
static PyObject *
psutil_net_io_counters(PyObject *self, PyObject *args)
{
    kstat_ctl_t    *kc = NULL;
    kstat_t *ksp;
    kstat_named_t *rbytes, *wbytes, *rpkts, *wpkts, *ierrs, *oerrs;

    PyObject *py_retdict = PyDict_New();
    PyObject *py_ifc_info = NULL;

    if (py_retdict == NULL)
        return NULL;
    kc = kstat_open();
    if (kc == NULL)
        goto error;

    ksp = kc->kc_chain;
    while (ksp != NULL) {
        if (ksp->ks_type != KSTAT_TYPE_NAMED)
            goto next;
        if (strcmp(ksp->ks_class, "net") != 0)
            goto next;
        /*
        // XXX "lo" (localhost) interface makes kstat_data_lookup() fail
        // (maybe because "ifconfig -a" says it's a virtual interface?).
        if ((strcmp(ksp->ks_module, "link") != 0) &&
            (strcmp(ksp->ks_module, "lo") != 0)) {
            goto skip;
        */
        if ((strcmp(ksp->ks_module, "link") != 0))
            goto next;

        if (kstat_read(kc, ksp, NULL) == -1) {
            errno = 0;
            continue;
        }

        rbytes = (kstat_named_t *)kstat_data_lookup(ksp, "rbytes");
        wbytes = (kstat_named_t *)kstat_data_lookup(ksp, "obytes");
        rpkts = (kstat_named_t *)kstat_data_lookup(ksp, "ipackets");
        wpkts = (kstat_named_t *)kstat_data_lookup(ksp, "opackets");
        ierrs = (kstat_named_t *)kstat_data_lookup(ksp, "ierrors");
        oerrs = (kstat_named_t *)kstat_data_lookup(ksp, "oerrors");

        if ((rbytes == NULL) || (wbytes == NULL) || (rpkts == NULL) ||
                (wpkts == NULL) || (ierrs == NULL) || (oerrs == NULL))
        {
            PyErr_SetString(PyExc_RuntimeError, "kstat_data_lookup() failed");
            goto error;
        }

#if defined(_INT64_TYPE)
        py_ifc_info = Py_BuildValue("(KKKKkkii)",
                                    wbytes->value.ui64,
                                    rbytes->value.ui64,
                                    wpkts->value.ui64,
                                    rpkts->value.ui64,
                                    ierrs->value.ui32,
                                    oerrs->value.ui32,
#else
        py_ifc_info = Py_BuildValue("(kkkkkkii)",
                                    wbytes->value.ui32,
                                    rbytes->value.ui32,
                                    wpkts->value.ui32,
                                    rpkts->value.ui32,
                                    ierrs->value.ui32,
                                    oerrs->value.ui32,
#endif
                                    0,  // dropin not supported
                                    0   // dropout not supported
                                   );
        if (!py_ifc_info)
            goto error;
        if (PyDict_SetItemString(py_retdict, ksp->ks_name, py_ifc_info))
            goto error;
        Py_DECREF(py_ifc_info);
        goto next;

next:
        ksp = ksp->ks_next;
    }

    kstat_close(kc);
    return py_retdict;

error:
    Py_XDECREF(py_ifc_info);
    Py_DECREF(py_retdict);
    if (kc != NULL)
        kstat_close(kc);
    return NULL;
}


#ifndef EXPER_IP_AND_ALL_IRES
#define EXPER_IP_AND_ALL_IRES   (1024+4)
#endif

// a signaler for connections without an actual status
static int PSUTIL_CONN_NONE = 128;

/*
 * Return TCP and UDP connections opened by process.
 * UNIX sockets are excluded.
 *
 * Thanks to:
 * https://github.com/DavidGriffith/finx/blob/master/
 *     nxsensor-3.5.0-1/src/sysdeps/solaris.c
 * ...and:
 * https://hg.java.net/hg/solaris~on-src/file/tip/usr/src/cmd/
 *     cmd-inet/usr.bin/netstat/netstat.c
 */
static PyObject *
psutil_net_connections(PyObject *self, PyObject *args)
{
    long pid;
    int sd = 0;
    mib2_tcpConnEntry_t *tp = NULL;
    mib2_udpEntry_t     *ude;
#if defined(AF_INET6)
    mib2_tcp6ConnEntry_t *tp6;
    mib2_udp6Entry_t     *ude6;
#endif
    char buf[512];
    int i, flags, getcode, num_ent, state;
    char lip[200], rip[200];
    int lport, rport;
    int processed_pid;
    int databuf_init = 0;
    struct strbuf ctlbuf, databuf;
    struct T_optmgmt_req *tor = (struct T_optmgmt_req *)buf;
    struct T_optmgmt_ack *toa = (struct T_optmgmt_ack *)buf;
    struct T_error_ack   *tea = (struct T_error_ack *)buf;
    struct opthdr        *mibhdr;

    PyObject *py_retlist = PyList_New(0);
    PyObject *py_tuple = NULL;
    PyObject *py_laddr = NULL;
    PyObject *py_raddr = NULL;
    PyObject *af_filter = NULL;
    PyObject *type_filter = NULL;

    if (py_retlist == NULL)
        return NULL;
    if (! PyArg_ParseTuple(args, "lOO", &pid, &af_filter, &type_filter))
        goto error;
    if (!PySequence_Check(af_filter) || !PySequence_Check(type_filter)) {
        PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence");
        goto error;
    }

    sd = open("/dev/arp", O_RDWR);
    if (sd == -1) {
        PyErr_SetFromErrnoWithFilename(PyExc_OSError, "/dev/arp");
        goto error;
    }

    /*
    XXX - These 2 are used in ifconfig.c but they seem unnecessary
    ret = ioctl(sd, I_PUSH, "tcp");
    if (ret == -1) {
        PyErr_SetFromErrno(PyExc_OSError);
        goto error;
    }
    ret = ioctl(sd, I_PUSH, "udp");
    if (ret == -1) {
        PyErr_SetFromErrno(PyExc_OSError);
        goto error;
    }
    */

    // OK, this mess is basically copied and pasted from nxsensor project
    // which copied and pasted it from netstat source code, mibget()
    // function.  Also see:
    // http://stackoverflow.com/questions/8723598/
    tor->PRIM_type = T_SVR4_OPTMGMT_REQ;
    tor->OPT_offset = sizeof (struct T_optmgmt_req);
    tor->OPT_length = sizeof (struct opthdr);
    tor->MGMT_flags = T_CURRENT;
    mibhdr = (struct opthdr *)&tor[1];
    mibhdr->level = EXPER_IP_AND_ALL_IRES;
    mibhdr->name  = 0;
    mibhdr->len   = 0;

    ctlbuf.buf = buf;
    ctlbuf.len = tor->OPT_offset + tor->OPT_length;
    flags = 0;  // request to be sent in non-priority

    if (putmsg(sd, &ctlbuf, (struct strbuf *)0, flags) == -1) {
        PyErr_SetFromErrno(PyExc_OSError);
        goto error;
    }

    mibhdr = (struct opthdr *)&toa[1];
    ctlbuf.maxlen = sizeof (buf);

    for (;;) {
        flags = 0;
        getcode = getmsg(sd, &ctlbuf, (struct strbuf *)0, &flags);

        if (getcode != MOREDATA ||
                ctlbuf.len < sizeof (struct T_optmgmt_ack) ||
                toa->PRIM_type != T_OPTMGMT_ACK ||
                toa->MGMT_flags != T_SUCCESS)
        {
            break;
        }
        if (ctlbuf.len >= sizeof (struct T_error_ack) &&
                tea->PRIM_type == T_ERROR_ACK)
        {
            PyErr_SetString(PyExc_RuntimeError, "ERROR_ACK");
            goto error;
        }
        if (getcode == 0 &&
                ctlbuf.len >= sizeof (struct T_optmgmt_ack) &&
                toa->PRIM_type == T_OPTMGMT_ACK &&
                toa->MGMT_flags == T_SUCCESS)
        {
            PyErr_SetString(PyExc_RuntimeError, "ERROR_T_OPTMGMT_ACK");
            goto error;
        }

        databuf.maxlen = mibhdr->len;
        databuf.len = 0;
        databuf.buf = (char *)malloc((int)mibhdr->len);
        if (!databuf.buf) {
            PyErr_NoMemory();
            goto error;
        }
        databuf_init = 1;

        flags = 0;
        getcode = getmsg(sd, (struct strbuf *)0, &databuf, &flags);
        if (getcode < 0) {
            PyErr_SetFromErrno(PyExc_OSError);
            goto error;
        }

        // TCPv4
        if (mibhdr->level == MIB2_TCP && mibhdr->name == MIB2_TCP_13) {
            tp = (mib2_tcpConnEntry_t *)databuf.buf;
            num_ent = mibhdr->len / sizeof(mib2_tcpConnEntry_t);
            for (i = 0; i < num_ent; i++, tp++) {
                processed_pid = tp->tcpConnCreationProcess;
                if (pid != -1 && processed_pid != pid)
                    continue;
                // construct local/remote addresses
                inet_ntop(AF_INET, &tp->tcpConnLocalAddress, lip, sizeof(lip));
                inet_ntop(AF_INET, &tp->tcpConnRemAddress, rip, sizeof(rip));
                lport = tp->tcpConnLocalPort;
                rport = tp->tcpConnRemPort;

                // contruct python tuple/list
                py_laddr = Py_BuildValue("(si)", lip, lport);
                if (!py_laddr)
                    goto error;
                if (rport != 0)
                    py_raddr = Py_BuildValue("(si)", rip, rport);
                else {
                    py_raddr = Py_BuildValue("()");
                }
                if (!py_raddr)
                    goto error;
                state = tp->tcpConnEntryInfo.ce_state;

                // add item
                py_tuple = Py_BuildValue("(iiiNNiI)", -1, AF_INET, SOCK_STREAM,
                                         py_laddr, py_raddr, state,
                                         processed_pid);
                if (!py_tuple)
                    goto error;
                if (PyList_Append(py_retlist, py_tuple))
                    goto error;
                Py_DECREF(py_tuple);
            }
        }
#if defined(AF_INET6)
        // TCPv6
        else if (mibhdr->level == MIB2_TCP6 && mibhdr->name == MIB2_TCP6_CONN)
        {
            tp6 = (mib2_tcp6ConnEntry_t *)databuf.buf;
            num_ent = mibhdr->len / sizeof(mib2_tcp6ConnEntry_t);

            for (i = 0; i < num_ent; i++, tp6++) {
                processed_pid = tp6->tcp6ConnCreationProcess;
                if (pid != -1 && processed_pid != pid)
                    continue;
                // construct local/remote addresses
                inet_ntop(AF_INET6, &tp6->tcp6ConnLocalAddress, lip, sizeof(lip));
                inet_ntop(AF_INET6, &tp6->tcp6ConnRemAddress, rip, sizeof(rip));
                lport = tp6->tcp6ConnLocalPort;
                rport = tp6->tcp6ConnRemPort;

                // contruct python tuple/list
                py_laddr = Py_BuildValue("(si)", lip, lport);
                if (!py_laddr)
                    goto error;
                if (rport != 0)
                    py_raddr = Py_BuildValue("(si)", rip, rport);
                else
                    py_raddr = Py_BuildValue("()");
                if (!py_raddr)
                    goto error;
                state = tp6->tcp6ConnEntryInfo.ce_state;

                // add item
                py_tuple = Py_BuildValue("(iiiNNiI)", -1, AF_INET6, SOCK_STREAM,
                                         py_laddr, py_raddr, state, processed_pid);
                if (!py_tuple)
                    goto error;
                if (PyList_Append(py_retlist, py_tuple))
                    goto error;
                Py_DECREF(py_tuple);
            }
        }
#endif
        // UDPv4
        else if (mibhdr->level == MIB2_UDP || mibhdr->level == MIB2_UDP_ENTRY) {
            ude = (mib2_udpEntry_t *)databuf.buf;
            num_ent = mibhdr->len / sizeof(mib2_udpEntry_t);
            for (i = 0; i < num_ent; i++, ude++) {
                processed_pid = ude->udpCreationProcess;
                if (pid != -1 && processed_pid != pid)
                    continue;
                // XXX Very ugly hack! It seems we get here only the first
                // time we bump into a UDPv4 socket.  PID is a very high
                // number (clearly impossible) and the address does not
                // belong to any valid interface.  Not sure what else
                // to do other than skipping.
                if (processed_pid > 131072)
                    continue;
                inet_ntop(AF_INET, &ude->udpLocalAddress, lip, sizeof(lip));
                lport = ude->udpLocalPort;
                py_laddr = Py_BuildValue("(si)", lip, lport);
                if (!py_laddr)
                    goto error;
                py_raddr = Py_BuildValue("()");
                if (!py_raddr)
                    goto error;
                py_tuple = Py_BuildValue("(iiiNNiI)", -1, AF_INET, SOCK_DGRAM,
                                         py_laddr, py_raddr, PSUTIL_CONN_NONE,
                                         processed_pid);
                if (!py_tuple)
                    goto error;
                if (PyList_Append(py_retlist, py_tuple))
                    goto error;
                Py_DECREF(py_tuple);
            }
        }
#if defined(AF_INET6)
        // UDPv6
        else if (mibhdr->level == MIB2_UDP6 ||
                    mibhdr->level == MIB2_UDP6_ENTRY)
            {
            ude6 = (mib2_udp6Entry_t *)databuf.buf;
            num_ent = mibhdr->len / sizeof(mib2_udp6Entry_t);
            for (i = 0; i < num_ent; i++, ude6++) {
                processed_pid = ude6->udp6CreationProcess;
                if (pid != -1 && processed_pid != pid)
                    continue;
                inet_ntop(AF_INET6, &ude6->udp6LocalAddress, lip, sizeof(lip));
                lport = ude6->udp6LocalPort;
                py_laddr = Py_BuildValue("(si)", lip, lport);
                if (!py_laddr)
                    goto error;
                py_raddr = Py_BuildValue("()");
                if (!py_raddr)
                    goto error;
                py_tuple = Py_BuildValue("(iiiNNiI)", -1, AF_INET6, SOCK_DGRAM,
                                         py_laddr, py_raddr, PSUTIL_CONN_NONE,
                                         processed_pid);
                if (!py_tuple)
                    goto error;
                if (PyList_Append(py_retlist, py_tuple))
                    goto error;
                Py_DECREF(py_tuple);
            }
        }
#endif
        free(databuf.buf);
    }

    close(sd);
    return py_retlist;

error:
    Py_XDECREF(py_tuple);
    Py_XDECREF(py_laddr);
    Py_XDECREF(py_raddr);
    Py_DECREF(py_retlist);
    if (databuf_init == 1)
        free(databuf.buf);
    if (sd != 0)
        close(sd);
    return NULL;
}


static PyObject *
psutil_boot_time(PyObject *self, PyObject *args)
{
    float boot_time = 0.0;
    struct utmpx *ut;

    while (NULL != (ut = getutxent())) {
        if (ut->ut_type == BOOT_TIME) {
            boot_time = (float)ut->ut_tv.tv_sec;
            break;
        }
    }
    endutent();
    if (boot_time != 0.0) {
        return Py_BuildValue("f", boot_time);
    }
    else {
        PyErr_SetString(PyExc_RuntimeError, "can't determine boot time");
        return NULL;
    }
}


/*
 * Return the number of physical CPU cores on the system.
 */
static PyObject *
psutil_cpu_count_phys(PyObject *self, PyObject *args)
{
    kstat_ctl_t *kc;
    kstat_t *ksp;
    int ncpus = 0;

    kc = kstat_open();
    if (kc == NULL)
        goto error;
    ksp = kstat_lookup(kc, "cpu_info", -1, NULL);
    if (ksp == NULL)
        goto error;

    for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
        if (strcmp(ksp->ks_module, "cpu_info") != 0)
            continue;
        if (kstat_read(kc, ksp, NULL) == -1)
            goto error;
        ncpus += 1;
    }

    kstat_close(kc);
    if (ncpus > 0)
        return Py_BuildValue("i", ncpus);
    else
        goto error;

error:
    // mimic os.cpu_count()
    if (kc != NULL)
        kstat_close(kc);
    Py_RETURN_NONE;
}


/*
 * Return stats about a particular network
 * interface.  References:
 * https://github.com/dpaleino/wicd/blob/master/wicd/backends/be-ioctl.py
 * http://www.i-scream.org/libstatgrab/
 */
static PyObject*
psutil_net_if_stats(PyObject* self, PyObject* args)
{
    kstat_ctl_t *kc = NULL;
    kstat_t *ksp;
    kstat_named_t *knp;
    int ret;
    int sock = 0;
    int duplex;
    int speed;

    PyObject *py_retdict = PyDict_New();
    PyObject *py_ifc_info = NULL;
    PyObject *py_is_up = NULL;

    if (py_retdict == NULL)
        return NULL;
    kc = kstat_open();
    if (kc == NULL)
        goto error;
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock == -1)
        goto error;

    for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
        if (strcmp(ksp->ks_class, "net") == 0) {
            struct ifreq ifr;

            kstat_read(kc, ksp, NULL);
            if (ksp->ks_type != KSTAT_TYPE_NAMED)
                continue;
            if (strcmp(ksp->ks_class, "net") != 0)
                continue;

            strncpy(ifr.ifr_name, ksp->ks_name, sizeof(ifr.ifr_name));
            ret = ioctl(sock, SIOCGIFFLAGS, &ifr);
            if (ret == -1)
                continue;  // not a network interface

            // is up?
            if ((ifr.ifr_flags & IFF_UP) != 0) {
                if ((knp = kstat_data_lookup(ksp, "link_up")) != NULL) {
                    if (knp->value.ui32 != 0u)
                        py_is_up = Py_True;
                    else
                        py_is_up = Py_False;
                }
                else {
                    py_is_up = Py_True;
                }
            }
            else {
                py_is_up = Py_False;
            }
            Py_INCREF(py_is_up);

            // duplex
            duplex = 0;  // unknown
            if ((knp = kstat_data_lookup(ksp, "link_duplex")) != NULL) {
                if (knp->value.ui32 == 1)
                    duplex = 1;  // half
                else if (knp->value.ui32 == 2)
                    duplex = 2;  // full
            }

            // speed
            if ((knp = kstat_data_lookup(ksp, "ifspeed")) != NULL)
                // expressed in bits per sec, we want mega bits per sec
                speed = (int)knp->value.ui64 / 1000000;
            else
                speed = 0;

            // mtu
            ret = ioctl(sock, SIOCGIFMTU, &ifr);
            if (ret == -1)
                goto error;

            py_ifc_info = Py_BuildValue("(Oiii)", py_is_up, duplex, speed,
                                        ifr.ifr_mtu);
            if (!py_ifc_info)
                goto error;
            if (PyDict_SetItemString(py_retdict, ksp->ks_name, py_ifc_info))
                goto error;
            Py_DECREF(py_ifc_info);
        }
    }

    close(sock);
    kstat_close(kc);
    return py_retdict;

error:
    Py_XDECREF(py_is_up);
    Py_XDECREF(py_ifc_info);
    Py_DECREF(py_retdict);
    if (sock != 0)
        close(sock);
    if (kc != NULL)
        kstat_close(kc);
    PyErr_SetFromErrno(PyExc_OSError);
    return NULL;
}


/*
 * define the psutil C module methods and initialize the module.
 */
static PyMethodDef
PsutilMethods[] =
{
    // --- process-related functions
    {"proc_basic_info", psutil_proc_basic_info, METH_VARARGS,
     "Return process ppid, rss, vms, ctime, nice, nthreads, status and tty"},
    {"proc_name_and_args", psutil_proc_name_and_args, METH_VARARGS,
     "Return process name and args."},
    {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS,
     "Return process user and system CPU times."},
    {"proc_cred", psutil_proc_cred, METH_VARARGS,
     "Return process uids/gids."},
    {"query_process_thread", psutil_proc_query_thread, METH_VARARGS,
     "Return info about a process thread"},
    {"proc_memory_maps", psutil_proc_memory_maps, METH_VARARGS,
     "Return process memory mappings"},
    {"proc_num_ctx_switches", psutil_proc_num_ctx_switches, METH_VARARGS,
     "Return the number of context switches performed by process"},

    // --- system-related functions
    {"swap_mem", psutil_swap_mem, METH_VARARGS,
     "Return information about system swap memory."},
    {"users", psutil_users, METH_VARARGS,
     "Return currently connected users."},
    {"disk_partitions", psutil_disk_partitions, METH_VARARGS,
     "Return disk partitions."},
    {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS,
     "Return system per-CPU times."},
    {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS,
     "Return a Python dict of tuples for disk I/O statistics."},
    {"net_io_counters", psutil_net_io_counters, METH_VARARGS,
     "Return a Python dict of tuples for network I/O statistics."},
    {"boot_time", psutil_boot_time, METH_VARARGS,
     "Return system boot time in seconds since the EPOCH."},
    {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS,
     "Return the number of physical CPUs on the system."},
    {"net_connections", psutil_net_connections, METH_VARARGS,
     "Return TCP and UDP syste-wide open connections."},
    {"net_if_stats", psutil_net_if_stats, METH_VARARGS,
     "Return NIC stats (isup, duplex, speed, mtu)"},

{NULL, NULL, 0, NULL}
};


struct module_state {
    PyObject *error;
};

#if PY_MAJOR_VERSION >= 3
#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m))
#else
#define GETSTATE(m) (&_state)
#endif

#if PY_MAJOR_VERSION >= 3

static int
psutil_sunos_traverse(PyObject *m, visitproc visit, void *arg) {
    Py_VISIT(GETSTATE(m)->error);
    return 0;
}

static int
psutil_sunos_clear(PyObject *m) {
    Py_CLEAR(GETSTATE(m)->error);
    return 0;
}

static struct PyModuleDef moduledef = {
    PyModuleDef_HEAD_INIT,
    "psutil_sunos",
    NULL,
    sizeof(struct module_state),
    PsutilMethods,
    NULL,
    psutil_sunos_traverse,
    psutil_sunos_clear,
    NULL
};

#define INITERROR return NULL

PyMODINIT_FUNC PyInit__psutil_sunos(void)

#else
#define INITERROR return

void init_psutil_sunos(void)
#endif
{
#if PY_MAJOR_VERSION >= 3
    PyObject *module = PyModule_Create(&moduledef);
#else
    PyObject *module = Py_InitModule("_psutil_sunos", PsutilMethods);
#endif
    PyModule_AddIntConstant(module, "version", PSUTIL_VERSION);

    PyModule_AddIntConstant(module, "SSLEEP", SSLEEP);
    PyModule_AddIntConstant(module, "SRUN", SRUN);
    PyModule_AddIntConstant(module, "SZOMB", SZOMB);
    PyModule_AddIntConstant(module, "SSTOP", SSTOP);
    PyModule_AddIntConstant(module, "SIDL", SIDL);
    PyModule_AddIntConstant(module, "SONPROC", SONPROC);
    PyModule_AddIntConstant(module, "SWAIT", SWAIT);

    PyModule_AddIntConstant(module, "PRNODEV", PRNODEV);  // for process tty

    PyModule_AddIntConstant(module, "TCPS_CLOSED", TCPS_CLOSED);
    PyModule_AddIntConstant(module, "TCPS_CLOSING", TCPS_CLOSING);
    PyModule_AddIntConstant(module, "TCPS_CLOSE_WAIT", TCPS_CLOSE_WAIT);
    PyModule_AddIntConstant(module, "TCPS_LISTEN", TCPS_LISTEN);
    PyModule_AddIntConstant(module, "TCPS_ESTABLISHED", TCPS_ESTABLISHED);
    PyModule_AddIntConstant(module, "TCPS_SYN_SENT", TCPS_SYN_SENT);
    PyModule_AddIntConstant(module, "TCPS_SYN_RCVD", TCPS_SYN_RCVD);
    PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_1", TCPS_FIN_WAIT_1);
    PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_2", TCPS_FIN_WAIT_2);
    PyModule_AddIntConstant(module, "TCPS_LAST_ACK", TCPS_LAST_ACK);
    PyModule_AddIntConstant(module, "TCPS_TIME_WAIT", TCPS_TIME_WAIT);
    // sunos specific
    PyModule_AddIntConstant(module, "TCPS_IDLE", TCPS_IDLE);
    // sunos specific
    PyModule_AddIntConstant(module, "TCPS_BOUND", TCPS_BOUND);
    PyModule_AddIntConstant(module, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE);

    if (module == NULL)
        INITERROR;
#if PY_MAJOR_VERSION >= 3
    return module;
#endif
}
