OSDN Git Service

Move comma_args() from ps.c to lib.
[android-x86/external-toybox.git] / toys / posix / ps.c
1 /* ps.c - show process list
2  *
3  * Copyright 2015 Rob Landley <rob@landley.net>
4  *
5  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ps.html
6  * And http://kernel.org/doc/Documentation/filesystems/proc.txt Table 1-4
7  * And linux kernel source fs/proc/array.c function do_task_stat()
8  *
9  * Deviations from posix: no -n because /proc/self/wchan exists; we use -n to
10  * mean "show numeric users and groups" instead.
11  * Posix says default output should have field named "TTY" but if you "-o tty"
12  * the same field should be called "TT" which is _INSANE_ and I'm not doing it.
13  * Similarly -f outputs USER but calls it UID (we call it USER).
14  * It also says that -o "args" and "comm" should behave differently but use
15  * the same title, which is not the same title as the default output. (No.)
16  * Select by session id is -s not -g.
17  *
18  * Posix defines -o ADDR as "The address of the process" but the process
19  * start address is a constant on any elf system with mmu. The procps ADDR
20  * field always prints "-" with an alignment of 1, which is why it has 11
21  * characters left for "cmd" in in 80 column "ps -l" mode. On x86-64 you
22  * need 12 chars, leaving nothing for cmd: I.E. posix 2008 ps -l mode can't
23  * be sanely implemented on 64 bit Linux systems. In procps there's ps -y
24  * which changes -l by removing the "F" column and swapping RSS for ADDR,
25  * leaving 9 chars for cmd, so we're using that as our -l output.
26  *
27  * Added a bunch of new -o fields posix doesn't mention, and we don't
28  * label "ps -o command,args,comm" as "COMMAND COMMAND COMMAND". We don't
29  * output argv[0] unmodified for -o comm or -o args (but procps violates
30  * posix for -o comm anyway, it's stat[2] not argv[0]).
31  *
32  * TODO: ps aux (att & bsd style "ps -ax" vs "ps ax" behavior difference)
33  * TODO: switch -fl to -y
34  * TODO: thread support /proc/$d/task/%d/stat (and -o stat has "l")
35
36 USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflno*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN))
37 USE_TTOP(NEWTOY(ttop, ">0d#=3n#<1mb", TOYFLAG_USR|TOYFLAG_BIN))
38
39 config PS
40   bool "ps"
41   default y
42   help
43     usage: ps [-AadeflnwZ] [-gG GROUP,] [-k FIELD,] [-o FIELD,] [-p PID,] [-t TTY,] [-uU USER,]
44
45     List processes.
46
47     Which processes to show (selections may be comma separated lists):
48
49     -A  All processes
50     -a  Processes with terminals that aren't session leaders
51     -d  All processes that aren't session leaders
52     -e  Same as -A
53     -g  Belonging to GROUPs
54     -G  Belonging to real GROUPs (before sgid)
55     -p  PIDs (--pid)
56     -P  Parent PIDs (--ppid)
57     -s  In session IDs
58     -t  Attached to selected TTYs
59     -u  Owned by USERs
60     -U  Owned by real USERs (before suid)
61
62     Output modifiers:
63
64     -k  Sort FIELDs in +increasing or -decreasting order (--sort)
65     -n  Show numeric USER and GROUP
66     -w  Wide output (don't truncate at terminal width)
67
68     Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD)
69
70     -f  Full listing (-o USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD)
71     -l  Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD)
72     -o  Output the listed FIELDs, each with optional :size and/or =title
73     -Z  Include LABEL
74
75     Available -o FIELDs:
76
77       ADDR    Instruction pointer
78       ARGS    Command line (argv[0-X] minus path)
79       CMD     COMM without -f, ARGS with -f
80       CMDLINE Command line (argv[0-X])
81       COMM    Original command name
82       COMMAND Original command path
83       CPU     Which processor running on
84       ETIME   Elapsed time since process start
85       F       Flags (1=FORKNOEXEC, 4=SUPERPRIV)
86       GID     Group id
87       GROUP   Group name
88       LABEL   Security label
89       MAJFL   Major page faults
90       MINFL   Minor page faults
91       NAME    Command name (argv[0])
92       NI      Niceness (lower is faster)
93       PCPU    Percentage of CPU time used
94       PGID    Process Group ID
95       PID     Process ID
96       PPID    Parent Process ID
97       PRI     Priority (higher is faster)
98       PSR     Processor last executed on
99       RGID    Real (before sgid) group ID
100       RGROUP  Real (before sgid) group name
101       RSS     Resident Set Size (memory in use)
102       RTPRIO  Realtime priority
103       RUID    Real (before suid) user ID
104       RUSER   Real (before suid) user name
105       S       Process state:
106               R (running) S (sleeping) D (device I/O) T (stopped)  t (traced)
107               Z (zombie)  X (deader)   x (dead)       K (wakekill) W (waking)
108       SCHED   Scheduling policy (0=other, 1=fifo, 2=rr, 3=batch, 4=iso, 5=idle)
109       STAT    Process state (S) plus:
110               < high priority          N low priority L locked memory
111               s session leader         + foreground   l multithreaded
112       STIME   Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss)
113       SZ      Memory Size (4k pages needed to completely swap out process)
114       TIME    CPU time consumed
115       TTY     Controlling terminal
116       UID     User id
117       USER    User name
118       VSZ     Virtual memory size (1k units)
119       WCHAN   Waiting in kernel for
120
121 config TTOP
122   bool "ttop"
123   default n
124   help
125
126     usage: ttop [-mb] [ -d seconds ] [ -n iterations ]
127
128     Provide a view of process activity in real time.
129     Keys
130        N/M/P/T show CPU usage, sort by pid/mem/cpu/time
131        S       show memory
132        R       reverse sort
133        H       toggle threads
134        C,1     toggle SMP
135        Q,^C    exit
136
137     Options
138        -n Iterations before exiting
139        -d Delay between updates
140        -m Same as 's' key
141        -b Batch mode
142 */
143
144 #define FOR_ps
145 #include "toys.h"
146
147 GLOBALS(
148   union {
149     struct {
150       struct arg_list *G;
151       struct arg_list *g;
152       struct arg_list *U;
153       struct arg_list *u;
154       struct arg_list *t;
155       struct arg_list *s;
156       struct arg_list *p;
157       struct arg_list *o;
158       struct arg_list *P;
159       struct arg_list *k;
160     } ps;
161     struct {
162       long n;
163       long d;
164     } ttop;
165   };
166
167   struct sysinfo si;
168   struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU;
169   unsigned width;
170   dev_t tty;
171   void *fields, *kfields;
172   long long ticks, bits;
173   size_t header_len;
174   int kcount;
175 )
176
177 struct strawberry {
178   struct strawberry *next, *prev;
179   short which, len, reverse;
180   char *title;
181   char forever[];
182 };
183
184 /* The slot[] array is mostly populated from /proc/$PID/stat (kernel proc.txt
185  * table 1-4) but we shift and repurpose fields, with the result being:
186  *
187  * 0  pid         process id                1  ppid        parent process id
188  * 2  pgrp        process group             3  sid         session id
189  * 4  tty_nr      tty the process uses      5  tty_pgrp    pgrp of the tty
190  * 6  flags       task flags                7  min_flt     minor faults
191  * 8  cmin_flt    minor faults+child        9  maj_flt     major faults
192  * 10 cmaj_flt    major faults+child        11 utime       user+kernel jiffies
193  * 12 stime       kernel mode jiffies       13 cutime      user jiffies+child
194  * 14 cstime      kernel mode jiffies+child 15 priority    priority level
195  * 16 nice        nice level                17 num_threads number of threads
196  * 18 vmlck       locked memory             19 start_time  jiffies after boot
197  * 20 vsize       virtual memory size       21 rss         resident set size
198  * 22 rsslim      limit in bytes on rss     23 start_code  code segment addr
199  * 24 end_code    code segment address      25 start_stack stack address
200  * 26 esp         current value of ESP      27 eip         current value of EIP
201  * 28 pending     bitmap of pending signals 29 blocked     blocked signal bmap
202  * 30 sigign      bitmap of ignored signals 31 uid         user id
203  * 32 ruid        real user id              33 gid         group id
204  * 34 rgid        real group id             35 exit_signal sent to parent thread
205  * 36 task_cpu    CPU task is scheduled on  37 rt_priority realtime priority
206  * 38 policy      man sched_setscheduler    39 blkio_ticks spent wait block IO
207  * 40 gtime       guest jiffies of task     41 cgtime      guest jiff of child
208  * 42 start_data  program data+bss address  43 end_data    program data+bss
209  * 44 start_brk   heap expand with brk()    45 argv0len    argv[0] length
210  * 46 uptime      sysinfo.uptime @read time 47 vsz         Virtual Size
211  * 48 rss         Resident Set Size         49 shr         Shared memory
212  */
213
214 // Data layout in toybuf
215 struct carveup {
216   long long slot[50];       // data from /proc, skippint #2 and #3
217   unsigned short offset[5]; // offset of fields in str[] (skip name, always 0)
218   char state;
219   char str[];               // name, tty, command, wchan, attr, cmdline
220 };
221
222 // TODO: Android uses -30 for LABEL, but ideally it would auto-size.
223 struct typography {
224   char *name;
225   signed char width, slot;
226
227 } static const typos[] = TAGGED_ARRAY(PS,
228   // stat#s: PID PPID PRI NI ADDR SZ RSS PGID VSZ MAJFL MINFL PR PSR RTPRIO
229   // SCHED
230   {"PID", 5, 0}, {"PPID", 5, 1}, {"PRI", 3, 15}, {"NI", 3, 16},
231   {"ADDR", 4+sizeof(long), 27}, {"SZ", 5, 20}, {"RSS", 5, 21}, {"PGID", 5, 2},
232   {"VSZ", 6, 20}, {"MAJFL", 6, 9}, {"MINFL", 6, 7}, {"PR", 2, 15},
233   {"PSR", 3, 36}, {"RTPRIO", 6, 37}, {"SCH", 3, 38},
234
235   // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP
236   {"UID", 5, 31}, {"USER", -8, 64|31}, {"RUID", 4, 32}, {"RUSER", -8, 64|32},
237   {"GID", 8, 33}, {"GROUP", -8, 64|33}, {"RGID", 4, 34}, {"RGROUP", -8, 64|34},
238
239   // CMD TTY WCHAN LABEL CMDLINE COMMAND
240   {"COMM", -15, -1}, {"TTY", -8, -2}, {"WCHAN", -6, -3}, {"LABEL", -30, -4},
241   {"COMMAND", -27, -5}, {"CMDLINE", -27, -6}, {"ARGS", -27, -6},
242   {"NAME", -15, -6}, {"CMD", -27, -1},
243
244   // TIME ELAPSED TIME+
245   {"TIME", 8, 11}, {"ELAPSED", 11, 19}, {"TIME+", 9, 11},
246
247   // Remaining ungrouped
248   {"STIME", 5, 19}, {"F", 1, 64|6}, {"S", -1, 64}, {"C", 1, 0}, {"%CPU", 4, 64},
249   {"STAT", -5, 64}, {"%VSZ", 5, 23}, {"VIRT", 4, 47}, {"RES", 4, 48},
250   {"SHR", 4, 49}
251 );
252
253 // Return 1 to keep, 0 to discard
254 static int match_process(long long *slot)
255 {
256   struct ptr_len match[] = {
257     {&TT.gg, 33}, {&TT.GG, 34}, {&TT.pp, 0}, {&TT.PP, 1}, {&TT.ss, 3},
258     {&TT.tt, 4}, {&TT.uu, 31}, {&TT.UU, 32}
259   };
260   int i, j;
261   long *ll = 0;
262
263   // Do we have -g -G -p -P -s -t -u -U options selecting processes?
264   for (i = 0; i < ARRAY_LEN(match); i++) {
265     struct ptr_len *mm = match[i].ptr;
266     if (mm->len) {
267       ll = mm->ptr;
268       for (j = 0; j<mm->len; j++) if (ll[j] == slot[match[i].len]) return 1;
269     }
270   }
271
272   // If we had selections and didn't match them, don't display
273   if (ll) return 0;
274
275   // Filter implicit categories for other display types
276   if ((toys.optflags&(FLAG_a|FLAG_d)) && slot[3]==*slot) return 0;
277   if ((toys.optflags&FLAG_a) && !slot[4]) return 0;
278   if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e)) && TT.tty!=slot[4])
279     return 0;
280
281   return 1;
282 }
283
284 // Convert field to string representation
285 static char *string_field(struct carveup *tb, struct strawberry *field)
286 {
287   char *buf = toybuf+sizeof(toybuf)-260, *out = buf, *s;
288   int which = field->which, sl = typos[which].slot;
289   long long *slot = tb->slot, ll = (sl >= 0) ? slot[sl&63] : 0;
290
291   // Default: unsupported (5 "C")
292   sprintf(out, "-");
293
294   // stat#s: PID PPID PRI NI ADDR SZ RSS PGID VSZ MAJFL MINFL PR PSR RTPRIO SCH
295   if (which <= PS_SCH) {
296     char *fmt = "%lld";
297
298     if (which==PS_PRI) ll = 39-ll;
299     if (which==PS_ADDR) fmt = "%llx";
300     else if (which==PS_SZ) ll >>= 12;
301     else if (which==PS_RSS) ll <<= 2;
302     else if (which==PS_VSZ) ll >>= 10;
303     else if (which==PS_PR && ll<-9) fmt="RT";
304     else if (which==PS_RTPRIO && ll == 0) fmt="-";
305     sprintf(out, fmt, ll);
306
307   // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP
308   } else if (which <= PS_RGROUP) {
309     sprintf(out, "%lld", ll);
310     if (!(toys.optflags&FLAG_n) && (sl&64)) {
311       if (which > PS_RUSER) {
312         struct group *gr = getgrgid(ll);
313
314         if (gr) out = gr->gr_name;
315       } else {
316         struct passwd *pw = getpwuid(ll);
317
318         if (pw) out = pw->pw_name;
319       }
320     }
321
322   // COMM TTY WCHAN LABEL COMMAND CMDLINE ARGS NAME CMD
323
324   // CMD TTY WCHAN LABEL CMDLINE COMMAND COMM NAME
325   } else if (sl < 0) {
326     if (which==PS_CMD && (toys.optflags&FLAG_f)) sl = typos[which=PS_ARGS].slot;
327     if (slot[45])
328       tb->str[tb->offset[4]+slot[45]] = (which == PS_NAME) ? 0 : ' ';
329     out = tb->str;
330     sl *= -1;
331     if (--sl) out += tb->offset[--sl];
332     if (which==PS_ARGS)
333       for (s = out; *s && *s != ' '; s++) if (*s == '/') out = s+1;
334     if (which>=PS_COMMAND && !*out) sprintf(out = buf, "[%s]", tb->str);
335   // TIME ELAPSED TIME+
336   } else if (which <= PS_TIME_) {
337     int unit = 60, pad = 2, j = TT.ticks; 
338     time_t seconds;
339
340     if (which!=PS_TIME_) unit *= 60*24;
341     else pad = 0;
342     if (which==PS_ELAPSED) ll = (slot[46]*j)-slot[19];
343     seconds = ll/j;
344
345     // Output days-hours:mins:secs, skipping non-required fields with zero
346     // TIME has 3 required fields, ETIME has 2. (Posix!) TIME+ is from top
347     for (s = 0, j = 2*(which==PS_TIME_); j<4; j++) {
348       if (!s && (seconds>unit || j == 1+(which!=PS_TIME))) s = out;
349       if (s) {
350         s += sprintf(s, j ? "%0*ld": "%*ld", pad, (long)(seconds/unit));
351         pad = 2;
352         if ((*s = "-::"[j])) s++;
353       }
354       seconds %= unit;
355       unit /= j ? 60 : 24;
356     }
357     if (which==PS_TIME_ && s-out<8)
358       sprintf(s, ".%02lld", (100*(ll%TT.ticks))/TT.ticks);
359
360   // Posix doesn't specify what flags should say. Man page says
361   // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h
362   } else if (which==PS_F) sprintf(out, "%llo", (slot[6]>>6)&5);
363   else if (which==PS_S || which==PS_STAT) {
364     s = out;
365     *s++ = tb->state;
366     if (which==PS_STAT) {
367       // TODO l = multithreaded
368       if (slot[16]<0) *s++ = '<';
369       else if (slot[16]>0) *s++ = 'N';
370       if (slot[3]==*slot) *s++ = 's';
371       if (slot[18]) *s++ = 'L';
372       if (slot[5]==*slot) *s++ = '+';
373     } 
374     *s = 0;
375   } else if (which==PS_STIME) {
376     time_t t = time(0)-slot[46]+slot[19]/TT.ticks;
377
378     // Padding behavior's a bit odd: default field size is just hh:mm.
379     // Increasing stime:size reveals more data at left until full,
380     // so move start address so yyyy-mm-dd hh:mm revealed on left at :16,
381     // then add :ss on right for :19.
382     strftime(out, 260, "%F %T", localtime(&t));
383     out = out+strlen(out)-3-abs(field->len);
384     if (out<buf) out = buf;
385   } else if (which==PS__CPU || which==PS__VSZ) {
386     if (which==PS__CPU) {
387       ll = (slot[46]*TT.ticks-slot[19]);
388       sl = (slot[11]*1000)/ll;
389     } else sl = (slot[23]*1000)/TT.si.totalram;
390     sprintf(out, "%d.%d", sl/10, sl%10);
391   } else if (which==PS_VIRT || which==PS_RES || which==PS_SHR)
392     human_readable(out, slot[typos[which].slot]*sysconf(_SC_PAGESIZE), 0);
393
394   return out;
395 }
396
397 // Display process data that get_ps() read from /proc, formatting with TT.fields
398 static void show_ps(struct carveup *tb)
399 {
400   struct strawberry *field;
401   int i, len, width = TT.width;
402
403   // Loop through fields to display
404   for (field = TT.fields; field; field = field->next) {
405     char *out = string_field(tb, field);
406
407     // Output the field, appropriately padded
408     len = width - (field != TT.fields);
409     if (!field->next && field->len<0) i = 0;
410     else {
411       i = len<abs(field->len) ? len : field->len;
412       len = abs(i);
413     }
414
415     // TODO test utf8 fontmetrics
416     width -= printf(" %*.*s" + (field == TT.fields), i, len, out);
417     if (!width) break;
418   }
419   xputc('\n');
420 }
421
422 // dirtree callback: read data about process to display, store, or discard it.
423 // Fills toybuf with struct carveup and either DIRTREE_SAVEs a copy to ->extra
424 // (in -k mode) or calls show_ps on toybuf (no malloc/copy/free there).
425 static int get_ps(struct dirtree *new)
426 {
427   struct {
428     char *name;
429     long long bits;
430   } fetch[] = {
431     {"fd/", _PS_TTY}, {"wchan", _PS_WCHAN}, {"attr/current", _PS_LABEL},
432     {"exe", _PS_COMMAND}, {"cmdline",
433      (_PS_CMD*!!(toys.optflags&FLAG_f))|_PS_CMDLINE|_PS_ARGS|_PS_NAME}
434   };
435   struct carveup *tb = (void *)toybuf;
436   long long *slot = tb->slot;
437   char *name, *s, *buf = tb->str, *end = 0;
438   int i, j, fd, ksave = DIRTREE_SAVE*!!(toys.optflags&FLAG_k);
439   off_t len;
440
441   // Recurse one level into /proc children, skip non-numeric entries
442   if (!new->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP|ksave;
443   if (!(*slot = atol(new->name))) return 0;
444   fd = dirtree_parentfd(new);
445
446   len = 2048;
447   sprintf(buf, "%lld/stat", *slot);
448   if (!readfileat(fd, buf, buf, &len)) return 0;
449
450   // parse oddball fields (name and state). Name can have embedded ')' so match
451   // _last_ ')' in stat (although VFS limits filenames to 255 bytes max).
452   // All remaining fields should be numeric.
453   if (!(name = strchr(buf, '('))) return 0;
454   for (s = ++name; *s; s++) if (*s == ')') end = s;
455   if (!end || end-name>255) return 0;
456
457   // Parse numeric fields (starting at 4th field in slot[1])
458   if (1>sscanf(s = end, ") %c%n", &tb->state, &i)) return 0;
459   for (j = 1; j<50; j++) if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break;
460
461   // Now we've read the data, move status and name right after slot[] array,
462   // and convert low chars to ? while we're at it.
463   for (i = 0; i<end-name; i++)
464     if ((tb->str[i] = name[i]) < ' ') tb->str[i] = '?';
465   buf = tb->str+i;
466   *buf++ = 0;
467   len = sizeof(toybuf)-(buf-toybuf);
468
469   // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch
470   // or numeric wchan, and the remaining two are always zero), and vmlck into
471   // 18 (which is "obsolete, always 0" from stat)
472   slot[31] = new->st.st_uid;
473   slot[33] = new->st.st_gid;
474
475   // TIME and TIME+ use combined value, ksort needs 'em added.
476   slot[11] += slot[12];
477
478   // If RGROUP RUSER STAT RUID RGID happening, or -G or -U, parse "status"
479   // and save ruid, rgid, and vmlck.
480   if ((TT.bits&(_PS_RGROUP|_PS_RUSER|_PS_STAT|_PS_RUID|_PS_RGID))
481     || TT.GG.len || TT.UU.len)
482   {
483     off_t temp = len;
484
485     sprintf(buf, "%lld/status", *slot);
486     if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
487     s = strstr(buf, "\nUid:");
488     slot[32] = s ? atol(s+5) : new->st.st_uid;
489     s = strstr(buf, "\nGid:");
490     slot[34] = s ? atol(s+5) : new->st.st_gid;
491     s = strstr(buf, "\nVmLck:");
492     if (s) slot[18] = atoll(s+5);
493   }
494
495   // We now know enough to skip processes we don't care about.
496   if (!match_process(slot)) return 0;
497
498   // Do we need to read "statm"?
499   if (TT.bits&(_PS_VIRT|_PS_RES|_PS_SHR)) {
500     off_t temp = len;
501
502     sprintf(buf, "%lld/statm", *slot);
503     if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
504     
505     for (s = buf, i=0; i<3; i++)
506       if (!sscanf(s, " %lld%n", slot+47+i, &j)) slot[47+i] = 0;
507       else s += j;
508   }
509
510   // /proc data is generated as it's read, so for maximum accuracy on slow
511   // systems (or ps | more) we re-fetch uptime as we fetch each /proc line.
512   sysinfo(&TT.si);
513   slot[46] = TT.si.uptime;
514
515   // Fetch string data while parentfd still available, appending to buf.
516   // (There's well over 3k of toybuf left. We could dynamically malloc, but
517   // it'd almost never get used, querying length of a proc file is awkward,
518   // fixed buffer is nommu friendly... Wait for somebody to complain. :)
519   slot[45] = 0;
520   for (j = 0; j<ARRAY_LEN(fetch); j++) { 
521     tb->offset[j] = buf-(tb->str);
522     if (!(TT.bits&fetch[j].bits)) {
523       *buf++ = 0;
524       continue;
525     }
526
527     // Determine remaining space, reserving minimum of 256 bytes/field and
528     // 260 bytes scratch space at the end (for output conversion later).
529     len = sizeof(toybuf)-(buf-toybuf)-260-256*(ARRAY_LEN(fetch)-j);
530     sprintf(buf, "%lld/%s", *slot, fetch[j].name);
531
532     // For cmdline we readlink instead of read contents
533     if (j==3) {
534       if (0>=(len = readlinkat(fd, buf, buf, len))) buf[len] = 0;
535  
536     // If it's not the TTY field, data we want is in a file.
537     // Last length saved in slot[] is command line (which has embedded NULs)
538     } else if (!j) {
539       int rdev = slot[4];
540       struct stat st;
541
542       // Call no tty "?" rather than "0:0".
543       strcpy(buf, "?");
544       if (rdev) {
545         // Can we readlink() our way to a name?
546         for (i = 0; i<3; i++) {
547           sprintf(buf, "%lld/fd/%i", *slot, i);
548           if (!fstatat(fd, buf, &st, 0) && S_ISCHR(st.st_mode)
549             && st.st_rdev == rdev && 0<(len = readlinkat(fd, buf, buf, len)))
550           {
551             buf[len] = 0;
552             break;
553           }
554         }
555
556         // Couldn't find it, try all the tty drivers.
557         if (i == 3) {
558           FILE *fp = fopen("/proc/tty/drivers", "r");
559           int tty_major = 0, maj = major(rdev), min = minor(rdev);
560
561           if (fp) {
562             while (fscanf(fp, "%*s %256s %d %*s %*s", buf, &tty_major) == 2) {
563               // TODO: we could parse the minor range too.
564               if (tty_major == maj) {
565                 sprintf(buf+strlen(buf), "%d", min);
566                 if (!stat(buf, &st) && S_ISCHR(st.st_mode) && st.st_rdev==rdev)
567                   break;
568               }
569               tty_major = 0;
570             }
571             fclose(fp);
572           }
573
574           // Really couldn't find it, so just show major:minor.
575           if (!tty_major) sprintf(buf, "%d:%d", maj, min);
576         }
577
578         s = buf;
579         if (strstart(&s, "/dev/")) memmove(buf, s, strlen(s)+1);
580       }
581
582     // Data we want is in a file.
583     // Last length saved in slot[] is command line (which has embedded NULs)
584     } else {
585       readfileat(fd, buf, buf, &len);
586
587       // When command has no arguments, don't space over the NUL
588       if (len>0) {
589         int temp = 0;
590
591         if (buf[len-1]=='\n') buf[--len] = 0;
592
593         // Turn NUL to space, other low ascii to ?
594         for (i=0; i<len; i++) {
595           char c = buf[i];
596
597           if (!c) {
598             if (!temp) temp = i;
599             c = ' ';
600           } else if (c<' ') c = '?';
601           buf[i] = c;
602         }
603         len = temp; // position of _first_ NUL
604       } else *buf = len = 0;
605       // Store end of argv[0] so NAME and CMDLINE can differ.
606       slot[45] = len;
607     }
608
609     buf += strlen(buf)+1;
610   }
611
612   // If we need to sort the output, add it to the list and return.
613   if (ksave) {
614     s = xmalloc(buf-toybuf);
615     new->extra = (long)s;
616     memcpy(s, toybuf, buf-toybuf);
617     TT.kcount++;
618
619   // Otherwise display it now
620   } else show_ps(tb);
621
622   return ksave;
623 }
624
625 static char *parse_ko(void *data, char *type, int length)
626 {
627   struct strawberry *field;
628   char *width, *title, *end, *s;
629   int i, j, k;
630
631   // Get title, length of title, type, end of type, and display width
632
633   // Chip off =name to display
634   if ((end = strchr(type, '=')) && length>(end-type)) {
635     title = end+1;
636     length -= (end-type)+1;
637   } else {
638     end = type+length;
639     title = 0;
640   }
641
642   // Chip off :width to display
643   if ((width = strchr(type, ':')) && width<end) {
644     if (!title) length = width-type;
645   } else width = 0;
646
647   // Allocate structure, copy title
648   field = xzalloc(sizeof(struct strawberry)+(length+1)*!!title);
649   if (title) {
650     memcpy(field->title = field->forever, title, length);
651     field->title[field->len = length] = 0;
652   }
653
654   if (width) {
655     field->len = strtol(++width, &title, 10);
656     if (!isdigit(*width) || title != end) return title;
657     end = --width;
658   }
659
660   // Find type
661   if (*(struct strawberry **)data == TT.kfields) {
662     field->reverse = 1;
663     if (*type == '-') field->reverse = -1;
664     else if (*type != '+') type--;
665     type++;
666   }
667   for (i = 0; i<ARRAY_LEN(typos); i++) {
668     field->which = i;
669     for (j = 0; j<2; j++) {
670       if (!j) s = typos[i].name;
671       // posix requires alternate names for some fields
672       else if (-1==(k = stridx((char []){PS_NI, PS_SCH, PS_ELAPSED, PS__CPU,
673         PS_VSZ, PS_USER, 0}, i))) continue;
674       else {
675         s = ((char *[]){"NICE", "SCHED", "ETIME", "PCPU", "VSIZE", "UNAME"})[k];
676       }
677
678       if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break;
679     }
680     if (j!=2) break;
681   }
682   if (i==ARRAY_LEN(typos)) return type;
683   if (!field->title) field->title = typos[field->which].name;
684   if (!field->len) field->len = typos[field->which].width;
685   else if (typos[field->which].width<0) field->len *= -1;
686   dlist_add_nomalloc(data, (void *)field);
687
688   // Print padded header for -o.
689   if (*(struct strawberry **)data == TT.fields) {
690     TT.header_len +=
691       snprintf(toybuf + TT.header_len, sizeof(toybuf) - TT.header_len,
692                " %*s" + (field == TT.fields), field->len, field->title);
693     TT.bits |= 1LL<<field->which;
694   }
695
696   return 0;
697 }
698
699 // Parse -p -s -t -u -U -g -G
700 static char *parse_rest(void *data, char *str, int len)
701 {
702   struct ptr_len *pl = (struct ptr_len *)data;
703   long *ll = pl->ptr;
704   char *end;
705   int num = 0;
706
707   // numeric: -p, -s
708   // gg, GG, pp, ss, tt, uu, UU, *parsing;
709  
710   // Allocate next chunk of data
711   if (!(15&pl->len))
712     ll = pl->ptr = xrealloc(pl->ptr, sizeof(long)*(pl->len+16));
713
714   // Parse numerical input
715   if (isdigit(*str)) {
716     ll[pl->len] = xstrtol(str, &end, 10);
717     if (end==(len+str)) num++;
718   }
719
720   if (pl==&TT.pp || pl==&TT.ss) {
721     if (num && ll[pl->len]>0) {
722       pl->len++;
723
724       return 0;
725     }
726   } else if (pl==&TT.tt) {
727     // -t pts = 12,pts/12 tty = /dev/tty2,tty2,S0
728     if (!num) {
729       if (strstart(&str, strcpy(toybuf, "/dev/"))) len -= 5;
730       if (strstart(&str, "pts/")) {
731         len -= 4;
732         num++;
733       } else if (strstart(&str, "tty")) len -= 3;
734     }
735     if (len<256 && (!(end = strchr(str, '/')) || end-str>len)) {
736       struct stat st;
737
738       end = toybuf + sprintf(toybuf, "/dev/%s", num ? "pts/" : "tty");
739       memcpy(end, str, len);
740       end[len] = 0;
741       xstat(toybuf, &st);
742       ll[pl->len++] = st.st_rdev;
743
744       return 0;
745     }
746   } else if (len<255) {
747     char name[256];
748
749     if (num) {
750       pl->len++;
751
752       return 0;
753     }
754
755     memcpy(name, str, len);
756     name[len] = 0;
757     if (pl==&TT.gg || pl==&TT.GG) {
758       struct group *gr = getgrnam(name);
759       if (gr) {
760         ll[pl->len++] = gr->gr_gid;
761
762         return 0;
763       }
764     } else if (pl==&TT.uu || pl==&TT.UU) {
765       struct passwd *pw = getpwnam(name);
766       if (pw) {
767         ll[pl->len++] = pw->pw_uid;
768
769         return 0;
770       }
771     }
772   }
773
774   // Return error
775   return str;
776 }
777
778 // sort for -k
779 static int ksort(void *aa, void *bb)
780 {
781   struct strawberry *field;
782   struct carveup *ta = *(struct carveup **)aa, *tb = *(struct carveup **)bb;
783   int ret = 0, slot;
784
785   for (field = TT.kfields; field; field = field->next) {
786
787     slot = typos[field->which].slot;
788     // Compare as strings?
789     if (slot&64) {
790       memccpy(toybuf, string_field(ta, field), 0, 2048);
791       toybuf[2048] = 0;
792       ret = strcmp(toybuf, string_field(tb, field));
793     } else {
794       if (ta->slot[slot]<tb->slot[slot]) ret = -1;
795       if (ta->slot[slot]>tb->slot[slot]) ret = 1;
796     }
797
798     if (ret) return ret*field->reverse;
799   }
800
801   return 0;
802 }
803
804 void ps_main(void)
805 {
806   struct dirtree *dt;
807   int i;
808
809   TT.ticks = sysconf(_SC_CLK_TCK);
810   TT.width = 99999;
811   if (!(toys.optflags&FLAG_w)) terminal_size(&TT.width, 0);
812
813   // find controlling tty, falling back to /dev/tty if none
814   for (i = 0; !TT.tty && i<4; i++) {
815     struct stat st;
816     int fd = i;
817
818     if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break;
819
820     if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev;
821     if (i==3) close(fd);
822   }
823
824   // parse command line options other than -o
825   comma_args(TT.ps.P, &TT.PP, "bad -P", parse_rest);
826   comma_args(TT.ps.p, &TT.pp, "bad -p", parse_rest);
827   comma_args(TT.ps.t, &TT.tt, "bad -t", parse_rest);
828   comma_args(TT.ps.s, &TT.ss, "bad -s", parse_rest);
829   comma_args(TT.ps.u, &TT.uu, "bad -u", parse_rest);
830   comma_args(TT.ps.U, &TT.UU, "bad -u", parse_rest);
831   comma_args(TT.ps.g, &TT.gg, "bad -g", parse_rest);
832   comma_args(TT.ps.G, &TT.GG, "bad -G", parse_rest);
833   comma_args(TT.ps.k, &TT.kfields, "bad -k", parse_ko);
834   dlist_terminate(TT.kfields);
835
836   // Parse manual field selection, or default/-f/-l, plus -Z,
837   // constructing the header line in toybuf as we go.
838   if (toys.optflags&FLAG_Z) {
839     struct arg_list Z = { 0, "LABEL" };
840
841     comma_args(&Z, &TT.fields, "-Z", parse_ko);
842   }
843   if (TT.ps.o) comma_args(TT.ps.o, &TT.fields, "bad -o field", parse_ko);
844   else {
845     struct arg_list al;
846
847     al.next = 0;
848     if (toys.optflags&FLAG_f)
849       al.arg = "USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD";
850     else if (toys.optflags&FLAG_l)
851       al.arg = "F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD";
852     else if (CFG_TOYBOX_ON_ANDROID)
853       al.arg = "USER,PID,PPID,VSIZE,RSS,WCHAN:10,ADDR:10=PC,S,CMDLINE";
854     else al.arg = "PID,TTY,TIME,CMD";
855
856     comma_args(&al, &TT.fields, 0, parse_ko);
857   }
858   dlist_terminate(TT.fields);
859   printf("%s\n", toybuf);
860
861   dt = dirtree_read("/proc", get_ps);
862
863   if (toys.optflags&FLAG_k) {
864     struct carveup **tbsort = xmalloc(TT.kcount*sizeof(struct carveup *));
865
866     // descend into child list
867     *tbsort = (void *)dt;
868     dt = dt->child;
869     free(*tbsort);
870
871     // populate array
872     i = 0;
873     while (dt) {
874       void *temp = dt->next;
875
876       tbsort[i++] = (void *)dt->extra;
877       free(dt);
878       dt = temp;
879     }
880
881     // Sort and show
882     qsort(tbsort, TT.kcount, sizeof(struct carveup *), (void *)ksort);
883     for (i = 0; i<TT.kcount; i++) {
884       show_ps(tbsort[i]);
885       free(tbsort[i]);
886     }
887     if (CFG_TOYBOX_FREE) free(tbsort);
888   }
889
890   if (CFG_TOYBOX_FREE) {
891     free(TT.gg.ptr);
892     free(TT.GG.ptr);
893     free(TT.pp.ptr);
894     free(TT.PP.ptr);
895     free(TT.ss.ptr);
896     free(TT.tt.ptr);
897     free(TT.uu.ptr);
898     free(TT.UU.ptr);
899     llist_traverse(TT.fields, free);
900   }
901 }
902
903 #define CLEANUP_ps
904 #define FOR_top
905 #include "generated/flags.h"
906
907 void ttop_main(void)
908 {
909   ps_main();
910 }