OSDN Git Service

Add basic sort logic.
[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  * TODO: ps aux (att & bsd style "ps -ax" vs "ps ax" behavior difference)
28  * TODO: finalize F, remove C
29  *       switch -fl to -y, use "string" instead of constants to set, remove C
30  * TODO: --sort
31  * TODO: way too many hardwired constants here, how can I generate them?
32  * TODO: thread support /proc/$d/task/%d/stat (and -o stat has "l")
33  *
34  * Design issue: the -o fields are an ordered array, and the order is
35  * significant. The array index is used in strawberry->which (consumed
36  * in do_ps()) and in the bitmasks enabling default fields in ps_main().
37
38 USE_PS(NEWTOY(ps, "k(sort)P(ppid)*aAdeflno*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN))
39
40 config PS
41   bool "ps"
42   default y
43   help
44     usage: ps [-AadeflnwZ] [-gG GROUP,] [-k FIELD,] [-o FIELD,] [-p PID,] [-t TTY,] [-uU USER,]
45
46     List processes.
47
48     Which processes to show (selections may be comma separated lists):
49
50     -A  All processes
51     -a  Processes with terminals that aren't session leaders
52     -d  All processes that aren't session leaders
53     -e  Same as -A
54     -g  Belonging to GROUPs
55     -G  Belonging to real GROUPs (before sgid)
56     -p  PIDs (--pid)
57     -P  Parent PIDs (--ppid)
58     -s  In session IDs
59     -t  Attached to selected TTYs
60     -u  Owned by USERs
61     -U  Owned by real USERs (before suid)
62
63     Output modifiers:
64
65     -k  Sort FIELDs in +increasing or -decreasting order (--sort)
66     -n  Show numeric USER and GROUP
67     -w  Wide output (don't truncate at terminal width)
68
69     Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD)
70
71     -f  Full listing (-o USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD)
72     -l  Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD)
73     -o  Output the listed FIELDs, each with optional :size and/or =title
74     -Z  Include LABEL
75
76     Available -o FIELDs:
77
78       ADDR    Instruction pointer
79       CMD     Command name (original)
80       CMDLINE Command name (current argv[0])
81       COMM    Command line (with arguments)
82       ETIME   Elapsed time since process start
83       F       Process flags (PF_*) from linux source file include/sched.h
84               (in octal rather than hex because posix)
85       GID     Group id
86       GROUP   Group name
87       LABEL   Security label
88       MAJFL   Major page faults
89       MINFL   Minor page faults
90       NI      Niceness of process (lower niceness is higher priority)
91       PCPU    Percentage of CPU time used
92       PGID    Process Group ID
93       PID     Process ID
94       PPID    Parent Process ID
95       PRI     Priority
96       RGID    Real (before sgid) group ID
97       RGROUP  Real (before sgid) group name
98       RSS     Resident Set Size (memory currently used)
99       RUID    Real (before suid) user ID
100       RUSER   Real (before suid) user name
101       S       Process state:
102               R (running) S (sleeping) D (disk sleep) T (stopped)  t (traced)
103               Z (zombie)  X (dead)     x (dead)       K (wakekill) W (waking)
104       STAT    Process state (S) plus:
105               < high priority          N low priority L locked memory
106               s session leader         + foreground   l multithreaded
107       STIME   Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss)
108       SZ      Memory Size (4k pages needed to completely swap out process)
109       TIME    CPU time consumed
110       TTY     Controlling terminal
111       UID     User id
112       USER    User name
113       VSZ     Virtual memory size (1k units)
114       WCHAN   Waiting in kernel for
115 */
116
117 #define FOR_ps
118 #include "toys.h"
119
120 GLOBALS(
121   struct arg_list *G;
122   struct arg_list *g;
123   struct arg_list *U;
124   struct arg_list *u;
125   struct arg_list *t;
126   struct arg_list *s;
127   struct arg_list *p;
128   struct arg_list *o;
129   struct arg_list *P;
130   struct arg_list *k;
131
132   struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU, *parsing;
133   unsigned width;
134   dev_t tty;
135   void *fields;
136   long long ticks, bits;
137   size_t header_len;
138   int kcount;
139 )
140
141 struct strawberry {
142   struct strawberry *next, *prev;
143   short which, len;
144   char *title;
145   char forever[];
146 };
147
148 // Data layout in toybuf
149 struct carveup {
150   long long slot[50];       // data from /proc, skippint #2 and #3
151   unsigned short offset[4]; // offset of fields in str[] (skip name, always 0)
152   char state;
153   char str[];              // name, tty, wchan, attr, cmdline
154 };
155
156 // Return 1 to keep, 0 to discard
157 static int match_process(long long *slot)
158 {
159   struct ptr_len *match[] = {
160     &TT.gg, &TT.GG, &TT.pp, &TT.PP, &TT.ss, &TT.tt, &TT.uu, &TT.UU
161   };
162   int i, j, mslot[] = {33, 34, 0, 1, 3, 4, 31, 32};
163   long *ll = 0;
164
165   // Do we have -g -G -p -P -s -t -u -U options selecting processes?
166   for (i = 0; i < ARRAY_LEN(match); i++) {
167     if (match[i]->len) {
168       ll = match[i]->ptr;
169       for (j = 0; j<match[i]->len; j++) if (ll[j] == slot[mslot[i]]) return 1;
170     }
171   }
172
173   // If we had selections and didn't match them, don't display
174   if (ll) return 0;
175
176   // Filter implicit categories for other display types
177   if ((toys.optflags&(FLAG_a|FLAG_d)) && slot[3]==*slot) return 0;
178   if ((toys.optflags&FLAG_a) && !slot[4]) return 0;
179   if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e)) && TT.tty!=slot[4])
180     return 0;
181
182   return 1;
183 }
184
185 static char *string_field(struct carveup *tb, struct strawberry *field)
186 {
187   char *buf = toybuf+sizeof(toybuf)-64, *out = buf, *s;
188   long long ll, *slot = tb->slot;
189   int i;
190
191   // Default: unsupported (5 "C")
192   sprintf(out, "-");
193
194   // stat#s: PID, PPID, PRI, NI, ADDR, SZ, RSS, PGID, VSS, MAJFL, MINFL
195   if (-1!=(i = stridx((char[]){3,4,6,7,8,9,24,19,23,25,30,0}, field->which)))
196   {
197     char *fmt = "%lld";
198
199     ll = slot[((char[]){0,1,15,16,27,20,21,2,20,9,7})[i]];
200     if (i==2) ll--;
201     if (i==4) fmt = "%llx";
202     else if (i==5) ll >>= 12;
203     else if (i==6) ll <<= 2;
204     else if (i==8) ll >>= 10;
205     sprintf(out, fmt, ll);
206   // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP
207   } else if (-1!=(i = stridx((char[]){2,22,28,21,26,17,29,20}, field->which)))
208   {
209     int id = slot[31+i/2]; // uid, ruid, gid, rgid
210
211     // Even entries are numbers, odd are names
212     sprintf(out, "%d", id);
213     if (!(toys.optflags&FLAG_n) && i&1) {
214       if (i>3) {
215         struct group *gr = getgrgid(id);
216
217         if (gr) out = gr->gr_name;
218       } else {
219         struct passwd *pw = getpwuid(id);
220
221         if (pw) out = pw->pw_name;
222       }
223     }
224   // CMD TTY WCHAN LABEL (CMDLINE handled elsewhere)
225   } else if (-1!=(i = stridx((char[]){15,12,10,31}, field->which))) {
226     out = tb->str;
227     if (i) out += tb->offset[i-1];
228
229   // F (also assignment of i used by later tests)
230   // Posix doesn't specify what flags should say. Man page says
231   // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h
232   } else if (!(i = field->which)) sprintf(out, "%llo", (slot[6]>>6)&5);
233   // S STAT
234   else if (i==1 || i==27) {
235     s = out;
236     *s++ = tb->state;
237     if (i==27) {
238       // TODO l = multithreaded
239       if (slot[16]<0) *s++ = '<';
240       else if (slot[16]>0) *s++ = 'N';
241       if (slot[3]==*slot) *s++ = 's';
242       if (slot[18]) *s++ = 'L';
243       if (slot[5]==*slot) *s++ = '+';
244     } 
245     *s = 0;
246   // STIME
247   } else if (i==11) {
248     time_t t = time(0)-slot[46]+slot[19]/sysconf(_SC_CLK_TCK);
249
250     // Padding behavior's a bit odd: default field size is just hh:mm.
251     // Increasing stime:size reveals more data at left until full,
252     // so move start address so yyyy-mm-dd hh:mm revealed on left at :16,
253     // then add :ss on right for :19.
254     strftime(out, 64, "%F %T", localtime(&t));
255     out = out+strlen(out)-3-abs(field->len);
256     if (out<buf) out = buf;
257
258   // TIME ELAPSED
259   } else if (i==13 || i==16) {
260     int unit = 60*60*24, j = sysconf(_SC_CLK_TCK);
261     time_t seconds = (i==16) ? (slot[46]*j)-slot[19] : slot[11]+slot[12];
262
263     seconds /= j;
264     for (s = 0, j = 0; j<4; j++) {
265       // TIME has 3 required fields, ETIME has 2. (Posix!)
266       if (!s && (seconds>unit || j == 1+(i==16))) s = out;
267       if (s) {
268         s += sprintf(s, j ? "%02ld": "%2ld", (long)(seconds/unit));
269         if ((*s = "-::"[j])) s++;
270       }
271       seconds %= unit;
272       unit /= j ? 60 : 24;
273     }
274
275   // COMM - command line including arguments
276   // CMDLINE - command name from /proc/pid/cmdline (no arguments)
277   } else if (i==14 || i==32) {
278     if (slot[47]<1) {
279       out = toybuf+sizeof(toybuf)-300;
280       sprintf(out, "[%s]", tb->str); // kernel thread, use real name
281     } else {
282       out = tb->str+tb->offset[3];
283       if (slot[47]!=INT_MAX) out[slot[47]] = ' '*(i==14);
284     }
285
286   // %CPU
287   } else if (i==18) {
288     ll = (slot[46]*sysconf(_SC_CLK_TCK)-slot[19]);
289     i = ((slot[11]+slot[12])*1000)/ll;
290     sprintf(out, "%d.%d", i/10, i%10);
291   }
292
293   return out;
294 }
295
296 // Display process data that get_ps() read from /proc, formatting with TT.fields
297 static void show_ps(struct carveup *tb)
298 {
299   struct strawberry *field;
300   int i, len, width = TT.width;
301
302   // Loop through fields to display
303   for (field = TT.fields; field; field = field->next) {
304     char *out = string_field(tb, field);
305
306     // Output the field, appropriately padded
307     len = width - (field != TT.fields);
308     if (!field->next && field->len<0) i = 0;
309     else {
310       i = len<abs(field->len) ? len : field->len;
311       len = abs(i);
312     }
313
314     // TODO test utf8 fontmetrics
315     width -= printf(" %*.*s" + (field == TT.fields), i, len, out);
316     if (!width) break;
317   }
318   xputc('\n');
319 }
320
321 // dirtree callback: read data about process to display, store, or discard it.
322 // Collects slot[50] plus  stat, name, tty, wchan, attr, cmdline
323 static int get_ps(struct dirtree *new)
324 {
325   struct {
326     char *name;
327     long long bits;
328   } fetch[] = {{"fd/", 1<<12}, {"wchan", 1<<10}, {"attr/current", 1<<31},
329                {"cmdline", (1<<14)|(1LL<<32)}};
330   struct carveup *tb = (void *)toybuf;
331   long long *slot = (void *)toybuf;
332   char *name, *s, *buf = tb->str, *end = 0;
333   int i, j, fd, ksave = DIRTREE_SAVE*!!(toys.optflags&FLAG_k);
334   off_t len;
335
336   // Recurse one level into /proc children, skip non-numeric entries
337   if (!new->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP|ksave;
338   if (!(*slot = atol(new->name))) return 0;
339   fd = dirtree_parentfd(new);
340
341   len = 2048;
342   sprintf(buf, "%lld/stat", *slot);
343   if (!readfileat(fd, buf, buf, &len)) return 0;
344
345   // parse oddball fields (name and state). Name can have embedded ')' so match
346   // _last_ ')' in stat (although VFS limits filenames to 255 bytes max).
347   // All remaining fields should be numeric.
348   if (!(name = strchr(buf, '('))) return 0;
349   for (s = ++name; *s; s++) if (*s == ')') end = s;
350   if (!end || end-name>255) return 0;
351
352   // Move name right after slot[] array (pid already set, so stomping it's ok)
353   // and convert low chars to spaces while we're at it.
354   for (i = 0; i<end-name; i++)
355     if ((tb->str[i] = name[i]) < ' ') tb->str[i] = ' ';
356   buf = tb->str+i;
357   *buf++ = 0;
358
359   // Parse numeric fields (starting at 4th field in slot[1])
360   if (1>sscanf(s = end, ") %c%n", &tb->state, &i)) return 0;
361   for (j = 1; j<50; j++) if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break;
362
363   // Now we've read the data, move status and name right after slot[] array,
364   // and convert low chars to spaces while we're at it.
365   for (i = 0; i<end-name; i++)
366     if ((tb->str[i] = name[i]) < ' ') tb->str[i] = ' ';
367   buf = tb->str+i;
368   *buf++ = 0;
369
370   // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch
371   // or numeric wchan, and the remaining two are always zero), and vmlck into
372   // 18 (which is "obsolete, always 0")
373   slot[31] = new->st.st_uid;
374   slot[33] = new->st.st_gid;
375
376   // If RGROUP RUSER STAT RUID RGID happening, or -G or -U, parse "status"
377   // Save ruid in slot[34] and rgid in slot[35], which are otherwise zero,
378   // and vmlck into slot[18] (it_real_value, also always zero).
379   if ((TT.bits & 0x38300000) || TT.GG.len || TT.UU.len) {
380     len = sizeof(toybuf)-(buf-toybuf);
381     sprintf(buf, "%lld/status", *slot);
382     if (!readfileat(fd, buf, buf, &len)) *buf = 0;
383     s = strstr(buf, "\nUid:");
384     slot[32] = s ? atol(s+5) : new->st.st_uid;
385     s = strstr(buf, "\nGid:");
386     slot[34] = s ? atol(s+5) : new->st.st_gid;
387     s = strstr(buf, "\nVmLck:");
388     if (s) slot[18] = atoll(s+5);
389   }
390
391   // We now know enough to skip processes we don't care about.
392   if (!match_process(slot)) return 0;
393
394   // /proc data is generated as it's read, so for maximum accuracy on slow
395   // systems (or ps | more) we re-fetch uptime as we fetch each /proc line.
396   sysinfo((void *)(toybuf+2048));
397   slot[46] = ((struct sysinfo *)toybuf)->uptime;
398
399   // fetch remaining data while parentfd still available, appending to buf.
400   // (There's well over 3k of toybuf left. We could dynamically malloc, but
401   // it'd almost never get used, querying length of a proc file is awkward,
402   // fixed buffer is nommu friendly... Wait for somebody to complain. :)
403   for (j = 0; j<ARRAY_LEN(fetch); j++) { 
404     tb->offset[j] = buf-(tb->str);
405     if (!(TT.bits&fetch[j].bits)) {
406       *buf++ = 0;
407       continue;
408     }
409
410     // Determine remaining space, reserving minimum of 256 bytes/field and
411     // 64 bytes scratch space at the end (for output conversion later).
412     len = sizeof(toybuf)-(buf-toybuf)-64-256*(ARRAY_LEN(fetch)-j);
413     sprintf(buf, "%lld/%s", *slot, fetch[j].name);
414
415     // If it's not the TTY field, data we want is in a file.
416     // Last length saved in slot[] is command line (which has embedded NULs)
417     if (j) {
418       readfileat(fd, buf, buf, &len);
419
420       // When command has no arguments, don't space over the NUL
421       if (len>0) {
422         int temp = 0;
423
424         if (buf[len-1]=='\n') buf[--len] = 0;
425
426         // Always escape spaces because an executable named esc[0m would be bad.
427         // Escaping low ascii does not affect utf8.
428         for (i=0; i<len; i++) {
429           if (!temp && !buf[i]) temp = i;
430           if (buf[i]<' ') buf[i] = ' ';
431         }
432         if (temp) len = temp; // position of _first_ NUL
433         else len = INT_MAX;
434       } else *buf = len = 0;
435       // Store end of argv[0] so COMM and CMDLINE can differ.
436       slot[47] = len;
437     } else {
438       int rdev = slot[4];
439       struct stat st;
440
441       // Call no tty "?" rather than "0:0".
442       strcpy(buf, "?");
443       if (rdev) {
444         // Can we readlink() our way to a name?
445         for (i = 0; i<3; i++) {
446           sprintf(buf, "%lld/fd/%i", *slot, i);
447           if (!fstatat(fd, buf, &st, 0) && S_ISCHR(st.st_mode)
448             && st.st_rdev == rdev && 0<(len = readlinkat(fd, buf, buf, len)))
449           {
450             buf[len] = 0;
451             break;
452           }
453         }
454
455         // Couldn't find it, try all the tty drivers.
456         if (i == 3) {
457           FILE *fp = fopen("/proc/tty/drivers", "r");
458           int tty_major = 0, maj = major(rdev), min = minor(rdev);
459
460           if (fp) {
461             while (fscanf(fp, "%*s %256s %d %*s %*s", buf, &tty_major) == 2) {
462               // TODO: we could parse the minor range too.
463               if (tty_major == maj) {
464                 sprintf(buf+strlen(buf), "%d", min);
465                 if (!stat(buf, &st) && S_ISCHR(st.st_mode) && st.st_rdev==rdev)
466                   break;
467               }
468               tty_major = 0;
469             }
470             fclose(fp);
471           }
472
473           // Really couldn't find it, so just show major:minor.
474           if (!tty_major) sprintf(buf, "%d:%d", maj, min);
475         }
476
477         s = buf;
478         if (strstart(&s, "/dev/")) memmove(buf, s, strlen(s)+1);;
479       }
480     }
481     buf += strlen(buf)+1;
482   }
483
484   // If we need to sort the output, add it to the list and return.
485   if (ksave) {
486     s = xmalloc(buf-toybuf);
487     new->extra = (long)s;
488     memcpy(s, toybuf, buf-toybuf);
489     TT.kcount++;
490
491   // Otherwise display it now
492   } else show_ps(tb);
493
494   return ksave;
495 }
496
497 // Traverse arg_list of csv, calling callback on each value
498 void comma_args(struct arg_list *al, char *err,
499   char *(*callback)(char *str, int len))
500 {
501   char *next, *arg;
502   int len;
503
504   while (al) {
505     arg = al->arg;
506     while ((next = comma_iterate(&arg, &len)))
507       if ((next = callback(next, len)))
508         perror_exit("%s '%s'\n%*c", err, al->arg,
509           (int)(5+strlen(toys.which->name)+strlen(err)+next-al->arg), '^');
510     al = al->next;
511   }
512 }
513
514 static char *parse_o(char *type, int length)
515 {
516   struct strawberry *field;
517   char *width, *title, *end, *s, *typos[] = {
518          "F", "S", "UID", "PID", "PPID", "C", "PRI", "NI", "ADDR", "SZ",
519          "WCHAN", "STIME", "TTY", "TIME", "CMD", "COMMAND", "ELAPSED", "GROUP",
520          "%CPU", "PGID", "RGROUP", "RUSER", "USER", "VSZ", "RSS", "MAJFL",
521          "GID", "STAT", "RUID", "RGID", "MINFL", "LABEL", "CMDLINE"
522   };
523   // TODO: Android uses -30 for LABEL, but ideally it would auto-size.
524   signed char widths[] = {1,-1,5,5,5,2,3,3,4+sizeof(long),5,
525                           -6,5,-8,8,-27,-27,11,-8,
526                           4,5,-8,-8,-8,6,5,6,
527                           8,-5,4,4,6,-30,-27};
528   int i, j, k;
529
530   // Get title, length of title, type, end of type, and display width
531
532   // Chip off =name to display
533   if ((end = strchr(type, '=')) && length>(end-type)) {
534     title = end+1;
535     length -= (end-type)+1;
536   } else {
537     end = type+length;
538     title = 0;
539   }
540
541   // Chip off :width to display
542   if ((width = strchr(type, ':')) && width<end) {
543     if (!title) length = width-type;
544   } else width = 0;
545
546   // Allocate structure, copy title
547   field = xzalloc(sizeof(struct strawberry)+(length+1)*!!title);
548   if (title) {
549     memcpy(field->title = field->forever, title, length);
550     field->title[field->len = length] = 0;
551   }
552
553   if (width) {
554     field->len = strtol(++width, &title, 10);
555     if (!isdigit(*width) || title != end) return title;
556     end = --width;
557   }
558
559   // Find type
560   for (i = 0; i<ARRAY_LEN(typos); i++) {
561     field->which = i;
562     for (j = 0; j<2; j++) {
563       if (!j) s = typos[i];
564       // posix requires alternate names for some fields
565       else if (-1==(k = stridx((char []){7,14,15,16,18,23,22,0}, i))) continue;
566       else s = ((char *[]){"NICE","ARGS","COMM","ETIME","PCPU",
567                            "VSIZE","UNAME"})[k];
568
569       if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break;
570     }
571     if (j!=2) break;
572   }
573   if (i==ARRAY_LEN(typos)) return type;
574   if (!field->title) field->title = typos[field->which];
575   if (!field->len) field->len = widths[field->which];
576   else if (widths[field->which]<0) field->len *= -1;
577   dlist_add_nomalloc((void *)&TT.fields, (void *)field);
578
579   // Print padded header.
580   TT.header_len +=
581     snprintf(toybuf + TT.header_len, sizeof(toybuf) - TT.header_len,
582              " %*s" + (field == TT.fields), field->len, field->title);
583   TT.bits |= (i = 1<<field->which);
584
585   return 0;
586 }
587
588 // Parse -p -s -t -u -U -g -G
589 static char *parse_rest(char *str, int len)
590 {
591   struct ptr_len *pl = TT.parsing;
592   long *ll = pl->ptr;
593   char *end;
594   int num = 0;
595
596   // numeric: -p, -s
597   // gg, GG, pp, ss, tt, uu, UU, *parsing;
598  
599   // Allocate next chunk of data
600   if (!(15&pl->len))
601     ll = pl->ptr = xrealloc(pl->ptr, sizeof(long)*(pl->len+16));
602
603   // Parse numerical input
604   if (isdigit(*str)) {
605     ll[pl->len] = xstrtol(str, &end, 10);
606     if (end==(len+str)) num++;
607   }
608
609   if (pl==&TT.pp || pl==&TT.ss) {
610     if (num && ll[pl->len]>0) {
611       pl->len++;
612
613       return 0;
614     }
615   } else if (pl==&TT.tt) {
616     // -t pts = 12,pts/12 tty = /dev/tty2,tty2,S0
617     if (!num) {
618       if (strstart(&str, strcpy(toybuf, "/dev/"))) len -= 5;
619       if (strstart(&str, "pts/")) {
620         len -= 4;
621         num++;
622       } else if (strstart(&str, "tty")) len -= 3;
623     }
624     if (len<256 && (!(end = strchr(str, '/')) || end-str>len)) {
625       struct stat st;
626
627       end = toybuf + sprintf(toybuf, "/dev/%s", num ? "pts/" : "tty");
628       memcpy(end, str, len);
629       end[len] = 0;
630       xstat(toybuf, &st);
631       ll[pl->len++] = st.st_rdev;
632
633       return 0;
634     }
635   } else if (len<255) {
636     char name[256];
637
638     if (num) {
639       pl->len++;
640
641       return 0;
642     }
643
644     memcpy(name, str, len);
645     name[len] = 0;
646     if (pl==&TT.gg || pl==&TT.GG) {
647       struct group *gr = getgrnam(name);
648       if (gr) {
649         ll[pl->len++] = gr->gr_gid;
650
651         return 0;
652       }
653     } else if (pl==&TT.uu || pl==&TT.UU) {
654       struct passwd *pw = getpwnam(name);
655       if (pw) {
656         ll[pl->len++] = pw->pw_uid;
657
658         return 0;
659       }
660     }
661   }
662
663   // Return error
664   return str;
665 }
666
667 // sort for -k
668 static int ksort(void *aa, void *bb)
669 {
670   long long lla, llb;
671   char *out, *end;
672
673   // Convert fields to string version, saving first in toybuf
674   out = string_field((void *)aa, TT.fields);
675   memccpy(toybuf, out, 0, 2048);
676   toybuf[2048] = 0;
677   out = string_field((void *)bb, TT.fields);
678
679   // Numeric comparison?
680   llb = strtoll(out, &end, 10);
681   if (!*end) {
682     lla = strtoll(toybuf, &end, 10);
683     if (!*end) {
684       if (lla<llb) return -1;
685       if (lla>llb) return 1;
686       return 0;
687     }
688   }
689
690   // String compare
691   return strcmp(toybuf, out);
692 }
693
694 void ps_main(void)
695 {
696   struct dirtree *dt;
697   int i;
698
699   TT.width = 99999;
700   if (!(toys.optflags&FLAG_w)) terminal_size(&TT.width, 0);
701
702   // find controlling tty, falling back to /dev/tty if none
703   for (i = 0; !TT.tty && i<4; i++) {
704     struct stat st;
705     int fd = i;
706
707     if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break;
708
709     if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev;
710     if (i==3) close(fd);
711   }
712
713   // parse command line options other than -o
714   TT.parsing = &TT.PP;
715   comma_args(TT.P, "bad -P", parse_rest);
716   TT.parsing = &TT.pp;
717   comma_args(TT.p, "bad -p", parse_rest);
718   TT.parsing = &TT.tt;
719   comma_args(TT.t, "bad -t", parse_rest);
720   TT.parsing = &TT.ss;
721   comma_args(TT.s, "bad -s", parse_rest);
722   TT.parsing = &TT.uu;
723   comma_args(TT.u, "bad -u", parse_rest);
724   TT.parsing = &TT.UU;
725   comma_args(TT.U, "bad -u", parse_rest);
726   TT.parsing = &TT.gg;
727   comma_args(TT.g, "bad -g", parse_rest);
728   TT.parsing = &TT.GG;
729   comma_args(TT.G, "bad -G", parse_rest);
730
731   // Parse manual field selection, or default/-f/-l, plus -Z,
732   // constructing the header line in toybuf as we go.
733   if (toys.optflags&FLAG_Z) {
734     struct arg_list Z = { 0, "LABEL" };
735
736     comma_args(&Z, "-Z", parse_o);
737   }
738   if (TT.o) comma_args(TT.o, "bad -o field", parse_o);
739   else {
740     struct arg_list al;
741
742     al.next = 0;
743     if (toys.optflags&FLAG_f)
744       al.arg = "USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD";
745     else if (toys.optflags&FLAG_l)
746       al.arg = "F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD";
747     else if (CFG_TOYBOX_ON_ANDROID)
748       al.arg = "USER,PID,PPID,VSIZE,RSS,WCHAN:10,ADDR:10=PC,S,CMDLINE";
749     else al.arg = "PID,TTY,TIME,CMD";
750
751     comma_args(&al, 0, parse_o);
752   }
753   dlist_terminate(TT.fields);
754   printf("%s\n", toybuf);
755
756   dt = dirtree_read("/proc", get_ps);
757
758   if (toys.optflags&FLAG_k) {
759     struct carveup **tbsort = xmalloc(TT.kcount*sizeof(struct carveup *));
760
761     // descend into child list
762     *tbsort = (void *)dt;
763     dt = dt->child;
764     free(*tbsort);
765
766     // populate array
767     i = 0;
768     while (dt) {
769       void *temp = dt->next;
770
771       tbsort[i++] = (void *)dt->extra;
772       free(dt);
773       dt = temp;
774     }
775
776     qsort(tbsort, TT.kcount, sizeof(struct dirtree *), (void *)ksort);
777     for (i = 0; i<TT.kcount; i++) {
778       show_ps(tbsort[i]);
779       free(tbsort[i]);
780     }
781   }
782
783   if (CFG_TOYBOX_FREE) {
784     free(TT.gg.ptr);
785     free(TT.GG.ptr);
786     free(TT.pp.ptr);
787     free(TT.PP.ptr);
788     free(TT.ss.ptr);
789     free(TT.tt.ptr);
790     free(TT.uu.ptr);
791     free(TT.UU.ptr);
792     llist_traverse(TT.fields, free);
793   }
794 }