text: /*
   * 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 <error.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 "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);
  }
  