/*
 * Copyright (C) Tildeslash Ltd. All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3.
 *
 * This program 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 Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 *
 * You must obey the GNU Affero General Public License in all respects
 * for all of the code used other than OpenSSL.  
 */


#include "config.h"

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef TIME_WITH_SYS_TIME
#include <time.h>

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#else
#include <time.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef HAVE_ASM_PARAM_H
#include <asm/param.h>
#endif

#ifdef HAVE_GLOB_H
#include <glob.h>
#endif

#ifndef HZ
# define HZ sysconf(_SC_CLK_TCK)
#endif

#include "monit.h"
#include "process.h"
#include "process_sysdep.h"


/**
 *  System dependent resource gathering code for Linux.
 *
 *  @file
 */


/* ----------------------------------------------------------------- Private */


#define MEMTOTAL  "MemTotal:"
#define MEMFREE   "MemFree:"
#define MEMBUF    "Buffers:"
#define MEMCACHE  "Cached:"
#define SWAPTOTAL "SwapTotal:"
#define SWAPFREE  "SwapFree:"

#define NSEC_PER_SEC    1000000000L

static unsigned long long old_cpu_user     = 0;
static unsigned long long old_cpu_syst     = 0;
static unsigned long long old_cpu_wait     = 0;
static unsigned long long old_cpu_total    = 0;
static int                page_shift_to_kb = 0;


/**
 * Get system start time
 * @return seconds since unix epoch
 */
static time_t get_starttime() {
  char   buf[1024];
  double up = 0;

  if (! read_proc_file(buf, 1024, "uptime", -1, NULL)) {
    LogError("system statistic error -- cannot get system uptime\n");
    return 0;
  }

  if (sscanf(buf, "%lf", &up) != 1) {
    LogError("system statistic error -- invalid uptime\n");
    return 0;
  }

  return time(NULL) - (time_t)up;
}


/* ------------------------------------------------------------------ Public */


int init_process_info_sysdep(void) {
  char *ptr;
  char  buf[1024];
  long  page_size;
  int   page_shift;  

  if (! read_proc_file(buf, sizeof(buf), "meminfo", -1, NULL)) 
    return FALSE;
  if (! (ptr = strstr(buf, MEMTOTAL))) {
    DEBUG("system statistic error -- cannot get real memory amount\n");
    return FALSE;
  }
  if (sscanf(ptr+strlen(MEMTOTAL), "%ld", &systeminfo.mem_kbyte_max) != 1) {
    DEBUG("system statistic error -- cannot get real memory amount\n");
    return FALSE;
  }

  if ((systeminfo.cpus = sysconf(_SC_NPROCESSORS_CONF)) < 0) {
    DEBUG("system statistic error -- cannot get cpu count: %s\n", STRERROR);
    return FALSE;
  } else if (systeminfo.cpus == 0) {
    DEBUG("system reports cpu count 0, setting dummy cpu count 1\n");
    systeminfo.cpus = 1;
  }

  if ((page_size = sysconf(_SC_PAGESIZE)) <= 0) {
    DEBUG("system statistic error -- cannot get page size: %s\n", STRERROR);
    return FALSE;
  }

  for (page_shift = 0; page_size != 1; page_size >>= 1, page_shift++);
  page_shift_to_kb = page_shift - 10;

  return TRUE;
}


/**
 * Read all processes of the proc files system to initialize
 * the process tree (sysdep version... but should work for
 * all procfs based unices) 
 * @param reference  reference of ProcessTree
 * @return treesize>0 if succeeded otherwise =0.
 */
int initprocesstree_sysdep(ProcessTree_T ** reference) {
  int                 i = 0, j;
  int                 rv, bytes = 0;
  int                 treesize = 0;
  int                 stat_ppid = 0;
  char               *tmp = NULL;
  char                procname[STRLEN];
  char                buf[1024];
  char                stat_item_state;
  long                stat_item_cutime = 0;
  long                stat_item_cstime = 0;
  long                stat_item_rss = 0;
  glob_t              globbuf;
  unsigned long       stat_item_utime = 0;
  unsigned long       stat_item_stime = 0;
  unsigned long long  stat_item_starttime = 0ULL;
  ProcessTree_T      *pt = NULL;

  ASSERT(reference);

  /* Find all processes in the /proc directory */
  if ((rv = glob("/proc/[0-9]*", GLOB_ONLYDIR, NULL, &globbuf))) {
    LogError("system statistic error -- glob failed: %d (%s)\n", rv, STRERROR);
    return FALSE;
  } 

  treesize = globbuf.gl_pathc;

  pt = CALLOC(sizeof(ProcessTree_T), treesize);

  /* Insert data from /proc directory */
  for (i = 0; i < treesize; i++) {

    pt[i].pid = atoi(globbuf.gl_pathv[i] + strlen("/proc/"));

    if (!read_proc_file(buf, sizeof(buf), "stat", pt[i].pid, NULL)) {
      DEBUG("system statistic error -- cannot read /proc/%d/stat\n", pt[i].pid);
      continue;
    }

    pt[i].time = get_float_time();

    if (!(tmp = strrchr(buf, ')'))) {
      DEBUG("system statistic error -- file /proc/%d/stat parse error\n", pt[i].pid);
      continue;
    }
    *tmp = 0;
    if (sscanf(buf, "%*d (%256s", procname) != 1) {
      DEBUG("system statistic error -- file /proc/%d/stat process name parse error\n", pt[i].pid);
      continue;
    }

    tmp += 2;

    /* This implementation is done by using fs/procfs/array.c as a basis
     * it is also worth looking into the source of the procps utils */
    if (sscanf(tmp,
         "%c %d %*d %*d %*d %*d %*u %*u"
         "%*u %*u %*u %lu %lu %ld %ld %*d %*d %*d "
         "%*u %llu %*u %ld %*u %*u %*u %*u %*u "
         "%*u %*u %*u %*u %*u %*u %*u %*u %*d %*d\n",
         &stat_item_state,
         &stat_ppid,
         &stat_item_utime,
         &stat_item_stime,
         &stat_item_cutime,
         &stat_item_cstime,
         &stat_item_starttime,
         &stat_item_rss) != 8) {
      DEBUG("system statistic error -- file /proc/%d/stat parse error\n", pt[i].pid);
      continue;
    }

    pt[i].ppid      = stat_ppid;
    pt[i].starttime = get_starttime() + (time_t)(stat_item_starttime / HZ);

    /* jiffies -> seconds = 1 / HZ
     * HZ is defined in "asm/param.h"  and it is usually 1/100s but on
     * alpha system it is 1/1024s */
    pt[i].cputime     = ((float)(stat_item_utime + stat_item_stime) * 10.0) / HZ;
    pt[i].cpu_percent = 0;

    /* State is Zombie -> then we are a Zombie ... clear or? (-: */
    if (stat_item_state == 'Z')
      pt[i].status_flag |= PROCESS_ZOMBIE;

    if (page_shift_to_kb < 0)
      pt[i].mem_kbyte = (stat_item_rss >> abs(page_shift_to_kb));
    else
      pt[i].mem_kbyte = (stat_item_rss << abs(page_shift_to_kb));

    if (! read_proc_file(buf, sizeof(buf), "cmdline", pt[i].pid, &bytes)) {
      DEBUG("system statistic error -- cannot read /proc/%d/cmdline\n", pt[i].pid);
      continue;
    }
    /* The cmdline file contains argv elements/strings terminated separated by '\0' => join the string: */
    for (j = 0; j < (bytes - 1); j++)
      if (buf[j] == 0)
        buf[j] = ' ';
    pt[i].cmdline = *buf ? Str_dup(buf) : Str_dup(procname);
  }

  *reference = pt;
  globfree(&globbuf);

  return treesize;
}


/**
 * This routine returns 'nelem' double precision floats containing
 * the load averages in 'loadv'; at most 3 values will be returned.
 * @param loadv destination of the load averages
 * @param nelem number of averages
 * @return: 0 if successful, -1 if failed (and all load averages are 0).
 */
int getloadavg_sysdep(double *loadv, int nelem) {
#ifdef HAVE_GETLOADAVG
        return getloadavg(loadv, nelem);
#else
        char buf[STRLEN];
        double load[3];
        if (! read_proc_file(buf, sizeof(buf), "loadavg", -1, NULL))
                return -1;
        if (sscanf(buf, "%lf %lf %lf", &load[0], &load[1], &load[2]) != 3) {
                DEBUG("system statistic error -- cannot get load average\n");
                return -1;
        }
        for (int i = 0; i < nelem; i++)
                loadv[i] = load[i];
        return 0;
#endif
}


/**
 * This routine returns kbyte of real memory in use.
 * @return: TRUE if successful, FALSE if failed
 */
int used_system_memory_sysdep(SystemInfo_T *si) {
  char          *ptr;
  char           buf[1024];
  unsigned long  mem_free = 0UL;
  unsigned long  buffers = 0UL;
  unsigned long  cached = 0UL;
  unsigned long  swap_total = 0UL;
  unsigned long  swap_free = 0UL;

  if (! read_proc_file(buf, 1024, "meminfo", -1, NULL)) {
    LogError("system statistic error -- cannot get real memory free amount\n");
    goto error;
  }

  /* Memory */
  if (! (ptr = strstr(buf, MEMFREE)) || sscanf(ptr + strlen(MEMFREE), "%ld", &mem_free) != 1) {
    LogError("system statistic error -- cannot get real memory free amount\n");
    goto error;
  }
  if (! (ptr = strstr(buf, MEMBUF)) || sscanf(ptr + strlen(MEMBUF), "%ld", &buffers) != 1)
    DEBUG("system statistic error -- cannot get real memory buffers amount\n");
  if (! (ptr = strstr(buf, MEMCACHE)) || sscanf(ptr + strlen(MEMCACHE), "%ld", &cached) != 1)
    DEBUG("system statistic error -- cannot get real memory cache amount\n");
  si->total_mem_kbyte = systeminfo.mem_kbyte_max - mem_free - buffers - cached;

  /* Swap */
  if (! (ptr = strstr(buf, SWAPTOTAL)) || sscanf(ptr + strlen(SWAPTOTAL), "%ld", &swap_total) != 1) {
    LogError("system statistic error -- cannot get swap total amount\n");
    goto error;
  }
  if (! (ptr = strstr(buf, SWAPFREE)) || sscanf(ptr + strlen(SWAPFREE), "%ld", &swap_free) != 1) {
    LogError("system statistic error -- cannot get swap free amount\n");
    goto error;
  }
  si->swap_kbyte_max   = swap_total;
  si->total_swap_kbyte = swap_total - swap_free;

  return TRUE;

  error:
  si->total_mem_kbyte = 0;
  si->swap_kbyte_max = 0;
  return FALSE;
}


/**
 * This routine returns system/user CPU time in use.
 * @return: TRUE if successful, FALSE if failed (or not available)
 */
int used_system_cpu_sysdep(SystemInfo_T *si) {
  int                rv;
  unsigned long long cpu_total;
  unsigned long long cpu_user;
  unsigned long long cpu_nice;
  unsigned long long cpu_syst;
  unsigned long long cpu_idle;
  unsigned long long cpu_wait;
  unsigned long long cpu_irq;
  unsigned long long cpu_softirq;
  char               buf[1024];

  if (!read_proc_file(buf, 1024, "stat", -1, NULL)) {
    LogError("system statistic error -- cannot read /proc/stat\n");
    goto error;
  }

  rv = sscanf(buf, "cpu %llu %llu %llu %llu %llu %llu %llu",
         &cpu_user,
         &cpu_nice,
         &cpu_syst,
         &cpu_idle,
         &cpu_wait,
         &cpu_irq,
         &cpu_softirq);
  if (rv < 4) {
    LogError("system statistic error -- cannot read cpu usage\n");
    goto error;
  } else if (rv == 4) {
    /* linux 2.4.x doesn't support these values */
    cpu_wait    = 0;
    cpu_irq     = 0;
    cpu_softirq = 0;
  }

  cpu_total = cpu_user + cpu_nice + cpu_syst + cpu_idle + cpu_wait + cpu_irq + cpu_softirq;
  cpu_user  = cpu_user + cpu_nice;

  if (old_cpu_total == 0) {
    si->total_cpu_user_percent = -10;
    si->total_cpu_syst_percent = -10;
    si->total_cpu_wait_percent = -10;
  } else {
    unsigned long long delta = cpu_total - old_cpu_total;

    si->total_cpu_user_percent = (int)(1000 * (double)(cpu_user - old_cpu_user) / delta);
    si->total_cpu_syst_percent = (int)(1000 * (double)(cpu_syst - old_cpu_syst) / delta);
    si->total_cpu_wait_percent = (int)(1000 * (double)(cpu_wait - old_cpu_wait) / delta);
  }

  old_cpu_user  = cpu_user;
  old_cpu_syst  = cpu_syst;
  old_cpu_wait  = cpu_wait;
  old_cpu_total = cpu_total;
  return TRUE;

  error:
  si->total_cpu_user_percent = 0;
  si->total_cpu_syst_percent = 0;
  si->total_cpu_wait_percent = 0;
  return FALSE;
}


