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