/*
htop - SolarisProcessTable.c
(C) 2014 Hisham H. Muhammad
(C) 2017,2018 Guy M. Broome
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/

#include "config.h" // IWYU pragma: keep

#include "solaris/SolarisProcessTable.h"

#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/user.h>
#include <limits.h>
#include <string.h>
#include <procfs.h>
#include <errno.h>
#include <pwd.h>
#include <math.h>
#include <time.h>

#include "CRT.h"
#include "solaris/Platform.h"
#include "solaris/SolarisMachine.h"
#include "solaris/SolarisProcess.h"


#define GZONE "global    "
#define UZONE "unknown   "

static char* SolarisProcessTable_readZoneName(kstat_ctl_t* kd, SolarisProcess* sproc) {
   char* zname;

   if ( sproc->zoneid == 0 ) {
      zname = xStrdup(GZONE);
   } else if ( kd == NULL ) {
      zname = xStrdup(UZONE);
   } else {
      kstat_t* ks = kstat_lookup_wrapper( kd, "zones", sproc->zoneid, NULL );
      zname = xStrdup(ks == NULL ? UZONE : ks->ks_name);
   }

   return zname;
}

ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {
   SolarisProcessTable* this = xCalloc(1, sizeof(SolarisProcessTable));
   Object_setClass(this, Class(ProcessTable));

   ProcessTable* super = &this->super;
   ProcessTable_init(super, Class(SolarisProcess), host, pidMatchList);

   return super;
}

void ProcessTable_delete(Object* cast) {
   SolarisProcessTable* this = (SolarisProcessTable*) cast;
   ProcessTable_done(&this->super);
   free(this);
}

static void SolarisProcessTable_updateExe(pid_t pid, Process* proc) {
   char path[32];
   xSnprintf(path, sizeof(path), "/proc/%d/path/a.out", pid);

   char target[PATH_MAX];
   ssize_t ret = readlink(path, target, sizeof(target) - 1);
   if (ret <= 0)
      return;

   target[ret] = '\0';
   Process_updateExe(proc, target);
}

static void SolarisProcessTable_updateCwd(pid_t pid, Process* proc) {
   char path[32];
   xSnprintf(path, sizeof(path), "/proc/%d/cwd", pid);

   char target[PATH_MAX];
   ssize_t ret = readlink(path, target, sizeof(target) - 1);
   if (ret <= 0)
      return;

   target[ret] = '\0';
   free_and_xStrdup(&proc->procCwd, target);
}

/* Taken from: https://docs.oracle.com/cd/E19253-01/817-6223/6mlkidlom/index.html#tbl-sched-state */
static inline ProcessState SolarisProcessTable_getProcessState(char state) {
   switch (state) {
      case 'S': return SLEEPING;
      case 'R': return RUNNABLE;
      case 'O': return RUNNING;
      case 'Z': return ZOMBIE;
      case 'T': return STOPPED;
      case 'I': return IDLE;
      default: return UNKNOWN;
   }
}

/* NOTE: the following is a callback function of type proc_walk_f
 *       and MUST conform to the appropriate definition in order
 *       to work.  See libproc(3LIB) on a Solaris or Illumos
 *       system for more info.
 */

static int SolarisProcessTable_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void* listptr) {
   bool preExisting;
   pid_t getpid;

   // Setup process list
   ProcessTable* pt = (ProcessTable*) listptr;
   const Machine* host = pt->super.host;
   const SolarisMachine* shost = (const SolarisMachine*) host;

   id_t lwpid_real = _lwpsinfo->pr_lwpid;
   if (lwpid_real > 1023) {
      return 0;
   }

   pid_t lwpid   = (_psinfo->pr_pid * 1024) + lwpid_real;
   bool onMasterLWP = (_lwpsinfo->pr_lwpid == _psinfo->pr_lwp.pr_lwpid);
   if (onMasterLWP) {
      getpid = _psinfo->pr_pid * 1024;
   } else {
      getpid = lwpid;
   }

   Process* proc            = ProcessTable_getProcess(pt, getpid, &preExisting, SolarisProcess_new);
   SolarisProcess* sproc    = (SolarisProcess*) proc;
   const Settings* settings = host->settings;

   // Common code pass 1
   proc->super.show         = false;
   sproc->taskid            = _psinfo->pr_taskid;
   sproc->projid            = _psinfo->pr_projid;
   sproc->poolid            = _psinfo->pr_poolid;
   sproc->contid            = _psinfo->pr_contract;
   proc->priority           = _lwpsinfo->pr_pri;
   proc->nice               = _lwpsinfo->pr_nice - NZERO;
   proc->processor          = _lwpsinfo->pr_onpro;
   proc->state              = SolarisProcessTable_getProcessState(_lwpsinfo->pr_sname);
   // NOTE: This 'percentage' is a 16-bit BINARY FRACTIONS where 1.0 = 0x8000
   // Source: https://docs.oracle.com/cd/E19253-01/816-5174/proc-4/index.html
   // (accessed on 18 November 2017)
   proc->percent_mem        = ((uint16_t)_psinfo->pr_pctmem / (double)32768) * (double)100.0;
   proc->pgrp               = _psinfo->pr_pgid;
   proc->nlwp               = _psinfo->pr_nlwp;
   proc->session            = _psinfo->pr_sid;

   proc->tty_nr             = _psinfo->pr_ttydev;
   const char* name = (_psinfo->pr_ttydev != PRNODEV) ? ttyname(_psinfo->pr_ttydev) : NULL;
   if (!name) {
      free(proc->tty_name);
      proc->tty_name = NULL;
   } else {
      free_and_xStrdup(&proc->tty_name, name);
   }

   proc->m_resident         = _psinfo->pr_rssize;  // KB
   proc->m_virt             = _psinfo->pr_size;    // KB

   if (proc->st_uid != _psinfo->pr_euid) {
      proc->st_uid          = _psinfo->pr_euid;
      proc->user            = UsersTable_getRef(host->usersTable, proc->st_uid);
   }

   if (!preExisting) {
      sproc->realpid        = _psinfo->pr_pid;
      sproc->lwpid          = lwpid_real;
      sproc->zoneid         = _psinfo->pr_zoneid;
      sproc->zname          = SolarisProcessTable_readZoneName(shost->kd, sproc);
      SolarisProcessTable_updateExe(_psinfo->pr_pid, proc);

      Process_updateComm(proc, _psinfo->pr_fname);
      Process_updateCmdline(proc, _psinfo->pr_psargs, 0, 0);

      if (settings->ss->flags & PROCESS_FLAG_CWD) {
         SolarisProcessTable_updateCwd(_psinfo->pr_pid, proc);
      }
   }

   // End common code pass 1

   if (onMasterLWP) { // Are we on the representative LWP?
      Process_setParent(proc, (_psinfo->pr_ppid * 1024));
      Process_setThreadGroup(proc, (_psinfo->pr_ppid * 1024));
      sproc->realppid       = _psinfo->pr_ppid;
      sproc->realtgid       = _psinfo->pr_ppid;

      // See note above (in common section) about this BINARY FRACTION
      proc->percent_cpu     = ((uint16_t)_psinfo->pr_pctcpu / (double)32768) * (double)100.0;
      Process_updateCPUFieldWidths(proc->percent_cpu);

      proc->time            = _psinfo->pr_time.tv_sec * 100 + _psinfo->pr_time.tv_nsec / 10000000;
      if (!preExisting) { // Tasks done only for NEW processes
         proc->isUserlandThread = false;
         proc->starttime_ctime = _psinfo->pr_start.tv_sec;
      }

      // Update proc and thread counts based on settings
      if (proc->isKernelThread && !settings->hideKernelThreads) {
         pt->kernelThreads += proc->nlwp;
         pt->totalTasks += proc->nlwp + 1;
         if (proc->state == RUNNING) {
            pt->runningTasks++;
         }
      } else if (!proc->isKernelThread) {
         if (proc->state == RUNNING) {
            pt->runningTasks++;
         }
         if (settings->hideUserlandThreads) {
            pt->totalTasks++;
         } else {
            pt->userlandThreads += proc->nlwp;
            pt->totalTasks += proc->nlwp + 1;
         }
      }
      proc->super.show = !(settings->hideKernelThreads && proc->isKernelThread);
   } else { // We are not in the master LWP, so jump to the LWP handling code
      proc->percent_cpu        = ((uint16_t)_lwpsinfo->pr_pctcpu / (double)32768) * (double)100.0;
      Process_updateCPUFieldWidths(proc->percent_cpu);

      proc->time               = _lwpsinfo->pr_time.tv_sec * 100 + _lwpsinfo->pr_time.tv_nsec / 10000000;
      if (!preExisting) { // Tasks done only for NEW LWPs
         proc->isUserlandThread    = true;
         Process_setParent(proc, _psinfo->pr_pid * 1024);
         Process_setThreadGroup(proc, _psinfo->pr_pid * 1024);
         sproc->realppid       = _psinfo->pr_pid;
         sproc->realtgid       = _psinfo->pr_pid;
         proc->starttime_ctime = _lwpsinfo->pr_start.tv_sec;
      }

      // Top-level process only gets this for the representative LWP
      if (proc->isKernelThread && !settings->hideKernelThreads) {
         proc->super.show = true;
      }
      if (!proc->isKernelThread && !settings->hideUserlandThreads) {
         proc->super.show = true;
      }
   } // Top-level LWP or subordinate LWP

   // Common code pass 2

   if (!preExisting) {
      if ((sproc->realppid <= 0) && !(sproc->realpid <= 1)) {
         proc->isKernelThread = true;
      } else {
         proc->isKernelThread = false;
      }

      Process_fillStarttimeBuffer(proc);
      ProcessTable_add(pt, proc);
   }

   proc->super.updated = true;

   // End common code pass 2

   return 0;
}

void ProcessTable_goThroughEntries(ProcessTable* super) {
   super->kernelThreads = 1;
   proc_walk(&SolarisProcessTable_walkproc, super, PR_WALK_LWP);
}
