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  * Note: iotop is STAYROOT so it can read other process's /proc/$PID/io
33  *       files (why they're not globally readable when the rest of proc
34  *       data is...?) and get a global I/O picture. Normal top is NOT,
35  *       even though you can -o AIO there, to give sysadmins the option
36  *       to reduce security exposure.)
37  *
38  * TODO: ps aux (att & bsd style "ps -ax" vs "ps ax" behavior difference)
39  * TODO: switch -fl to -y
40  * TODO: thread support /proc/$d/task/%d/stat (and -o stat has "l")
41  * TODO: iotop: Window size change: respond immediately. Why not padding
42  *       at right edge? (Not adjusting to screen size at all? Header wraps?)
43  * TODO: top: thread support and SMP
44  * TODO: pgrep -f only searches the amount of cmdline that fits in toybuf.
45 
46 USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflMno*O*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
47 // stayroot because iotop needs root to read other process' proc/$$/io
48 USE_TOP(NEWTOY(top, ">0m" "k*o*p*u*s#<1=9d#=3<1n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
49 USE_IOTOP(NEWTOY(iotop, ">0AaKO" "k*o*p*u*s#<1=7d#=3<1n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE))
50 USE_PGREP(NEWTOY(pgrep, "?cld:u*U*t*s*P*g*G*fnovxL:[-no]", TOYFLAG_USR|TOYFLAG_BIN))
51 USE_PKILL(NEWTOY(pkill,     "Vu*U*t*s*P*g*G*fnovxl:[-no]", TOYFLAG_USR|TOYFLAG_BIN))
52 
53 config PS
54   bool "ps"
55   default y
56   help
57     usage: ps [-AadeflnwZ] [-gG GROUP,] [-k FIELD,] [-o FIELD,] [-p PID,] [-t TTY,] [-uU USER,]
58 
59     List processes.
60 
61     Which processes to show (selections may be comma separated lists):
62 
63     -A	All processes
64     -a	Processes with terminals that aren't session leaders
65     -d	All processes that aren't session leaders
66     -e	Same as -A
67     -g	Belonging to GROUPs
68     -G	Belonging to real GROUPs (before sgid)
69     -p	PIDs (--pid)
70     -P	Parent PIDs (--ppid)
71     -s	In session IDs
72     -t	Attached to selected TTYs
73     -u	Owned by USERs
74     -U	Owned by real USERs (before suid)
75 
76     Output modifiers:
77 
78     -k	Sort FIELDs in +increasing or -decreasting order (--sort)
79     -M	Measure field widths (expanding as necessary)
80     -n	Show numeric USER and GROUP
81     -w	Wide output (don't truncate at terminal width)
82 
83     Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD)
84 
85     -f	Full listing (-o USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD)
86     -l	Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD)
87     -o	Output FIELDs instead of defaults, each with optional :size and =title
88     -O  Add FIELDS to defaults
89     -Z	Include LABEL
90 
91     Available -o FIELDs:
92 
93       ADDR  Instruction pointer               ARGS    Command line (argv[] -path)
94       CMD   COMM without -f, ARGS with -f     CMDLINE Command line (argv[])
95       COMM  Original command name             COMMAND Original command path
96       CPU   Which processor running on        ETIME   Elapsed time since PID start
97       F     Flags (1=FORKNOEXEC 4=SUPERPRIV)  GID     Group id
98       GROUP Group name                        LABEL   Security label
99       MAJFL Major page faults                 MINFL   Minor page faults
100       NAME  Command name (argv[0])            NI      Niceness (lower is faster)
101       PCPU  Percentage of CPU time used       PGID    Process Group ID
102       PID   Process ID                        PPID    Parent Process ID
103       PRI   Priority (higher is faster)       PSR     Processor last executed on
104       RGID  Real (before sgid) group ID       RGROUP  Real (before sgid) group name
105       RSS   Resident Set Size (pages in use)  RTPRIO  Realtime priority
106       RUID  Real (before suid) user ID        RUSER   Real (before suid) user name
107       S     Process state:
108             R (running) S (sleeping) D (device I/O) T (stopped)  t (traced)
109             Z (zombie)  X (deader)   x (dead)       K (wakekill) W (waking)
110       SCHED Scheduling policy (0=other, 1=fifo, 2=rr, 3=batch, 4=iso, 5=idle)
111       STAT  Process state (S) plus:
112             < high priority          N low priority L locked memory
113             s session leader         + foreground   l multithreaded
114       STIME Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss)
115       SZ    Memory Size (4k pages needed to completely swap out process)
116       TIME  CPU time consumed                 TTY     Controlling terminal
117       UID   User id                           USER    User name
118       VSZ   Virtual memory size (1k units)    %VSZ    VSZ as % of physical memory
119       WCHAN Waiting in kernel for
120 
121 config TOP
122   bool "top"
123   default y
124   help
125     usage: top [-m] [ -d seconds ] [ -n iterations ]
126 
127     Show process activity in real time.
128 
129     -k	Fallback sort FIELDS (default -S,-%CPU,-ETIME,-PID)
130     -o	Show FIELDS (def PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,CMDLINE)
131     -s	Sort by field number (1-X, default 9)
132 
133 # Requires CONFIG_IRQ_TIME_ACCOUNTING in the kernel for /proc/$$/io
134 config IOTOP
135   bool "iotop"
136   default y
137   help
138     usage: iotop [-AaKO]
139 
140     Rank processes by I/O.
141 
142     -A	All I/O, not just disk
143     -a	Accumulated I/O (not percentage)
144     -K	Kilobytes
145     -k	Fallback sort FIELDS (default -[D]IO,-ETIME,-PID)
146     -O	Only show processes doing I/O
147     -o	Show FIELDS (default PID,PR,USER,[D]READ,[D]WRITE,SWAP,[D]IO,COMM)
148     -s	Sort by field number (0-X, default 6)
149 
150 config TOP_COMMON
151   bool
152   default y
153   help
154     usage: COMMON [-bq] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,] [-s SORT]
155 
156     -b	Batch mode (no tty)
157     -d	Delay SECONDS between each cycle (default 3)
158     -n	Exit after NUMBER iterations
159     -p	Show these PIDs
160     -u	Show these USERs
161     -q	Quiet (no header lines)
162 
163     Cursor LEFT/RIGHT to change sort, UP/DOWN move list, space to force
164     update, R to reverse sort, Q to exit.
165 
166 config PGREP
167   bool "pgrep"
168   default y
169   depends on PGKILL_COMMON
170   help
171     usage: pgrep [-cL] [-d DELIM] [-L SIGNAL] [PATTERN]
172 
173     Search for process(es). PATTERN is an extended regular expression checked
174     against command names.
175 
176     -c	Show only count of matches
177     -d	Use DELIM instead of newline
178     -L	Send SIGNAL instead of printing name
179     -l	Show command name
180 
181 config PGKILL_COMMON
182   bool
183   default y
184   help
185     usage: pgrep [-fnovx] [-G GID,] [-g PGRP,] [-P PPID,] [-s SID,] [-t TERM,] [-U UID,] [-u EUID,]
186 
187     -f	Check full command line for PATTERN
188     -G	Match real Group ID(s)
189     -g	Match Process Group(s) (0 is current user)
190     -n	Newest match only
191     -o	Oldest match only
192     -P	Match Parent Process ID(s)
193     -s	Match Session ID(s) (0 for current)
194     -t	Match Terminal(s)
195     -U	Match real User ID(s)
196     -u	Match effective User ID(s)
197     -v	Negate the match
198     -x	Match whole command (not substring)
199 
200 config PKILL
201   bool "pkill"
202   default y
203   help
204     usage: pkill [-l SIGNAL] [PATTERN]
205 
206     -l	SIGNAL to send
207     -V	verbose
208 */
209 
210 #define FOR_ps
211 #include "toys.h"
212 
213 GLOBALS(
214   union {
215     struct {
216       struct arg_list *G;
217       struct arg_list *g;
218       struct arg_list *U;
219       struct arg_list *u;
220       struct arg_list *t;
221       struct arg_list *s;
222       struct arg_list *p;
223       struct arg_list *O;
224       struct arg_list *o;
225       struct arg_list *P;
226       struct arg_list *k;
227     } ps;
228     struct {
229       long n;
230       long d;
231       long s;
232       struct arg_list *u;
233       struct arg_list *p;
234       struct arg_list *o;
235       struct arg_list *k;
236     } top;
237     struct{
238       char *L;
239       struct arg_list *G;
240       struct arg_list *g;
241       struct arg_list *P;
242       struct arg_list *s;
243       struct arg_list *t;
244       struct arg_list *U;
245       struct arg_list *u;
246       char *d;
247 
248       void *regexes, *snapshot;
249       int signal;
250       pid_t self, match;
251     } pgrep;
252   };
253 
254   struct sysinfo si;
255   struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU;
256   unsigned width, height;
257   dev_t tty;
258   void *fields, *kfields;
259   long long ticks, bits, time;
260   int kcount, forcek, sortpos;
261   int (*match_process)(long long *slot);
262   void (*show_process)(void *tb);
263 )
264 
265 struct strawberry {
266   struct strawberry *next, *prev;
267   short which, len, reverse;
268   char *title;
269   char forever[];
270 };
271 
272 /* The slot[] array is mostly populated from /proc/$PID/stat (kernel proc.txt
273  * table 1-4) but we shift and repurpose fields, with the result being: */
274 
275 enum {
276  SLOT_pid,      /*process id*/            SLOT_ppid,      // parent process id
277  SLOT_pgrp,     /*process group*/         SLOT_sid,       // session id
278  SLOT_ttynr,    /*tty the process uses*/  SLOT_ttypgrp,   // pgrp of the tty
279  SLOT_flags,    /*task flags*/            SLOT_minflt,    // minor faults
280  SLOT_cminflt,  /*minor faults+child*/    SLOT_majflt,    // major faults
281  SLOT_cmajflt,  /*major faults+child*/    SLOT_utime,     // user+kernel jiffies
282  SLOT_stime,    /*kernel mode jiffies*/   SLOT_cutime,    // utime+child
283  SLOT_cstime,   /*stime+child*/           SLOT_priority,  // priority level
284  SLOT_nice,     /*nice level*/            SLOT_numthreads,// thread count
285  SLOT_vmlck,    /*locked memory*/         SLOT_starttime, // jiffies after boot
286  SLOT_vsize,    /*virtual memory size*/   SLOT_rss,       // resident set size
287  SLOT_rsslim,   /*limit in bytes on rss*/ SLOT_startcode, // code segment addr
288  SLOT_endcode,  /*code segment address*/  SLOT_startstack,// stack address
289  SLOT_esp,      /*task stack pointer*/    SLOT_eip,       // instruction pointer
290  SLOT_iobytes,  /*All I/O bytes*/         SLOT_diobytes,  // disk I/O bytes
291  SLOT_utime2,   /*relative utime (top)*/  SLOT_uid,       // user id
292  SLOT_ruid,     /*real user id*/          SLOT_gid,       // group id
293  SLOT_rgid,     /*real group id*/         SLOT_exitsig,   // sent to parent
294  SLOT_taskcpu,  /*CPU running on*/        SLOT_rtprio,    // realtime priority
295  SLOT_policy,   /*man sched_setscheduler*/SLOT_blkioticks,// IO wait time
296  SLOT_gtime,    /*guest jiffies of task*/ SLOT_cgtime,    // gtime+child
297  SLOT_startbss, /*data/bss address*/      SLOT_endbss,    // end addr data+bss
298  SLOT_upticks,  /*46-19 (divisor for %)*/ SLOT_argv0len,  // argv[0] length
299  SLOT_uptime,   /*si.uptime @read time*/  SLOT_vsz,       // Virtual mem Size
300  SLOT_rss2,     /*Resident Set Size*/     SLOT_shr,       // Shared memory
301  SLOT_rchar,    /*All bytes read*/        SLOT_wchar,     // All bytes written
302  SLOT_rbytes,   /*Disk bytes read*/       SLOT_wbytes,    // Disk bytes written
303  SLOT_swap,     /*Swap pages used*/
304 };
305 
306 // Data layout in toybuf
307 struct carveup {
308   long long slot[55];       // data from /proc
309   unsigned short offset[5]; // offset of fields in str[] (skip name, always 0)
310   char state;
311   char str[];               // name, tty, command, wchan, attr, cmdline
312 };
313 
314 // TODO: Android uses -30 for LABEL, but ideally it would auto-size.
315 // 64|slot means compare as string when sorting
316 struct typography {
317   char *name;
318   signed char width, slot;
319 } static const typos[] = TAGGED_ARRAY(PS,
320   // Numbers
321   {"PID", 5, SLOT_pid}, {"PPID", 5, SLOT_ppid}, {"PRI", 3, SLOT_priority},
322   {"NI", 3, SLOT_nice}, {"ADDR", 4+sizeof(long), SLOT_eip},
323   {"SZ", 5, SLOT_vsize}, {"RSS", 5, SLOT_rss}, {"PGID", 5, SLOT_pgrp},
324   {"VSZ", 6, SLOT_vsize}, {"MAJFL", 6, SLOT_majflt}, {"MINFL", 6, SLOT_minflt},
325   {"PR", 2, SLOT_priority}, {"PSR", 3, SLOT_taskcpu},
326   {"RTPRIO", 6, SLOT_rtprio}, {"SCH", 3, SLOT_policy}, {"CPU", 3, SLOT_taskcpu},
327 
328   // String fields
329   {"COMM", -15, -1}, {"TTY", -8, -2}, {"WCHAN", -6, -3}, {"LABEL", -30, -4},
330   {"COMMAND", -27, -5}, {"CMDLINE", -27, -6}, {"ARGS", -27, -6},
331   {"NAME", -15, -6}, {"CMD", -27, -1},
332 
333   // user/group
334   {"UID", 5, SLOT_uid}, {"USER", -8, 64|SLOT_uid}, {"RUID", 4, SLOT_ruid},
335   {"RUSER", -8, 64|SLOT_ruid}, {"GID", 8, SLOT_gid}, {"GROUP", -8, 64|SLOT_gid},
336   {"RGID", 4, SLOT_rgid}, {"RGROUP", -8, 64|SLOT_rgid},
337 
338   // clock displays
339   {"TIME", 8, SLOT_utime}, {"ELAPSED", 11, SLOT_starttime},
340   {"TIME+", 9, SLOT_utime},
341 
342   // Percentage displays
343   {"C", 1, SLOT_utime2}, {"%VSZ", 5, SLOT_vsize}, {"%MEM", 5, SLOT_rss},
344   {"%CPU", 4, SLOT_utime2},
345 
346   // human_readable
347   {"VIRT", 4, SLOT_vsz}, {"RES", 4, SLOT_rss2},
348   {"SHR", 4, SLOT_shr}, {"READ", 6, SLOT_rchar}, {"WRITE", 6, SLOT_wchar},
349   {"IO", 6, SLOT_iobytes}, {"DREAD", 6, SLOT_rbytes},
350   {"DWRITE", 6, SLOT_wbytes}, {"SWAP", 6, SLOT_swap}, {"DIO", 6, SLOT_diobytes},
351 
352   // Misc
353   {"STIME", 5, SLOT_starttime}, {"F", 1, 64|SLOT_flags}, {"S", -1, 64},
354   {"STAT", -5, 64},
355 );
356 
357 // Return 0 to discard, nonzero to keep
shared_match_process(long long * slot)358 static int shared_match_process(long long *slot)
359 {
360   struct ptr_len match[] = {
361     {&TT.gg, SLOT_gid}, {&TT.GG, SLOT_rgid}, {&TT.pp, SLOT_pid},
362     {&TT.PP, SLOT_ppid}, {&TT.ss, SLOT_sid}, {&TT.tt, SLOT_ttynr},
363     {&TT.uu, SLOT_uid}, {&TT.UU, SLOT_ruid}
364   };
365   int i, j;
366   long *ll = 0;
367 
368   // Do we have -g -G -p -P -s -t -u -U options selecting processes?
369   for (i = 0; i < ARRAY_LEN(match); i++) {
370     struct ptr_len *mm = match[i].ptr;
371     if (mm->len) {
372       ll = mm->ptr;
373       for (j = 0; j<mm->len; j++) if (ll[j] == slot[match[i].len]) return 1;
374     }
375   }
376 
377   return ll ? 0 : -1;
378 }
379 
380 
381 // Return 0 to discard, nonzero to keep
ps_match_process(long long * slot)382 static int ps_match_process(long long *slot)
383 {
384   int i = shared_match_process(slot);
385 
386   if (i>0) return 1;
387   // If we had selections and didn't match them, don't display
388   if (!i) return 0;
389 
390   // Filter implicit categories for other display types
391   if ((toys.optflags&(FLAG_a|FLAG_d)) && slot[SLOT_sid]==*slot) return 0;
392   if ((toys.optflags&FLAG_a) && !slot[SLOT_ttynr]) return 0;
393   if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e))
394       && TT.tty!=slot[SLOT_ttynr]) return 0;
395 
396   return 1;
397 }
398 
399 // Convert field to string representation
string_field(struct carveup * tb,struct strawberry * field)400 static char *string_field(struct carveup *tb, struct strawberry *field)
401 {
402   char *buf = toybuf+sizeof(toybuf)-260, *out = buf, *s;
403   int which = field->which, sl = typos[which].slot;
404   long long *slot = tb->slot, ll = (sl >= 0) ? slot[sl&63] : 0;
405 
406   // numbers, mostly from /proc/$PID/stat
407   if (which <= PS_CPU) {
408     char *fmt = "%lld";
409 
410     if (which==PS_PRI) ll = 39-ll;
411     if (which==PS_ADDR) fmt = "%llx";
412     else if (which==PS_SZ) ll >>= 12;
413     else if (which==PS_RSS) ll <<= 2;
414     else if (which==PS_VSZ) ll >>= 10;
415     else if (which==PS_PR && ll<-9) fmt="RT";
416     else if (which==PS_RTPRIO && ll == 0) fmt="-";
417     sprintf(out, fmt, ll);
418 
419   // String fields
420   } else if (sl < 0) {
421     if (slot[SLOT_argv0len])
422       tb->str[tb->offset[4]+slot[SLOT_argv0len]] = (which==PS_NAME) ? 0 : ' ';
423     out = tb->str;
424     sl *= -1;
425     if (--sl) out += tb->offset[--sl];
426     if (which==PS_ARGS)
427       for (s = out; *s && *s != ' '; s++) if (*s == '/') out = s+1;
428     if (which>=PS_COMMAND && !*out) sprintf(out = buf, "[%s]", tb->str);
429 
430   // user/group
431   } else if (which <= PS_RGROUP) {
432     sprintf(out, "%lld", ll);
433     if (sl&64) {
434       if (which > PS_RUSER) {
435         struct group *gr = getgrgid(ll);
436 
437         if (gr) out = gr->gr_name;
438       } else {
439         struct passwd *pw = getpwuid(ll);
440 
441         if (pw) out = pw->pw_name;
442       }
443     }
444 
445   // Clock displays
446   } else if (which <= PS_TIME_) {
447     int unit = 60, pad = 2, j = TT.ticks;
448     time_t seconds;
449 
450     if (which!=PS_TIME_) unit *= 60*24;
451     else pad = 0;
452     // top adjusts slot[SLOT_upticks], we want original meaning.
453     if (which==PS_ELAPSED) ll = (slot[SLOT_uptime]*j)-slot[SLOT_starttime];
454     seconds = ll/j;
455 
456     // Output days-hours:mins:secs, skipping non-required fields with zero
457     // TIME has 3 required fields, ETIME has 2. (Posix!) TIME+ is from top
458     for (s = 0, j = 2*(which==PS_TIME_); j<4; j++) {
459       if (!s && (seconds>unit || j == 1+(which!=PS_TIME))) s = out;
460       if (s) {
461         s += sprintf(s, j ? "%0*ld": "%*ld", pad, (long)(seconds/unit));
462         pad = 2;
463         if ((*s = "-::"[j])) s++;
464       }
465       seconds %= unit;
466       unit /= j ? 60 : 24;
467     }
468     if (which==PS_TIME_ && s-out<8)
469       sprintf(s, ".%02lld", (100*(ll%TT.ticks))/TT.ticks);
470 
471   // Percentage displays
472   } else if (which <= PS__CPU) {
473     ll = slot[sl&63]*1000;
474     if (which==PS__VSZ || which==PS__MEM)
475       ll /= TT.si.totalram/((which==PS__VSZ) ? 1024 : 4096);
476     else if (slot[SLOT_upticks]) ll /= slot[SLOT_upticks];
477     sl = ll;
478     if (which==PS_C) sl += 5;
479     sprintf(out, "%d", sl/10);
480     if (which!=PS_C && sl<1000) sprintf(out+strlen(out), ".%d", sl%10);
481 
482   // Human readable
483   } else if (which <= PS_DIO) {
484     ll = slot[typos[which].slot];
485     if (which <= PS_SHR) ll *= sysconf(_SC_PAGESIZE);
486     if (TT.forcek) sprintf(out, "%lldk", ll/1024);
487     else human_readable(out, ll, 0);
488 
489   // Posix doesn't specify what flags should say. Man page says
490   // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h
491   } else if (which==PS_F) sprintf(out, "%llo", (slot[SLOT_flags]>>6)&5);
492   else if (which==PS_S || which==PS_STAT) {
493     s = out;
494     *s++ = tb->state;
495     if (which==PS_STAT) {
496       // TODO l = multithreaded
497       if (slot[SLOT_nice]<0) *s++ = '<';
498       else if (slot[SLOT_nice]>0) *s++ = 'N';
499       if (slot[SLOT_sid]==*slot) *s++ = 's';
500       if (slot[SLOT_vmlck]) *s++ = 'L';
501       if (slot[SLOT_ttypgrp]==*slot) *s++ = '+';
502     }
503     *s = 0;
504   } else if (which==PS_STIME) {
505     time_t t = time(0)-slot[SLOT_uptime]+slot[SLOT_starttime]/TT.ticks;
506 
507     // Padding behavior's a bit odd: default field size is just hh:mm.
508     // Increasing stime:size reveals more data at left until full,
509     // so move start address so yyyy-mm-dd hh:mm revealed on left at :16,
510     // then add :ss on right for :19.
511     strftime(out, 260, "%F %T", localtime(&t));
512     out = out+strlen(out)-3-abs(field->len);
513     if (out<buf) out = buf;
514 
515   } else if (CFG_TOYBOX_DEBUG) error_exit("bad which %d", which);
516 
517   return out;
518 }
519 
520 // Display process data that get_ps() read from /proc, formatting with TT.fields
show_ps(struct carveup * tb)521 static void show_ps(struct carveup *tb)
522 {
523   struct strawberry *field;
524   int pad, len, width = TT.width;
525 
526   // Loop through fields to display
527   for (field = TT.fields; field; field = field->next) {
528     char *out = string_field(tb, field);
529 
530     // Output the field, appropriately padded
531     if (field != TT.fields) {
532       putchar(' ');
533       width--;
534     }
535     len = width;
536     pad = 0;
537     if (field->next || field->len>0)
538       len = abs(pad = width<abs(field->len) ? width : field->len);
539 
540     if (TT.tty) width -= draw_trim(out, pad, len);
541     else width -= printf("%*.*s", pad, len, out);
542     if (!width) break;
543   }
544   xputc(TT.time ? '\r' : '\n');
545 }
546 
547 // dirtree callback: read data about process to display, store, or discard it.
548 // Fills toybuf with struct carveup and either DIRTREE_SAVEs a copy to ->extra
549 // (in -k mode) or calls show_ps on toybuf (no malloc/copy/free there).
get_ps(struct dirtree * new)550 static int get_ps(struct dirtree *new)
551 {
552   struct {
553     char *name;
554     long long bits;
555   } fetch[] = {
556     {"fd/", _PS_TTY}, {"wchan", _PS_WCHAN}, {"attr/current", _PS_LABEL},
557     {"exe", _PS_COMMAND}, {"cmdline", _PS_CMDLINE|_PS_ARGS|_PS_NAME}
558   };
559   struct carveup *tb = (void *)toybuf;
560   long long *slot = tb->slot;
561   char *name, *s, *buf = tb->str, *end = 0;
562   int i, j, fd;
563   off_t len;
564 
565   // Recurse one level into /proc children, skip non-numeric entries
566   if (!new->parent)
567     return DIRTREE_RECURSE|DIRTREE_SHUTUP|(DIRTREE_SAVE*!TT.show_process);
568 
569   memset(slot, 0, sizeof(tb->slot));
570   if (!(*slot = atol(new->name))) return 0;
571   fd = dirtree_parentfd(new);
572 
573   len = 2048;
574   sprintf(buf, "%lld/stat", *slot);
575   if (!readfileat(fd, buf, buf, &len)) return 0;
576 
577   // parse oddball fields (name and state). Name can have embedded ')' so match
578   // _last_ ')' in stat (although VFS limits filenames to 255 bytes max).
579   // All remaining fields should be numeric.
580   if (!(name = strchr(buf, '('))) return 0;
581   for (s = ++name; *s; s++) if (*s == ')') end = s;
582   if (!end || end-name>255) return 0;
583 
584   // Parse numeric fields (starting at 4th field in slot[SLOT_ppid])
585   if (1>sscanf(s = end, ") %c%n", &tb->state, &i)) return 0;
586   for (j = 1; j<50; j++) if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break;
587 
588   // Now we've read the data, move status and name right after slot[] array,
589   // and convert low chars to ? for non-tty display while we're at it.
590   for (i = 0; i<end-name; i++)
591     if ((tb->str[i] = name[i]) < ' ')
592       if (!TT.tty) tb->str[i] = '?';
593   buf = tb->str+i;
594   *buf++ = 0;
595   len = sizeof(toybuf)-(buf-toybuf);
596 
597   // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch
598   // or numeric wchan, and the remaining two are always zero), and vmlck into
599   // 18 (which is "obsolete, always 0" from stat)
600   slot[SLOT_uid] = new->st.st_uid;
601   slot[SLOT_gid] = new->st.st_gid;
602 
603   // TIME and TIME+ use combined value, ksort needs 'em added.
604   slot[SLOT_utime] += slot[SLOT_stime];
605   slot[SLOT_utime2] = slot[SLOT_utime];
606 
607   // If RGROUP RUSER STAT RUID RGID SWAP happening, or -G or -U, parse "status"
608   // and save ruid, rgid, and vmlck.
609   if ((TT.bits&(_PS_RGROUP|_PS_RUSER|_PS_STAT|_PS_RUID|_PS_RGID|_PS_SWAP
610                |_PS_IO|_PS_DIO)) || TT.GG.len || TT.UU.len)
611   {
612     off_t temp = len;
613 
614     sprintf(buf, "%lld/status", *slot);
615     if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
616     s = strafter(buf, "\nUid:");
617     slot[SLOT_ruid] = s ? atol(s) : new->st.st_uid;
618     s = strafter(buf, "\nGid:");
619     slot[SLOT_rgid] = s ? atol(s) : new->st.st_gid;
620     if ((s = strafter(buf, "\nVmLck:"))) slot[SLOT_vmlck] = atoll(s);
621     if ((s = strafter(buf, "\nVmSwap:"))) slot[SLOT_swap] = atoll(s);
622   }
623 
624   // Do we need to read "io"?
625   if (TT.bits&(_PS_READ|_PS_WRITE|_PS_DREAD|_PS_DWRITE|_PS_IO|_PS_DIO)) {
626     off_t temp = len;
627 
628     sprintf(buf, "%lld/io", *slot);
629     if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
630     if ((s = strafter(buf, "rchar:"))) slot[SLOT_rchar] = atoll(s);
631     if ((s = strafter(buf, "wchar:"))) slot[SLOT_wchar] = atoll(s);
632     if ((s = strafter(buf, "read_bytes:"))) slot[SLOT_rbytes] = atoll(s);
633     if ((s = strafter(buf, "write_bytes:"))) slot[SLOT_wbytes] = atoll(s);
634     slot[SLOT_iobytes] = slot[SLOT_rchar]+slot[SLOT_wchar]+slot[SLOT_swap];
635     slot[SLOT_diobytes] = slot[SLOT_rbytes]+slot[SLOT_wbytes]+slot[SLOT_swap];
636   }
637 
638   // We now know enough to skip processes we don't care about.
639   if (TT.match_process && !TT.match_process(slot)) return 0;
640 
641   // /proc data is generated as it's read, so for maximum accuracy on slow
642   // systems (or ps | more) we re-fetch uptime as we fetch each /proc line.
643   sysinfo(&TT.si);
644   slot[SLOT_uptime] = TT.si.uptime;
645   slot[SLOT_upticks] = slot[SLOT_uptime]*TT.ticks - slot[SLOT_starttime];
646 
647   // Do we need to read "statm"?
648   if (TT.bits&(_PS_VIRT|_PS_RES|_PS_SHR)) {
649     off_t temp = len;
650 
651     sprintf(buf, "%lld/statm", *slot);
652     if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
653 
654     for (s = buf, i=0; i<3; i++)
655       if (!sscanf(s, " %lld%n", slot+SLOT_vsz+i, &j)) slot[SLOT_vsz+i] = 0;
656       else s += j;
657   }
658 
659   // Fetch string data while parentfd still available, appending to buf.
660   // (There's well over 3k of toybuf left. We could dynamically malloc, but
661   // it'd almost never get used, querying length of a proc file is awkward,
662   // fixed buffer is nommu friendly... Wait for somebody to complain. :)
663   slot[SLOT_argv0len] = 0;
664   for (j = 0; j<ARRAY_LEN(fetch); j++) {
665     tb->offset[j] = buf-(tb->str);
666     if (!(TT.bits&fetch[j].bits)) {
667       *buf++ = 0;
668       continue;
669     }
670 
671     // Determine remaining space, reserving minimum of 256 bytes/field and
672     // 260 bytes scratch space at the end (for output conversion later).
673     len = sizeof(toybuf)-(buf-toybuf)-260-256*(ARRAY_LEN(fetch)-j);
674     sprintf(buf, "%lld/%s", *slot, fetch[j].name);
675 
676     // For cmdline we readlink instead of read contents
677     if (j==3) {
678       if ((len = readlinkat(fd, buf, buf, len))>0) buf[len] = 0;
679       else *buf = 0;
680 
681     // If it's not the TTY field, data we want is in a file.
682     // Last length saved in slot[] is command line (which has embedded NULs)
683     } else if (!j) {
684       int rdev = slot[SLOT_ttynr];
685       struct stat st;
686 
687       // Call no tty "?" rather than "0:0".
688       strcpy(buf, "?");
689       if (rdev) {
690         // Can we readlink() our way to a name?
691         for (i = 0; i<3; i++) {
692           sprintf(buf, "%lld/fd/%i", *slot, i);
693           if (!fstatat(fd, buf, &st, 0) && S_ISCHR(st.st_mode)
694             && st.st_rdev == rdev && 0<(len = readlinkat(fd, buf, buf, len)))
695           {
696             buf[len] = 0;
697             break;
698           }
699         }
700 
701         // Couldn't find it, try all the tty drivers.
702         if (i == 3) {
703           FILE *fp = fopen("/proc/tty/drivers", "r");
704           int tty_major = 0, maj = major(rdev), min = minor(rdev);
705 
706           if (fp) {
707             while (fscanf(fp, "%*s %256s %d %*s %*s", buf, &tty_major) == 2) {
708               // TODO: we could parse the minor range too.
709               if (tty_major == maj) {
710                 sprintf(buf+strlen(buf), "%d", min);
711                 if (!stat(buf, &st) && S_ISCHR(st.st_mode) && st.st_rdev==rdev)
712                   break;
713               }
714               tty_major = 0;
715             }
716             fclose(fp);
717           }
718 
719           // Really couldn't find it, so just show major:minor.
720           if (!tty_major) sprintf(buf, "%d:%d", maj, min);
721         }
722 
723         s = buf;
724         if (strstart(&s, "/dev/")) memmove(buf, s, strlen(s)+1);
725       }
726 
727     // Data we want is in a file.
728     // Last length saved in slot[] is command line (which has embedded NULs)
729     } else {
730 
731       // When command has no arguments, don't space over the NUL
732       if (readfileat(fd, buf, buf, &len) && len>0) {
733         int temp = 0;
734 
735         if (buf[len-1]=='\n') buf[--len] = 0;
736 
737         // Turn NUL to space, other low ascii to ? (in non-tty mode)
738         for (i=0; i<len; i++) {
739           char c = buf[i];
740 
741           if (!c) {
742             if (!temp) temp = i;
743             c = ' ';
744           } else if (!TT.tty && c<' ') c = '?';
745           buf[i] = c;
746         }
747         len = temp; // position of _first_ NUL
748       } else *buf = len = 0;
749       // Store end of argv[0] so NAME and CMDLINE can differ.
750       slot[SLOT_argv0len] = len;
751     }
752 
753     buf += strlen(buf)+1;
754   }
755 
756   TT.kcount++;
757   if (TT.show_process) {
758     TT.show_process(tb);
759 
760     return 0;
761   }
762 
763   // If we need to sort the output, add it to the list and return.
764   s = xmalloc(buf-toybuf);
765   new->extra = (long)s;
766   memcpy(s, toybuf, buf-toybuf);
767 
768   return DIRTREE_SAVE;
769 }
770 
parse_ko(void * data,char * type,int length)771 static char *parse_ko(void *data, char *type, int length)
772 {
773   struct strawberry *field;
774   char *width, *title, *end, *s;
775   int i, j, k;
776 
777   // Get title, length of title, type, end of type, and display width
778 
779   // Chip off =name to display
780   if ((end = strchr(type, '=')) && length>(end-type)) {
781     title = end+1;
782     length -= (end-type)+1;
783   } else {
784     end = type+length;
785     title = 0;
786   }
787 
788   // Chip off :width to display
789   if ((width = strchr(type, ':')) && width<end) {
790     if (!title) length = width-type;
791   } else width = 0;
792 
793   // Allocate structure, copy title
794   field = xzalloc(sizeof(struct strawberry)+(length+1)*!!title);
795   if (title) {
796     memcpy(field->title = field->forever, title, length);
797     field->title[field->len = length] = 0;
798   }
799 
800   if (width) {
801     field->len = strtol(++width, &title, 10);
802     if (!isdigit(*width) || title != end) return title;
803     end = --width;
804   }
805 
806   // Find type
807   field->reverse = 1;
808   if (*type == '-') field->reverse = -1;
809   else if (*type != '+') type--;
810   type++;
811   for (i = 0; i<ARRAY_LEN(typos); i++) {
812     field->which = i;
813     for (j = 0; j<2; j++) {
814       if (!j) s = typos[i].name;
815       // posix requires alternate names for some fields
816       else if (-1==(k = stridx((char []){PS_NI, PS_SCH, PS_ELAPSED, PS__CPU,
817         PS_VSZ, PS_USER, 0}, i))) continue;
818       else
819         s = ((char *[]){"NICE", "SCHED", "ETIME", "PCPU", "VSIZE", "UNAME"})[k];
820 
821       if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break;
822     }
823     if (j!=2) break;
824   }
825   if (i==ARRAY_LEN(typos)) return type;
826   if (!field->title) field->title = typos[field->which].name;
827   if (!field->len) field->len = typos[field->which].width;
828   else if (typos[field->which].width<0) field->len *= -1;
829   dlist_add_nomalloc(data, (void *)field);
830 
831   return 0;
832 }
833 
get_headers(struct strawberry * fields,char * buf,int blen)834 long long get_headers(struct strawberry *fields, char *buf, int blen)
835 {
836   long long bits = 0;
837   int len = 0;
838 
839   for (; fields; fields = fields->next) {
840     len += snprintf(buf+len, blen-len, " %*s"+!bits, fields->len,
841       fields->title);
842     bits |= 1LL<<fields->which;
843   }
844 
845   return bits;
846 }
847 
848 // Parse -p -s -t -u -U -g -G
parse_rest(void * data,char * str,int len)849 static char *parse_rest(void *data, char *str, int len)
850 {
851   struct ptr_len *pl = (struct ptr_len *)data;
852   long *ll = pl->ptr;
853   char *end;
854   int num = 0;
855 
856   // Allocate next chunk of data
857   if (!(15&pl->len))
858     ll = pl->ptr = xrealloc(pl->ptr, sizeof(long)*(pl->len+16));
859 
860   // Parse numerical input
861   if (isdigit(*str)) {
862     ll[pl->len] = xstrtol(str, &end, 10);
863     if (end==(len+str)) num++;
864   }
865 
866   if (pl==&TT.pp || pl==&TT.ss) {
867     if (num && ll[pl->len]>0) {
868       pl->len++;
869 
870       return 0;
871     }
872   } else if (pl==&TT.tt) {
873     // -t pts = 12,pts/12 tty = /dev/tty2,tty2,S0
874     if (!num) {
875       if (strstart(&str, strcpy(toybuf, "/dev/"))) len -= 5;
876       if (strstart(&str, "pts/")) {
877         len -= 4;
878         num++;
879       } else if (strstart(&str, "tty")) len -= 3;
880     }
881     if (len<256 && (!(end = strchr(str, '/')) || end-str>len)) {
882       struct stat st;
883 
884       end = toybuf + sprintf(toybuf, "/dev/%s", num ? "pts/" : "tty");
885       memcpy(end, str, len);
886       end[len] = 0;
887       xstat(toybuf, &st);
888       ll[pl->len++] = st.st_rdev;
889 
890       return 0;
891     }
892   } else if (len<255) {
893     char name[256];
894 
895     if (num) {
896       pl->len++;
897 
898       return 0;
899     }
900 
901     memcpy(name, str, len);
902     name[len] = 0;
903     if (pl==&TT.gg || pl==&TT.GG) {
904       struct group *gr = getgrnam(name);
905       if (gr) {
906         ll[pl->len++] = gr->gr_gid;
907 
908         return 0;
909       }
910     } else if (pl==&TT.uu || pl==&TT.UU) {
911       struct passwd *pw = getpwnam(name);
912       if (pw) {
913         ll[pl->len++] = pw->pw_uid;
914 
915         return 0;
916       }
917     }
918   }
919 
920   // Return error
921   return str;
922 }
923 
924 // sort for -k
ksort(void * aa,void * bb)925 static int ksort(void *aa, void *bb)
926 {
927   struct strawberry *field;
928   struct carveup *ta = *(struct carveup **)aa, *tb = *(struct carveup **)bb;
929   int ret = 0, slot;
930 
931   for (field = TT.kfields; field && !ret; field = field->next) {
932     slot = typos[field->which].slot;
933 
934     // Can we do numeric sort?
935     if (!(slot&64)) {
936       if (ta->slot[slot]<tb->slot[slot]) ret = -1;
937       if (ta->slot[slot]>tb->slot[slot]) ret = 1;
938     }
939 
940     // fallback to string sort
941     if (!ret) {
942       memccpy(toybuf, string_field(ta, field), 0, 2048);
943       toybuf[2048] = 0;
944       ret = strcmp(toybuf, string_field(tb, field));
945     }
946     ret *= field->reverse;
947   }
948 
949   return ret;
950 }
951 
collate(int count,struct dirtree * dt,int (* sort)(void * a,void * b))952 static struct carveup **collate(int count, struct dirtree *dt,
953   int (*sort)(void *a, void *b))
954 {
955   struct dirtree *temp;
956   struct carveup **tbsort = xmalloc(count*sizeof(struct carveup *));
957   int i;
958 
959   // descend into child list
960   *tbsort = (void *)dt;
961   dt = dt->child;
962   free(*tbsort);
963 
964   // populate array
965   for (i = 0; i < count; i++) {
966     temp = dt->next;
967     tbsort[i] = (void *)dt->extra;
968     free(dt);
969     dt = temp;
970   }
971 
972   return tbsort;
973 }
974 
default_ko(char * s,void * fields,char * err,struct arg_list * arg)975 static void default_ko(char *s, void *fields, char *err, struct arg_list *arg)
976 {
977   struct arg_list def;
978 
979   memset(&def, 0, sizeof(struct arg_list));
980   def.arg = s;
981   comma_args(arg ? arg : &def, fields, err, parse_ko);
982 }
983 
shared_main(void)984 static void shared_main(void)
985 {
986   int i;
987 
988   TT.ticks = sysconf(_SC_CLK_TCK);
989   if (!TT.width) {
990     TT.width = (toys.which->name[1] == 's') ? 99999 : 80;
991     TT.height = 25;
992     terminal_size(&TT.width, &TT.height);
993   }
994 
995   // find controlling tty, falling back to /dev/tty if none
996   for (i = 0; !TT.tty && i<4; i++) {
997     struct stat st;
998     int fd = i;
999 
1000     if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break;
1001 
1002     if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev;
1003     if (i==3) close(fd);
1004   }
1005 }
1006 
ps_main(void)1007 void ps_main(void)
1008 {
1009   struct dirtree *dt;
1010   char *s;
1011   int i;
1012 
1013   if (toys.optflags&FLAG_w) TT.width = 99999;
1014   shared_main();
1015 
1016   // parse command line options other than -o
1017   comma_args(TT.ps.P, &TT.PP, "bad -P", parse_rest);
1018   comma_args(TT.ps.p, &TT.pp, "bad -p", parse_rest);
1019   comma_args(TT.ps.t, &TT.tt, "bad -t", parse_rest);
1020   comma_args(TT.ps.s, &TT.ss, "bad -s", parse_rest);
1021   comma_args(TT.ps.u, &TT.uu, "bad -u", parse_rest);
1022   comma_args(TT.ps.U, &TT.UU, "bad -U", parse_rest);
1023   comma_args(TT.ps.g, &TT.gg, "bad -g", parse_rest);
1024   comma_args(TT.ps.G, &TT.GG, "bad -G", parse_rest);
1025   comma_args(TT.ps.k, &TT.kfields, "bad -k", parse_ko);
1026   dlist_terminate(TT.kfields);
1027 
1028   // Parse manual field selection, or default/-f/-l, plus -Z and -O
1029   if (toys.optflags&FLAG_Z) default_ko("LABEL", &TT.fields, 0, 0);
1030   if (toys.optflags&FLAG_f) s = "USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD";
1031   else if (toys.optflags&FLAG_l)
1032     s = "F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD";
1033   else if (CFG_TOYBOX_ON_ANDROID)
1034     s = "USER,PID,PPID,VSIZE,RSS,WCHAN:10,ADDR:10=PC,S,NAME";
1035   else s = "PID,TTY,TIME,CMD";
1036   default_ko(s, &TT.fields, "bad -o", TT.ps.o);
1037   if (TT.ps.O) {
1038     if (TT.fields) TT.fields = ((struct strawberry *)TT.fields)->prev;
1039     comma_args(TT.ps.O, &TT.fields, "bad -O", parse_ko);
1040     if (TT.fields) TT.fields = ((struct strawberry *)TT.fields)->next;
1041   }
1042   dlist_terminate(TT.fields);
1043 
1044   // -f and -n change the meaning of some fields
1045   if (toys.optflags&(FLAG_f|FLAG_n)) {
1046     struct strawberry *ever;
1047 
1048     for (ever = TT.fields; ever; ever = ever->next) {
1049       if ((toys.optflags&FLAG_f) && ever->which==PS_CMD) ever->which = PS_ARGS;
1050       if ((toys.optflags&FLAG_n) && ever->which>=PS_UID
1051         && ever->which<=PS_RGROUP && (typos[ever->which].slot&64))
1052           ever->which--;
1053     }
1054   }
1055 
1056   // Calculate seen fields bit array, and if we aren't deferring printing
1057   // print headers now (for low memory/nommu systems).
1058   TT.bits = get_headers(TT.fields, toybuf, sizeof(toybuf));
1059   if (!(toys.optflags&FLAG_M)) printf("%.*s\n", TT.width, toybuf);
1060   if (!(toys.optflags&(FLAG_k|FLAG_M))) TT.show_process = (void *)show_ps;
1061   TT.match_process = ps_match_process;
1062   dt = dirtree_read("/proc", get_ps);
1063 
1064   if (toys.optflags&(FLAG_k|FLAG_M)) {
1065     struct carveup **tbsort = collate(TT.kcount, dt, ksort);
1066 
1067     if (toys.optflags&FLAG_M) {
1068       for (i = 0; i<TT.kcount; i++) {
1069         struct strawberry *field;
1070 
1071         for (field = TT.fields; field; field = field->next) {
1072           int len = strlen(string_field(tbsort[i], field));
1073 
1074           if (abs(field->len)<len) field->len = (field->len<0) ? -len : len;
1075         }
1076       }
1077 
1078       // Now that we've recalculated field widths, re-pad headers again
1079       get_headers(TT.fields, toybuf, sizeof(toybuf));
1080       printf("%.*s\n", TT.width, toybuf);
1081     }
1082 
1083     if (toys.optflags&FLAG_k)
1084       qsort(tbsort, TT.kcount, sizeof(struct carveup *), (void *)ksort);
1085     for (i = 0; i<TT.kcount; i++) {
1086       show_ps(tbsort[i]);
1087       free(tbsort[i]);
1088     }
1089     if (CFG_TOYBOX_FREE) free(tbsort);
1090   }
1091 
1092   if (CFG_TOYBOX_FREE) {
1093     free(TT.gg.ptr);
1094     free(TT.GG.ptr);
1095     free(TT.pp.ptr);
1096     free(TT.PP.ptr);
1097     free(TT.ss.ptr);
1098     free(TT.tt.ptr);
1099     free(TT.uu.ptr);
1100     free(TT.UU.ptr);
1101     llist_traverse(TT.fields, free);
1102   }
1103 }
1104 
1105 #define CLEANUP_ps
1106 #define FOR_top
1107 #include "generated/flags.h"
1108 
1109 // select which of the -o fields to sort by
setsort(int pos)1110 static void setsort(int pos)
1111 {
1112   struct strawberry *field, *going2;
1113   int i = 0;
1114 
1115   if (pos<0) pos = 0;
1116 
1117   for (field = TT.fields; field; field = field->next) {
1118     if ((TT.sortpos = i++)<pos && field->next) continue;
1119     going2 = TT.kfields;
1120     going2->which = field->which;
1121     going2->len = field->len;
1122     break;
1123   }
1124 }
1125 
1126 // If we have both, adjust slot[deltas[]] to be relative to previous
1127 // measurement rather than process start. Stomping old.data is fine
1128 // because we free it after displaying.
merge_deltas(long long * oslot,long long * nslot,int milis)1129 static int merge_deltas(long long *oslot, long long *nslot, int milis)
1130 {
1131   char deltas[] = {SLOT_utime2, SLOT_iobytes, SLOT_diobytes, SLOT_rchar,
1132                    SLOT_wchar, SLOT_rbytes, SLOT_wbytes, SLOT_swap};
1133   int i;
1134 
1135   for (i = 0; i<ARRAY_LEN(deltas); i++)
1136     oslot[deltas[i]] = nslot[deltas[i]] - oslot[deltas[i]];
1137   oslot[SLOT_upticks] = (milis*TT.ticks)/1000;
1138 
1139   return 1;
1140 }
1141 
header_line(int line,int rev)1142 static int header_line(int line, int rev)
1143 {
1144   if (!line) return 0;
1145 
1146   printf("%s%*.*s%s\r\n", rev ? "\033[7m" : "",
1147     (toys.optflags&FLAG_b) ? 0 : -TT.width, TT.width, toybuf,
1148     rev ? "\033[0m" : "");
1149 
1150   return line-1;
1151 }
1152 
1153 // Get current time in miliseconds
militime(void)1154 static long long militime(void)
1155 {
1156   struct timespec ts;
1157 
1158   clock_gettime(CLOCK_MONOTONIC, &ts);
1159 
1160   return ts.tv_sec*1000+ts.tv_nsec/1000000;
1161 }
1162 
top_common(int (* filter)(long long * oslot,long long * nslot,int milis))1163 static void top_common(
1164   int (*filter)(long long *oslot, long long *nslot, int milis))
1165 {
1166   long long timeout = 0, now, stats[16];
1167   struct proclist {
1168     struct carveup **tb;
1169     int count;
1170     long long whence;
1171   } plist[2], *plold, *plnew, old, new, mix;
1172   char scratch[16], *pos, *cpufields[] = {"user", "nice", "sys", "idle",
1173     "iow", "irq", "sirq", "host"};
1174 
1175   unsigned tock = 0;
1176   int i, lines, topoff = 0, done = 0;
1177 
1178   toys.signal = SIGWINCH;
1179   TT.bits = get_headers(TT.fields, toybuf, sizeof(toybuf));
1180   *scratch = 0;
1181   memset(plist, 0, sizeof(plist));
1182   memset(stats, 0, sizeof(stats));
1183   do {
1184     struct dirtree *dt;
1185     int recalc = 1;
1186 
1187     plold = plist+(tock++&1);
1188     plnew = plist+(tock&1);
1189     plnew->whence = militime();
1190     dt= dirtree_read("/proc", get_ps);
1191     plnew->tb = collate(plnew->count = TT.kcount, dt, ksort);
1192     TT.kcount = 0;
1193 
1194     if (readfile("/proc/stat", pos = toybuf, sizeof(toybuf))) {
1195       long long *st = stats+8*(tock&1);
1196 
1197       // user nice system idle iowait irq softirq host
1198       sscanf(pos, "cpu %lld %lld %lld %lld %lld %lld %lld %lld",
1199         st, st+1, st+2, st+3, st+4, st+5, st+6, st+7);
1200     }
1201 
1202     // First time, wait a quarter of a second to collect a little delta data.
1203     if (!plold->tb) {
1204       msleep(250);
1205       continue;
1206     }
1207 
1208     // Collate old and new into "mix", depends on /proc read in pid sort order
1209     old = *plold;
1210     new = *plnew;
1211     mix.tb = xmalloc((old.count+new.count)*sizeof(struct carveup));
1212     mix.count = 0;
1213 
1214     while (old.count || new.count) {
1215       struct carveup *otb = *old.tb, *ntb = *new.tb;
1216 
1217       // If we just have old, discard it.
1218       if (old.count && (!new.count || *otb->slot < *ntb->slot)) {
1219         old.tb++;
1220         old.count--;
1221 
1222         continue;
1223       }
1224 
1225       // If we just have new, use it verbatim
1226       if (!old.count || *otb->slot > *ntb->slot) mix.tb[mix.count] = ntb;
1227       else {
1228         // Keep or discard
1229         if (filter(otb->slot, ntb->slot, new.whence-old.whence)) {
1230           mix.tb[mix.count] = otb;
1231           mix.count++;
1232         }
1233         old.tb++;
1234         old.count--;
1235       }
1236       new.tb++;
1237       new.count--;
1238     }
1239 
1240     // We will re-fetch no data before its time. - Mork calling Orson Welles
1241     for (;;) {
1242       char was, is;
1243 
1244       if (recalc) {
1245         qsort(mix.tb, mix.count, sizeof(struct carveup *), (void *)ksort);
1246         if (!(toys.optflags&FLAG_b)) {
1247           printf("\033[H\033[J");
1248           if (toys.signal) {
1249             toys.signal = 0;
1250             terminal_probesize(&TT.width, &TT.height);
1251           }
1252         }
1253         lines = TT.height;
1254       }
1255       if (recalc && !(toys.optflags&FLAG_q)) {
1256         if (*toys.which->name == 't') {
1257           struct strawberry alluc;
1258           long long ll, up = 0;
1259           long run[6];
1260           int j;
1261 
1262           alluc.which = PS_S;
1263           memset(run, 0, sizeof(run));
1264           for (i = 0; i<mix.count; i++)
1265             run[1+stridx("RSTZ", *string_field(mix.tb[i], &alluc))]++;
1266 
1267           sprintf(toybuf,
1268             "Tasks: %d total,%4ld running,%4ld sleeping,%4ld stopped,"
1269             "%4ld zombie", mix.count, run[1], run[2], run[3], run[4]);
1270           lines = header_line(lines, 0);
1271 
1272           if (readfile("/proc/meminfo", toybuf, sizeof(toybuf))) {
1273             for (i=0; i<6; i++) {
1274               pos = strafter(toybuf, (char *[]){"MemTotal:","\nMemFree:",
1275                     "\nBuffers:","\nCached:","\nSwapTotal:","\nSwapFree:"}[i]);
1276               run[i] = pos ? atol(pos) : 0;
1277             }
1278             sprintf(toybuf,
1279              "Mem:%10ldk total,%9ldk used,%9ldk free,%9ldk buffers",
1280               run[0], run[0]-run[1], run[1], run[2]);
1281             lines = header_line(lines, 0);
1282             sprintf(toybuf,
1283               "Swap:%9ldk total,%9ldk used,%9ldk free,%9ldk cached",
1284               run[4], run[4]-run[5], run[5], run[3]);
1285             lines = header_line(lines, 0);
1286           }
1287 
1288           pos = toybuf;
1289           i = sysconf(_SC_NPROCESSORS_CONF);
1290           pos += sprintf(pos, "%d%%cpu", i*100);
1291           j = 4+(i>10);
1292 
1293           // If a processor goes idle it's powered down and its idle ticks don't
1294           // advance, so calculate idle time as potential time - used.
1295           if (mix.count) up = mix.tb[0]->slot[SLOT_upticks];
1296           if (!up) up = 1;
1297           now = up*i;
1298           ll = stats[3] = stats[11] = 0;
1299           for (i = 0; i<8; i++) ll += stats[i]-stats[i+8];
1300           stats[3] = now - llabs(ll);
1301 
1302           for (i = 0; i<8; i++) {
1303             ll = (llabs(stats[i]-stats[i+8])*1000)/up;
1304             pos += sprintf(pos, "% *lld%%%s", j, (ll+5)/10, cpufields[i]);
1305           }
1306           lines = header_line(lines, 0);
1307         } else {
1308           struct strawberry *fields;
1309           struct carveup tb;
1310 
1311           memset(&tb, 0, sizeof(struct carveup));
1312           pos = stpcpy(toybuf, "Totals:");
1313           for (fields = TT.fields; fields; fields = fields->next) {
1314             long long ll, bits = 0;
1315             int slot = typos[fields->which].slot&63;
1316 
1317             if (fields->which<PS_C || fields->which>PS_DIO) continue;
1318             ll = 1LL<<fields->which;
1319             if (bits&ll) continue;
1320             bits |= ll;
1321             for (i=0; i<mix.count; i++)
1322               tb.slot[slot] += mix.tb[i]->slot[slot];
1323             pos += snprintf(pos, sizeof(toybuf)/2-(pos-toybuf),
1324               " %s: %*s,", typos[fields->which].name,
1325               fields->len, string_field(&tb, fields));
1326           }
1327           *--pos = 0;
1328           lines = header_line(lines, 0);
1329         }
1330 
1331         get_headers(TT.fields, pos = toybuf, sizeof(toybuf));
1332         for (i = 0, is = *pos; *pos; pos++) {
1333           was = is;
1334           is = *pos;
1335           if (isspace(was) && !isspace(is) && i++==TT.sortpos) pos[-1] = '[';
1336           if (!isspace(was) && isspace(is) && i==TT.sortpos+1) *pos = ']';
1337         }
1338         *pos = 0;
1339         lines = header_line(lines, 1);
1340       }
1341       if (!recalc) printf("\033[%dH\033[J", 1+TT.height-lines);
1342       recalc = 1;
1343 
1344       for (i = 0; i<lines && i+topoff<mix.count; i++) {
1345         if (i) xputc('\n');
1346         show_ps(mix.tb[i+topoff]);
1347       }
1348 
1349       if (TT.top.n && !--TT.top.n) {
1350         done++;
1351         break;
1352       }
1353 
1354       // Get current time in miliseconds
1355       now = militime();
1356       if (timeout<=now) timeout = new.whence+TT.top.d;
1357       if (timeout<=now || timeout>now+TT.top.d) timeout = now+TT.top.d;
1358 
1359       i = scan_key_getsize(scratch, timeout-now, &TT.width, &TT.height);
1360       if (i==-1 || i==3 || toupper(i)=='Q') {
1361         done++;
1362         break;
1363       }
1364       if (i==-2) break;
1365 
1366       // Flush unknown escape sequences.
1367       if (i==27) while (0<scan_key_getsize(scratch, 0, &TT.width, &TT.height));
1368       else if (i==' ') {
1369         timeout = 0;
1370         break;
1371       } else if (toupper(i)=='R')
1372         ((struct strawberry *)TT.kfields)->reverse *= -1;
1373       else {
1374         i -= 256;
1375         if (i == KEY_LEFT) setsort(TT.sortpos-1);
1376         else if (i == KEY_RIGHT) setsort(TT.sortpos+1);
1377         // KEY_UP is 0, so at end of strchr
1378         else if (strchr((char []){KEY_DOWN,KEY_PGUP,KEY_PGDN,KEY_UP}, i)) {
1379           recalc = 0;
1380 
1381           if (i == KEY_UP) topoff--;
1382           else if (i == KEY_DOWN) topoff++;
1383           else if (i == KEY_PGDN) topoff += lines;
1384           else if (i == KEY_PGUP) topoff -= lines;
1385           if (topoff<0) topoff = 0;
1386           if (topoff>mix.count) topoff = mix.count;
1387         }
1388       }
1389       continue;
1390     }
1391 
1392     free(mix.tb);
1393     for (i=0; i<plold->count; i++) free(plold->tb[i]);
1394     free(plold->tb);
1395   } while (!done);
1396 
1397   if (!(toys.optflags&FLAG_b)) tty_reset();
1398 }
1399 
top_setup(char * defo,char * defk)1400 static void top_setup(char *defo, char *defk)
1401 {
1402   int len;
1403 
1404   TT.time = militime();
1405   TT.top.d *= 1000;
1406   if (toys.optflags&FLAG_b) TT.width = TT.height = 99999;
1407   else {
1408     xset_terminal(0, 1, 0);
1409     sigatexit(tty_sigreset);
1410     xsignal(SIGWINCH, generic_signal);
1411     printf("\033[?25l\033[0m");
1412   }
1413   shared_main();
1414 
1415   comma_args(TT.top.u, &TT.uu, "bad -u", parse_rest);
1416   comma_args(TT.top.p, &TT.pp, "bad -p", parse_rest);
1417   TT.match_process = shared_match_process;
1418 
1419   default_ko(defo, &TT.fields, "bad -o", TT.top.o);
1420   dlist_terminate(TT.fields);
1421   len = strlen(toybuf);
1422   if (toybuf[len-1]!=' ' && len<sizeof(toybuf)-1) strcpy(toybuf+len, " ");
1423 
1424   // First (dummy) sort field is overwritten by setsort()
1425   default_ko("-S", &TT.kfields, 0, 0);
1426   default_ko(defk, &TT.kfields, "bad -k", TT.top.k);
1427   dlist_terminate(TT.kfields);
1428   setsort(TT.top.s-1);
1429 }
1430 
top_main(void)1431 void top_main(void)
1432 {
1433   // usage: [-h HEADER] -o OUTPUT -k SORT
1434 
1435   top_setup(
1436     "PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,ARGS",
1437     "-%CPU,-ETIME,-PID");
1438   top_common(merge_deltas);
1439 }
1440 
1441 #define CLEANUP_top
1442 #define FOR_iotop
1443 #include "generated/flags.h"
1444 
iotop_filter(long long * oslot,long long * nslot,int milis)1445 static int iotop_filter(long long *oslot, long long *nslot, int milis)
1446 {
1447   if (!(toys.optflags&FLAG_a)) merge_deltas(oslot, nslot, milis);
1448   else oslot[SLOT_upticks] = ((militime()-TT.time)*TT.ticks)/1000;
1449 
1450   return !(toys.optflags&FLAG_o)||oslot[SLOT_iobytes+!(toys.optflags&FLAG_A)];
1451 }
1452 
iotop_main(void)1453 void iotop_main(void)
1454 {
1455   char *s1 = 0, *s2 = 0, *d = "D"+!!(toys.optflags&FLAG_A);
1456 
1457   if (toys.optflags&FLAG_K) TT.forcek++;
1458 
1459   top_setup(s1 = xmprintf("PID,PR,USER,%sREAD,%sWRITE,SWAP,%sIO,COMM",d,d,d),
1460     s2 = xmprintf("-%sIO,-ETIME,-PID",d));
1461   free(s1);
1462   free(s2);
1463   top_common(iotop_filter);
1464 }
1465 
1466 // pkill's plumbing wraps pgrep's and thus mostly takes place in pgrep's flag
1467 // context, so force pgrep's flags on even when building pkill standalone.
1468 // (All the pgrep/pkill functions drop out when building ps standalone.)
1469 #define FORCE_FLAGS
1470 #define CLEANUP_iotop
1471 #define FOR_pgrep
1472 #include "generated/flags.h"
1473 
1474 struct regex_list {
1475   struct regex_list *next;
1476   regex_t reg;
1477 };
1478 
do_pgk(struct carveup * tb)1479 static void do_pgk(struct carveup *tb)
1480 {
1481   if (TT.pgrep.signal) {
1482     if (kill(*tb->slot, TT.pgrep.signal)) {
1483       char *s = num_to_sig(TT.pgrep.signal);
1484 
1485       if (!s) sprintf(s = toybuf, "%d", TT.pgrep.signal);
1486       perror_msg("%s->%lld", s, *tb->slot);
1487     }
1488   }
1489   if (!(toys.optflags&FLAG_c) && (!TT.pgrep.signal || TT.tty)) {
1490     printf("%lld", *tb->slot);
1491     if (toys.optflags&FLAG_l)
1492       printf(" %s", tb->str+tb->offset[4]*!!(toys.optflags&FLAG_f));
1493 
1494     printf("%s", TT.pgrep.d ? TT.pgrep.d : "\n");
1495   }
1496 }
1497 
match_pgrep(struct carveup * tb)1498 static void match_pgrep(struct carveup *tb)
1499 {
1500   regmatch_t match;
1501   struct regex_list *reg;
1502   char *name = tb->str+tb->offset[4]*!!(toys.optflags&FLAG_f);;
1503 
1504   // Never match ourselves.
1505   if (TT.pgrep.self == *tb->slot) return;
1506 
1507   if (TT.pgrep.regexes) {
1508     for (reg = TT.pgrep.regexes; reg; reg = reg->next) {
1509       if (regexec(&reg->reg, name, 1, &match, 0)) continue;
1510       if (toys.optflags&FLAG_x)
1511         if (match.rm_so || match.rm_eo!=strlen(name)) continue;
1512       break;
1513     }
1514     if ((toys.optflags&FLAG_v) ? !!reg : !reg) return;
1515   }
1516 
1517   // Repurpose a field for -c count
1518   TT.sortpos++;
1519   if (toys.optflags&(FLAG_n|FLAG_o)) {
1520     long long ll = tb->slot[SLOT_starttime];
1521 
1522     if (toys.optflags&FLAG_o) ll *= -1;
1523     if (TT.time && TT.time>ll) return;
1524     TT.time = ll;
1525     free(TT.pgrep.snapshot);
1526     TT.pgrep.snapshot = xmemdup(toybuf, (name+strlen(name)+1)-toybuf);
1527   } else do_pgk(tb);
1528 }
1529 
pgrep_match_process(long long * slot)1530 static int pgrep_match_process(long long *slot)
1531 {
1532   int match = shared_match_process(slot);
1533 
1534   return (toys.optflags&FLAG_v) ? !match : match;
1535 }
1536 
pgrep_main(void)1537 void pgrep_main(void)
1538 {
1539   char **arg;
1540   struct regex_list *reg;
1541 
1542   TT.pgrep.self = getpid();
1543 
1544   // No signal names start with "L", so no need for "L: " parsing.
1545   if (TT.pgrep.L && 1>(TT.pgrep.signal = sig_to_num(TT.pgrep.L)))
1546     error_exit("bad -L '%s'", TT.pgrep.L);
1547 
1548   comma_args(TT.pgrep.G, &TT.GG, "bad -G", parse_rest);
1549   comma_args(TT.pgrep.g, &TT.gg, "bad -g", parse_rest);
1550   comma_args(TT.pgrep.P, &TT.PP, "bad -P", parse_rest);
1551   comma_args(TT.pgrep.s, &TT.ss, "bad -s", parse_rest);
1552   comma_args(TT.pgrep.t, &TT.tt, "bad -t", parse_rest);
1553   comma_args(TT.pgrep.U, &TT.UU, "bad -U", parse_rest);
1554   comma_args(TT.pgrep.u, &TT.uu, "bad -u", parse_rest);
1555 
1556   if ((toys.optflags&(FLAG_x|FLAG_f)) ||
1557       !(toys.optflags&(FLAG_G|FLAG_g|FLAG_P|FLAG_s|FLAG_t|FLAG_U|FLAG_u)))
1558     if (!toys.optc) help_exit("No PATTERN");
1559 
1560   if (toys.optflags&FLAG_f) TT.bits |= _PS_CMDLINE;
1561   for (arg = toys.optargs; *arg; arg++) {
1562     reg = xmalloc(sizeof(struct regex_list));
1563     xregcomp(&reg->reg, *arg, REG_EXTENDED);
1564     reg->next = TT.pgrep.regexes;
1565     TT.pgrep.regexes = reg;
1566   }
1567   TT.match_process = pgrep_match_process;
1568   TT.show_process = (void *)match_pgrep;
1569 
1570   dirtree_read("/proc", get_ps);
1571   if (toys.optflags&FLAG_c) printf("%d\n", TT.sortpos);
1572   if (TT.pgrep.snapshot) {
1573     do_pgk(TT.pgrep.snapshot);
1574     if (CFG_TOYBOX_FREE) free(TT.pgrep.snapshot);
1575   }
1576   if (TT.pgrep.d) xputc('\n');
1577 }
1578 
1579 #define CLEANUP_pgrep
1580 #define FOR_pkill
1581 #include "generated/flags.h"
1582 
pkill_main(void)1583 void pkill_main(void)
1584 {
1585   if (!TT.pgrep.L) TT.pgrep.signal = SIGTERM;
1586   if (toys.optflags & FLAG_V) TT.tty = 1;
1587   pgrep_main();
1588 }
1589