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