/* GKrellM
|  Copyright (C) 1999-2021 Bill Wilson
|
|  Author:  Bill Wilson
|  Latest versions might be found at:  https://gkrellm.srcbox.net
|
|  solaris.c code is Copyright (C) Daisuke Yabuki <dxy@acm.org>
|
|
|  GKrellM is free software: you can redistribute it and/or modify it
|  under the terms of the GNU General Public License as published by
|  the Free Software Foundation, either version 3 of the License, or
|  (at your option) any later version.
|
|  GKrellM is distributed in the hope that it will be useful, but WITHOUT
|  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|  License for more details.
|
|  You should have received a copy of the GNU General Public License
|  along with this program. If not, see http://www.gnu.org/licenses/
|
|
|  Additional permission under GNU GPL version 3 section 7
|
|  If you modify this program, or any covered work, by linking or
|  combining it with the OpenSSL project's OpenSSL library (or a
|  modified version of that library), containing parts covered by
|  the terms of the OpenSSL or SSLeay licenses, you are granted
|  additional permission to convey the resulting work.
|  Corresponding Source for a non-source form of such a combination
|  shall include the source code for the parts of OpenSSL used as well
|  as that of the covered work.
*/

#include <errno.h>
#include <fcntl.h>
#include <glib.h>
#include <kstat.h>
#include <kvm.h>
#include <libdevinfo.h>
#include <stdio.h>
#include <sys/dkio.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <unistd.h>

kstat_ctl_t *kc;
kvm_t *kd = NULL;

struct nlist nl[] = {
    { "mpid" },
    { 0 }
};


/*
 * An sd instance could be a cdrom or a harddrive. You can't simply tell,
 * from contents of kstat, which type of device an sd device is
 * (well, maybe you could, but at least i can't.)
 * It, however, doesn't make much sense to count cdrom read/write as
 * "Disk" activity. So I'd like to exclude removable media's from
 * monitoring target. In order to do this, I try to open a physical
 * device of a corresponding sd instance. If it's succeeded, I assume
 * it's a hard drive. If I get ENXIO or EBUSY, I'll guess it's CDROM.
 * If you come up with a better (simpler or safer) way to tell it's
 * a removable media or a hard drive, please drop me an e-mail at
 * Daisuke Yabuki <dxy@acm.org>.
 * I don't know any other driver which handle both hard drive and
 * removable media, by the way. I hope it wouldn't do any harm on
 * other type of devices, i.e. ssd, or IDE drivers.
 */
static int
check_media_type(di_node_t root_node, const kstat_t *ksp) {
    di_node_t node = di_drv_first_node(ksp->ks_module, root_node);
    while (node != DI_NODE_NIL) {
        if (di_instance(node) != ksp->ks_instance) {
            node = di_drv_next_node(node);
            continue;
        }

        char *phys_path = di_devfs_path(node);
        if (phys_path == NULL) {
            perror("di_devfs_path");
            return -1;
        }

        char devices_path[256]; /* or OBP_MAXPATHLEN? */
        if (sprintf(devices_path, "/devices%s:c,raw", phys_path) <= 0) {
            di_devfs_path_free(phys_path);
            return -1;
        }
        di_devfs_path_free(phys_path);

        const int fd = open(devices_path, O_RDONLY);
        if (fd == -1) {
            const int err = errno;
            if (err == ENXIO || err == EBUSY) {
                close(fd);
                return 0; /* guess it's removable media */
            } else {
                gkrellm_debug(DEBUG_SYSDEP, "Could not check media type for device '%s': %s(%d)\n",
                    devices_path, g_strerror(err), err);
                close(fd);
                return -1; /* EACCESS (unless setgid sys) or suchlike */
            }
        }

        int dkRemovable;
        if (ioctl(fd, DKIOCREMOVABLE, &dkRemovable) < 0) {
            close(fd);
            return -1;
        }
        close(fd);

        return (dkRemovable == 0) ? 1 : 0;
    }

    return -1; /* shouldn't be reached */
}


static gboolean
kstat_is_disk(const kstat_t *ksp) {
    if (ksp->ks_type != KSTAT_TYPE_IO)
        return FALSE;
    if (strncmp(ksp->ks_class, "disk", 4) != 0)
        return FALSE; /* excluding nfs etc. */
    if (strcmp(ksp->ks_module, "fd") == 0)
        return FALSE; /* excluding fd */
    return TRUE;
}


static GList *disk_name_list;

/*
 * creating a preliminary list of drives, which should be a complete
 * list of drives available on the system. the list is not supposed to
 * contain nfs, fd, cdrom, cdrw etc.
 */
static void
init_disk_name_list(void) {
    gkrellm_debug(DEBUG_SYSDEP, "Creating list of non-removable disks\n");

    extern kstat_ctl_t *kc;
    if (kstat_chain_update(kc) == -1) {
        perror("kstat_chain_update");
        return;
    }

    // device tree info for checking for removable media
    di_node_t root_node = di_init("/", DINFOCPYALL);
    if (root_node == DI_NODE_NIL) {
        perror("di_init");
        return;
    }

    for (kstat_t *ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
        // skip non-disks and removable disks
        // check_media_type() may require elevated access rights!
        if (!kstat_is_disk(ksp) || check_media_type(root_node, ksp) == 0)
            continue;
        gkrellm_debug(DEBUG_SYSDEP, "Found disk: %s\n", ksp->ks_name);
        disk_name_list = g_list_append(disk_name_list, g_strdup(ksp->ks_name));
    }

    di_fini(root_node);
}

static void
cleanup_disk_name_list(void) {
    gkrellm_debug(DEBUG_SYSDEP, "Cleaning up list of non-removable disks\n");

    g_list_free_full(disk_name_list, g_free);
}


void
gkrellm_sys_main_init(void)
{
    /*
     * Most of stats (cpu, proc, disk, memory, net and uptime) are
     * unavailable if kstat_open() failed. So we just exit in that case.
     */
    if ((kc = kstat_open()) == NULL) {
        perror("kstat_open");
        exit(1);
    }

    /*
     * kvm is utilized solely for getting a value for proc.n_forks
     * from kernel variable called mpid. Even if kvm_open() fails,
     * we proceed without it.
     */
    kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL);
    if (kd == NULL) {
        gkrellm_debug(DEBUG_SYSDEP, "Could not open kvm, no process fork statistics.\n");
    } else {
        kvm_nlist(kd, nl);
    }

    /*
     * a function called by the following requires sys gid privilege.
     * the following function should be performed here just for that reason.
     */
    init_disk_name_list();

    if (setgid(getgid()) != 0) {
        perror("Failed to drop setgid privilege");
        exit(1);
    }
}

void
gkrellm_sys_main_cleanup(void)
{
    if (kd != NULL) {
        kvm_close(kd);
    }
    cleanup_disk_name_list();
}


/* ===================================================================== */
/* CPU monitor interface */

#include <kstat.h>
#include <sys/sysinfo.h>

void
gkrellm_sys_cpu_read_data(void)
{
    extern kstat_ctl_t *kc;
    kstat_t *ksp;
    cpu_stat_t cs;

    if (kstat_chain_update(kc) == -1) {
        perror("kstat_chain_update");
        return;
    }

    for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
        if (strcmp(ksp->ks_module, "cpu_stat"))
            continue;
        if (kstat_read(kc, ksp, &cs) == -1) {
            perror("kstat_read");
            continue;
        }
        gkrellm_cpu_assign_data(ksp->ks_instance,
                cs.cpu_sysinfo.cpu[CPU_USER],
                cs.cpu_sysinfo.cpu[CPU_WAIT],
                cs.cpu_sysinfo.cpu[CPU_KERNEL],
                cs.cpu_sysinfo.cpu[CPU_IDLE]);

    }
}

/*
 * note: on some SPARC systems, you can monitor temperature of CPUs
 * with kstat (unix::temperature:[min/max/state/trend...])
 */

gboolean
gkrellm_sys_cpu_init() {
    extern kstat_ctl_t *kc;
    kstat_t *ksp;
    gint    n_cpus = 0;

    if(kstat_chain_update(kc) == -1) {
        perror("kstat_chain_update");
        return FALSE;
    }

    for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
        if (strcmp(ksp->ks_module, "cpu_stat"))
            continue;
        if (kstat_read(kc, ksp, NULL) != -1) {
            gkrellm_cpu_add_instance(ksp->ks_instance);
            ++n_cpus;
            }
    }
    gkrellm_cpu_set_number_of_cpus(n_cpus);
    return TRUE;
}


/* ===================================================================== */
/* Proc monitor interface */

#include <utmp.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/loadavg.h>
#include <kstat.h>
#include <fcntl.h>
#include <kvm.h>
#include <sys/sysinfo.h>

void
gkrellm_sys_proc_read_data(void)
{

    double avenrun[LOADAVG_NSTATS], fload = 0;
    guint   n_processes = 0, n_forks = 0;
    int last_pid;
    extern kstat_ctl_t *kc;
    kstat_t *ksp;
    kstat_named_t *knp;

    extern kvm_t *kd;
    extern struct nlist nl[];

    if (!GK.second_tick) /* Only one read per second */
        return;

    if (getloadavg(avenrun, LOADAVG_NSTATS) > 0)
        fload = avenrun[LOADAVG_1MIN];

    if (kstat_chain_update(kc) == -1) {
        perror("kstat_chain_update");
        return;
    }
    ksp = kstat_lookup(kc, "unix", -1, "system_misc");
    if (ksp && kstat_read(kc, ksp, NULL) >= 0) {
        knp = (kstat_named_t *)kstat_data_lookup(ksp, "nproc");
        if (knp) {
            n_processes = knp->value.ui32;
        }
    }

    if (kd) {
        if (kvm_kread(kd, nl[0].n_value, (char *)&last_pid, sizeof(int)) != -1)
            n_forks = last_pid;
    } else {
        n_forks = 0;
    }
    /* NOTE: code to get 'n_running' is not implemented (stays untouched).
     * but it wouldn't do any harm since nobody seems to refer to it.
     */
    gkrellm_proc_assign_data(n_processes, 0, n_forks, fload);
}


void
gkrellm_sys_proc_read_users(void)
{
    static struct utmp *utmpp;
    gint    n_users;

    n_users = 0;
    setutent();
    while ((utmpp = getutent()) != NULL) {
        if (utmpp->ut_type == USER_PROCESS && utmpp->ut_name[0] != '\0')
            n_users++;
    }
    gkrellm_proc_assign_users(n_users);
}

gboolean
gkrellm_sys_proc_init(void)
{
    return TRUE;
}



/* ===================================================================== */
/* Disk monitor interface */

#define UNIT_SHIFT 3

#define NAME2MAJOR 0
#define MAJOR2NAME 1

typedef struct {
    gint major;
    gchar name[32];
} name_to_major_t;


gchar *
gkrellm_sys_disk_name_from_device(gint device_number, gint unit_number,
            gint *order)
{
    return NULL;    /* Disk data by device not implemented in Solaris */
}

gint
gkrellm_sys_disk_order_from_name(const gchar *name)
{
    return -1;  /* Append as added */
}

void
gkrellm_sys_disk_read_data(void)
{
    extern kstat_ctl_t *kc;
    if (kstat_chain_update(kc) == -1) {
        perror("kstat_chain_update");
        return;
    }

    for (kstat_t *ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
        if (!kstat_is_disk(ksp))
            continue;

        for (GList *list = disk_name_list; list; list = list->next) {
            const char *drive_name = (const char *)list->data;
            if (strcmp(drive_name, ksp->ks_name) != 0)
                continue;

            kstat_io_t kios;
            memset((void *)&kios, 0, sizeof(kstat_io_t));
            if (kstat_read(kc, ksp, &kios) == -1) {
                perror("kstat_read");
                return;
            }

            gkrellm_disk_assign_data_by_name(ksp->ks_name, kios.nread, kios.nwritten, FALSE);
            break;
        }
    }
}

gboolean
gkrellm_sys_disk_init(void)
{
    return TRUE;
}


/* ===================================================================== */
/* Inet monitor interface */

#include "../inet.h"

#include <stropts.h>
#include <inet/mib2.h>
#include <fcntl.h>
#include <sys/tihdr.h>

void
gkrellm_sys_inet_read_tcp_data() {

    ActiveTCP tcp;
    gint tcp_status;

    static int tcpfd = 0;

    mib2_tcpConnEntry_t *tp;
    mib2_tcp6ConnEntry_t *tp6;

    char buf[512];
    int i, flags, getcode, num_ent;
    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;

    if (tcpfd == 0) {
        if ((tcpfd = open("/dev/tcp", O_RDWR)) == -1) {
            perror("open");
        }
    }

    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 = MIB2_TCP;
    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(tcpfd, &ctlbuf, (struct strbuf *)0, flags) == -1) {
        perror("putmsg");
    }

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

    /* now receiving response from stream */

    for (;;) {
        flags = 0; /* read any messages available */
        getcode = getmsg(tcpfd, &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) {
             perror("ERROR_ACK");
             return;
        }

        if (getcode == 0 &&
                 ctlbuf.len >= sizeof (struct T_optmgmt_ack) &&
                 toa->PRIM_type == T_OPTMGMT_ACK &&
                 toa->MGMT_flags == T_SUCCESS) {
             return;
        }

        /* prepare for receiving data */
        databuf.maxlen = mibhdr->len;
        databuf.len    = 0;
        databuf.buf    = (char *)malloc((int)mibhdr->len);
        if(!databuf.buf) {
            perror("malloc");
            break;
        }
        flags = 0;

        getcode = getmsg(tcpfd, (struct strbuf *)0, &databuf, &flags);

        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++) {
                if (tp->tcpConnState != MIB2_TCP_established)
                    continue;
                tcp.local_port         = tp->tcpConnLocalPort;
                tcp.remote_addr.s_addr = tp->tcpConnRemAddress;
                tcp.remote_port        = tp->tcpConnRemPort;
                tcp.family             = AF_INET;
                tcp_status = (tp->tcpConnState == MIB2_TCP_established);
                if (tcp_status == TCP_ALIVE)
                    gkrellm_inet_log_tcp_port_data(&tcp);
            }
        }

        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++) {
                if (tp6->tcp6ConnState != MIB2_TCP_established)
                    continue;
                tcp.local_port          = tp6->tcp6ConnLocalPort;
                tcp.remote_port         = tp6->tcp6ConnRemPort;
                memcpy(&tcp.remote_addr6, &tp6->tcp6ConnRemAddress,
            sizeof(struct in6_addr));
                tcp.family              = AF_INET6;
                tcp_status = (tp6->tcp6ConnState == MIB2_TCP_established);
                if (tcp_status == TCP_ALIVE)
                    gkrellm_inet_log_tcp_port_data(&tcp);
            }
        }

        free(databuf.buf);
    }

}

gboolean
gkrellm_sys_inet_init(void)
{
    return TRUE;
}


/* ===================================================================== */
/* Net monitor interface */

#include <kstat.h>
#include <net/if.h>
#include <sys/sockio.h>

/*
 * FIXME: I haven't tested Net timer (and never will), but I believe it's
 * not going to work. Name of the lock file is different from one on Linux.
 * If you need this functionality, feel free to modify my code.
 */


static gboolean
kstat_is_net(const kstat_t *ksp) {
    if (ksp->ks_type != KSTAT_TYPE_NAMED)
        return FALSE;
    if (strcmp(ksp->ks_class, "net"))
        return FALSE;
    if (strcmp(ksp->ks_module, "link"))
        return FALSE;
    return TRUE;
}


void
gkrellm_sys_net_read_data(void)
{
    gulong  rx, tx;
    extern kstat_ctl_t *kc;
    kstat_t *ksp;
    kstat_named_t *knp;

    if (kstat_chain_update(kc) == -1) {
            perror("kstat_chain_update");
            return;
    }

    for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
        if (!kstat_is_net(ksp))
            continue;

        kstat_read(kc, ksp, NULL);

        knp = kstat_data_lookup(ksp, "rbytes");
        if (knp == NULL)
            continue;

        rx = knp->value.ui32;

        knp = kstat_data_lookup(ksp, "obytes");
        if (knp == NULL)
            continue;

        tx = knp->value.ui32;

        gkrellm_net_assign_data(ksp->ks_name, rx, tx);
    }

}


gboolean
gkrellm_sys_net_isdn_online(void)
{
    return FALSE;
}

void
gkrellm_sys_net_check_routes(void)
{
}

gboolean
gkrellm_sys_net_init(void)
{
    gkrellm_net_set_lock_directory("/var/spool/locks");
    gkrellm_net_add_timer_type_ppp("ipdptp0");
    gkrellm_net_add_timer_type_ppp("ppp0");
    return TRUE;
}



/* ===================================================================== */
/* Memory/Swap monitor interface */

#include <unistd.h>
#include <kstat.h>
#include <sys/stat.h>
#include <sys/swap.h>

static guint64  swap_total, swap_used;

void
gkrellm_sys_mem_read_data() {

    gulong pagesize;
    guint64 total, used = 0, free = 0;
    static gulong pageshift = 0, physpages = 0;
    extern kstat_ctl_t *kc;
    kstat_t *ksp;
    kstat_named_t *knp;

    struct anoninfo ai;

    if (!GK.second_tick)
        return;

    if (pageshift == 0) {
        for (pagesize = sysconf(_SC_PAGESIZE); pagesize > 1; pagesize >>= 1)
            pageshift++;
    }
    if (physpages == 0) {
        physpages = sysconf(_SC_PHYS_PAGES);
    }

    total = physpages;
    total <<= pageshift;

    ksp = kstat_lookup(kc, "unix", -1, "system_pages");
    if (ksp && kstat_read(kc, ksp, NULL) >= 0) {
        knp = (kstat_named_t *)kstat_data_lookup(ksp, "pagesfree");
        if (knp) {
            free = knp->value.ui32;
            free <<= pageshift;
            used = total - free;
        }
    }
    gkrellm_mem_assign_data(total, used, free, 0, 0, 0);
    if (swapctl(SC_AINFO, &ai) == -1) {
        perror("swapctl");
    }
    swap_total = ai.ani_max;
    swap_total <<= pageshift;

    swap_used  = ai.ani_resv;
    swap_used  <<= pageshift;

    /* NEED TO BE COMPLETED
     * mem.x_used, mem.shared, mem.buffers, mem.cached
     * swap_chart.page_in, swap_chart.page_out (for swap pages in/out chart)
     */

}

void
gkrellm_sys_swap_read_data(void)
{
    /* page in/out UNIMPLEMENTED */
    gkrellm_swap_assign_data(swap_total, swap_used, 0, 0);
}

gboolean
gkrellm_sys_mem_init(void)
{
    return TRUE;
}


/* ===================================================================== */
/* FS monitor interface */

#include <sys/mnttab.h>
#include <sys/vfstab.h>
#include <sys/statvfs.h>
#include <sys/cdio.h>

gboolean
gkrellm_sys_fs_fstab_modified(void)
{
    struct stat     s;
    static time_t   fstab_mtime;
    gint            modified = FALSE;

    if (stat("/etc/fstab", &s) == 0 && s.st_mtime != fstab_mtime)
        modified = TRUE;
    fstab_mtime = s.st_mtime;
    return modified;
}


void
gkrellm_sys_fs_get_fstab_list(){
    FILE *fp;
    struct vfstab vfsbuf;

    if ((fp = fopen(VFSTAB, "r")) == NULL)
        return;

    while (getvfsent(fp, &vfsbuf) == 0) {
        if (!vfsbuf.vfs_fstype || strcmp(vfsbuf.vfs_fstype, "ufs"))
            continue;
        gkrellm_fs_add_to_fstab_list(
                vfsbuf.vfs_mountp  ? vfsbuf.vfs_mountp  : "",
                vfsbuf.vfs_special ? vfsbuf.vfs_special : "",
                vfsbuf.vfs_fstype  ? vfsbuf.vfs_fstype  : "",
                vfsbuf.vfs_mntopts ? vfsbuf.vfs_mntopts : "");
    }

    fclose(fp);
}

void
gkrellm_sys_fs_get_mounts_list(void){
    FILE *fp;
    struct mnttab mntbuf;

    if ((fp = fopen(MNTTAB, "r")) == NULL)
        return;

    while (getmntent(fp, &mntbuf) == 0) {
        if (strcmp(mntbuf.mnt_fstype, "ufs") &&
                                  strcmp(mntbuf.mnt_fstype, "nfs"))
            continue;
        gkrellm_fs_add_to_mounts_list(
                mntbuf.mnt_mountp  ? mntbuf.mnt_mountp  : "",
                mntbuf.mnt_special ? mntbuf.mnt_special : "",
                mntbuf.mnt_fstype  ? mntbuf.mnt_fstype  : "");
    }
    fclose(fp);
}

void
gkrellm_sys_fs_get_fsusage(gpointer fs, gchar *dir){
    struct statvfs st;

    if (dir && statvfs(dir, &st) == 0) {
        gkrellm_fs_assign_fsusage_data(fs,
                    (gint64) st.f_blocks, (gint64) st.f_bavail,
                    (glong) st.f_bfree, (glong) st.f_bsize);
    } else {
        gkrellm_fs_assign_fsusage_data(fs, 0, 0, 0, 0);
    }
}

static void
eject_solaris_cdrom(gchar *device) {
#if defined(CDROMEJECT)
        gint    d;

        if ((d = open(device, O_RDONLY)) >= 0) {
                ioctl(d, CDROMEJECT);
                close(d);
        }
#endif
}

gboolean
gkrellm_sys_fs_init(void)
{
    gkrellm_fs_setup_eject(NULL, NULL, eject_solaris_cdrom, NULL);
    return TRUE;
}


/* ===================================================================== */
/* Battery monitor interface */
void
gkrellm_sys_battery_read_data(void)
{
}

gboolean
gkrellm_sys_battery_init(void)
{
      return FALSE;
}

/* ===================================================================== */
/* Uptime monitor interface */

#include <time.h>
#include <kstat.h>

time_t
gkrellm_sys_uptime_read_uptime(void)
{
    return (time_t) 0;  /* Will calculate using base_uptime */
}

gboolean
gkrellm_sys_uptime_init(void) {
    time_t      boot, now, base_uptime;

    extern kstat_ctl_t *kc;
    kstat_t *ksp;
    kstat_named_t *knp;

    boot = 0;

    if (kstat_chain_update(kc) == -1) {
        perror("kstat_chain_update");
        return FALSE;
    }
    ksp = kstat_lookup(kc, "unix", -1, "system_misc");
    if (ksp && kstat_read(kc, ksp, NULL) >= 0) {
        knp = (kstat_named_t *)kstat_data_lookup(ksp, "boot_time");
        if (knp) {
            boot = knp->value.ui32;
        }
    }
    if (time(&now) < 0)
        return FALSE;
    if (now <= boot)
        return FALSE;

    base_uptime = now - boot;
    base_uptime += 30;
    gkrellm_uptime_set_base_uptime(base_uptime);

    return (base_uptime == (time_t) 0) ? FALSE : TRUE;
}


/* ===================================================================== */
/* Sensor monitor interface */
/* (nonfunctional) */

gboolean
gkrellm_sys_sensors_get_power(gchar *path, gint id,
		gint iodev, gint interface, gfloat *power)
{
	return FALSE;
}

gboolean
gkrellm_sys_sensors_init(void)
{
    return FALSE;
}

gboolean
gkrellm_sys_sensors_get_temperature(gchar *name, gint id, gint iodev, gint inter, gfloat *t)
{
    return FALSE;
}

gboolean
gkrellm_sys_sensors_get_fan(gchar *name, gint id, gint iodev, gint inter, gfloat *t)
{
    return FALSE;
}

gboolean
gkrellm_sys_sensors_get_voltage(gchar *name, gint id, gint iodev, gint inter, gfloat *t)
{
    return FALSE;
}

