/* Show process information in 'top'-like form.

   Copyright (C) 1995,96,97,98,99,2001,02 Free Software Foundation, Inc.

   Written by Miles Bader <miles@gnu.org>, with extra contributions
   from Paul Emsley <paule@chem.gla.ac.uk>, Neal Walfield <neal@walfield.org>,
   and James A. Morrison <ja2morri@student.math.uwaterloo.ca>

   This program 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 2, or (at
   your option) any later version.

   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 General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA */


#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <locale.h>
#include <libintl.h>
#include <langinfo.h>

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <utmp.h>
#include <errno.h>
#include <error.h>
#include <ps.h>
#include <hurd.h>
#include <hurd/paths.h>
#include <hurd/ihash.h>
#include <mach.h>
#include <mach/vm_statistics.h>
#include <mach/default_pager.h>
#include <mach/default_pager_types.h>   /* for default pager stuff */
#include <assert.h>

#include <idvec.h>
#include <argz.h>
#include <signal.h>             /* for sigwinch */
#include <fcntl.h>
#include <pthread.h>

#include "pptop.h"
#include "aux.h"
#include "filter.h"
#include "fields.h"
#include "title.h"
#include "options.h"

#define _(STRING) gettext (STRING)

#define M 1048576 /* 1024 * 1024 */
#define K 1024

char *headers;
char *global_fmt_string;
char *sort_key_name = "CPU";
int sort_key_reverse = 1;
pthread_mutex_t header_lock;
int show_threads = 0;

void
setup_headers (int headcount)
{
  headers = (char *) malloc (headcount);
  if (!headers)
    error (1, errno, "Can't initialize the header array");

  headers[UPTIME_LINE] = UPTIME_LINE;
  headers[PROCESS_LINE] = PROCESS_LINE;
  headers[CPU_LINE] = CPU_LINE;
  headers[MEMORY_LINE] = MEMORY_LINE;
  headers[PAGER_LINE] = PAGER_LINE;
  headers[LOADMACH_LINE] = LOADMACH_LINE;
  headers[TABLE_HEADER] = TABLE_HEADER;
}

int
fix_head_count (void)
{
  int i, j = 0;

  for (i = 0; i < 7; i++)
    if (headers[i] != -1) 
      {
	headers[i] = j;
	j++;
      }
  return j;
}

int
adjust_headers (int adj_field)
{
  if (headers[adj_field] == -1)
    headers[adj_field] = adj_field;
  else
    headers[adj_field] = -1;

  return fix_head_count ();
}

/* load_proc_field is "borrowed" from Neal Walfield's procfs
   - Thanks Neal. */

/* Verify that PSTAT_FIELD is available in PROC */
error_t
load_proc_field (struct proc_stat *p, int PSTAT_FIELD)
{
  if (!(p->flags & PSTAT_FIELD))
    {
      /* The requested data is not available, try to
         have it loaded */
      int err = proc_stat_set_flags (p, PSTAT_FIELD);
      if (err)
        return err;

      /* The call was successful, but according to the interface
         the data may still not be available. */
      if (!(p->flags & PSTAT_FIELD))
        /* XXX - FIXME
           proc_stat_set_flags succeeded but it did not set the PSTAT_ARGS flag
           what is the correct error code to return in this situation? */
        return EGRATUITOUS;
    }

  return 0;
}

void
sigHandler (int signal)
{
  undo_curses ();
  exit (0);
}


#include "specs.h"
const char *fmt_string = "default";

int
main (int argc, char **argv)
{
  int err;
  struct ps_stream *strum;

  signal (SIGINT, sigHandler);

  setlocale (LC_ALL, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);

  strum = (struct ps_stream *) malloc (sizeof (struct ps_stream));
  if (!strum)
    error (1, errno, "No memory available for strum.");

  err = init_ps_stream (strum);
  if (err)
    error (1, err, "Couldn't init_ps_stream with strum.");

  setup_headers (7);
  err = setup_fields ();
  if (err)
    error (1, err, "Cannot initialize fields");

  pthread_mutex_init(&header_lock, NULL);
  
  setup_filters ();

  do_curses_setup ();

  while (1)
    {
      clear ();
      new_update (strum);
      refresh ();
      if (wait_and_check_for_quit () == TRUE)
        {
          free (strum);
          undo_curses ();
	  pthread_mutex_destroy (&header_lock);
          return (0);
        }
    }

  return 0;                     /* never happens */
}

error_t
pptop_print_uptime (struct ps_context * context, int line)
{
  char now_str[32];
  char *uptime_text;
  struct proc_stat *ps;
  error_t err = 0;
  struct tm *now;
  time_t now_t;
  time_t uptime;
  time_t init_time;
  int nusers = 0;
  struct utmp *ut;

  setutent ();
  while ((ut = getutent ())) {
    if (ut->ut_type == LOGIN_PROCESS || ut->ut_type == USER_PROCESS)
      if (*ut->ut_name || *ut->ut_line)
	nusers++;
  }
  endutent ();

  err = ps_context_find_proc_stat (context, 1, &ps);
  if (err)
    return err;

  now_t = time (NULL);
  now = localtime (&now_t);
  strftime (now_str, sizeof (now_str), nl_langinfo (T_FMT_AMPM), now);

  err = proc_stat_set_flags (ps, PSTAT_TASK_BASIC);
  if (err && !(ps->flags & PSTAT_TASK_BASIC))
    {
      return err;
    }

  init_time = proc_stat_task_basic_info (ps)->creation_time.seconds;
  uptime = now_t - init_time;
  

  if (asprintf (&uptime_text, " %s up %ld days %ld:%02ld, %i %s", 
		  now_str, uptime / (24 * 60 * 60),   /* Uptime in days */
                  uptime / (60 * 60) % 24,      /* Uptime in hours */
                  uptime / (60) % 60, /* Uptime in minutes */
		  nusers, nusers == 1 ? "user": "users") > 0)
    {
      bold_text (headers[UPTIME_LINE], 0, uptime_text);
      free (uptime_text);
    }

  return err;
}


int
new_update (struct ps_stream *strum)
{
  host_t host;
  process_t pp;
  struct ps_context *context;
  struct proc_stat_list *procset;
  error_t err;
  hurd_ihash_t offsets;

  char *tty_names = 0;
  unsigned num_tty_names = 0;
  struct idvec *only_uids = make_idvec ();
  struct idvec *not_uids = make_idvec ();

  /* the string which we will use sprintf to write formatted text
     which will then be used in the ncurses function */
  char *printing_str;

  int proc_stat_has_ctty (struct proc_stat *ps)
  {
    if (proc_stat_has (ps, PSTAT_TTY))
      /* Only match processes whose tty we can figure out.  */
      {
        /* I think we have a memory leak in here */
        struct ps_tty *tty = proc_stat_tty (ps);
        if (tty)
          {
            char *try = 0;
            const char *name = ps_tty_name (tty);
            const char *short_name = ps_tty_short_name (tty);

            while ((try = argz_next (tty_names, num_tty_names, try)))
              if ((name && strcmp (try, name) == 0)
                  || (short_name && strcmp (try, short_name) == 0))
                return TRUE;
          }
      }
    return FALSE;
  }


  /* Returns TRUE if PS is owned by any of the users in ONLY_UIDS, 
     and none in NOT_UIDS.  */
  int proc_stat_owner_ok (struct proc_stat *ps)
  {
    int uid = proc_stat_owner_uid (ps);
    if (only_uids->num > 0 && !idvec_contains (only_uids, uid))
      return 0;
    if (not_uids->num > 0 && idvec_contains (not_uids, uid))
      return 0;
    return 1;
  }


  host = ps_get_host ();        /*  not used. */


  pp = getproc ();              /* the vital Hurd call */

  err = ps_context_create (pp, &context);
  if (err)
    return err;

  err = proc_stat_list_create (context, &procset);
  if (err)
    return err;

  err = proc_stat_list_add_all (procset, NULL, NULL);
  if (err)
    return err;

  hurd_ihash_create (&offsets, HURD_IHASH_NO_LOCP);

  /* Lets try to run proc_stat_list_fmt on a proc_stat_list 
     pointer (here it is procset).
     We need a stream and a fmt */
  {
    int nproc = 0, stopped = 0, zombie = 0, running = 0, sleeping = 0, 
      idle = 0, halted = 0, waiting = 0;
    int i;
    struct proc_stat *p;
    task_basic_info_t tbi;
    struct ps_fmt *fmt;

/*     pid_t *pids; */

    int posix_fmt = 0;
    char *local_fmt_string;
/*      char *sort_key_name = NULL; */
/*     int sort_reverse = FALSE; */
/*     int print_heading = TRUE; */
/*     int squash_bogus_fields = TRUE; */
/*     int squash_nominal_fields = TRUE; */
/*     int top = 1; */
    int show_non_hurd_procs = 1;

    char *strum_string;
    char **ps_strings_list;

    int find_proc_state (struct proc_stat *eieio)
      { 
	nproc++;
	if (proc_stat_state (eieio) & PSTAT_STATE_P_STOP)
	  stopped++;
	else if (proc_stat_state (eieio) & PSTAT_STATE_P_ZOMBIE)
	  zombie++;
	else if (proc_stat_state (eieio) & PSTAT_STATE_T_RUN)
	  running++;
	else if (proc_stat_state (eieio) & PSTAT_STATE_T_HALT)
	  halted++;
	else if (proc_stat_state (eieio) & PSTAT_STATE_T_WAIT)
	  waiting++;
	/* for more consistent results we shall say anything that
	   hasn't sleep until it's idle is still running */
	else if (proc_stat_state (eieio) & PSTAT_STATE_T_SLEEP)
	  running++;
	else if (proc_stat_state (eieio) & PSTAT_STATE_T_IDLE)
	  sleeping++;
	else
	  idle++; /* lets count how many things are not in the above list */

	return 0;
      }

    local_fmt_string = get_fields ();
    if (!local_fmt_string)
      {
	undo_curses ();
	error (1, errno, "Cannot allocate local format string");
      }

    err = proc_stat_list_for_each (procset, find_proc_state);
    if (err)
      return 0;

    printing_str = (char *) malloc (COLS + 1);
    if (!printing_str)
      {
        undo_curses ();
        error (1, errno, "No memory available");
      }

    snprintf (printing_str, COLS, 
	      "%d processes:  %d R  %d W  %d H  %d sleep  %d stop  %d Z  %d U",
	      nproc, running, waiting, halted, sleeping, stopped, zombie, idle);


    /* First clear the screen (of any garbage) */
    erase (); 
    /* If you think that pptop flashes,
       it's because of this.  It may be preferable to remove this and
       instead extend every line to the width of the terminal (with
       spaces) so that the underneath is removed. But I haven't done
       that because I don't think that the flashing is too bad.  */

    pptop_print_uptime (context, 0);

    bold_text (headers[PROCESS_LINE], 0, printing_str);

    /* Reference:
       psout(procset, local_fmt_string, posix_fmt, &ps_specs,
       sort_key_name,sort_reverse,
       output_width, print_heading,
       squash_bogus_fields, squash_nominal_fields,top);
     */

    /* Filter out any processes that we don't want to show.  */

    if (only_uids->num || not_uids->num)
      proc_stat_list_filter1 (procset, proc_stat_owner_ok,
                              PSTAT_OWNER_UID, FALSE);
    if (num_tty_names > 0)
      {
        /* We set the PSTAT_TTY flag separately so that our filter function
           can look at any procs that fail to set it.  */
        proc_stat_list_set_flags (procset, PSTAT_TTY);
        proc_stat_list_filter1 (procset, proc_stat_has_ctty, 0, FALSE);
      }

    do_filters (procset);

    if (show_threads)
      proc_stat_list_add_threads (procset);

    proc_stat_list_filter (procset, &ps_alive_filter, FALSE);
    if (sort_key_name != NULL)
      {
        {
          const struct ps_fmt_spec *sort_key;
          sort_key = ps_fmt_specs_find (&ps_specs, sort_key_name);
          if (sort_key != NULL)
	    proc_stat_list_sort (procset, sort_key, sort_key_reverse);
        }
      }

    err = ps_fmt_create (local_fmt_string, posix_fmt, &ps_specs, &fmt);

    if (err)
      {
        char *problem;
        ps_fmt_creation_error (local_fmt_string,
                               posix_fmt, &ps_specs, &problem);
        error (4, 0, "%s", problem);
        return 1;               /* never reached, the program exited. */
      }

    for (i = 0; i < nproc; i++)
      {
        p = procset->proc_stats[i];

        /* Later, we will want to add filter
           switching.  Here is where we
           arrange it. */

        /* err = load_proc_field(p, PSTAT_TASK_BASIC|PSTAT_OWNER_UID|PSTAT_OWNER);
         */
        err = load_proc_field (p, PSTAT_TASK_BASIC | PSTAT_OWNER);

        if (!err)
          {
            tbi = proc_stat_task_basic_info (p);
            if (tbi != NULL)
              {
                /* 
                   printf(" times: %d %d\n", tbi->system_time.seconds,
                   tbi->system_time.microseconds);
                 */
              }
            else
              {
                printf ("\n");
              }                 /* if tbi */
          }                     /* if !err */
      }                         /* for */

    {
      static host_basic_info_t basic_info;
      static host_load_info_t load_info;
      struct vm_statistics state;
      struct default_pager_info pager_info;
      mach_port_t host, def_pager = MACH_PORT_NULL;

      /* Lets have some HOST info */
      err = ps_host_basic_info (&basic_info);
      if (!err)
        {
          snprintf (printing_str, COLS + 1,
                    "CPUs available: %d(%d), type: %d(%d)", 
		    /*    Memory: %dMb\n", */
		    basic_info->avail_cpus, 
                    basic_info->max_cpus,
                    basic_info->cpu_type,
                    basic_info->cpu_subtype);
/*                     basic_info->memory_size / M); */
          bold_text (headers[CPU_LINE], 0, printing_str);
        }
      else
        {
          printf ("error getting basic_info \n");
          free (local_fmt_string);
          free (printing_str);
          return err;
        }


      /* Lets have some LOAD info */
      err = ps_host_load_info (&load_info);

      if (!err)
        {
          snprintf (printing_str, COLS + 1,
                    "Load Average: %6.3f %6.3f %6.3f  Mach factors: %6.3f %6.3f %6.3f",
                    (float) load_info->avenrun[0] / LOAD_SCALE,
                    (float) load_info->avenrun[1] / LOAD_SCALE,
                    (float) load_info->avenrun[2] / LOAD_SCALE,
                    (float) load_info->mach_factor[0] / LOAD_SCALE,
                    (float) load_info->mach_factor[1] / LOAD_SCALE,
                    (float) load_info->mach_factor[2] / LOAD_SCALE);
          bold_text (headers[LOADMACH_LINE], 0, printing_str);
        }
      else
        {
          printf ("error getting load_info \n");
          return err;
        }
    
      /* now the reverse video column labels format */
      ps_fmt_write_titles (fmt, strum);
      strum_string = get_strum_string (strum);
      {
	char *bar_string;
	bar_string = extend_string (&strum_string, COLS, ' ');
	if (!pthread_mutex_trylock (&header_lock))
	  {
	    rv_text (headers[TABLE_HEADER], bar_string);
	    pthread_mutex_unlock (&header_lock);
	  }
	else
 	  mvaddstr (headers[TABLE_HEADER], 0, 
		   get_title ()); 

	free (bar_string);
      }

      /* vm information */
      memset (&state, 0, sizeof (struct vm_statistics));
      err = vm_statistics (mach_task_self (), &state);
      if (err)
	snprintf (printing_str, COLS + 1, "Memory: %s", strerror (err));
      else 
	{
	  size_t m = M;
	  size_t used, total, inactive, free = state.free_count 
	    * state.pagesize, rtotal = basic_info->memory_size / M;
	  used = (state.active_count + state.wire_count) * state.pagesize;
          inactive = state.inactive_count * state.pagesize;
	  total = (free + used + inactive) / M;
	  snprintf (printing_str, COLS + 1, "Memory: total %zdM  active %zdM  inactive %zdM  kernel %zdM  free %zdM",
		    rtotal, used / m, inactive / m, rtotal - total, free / m);
	}
      bold_text (headers[MEMORY_LINE], 0, printing_str);
      
      /* mach pager stuff */
      memset (&pager_info, 0, sizeof (struct default_pager_info));
      if ((err = get_privileged_ports (&host, 0)))
	{
	  if (err == EPERM)
	    def_pager = file_name_lookup (_SERVERS_DEFPAGER, O_RDONLY, 0);
	}
      else 
	err = vm_set_default_memory_manager (host, &def_pager);

      if (!MACH_PORT_VALID (def_pager) || (def_pager == MACH_PORT_NULL)
	  || (err = default_pager_info (def_pager, &pager_info)))
	snprintf (printing_str, COLS + 1, "Pager Info: %s", strerror (err));
      else
	snprintf (printing_str, COLS + 1,
		  "Pager Info: total %zdM  free %zdM",
		  pager_info.dpi_total_space / M,
		  pager_info.dpi_free_space / M);
	
      bold_text (headers[PAGER_LINE], 0, printing_str);
      mach_port_deallocate (mach_task_self (), host);
      mach_port_deallocate (mach_task_self (), def_pager);
    }
/*
 * Cannot do this because you've already
 * freed the 'bar_string' which is 
 * the re-allocated version of this string.
 * This is why it segfaults.
     free(strum_string);
 */

    {                           /* ps strings */

      err = proc_stat_list_fmt (procset, fmt, strum);
      if (err)
        {
          printf ("can't do proc_stat_list_fmt\n");
          free (procset);
          free (strum_string);
          free (local_fmt_string);
          free (printing_str);
          return 1;
        }
      strum_string = get_strum_string (strum);

      /* Send string_splitter an extra
         argument that is the the maximum
         number of lines we can deal with
         (this is so that the malloc there
         need not be predefined).
       */
      ps_strings_list = string_splitter (strum_string, LINES);

      /* recall that LINES is a global
         variable containing the number of
         lines on the display.

         There are TABLE_HEADER lines of header.  This may
         change in the future. */

      curses_print_strings (ps_strings_list, get_lines (), 
			    fix_head_count () - 1);
      mvaddstr (headers[TABLE_HEADER], get_title_pos () , "");


      free (ps_strings_list);
      free (strum_string);
      free (local_fmt_string);
      free (printing_str);

    }                           /* ps strings */

  }                             /* finish local context */

  free (only_uids);
  free (not_uids);
  ps_context_free (context);
  free (tty_names);

  return 0;                     /* no error */
}
