/*
 * 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_ERRNO_H
#include <errno.h>
#endif

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

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

#ifdef HAVE_KVM_H
#include <kvm.h>
#endif

#ifdef HAVE_SYS_SYSCTL_H
#include <sys/sysctl.h>
#endif

#ifdef HAVE_SYS_VMMETER_H
#include <sys/vmmeter.h>
#endif

#ifdef HAVE_MACH_MACH_H
#include <mach/mach.h>
#endif

#ifdef HAVE_MACH_HOST_INFO_H
#include <mach/host_info.h>
#endif

#ifdef HAVE_MACH_MACH_HOST_H
#include <mach/mach_host.h>
#endif


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


/**
 *  System dependent resource gathering code for MacOS X.
 *
 *  @file
 */


#define ARGSSIZE 8192


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


static int  hz;
static int  pagesize_kbyte;
static long total_old    = 0;
static long cpu_user_old = 0;
static long cpu_syst_old = 0;


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


int init_process_info_sysdep(void) {
  int              mib[2];
  size_t           len;
  struct clockinfo clock;
  uint64_t         memsize;

  mib[0] = CTL_KERN;
  mib[1] = KERN_CLOCKRATE;
  len    = sizeof(clock);
  if (sysctl(mib, 2, &clock, &len, NULL, 0) == -1) {
    DEBUG("system statistic error -- cannot get clock rate: %s\n", STRERROR);
    return FALSE;
  }
  hz     = clock.hz;

  mib[0] = CTL_HW;
  mib[1] = HW_NCPU;
  len    = sizeof(systeminfo.cpus);
  if (sysctl(mib, 2, &systeminfo.cpus, &len, NULL, 0) == -1) {
    DEBUG("system statistic error -- cannot get cpu count: %s\n", STRERROR);
    return FALSE;
  }

  mib[1]  = HW_MEMSIZE;
  len     = sizeof(memsize);
  memsize = 0L;
  if (sysctl(mib, 2, &memsize, &len, NULL, 0 ) == -1) {
    DEBUG("system statistic error -- cannot get real memory amount: %s\n", STRERROR);
    return FALSE;
  }
  systeminfo.mem_kbyte_max = (memsize / 1024);

  mib[1] = HW_PAGESIZE;
  len    = sizeof(pagesize_kbyte);
  if (sysctl(mib, 2, &pagesize_kbyte, &len, NULL, 0) == -1) {
    DEBUG("system statistic error -- cannot get memory page size: %s\n", STRERROR);
    return FALSE;
  }
  pagesize_kbyte /= 1024;

  return TRUE;
}


/**
 * Read all processes to initialize the information tree.
 * @param reference  reference of ProcessTree
 * @return treesize>0 if succeeded otherwise =0.
 */
int initprocesstree_sysdep(ProcessTree_T **reference) {
  int                i;
  size_t             treesize;
  mach_port_t        mytask = mach_task_self();
  ProcessTree_T     *pt;
  struct kinfo_proc *pinfo;
  size_t             pinfo_size = 0;
  char              *args;
  size_t             args_size = 0;
  size_t             size;
  int                mib[4];

  mib[0] = CTL_KERN;
  mib[1] = KERN_PROC;
  mib[2] = KERN_PROC_ALL;
  mib[3] = 0;
  if (sysctl(mib, 4, NULL, &pinfo_size, NULL, 0) < 0) {
    LogError("system statistic error -- sysctl failed: %s\n", STRERROR);
    return FALSE;
  }
  pinfo = CALLOC(1, pinfo_size);
  if (sysctl(mib, 4, pinfo, &pinfo_size, NULL, 0)) {
    FREE(pinfo);
    LogError("system statistic error -- sysctl failed: %s\n", STRERROR);
    return FALSE;
  }
  treesize = pinfo_size / sizeof(struct kinfo_proc);
  pt = CALLOC(sizeof(ProcessTree_T), treesize);

  mib[0] = CTL_KERN;
  mib[1] = KERN_ARGMAX;
  size = sizeof(args_size);
  if (sysctl(mib, 2, &args_size, &size, NULL, 0) == -1) {
    FREE(pinfo);
    FREE(pt);
    LogError("system statistic error -- sysctl failed: %s\n", STRERROR);
    return FALSE;
  }
  args = CALLOC(1, args_size + 1);
  size = args_size; // save for per-process sysctl loop

  for (i = 0; i < treesize; i++) {
    mach_port_t task;

    pt[i].pid       = pinfo[i].kp_proc.p_pid;
    pt[i].ppid      = pinfo[i].kp_eproc.e_ppid;
    pt[i].starttime = pinfo[i].kp_proc.p_starttime.tv_sec;

    args_size = size;
    mib[0] = CTL_KERN;
    mib[1] = KERN_PROCARGS2;
    mib[2] = pt[i].pid;
    if (sysctl(mib, 3, args, &args_size, NULL, 0) != -1) {
      /* KERN_PROCARGS2 sysctl() returns following pseudo structure:
       *        struct {
       *                int argc
       *                char execname[];
       *                char argv[argc][];
       *                char env[][];
       *        }
       * The strings are terminated with '\0' and may have variable '\0' padding
       */
      int  argc = *args;
      char *p = args + sizeof(int); // arguments beginning
      StringBuffer_T cmdline = StringBuffer_create(64);
      p += strlen(p); // skip exename
      while (argc && p < args + args_size) {
        if (*p == 0) { // skip terminating 0 and variable length 0 padding
          p++;
          continue;
        }
        StringBuffer_append(cmdline, argc-- ? "%s " : "%s", p);
        p += strlen(p);
      }
      if (StringBuffer_length(cmdline))
        pt[i].cmdline = Str_dup(StringBuffer_toString(StringBuffer_trim(cmdline)));
      StringBuffer_free(&cmdline);
    }
    if (! pt[i].cmdline || ! *pt[i].cmdline) {
      FREE(pt[i].cmdline);
      pt[i].cmdline = Str_dup(pinfo[i].kp_proc.p_comm);
    }

    if (pinfo[i].kp_proc.p_stat == SZOMB)
      pt[i].status_flag |= PROCESS_ZOMBIE;
    pt[i].time = get_float_time();

    if (task_for_pid(mytask, pt[i].pid, &task) == KERN_SUCCESS) {
      mach_msg_type_number_t   count;
      task_basic_info_data_t   taskinfo;
      thread_array_t           threadtable;
      unsigned int             threadtable_size;
      thread_basic_info_t      threadinfo;
      thread_basic_info_data_t threadinfo_data;

      count = TASK_BASIC_INFO_COUNT;
      if (task_info(task, TASK_BASIC_INFO, (task_info_t)&taskinfo, &count) == KERN_SUCCESS) {
        pt[i].mem_kbyte   = (unsigned long)(taskinfo.resident_size / 1024);
        pt[i].cputime     = (long)((taskinfo.user_time.seconds + taskinfo.system_time.seconds) * 10 + (taskinfo.user_time.microseconds + taskinfo.system_time.microseconds) / 100000);
        pt[i].cpu_percent = 0;
      }
      if (task_threads(task, &threadtable, &threadtable_size) == KERN_SUCCESS) {
        int j;

        threadinfo = &threadinfo_data;
        for (j = 0; j < threadtable_size; j++) {
          count = THREAD_BASIC_INFO_COUNT;
          if (thread_info(threadtable[j], THREAD_BASIC_INFO, (thread_info_t)threadinfo, &count) == KERN_SUCCESS) {
            if ((threadinfo->flags & TH_FLAGS_IDLE) == 0) {
              pt[i].cputime += (long)((threadinfo->user_time.seconds + threadinfo->system_time.seconds) * 10 + (threadinfo->user_time.microseconds + threadinfo->system_time.microseconds) / 100000);
              pt[i].cpu_percent = 0;
            }
          }
          mach_port_deallocate(mytask, threadtable[j]);
        }
        vm_deallocate(mytask, (vm_address_t)threadtable,threadtable_size * sizeof(thread_act_t));
      }
      mach_port_deallocate(mytask, task); 	
    }
  }
  FREE(args);
  FREE(pinfo);

  *reference = pt;

  return (int)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) {
  return getloadavg(loadv, nelem);
}


/**
 * This routine returns kbyte of real memory in use.
 * @return: TRUE if successful, FALSE if failed (or not available)
 */
int used_system_memory_sysdep(SystemInfo_T *si) {
  int                    mib[2];
  unsigned int           pw, pa, pi;
  size_t                 len;
  struct xsw_usage       swap;
  kern_return_t          kret;
  vm_statistics_data_t   page_info;
  mach_msg_type_number_t count;

  /* Memory */
  count = HOST_VM_INFO_COUNT;
  kret  = host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&page_info, &count);
  if (kret != KERN_SUCCESS) {
    DEBUG("system statistic error -- cannot get memory usage\n");
    return FALSE;
  }
  pw = page_info.wire_count * pagesize_kbyte;
  pa = page_info.active_count * pagesize_kbyte;
  pi = page_info.inactive_count * pagesize_kbyte;
  si->total_mem_kbyte = pw + pa + pi;

  /* Swap */
  mib[0] = CTL_VM;
  mib[1] = VM_SWAPUSAGE;
  len    = sizeof(struct xsw_usage);
  if (sysctl(mib, 2, &swap, &len, NULL, 0) == -1) {
    DEBUG("system statistic error -- cannot get swap usage: %s\n", STRERROR);
    si->swap_kbyte_max = 0;
    return FALSE;
  }
  si->swap_kbyte_max   = (unsigned long)(double)(swap.xsu_total) / 1024.;
  si->total_swap_kbyte = (unsigned long)(double)(swap.xsu_used) / 1024.;

  return TRUE;
}


/**
 * This routine returns system/user CPU time in use.
 * @return: TRUE if successful, FALSE if failed
 */
int used_system_cpu_sysdep(SystemInfo_T *si) {
  int                       i;
  long                      total;
  long                      total_new = 0;
  kern_return_t             kret;
  host_cpu_load_info_data_t cpu_info;
  mach_msg_type_number_t    count;

  count = HOST_CPU_LOAD_INFO_COUNT;
  kret  = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&cpu_info, &count);
  if (kret == KERN_SUCCESS) {
    for (i = 0; i < CPU_STATE_MAX; i++)
      total_new += cpu_info.cpu_ticks[i];
    total     = total_new - total_old;
    total_old = total_new;

    si->total_cpu_user_percent = (total > 0)?(int)(1000 * (double)(cpu_info.cpu_ticks[CPU_STATE_USER] - cpu_user_old) / total):-10;
    si->total_cpu_syst_percent = (total > 0)?(int)(1000 * (double)(cpu_info.cpu_ticks[CPU_STATE_SYSTEM] - cpu_syst_old) / total):-10;
    si->total_cpu_wait_percent = 0; /* there is no wait statistic available */

    cpu_user_old = cpu_info.cpu_ticks[CPU_STATE_USER];
    cpu_syst_old = cpu_info.cpu_ticks[CPU_STATE_SYSTEM];

    return TRUE;
  }
  return FALSE;
}

