/*
 * libprocps - Library to read proc filesystem
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/stat.h>
#include <sys/types.h>

#include <proc/readstat.h>
#include "procps-private.h"

#define STAT_FILE "/proc/stat"

struct stat_data {
    struct procps_jiffs cpu;
    unsigned int intr;
    unsigned int ctxt;
    unsigned int btime;
    unsigned int procs;
    unsigned int procs_blocked;
    unsigned int procs_running;
};

struct procps_jiffs_private {
    struct procps_jiffs_hist cpu;
    // future additions go here, please
};

struct procps_stat {
    int refcount;
    int stat_fd;
    struct stat_data data;
    int jiff_hists_alloc;
    int jiff_hists_inuse;
    struct procps_jiffs_private cpu_summary;
    struct procps_jiffs_private *jiff_hists;
};


/*
 * procps_stat_new:
 *
 * Create a new container to hold the stat information
 *
 * The initial refcount is 1, and needs to be decremented
 * to release the resources of the structure.
 *
 * Returns: a new stat info container
 */
PROCPS_EXPORT int procps_stat_new (
        struct procps_stat **info)
{
    struct procps_stat *v;
    v = calloc(1, sizeof(struct procps_stat));
    if (!v)
        return -ENOMEM;

    v->refcount = 1;
    v->stat_fd = -1;
/*  v->jiff_hists_alloc = 0;   unnecessary with calloc,  */
/*  v->jiff_hists_inuse = 0;   but serves as a reminder  */
    *info = v;
    return 0;
}

/*
 * procps_stat_read:
 *
 * Read the data out of /proc/stat putting the information
 * into the supplied info structure.
 *
 * If CPU stats only needed, set cpu_only to non-zero
 */
PROCPS_EXPORT int procps_stat_read (
        struct procps_stat *info,
        const int cpu_only)
{
    char buf[8192];
    char *head, *tail;
    int size;

    if (info == NULL)
        return -EINVAL;

    memset(&(info->data), 0, sizeof(struct stat_data));
    /* read in the data */

    if (-1 == info->stat_fd && (info->stat_fd = open(STAT_FILE, O_RDONLY)) == -1) {
        return -errno;
    }
    if (lseek(info->stat_fd, 0L, SEEK_SET) == -1) {
        return -errno;
    }
    for (;;) {
        if ((size = read(info->stat_fd, buf, sizeof(buf)-1)) < 0) {
            if (errno == EINTR || errno == EAGAIN)
                continue;
            return -errno;
        }
        break;
    }
    if (size == 0)
        return 0;
    buf[size] = '\0';

    /* Scan the file */
    head = buf;
    do {
        tail = strchr(head, ' ');
        if (!tail)
            break;
        *tail = '\0';
        if (0 == strcmp(head, "cpu")) {
            if (sscanf(tail+1, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
                &(info->data.cpu.user),
                &(info->data.cpu.nice),
                &(info->data.cpu.system),
                &(info->data.cpu.idle),
                &(info->data.cpu.iowait),
                &(info->data.cpu.irq),
                &(info->data.cpu.sirq),
                &(info->data.cpu.stolen),
                &(info->data.cpu.guest),
                &(info->data.cpu.nice)
                ) != 10)
                return -1;

            if (cpu_only)
                return 0; // we got what we need
        } else if (0 == strcmp(head, "intr")) {
            info->data.intr = strtoul(tail+1, &tail, 10);
        } else if (0 == strcmp(head, "ctxt")) {
            info->data.ctxt = strtoul(tail+1, &tail, 10);
        } else if (0 == strcmp(head, "btime")) {
            info->data.btime = strtoul(tail+1, &tail, 10);
        } else if (0 == strcmp(head, "processes")) {
            info->data.procs = strtoul(tail+1, &tail, 10);
        } else if (0 == strcmp(head, "procs_blocked")) {
            info->data.procs_blocked = strtoul(tail+1, &tail, 10);
        } else if (0 == strcmp(head, "procs_running")) {
            info->data.procs_running = strtoul(tail+1, &tail, 10);
        }
        if (tail[0] != '\n')
            tail = strchr(tail+1, '\n');
        if (!tail)
            break;
        head = tail + 1;
    } while (tail);
    if (info->data.procs)
        info->data.procs--; // exclude this process
    return 0;
}

PROCPS_EXPORT int procps_stat_ref (
        struct procps_stat *info)
{
    if (info == NULL)
        return -EINVAL;
    info->refcount++;
    return info->refcount;
}

PROCPS_EXPORT int procps_stat_unref (
        struct procps_stat **info)
{
    if (info == NULL || *info == NULL)
        return -EINVAL;
    (*info)->refcount--;
    if ((*info)->refcount == 0) {
        if ((*info)->jiff_hists != NULL)
            free((*info)->jiff_hists);
        free(*info);
        *info = NULL;
        return 0;
    }
    return (*info)->refcount;
}

PROCPS_EXPORT jiff procps_stat_cpu_get (
        struct procps_stat *info,
        enum procps_cpu_item item)
{
    switch (item) {
        case PROCPS_CPU_USER:
            return info->data.cpu.user;
        case PROCPS_CPU_NICE:
            return info->data.cpu.nice;
        case PROCPS_CPU_SYSTEM:
            return info->data.cpu.system;
        case PROCPS_CPU_IDLE:
            return info->data.cpu.idle;
        case PROCPS_CPU_IOWAIT:
            return info->data.cpu.iowait;
        case PROCPS_CPU_IRQ:
            return info->data.cpu.irq;
        case PROCPS_CPU_SIRQ:
            return info->data.cpu.sirq;
        case PROCPS_CPU_STOLEN:
            return info->data.cpu.stolen;
        case PROCPS_CPU_GUEST:
            return info->data.cpu.guest;
        case PROCPS_CPU_GNICE:
            return info->data.cpu.gnice;
        default:
            return 0;
    }
}

PROCPS_EXPORT int procps_stat_cpu_getstack (
        struct procps_stat *info,
        struct stat_result *these)
{
    if (these == NULL)
        return -EINVAL;

    for (;;) {
        switch (these->item) {
            case PROCPS_CPU_USER:
                these->result.jiff = info->data.cpu.user;
                break;
            case PROCPS_CPU_NICE:
                these->result.jiff = info->data.cpu.nice;
                break;
            case PROCPS_CPU_SYSTEM:
                these->result.jiff = info->data.cpu.system;
                break;
            case PROCPS_CPU_IDLE:
                these->result.jiff = info->data.cpu.idle;
                break;
            case PROCPS_CPU_IOWAIT:
                these->result.jiff = info->data.cpu.iowait;
                break;
            case PROCPS_CPU_IRQ:
                these->result.jiff = info->data.cpu.irq;
                break;
            case PROCPS_CPU_SIRQ:
                these->result.jiff = info->data.cpu.sirq;
                break;
            case PROCPS_CPU_STOLEN:
                these->result.jiff = info->data.cpu.stolen;
                break;
            case PROCPS_CPU_GUEST:
                these->result.jiff = info->data.cpu.guest;
                break;
            case PROCPS_CPU_GNICE:
                these->result.jiff = info->data.cpu.gnice;
                break;
            case PROCPS_CPU_noop:
                // don't disturb potential user data in the result struct
                break;
            case PROCPS_CPU_stack_end:
                return 0;
            default:
                return -EINVAL;
        }
        ++these;
    }
}

PROCPS_EXPORT unsigned int procps_stat_sys_get (
        struct procps_stat *info,
        enum procps_stat_item item)
{
    switch (item) {
        case PROCPS_STAT_INTR:
            return info->data.intr;
        case PROCPS_STAT_CTXT:
            return info->data.ctxt;
        case PROCPS_STAT_BTIME:
            return info->data.btime;
        case PROCPS_STAT_PROCS:
            return info->data.procs;
        case PROCPS_STAT_PROCS_BLK:
            return info->data.procs_blocked;
        case PROCPS_STAT_PROCS_RUN:
            return info->data.procs_running;
        default:
            return 0;
    }
}

PROCPS_EXPORT int procps_stat_sys_getstack (
        struct procps_stat *info,
        struct stat_result *these)
{
    if (these == NULL)
        return -EINVAL;

    for (;;) {
        switch (these->item) {
            case PROCPS_STAT_INTR:
                these->result.u_int = info->data.intr;
                break;
            case PROCPS_STAT_CTXT:
                these->result.u_int = info->data.ctxt;
                break;
            case PROCPS_STAT_BTIME:
                these->result.u_int = info->data.btime;
                break;
            case PROCPS_STAT_PROCS:
                these->result.u_int = info->data.procs;
                break;
            case PROCPS_STAT_PROCS_BLK:
                these->result.u_int = info->data.procs_blocked;
                break;
            case PROCPS_STAT_PROCS_RUN:
                these->result.u_int = info->data.procs_running;
                break;
            case PROCPS_STAT_noop:
                // don't disturb potential user data in the result struct
                break;
            case PROCPS_STAT_stack_end:
                return 0;
            default:
                return -EINVAL;
        }
        ++these;
    }
}

/*
 * procps_stat_read_jiffs:
 *
 * Read the cpu data out of /proc/stat putting the information
 * into the dynamically acquired 'jiff_hists' hanging off the
 * info structure.  Along the way we gather historical stats and
 * and embed the cpu summary line in the info structure as well
 * ( since we had to read that first /proc/stat line anyway ).
 *
 * This is all private information but can be copied to caller
 * supplied structures upon request.
 */
PROCPS_EXPORT int procps_stat_read_jiffs (
        struct procps_stat *info)
{
  #define ALLOCincr 32
    struct procps_jiffs_private *sum_ptr, *cpu_ptr;
    char buf[8192], *bp;
    int i, rc, size;

    if (info == NULL)
        return -EINVAL;

    if (!info->jiff_hists_alloc) {
        info->jiff_hists = calloc(ALLOCincr, sizeof(struct procps_jiffs_private));
        if (!(info->jiff_hists))
            return -ENOMEM;
        info->jiff_hists_alloc = ALLOCincr;
        info->jiff_hists_inuse = 0;
    }

    if (-1 == info->stat_fd && (info->stat_fd = open(STAT_FILE, O_RDONLY)) == -1)
        return -errno;
    if (lseek(info->stat_fd, 0L, SEEK_SET) == -1)
        return -errno;
    for (;;) {
        if ((size = read(info->stat_fd, buf, sizeof(buf)-1)) < 0) {
            if (errno == EINTR || errno == EAGAIN)
                continue;
            return -errno;
        }
        break;
    }
    if (size == 0)
        return 0;
    buf[size] = '\0';
    bp = buf;

    sum_ptr = &info->cpu_summary;
    // remember from last time around
    memcpy(&sum_ptr->cpu.old, &sum_ptr->cpu.new, sizeof(struct procps_jiffs));
    // then value the summary line
    if (8 > sscanf(bp, "cpu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu"
        , &sum_ptr->cpu.new.user,  &sum_ptr->cpu.new.nice,   &sum_ptr->cpu.new.system
        , &sum_ptr->cpu.new.idle,  &sum_ptr->cpu.new.iowait, &sum_ptr->cpu.new.irq
        , &sum_ptr->cpu.new.sirq,  &sum_ptr->cpu.new.stolen
        , &sum_ptr->cpu.new.guest, &sum_ptr->cpu.new.gnice))
            return -1;
    sum_ptr->cpu.id = -1;              // mark as summary

    i = 0;
reap_em_again:
    cpu_ptr = info->jiff_hists + i;    // adapt to relocated if reap_em_again
    do {
        bp = 1 + strchr(bp, '\n');
        // remember from last time around
        memcpy(&cpu_ptr->cpu.old, &cpu_ptr->cpu.new, sizeof(struct procps_jiffs));
        if (8 > (rc = sscanf(bp, "cpu%d %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu"
            , &cpu_ptr->cpu.id
            , &cpu_ptr->cpu.new.user,  &cpu_ptr->cpu.new.nice,   &cpu_ptr->cpu.new.system
            , &cpu_ptr->cpu.new.idle,  &cpu_ptr->cpu.new.iowait, &cpu_ptr->cpu.new.irq
            , &cpu_ptr->cpu.new.sirq,  &cpu_ptr->cpu.new.stolen
            , &cpu_ptr->cpu.new.guest, &cpu_ptr->cpu.new.gnice))) {
                memmove(cpu_ptr, sum_ptr, sizeof(struct procps_jiffs_hist));
                break;                 // we must tolerate cpus taken offline
        }
        ++i;
        ++cpu_ptr;
    } while (i < info->jiff_hists_alloc);

    if (i == info->jiff_hists_alloc && rc >= 8) {
        info->jiff_hists_alloc += ALLOCincr;
        info->jiff_hists = realloc(info->jiff_hists, info->jiff_hists_alloc * sizeof(struct procps_jiffs_private));
        if (!(info->jiff_hists))
            return -ENOMEM;
        goto reap_em_again;
    }

    info->jiff_hists_inuse = i;
    return i;
  #undef ALLOCincr
}

/*
 * procps_stat_jiffs_get:
 *
 * Return the designated cpu data in the caller supplied structure.
 * A negative 'which' denotes the cpu_summary, not a real cpu.
 *
 * This function deals only with the 'current' jiffs counts.
 */
PROCPS_EXPORT int procps_stat_jiffs_get (
        struct procps_stat *info,
        struct procps_jiffs *dest,
        int which)
{
    struct procps_jiffs_private *p;
    int i;

    if (info == NULL || dest == NULL)
        return -EINVAL;
    if (which < 0) {
        // note, we're just copying the 'new' portion of our procps_jiffs_private
        memcpy(dest, &info->cpu_summary, sizeof(struct procps_jiffs));
        return 0;
    }
    p = info->jiff_hists;
    for (i = 0; i < info->jiff_hists_inuse; i++) {
        if (p->cpu.id == which) {
            // note, we're just copying the 'new' portion of our procps_jiffs_private
            memcpy(dest, p, sizeof(struct procps_jiffs));
            return 0;
        }
        ++p;
    }
    return -1;
}

/*
 * procps_stat_jiffs_fill:
 *
 * Refresh available cpu data, then return all cpu data in the caller
 * supplied structures, up to the lesser of maxdests or total available.
 *
 * We tolerate a maxdests greater than the total available, and
 * the caller had better tolerate fewer returned than requested.
 *
 * This function deals only with the 'current' jiffs counts.
 */
PROCPS_EXPORT int procps_stat_jiffs_fill (
        struct procps_stat *info,
        struct procps_jiffs *dests,
        int maxdests)
{
    int i, rc;

    if (info == NULL || dests == NULL)
        return -EINVAL;
    if ((rc = procps_stat_read_jiffs(info)) < 0)
        return rc;
    if (!info->jiff_hists_inuse)
        return -1;

    for (i = 0; i < info->jiff_hists_inuse && i < maxdests; i++) {
        // note, we're just copying the 'new' portion of our procps_jiffs_private
        memcpy(dests + i, info->jiff_hists + i, sizeof(struct procps_jiffs));
    }
    return i;
}

/*
 * procps_stat_jiffs_hist_get:
 *
 * Return the designated cpu data in the caller supplied structure.
 * A negative 'which' denotes the cpu_summary, not a real cpu.
 *
 * This function provides both 'new' and 'old' jiffs counts.
 */
PROCPS_EXPORT int procps_stat_jiffs_hist_get (
        struct procps_stat *info,
        struct procps_jiffs_hist *dest,
        int which)
{
    struct procps_jiffs_private *p;
    int i;

    if (info == NULL || dest == NULL)
        return -EINVAL;
    if (which < 0) {
        memcpy(dest, &info->cpu_summary, sizeof(struct procps_jiffs_hist));
        return 0;
    }
    p = info->jiff_hists;
    for (i = 0; i < info->jiff_hists_inuse; i++) {
        if (p->cpu.id == which) {
            memcpy(dest, p, sizeof(struct procps_jiffs_hist));
            return 0;
        }
        ++p;
    }
    return -1;
}

/*
 * procps_stat_jiffs_hist_fill:
 *
 * Refresh available cpu data, then return all cpu data in the caller
 * supplied structures, up to the lesser of maxdests or total available.
 *
 * We tolerate a maxdests greater than the total available, and
 * the caller had better tolerate fewer returned than requested.
 *
 * This function provides both 'new' and 'old' jiffs counts.
 */
PROCPS_EXPORT int procps_stat_jiffs_hist_fill (
        struct procps_stat *info,
        struct procps_jiffs_hist *dests,
        int maxdests)
{
    int i, rc;

    if (info == NULL || dests == NULL)
        return -EINVAL;
    if ((rc = procps_stat_read_jiffs(info)) < 0)
        return rc;
    if (!info->jiff_hists_inuse)
        return -1;

    for (i = 0; i < info->jiff_hists_inuse && i < maxdests; i++) {
        memcpy(dests + i, info->jiff_hists + i, sizeof(struct procps_jiffs_hist));
    }
    return i;
}