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, 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, *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