/*
 * Copyright 1998 by Albert Cahalan; all rights resered.         
 * This file may be used subject to the terms and conditions of the
 * GNU Library General Public License Version 2, or any later version  
 * at your option, as published by the Free Software Foundation.
 * 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 Library General Public License for more details.
 */                                 
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <string.h>

/*#undef __GLIBC_MINOR__
#define __GLIBC_MINOR__ 1 */
#include "common.h"
/*#undef __GLIBC_MINOR__
#define __GLIBC_MINOR__ 0 */

#include <sys/sysmacros.h>
#include "../proc/version.h"
#include "../proc/sysinfo.h"


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


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

int             all_processes = -1;
char           *bsd_j_format = (char *)0xdeadbeef;
char           *bsd_l_format = (char *)0xdeadbeef;
char           *bsd_s_format = (char *)0xdeadbeef;
char           *bsd_u_format = (char *)0xdeadbeef;
char           *bsd_v_format = (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];
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;
unsigned        personality = 0xffffffff;
int             prefer_bsd_defaults = -1;
int             screen_cols = -1;
int             screen_rows = -1;
unsigned long   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 */
char           *sysv_f_format = (char *)0xdeadbeef;
char           *sysv_fl_format = (char *)0xdeadbeef;
char           *sysv_j_format = (char *)0xdeadbeef;
char           *sysv_l_format = (char *)0xdeadbeef;
int             unix_f_option = -1;
int             user_is_number = -1;
int             wchan_is_number = -1;


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.
 */
static void set_screen_size(void){
  struct winsize ws;
  char *columns; /* Unix98 environment variable */
  char *lines;   /* Unix98 environment variable */
  if(ioctl(1, TIOCGWINSZ, &ws) != -1 && ws.ws_col>0 && ws.ws_row>0){
    screen_cols = ws.ws_col;
    screen_rows = ws.ws_row;
  }else{  /* TODO: ought to do tgetnum("co") and tgetnum("li") now */
    screen_cols = 80;
    screen_rows = 24;
  }
  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(((personality_table_struct*)a)->name,((personality_table_struct*)b)->name);
}

static const char *set_personality(void){
  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},
  {"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';
  saved_personality_text = strdup(buf);

  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_CUMUL_MARKED | PER_OLD_m;
    prefer_bsd_defaults = 1;
    sysv_f_format  = "RD_f";
    /* sysv_fl_format = "RD_fl"; */   /* Debian 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:
    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:
  case_posix:
  case_sco:
  case_solaris2:
  case_sysv:
  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;
  running_only          = 0;
  seconds_since_boot    = uptime(0,0);
  selection_list        = NULL;
  simple_select         = 0;
  sort_list             = NULL;
  unix_f_option         = 0;
  user_is_number        = 0;
  wchan_is_number       = 0;
}

/*********** spew variables ***********/
void self_info(void){
#ifndef EMULATE_FPUTS
  int count;
#endif
  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: libc %d, internal version %d.%d\n\n",
    __GNU_LIBRARY__, __GLIBC__, __GLIBC_MINOR__
  );

#ifdef EMULATE_FPUTS
  fprintf(stderr, "libc assumed lame, using fprintf to emulate fputs.\n\n");
#else
  fprintf(stderr, "fputs(\"");
  count = fputs("123456789", stderr);
  fprintf(stderr, "\", stderr) gives %d, which is %s.\n",
    count, count==9?"good":"BAD!\nAdjust ps/common.h or libc, then recompile"
  );
  if(count!=9){
    fprintf(stderr, "(procps includes a libc patch called glibc.patch)\n");
  }
  fprintf(stderr, "\n");
#endif

  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
  );

/*  open_psdb(namelist_file); */
  fprintf(stderr,
    "personality=0x%08x (from \"%s\")\n"
    "EUID=%d TTY=%d,%d Hertz=%Ld\n"
/*    "namelist_file=\"%s\"\n" */
    ,
    personality, saved_personality_text,
    cached_euid, (int)major(cached_tty), (int)minor(cached_tty), Hertz   /* ,
    namelist_file?namelist_file:"<no System.map file>"  */
  );
}