/*
 * global.c - generic ps symbols and functions
 * Copyright 1998-2002 by Albert Cahalan
 *
 * 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 <fcntl.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

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

#include "../proc/wchan.h"
#include "../proc/version.h"
#include "../proc/sysinfo.h"

#include "../include/c.h"
#include "common.h"

#ifndef __GNU_LIBRARY__
#define __GNU_LIBRARY__ -1
#endif
#ifndef __GLIBC__
#define __GLIBC__ -1
#endif
#ifndef __GLIBC_MINOR__
#define __GLIBC_MINOR__ -1
#endif


static const char * saved_personality_text = "You found a bug!";

int             all_processes = -1;
const char     *bsd_j_format = (const char *)0xdeadbeef;
const char     *bsd_l_format = (const char *)0xdeadbeef;
const char     *bsd_s_format = (const char *)0xdeadbeef;
const char     *bsd_u_format = (const char *)0xdeadbeef;
const char     *bsd_v_format = (const char *)0xdeadbeef;
int             bsd_c_option = -1;
int             bsd_e_option = -1;
uid_t           cached_euid = -1;
dev_t           cached_tty = -1;
char            forest_prefix[4 * 32*1024 + 100];     // FIXME
int             forest_type = -1;
unsigned        format_flags = 0xffffffff;   /* -l -f l u s -j... */
format_node    *format_list = (format_node *)0xdeadbeef; /* digested formatting options */
unsigned        format_modifiers = 0xffffffff;   /* -c -j -y -P -L... */
int             header_gap = -1;
int             header_type = -1;
int             include_dead_children = -1;
int             lines_to_next_header = -1;
const char     *namelist_file = (const char *)0xdeadbeef;
int             negate_selection = -1;
int             running_only = -1;
int             page_size = -1;  // "int" for math reasons?
unsigned        personality = 0xffffffff;
int             prefer_bsd_defaults = -1;
int             screen_cols = -1;
int             screen_rows = -1;
time_t          seconds_since_boot = -1;
selection_node *selection_list = (selection_node *)0xdeadbeef;
unsigned        simple_select = 0xffffffff;
sort_node      *sort_list = (sort_node *)0xdeadbeef; /* ready-to-use sort list */
const char     *sysv_f_format = (const char *)0xdeadbeef;
const char     *sysv_fl_format = (const char *)0xdeadbeef;
const char     *sysv_j_format = (const char *)0xdeadbeef;
const char     *sysv_l_format = (const char *)0xdeadbeef;
unsigned        thread_flags = 0xffffffff;
int             unix_f_option = -1;
int             user_is_number = -1;
int             wchan_is_number = -1;
const char     *the_word_help;

static void reset_selection_list(void){
  selection_node *old;
  selection_node *walk = selection_list;
  if(selection_list == (selection_node *)0xdeadbeef){
    selection_list = NULL;
    return;
  }
  while(walk){
    old = walk;
    walk = old->next;
    free(old->u);
    free(old);
  }
  selection_list = NULL;
}

// The rules:
// 1. Defaults are implementation-specific. (ioctl,termcap,guess)
// 2. COLUMNS and LINES override the defaults. (standards compliance)
// 3. Command line options override everything else.
// 4. Actual output may be more if the above is too narrow.
//
// SysV tends to spew semi-wide output in all cases. The args
// will be limited to 64 or 80 characters, without regard to
// screen size. So lines of 120 to 160 chars are normal.
// Tough luck if you want more or less than that! HP-UX has a
// new "-x" option for 1024-char args in place of comm that
// we'll implement at some point.
//
// BSD tends to make a good effort, then fall back to 80 cols.
// Use "ww" to get infinity. This is nicer for "ps | less"
// and "watch ps". It can run faster too.
static void set_screen_size(void){
  struct winsize ws;
  char *columns; /* Unix98 environment variable */
  char *lines;   /* Unix98 environment variable */

  do{
    int fd;
    if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 && ws.ws_col>0 && ws.ws_row>0) break;
    if(ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) != -1 && ws.ws_col>0 && ws.ws_row>0) break;
    if(ioctl(STDIN_FILENO,  TIOCGWINSZ, &ws) != -1 && ws.ws_col>0 && ws.ws_row>0) break;
    fd = open("/dev/tty", O_NOCTTY|O_NONBLOCK|O_RDONLY);
    if(fd != -1){
      int ret = ioctl(fd, TIOCGWINSZ, &ws);
      close(fd);
      if(ret != -1 && ws.ws_col>0 && ws.ws_row>0) break;
    }
    // TODO: ought to do tgetnum("co") and tgetnum("li") here
    ws.ws_col = 80;
    ws.ws_row = 24;
  }while(0);
  screen_cols = ws.ws_col;  // hmmm, NetBSD subtracts 1
  screen_rows = ws.ws_row;

  // TODO: delete this line
  if(!isatty(STDOUT_FILENO)) screen_cols = OUTBUF_SIZE;

  columns = getenv("COLUMNS");
  if(columns && *columns){
    long t;
    char *endptr;
    t = strtol(columns, &endptr, 0);
    if(!*endptr && (t>0) && (t<(long)OUTBUF_SIZE)) screen_cols = (int)t;
  }

  lines   = getenv("LINES");
  if(lines && *lines){
    long t;
    char *endptr;
    t = strtol(lines, &endptr, 0);
    if(!*endptr && (t>0) && (t<(long)OUTBUF_SIZE)) screen_rows = (int)t;
  }

  if((screen_cols<9) || (screen_rows<2))
    fprintf(stderr,_("your %dx%d screen size is bogus. expect trouble\n"),
      screen_cols, screen_rows
    );
}

/**************** personality control **************/

typedef struct personality_table_struct {
  const char *name; /* personality name */
  const void *jump; /* See gcc extension info.   :-)   */
} personality_table_struct;

static int compare_personality_table_structs(const void *a, const void *b){
  return strcasecmp(((const personality_table_struct*)a)->name,((const personality_table_struct*)b)->name);
}

static const char *set_personality(void){
  const char *s;
  size_t sl;
  char buf[16];
  personality_table_struct findme = { buf, NULL};
  personality_table_struct *found;
  static const personality_table_struct personality_table[] = {
  {"390",      &&case_390},
  {"aix",      &&case_aix},
  {"bsd",      &&case_bsd},
  {"compaq",   &&case_compaq},
  {"debian",   &&case_debian},
  {"default",  &&case_default},
  {"digital",  &&case_digital},
  {"gnu",      &&case_gnu},
  {"hp",       &&case_hp},
  {"hpux",     &&case_hpux},
  {"irix",     &&case_irix},
  {"linux",    &&case_linux},
  {"old",      &&case_old},
  {"os390",    &&case_os390},
  {"posix",    &&case_posix},
  {"s390",     &&case_s390},
  {"sco",      &&case_sco},
  {"sgi",      &&case_sgi},
  {"solaris2", &&case_solaris2},
  {"sunos4",   &&case_sunos4},
  {"svr4",     &&case_svr4},
  {"sysv",     &&case_sysv},
  {"tru64",    &&case_tru64},
  {"unix",     &&case_unix},
  {"unix95",   &&case_unix95},
  {"unix98",   &&case_unix98},
  {"unknown",  &&case_unknown}
  };
  const int personality_table_count = sizeof(personality_table)/sizeof(personality_table_struct);

  personality = 0;
  prefer_bsd_defaults = 0;

  bsd_j_format = "OL_j";
  bsd_l_format = "OL_l";
  bsd_s_format = "OL_s";
  bsd_u_format = "OL_u";
  bsd_v_format = "OL_v";

  /* When these are NULL, the code does SysV output modifier logic */
  sysv_f_format  = NULL;
  sysv_fl_format = NULL;
  sysv_j_format  = NULL;
  sysv_l_format  = NULL;

  s = getenv("PS_PERSONALITY");
  if(!s || !*s) s = getenv("CMD_ENV");
  if(!s || !*s) s="unknown";   /* "Do The Right Thing[tm]" */
  if(getenv("I_WANT_A_BROKEN_PS")) s="old";
  sl = strlen(s);
  if(sl > 15) return _("environment specified an unknown personality");
  strncpy(buf, s, sl);
  buf[sl] = '\0';
  if ((saved_personality_text = strdup(buf))==NULL) {
    fprintf(stderr, _("cannot strdup() personality text\n"));
    exit(EXIT_FAILURE);
  }

  found = bsearch(&findme, personality_table, personality_table_count,
      sizeof(personality_table_struct), compare_personality_table_structs
  );

  if(!found) return _("environment specified an unknown personality");

  goto *(found->jump);    /* See gcc extension info.  :-)   */

  case_bsd:
    personality = PER_FORCE_BSD | PER_BSD_h | PER_BSD_m;
    prefer_bsd_defaults = 1;
    bsd_j_format = "FB_j";
    bsd_l_format = "FB_l";
    /* bsd_s_format not used */
    bsd_u_format = "FB_u";
    bsd_v_format = "FB_v";
    return NULL;

  case_old:
    personality = PER_FORCE_BSD | PER_OLD_m;
    prefer_bsd_defaults = 1;
    return NULL;

  case_debian:  /* Toss this? They don't seem to care much. */
  case_gnu:
    personality = PER_GOOD_o | PER_OLD_m;
    prefer_bsd_defaults = 1;
    sysv_f_format  = "RD_f";
    /* sysv_fl_format = "RD_fl"; */   /* old Debian ps can't do this! */
    sysv_j_format  = "RD_j";
    sysv_l_format  = "RD_l";
    return NULL;

  case_linux:
    personality = PER_GOOD_o | PER_ZAP_ADDR | PER_SANE_USER;
    return NULL;

  case_default: /* use defaults for ps, ignoring other environment variables */
    return NULL;

  case_unknown: /* defaults, but also check inferior environment variables */
    if(
      getenv("UNIX95")     /* Irix */
      || getenv("POSIXLY_CORRECT")  /* most gnu stuff */
      || (getenv("POSIX2") && !strcmp(getenv("POSIX2"), "on")) /* Unixware 7 */
    ) personality = PER_BROKEN_o;
    return NULL;

  case_aix:
    bsd_j_format = "FB_j";
    bsd_l_format = "FB_l";
    /* bsd_s_format not used */
    bsd_u_format = "FB_u";
    bsd_v_format = "FB_v";
    return NULL;

  case_tru64:
  case_compaq:
  case_digital:
    // no PER_NO_DEFAULT_g even though man page claims it
    // Reality: the g is a NOP
    personality = PER_GOOD_o | PER_BSD_h;
    prefer_bsd_defaults = 1;
    sysv_f_format  = "F5FMT";
    sysv_fl_format = "FL5FMT";
    sysv_j_format  = "JFMT";
    sysv_l_format  = "L5FMT";
    bsd_j_format = "JFMT";
    bsd_l_format = "LFMT";
    bsd_s_format = "SFMT";
    bsd_u_format = "UFMT";
    bsd_v_format = "VFMT";
    return NULL;

  case_sunos4:
    personality = PER_NO_DEFAULT_g;
    prefer_bsd_defaults = 1;
    bsd_j_format = "FB_j";
    bsd_l_format = "FB_l";
    /* bsd_s_format not used */
    bsd_u_format = "FB_u";
    bsd_v_format = "FB_v";
    return NULL;

  case_irix:
  case_sgi:
    s = getenv("_XPG");
    if(s && s[0]>'0' && s[0]<='9') personality = PER_BROKEN_o;
    else personality = PER_IRIX_l;
    return NULL;

  case_os390:  /* IBM's OS/390 OpenEdition on the S/390 mainframe */
  case_s390:
  case_390:
    sysv_j_format  = "J390";  /* don't know what -jl and -jf do */
    return NULL;

  case_hp:
  case_hpux:
    personality = PER_BROKEN_o | PER_HPUX_x;
    return NULL;

  case_svr4:
  case_sysv:
  case_sco:
    personality = PER_BROKEN_o | PER_SVR4_x;
    return NULL;

  case_posix:
  case_solaris2:
  case_unix95:
  case_unix98:
  case_unix:
    personality = PER_BROKEN_o;
    return NULL;
}


/************ Call this to reinitialize everything ***************/
void reset_global(void){
  static proc_t p;
  reset_selection_list();
  look_up_our_self(&p);
  set_screen_size();
  set_personality();

  all_processes         = 0;
  bsd_c_option          = 0;
  bsd_e_option          = 0;
  cached_euid           = geteuid();
  cached_tty            = p.tty;
/* forest_prefix must be all zero because of POSIX */
  forest_type           = 0;
  format_flags          = 0;   /* -l -f l u s -j... */
  format_list           = NULL; /* digested formatting options */
  format_modifiers      = 0;   /* -c -j -y -P -L... */
  header_gap            = -1;  /* send lines_to_next_header to -infinity */
  header_type           = HEAD_SINGLE;
  include_dead_children = 0;
  lines_to_next_header  = 1;
  namelist_file         = NULL;
  negate_selection      = 0;
  page_size             = getpagesize();
  running_only          = 0;
  seconds_since_boot    = uptime(0,0);
  selection_list        = NULL;
  simple_select         = 0;
  sort_list             = NULL;
  thread_flags          = 0;
  unix_f_option         = 0;
  user_is_number        = 0;
  wchan_is_number       = 0;
/* Translation Note:
   . The following translatable word will be used to recognize the
   . user's request for help text.  In other words, the translation
   . you provide will alter program behavior.
   .
   . It must be limited to 15 characters or less.
   */
  the_word_help         = _("help");
}

static const char archdefs[] =
#ifdef __alpha__
" alpha"
#endif
#ifdef __arm__
" arm"
#endif
#ifdef __hppa__
" hppa"
#endif
#ifdef __i386__
" i386"
#endif
#ifdef __ia64__
" ia64"
#endif
#ifdef __mc68000__
" mc68000"
#endif
#ifdef __mips64__
" mips64"
#endif
#ifdef __mips__
" mips"
#endif
#ifdef __powerpc__
" powerpc"
#endif
#ifdef __sh3__
" sh3"
#endif
#ifdef __sh__
" sh"
#endif
#ifdef __sparc__
" sparc"
#endif
#ifdef __sparc_v9__
" sparc_v9"
#endif
#ifdef __x86_64__
" x86_64"
#endif
"";

/*********** spew variables ***********/
void self_info(void){
  fprintf(stderr,
    "BSD j    %s\n"
    "BSD l    %s\n"
    "BSD s    %s\n"
    "BSD u    %s\n"
    "BSD v    %s\n"
    "SysV -f  %s\n"
    "SysV -fl %s\n"
    "SysV -j  %s\n"
    "SysV -l  %s\n"
    "\n",
    bsd_j_format   ? bsd_j_format   : "(none)",
    bsd_l_format   ? bsd_l_format   : "(none)",
    bsd_s_format   ? bsd_s_format   : "(none)",
    bsd_u_format   ? bsd_u_format   : "(none)",
    bsd_v_format   ? bsd_v_format   : "(none)",
    sysv_f_format  ? sysv_f_format  : "(none)",
    sysv_fl_format ? sysv_fl_format : "(none)",
    sysv_j_format  ? sysv_j_format  : "(none)",
    sysv_l_format  ? sysv_l_format  : "(none)"
  );

  display_version();
  fprintf(stderr, "Linux version %d.%d.%d\n",
    LINUX_VERSION_MAJOR(linux_version_code),
    LINUX_VERSION_MINOR(linux_version_code),
    LINUX_VERSION_PATCH(linux_version_code)
  );
  /* __libc_print_version(); */  /* how can we get the run-time version? */
  fprintf(stderr, "Compiled with: glibc %d.%d, gcc %d.%d\n\n",
    __GLIBC__, __GLIBC_MINOR__, __GNUC__, __GNUC_MINOR__
  );

  fprintf(stderr,
    "header_gap=%d lines_to_next_header=%d\n"
    "screen_cols=%d screen_rows=%d\n"
    "\n",
    header_gap, lines_to_next_header,
    screen_cols, screen_rows
  );

  fprintf(stderr,
    "personality=0x%08x (from \"%s\")\n"
    "EUID=%d TTY=%d,%d Hertz=%lld page_size=%d\n",
    personality, saved_personality_text,
    cached_euid, (int)major(cached_tty), (int)minor(cached_tty), Hertz,
    (int)(page_size)
  );

  fprintf(stderr,
    "sizeof(proc_t)=%d sizeof(long)=%d sizeof(KLONG)=%d\n",
    (int)sizeof(proc_t), (int)sizeof(long), (int)sizeof(KLONG)
  );

  fprintf(stderr, "archdefs:%s\n", archdefs);

  open_psdb(namelist_file);
  fprintf(stderr,"namelist_file=\"%s\"\n",namelist_file?namelist_file:"<no System.map file>");
}

void __attribute__ ((__noreturn__))
catastrophic_failure(const char *filename,
		     unsigned int linenum,
		     const char *message)
{
  error_at_line(0, 0, filename, linenum, "%s", message);
  exit(EXIT_FAILURE);
}
