OSDN Git Service

Fix "ps -T 1234" to show thread belonging to that PID.
[android-x86/external-toybox.git] / toys / posix / ps.c
index e5adc19..7741dcd 100644 (file)
@@ -13,7 +13,8 @@
  * 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.
+ * Select by session id is -s not -g. Posix doesn't say truncated fields
+ * should end with "+" but it's pretty common behavior.
  *
  * 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
 
 USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflMno*O*p(pid)*s*t*Tu*U*g*G*wZ[!ol][+Ae][!oO]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
 // stayroot because iotop needs root to read other process' proc/$$/io
-USE_TOP(NEWTOY(top, ">0m" "O*Hk*o*p*u*s#<1d#=3<1n#<1bq[!oO]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
-USE_IOTOP(NEWTOY(iotop, ">0AaKO" "k*o*p*u*s#<1=7d#=3<1n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE))
+// TOP and IOTOP have a large common option block used for common processing,
+// the default values are different but the flags are in the same order.
+USE_TOP(NEWTOY(top, ">0O*" "Hk*o*p*u*s#<1d#=3<1m#n#<1bq[!oO]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
+USE_IOTOP(NEWTOY(iotop, ">0AaKO" "Hk*o*p*u*s#<1=7d#=3<1m#n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE))
 USE_PGREP(NEWTOY(pgrep, "?cld:u*U*t*s*P*g*G*fnovxL:[-no]", TOYFLAG_USR|TOYFLAG_BIN))
 USE_PKILL(NEWTOY(pkill,    "?Vu*U*t*s*P*g*G*fnovxl:[-no]", TOYFLAG_USR|TOYFLAG_BIN))
 
@@ -79,11 +82,11 @@ config PS
     -k Sort FIELDs in +increasing or -decreasting order (--sort)
     -M Measure field widths (expanding as necessary)
     -n Show numeric USER and GROUP
-    -w Wide output (don't truncate at terminal width)
+    -w Wide output (don't truncate fields)
 
     Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD)
 
-    -f Full listing (-o USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD)
+    -f Full listing (-o USER:12=UID,PID,PPID,C,STIME,TTY,TIME,ARGS=CMD)
     -l Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD)
     -o Output FIELDs instead of defaults, each with optional :size and =title
     -O Add FIELDS to defaults
@@ -91,9 +94,9 @@ config PS
 
     Command line -o fields:
 
-      ARGS     Command line (argv[] -path)    CMD   COMM, or ARGS with -f
-      CMDLINE  Command line (argv[])          COMM  Original command name (stat[2])
-      COMMAND  Command name (argv[0])         NAME  Command name (argv[0] -path)
+      ARGS     CMDLINE minus initial path     CMD  Command (thread) name (stat[2])
+      CMDLINE  Command line (argv[])          COMM Command filename (/proc/$PID/exe)
+      COMMAND  Command file (/proc/$PID/exe)  NAME Process name (argv[0] of $PID)
 
     Process attribute -o FIELDs:
 
@@ -127,10 +130,9 @@ config PS
 
 config TOP
   bool "top"
-  depends on TOP_COMMON
   default y
   help
-    usage: top [-H] [-k FIELD,] [-o FIELD,] [-s SORT]
+    usage: top [-Hbq] [-k FIELD,] [-o FIELD,] [-s SORT] [-n NUMBER] [-m LINES] [-d SECONDS] [-p PID,] [-u USER,]
 
     Show process activity in real time.
 
@@ -139,31 +141,35 @@ config TOP
     -o Show FIELDS (def PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,CMDLINE)
     -O Add FIELDS (replacing PR,NI,VIRT,RES,SHR,S from default)
     -s Sort by field number (1-X, default 9)
+    -b Batch mode (no tty)
+    -d Delay SECONDS between each cycle (default 3)
+    -m Maximum number of tasks to show
+    -n Exit after NUMBER iterations
+    -p Show these PIDs
+    -u Show these USERs
+    -q Quiet (no header lines)
+
+    Cursor LEFT/RIGHT to change sort, UP/DOWN move list, space to force
+    update, R to reverse sort, Q to exit.
 
 # Requires CONFIG_IRQ_TIME_ACCOUNTING in the kernel for /proc/$$/io
 config IOTOP
   bool "iotop"
-  depends on TOP_COMMON
   default y
   help
-    usage: iotop [-AaKO]
+    usage: iotop [-AaKObq] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,]
 
     Rank processes by I/O.
 
     -A All I/O, not just disk
     -a Accumulated I/O (not percentage)
+    -H Show threads
     -K Kilobytes
     -k Fallback sort FIELDS (default -[D]IO,-ETIME,-PID)
+    -m Maximum number of tasks to show
     -O Only show processes doing I/O
     -o Show FIELDS (default PID,PR,USER,[D]READ,[D]WRITE,SWAP,[D]IO,COMM)
     -s Sort by field number (0-X, default 6)
-
-config TOP_COMMON
-  bool
-  default y
-  help
-    usage: * [-bq] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,]
-
     -b Batch mode (no tty)
     -d Delay SECONDS between each cycle (default 3)
     -n Exit after NUMBER iterations
@@ -177,9 +183,8 @@ config TOP_COMMON
 config PGREP
   bool "pgrep"
   default y
-  depends on PGKILL_COMMON
   help
-    usage: pgrep [-cL] [-d DELIM] [-L SIGNAL] [PATTERN]
+    usage: pgrep [-clfnovx] [-d DELIM] [-L SIGNAL] [PATTERN] [-G GID,] [-g PGRP,] [-P PPID,] [-s SID,] [-t TERM,] [-U UID,] [-u EUID,]
 
     Search for process(es). PATTERN is an extended regular expression checked
     against command names.
@@ -188,23 +193,27 @@ config PGREP
     -d Use DELIM instead of newline
     -L Send SIGNAL instead of printing name
     -l Show command name
+    -f Check full command line for PATTERN
+    -G Match real Group ID(s)
+    -g Match Process Group(s) (0 is current user)
+    -n Newest match only
+    -o Oldest match only
+    -P Match Parent Process ID(s)
+    -s Match Session ID(s) (0 for current)
+    -t Match Terminal(s)
+    -U Match real User ID(s)
+    -u Match effective User ID(s)
+    -v Negate the match
+    -x Match whole command (not substring)
 
 config PKILL
   bool "pkill"
   default y
-  depends on PGKILL_COMMON
   help
-    usage: pkill [-SIGNAL|-l SIGNAL] [PATTERN]
+    usage: pkill [-fnovx] [-SIGNAL|-l SIGNAL] [PATTERN] [-G GID,] [-g PGRP,] [-P PPID,] [-s SID,] [-t TERM,] [-U UID,] [-u EUID,]
 
     -l Send SIGNAL (default SIGTERM)
     -V verbose
-
-config PGKILL_COMMON
-  bool
-  default y
-  help
-    usage: * [-fnovx] [-G GID,] [-g PGRP,] [-P PPID,] [-s SID,] [-t TERM,] [-U UID,] [-u EUID,]
-
     -f Check full command line for PATTERN
     -G Match real Group ID(s)
     -g Match Process Group(s) (0 is current user)
@@ -239,6 +248,7 @@ GLOBALS(
     } ps;
     struct {
       long n;
+      long m;
       long d;
       long s;
       struct arg_list *u;
@@ -276,6 +286,8 @@ GLOBALS(
   void (*show_process)(void *tb);
 )
 
+/* Linked list of fields selected for display, in order, with :len and =title */
+
 struct strawberry {
   struct strawberry *next, *prev;
   short which, len, reverse;
@@ -283,8 +295,15 @@ struct strawberry {
   char forever[];
 };
 
-/* The slot[] array is mostly populated from /proc/$PID/stat (kernel proc.txt
- * table 1-4) but we shift and repurpose fields, with the result being: */
+/* The function get_ps() reads all the data about one process, saving it in
+ * toybox as a struct carveup. Simple ps calls then pass toybuf directly to
+ * show_ps(), but features like sorting instead append a copy to a linked list
+ * for further processing once all processes have been read.
+ *
+ * struct carveup contains a slot[] array of 64 bit values, with the following
+ * data at each position in the array. Most is read from /proc/$PID/stat (see
+ * https://kernel.org/doc/Documentation/filesystems/proc.txt table 1-4) but
+ * we we replace several fields with don't use with other data. */
 
 enum {
  SLOT_pid,      /*process id*/            SLOT_ppid,      // parent process id
@@ -293,7 +312,7 @@ enum {
  SLOT_flags,    /*task flags*/            SLOT_minflt,    // minor faults
  SLOT_cminflt,  /*minor faults+child*/    SLOT_majflt,    // major faults
  SLOT_cmajflt,  /*major faults+child*/    SLOT_utime,     // user+kernel jiffies
- SLOT_stime,    /*kernel mode jiffies*/   SLOT_cutime,    // utime+child
+ SLOT_stime,    /*kernel mode jiffies*/   SLOT_cutime,    // utime+child utime
  SLOT_cstime,   /*stime+child*/           SLOT_priority,  // priority level
  SLOT_nice,     /*nice level*/            SLOT_numthreads,// thread count
  SLOT_vmlck,    /*locked memory*/         SLOT_starttime, // jiffies after boot
@@ -309,7 +328,7 @@ enum {
  SLOT_policy,   /*man sched_setscheduler*/SLOT_blkioticks,// IO wait time
  SLOT_gtime,    /*guest jiffies of task*/ SLOT_cgtime,    // gtime+child
  SLOT_startbss, /*data/bss address*/      SLOT_endbss,    // end addr data+bss
- SLOT_upticks,  /*46-19 (divisor for %)*/ SLOT_argv0len,  // argv[0] length
+ SLOT_upticks,  /*uptime-starttime*/      SLOT_argv0len,  // argv[0] length
  SLOT_uptime,   /*si.uptime @read time*/  SLOT_vsz,       // Virtual mem Size
  SLOT_rss2,     /*Resident Set Size*/     SLOT_shr,       // Shared memory
  SLOT_rchar,    /*All bytes read*/        SLOT_wchar,     // All bytes written
@@ -318,57 +337,85 @@ enum {
  SLOT_tid,      /*Thread ID*/             SLOT_tcount,    // Thread count
  SLOT_pcy,      /*Android sched policy*/
 
- SLOT_count
+ SLOT_count /* Size of array */
 };
 
+/* In addition to slot[], carevup contains 6 string fields to display
+   command name, tty device, selinux label... They're stored one after the
+   other in str[] (separated by null terminators), and offset[] contains the
+   starting position of each string after the first (which is always 0). */
+
 // Data layout in toybuf
 struct carveup {
   long long slot[SLOT_count]; // data (see enum above)
-  unsigned short offset[5];   // offset of fields in str[] (skip name, always 0)
+  unsigned short offset[6];   // offset of fields in str[] (skip CMD, always 0)
   char state;
-  char str[];                 // name, tty, command, wchan, attr, cmdline
+  char str[];                 // CMD, TTY, WCHAN, LABEL, COMM, ARGS, NAME
 };
 
+/* The typos[] array lists all the types understood by "ps -o", I.E all the
+ * columns ps and top know how to display. Each entry has:
+ *
+ * name: the column name, displayed at top and used to select column with -o
+ *
+ * width: the display width. Fields are padded to this width when displaying
+ *        to a terminal (negative means right justified). Strings are truncated
+ *        to fit, numerical fields are padded but not truncated (although
+ *        the display code reclaims unused padding from later fields to try to
+ *        get the overflow back).
+ *
+ * slot: which slot[] out of carveup. Negative means it's a string field.
+ *       Setting bit |64 requests extra display/sort processing.
+ *
+ * The TAGGED_ARRAY plumbing produces an enum of indexes, the "tag" is the
+ * first string argument and the prefix is the first argument to TAGGED_ARRAY
+ * so in this case "NAME" becomes PS_NAME which is the offset into typos[]
+ * for that entry, and also _PS_NAME (the bit position, 1<<PS_NAME).
+ * We record active columns in TT.bits, ala:
+ *
+ *   if (TT.bits & _PS_NAME) printf("-o included PS_NAME");
+ */
+
 // TODO: Android uses -30 for LABEL, but ideally it would auto-size.
 // 64|slot means compare as string when sorting
 struct typography {
   char *name;
   signed char width, slot;
 } static const typos[] = TAGGED_ARRAY(PS,
-  // Numbers
+  // Numbers. (What's in slot[] is what's displayed, sorted numerically.)
   {"PID", 5, SLOT_pid}, {"PPID", 5, SLOT_ppid}, {"PRI", 3, SLOT_priority},
   {"NI", 3, SLOT_nice}, {"ADDR", 4+sizeof(long), SLOT_eip},
-  {"SZ", 5, SLOT_vsize}, {"RSS", 5, SLOT_rss}, {"PGID", 5, SLOT_pgrp},
-  {"VSZ", 6, SLOT_vsize}, {"MAJFL", 6, SLOT_majflt}, {"MINFL", 6, SLOT_minflt},
+  {"SZ", 5, SLOT_vsize}, {"RSS", 6, SLOT_rss}, {"PGID", 5, SLOT_pgrp},
+  {"VSZ", 7, SLOT_vsize}, {"MAJFL", 6, SLOT_majflt}, {"MINFL", 6, SLOT_minflt},
   {"PR", 2, SLOT_priority}, {"PSR", 3, SLOT_taskcpu},
   {"RTPRIO", 6, SLOT_rtprio}, {"SCH", 3, SLOT_policy}, {"CPU", 3, SLOT_taskcpu},
   {"TID", 5, SLOT_tid}, {"TCNT", 4, SLOT_tcount}, {"BIT", 3, SLOT_bits},
 
-  // String fields
-  {"COMM", -15, -1}, {"TTY", -8, -2}, {"WCHAN", -6, -3}, {"LABEL", -30, -4},
-  {"COMMAND", -27, -5}, {"CMDLINE", -27, -6}, {"ARGS", -27, -6},
-  {"NAME", -15, -6}, {"CMD", -27, -1},
+  // String fields (-1 is carveup->str, rest are str+offset[1-slot])
+  {"TTY", -8, -2}, {"WCHAN", -6, -3}, {"LABEL", -30, -4}, {"COMM", -27, -5},
+  {"NAME", -27, -7}, {"COMMAND", -27, -5}, {"CMDLINE", -27, -6},
+  {"ARGS", -27, -6}, {"CMD", -15, -1},
 
-  // user/group
-  {"UID", 5, SLOT_uid}, {"USER", -8, 64|SLOT_uid}, {"RUID", 4, SLOT_ruid},
+  // user/group (may call getpwuid() or similar)
+  {"UID", 5, SLOT_uid}, {"USER", -12, 64|SLOT_uid}, {"RUID", 4, SLOT_ruid},
   {"RUSER", -8, 64|SLOT_ruid}, {"GID", 8, SLOT_gid}, {"GROUP", -8, 64|SLOT_gid},
   {"RGID", 4, SLOT_rgid}, {"RGROUP", -8, 64|SLOT_rgid},
 
-  // clock displays
+  // clock displays (00:00:00)
   {"TIME", 8, SLOT_utime}, {"ELAPSED", 11, SLOT_starttime},
   {"TIME+", 9, SLOT_utime},
 
-  // Percentage displays
+  // Percentage displays (fixed point, one decimal digit. 123 -> 12.3)
   {"C", 1, SLOT_utime2}, {"%VSZ", 5, SLOT_vsize}, {"%MEM", 5, SLOT_rss},
   {"%CPU", 4, SLOT_utime2},
 
-  // human_readable
+  // human_readable (function human_readable() in lib, 1.23M, 1.4G, etc)
   {"VIRT", 4, SLOT_vsz}, {"RES", 4, SLOT_rss2},
   {"SHR", 4, SLOT_shr}, {"READ", 6, SLOT_rchar}, {"WRITE", 6, SLOT_wchar},
   {"IO", 6, SLOT_iobytes}, {"DREAD", 6, SLOT_rbytes},
   {"DWRITE", 6, SLOT_wbytes}, {"SWAP", 6, SLOT_swap}, {"DIO", 6, SLOT_diobytes},
 
-  // Misc
+  // Misc (special cases)
   {"STIME", 5, SLOT_starttime}, {"F", 1, 64|SLOT_flags}, {"S", -1, 64},
   {"STAT", -5, 64}, {"PCY", 3, 64|SLOT_pcy},
 );
@@ -438,17 +485,19 @@ static char *string_field(struct carveup *tb, struct strawberry *field)
 
   // String fields
   } else if (sl < 0) {
-    // If there's data after argv[0], insert null or space as appropriate
-    if (slot[SLOT_argv0len])
-      tb->str[tb->offset[4]+slot[SLOT_argv0len]] = (which==PS_NAME) ? 0 : ' ';
     out = tb->str;
     sl *= -1;
     // First string slot has offset 0, others are offset[-slot-2]
     if (--sl) out += tb->offset[--sl];
-    if (which==PS_ARGS)
-      for (s = out; *s && *s != ' '; s++) if (*s == '/') out = s+1;
-    if (which>PS_COMMAND && (!*out || *slot != slot[SLOT_tid]))
-      sprintf(out = buf, "[%s]", tb->str);
+    if (which==PS_ARGS || which==PS_COMM) {
+      int i;
+
+      s = out;
+      for (i = 0; (which==PS_ARGS) ? i < slot[SLOT_argv0len] : out[i]; i++)
+        if (out[i] == '/') s = out+i+1;
+      out = s;
+    }
+    if (which>=PS_COMM && !*out) sprintf(out = buf, "[%s]", tb->str);
 
   // user/group
   } else if (which <= PS_RGROUP) {
@@ -542,16 +591,20 @@ static char *string_field(struct carveup *tb, struct strawberry *field)
 }
 
 // Display process data that get_ps() read from /proc, formatting with TT.fields
-static void show_ps(struct carveup *tb)
+static void show_ps(void *p)
 {
+  struct carveup *tb = p;
   struct strawberry *field;
-  int pad, len, width = TT.width, abslen, adjlen, olen, extra = 0;
+  int pad, len, width = TT.width, abslen, sign, olen, extra = 0;
 
   // Loop through fields to display
   for (field = TT.fields; field; field = field->next) {
     char *out = string_field(tb, field);
 
     // Output the field, appropriately padded
+
+    // Minimum one space between each field
+    if (width<2) break;
     if (field != TT.fields) {
       putchar(' ');
       width--;
@@ -560,26 +613,41 @@ static void show_ps(struct carveup *tb)
     // Don't truncate number fields, but try to reclaim extra offset from later
     // fields that can naturally be shorter
     abslen = abs(field->len);
-    adjlen = field->len;
-    if (field->which<=PS_BIT || extra) olen = strlen(out);
-    if (field->which<=PS_BIT && olen>abslen) {
+    sign = field->len<0 ? -1 : 1;
+    olen = (TT.tty) ? utf8len(out) : strlen(out);
+    if ((field->which<=PS_BIT || (toys.optflags&FLAG_w)) && olen>abslen) {
+      // overflow but remember by how much
       extra += olen-abslen;
       abslen = olen;
-      adjlen = (adjlen<0) ? -olen : olen;
     } else if (extra && olen<abslen) {
-      olen = abslen-olen;
-      if (olen>extra) olen = extra;
-      abslen -= olen;
-      adjlen -= (adjlen<0) ? -olen : olen;
-      extra -= olen;
+      int unused = abslen-olen;
+
+      // If later fields have slack space, take back overflow
+      if (unused>extra) unused = extra;
+      abslen -= unused;
+      extra -= unused;
+    }
+    if (abslen>width) abslen = width;
+    len = pad = abslen;
+    pad *= sign;
+
+    // If last field is left justified, no trailing spaces.
+    if (!field->next && sign<0) {
+      pad = -1;
+      len = width;
+    }
+
+    // If we truncated a left-justified field, show + instead of last char
+    if (olen>len && len>1 && sign<0) {
+      width--;
+      len--;
+      if (field->next) pad++;
+      abslen = 0;
     }
-    len = width;
-    pad = 0;
-    if (field->next || field->len>0)
-      len = abs(pad = width<abslen ? width : adjlen);
 
     if (TT.tty) width -= draw_trim(out, pad, len);
     else width -= printf("%*.*s", pad, len, out);
+    if (!abslen) putchar('+');
     if (!width) break;
   }
   xputc(TT.time ? '\r' : '\n');
@@ -591,12 +659,13 @@ static void show_ps(struct carveup *tb)
 static int get_ps(struct dirtree *new)
 {
   struct {
-    char *name;
-    long long bits;
+    char *name;     // Path under /proc/$PID directory
+    long long bits; // Only fetch extra data if an -o field is displaying it
   } fetch[] = {
     // sources for carveup->offset[] data
     {"fd/", _PS_TTY}, {"wchan", _PS_WCHAN}, {"attr/current", _PS_LABEL},
-    {"exe", _PS_COMMAND}, {"cmdline", _PS_CMDLINE|_PS_ARGS|_PS_NAME}
+    {"exe", _PS_COMMAND|_PS_COMM}, {"cmdline", _PS_CMDLINE|_PS_ARGS|_PS_NAME},
+    {"", _PS_NAME}
   };
   struct carveup *tb = (void *)toybuf;
   long long *slot = tb->slot;
@@ -606,13 +675,16 @@ static int get_ps(struct dirtree *new)
 
   // Recurse one level into /proc children, skip non-numeric entries
   if (!new->parent)
-    return DIRTREE_RECURSE|DIRTREE_SHUTUP
+    return DIRTREE_RECURSE|DIRTREE_SHUTUP|DIRTREE_PROC
       |(DIRTREE_SAVE*(TT.threadparent||!TT.show_process));
 
   memset(slot, 0, sizeof(tb->slot));
-  if (!(tb->slot[SLOT_tid] = *slot = atol(new->name))) return 0;
-  if (TT.threadparent && TT.threadparent->extra)
-    if (*slot == *(((struct carveup *)TT.threadparent->extra)->slot)) return 0;
+  tb->slot[SLOT_tid] = *slot = atol(new->name);
+  if (TT.threadparent && TT.threadparent->extra) {
+    *slot = *(((struct carveup *)TT.threadparent->extra)->slot);
+    // Parent also shows up as a thread, discard duplicate
+    if (*slot == tb->slot[SLOT_tid]) return 0;
+  }
   fd = dirtree_parentfd(new);
 
   len = 2048;
@@ -628,7 +700,8 @@ static int get_ps(struct dirtree *new)
 
   // Parse numeric fields (starting at 4th field in slot[SLOT_ppid])
   if (1>sscanf(s = end, ") %c%n", &tb->state, &i)) return 0;
-  for (j = 1; j<50; j++) if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break;
+  for (j = 1; j<SLOT_count; j++)
+    if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break;
 
   // Now we've read the data, move status and name right after slot[] array,
   // and convert low chars to ? for non-tty display while we're at it.
@@ -733,16 +806,30 @@ static int get_ps(struct dirtree *new)
     sprintf(buf, "%lld/%s", *slot, fetch[j].name);
 
     // For exe we readlink instead of read contents
-    if (j==3) {
-      // Thread doesn't have exe, so use parent's
-      if (TT.threadparent && TT.threadparent->extra) {
-        struct carveup *ptb = (void *)TT.threadparent->extra;
-        i = strlen(s = ptb->str+ptb->offset[3]);
+    if (j==3 || j==5) {
+      struct carveup *ptb = 0;
+      int k;
+
+      // Thread doesn't have exe or argv[0], so use parent's
+      if (TT.threadparent && TT.threadparent->extra)
+        ptb = (void *)TT.threadparent->extra;
+
+      if (j==3 && !ptb) len = readlinkat0(fd, buf, buf, len);
+      else {
+        if (j==3) i = strlen(s = ptb->str+ptb->offset[3]);
+        else {
+          if (!ptb || tb->slot[SLOT_argv0len]) ptb = tb;
+          i = ptb->slot[SLOT_argv0len];
+          s = ptb->str+ptb->offset[4];
+          while (-1!=(k = stridx(s, '/')) && k<i) {
+            s += k+1;
+            i -= k+1;
+          }
+        }
         if (i<len) len = i;
-        memcpy(buf, s, i);
+        memcpy(buf, s, len);
         buf[len] = 0;
-      } else if ((len = readlinkat(fd, buf, buf, len))>0) buf[len] = 0;
-      else *buf = len = 0;
+      }
 
     // If it's not the TTY field, data we want is in a file.
     // Last length saved in slot[] is command line (which has embedded NULs)
@@ -757,11 +844,8 @@ static int get_ps(struct dirtree *new)
         for (i = 0; i<3; i++) {
           sprintf(buf, "%lld/fd/%i", *slot, i);
           if (!fstatat(fd, buf, &st, 0) && S_ISCHR(st.st_mode)
-            && st.st_rdev == rdev && 0<(len = readlinkat(fd, buf, buf, len)))
-          {
-            buf[len] = 0;
-            break;
-          }
+            && st.st_rdev == rdev && (len = readlinkat0(fd, buf, buf, len)))
+              break;
         }
 
         // Couldn't find it, try all the tty drivers.
@@ -788,18 +872,21 @@ static int get_ps(struct dirtree *new)
         }
 
         s = buf;
-        if (strstart(&s, "/dev/")) memmove(buf, s, len -= 5);
+        if (strstart(&s, "/dev/")) memmove(buf, s, len -= 4);
       }
 
     // Data we want is in a file.
     // Last length saved in slot[] is command line (which has embedded NULs)
     } else {
+      int temp = 0;
 
       // When command has no arguments, don't space over the NUL
       if (readfileat(fd, buf, buf, &len) && len>0) {
-        int temp = 0;
 
-        if (buf[len-1]=='\n') buf[--len] = 0;
+        // Trim trailing whitespace and NUL bytes
+        while (len)
+          if (!buf[len-1] || isspace(buf[len-1])) buf[--len] = 0;
+          else break;
 
         // Turn NUL to space, other low ascii to ? (in non-tty mode)
         // cmdline has a trailing NUL that we don't want to turn to space.
@@ -812,10 +899,11 @@ static int get_ps(struct dirtree *new)
           } else if (!TT.tty && c<' ') c = '?';
           buf[i] = c;
         }
-        // Store end of argv[0] so NAME and CMDLINE can differ.
-        // We do it for each file string slot but last is cmdline, which sticks.
-        slot[SLOT_argv0len] = temp;  // Position of _first_ NUL
       } else *buf = len = 0;
+
+      // Store end of argv[0] so ARGS and CMDLINE can differ.
+      // We do it for each file string slot but last is cmdline, which sticks.
+      slot[SLOT_argv0len] = temp ? temp : len;  // Position of _first_ NUL
     }
 
     // Above calculated/retained len, so we don't need to re-strlen.
@@ -844,8 +932,7 @@ static int get_threads(struct dirtree *new)
   unsigned pid, kcount;
 
   if (!new->parent) return get_ps(new);
-
-  if (!(pid = atol(new->name))) return 0;
+  pid = atol(new->name);
 
   TT.threadparent = new;
   if (!get_ps(new)) {
@@ -858,7 +945,8 @@ static int get_threads(struct dirtree *new)
   // Disable show_process at least until we can calculate tcount
   kcount = TT.kcount;
   sprintf(toybuf, "/proc/%u/task", pid);
-  new->child = dirtree_flagread(toybuf, DIRTREE_SHUTUP, get_ps);
+  new->child = dirtree_flagread(toybuf, DIRTREE_SHUTUP|DIRTREE_PROC, get_ps);
+  if (new->child == DIRTREE_ABORTVAL) new->child = 0;
   TT.threadparent = 0;
   kcount = TT.kcount-kcount+1;
   tb = (void *)new->extra;
@@ -874,15 +962,16 @@ static int get_threads(struct dirtree *new)
   // Save or display
   if (!TT.show_process) return DIRTREE_SAVE;
   TT.show_process((void *)new->extra);
-  dt = new->child;
-  new->child = 0;
-  while (dt->child) {
-    new = dt->child->next;
-    TT.show_process((void *)dt->child->extra);
-    free(dt->child);
-    dt->child = new;
+  if ((dt = new->child)) {
+    new->child = 0;
+    while (dt->child) {
+      new = dt->child->next;
+      TT.show_process((void *)dt->child->extra);
+      free(dt->child);
+      dt->child = new;
+    }
+    free(dt);
   }
-  free(dt);
 
   return 0;
 }
@@ -950,7 +1039,7 @@ static char *parse_ko(void *data, char *type, int length)
   return 0;
 }
 
-long long get_headers(struct strawberry *fields, char *buf, int blen)
+static long long get_headers(struct strawberry *fields, char *buf, int blen)
 {
   long long bits = 0;
   int len = 0;
@@ -980,6 +1069,8 @@ static char *parse_rest(void *data, char *str, int len)
   if (isdigit(*str)) {
     ll[pl->len] = xstrtol(str, &end, 10);
     if (end==(len+str)) num++;
+    // For pkill, -s 0 represents pkill's session id.
+    if (pl==&TT.ss && ll[pl->len]==0) ll[pl->len] = getsid(0);
   }
 
   if (pl==&TT.pp || pl==&TT.ss) {
@@ -1073,8 +1164,8 @@ static struct carveup **collate_leaves(struct carveup **tb, struct dirtree *dt)
   while (dt) {
     struct dirtree *next = dt->next;
 
+    if (dt->extra) *(tb++) = (void *)dt->extra;
     if (dt->child) tb = collate_leaves(tb, dt->child);
-    else *(tb++) = (void *)dt->extra;
     free(dt);
     dt = next;
   }
@@ -1100,37 +1191,26 @@ static void default_ko(char *s, void *fields, char *err, struct arg_list *arg)
   comma_args(arg ? arg : &def, fields, err, parse_ko);
 }
 
-static void shared_main(void)
+void ps_main(void)
 {
+  char **arg;
+  struct dirtree *dt;
+  char *not_o;
   int i;
 
-  TT.ticks = sysconf(_SC_CLK_TCK);
-  if (!TT.width) {
-    TT.width = (toys.which->name[1] == 's') ? 99999 : 80;
-    TT.height = 25;
-    terminal_size(&TT.width, &TT.height);
-  }
+  TT.ticks = sysconf(_SC_CLK_TCK); // units for starttime/uptime
 
-  // find controlling tty, falling back to /dev/tty if none
-  for (i = 0; !TT.tty && i<4; i++) {
+  if (-1 != (i = tty_fd())) {
     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);
+    if (!fstat(i, &st)) TT.tty = st.st_rdev;
   }
-}
-
-void ps_main(void)
-{
-  struct dirtree *dt;
-  char *not_o;
-  int i;
 
+  // If we can't query terminal size pad to 80 but do -w
+  TT.width = 80;
+  if (!isatty(1) || !terminal_size(&TT.width, 0))
+    toys.optflags |= FLAG_w;
   if (toys.optflags&FLAG_w) TT.width = 99999;
-  shared_main();
 
   // parse command line options other than -o
   comma_args(TT.ps.P, &TT.PP, "bad -P", parse_rest);
@@ -1144,15 +1224,22 @@ void ps_main(void)
   comma_args(TT.ps.k, &TT.kfields, "bad -k", parse_ko);
   dlist_terminate(TT.kfields);
 
+  // It's undocumented, but traditionally extra arguments are extra -p args
+  for (arg = toys.optargs; *arg; arg++)
+    if (parse_rest(&TT.pp, *arg, strlen(*arg))) error_exit_raw(*arg);
+
   // Figure out which fields to display
   not_o = "%sTTY,TIME,CMD";
   if (toys.optflags&FLAG_f)
-    sprintf(not_o = toybuf+128, "USER:8=UID,%%sPPID,%s,STIME,TTY,TIME,CMD",
+    sprintf(not_o = toybuf+128,
+      "USER:12=UID,%%sPPID,%s,STIME,TTY,TIME,ARGS=CMD",
       (toys.optflags&FLAG_T) ? "TCNT" : "C");
   else if (toys.optflags&FLAG_l)
-    not_o = "F,S,UID,%sPPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD";
+    not_o = "F,S,UID,%sPPID,C,PRI,NI,BIT,SZ,WCHAN,TTY,TIME,CMD";
   else if (CFG_TOYBOX_ON_ANDROID)
-    not_o = "USER,%sPPID,VSIZE,RSS,WCHAN:10,ADDR:10=PC,S,NAME";
+    sprintf(not_o = toybuf+128,
+            "USER,%%sPPID,VSIZE,RSS,WCHAN:10,ADDR:10,S,%s",
+            (toys.optflags&FLAG_T) ? "CMD" : "NAME");
   sprintf(toybuf, not_o, (toys.optflags & FLAG_T) ? "PID,TID," : "PID,");
 
   // Init TT.fields. This only uses toybuf if TT.ps.o is NULL
@@ -1171,7 +1258,6 @@ void ps_main(void)
     struct strawberry *ever;
 
     for (ever = TT.fields; ever; ever = ever->next) {
-      if ((toys.optflags&FLAG_f) && ever->which==PS_CMD) ever->which = PS_ARGS;
       if ((toys.optflags&FLAG_n) && ever->which>=PS_UID
         && ever->which<=PS_RGROUP && (typos[ever->which].slot&64))
           ever->which--;
@@ -1182,13 +1268,13 @@ void ps_main(void)
   // print headers now (for low memory/nommu systems).
   TT.bits = get_headers(TT.fields, toybuf, sizeof(toybuf));
   if (!(toys.optflags&FLAG_M)) printf("%.*s\n", TT.width, toybuf);
-  if (!(toys.optflags&(FLAG_k|FLAG_M))) TT.show_process = (void *)show_ps;
+  if (!(toys.optflags&(FLAG_k|FLAG_M))) TT.show_process = show_ps;
   TT.match_process = ps_match_process;
-  dt = dirtree_read("/proc",
+  dt = dirtree_flagread("/proc", DIRTREE_SHUTUP|DIRTREE_PROC,
     ((toys.optflags&FLAG_T) || (TT.bits&(_PS_TID|_PS_TCNT)))
       ? get_threads : get_ps);
 
-  if (toys.optflags&(FLAG_k|FLAG_M)) {
+  if ((dt != DIRTREE_ABORTVAL) && toys.optflags&(FLAG_k|FLAG_M)) {
     struct carveup **tbsort = collate(TT.kcount, dt);
 
     if (toys.optflags&FLAG_M) {
@@ -1279,14 +1365,6 @@ static int header_line(int line, int rev)
   return line-1;
 }
 
-static long long millitime(void)
-{
-  struct timespec ts;
-
-  clock_gettime(CLOCK_MONOTONIC, &ts);
-  return ts.tv_sec*1000+ts.tv_nsec/1000000;
-}
-
 static void top_common(
   int (*filter)(long long *oslot, long long *nslot, int milis))
 {
@@ -1314,9 +1392,10 @@ static void top_common(
     plold = plist+(tock++&1);
     plnew = plist+(tock&1);
     plnew->whence = millitime();
-    dt = dirtree_read("/proc",
+    dt = dirtree_flagread("/proc", DIRTREE_SHUTUP|DIRTREE_PROC,
       ((toys.optflags&FLAG_H) || (TT.bits&(_PS_TID|_PS_TCNT)))
         ? get_threads : get_ps);
+    if (dt == DIRTREE_ABORTVAL) error_exit("no /proc");
     plnew->tb = collate(plnew->count = TT.kcount, dt);
     TT.kcount = 0;
 
@@ -1379,6 +1458,7 @@ static void top_common(
             terminal_probesize(&TT.width, &TT.height);
           }
         }
+        if (TT.top.m) TT.height = TT.top.m+5;
         lines = TT.height;
       }
       if (recalc && !(toys.optflags&FLAG_q)) {
@@ -1501,6 +1581,7 @@ static void top_common(
         break;
       }
       if (i==-2) break;
+      if (i==-3) continue;
 
       // Flush unknown escape sequences.
       if (i==27) while (0<scan_key_getsize(scratch, 0, &TT.width, &TT.height));
@@ -1539,15 +1620,23 @@ static void top_common(
 static void top_setup(char *defo, char *defk)
 {
   TT.top.d *= 1000;
+
+  TT.ticks = sysconf(_SC_CLK_TCK); // units for starttime/uptime
+  TT.tty = tty_fd() != -1;
+
+  // Are we doing "batch" output or interactive?
   if (toys.optflags&FLAG_b) TT.width = TT.height = 99999;
   else {
+    // Grab starting time, make terminal raw, switch off cursor,
+    // set signal handler to put terminal/cursor back to normal at exit.
     TT.time = millitime();
     set_terminal(0, 1, 0);
     sigatexit(tty_sigreset);
     xsignal(SIGWINCH, generic_signal);
     printf("\033[?25l\033[0m");
+    TT.width = 80;
+    TT.height = 25;
   }
-  shared_main();
 
   comma_args(TT.top.u, &TT.uu, "bad -u", parse_rest);
   comma_args(TT.top.p, &TT.pp, "bad -p", parse_rest);
@@ -1565,10 +1654,9 @@ static void top_setup(char *defo, char *defk)
 
 void top_main(void)
 {
-  // usage: [-h HEADER] -o OUTPUT -k SORT
-
-  sprintf(toybuf, "PID,USER,%s%%CPU,%%MEM,TIME+,ARGS",
-    TT.top.O ? "" : "PR,NI,VIRT,RES,SHR,S,");
+  sprintf(toybuf, "PID,USER,%s%%CPU,%%MEM,TIME+,%s",
+    TT.top.O ? "" : "PR,NI,VIRT,RES,SHR,S,",
+    toys.optflags&FLAG_H ? "CMD:15=THREAD,NAME=PROCESS" : "ARGS");
   if (!TT.top.s) TT.top.s = TT.top.O ? 3 : 9;
   top_setup(toybuf, "-%CPU,-ETIME,-PID");
   if (TT.top.O) {
@@ -1638,8 +1726,9 @@ static void do_pgk(struct carveup *tb)
   }
 }
 
-static void match_pgrep(struct carveup *tb)
+static void match_pgrep(void *p)
 {
+  struct carveup *tb = p;
   regmatch_t match;
   struct regex_list *reg;
   char *name = tb->str+tb->offset[4]*!!(toys.optflags&FLAG_f);;
@@ -1657,7 +1746,10 @@ static void match_pgrep(struct carveup *tb)
     if ((toys.optflags&FLAG_v) ? !!reg : !reg) return;
   }
 
-  // Repurpose a field for -c count
+  // pgrep should return success if there's a match.
+  toys.exitval = 0;
+
+  // Repurpose a field for -c count.
   TT.sortpos++;
   if (toys.optflags&(FLAG_n|FLAG_o)) {
     long long ll = tb->slot[SLOT_starttime];
@@ -1684,7 +1776,7 @@ void pgrep_main(void)
 
   TT.pgrep.self = getpid();
 
-  // No signal names start with "L", so no need for "L: " parsing.
+  // No signal names start with "L", so no need for "L: " in optstr.
   if (TT.pgrep.L && 1>(TT.pgrep.signal = sig_to_num(TT.pgrep.L)))
     error_exit("bad -L '%s'", TT.pgrep.L);
 
@@ -1708,9 +1800,12 @@ void pgrep_main(void)
     TT.pgrep.regexes = reg;
   }
   TT.match_process = pgrep_match_process;
-  TT.show_process = (void *)match_pgrep;
+  TT.show_process = match_pgrep;
+
+  // pgrep should return failure if there are no matches.
+  toys.exitval = 1;
 
-  dirtree_read("/proc", get_ps);
+  dirtree_flagread("/proc", DIRTREE_SHUTUP|DIRTREE_PROC, get_ps);
   if (toys.optflags&FLAG_c) printf("%d\n", TT.sortpos);
   if (TT.pgrep.snapshot) {
     do_pgk(TT.pgrep.snapshot);