From 9bdd8fd4549e8392b5bec0298e9978436392e342 Mon Sep 17 00:00:00 2001
From: Rob Landley <rob@landley.net>
Date: Wed, 28 Oct 2015 23:30:36 -0500
Subject: Promote ps to posix.

---
 toys/pending/ps.c | 613 ------------------------------------------------------
 toys/posix/ps.c   | 613 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 613 insertions(+), 613 deletions(-)
 delete mode 100644 toys/pending/ps.c
 create mode 100644 toys/posix/ps.c

(limited to 'toys')

diff --git a/toys/pending/ps.c b/toys/pending/ps.c
deleted file mode 100644
index 6c63d550..00000000
--- a/toys/pending/ps.c
+++ /dev/null
@@ -1,613 +0,0 @@
-/* ps.c - show process list
- *
- * Copyright 2015 Rob Landley <rob@landley.net>
- *
- * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ps.html
- * And http://kernel.org/doc/Documentation/filesystems/proc.txt Table 1-4
- * And linux kernel source fs/proc/array.c function do_task_stat()
- *
- * Deviations from posix: no -n because /proc/self/wchan exists.
- * Posix says default output should have field named "TTY" but if you "-o tty"
- * the same field should be called "TT" which is _INSANE_ and I'm not doing it.
- * Similarly -f outputs USER but calls it UID (we call it USER).
- * It also says that -o "args" and "comm" should behave differently but use
- * the same title, which is not the same title as the default output. (No.)
- * Select by session id is -s not -g.
- *
- * Posix defines -o ADDR as "The address of the process" but the process
- * start address is a constant on any elf system with mmu. The procps ADDR
- * field always prints "-" with an alignment of 1, which is why it has 11
- * characters left for "cmd" in in 80 column "ps -l" mode. On x86-64 you
- * need 12 chars, leaving nothing for cmd: I.E. posix 2008 ps -l mode can't
- * be sanely implemented on 64 bit Linux systems. In procps there's ps -y
- * which changes -l by removing the "F" column and swapping RSS for ADDR,
- * leaving 9 chars for cmd, so we're using that as our -l output.
- *
- * TODO: ps aux (att & bsd style "ps -ax" vs "ps ax" behavior difference)
- * TODO: finalize F, remove C
- *       switch -fl to -y, use "string" instead of constants to set, remove C
- * TODO: --sort -Z
- * TODO: way too many hardwired constants here, how can I generate them?
- * TODO: thread support /proc/$d/task/%d/stat (and -o stat has "l")
- *
- * Design issue: the -o fields are an ordered array, and the order is
- * significant. The array index is used in strawberry->which (consumed
- * in do_ps()) and in the bitmasks enabling default fields in ps_main().
-
-USE_PS(NEWTOY(ps, "aAdeflo*p*s*t*u*U*g*G*w[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN))
-
-config PS
-  bool "ps"
-  default n
-  help
-    usage: ps [-Aadeflw] [-gG GROUP] [-o FIELD] [-p PID] [-t TTY] [-uU USER]
-
-    List processes.
-
-    Which processes to show (selections may be comma separated lists):
-
-    -A	All processes
-    -a	Processes with terminals that aren't session leaders
-    -d	All processes that aren't session leaders
-    -e	Same as -A
-    -g	belonging to GROUPs
-    -G	belonging to real GROUPs (before sgid)
-    -p	PIDs
-    -s	in session IDs
-    -t	attached to selected TTYs
-    -u	owned by USERs
-    -U	owned by real USERs (before suid)
-    -w	Wide output (don't truncate at terminal width)
-
-    Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD)
-
-    -f	Full listing (-o USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD)
-    -l	Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD)
-    -o	Output the listed FIELDs, each with optional :size and/or =title
-
-    Available -o FIELDs:
-
-      ADDR   Instruction pointer
-      CMD    Command line
-      ETIME  Elapsed time since process start
-      F      Process flags (PF_*) from linux source file include/sched.h
-             (in octal rather than hex because posix)
-      GID    Group id
-      GROUP  Group name
-      MAJFL  Major page faults
-      MINFL  Minor page faults
-      NI     Niceness of process (lower niceness is higher priority)
-      PCPU   Percentage of CPU time used
-      PGID   Process Group ID
-      PID    Process ID
-      PPID   Parent Process ID
-      PRI    Priority
-      RGID   Real (before sgid) group ID
-      RGROUP Real (before sgid) group name
-      RSS    Resident Set Size (memory currently used)
-      RUID   Real (before suid) user ID
-      RUSER  Real (before suid) user name
-      S      Process state:
-             R (running) S (sleeping) D (disk sleep) T (stopped)  t (traced)
-             Z (zombie)  X (dead)     x (dead)       K (wakekill) W (waking)
-      STAT   Process state (S) plus:
-             < high priority          N low priority L locked memory
-             s session leader         + foreground   l multithreaded
-      STIME  Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss)
-      SZ     Memory Size (4k pages needed to completely swap out process)
-      TIME   CPU time consumed
-      TTY    Controlling terminal
-      UID    User id
-      USER   User name
-      VSZ    Virtual memory size (1k units)
-      WCHAN  Waiting in kernel for
-*/
-
-#define FOR_ps
-#include "toys.h"
-
-GLOBALS(
-  struct arg_list *G;
-  struct arg_list *g;
-  struct arg_list *U;
-  struct arg_list *u;
-  struct arg_list *t;
-  struct arg_list *s;
-  struct arg_list *p;
-  struct arg_list *o;
-
-  struct ptr_len gg, GG, pp, ss, tt, uu, UU, *parsing;
-  unsigned width;
-  dev_t tty;
-  void *fields;
-  long bits;
-  long long ticks;
-)
-
-struct strawberry {
-  struct strawberry *next, *prev;
-  short which, len;
-  char *title;
-  char forever[];
-};
-
-static time_t get_uptime(void)
-{
-  struct sysinfo si;
-
-  sysinfo(&si);
-
-  return si.uptime;
-}
-
-// Return 1 to display, 0 to skip
-static int match_process(long long *slot)
-{
-  struct ptr_len *match[] = {&TT.gg,&TT.GG,&TT.pp,&TT.ss,&TT.tt,&TT.uu,&TT.UU};
-  int i, j, mslot[] = {33, 34, 0, 3, 4, 31, 32};
-  long *ll = 0;
-
-  // Do we have -g -G -p -s -t -u -U options selecting processes?
-  for (i = 0; i < ARRAY_LEN(match); i++) {
-    if (match[i]->len) {
-      ll = match[i]->ptr;
-      for (j = 0; j<match[i]->len; j++) if (ll[j] == slot[mslot[i]]) return 1;
-    }
-  }
-
-  // If we had selections and didn't match them, don't display
-  if (ll) return 0;
-
-  // Filter implicit categories for other display types
-  if ((toys.optflags&(FLAG_a|FLAG_d)) && slot[3]==*slot) return 0;
-  if ((toys.optflags&FLAG_a) && !slot[4]) return 0;
-  if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e)) && TT.tty!=slot[4])
-    return 0;
-
-  return 1;
-}
-
-// dirtree callback.
-// toybuf used as: 1024 /proc/$PID/stat, 1024 slot[], 2048 /proc/$PID/cmdline
-static int do_ps(struct dirtree *new)
-{
-  struct strawberry *field;
-  long long *slot = (void *)(toybuf+1024), ll;
-  char *name, *s, state;
-  int nlen, i, fd, len, width = TT.width;
-
-  if (!new->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP;
-  if (!(*slot = atol(new->name))) return 0;
-
-  // name field limited to 256 bytes by VFS, plus 40 fields * max 20 chars:
-  // 1000-ish total, but some forced zero so actually there's headroom.
-  sprintf(toybuf, "%lld/stat", *slot);
-  if (!readfileat(dirtree_parentfd(new), toybuf, toybuf, 1024)) return 0;
-
-  // parse oddball fields (name and state)
-  if (!(s = strchr(toybuf, '('))) return 0;
-  for (name = ++s; *s != ')'; s++) if (!*s) return 0;
-  nlen = s++-name;
-  if (1>sscanf(++s, " %c%n", &state, &i)) return 0;
-
-  // parse numeric fields (PID = 0, skip 2, then 4th field goes in slot[1])
-  for (len = 1; len<100; len++)
-    if (1>sscanf(s += i, " %lld%n", slot+len, &i)) break;
-
-  // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch
-  // or numeric wchan, and the remaining two are always zero).
-  slot[31] = new->st.st_uid;
-  slot[33] = new->st.st_gid;
-
-  // If RGROUP RUSER STAT RUID RGID
-  // Save ruid in slot[34] and rgid in slot[35], which are otherwise zero,
-  // and vmlck into slot[18] (it_real_value, also always zero).
-  if ((TT.bits & 0x38300000) || TT.GG.len || TT.UU.len) {
-    char *out = toybuf+2048;
-
-    sprintf(out, "%lld/status", *slot);
-    if (!readfileat(dirtree_parentfd(new), out, out, 2048)) *out = 0;
-    s = strstr(out, "\nUid:");
-    slot[32] = s ? atol(s+5) : new->st.st_uid;
-    s = strstr(out, "\nGid:");
-    slot[34] = s ? atol(s+5) : new->st.st_gid;
-    s = strstr(out, "\nVmLck:");
-    if (s) slot[18] = atoll(s+5);
-  }
-
-  // skip processes we don't care about.
-  if (!match_process(slot)) return 0;
-
-  // At this point 512 bytes at toybuf+512 are free (already parsed).
-  // Start of toybuf still has name in it.
-
-  // Loop through fields
-  for (field = TT.fields; field; field = field->next) {
-    char *out = toybuf+2048, *scratch = toybuf+512;
-
-    // Default: unsupported (5 "C")
-    sprintf(out, "-");
-
-    // PID, PPID, PRI, NI, ADDR, SZ, RSS, PGID, VSS, MAJFL, MINFL
-    if (-1!=(i = stridx((char[]){3,4,6,7,8,9,24,19,23,25,30,0}, field->which)))
-    {
-      char *fmt = "%lld";
-
-      ll = slot[((char[]){0,1,15,16,27,20,21,2,20,9,7})[i]];
-      if (i==2) ll--;
-      if (i==4) fmt = "%llx";
-      else if (i==5) ll >>= 12;
-      else if (i==6) ll <<= 2;
-      else if (i==8) ll >>= 10;
-      sprintf(out, fmt, ll);
-    // UID USER RUID RUSER GID GROUP RGID RGROUP
-    } else if (-1!=(i = stridx((char[]){2,22,28,21,26,17,29,20}, field->which)))
-    {
-      int id = slot[31+i/2]; // uid, ruid, gid, rgid
-
-      // Even entries are numbers, odd are names
-      sprintf(out, "%d", id);
-      if (i&1) {
-        if (i>3) {
-          struct group *gr = getgrgid(id);
-
-          if (gr) out = gr->gr_name;
-        } else {
-          struct passwd *pw = getpwuid(id);
-
-          if (pw) out = pw->pw_name;
-        }
-      }
- 
-    // F (also assignment of i used by later tests)
-    // Posix doesn't specify what flags should say. Man page says
-    // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h
-    } else if (!(i = field->which)) sprintf(out, "%llo", (slot[6]>>6)&5);
-    // S STAT
-    else if (i==1 || i==27) {
-      sprintf(out, "%c", state);
-      if (i==27) {
-        // TODO l = multithreaded
-        s = out+1;
-        if (slot[16]<0) *s++ = '<';
-        else if (slot[16]>0) *s++ = 'N';
-        if (slot[3]==*slot) *s++ = 's';
-        if (slot[18]) *s++ = 'L';
-        if (slot[5]==*slot) *s++ = '+';
-        *s = 0;
-      } 
-    // WCHAN
-    } else if (i==10) {
-      sprintf(scratch, "%lld/wchan", *slot);
-      readfileat(dirtree_parentfd(new), scratch, out, 2047);
-
-    // STIME
-    } else if (i==11) {
-      time_t t = time(0) - get_uptime() + slot[19]/sysconf(_SC_CLK_TCK);
-
-      // Padding behavior's a bit odd: default field size is just hh:mm.
-      // Increasing stime:size reveals more data at left until full
-      // yyyy-mm-dd hh:mm revealed at :16, then adds :ss at end for :19. But
-      // expanding last field just adds :ss.
-      strftime(scratch, 512, "%F %T", localtime(&t));
-      out = scratch+strlen(scratch)-3-abs(field->len);
-      if (out<scratch) out = scratch;
-
-    // TTY
-    } else if (i==12) {
-
-      // Can we readlink() our way to a name?
-      for (i=0; i<3; i++) {
-        struct stat st;
-
-        sprintf(scratch, "%lld/fd/%i", *slot, i);
-        fd = dirtree_parentfd(new);
-        if (!fstatat(fd, scratch, &st, 0) && S_ISCHR(st.st_mode)
-          && st.st_rdev == slot[4]
-          && 0<(len = readlinkat(fd, scratch, out, 2047)))
-        {
-          out[len] = 0;
-          if (!strncmp(out, "/dev/", 5)) out += 5;
-
-          break;
-        }
-      }
-
-      // Couldn't find it, show major:minor
-      if (i==3) {
-        i = slot[4];
-        sprintf(out, "%d:%d", (i>>8)&0xfff, ((i>>12)&0xfff00)|(i&0xff));
-      }
-
-    // TIME ELAPSED
-    } else if (i==13 || i==16) {
-      int unit = 60*60*24, j = sysconf(_SC_CLK_TCK);
-      time_t seconds = (i==16) ? (get_uptime()*j)-slot[19] : slot[11]+slot[12];
-
-      seconds /= j;
-      for (s = 0, j = 0; j<4; j++) {
-        // TIME has 3 required fields, ETIME has 2. (Posix!)
-        if (!s && (seconds>unit || j == 1+(i==16))) s = out;
-        if (s) {
-          s += sprintf(s, j ? "%02ld": "%2ld", (long)(seconds/unit));
-          if ((*s = "-::"[j])) s++;
-        }
-        seconds %= unit;
-        unit /= j ? 60 : 24;
-      }
-
-    // COMMAND CMD
-    // Command line limited to 2k displayable. We could dynamically malloc, but
-    // it'd almost never get used, querying length of a proc file is awkward,
-    // fixed buffer is nommu friendly... Wait for somebody to complain. :)
-    } else if (i==14 || i==15) {
-      int fd;
-
-      len = 0;
-      sprintf(out, "%lld/cmdline", *slot);
-      fd = openat(dirtree_parentfd(new), out, O_RDONLY);
- 
-      if (fd != -1) {
-        if (0<(len = read(fd, out, 2047))) {
-          if (!out[len-1]) len--;
-          else out[len] = 0;
-          for (i = 0; i<len; i++) if (out[i] < ' ') out[i] = ' ';
-        }
-        close(fd);
-      }
-
-      if (len<1) sprintf(out, "[%.*s]", nlen, name);
-    // %CPU
-    } else if (i==18) {
-      ll = (get_uptime()*sysconf(_SC_CLK_TCK)-slot[19]);
-      len = ((slot[11]+slot[12])*1000)/ll;
-      sprintf(out, "%d.%d", len/10, len%10);
-    }
-
-    // Output the field, appropriately padded
-    len = width - (field != TT.fields);
-    if (!field->next && field->len<0) i = 0;
-    else {
-      i = len<abs(field->len) ? len : field->len;
-      len = abs(i);
-    }
-
-    // TODO test utf8 fontmetrics
-    width -= printf(" %*.*s" + (field == TT.fields), i, len, out);
-    if (!width) break;
-  }
-  xputc('\n');
-
-  return 0;
-}
-
-// Traverse arg_list of csv, calling callback on each value
-void comma_args(struct arg_list *al, char *err,
-  char *(*callback)(char *str, int len))
-{
-  char *next, *arg;
-  int len;
-
-  while (al) {
-    arg = al->arg;
-    while ((next = comma_iterate(&arg, &len)))
-      if ((next = callback(next, len)))
-        perror_exit("%s '%s'@%ld", err, al->arg, 1+next-al->arg);
-    al = al->next;
-  }
-}
-
-static char *parse_o(char *type, int length)
-{
-  struct strawberry *field;
-  char *width, *title, *end, *s, *typos[] = {
-         "F", "S", "UID", "PID", "PPID", "C", "PRI", "NI", "ADDR", "SZ",
-         "WCHAN", "STIME", "TTY", "TIME", "CMD", "COMMAND", "ELAPSED", "GROUP",
-         "%CPU", "PGID", "RGROUP", "RUSER", "USER", "VSZ", "RSS", "MAJFL",
-         "GID", "STAT", "RUID", "RGID", "MINFL"
-  };
-  signed char widths[] = {1,-1,5,5,5,2,3,3,4+sizeof(long),5,
-                          -6,5,-8,8,-27,-27,11,-8,
-                          4,5,-8,-8,-8,6,5,6,
-                          8,-5,4,4,6};
-  int i, j, k;
-
-  // Get title, length of title, type, end of type, and display width
-
-  // Chip off =name to display
-  if ((end = strchr(type, '=')) && length>(end-type)) {
-    title = end+1;
-    length -= (end-type)+1;
-  } else {
-    end = type+length;
-    title = 0;
-  }
-
-  // Chip off :width to display
-  if ((width = strchr(type, ':')) && width<end) {
-    if (!title) length = width-type;
-  } else width = 0;
-
-  // Allocate structure, copy title
-  field = xzalloc(sizeof(struct strawberry)+(length+1)*!!title);
-  if (title) {
-    memcpy(field->title = field->forever, title, length);
-    field->title[field->len = length] = 0;
-  }
-
-  if (width) {
-    field->len = strtol(++width, &title, 10);
-    if (!isdigit(*width) || title != end) return title;
-    end = --width;
-  }
-
-  // Find type
-  for (i = 0; i<ARRAY_LEN(typos); i++) {
-    field->which = i;
-    for (j = 0; j<2; j++) {
-      if (!j) s = typos[i];
-      // posix requires alternate names for some fields
-      else if (-1==(k = stridx((char []){7,14,15,16,18,23,22,0}, i))) continue;
-      else s = ((char *[]){"NICE","ARGS","COMM","ETIME","PCPU",
-                           "VSIZE","UNAME"})[k];
-
-      if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break;
-    }
-    if (j!=2) break;
-  }
-  if (i==ARRAY_LEN(typos)) return type;
-  if (!field->title) field->title = typos[field->which];
-  if (!field->len) field->len = widths[field->which];
-  else if (widths[field->which]<0) field->len *= -1;
-  dlist_add_nomalloc((void *)&TT.fields, (void *)field);
-
-  // Print padded header.
-  printf(" %*s" + (field == TT.fields), field->len, field->title);
-  TT.bits |= (i = 1<<field->which);
-
-  return 0;
-}
-
-// Parse -p -s -t -u -U -g -G
-static char *parse_rest(char *str, int len)
-{
-  struct ptr_len *pl = TT.parsing;
-  long *ll = pl->ptr;
-  char *end;
-  int num = 0;
-
-  // numeric: -p, -s
-  // gg, GG, pp, ss, tt, uu, UU, *parsing;
- 
-  // Allocate next chunk of data
-  if (!(15&pl->len))
-    ll = pl->ptr = xrealloc(pl->ptr, sizeof(long)*(pl->len+16));
-
-  // Parse numerical input
-  if (isdigit(*str)) {
-    ll[pl->len] = xstrtol(str, &end, 10);
-    if (end==(len+str)) num++;
-  }
-
-  if (pl==&TT.pp || pl==&TT.ss) {
-    if (num && ll[pl->len]>0) {
-      pl->len++;
-
-      return 0;
-    }
-  } else if (pl==&TT.tt) {
-    // -t pts = 12,pts/12 tty = /dev/tty2,tty2,S0
-    if (!num) {
-      if (strstart(&str, strcpy(toybuf, "/dev/"))) len -= 5;
-      if (strstart(&str, "pts/")) {
-        len -= 4;
-        num++;
-      } else if (strstart(&str, "tty")) len -= 3;
-    }
-    if (len<256 && (!(end = strchr(str, '/')) || end-str>len)) {
-      struct stat st;
-
-      end = toybuf + sprintf(toybuf, "/dev/%s", num ? "pts/" : "tty");
-      memcpy(end, str, len);
-      end[len] = 0;
-      xstat(toybuf, &st);
-      ll[pl->len++] = st.st_rdev;
-
-      return 0;
-    }
-  } else if (len<255) {
-    char name[256];
-
-    if (num) {
-      pl->len++;
-
-      return 0;
-    }
-
-    memcpy(name, str, len);
-    name[len] = 0;
-    if (pl==&TT.gg || pl==&TT.GG) {
-      struct group *gr = getgrnam(name);
-      if (gr) {
-        ll[pl->len++] = gr->gr_gid;
-
-        return 0;
-      }
-    } else if (pl==&TT.uu || pl==&TT.UU) {
-      struct passwd *pw = getpwnam(name);
-      if (pw) {
-        ll[pl->len++] = pw->pw_uid;
-
-        return 0;
-      }
-    }
-  }
-
-  // Return error
-  return str;
-}
-
-void ps_main(void)
-{
-  int i;
-
-  TT.width = 99999;
-  if (!(toys.optflags&FLAG_w)) terminal_size(&TT.width, 0);
-
-  // find controlling tty, falling back to /dev/tty if none
-  for (i = 0; !TT.tty && i<4; i++) {
-    struct stat st;
-    int fd = i;
-
-    if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break;
-
-    if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev;
-    if (i==3) close(fd);
-  }
-
-  // parse command line options other than -o
-  TT.parsing = &TT.pp;
-  comma_args(TT.p, "bad -p", parse_rest);
-  TT.parsing = &TT.tt;
-  comma_args(TT.t, "bad -t", parse_rest);
-  TT.parsing = &TT.ss;
-  comma_args(TT.s, "bad -s", parse_rest);
-  TT.parsing = &TT.uu;
-  comma_args(TT.u, "bad -u", parse_rest);
-  TT.parsing = &TT.UU;
-  comma_args(TT.U, "bad -u", parse_rest);
-  TT.parsing = &TT.gg;
-  comma_args(TT.g, "bad -g", parse_rest);
-  TT.parsing = &TT.GG;
-  comma_args(TT.G, "bad -G", parse_rest);
-
-  // Manual field selection, or default/-f/-l. Also prints header.
-  if (TT.o) comma_args(TT.o, "-o", parse_o);
-  else {
-    struct arg_list al;
-
-    al.next = 0;
-    if (toys.optflags&FLAG_f)
-      al.arg = "USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD";
-    else if (toys.optflags&FLAG_l)
-      al.arg = "F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD";
-    else al.arg = "PID,TTY,TIME,CMD";
-
-    comma_args(&al, 0, parse_o);
-  }
-  dlist_terminate(TT.fields);
-  xputc('\n');
-
-  dirtree_read("/proc", do_ps);
-
-  if (CFG_TOYBOX_FREE) {
-    free(TT.gg.ptr);
-    free(TT.GG.ptr);
-    free(TT.pp.ptr);
-    free(TT.ss.ptr);
-    free(TT.tt.ptr);
-    free(TT.uu.ptr);
-    free(TT.UU.ptr);
-    llist_traverse(TT.fields, free);
-  }
-}
diff --git a/toys/posix/ps.c b/toys/posix/ps.c
new file mode 100644
index 00000000..0dd4952d
--- /dev/null
+++ b/toys/posix/ps.c
@@ -0,0 +1,613 @@
+/* ps.c - show process list
+ *
+ * Copyright 2015 Rob Landley <rob@landley.net>
+ *
+ * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ps.html
+ * And http://kernel.org/doc/Documentation/filesystems/proc.txt Table 1-4
+ * And linux kernel source fs/proc/array.c function do_task_stat()
+ *
+ * Deviations from posix: no -n because /proc/self/wchan exists.
+ * Posix says default output should have field named "TTY" but if you "-o tty"
+ * the same field should be called "TT" which is _INSANE_ and I'm not doing it.
+ * Similarly -f outputs USER but calls it UID (we call it USER).
+ * It also says that -o "args" and "comm" should behave differently but use
+ * the same title, which is not the same title as the default output. (No.)
+ * Select by session id is -s not -g.
+ *
+ * Posix defines -o ADDR as "The address of the process" but the process
+ * start address is a constant on any elf system with mmu. The procps ADDR
+ * field always prints "-" with an alignment of 1, which is why it has 11
+ * characters left for "cmd" in in 80 column "ps -l" mode. On x86-64 you
+ * need 12 chars, leaving nothing for cmd: I.E. posix 2008 ps -l mode can't
+ * be sanely implemented on 64 bit Linux systems. In procps there's ps -y
+ * which changes -l by removing the "F" column and swapping RSS for ADDR,
+ * leaving 9 chars for cmd, so we're using that as our -l output.
+ *
+ * TODO: ps aux (att & bsd style "ps -ax" vs "ps ax" behavior difference)
+ * TODO: finalize F, remove C
+ *       switch -fl to -y, use "string" instead of constants to set, remove C
+ * TODO: --sort -Z
+ * TODO: way too many hardwired constants here, how can I generate them?
+ * TODO: thread support /proc/$d/task/%d/stat (and -o stat has "l")
+ *
+ * Design issue: the -o fields are an ordered array, and the order is
+ * significant. The array index is used in strawberry->which (consumed
+ * in do_ps()) and in the bitmasks enabling default fields in ps_main().
+
+USE_PS(NEWTOY(ps, "aAdeflo*p*s*t*u*U*g*G*w[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN))
+
+config PS
+  bool "ps"
+  default y
+  help
+    usage: ps [-Aadeflw] [-gG GROUP] [-o FIELD] [-p PID] [-t TTY] [-uU USER]
+
+    List processes.
+
+    Which processes to show (selections may be comma separated lists):
+
+    -A	All processes
+    -a	Processes with terminals that aren't session leaders
+    -d	All processes that aren't session leaders
+    -e	Same as -A
+    -g	belonging to GROUPs
+    -G	belonging to real GROUPs (before sgid)
+    -p	PIDs
+    -s	in session IDs
+    -t	attached to selected TTYs
+    -u	owned by USERs
+    -U	owned by real USERs (before suid)
+    -w	Wide output (don't truncate at terminal width)
+
+    Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD)
+
+    -f	Full listing (-o USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD)
+    -l	Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD)
+    -o	Output the listed FIELDs, each with optional :size and/or =title
+
+    Available -o FIELDs:
+
+      ADDR   Instruction pointer
+      CMD    Command line
+      ETIME  Elapsed time since process start
+      F      Process flags (PF_*) from linux source file include/sched.h
+             (in octal rather than hex because posix)
+      GID    Group id
+      GROUP  Group name
+      MAJFL  Major page faults
+      MINFL  Minor page faults
+      NI     Niceness of process (lower niceness is higher priority)
+      PCPU   Percentage of CPU time used
+      PGID   Process Group ID
+      PID    Process ID
+      PPID   Parent Process ID
+      PRI    Priority
+      RGID   Real (before sgid) group ID
+      RGROUP Real (before sgid) group name
+      RSS    Resident Set Size (memory currently used)
+      RUID   Real (before suid) user ID
+      RUSER  Real (before suid) user name
+      S      Process state:
+             R (running) S (sleeping) D (disk sleep) T (stopped)  t (traced)
+             Z (zombie)  X (dead)     x (dead)       K (wakekill) W (waking)
+      STAT   Process state (S) plus:
+             < high priority          N low priority L locked memory
+             s session leader         + foreground   l multithreaded
+      STIME  Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss)
+      SZ     Memory Size (4k pages needed to completely swap out process)
+      TIME   CPU time consumed
+      TTY    Controlling terminal
+      UID    User id
+      USER   User name
+      VSZ    Virtual memory size (1k units)
+      WCHAN  Waiting in kernel for
+*/
+
+#define FOR_ps
+#include "toys.h"
+
+GLOBALS(
+  struct arg_list *G;
+  struct arg_list *g;
+  struct arg_list *U;
+  struct arg_list *u;
+  struct arg_list *t;
+  struct arg_list *s;
+  struct arg_list *p;
+  struct arg_list *o;
+
+  struct ptr_len gg, GG, pp, ss, tt, uu, UU, *parsing;
+  unsigned width;
+  dev_t tty;
+  void *fields;
+  long bits;
+  long long ticks;
+)
+
+struct strawberry {
+  struct strawberry *next, *prev;
+  short which, len;
+  char *title;
+  char forever[];
+};
+
+static time_t get_uptime(void)
+{
+  struct sysinfo si;
+
+  sysinfo(&si);
+
+  return si.uptime;
+}
+
+// Return 1 to display, 0 to skip
+static int match_process(long long *slot)
+{
+  struct ptr_len *match[] = {&TT.gg,&TT.GG,&TT.pp,&TT.ss,&TT.tt,&TT.uu,&TT.UU};
+  int i, j, mslot[] = {33, 34, 0, 3, 4, 31, 32};
+  long *ll = 0;
+
+  // Do we have -g -G -p -s -t -u -U options selecting processes?
+  for (i = 0; i < ARRAY_LEN(match); i++) {
+    if (match[i]->len) {
+      ll = match[i]->ptr;
+      for (j = 0; j<match[i]->len; j++) if (ll[j] == slot[mslot[i]]) return 1;
+    }
+  }
+
+  // If we had selections and didn't match them, don't display
+  if (ll) return 0;
+
+  // Filter implicit categories for other display types
+  if ((toys.optflags&(FLAG_a|FLAG_d)) && slot[3]==*slot) return 0;
+  if ((toys.optflags&FLAG_a) && !slot[4]) return 0;
+  if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e)) && TT.tty!=slot[4])
+    return 0;
+
+  return 1;
+}
+
+// dirtree callback.
+// toybuf used as: 1024 /proc/$PID/stat, 1024 slot[], 2048 /proc/$PID/cmdline
+static int do_ps(struct dirtree *new)
+{
+  struct strawberry *field;
+  long long *slot = (void *)(toybuf+1024), ll;
+  char *name, *s, state;
+  int nlen, i, fd, len, width = TT.width;
+
+  if (!new->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP;
+  if (!(*slot = atol(new->name))) return 0;
+
+  // name field limited to 256 bytes by VFS, plus 40 fields * max 20 chars:
+  // 1000-ish total, but some forced zero so actually there's headroom.
+  sprintf(toybuf, "%lld/stat", *slot);
+  if (!readfileat(dirtree_parentfd(new), toybuf, toybuf, 1024)) return 0;
+
+  // parse oddball fields (name and state)
+  if (!(s = strchr(toybuf, '('))) return 0;
+  for (name = ++s; *s != ')'; s++) if (!*s) return 0;
+  nlen = s++-name;
+  if (1>sscanf(++s, " %c%n", &state, &i)) return 0;
+
+  // parse numeric fields (PID = 0, skip 2, then 4th field goes in slot[1])
+  for (len = 1; len<100; len++)
+    if (1>sscanf(s += i, " %lld%n", slot+len, &i)) break;
+
+  // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch
+  // or numeric wchan, and the remaining two are always zero).
+  slot[31] = new->st.st_uid;
+  slot[33] = new->st.st_gid;
+
+  // If RGROUP RUSER STAT RUID RGID
+  // Save ruid in slot[34] and rgid in slot[35], which are otherwise zero,
+  // and vmlck into slot[18] (it_real_value, also always zero).
+  if ((TT.bits & 0x38300000) || TT.GG.len || TT.UU.len) {
+    char *out = toybuf+2048;
+
+    sprintf(out, "%lld/status", *slot);
+    if (!readfileat(dirtree_parentfd(new), out, out, 2048)) *out = 0;
+    s = strstr(out, "\nUid:");
+    slot[32] = s ? atol(s+5) : new->st.st_uid;
+    s = strstr(out, "\nGid:");
+    slot[34] = s ? atol(s+5) : new->st.st_gid;
+    s = strstr(out, "\nVmLck:");
+    if (s) slot[18] = atoll(s+5);
+  }
+
+  // skip processes we don't care about.
+  if (!match_process(slot)) return 0;
+
+  // At this point 512 bytes at toybuf+512 are free (already parsed).
+  // Start of toybuf still has name in it.
+
+  // Loop through fields
+  for (field = TT.fields; field; field = field->next) {
+    char *out = toybuf+2048, *scratch = toybuf+512;
+
+    // Default: unsupported (5 "C")
+    sprintf(out, "-");
+
+    // PID, PPID, PRI, NI, ADDR, SZ, RSS, PGID, VSS, MAJFL, MINFL
+    if (-1!=(i = stridx((char[]){3,4,6,7,8,9,24,19,23,25,30,0}, field->which)))
+    {
+      char *fmt = "%lld";
+
+      ll = slot[((char[]){0,1,15,16,27,20,21,2,20,9,7})[i]];
+      if (i==2) ll--;
+      if (i==4) fmt = "%llx";
+      else if (i==5) ll >>= 12;
+      else if (i==6) ll <<= 2;
+      else if (i==8) ll >>= 10;
+      sprintf(out, fmt, ll);
+    // UID USER RUID RUSER GID GROUP RGID RGROUP
+    } else if (-1!=(i = stridx((char[]){2,22,28,21,26,17,29,20}, field->which)))
+    {
+      int id = slot[31+i/2]; // uid, ruid, gid, rgid
+
+      // Even entries are numbers, odd are names
+      sprintf(out, "%d", id);
+      if (i&1) {
+        if (i>3) {
+          struct group *gr = getgrgid(id);
+
+          if (gr) out = gr->gr_name;
+        } else {
+          struct passwd *pw = getpwuid(id);
+
+          if (pw) out = pw->pw_name;
+        }
+      }
+ 
+    // F (also assignment of i used by later tests)
+    // Posix doesn't specify what flags should say. Man page says
+    // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h
+    } else if (!(i = field->which)) sprintf(out, "%llo", (slot[6]>>6)&5);
+    // S STAT
+    else if (i==1 || i==27) {
+      sprintf(out, "%c", state);
+      if (i==27) {
+        // TODO l = multithreaded
+        s = out+1;
+        if (slot[16]<0) *s++ = '<';
+        else if (slot[16]>0) *s++ = 'N';
+        if (slot[3]==*slot) *s++ = 's';
+        if (slot[18]) *s++ = 'L';
+        if (slot[5]==*slot) *s++ = '+';
+        *s = 0;
+      } 
+    // WCHAN
+    } else if (i==10) {
+      sprintf(scratch, "%lld/wchan", *slot);
+      readfileat(dirtree_parentfd(new), scratch, out, 2047);
+
+    // STIME
+    } else if (i==11) {
+      time_t t = time(0) - get_uptime() + slot[19]/sysconf(_SC_CLK_TCK);
+
+      // Padding behavior's a bit odd: default field size is just hh:mm.
+      // Increasing stime:size reveals more data at left until full
+      // yyyy-mm-dd hh:mm revealed at :16, then adds :ss at end for :19. But
+      // expanding last field just adds :ss.
+      strftime(scratch, 512, "%F %T", localtime(&t));
+      out = scratch+strlen(scratch)-3-abs(field->len);
+      if (out<scratch) out = scratch;
+
+    // TTY
+    } else if (i==12) {
+
+      // Can we readlink() our way to a name?
+      for (i=0; i<3; i++) {
+        struct stat st;
+
+        sprintf(scratch, "%lld/fd/%i", *slot, i);
+        fd = dirtree_parentfd(new);
+        if (!fstatat(fd, scratch, &st, 0) && S_ISCHR(st.st_mode)
+          && st.st_rdev == slot[4]
+          && 0<(len = readlinkat(fd, scratch, out, 2047)))
+        {
+          out[len] = 0;
+          if (!strncmp(out, "/dev/", 5)) out += 5;
+
+          break;
+        }
+      }
+
+      // Couldn't find it, show major:minor
+      if (i==3) {
+        i = slot[4];
+        sprintf(out, "%d:%d", (i>>8)&0xfff, ((i>>12)&0xfff00)|(i&0xff));
+      }
+
+    // TIME ELAPSED
+    } else if (i==13 || i==16) {
+      int unit = 60*60*24, j = sysconf(_SC_CLK_TCK);
+      time_t seconds = (i==16) ? (get_uptime()*j)-slot[19] : slot[11]+slot[12];
+
+      seconds /= j;
+      for (s = 0, j = 0; j<4; j++) {
+        // TIME has 3 required fields, ETIME has 2. (Posix!)
+        if (!s && (seconds>unit || j == 1+(i==16))) s = out;
+        if (s) {
+          s += sprintf(s, j ? "%02ld": "%2ld", (long)(seconds/unit));
+          if ((*s = "-::"[j])) s++;
+        }
+        seconds %= unit;
+        unit /= j ? 60 : 24;
+      }
+
+    // COMMAND CMD
+    // Command line limited to 2k displayable. We could dynamically malloc, but
+    // it'd almost never get used, querying length of a proc file is awkward,
+    // fixed buffer is nommu friendly... Wait for somebody to complain. :)
+    } else if (i==14 || i==15) {
+      int fd;
+
+      len = 0;
+      sprintf(out, "%lld/cmdline", *slot);
+      fd = openat(dirtree_parentfd(new), out, O_RDONLY);
+ 
+      if (fd != -1) {
+        if (0<(len = read(fd, out, 2047))) {
+          if (!out[len-1]) len--;
+          else out[len] = 0;
+          for (i = 0; i<len; i++) if (out[i] < ' ') out[i] = ' ';
+        }
+        close(fd);
+      }
+
+      if (len<1) sprintf(out, "[%.*s]", nlen, name);
+    // %CPU
+    } else if (i==18) {
+      ll = (get_uptime()*sysconf(_SC_CLK_TCK)-slot[19]);
+      len = ((slot[11]+slot[12])*1000)/ll;
+      sprintf(out, "%d.%d", len/10, len%10);
+    }
+
+    // Output the field, appropriately padded
+    len = width - (field != TT.fields);
+    if (!field->next && field->len<0) i = 0;
+    else {
+      i = len<abs(field->len) ? len : field->len;
+      len = abs(i);
+    }
+
+    // TODO test utf8 fontmetrics
+    width -= printf(" %*.*s" + (field == TT.fields), i, len, out);
+    if (!width) break;
+  }
+  xputc('\n');
+
+  return 0;
+}
+
+// Traverse arg_list of csv, calling callback on each value
+void comma_args(struct arg_list *al, char *err,
+  char *(*callback)(char *str, int len))
+{
+  char *next, *arg;
+  int len;
+
+  while (al) {
+    arg = al->arg;
+    while ((next = comma_iterate(&arg, &len)))
+      if ((next = callback(next, len)))
+        perror_exit("%s '%s'@%ld", err, al->arg, 1+next-al->arg);
+    al = al->next;
+  }
+}
+
+static char *parse_o(char *type, int length)
+{
+  struct strawberry *field;
+  char *width, *title, *end, *s, *typos[] = {
+         "F", "S", "UID", "PID", "PPID", "C", "PRI", "NI", "ADDR", "SZ",
+         "WCHAN", "STIME", "TTY", "TIME", "CMD", "COMMAND", "ELAPSED", "GROUP",
+         "%CPU", "PGID", "RGROUP", "RUSER", "USER", "VSZ", "RSS", "MAJFL",
+         "GID", "STAT", "RUID", "RGID", "MINFL"
+  };
+  signed char widths[] = {1,-1,5,5,5,2,3,3,4+sizeof(long),5,
+                          -6,5,-8,8,-27,-27,11,-8,
+                          4,5,-8,-8,-8,6,5,6,
+                          8,-5,4,4,6};
+  int i, j, k;
+
+  // Get title, length of title, type, end of type, and display width
+
+  // Chip off =name to display
+  if ((end = strchr(type, '=')) && length>(end-type)) {
+    title = end+1;
+    length -= (end-type)+1;
+  } else {
+    end = type+length;
+    title = 0;
+  }
+
+  // Chip off :width to display
+  if ((width = strchr(type, ':')) && width<end) {
+    if (!title) length = width-type;
+  } else width = 0;
+
+  // Allocate structure, copy title
+  field = xzalloc(sizeof(struct strawberry)+(length+1)*!!title);
+  if (title) {
+    memcpy(field->title = field->forever, title, length);
+    field->title[field->len = length] = 0;
+  }
+
+  if (width) {
+    field->len = strtol(++width, &title, 10);
+    if (!isdigit(*width) || title != end) return title;
+    end = --width;
+  }
+
+  // Find type
+  for (i = 0; i<ARRAY_LEN(typos); i++) {
+    field->which = i;
+    for (j = 0; j<2; j++) {
+      if (!j) s = typos[i];
+      // posix requires alternate names for some fields
+      else if (-1==(k = stridx((char []){7,14,15,16,18,23,22,0}, i))) continue;
+      else s = ((char *[]){"NICE","ARGS","COMM","ETIME","PCPU",
+                           "VSIZE","UNAME"})[k];
+
+      if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break;
+    }
+    if (j!=2) break;
+  }
+  if (i==ARRAY_LEN(typos)) return type;
+  if (!field->title) field->title = typos[field->which];
+  if (!field->len) field->len = widths[field->which];
+  else if (widths[field->which]<0) field->len *= -1;
+  dlist_add_nomalloc((void *)&TT.fields, (void *)field);
+
+  // Print padded header.
+  printf(" %*s" + (field == TT.fields), field->len, field->title);
+  TT.bits |= (i = 1<<field->which);
+
+  return 0;
+}
+
+// Parse -p -s -t -u -U -g -G
+static char *parse_rest(char *str, int len)
+{
+  struct ptr_len *pl = TT.parsing;
+  long *ll = pl->ptr;
+  char *end;
+  int num = 0;
+
+  // numeric: -p, -s
+  // gg, GG, pp, ss, tt, uu, UU, *parsing;
+ 
+  // Allocate next chunk of data
+  if (!(15&pl->len))
+    ll = pl->ptr = xrealloc(pl->ptr, sizeof(long)*(pl->len+16));
+
+  // Parse numerical input
+  if (isdigit(*str)) {
+    ll[pl->len] = xstrtol(str, &end, 10);
+    if (end==(len+str)) num++;
+  }
+
+  if (pl==&TT.pp || pl==&TT.ss) {
+    if (num && ll[pl->len]>0) {
+      pl->len++;
+
+      return 0;
+    }
+  } else if (pl==&TT.tt) {
+    // -t pts = 12,pts/12 tty = /dev/tty2,tty2,S0
+    if (!num) {
+      if (strstart(&str, strcpy(toybuf, "/dev/"))) len -= 5;
+      if (strstart(&str, "pts/")) {
+        len -= 4;
+        num++;
+      } else if (strstart(&str, "tty")) len -= 3;
+    }
+    if (len<256 && (!(end = strchr(str, '/')) || end-str>len)) {
+      struct stat st;
+
+      end = toybuf + sprintf(toybuf, "/dev/%s", num ? "pts/" : "tty");
+      memcpy(end, str, len);
+      end[len] = 0;
+      xstat(toybuf, &st);
+      ll[pl->len++] = st.st_rdev;
+
+      return 0;
+    }
+  } else if (len<255) {
+    char name[256];
+
+    if (num) {
+      pl->len++;
+
+      return 0;
+    }
+
+    memcpy(name, str, len);
+    name[len] = 0;
+    if (pl==&TT.gg || pl==&TT.GG) {
+      struct group *gr = getgrnam(name);
+      if (gr) {
+        ll[pl->len++] = gr->gr_gid;
+
+        return 0;
+      }
+    } else if (pl==&TT.uu || pl==&TT.UU) {
+      struct passwd *pw = getpwnam(name);
+      if (pw) {
+        ll[pl->len++] = pw->pw_uid;
+
+        return 0;
+      }
+    }
+  }
+
+  // Return error
+  return str;
+}
+
+void ps_main(void)
+{
+  int i;
+
+  TT.width = 99999;
+  if (!(toys.optflags&FLAG_w)) terminal_size(&TT.width, 0);
+
+  // find controlling tty, falling back to /dev/tty if none
+  for (i = 0; !TT.tty && i<4; i++) {
+    struct stat st;
+    int fd = i;
+
+    if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break;
+
+    if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev;
+    if (i==3) close(fd);
+  }
+
+  // parse command line options other than -o
+  TT.parsing = &TT.pp;
+  comma_args(TT.p, "bad -p", parse_rest);
+  TT.parsing = &TT.tt;
+  comma_args(TT.t, "bad -t", parse_rest);
+  TT.parsing = &TT.ss;
+  comma_args(TT.s, "bad -s", parse_rest);
+  TT.parsing = &TT.uu;
+  comma_args(TT.u, "bad -u", parse_rest);
+  TT.parsing = &TT.UU;
+  comma_args(TT.U, "bad -u", parse_rest);
+  TT.parsing = &TT.gg;
+  comma_args(TT.g, "bad -g", parse_rest);
+  TT.parsing = &TT.GG;
+  comma_args(TT.G, "bad -G", parse_rest);
+
+  // Manual field selection, or default/-f/-l. Also prints header.
+  if (TT.o) comma_args(TT.o, "-o", parse_o);
+  else {
+    struct arg_list al;
+
+    al.next = 0;
+    if (toys.optflags&FLAG_f)
+      al.arg = "USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD";
+    else if (toys.optflags&FLAG_l)
+      al.arg = "F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD";
+    else al.arg = "PID,TTY,TIME,CMD";
+
+    comma_args(&al, 0, parse_o);
+  }
+  dlist_terminate(TT.fields);
+  xputc('\n');
+
+  dirtree_read("/proc", do_ps);
+
+  if (CFG_TOYBOX_FREE) {
+    free(TT.gg.ptr);
+    free(TT.GG.ptr);
+    free(TT.pp.ptr);
+    free(TT.ss.ptr);
+    free(TT.tt.ptr);
+    free(TT.uu.ptr);
+    free(TT.UU.ptr);
+    llist_traverse(TT.fields, free);
+  }
+}
-- 
cgit v1.2.3