1 /* ps.c - show process list
3 * Copyright 2015 Rob Landley <rob@landley.net>
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()
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.
18 * Posix defines -o ADDR as "The address of the process" but the process
19 * start address is a constant on any elf system with mmu. The procps ADDR
20 * field always prints "-" with an alignment of 1, which is why it has 11
21 * characters left for "cmd" in in 80 column "ps -l" mode. On x86-64 you
22 * need 12 chars, leaving nothing for cmd: I.E. posix 2008 ps -l mode can't
23 * be sanely implemented on 64 bit Linux systems. In procps there's ps -y
24 * which changes -l by removing the "F" column and swapping RSS for ADDR,
25 * leaving 9 chars for cmd, so we're using that as our -l output.
27 * Added a bunch of new -o fields posix doesn't mention, and we don't
28 * label "ps -o command,args,comm" as "COMMAND COMMAND COMMAND". We don't
29 * output argv[0] unmodified for -o comm or -o args (but procps violates
30 * posix for -o comm anyway, it's stat[2] not argv[0]).
32 * Note: iotop is STAYROOT so it can read other process's /proc/$PID/io
33 * files (why they're not globally readable when the rest of proc
34 * data is...?) and get a global I/O picture. Normal top is NOT,
35 * even though you can -o AIO there, to give sysadmins the option
36 * to reduce security exposure.)
38 * TODO: ps aux (att & bsd style "ps -ax" vs "ps ax" behavior difference)
39 * TODO: switch -fl to -y
40 * TODO: thread support /proc/$d/task/%d/stat (and -o stat has "l")
41 * TODO: iotop: Window size change: respond immediately. Why not padding
42 * at right edge? (Not adjusting to screen size at all? Header wraps?)
44 USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflno*O*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
45 // stayroot because iotop needs root to read other process' proc/$$/io
46 USE_TOP(NEWTOY(top, ">0m" "h:k*o*p*u*s#<1=9d#=3<1n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
47 USE_IOTOP(NEWTOY(iotop, ">0AaKO" "h:k*o*p*u*s#<1=7d#=3<1n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE))
48 USE_PGREP(NEWTOY(pgrep, "?cld:u*U*t*s*P*g*G*fnovxL:", TOYFLAG_USR|TOYFLAG_BIN))
49 USE_PKILL(NEWTOY(pkill, "Vu*U*t*s*P*g*G*fnovxl:", TOYFLAG_USR|TOYFLAG_BIN))
55 usage: ps [-AadeflnwZ] [-gG GROUP,] [-k FIELD,] [-o FIELD,] [-p PID,] [-t TTY,] [-uU USER,]
59 Which processes to show (selections may be comma separated lists):
62 -a Processes with terminals that aren't session leaders
63 -d All processes that aren't session leaders
65 -g Belonging to GROUPs
66 -G Belonging to real GROUPs (before sgid)
68 -P Parent PIDs (--ppid)
70 -t Attached to selected TTYs
72 -U Owned by real USERs (before suid)
76 -k Sort FIELDs in +increasing or -decreasting order (--sort)
77 -n Show numeric USER and GROUP
78 -w Wide output (don't truncate at terminal width)
80 Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD)
82 -f Full listing (-o USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD)
83 -l Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD)
84 -o Output FIELDs instead of defaults, each with optional :size and =title
85 -O Add FIELDS to defaults
90 ADDR Instruction pointer ARGS Command line (argv[] -path)
91 CMD COMM without -f, ARGS with -f CMDLINE Command line (argv[])
92 COMM Original command name COMMAND Original command path
93 CPU Which processor running on ETIME Elapsed time since PID start
94 F Flags (1=FORKNOEXEC 4=SUPERPRIV) GID Group id
95 GROUP Group name LABEL Security label
96 MAJFL Major page faults MINFL Minor page faults
97 NAME Command name (argv[0]) NI Niceness (lower is faster)
98 PCPU Percentage of CPU time used PGID Process Group ID
99 PID Process ID PPID Parent Process ID
100 PRI Priority (higher is faster) PSR Processor last executed on
101 RGID Real (before sgid) group ID RGROUP Real (before sgid) group name
102 RSS Resident Set Size (pages in use) RTPRIO Realtime priority
103 RUID Real (before suid) user ID RUSER Real (before suid) user name
105 R (running) S (sleeping) D (device I/O) T (stopped) t (traced)
106 Z (zombie) X (deader) x (dead) K (wakekill) W (waking)
107 SCHED Scheduling policy (0=other, 1=fifo, 2=rr, 3=batch, 4=iso, 5=idle)
108 STAT Process state (S) plus:
109 < high priority N low priority L locked memory
110 s session leader + foreground l multithreaded
111 STIME Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss)
112 SZ Memory Size (4k pages needed to completely swap out process)
113 TIME CPU time consumed TTY Controlling terminal
114 UID User id USER User name
115 VSZ Virtual memory size (1k units) %VSZ VSZ as % of physical memory
116 WCHAN Waiting in kernel for
122 usage: top [-m] [ -d seconds ] [ -n iterations ]
124 Show process activity in real time.
126 -h Header (default Tasks: %PID, %S=R running, %S=S sleeping, %S
127 -k Fallback sort FIELDS (default -S,-%CPU,-ETIME,-PID)
128 -o Show FIELDS (def PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,CMDLINE)
129 -s Sort by field number (1-X, default 9)
132 Tasks: %PID, %S=R= running, %S=S= sleeping, %S=T= stopped, %S=Z= zombie\n
133 Mem: %10KMEM total, %10KMUSED used, %10KMFREE free, %10KBUF buffers
134 Swap: %9KSWAP total, %10KSWUSED used, %10KSWFREE free, %10K
139 # Requires CONFIG_IRQ_TIME_ACCOUNTING in the kernel for /proc/$$/io
146 Rank processes by I/O.
148 -A All I/O, not just disk
149 -a Accumulated I/O (not percentage)
151 -k Fallback sort FIELDS (default -[D]IO,-ETIME,-PID)
152 -O Only show processes doing I/O
153 -o Show FIELDS (default PID,PR,USER,[D]READ,[D]WRITE,SWAP,[D]IO,COMM)
154 -s Sort by field number (0-X, default 6)
160 usage: COMMON [-bq] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,] [-s SORT]
162 -b Batch mode (no tty)
163 -d Delay SECONDS between each cycle (default 3)
164 -n Exit after NUMBER iterations
167 -q Quiet (no header lines)
169 Cursor LEFT/RIGHT to change sort, UP/DOWN move list, space to force
170 update, R to reverse sort, Q to exit.
175 depends on PGKILL_COMMON
177 usage: pgrep [-cL] [-d DELIM] [-L SIGNAL] [PATTERN]
179 Search for process(es). PATTERN is an extended regular expression checked
180 against command names.
182 -c Show only count of matches
183 -d Use DELIM instead of newline
184 -L Send SIGNAL instead of printing name
191 usage: pgrep [-fnovx] [-G GID,] [-g PGRP,] [-P PPID,] [-s SID,] [-t TERM,] [-U UID,] [-u EUID,]
193 -f Check full command line for PATTERN
194 -G Match real Group ID(s)
195 -g Match Process Group(s) (0 is current user)
198 -P Match Parent Process ID(s)
199 -s Match Session ID(s) (0 for current)
201 -U Match real User ID(s)
202 -u Match effective User ID(s)
204 -x Match whole command (not substring)
210 usage: pkill [-l SIGNAL] [PATTERN]
262 struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU;
263 unsigned width, height;
265 void *fields, *kfields;
266 long long ticks, bits;
267 int kcount, forcek, sortpos;
268 int (*match_process)(long long *slot);
269 void (*show_process)(void *tb);
273 struct strawberry *next, *prev;
274 short which, len, reverse;
279 /* The slot[] array is mostly populated from /proc/$PID/stat (kernel proc.txt
280 * table 1-4) but we shift and repurpose fields, with the result being: */
283 SLOT_pid, /*process id*/ SLOT_ppid, // parent process id
284 SLOT_pgrp, /*process group*/ SLOT_sid, // session id
285 SLOT_ttynr, /*tty the process uses*/ SLOT_ttypgrp, // pgrp of the tty
286 SLOT_flags, /*task flags*/ SLOT_minflt, // minor faults
287 SLOT_cminflt, /*minor faults+child*/ SLOT_majflt, // major faults
288 SLOT_cmajflt, /*major faults+child*/ SLOT_utime, // user+kernel jiffies
289 SLOT_stime, /*kernel mode jiffies*/ SLOT_cutime, // utime+child
290 SLOT_cstime, /*stime+child*/ SLOT_priority, // priority level
291 SLOT_nice, /*nice level*/ SLOT_numthreads,// thread count
292 SLOT_vmlck, /*locked memory*/ SLOT_starttime, // jiffies after boot
293 SLOT_vsize, /*virtual memory size*/ SLOT_rss, // resident set size
294 SLOT_rsslim, /*limit in bytes on rss*/ SLOT_startcode, // code segment addr
295 SLOT_endcode, /*code segment address*/ SLOT_startstack,// stack address
296 SLOT_esp, /*task stack pointer*/ SLOT_eip, // instruction pointer
297 SLOT_iobytes, /*All I/O bytes*/ SLOT_diobytes, // disk I/O bytes
298 SLOT_utime2, /*relative utime (top)*/ SLOT_uid, // user id
299 SLOT_ruid, /*real user id*/ SLOT_gid, // group id
300 SLOT_rgid, /*real group id*/ SLOT_exitsig, // sent to parent
301 SLOT_taskcpu, /*CPU running on*/ SLOT_rtprio, // realtime priority
302 SLOT_policy, /*man sched_setscheduler*/SLOT_blkioticks,// IO wait time
303 SLOT_gtime, /*guest jiffies of task*/ SLOT_cgtime, // gtime+child
304 SLOT_startbss, /*data/bss address*/ SLOT_endbss, // end addr data+bss
305 SLOT_upticks, /*46-19 (divisor for %)*/ SLOT_argv0len, // argv[0] length
306 SLOT_uptime, /*si.uptime @read time*/ SLOT_vsz, // Virtual mem Size
307 SLOT_rss2, /*Resident Set Size*/ SLOT_shr, // Shared memory
308 SLOT_rchar, /*All bytes read*/ SLOT_wchar, // All bytes written
309 SLOT_rbytes, /*Disk bytes read*/ SLOT_wbytes, // Disk bytes written
310 SLOT_swap, /*Swap pages used*/
313 // Data layout in toybuf
315 long long slot[55]; // data from /proc
316 unsigned short offset[5]; // offset of fields in str[] (skip name, always 0)
318 char str[]; // name, tty, command, wchan, attr, cmdline
321 // TODO: Android uses -30 for LABEL, but ideally it would auto-size.
322 // 64|slot means compare as string when sorting
325 signed char width, slot;
326 } static const typos[] = TAGGED_ARRAY(PS,
328 {"PID", 5, SLOT_pid}, {"PPID", 5, SLOT_ppid}, {"PRI", 3, SLOT_priority},
329 {"NI", 3, SLOT_nice}, {"ADDR", 4+sizeof(long), SLOT_eip},
330 {"SZ", 5, SLOT_vsize}, {"RSS", 5, SLOT_rss}, {"PGID", 5, SLOT_pgrp},
331 {"VSZ", 6, SLOT_vsize}, {"MAJFL", 6, SLOT_majflt}, {"MINFL", 6, SLOT_minflt},
332 {"PR", 2, SLOT_priority}, {"PSR", 3, SLOT_taskcpu},
333 {"RTPRIO", 6, SLOT_rtprio}, {"SCH", 3, SLOT_policy}, {"CPU", 3, SLOT_taskcpu},
336 {"COMM", -15, -1}, {"TTY", -8, -2}, {"WCHAN", -6, -3}, {"LABEL", -30, -4},
337 {"COMMAND", -27, -5}, {"CMDLINE", -27, -6}, {"ARGS", -27, -6},
338 {"NAME", -15, -6}, {"CMD", -27, -1},
341 {"UID", 5, SLOT_uid}, {"USER", -8, 64|SLOT_uid}, {"RUID", 4, SLOT_ruid},
342 {"RUSER", -8, 64|SLOT_ruid}, {"GID", 8, SLOT_gid}, {"GROUP", -8, 64|SLOT_gid},
343 {"RGID", 4, SLOT_rgid}, {"RGROUP", -8, 64|SLOT_rgid},
346 {"TIME", 8, SLOT_utime}, {"ELAPSED", 11, SLOT_starttime},
347 {"TIME+", 9, SLOT_utime},
349 // Percentage displays
350 {"C", 1, SLOT_utime2}, {"%VSZ", 5, SLOT_vsize}, {"%MEM", 5, SLOT_rss},
351 {"%CPU", 4, SLOT_utime2},
354 {"VIRT", 4, SLOT_vsz}, {"RES", 4, SLOT_rss2},
355 {"SHR", 4, SLOT_shr}, {"READ", 6, SLOT_rchar}, {"WRITE", 6, SLOT_wchar},
356 {"IO", 6, SLOT_iobytes}, {"DREAD", 6, SLOT_rbytes},
357 {"DWRITE", 6, SLOT_wbytes}, {"SWAP", 6, SLOT_swap}, {"DIO", 6, SLOT_diobytes},
360 {"STIME", 5, SLOT_starttime}, {"F", 1, 64|SLOT_flags}, {"S", -1, 64},
366 // Return 0 to discard, nonzero to keep
367 static int shared_match_process(long long *slot)
369 struct ptr_len match[] = {
370 {&TT.gg, SLOT_gid}, {&TT.GG, SLOT_rgid}, {&TT.pp, SLOT_pid},
371 {&TT.PP, SLOT_ppid}, {&TT.ss, SLOT_sid}, {&TT.tt, SLOT_ttynr},
372 {&TT.uu, SLOT_uid}, {&TT.UU, SLOT_ruid}
377 // Do we have -g -G -p -P -s -t -u -U options selecting processes?
378 for (i = 0; i < ARRAY_LEN(match); i++) {
379 struct ptr_len *mm = match[i].ptr;
382 for (j = 0; j<mm->len; j++) if (ll[j] == slot[match[i].len]) return 1;
390 // Return 0 to discard, nonzero to keep
391 static int ps_match_process(long long *slot)
393 int i = shared_match_process(slot);
396 // If we had selections and didn't match them, don't display
399 // Filter implicit categories for other display types
400 if ((toys.optflags&(FLAG_a|FLAG_d)) && slot[SLOT_sid]==*slot) return 0;
401 if ((toys.optflags&FLAG_a) && !slot[SLOT_ttynr]) return 0;
402 if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e))
403 && TT.tty!=slot[SLOT_ttynr]) return 0;
408 // Convert field to string representation
409 static char *string_field(struct carveup *tb, struct strawberry *field)
411 char *buf = toybuf+sizeof(toybuf)-260, *out = buf, *s;
412 int which = field->which, sl = typos[which].slot;
413 long long *slot = tb->slot, ll = (sl >= 0) ? slot[sl&63] : 0;
415 // numbers, mostly from /proc/$PID/stat
416 if (which <= PS_CPU) {
419 if (which==PS_PRI) ll = 39-ll;
420 if (which==PS_ADDR) fmt = "%llx";
421 else if (which==PS_SZ) ll >>= 12;
422 else if (which==PS_RSS) ll <<= 2;
423 else if (which==PS_VSZ) ll >>= 10;
424 else if (which==PS_PR && ll<-9) fmt="RT";
425 else if (which==PS_RTPRIO && ll == 0) fmt="-";
426 sprintf(out, fmt, ll);
430 if (slot[SLOT_argv0len])
431 tb->str[tb->offset[4]+slot[SLOT_argv0len]] = (which==PS_NAME) ? 0 : ' ';
434 if (--sl) out += tb->offset[--sl];
436 for (s = out; *s && *s != ' '; s++) if (*s == '/') out = s+1;
437 if (which>=PS_COMMAND && !*out) sprintf(out = buf, "[%s]", tb->str);
440 } else if (which <= PS_RGROUP) {
441 sprintf(out, "%lld", ll);
443 if (which > PS_RUSER) {
444 struct group *gr = getgrgid(ll);
446 if (gr) out = gr->gr_name;
448 struct passwd *pw = getpwuid(ll);
450 if (pw) out = pw->pw_name;
455 } else if (which <= PS_TIME_) {
456 int unit = 60, pad = 2, j = TT.ticks;
459 if (which!=PS_TIME_) unit *= 60*24;
461 // top adjusts slot[SLOT_upticks], we want original meaning.
462 if (which==PS_ELAPSED) ll = (slot[SLOT_uptime]*j)-slot[SLOT_starttime];
465 // Output days-hours:mins:secs, skipping non-required fields with zero
466 // TIME has 3 required fields, ETIME has 2. (Posix!) TIME+ is from top
467 for (s = 0, j = 2*(which==PS_TIME_); j<4; j++) {
468 if (!s && (seconds>unit || j == 1+(which!=PS_TIME))) s = out;
470 s += sprintf(s, j ? "%0*ld": "%*ld", pad, (long)(seconds/unit));
472 if ((*s = "-::"[j])) s++;
477 if (which==PS_TIME_ && s-out<8)
478 sprintf(s, ".%02lld", (100*(ll%TT.ticks))/TT.ticks);
480 // Percentage displays
481 } else if (which <= PS__CPU) {
482 ll = slot[sl&63]*1000;
483 if (which==PS__VSZ || which==PS__MEM)
484 ll /= TT.si.totalram/((which==PS__VSZ) ? 1024 : 4096);
485 else if (slot[SLOT_upticks]) ll /= slot[SLOT_upticks];
487 if (which==PS_C) sl += 5;
488 sprintf(out, "%d", sl/10);
489 if (which!=PS_C && sl<1000) sprintf(out+strlen(out), ".%d", sl%10);
492 } else if (which <= PS_DIO) {
493 ll = slot[typos[which].slot];
494 if (which <= PS_SHR) ll *= sysconf(_SC_PAGESIZE);
495 if (TT.forcek) sprintf(out, "%lldk", ll/1024);
496 else human_readable(out, ll, 0);
498 // Posix doesn't specify what flags should say. Man page says
499 // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h
500 } else if (which==PS_F) sprintf(out, "%llo", (slot[SLOT_flags]>>6)&5);
501 else if (which==PS_S || which==PS_STAT) {
504 if (which==PS_STAT) {
505 // TODO l = multithreaded
506 if (slot[SLOT_nice]<0) *s++ = '<';
507 else if (slot[SLOT_nice]>0) *s++ = 'N';
508 if (slot[SLOT_sid]==*slot) *s++ = 's';
509 if (slot[SLOT_vmlck]) *s++ = 'L';
510 if (slot[SLOT_ttypgrp]==*slot) *s++ = '+';
513 } else if (which==PS_STIME) {
514 time_t t = time(0)-slot[SLOT_uptime]+slot[SLOT_starttime]/TT.ticks;
516 // Padding behavior's a bit odd: default field size is just hh:mm.
517 // Increasing stime:size reveals more data at left until full,
518 // so move start address so yyyy-mm-dd hh:mm revealed on left at :16,
519 // then add :ss on right for :19.
520 strftime(out, 260, "%F %T", localtime(&t));
521 out = out+strlen(out)-3-abs(field->len);
522 if (out<buf) out = buf;
524 } else if (CFG_TOYBOX_DEBUG) error_exit("bad which %d", which);
529 // Display process data that get_ps() read from /proc, formatting with TT.fields
530 static void show_ps(struct carveup *tb)
532 struct strawberry *field;
533 int pad, len, width = TT.width;
535 // Loop through fields to display
536 for (field = TT.fields; field; field = field->next) {
537 char *out = string_field(tb, field);
539 // Output the field, appropriately padded
540 if (field != TT.fields) {
546 if (field->next || field->len>0)
547 len = abs(pad = width<abs(field->len) ? width : field->len);
549 if (TT.tty) width -= draw_trim(out, pad, len);
550 else width -= printf("%*.*s", pad, len, out);
555 // dirtree callback: read data about process to display, store, or discard it.
556 // Fills toybuf with struct carveup and either DIRTREE_SAVEs a copy to ->extra
557 // (in -k mode) or calls show_ps on toybuf (no malloc/copy/free there).
558 static int get_ps(struct dirtree *new)
564 {"fd/", _PS_TTY}, {"wchan", _PS_WCHAN}, {"attr/current", _PS_LABEL},
565 {"exe", _PS_COMMAND}, {"cmdline", _PS_CMDLINE|_PS_ARGS|_PS_NAME}
567 struct carveup *tb = (void *)toybuf;
568 long long *slot = tb->slot;
569 char *name, *s, *buf = tb->str, *end = 0;
573 // Recurse one level into /proc children, skip non-numeric entries
575 return DIRTREE_RECURSE|DIRTREE_SHUTUP|(DIRTREE_SAVE*!TT.show_process);
577 memset(slot, 0, sizeof(tb->slot));
578 if (!(*slot = atol(new->name))) return 0;
579 fd = dirtree_parentfd(new);
582 sprintf(buf, "%lld/stat", *slot);
583 if (!readfileat(fd, buf, buf, &len)) return 0;
585 // parse oddball fields (name and state). Name can have embedded ')' so match
586 // _last_ ')' in stat (although VFS limits filenames to 255 bytes max).
587 // All remaining fields should be numeric.
588 if (!(name = strchr(buf, '('))) return 0;
589 for (s = ++name; *s; s++) if (*s == ')') end = s;
590 if (!end || end-name>255) return 0;
592 // Parse numeric fields (starting at 4th field in slot[SLOT_ppid])
593 if (1>sscanf(s = end, ") %c%n", &tb->state, &i)) return 0;
594 for (j = 1; j<50; j++) if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break;
596 // Now we've read the data, move status and name right after slot[] array,
597 // and convert low chars to ? for non-tty display while we're at it.
598 for (i = 0; i<end-name; i++)
599 if ((tb->str[i] = name[i]) < ' ')
600 if (!TT.tty) tb->str[i] = '?';
603 len = sizeof(toybuf)-(buf-toybuf);
605 // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch
606 // or numeric wchan, and the remaining two are always zero), and vmlck into
607 // 18 (which is "obsolete, always 0" from stat)
608 slot[SLOT_uid] = new->st.st_uid;
609 slot[SLOT_gid] = new->st.st_gid;
611 // TIME and TIME+ use combined value, ksort needs 'em added.
612 slot[SLOT_utime] += slot[SLOT_stime];
613 slot[SLOT_utime2] = slot[SLOT_utime];
615 // If RGROUP RUSER STAT RUID RGID SWAP happening, or -G or -U, parse "status"
616 // and save ruid, rgid, and vmlck.
617 if ((TT.bits&(_PS_RGROUP|_PS_RUSER|_PS_STAT|_PS_RUID|_PS_RGID|_PS_SWAP
618 |_PS_IO|_PS_DIO)) || TT.GG.len || TT.UU.len)
622 sprintf(buf, "%lld/status", *slot);
623 if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
624 s = strafter(buf, "\nUid:");
625 slot[SLOT_ruid] = s ? atol(s) : new->st.st_uid;
626 s = strafter(buf, "\nGid:");
627 slot[SLOT_rgid] = s ? atol(s) : new->st.st_gid;
628 if ((s = strafter(buf, "\nVmLck:"))) slot[SLOT_vmlck] = atoll(s);
629 if ((s = strafter(buf, "\nVmSwap:"))) slot[SLOT_swap] = atoll(s);
632 // Do we need to read "io"?
633 if (TT.bits&(_PS_READ|_PS_WRITE|_PS_DREAD|_PS_DWRITE|_PS_IO|_PS_DIO)) {
636 sprintf(buf, "%lld/io", *slot);
637 if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
638 if ((s = strafter(buf, "rchar:"))) slot[SLOT_rchar] = atoll(s);
639 if ((s = strafter(buf, "wchar:"))) slot[SLOT_wchar] = atoll(s);
640 if ((s = strafter(buf, "read_bytes:"))) slot[SLOT_rbytes] = atoll(s);
641 if ((s = strafter(buf, "write_bytes:"))) slot[SLOT_wbytes] = atoll(s);
642 slot[SLOT_iobytes] = slot[SLOT_rchar]+slot[SLOT_wchar]+slot[SLOT_swap];
643 slot[SLOT_diobytes] = slot[SLOT_rbytes]+slot[SLOT_wbytes]+slot[SLOT_swap];
646 // We now know enough to skip processes we don't care about.
647 if (TT.match_process && !TT.match_process(slot)) return 0;
649 // /proc data is generated as it's read, so for maximum accuracy on slow
650 // systems (or ps | more) we re-fetch uptime as we fetch each /proc line.
652 slot[SLOT_uptime] = TT.si.uptime;
653 slot[SLOT_upticks] = slot[SLOT_uptime]*TT.ticks - slot[SLOT_rss];
655 // Do we need to read "statm"?
656 if (TT.bits&(_PS_VIRT|_PS_RES|_PS_SHR)) {
659 sprintf(buf, "%lld/statm", *slot);
660 if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
662 for (s = buf, i=0; i<3; i++)
663 if (!sscanf(s, " %lld%n", slot+SLOT_vsz+i, &j)) slot[SLOT_vsz+i] = 0;
667 // Fetch string data while parentfd still available, appending to buf.
668 // (There's well over 3k of toybuf left. We could dynamically malloc, but
669 // it'd almost never get used, querying length of a proc file is awkward,
670 // fixed buffer is nommu friendly... Wait for somebody to complain. :)
671 slot[SLOT_argv0len] = 0;
672 for (j = 0; j<ARRAY_LEN(fetch); j++) {
673 tb->offset[j] = buf-(tb->str);
674 if (!(TT.bits&fetch[j].bits)) {
679 // Determine remaining space, reserving minimum of 256 bytes/field and
680 // 260 bytes scratch space at the end (for output conversion later).
681 len = sizeof(toybuf)-(buf-toybuf)-260-256*(ARRAY_LEN(fetch)-j);
682 sprintf(buf, "%lld/%s", *slot, fetch[j].name);
684 // For cmdline we readlink instead of read contents
686 if ((len = readlinkat(fd, buf, buf, len))>0) buf[len] = 0;
689 // If it's not the TTY field, data we want is in a file.
690 // Last length saved in slot[] is command line (which has embedded NULs)
692 int rdev = slot[SLOT_ttynr];
695 // Call no tty "?" rather than "0:0".
698 // Can we readlink() our way to a name?
699 for (i = 0; i<3; i++) {
700 sprintf(buf, "%lld/fd/%i", *slot, i);
701 if (!fstatat(fd, buf, &st, 0) && S_ISCHR(st.st_mode)
702 && st.st_rdev == rdev && 0<(len = readlinkat(fd, buf, buf, len)))
709 // Couldn't find it, try all the tty drivers.
711 FILE *fp = fopen("/proc/tty/drivers", "r");
712 int tty_major = 0, maj = major(rdev), min = minor(rdev);
715 while (fscanf(fp, "%*s %256s %d %*s %*s", buf, &tty_major) == 2) {
716 // TODO: we could parse the minor range too.
717 if (tty_major == maj) {
718 sprintf(buf+strlen(buf), "%d", min);
719 if (!stat(buf, &st) && S_ISCHR(st.st_mode) && st.st_rdev==rdev)
727 // Really couldn't find it, so just show major:minor.
728 if (!tty_major) sprintf(buf, "%d:%d", maj, min);
732 if (strstart(&s, "/dev/")) memmove(buf, s, strlen(s)+1);
735 // Data we want is in a file.
736 // Last length saved in slot[] is command line (which has embedded NULs)
739 // When command has no arguments, don't space over the NUL
740 if (readfileat(fd, buf, buf, &len) && len>0) {
743 if (buf[len-1]=='\n') buf[--len] = 0;
745 // Turn NUL to space, other low ascii to ? (in non-tty mode)
746 for (i=0; i<len; i++) {
752 } else if (!TT.tty && c<' ') c = '?';
755 len = temp; // position of _first_ NUL
756 } else *buf = len = 0;
757 // Store end of argv[0] so NAME and CMDLINE can differ.
758 slot[SLOT_argv0len] = len;
761 buf += strlen(buf)+1;
764 if (TT.show_process) {
771 // If we need to sort the output, add it to the list and return.
772 s = xmalloc(buf-toybuf);
773 new->extra = (long)s;
774 memcpy(s, toybuf, buf-toybuf);
780 static char *parse_ko(void *data, char *type, int length)
782 struct strawberry *field;
783 char *width, *title, *end, *s;
786 // Get title, length of title, type, end of type, and display width
788 // Chip off =name to display
789 if ((end = strchr(type, '=')) && length>(end-type)) {
791 length -= (end-type)+1;
797 // Chip off :width to display
798 if ((width = strchr(type, ':')) && width<end) {
799 if (!title) length = width-type;
802 // Allocate structure, copy title
803 field = xzalloc(sizeof(struct strawberry)+(length+1)*!!title);
805 memcpy(field->title = field->forever, title, length);
806 field->title[field->len = length] = 0;
810 field->len = strtol(++width, &title, 10);
811 if (!isdigit(*width) || title != end) return title;
817 if (*type == '-') field->reverse = -1;
818 else if (*type != '+') type--;
820 for (i = 0; i<ARRAY_LEN(typos); i++) {
822 for (j = 0; j<2; j++) {
823 if (!j) s = typos[i].name;
824 // posix requires alternate names for some fields
825 else if (-1==(k = stridx((char []){PS_NI, PS_SCH, PS_ELAPSED, PS__CPU,
826 PS_VSZ, PS_USER, 0}, i))) continue;
828 s = ((char *[]){"NICE", "SCHED", "ETIME", "PCPU", "VSIZE", "UNAME"})[k];
830 if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break;
834 if (i==ARRAY_LEN(typos)) return type;
835 if (!field->title) field->title = typos[field->which].name;
836 if (!field->len) field->len = typos[field->which].width;
837 else if (typos[field->which].width<0) field->len *= -1;
838 dlist_add_nomalloc(data, (void *)field);
843 long long get_headers(struct strawberry *fields, char *buf, int blen)
848 for (; fields; fields = fields->next) {
849 len += snprintf(buf+len, blen-len, " %*s"+!bits, fields->len,
851 bits |= 1LL<<fields->which;
857 // Parse -p -s -t -u -U -g -G
858 static char *parse_rest(void *data, char *str, int len)
860 struct ptr_len *pl = (struct ptr_len *)data;
865 // Allocate next chunk of data
867 ll = pl->ptr = xrealloc(pl->ptr, sizeof(long)*(pl->len+16));
869 // Parse numerical input
871 ll[pl->len] = xstrtol(str, &end, 10);
872 if (end==(len+str)) num++;
875 if (pl==&TT.pp || pl==&TT.ss) {
876 if (num && ll[pl->len]>0) {
881 } else if (pl==&TT.tt) {
882 // -t pts = 12,pts/12 tty = /dev/tty2,tty2,S0
884 if (strstart(&str, strcpy(toybuf, "/dev/"))) len -= 5;
885 if (strstart(&str, "pts/")) {
888 } else if (strstart(&str, "tty")) len -= 3;
890 if (len<256 && (!(end = strchr(str, '/')) || end-str>len)) {
893 end = toybuf + sprintf(toybuf, "/dev/%s", num ? "pts/" : "tty");
894 memcpy(end, str, len);
897 ll[pl->len++] = st.st_rdev;
901 } else if (len<255) {
910 memcpy(name, str, len);
912 if (pl==&TT.gg || pl==&TT.GG) {
913 struct group *gr = getgrnam(name);
915 ll[pl->len++] = gr->gr_gid;
919 } else if (pl==&TT.uu || pl==&TT.UU) {
920 struct passwd *pw = getpwnam(name);
922 ll[pl->len++] = pw->pw_uid;
934 static int ksort(void *aa, void *bb)
936 struct strawberry *field;
937 struct carveup *ta = *(struct carveup **)aa, *tb = *(struct carveup **)bb;
940 for (field = TT.kfields; field && !ret; field = field->next) {
941 slot = typos[field->which].slot;
943 // Can we do numeric sort?
945 if (ta->slot[slot]<tb->slot[slot]) ret = -1;
946 if (ta->slot[slot]>tb->slot[slot]) ret = 1;
949 // fallback to string sort
951 memccpy(toybuf, string_field(ta, field), 0, 2048);
953 ret = strcmp(toybuf, string_field(tb, field));
955 ret *= field->reverse;
961 static struct carveup **collate(int count, struct dirtree *dt,
962 int (*sort)(void *a, void *b))
964 struct dirtree *temp;
965 struct carveup **tbsort = xmalloc(count*sizeof(struct carveup *));
968 // descend into child list
969 *tbsort = (void *)dt;
974 for (i = 0; i < count; i++) {
976 tbsort[i] = (void *)dt->extra;
984 static void default_ko(char *s, void *fields, char *err, struct arg_list *arg)
988 memset(&def, 0, sizeof(struct arg_list));
990 comma_args(arg ? arg : &def, fields, err, parse_ko);
993 static void shared_main(void)
997 TT.ticks = sysconf(_SC_CLK_TCK);
999 TT.width = (toys.which->name[1] == 's') ? 99999 : 80;
1001 terminal_size(&TT.width, &TT.height);
1004 // find controlling tty, falling back to /dev/tty if none
1005 for (i = 0; !TT.tty && i<4; i++) {
1009 if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break;
1011 if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev;
1012 if (i==3) close(fd);
1022 if (toys.optflags&FLAG_w) TT.width = 99999;
1025 // parse command line options other than -o
1026 comma_args(TT.ps.P, &TT.PP, "bad -P", parse_rest);
1027 comma_args(TT.ps.p, &TT.pp, "bad -p", parse_rest);
1028 comma_args(TT.ps.t, &TT.tt, "bad -t", parse_rest);
1029 comma_args(TT.ps.s, &TT.ss, "bad -s", parse_rest);
1030 comma_args(TT.ps.u, &TT.uu, "bad -u", parse_rest);
1031 comma_args(TT.ps.U, &TT.UU, "bad -U", parse_rest);
1032 comma_args(TT.ps.g, &TT.gg, "bad -g", parse_rest);
1033 comma_args(TT.ps.G, &TT.GG, "bad -G", parse_rest);
1034 comma_args(TT.ps.k, &TT.kfields, "bad -k", parse_ko);
1035 dlist_terminate(TT.kfields);
1037 // Parse manual field selection, or default/-f/-l, plus -Z and -O
1038 if (toys.optflags&FLAG_Z) default_ko("LABEL", &TT.fields, 0, 0);
1039 if (toys.optflags&FLAG_f) s = "USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD";
1040 else if (toys.optflags&FLAG_l)
1041 s = "F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD";
1042 else if (CFG_TOYBOX_ON_ANDROID)
1043 s = "USER,PID,PPID,VSIZE,RSS,WCHAN:10,ADDR:10=PC,S,NAME";
1044 else s = "PID,TTY,TIME,CMD";
1045 default_ko(s, &TT.fields, "bad -o", TT.ps.o);
1047 if (TT.fields) TT.fields = ((struct strawberry *)TT.fields)->prev;
1048 comma_args(TT.ps.O, &TT.fields, "bad -O", parse_ko);
1049 if (TT.fields) TT.fields = ((struct strawberry *)TT.fields)->next;
1051 dlist_terminate(TT.fields);
1053 // -f and -n change the meaning of some fields
1054 if (toys.optflags&(FLAG_f|FLAG_n)) {
1055 struct strawberry *ever;
1057 for (ever = TT.fields; ever; ever = ever->next) {
1058 if ((toys.optflags&FLAG_f) && ever->which==PS_CMD) ever->which = PS_ARGS;
1059 if ((toys.optflags&FLAG_n) && ever->which>=PS_UID
1060 && ever->which<=PS_RGROUP && (typos[ever->which].slot&64))
1065 TT.bits = get_headers(TT.fields, toybuf, sizeof(toybuf));
1066 printf("%s\n", toybuf);
1068 if (!(toys.optflags&FLAG_k)) TT.show_process = (void *)show_ps;
1069 TT.match_process = ps_match_process;
1070 dt = dirtree_read("/proc", get_ps);
1072 if (toys.optflags&FLAG_k) {
1073 struct carveup **tbsort = collate(TT.kcount, dt, ksort);
1075 qsort(tbsort, TT.kcount, sizeof(struct carveup *), (void *)ksort);
1076 for (i = 0; i<TT.kcount; i++) {
1081 if (CFG_TOYBOX_FREE) free(tbsort);
1084 if (CFG_TOYBOX_FREE) {
1093 llist_traverse(TT.fields, free);
1099 #include "generated/flags.h"
1101 // select which of the -o fields to sort by
1102 static void setsort(int pos)
1104 struct strawberry *field, *going2;
1109 for (field = TT.fields; field; field = field->next) {
1110 if ((TT.sortpos = i++)<pos && field->next) continue;
1111 going2 = TT.kfields;
1112 going2->which = field->which;
1113 going2->len = field->len;
1118 // If we have both, adjust slot[deltas[]] to be relative to previous
1119 // measurement rather than process start. Stomping old.data is fine
1120 // because we free it after displaying.
1121 static int merge_deltas(long long *oslot, long long *nslot)
1123 char deltas[] = {SLOT_utime2, SLOT_iobytes, SLOT_diobytes, SLOT_upticks,
1124 SLOT_rchar, SLOT_wchar, SLOT_rbytes, SLOT_wbytes, SLOT_swap};
1127 for (i = 0; i<ARRAY_LEN(deltas); i++)
1128 oslot[deltas[i]] = nslot[deltas[i]] - oslot[deltas[i]];
1133 static void top_common(char *intro,
1134 int (*filter)(long long *oslot, long long *nslot))
1137 long long timeout = 0, now;
1139 struct carveup **tb;
1141 } plist[2], *plold, *plnew, old, new, mix;
1142 char scratch[16], *header;
1144 int i, lines, topoff = 0, done = 0;
1146 TT.bits = get_headers(TT.fields, toybuf, sizeof(toybuf));
1147 header = xstrdup(toybuf);
1149 memset(plist, 0, sizeof(plist));
1151 struct dirtree *dt = dirtree_read("/proc", get_ps);
1153 plold = plist+(tock++&1);
1154 plnew = plist+(tock&1);
1155 plnew->tb = collate(plnew->count = TT.kcount, dt, ksort);
1158 // First time, wait a quarter of a second to collect a little delta data.
1164 // Collate old and new into "mix", depends on /proc read in pid sort order
1167 mix.tb = xmalloc((old.count+new.count)*sizeof(struct carveup));
1170 while (old.count || new.count) {
1171 struct carveup *otb = *old.tb, *ntb = *new.tb;
1173 // If we just have old, discard it.
1174 if (old.count && (!new.count || *otb->slot < *ntb->slot)) {
1181 // If we just have new, use it verbatim
1182 if (!old.count || *otb->slot > *ntb->slot) mix.tb[mix.count] = ntb;
1185 if (filter(otb->slot, ntb->slot)) {
1186 mix.tb[mix.count] = otb;
1196 // We will re-fetch no data before its time. - Mork calling Orson Welles
1198 int and, or, not, xor;
1199 char *pos, *pct, *end=end, buf[32], was, is;
1201 qsort(mix.tb, mix.count, sizeof(struct carveup *), (void *)ksort);
1203 if (!(toys.optflags&FLAG_b)) printf("\033[H\033[J");
1204 if (!(toys.optflags&FLAG_q)) {
1206 // Show intro lines, parsing printf-ish escapes
1208 pos = end = TT.top.h ? TT.top.h : intro;
1210 if (i==sizeof(toybuf)-8) error_exit("tilt");
1212 if (end && end<=pos) end = next_printf(pos, &pct);
1214 // Not an %escape sequence?
1215 if (!end || pct>pos) {
1219 if ((or = unescape(*++pos))) c = or;
1220 else if (!*pos) error_exit("bad -h \\");
1224 and = 8-utf8len(toybuf);
1225 while (and--) toybuf[i++] = ' ';
1226 } else if (!c || '\n'==c) {
1227 draw_str(toybuf, TT.width);
1230 if (1>--lines) break;
1233 } else toybuf[i++] = c;
1238 for (and = or = 0; and < ARRAY_LEN(typos); and++) {
1239 xor = strlen(typos[and].name);
1240 if (xor>or && !strncasecmp(typos[and].name, end, xor)) {
1245 if (!or) error_exit("bad -h '%.16ss'", end);
1246 snprintf(buf, sizeof(buf)-1, "%.*s", (int)(end-pct), pct);
1252 for (pos++; *pos && *pos != '='; pos++);
1253 if (!*pos++) error_exit("-h bad =");
1255 // i += snprintf(toybuf+i, sizeof(toybuf)-i, "[%s]", buf);
1256 i += snprintf(toybuf+i, sizeof(toybuf)-i, "{%d}", not);
1257 // for (and = 0; and<mix.count; and++) { }
1264 strcpy(pos = toybuf, header);
1265 for (i = 0, is = *pos; *pos; pos++) {
1268 if (isspace(was) && !isspace(is) && i++==TT.sortpos) pos[-1] = '[';
1269 if (!isspace(was) && isspace(is) && i==TT.sortpos+1) *pos = ']';
1272 printf("\033[7m%*.*s\033[0m\n\r",
1273 (toys.optflags&FLAG_b) ? 0 : -TT.width, TT.width, toybuf);
1276 if (!(toys.optflags&FLAG_b))
1277 terminal_probesize(&TT.width, &TT.height);
1280 for (i = 0; i<lines && i+topoff<mix.count; i++) {
1281 if (i) xprintf("\r\n");
1282 show_ps(mix.tb[i+topoff]);
1285 if (TT.top.n && !--TT.top.n) {
1290 // Get current time in miliseconds
1291 clock_gettime(CLOCK_MONOTONIC, &ts);
1292 now = ts.tv_sec*1000+ts.tv_nsec/1000000;
1293 if (timeout<=now) timeout += TT.top.d;
1294 if (timeout<=now) timeout = now+TT.top.d;
1296 i = scan_key_getsize(scratch, timeout-now, &TT.width, &TT.height);
1297 if (i==-1 || i==3 || toupper(i)=='Q') {
1303 // Flush unknown escape sequences.
1304 if (i==27) while (0<scan_key_getsize(scratch, 0, &TT.width, &TT.height));
1308 } else if (toupper(i)=='R')
1309 ((struct strawberry *)TT.kfields)->reverse *= -1;
1312 if (i == KEY_LEFT) setsort(TT.sortpos-1);
1313 else if (i == KEY_RIGHT) setsort(TT.sortpos+1);
1314 else if (i == KEY_UP) {
1315 if (topoff) topoff--;
1316 } else if (i == KEY_DOWN) topoff++;
1322 for (i=0; i<plold->count; i++) free(plold->tb[i]);
1325 if (!(toys.optflags&FLAG_b)) tty_reset();
1328 static void top_setup(char *defo, char *defk)
1333 if (toys.optflags&FLAG_b) TT.width = TT.height = 99999;
1335 xset_terminal(0, 1, 0);
1336 sigatexit(tty_sigreset);
1340 comma_args(TT.top.u, &TT.uu, "bad -u", parse_rest);
1341 comma_args(TT.top.p, &TT.pp, "bad -p", parse_rest);
1342 TT.match_process = shared_match_process;
1344 default_ko(defo, &TT.fields, "bad -o", TT.top.o);
1345 dlist_terminate(TT.fields);
1346 len = strlen(toybuf);
1347 if (toybuf[len-1]!=' ' && len<sizeof(toybuf)-1) strcpy(toybuf+len, " ");
1349 // First (dummy) sort field is overwritten by setsort()
1350 default_ko("-S", &TT.kfields, 0, 0);
1351 default_ko(defk, &TT.kfields, "bad -k", TT.top.k);
1352 dlist_terminate(TT.kfields);
1353 setsort(TT.top.s-1);
1358 // usage: [-h HEADER] -o OUTPUT -k SORT
1361 "PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,ARGS",
1362 "-%CPU,-ETIME,-PID");
1363 top_common("top", merge_deltas);
1368 #include "generated/flags.h"
1370 static int iotop_filter(long long *oslot, long long *nslot)
1372 if (!(toys.optflags&FLAG_a)) merge_deltas(oslot, nslot);
1374 return !(toys.optflags&FLAG_o)||oslot[SLOT_iobytes+!(toys.optflags&FLAG_A)];
1377 void iotop_main(void)
1379 char *s1 = 0, *s2 = 0, *d = "D"+!!(toys.optflags&FLAG_A);
1381 if (toys.optflags&FLAG_K) TT.forcek++;
1383 top_setup(s1 = xmprintf("PID,PR,USER,%sREAD,%sWRITE,SWAP,%sIO,COMM",d,d,d),
1384 s2 = xmprintf("-%sIO,-ETIME,-PID",d));
1387 top_common("iotop", iotop_filter);
1390 // pkill's plumbing wrap's pgrep's and thus mostly takes place in pgrep's flag
1391 // context, so force pgrep's flags on even when building pkill standalone.
1392 // (All the pgrep/pkill functions drop out when building ps standalone.)
1394 #define CLEANUP_iotop
1396 #include "generated/flags.h"
1399 struct regex_list *next;
1403 static void show_pgrep(struct carveup *tb)
1406 struct regex_list *reg;
1407 char *name = tb->str;
1409 // Never match ourselves.
1410 if (TT.pgrep.self == *tb->slot) return;
1412 if (toys.optflags&FLAG_f) name += tb->offset[4];
1414 if (TT.pgrep.regexes) {
1415 for (reg = TT.pgrep.regexes; reg; reg = reg->next) {
1416 if (regexec(®->reg, name, 1, &match, 0)) continue;
1417 if (toys.optflags&FLAG_x)
1418 if (match.rm_so || match.rm_eo!=strlen(name)) continue;
1421 if ((toys.optflags&FLAG_v) ? !!reg : !reg) return;
1424 // Repurpose a field for -c count
1426 if (TT.pgrep.signal) {
1427 if (kill(*tb->slot, TT.pgrep.signal)) {
1428 char *s = num_to_sig(TT.pgrep.signal);
1430 if (!s) sprintf(s = toybuf, "%d", TT.pgrep.signal);
1431 perror_msg("%s->%lld", s, *tb->slot);
1434 if (!(toys.optflags&FLAG_c) && (!TT.pgrep.signal || TT.tty)) {
1435 printf("%lld", *tb->slot);
1436 if (toys.optflags&FLAG_l) printf(" %s", name);
1438 printf("%s", TT.pgrep.d ? TT.pgrep.d : "\n");
1442 static int pgrep_match_process(long long *slot)
1444 int match = shared_match_process(slot);
1446 return (toys.optflags&FLAG_v) ? !match : match;
1449 void pgrep_main(void)
1452 struct regex_list *reg;
1454 TT.pgrep.self = getpid();
1456 // No signal names start with "L", so no need for "L: " parsing.
1457 if (TT.pgrep.L && 1>(TT.pgrep.signal = sig_to_num(TT.pgrep.L)))
1458 error_exit("bad -L '%s'", TT.pgrep.L);
1460 comma_args(TT.pgrep.G, &TT.GG, "bad -G", parse_rest);
1461 comma_args(TT.pgrep.g, &TT.gg, "bad -g", parse_rest);
1462 comma_args(TT.pgrep.P, &TT.PP, "bad -P", parse_rest);
1463 comma_args(TT.pgrep.s, &TT.ss, "bad -s", parse_rest);
1464 comma_args(TT.pgrep.t, &TT.tt, "bad -t", parse_rest);
1465 comma_args(TT.pgrep.U, &TT.UU, "bad -U", parse_rest);
1466 comma_args(TT.pgrep.u, &TT.uu, "bad -u", parse_rest);
1468 if ((toys.optflags&(FLAG_x|FLAG_f)) ||
1469 !(toys.optflags&(FLAG_G|FLAG_g|FLAG_P|FLAG_s|FLAG_t|FLAG_U|FLAG_u)))
1470 if (!toys.optc) help_exit("No PATTERN");
1472 if (toys.optflags&FLAG_f) TT.bits |= _PS_CMDLINE;
1473 for (arg = toys.optargs; *arg; arg++) {
1474 reg = xmalloc(sizeof(struct regex_list));
1475 xregcomp(®->reg, *arg, REG_EXTENDED);
1476 reg->next = TT.pgrep.regexes;
1477 TT.pgrep.regexes = reg;
1479 TT.match_process = pgrep_match_process;
1480 TT.show_process = (void *)show_pgrep;
1482 dirtree_read("/proc", get_ps);
1483 if (toys.optflags&FLAG_c) printf("%d\n", TT.sortpos);
1484 if (TT.pgrep.d) xputc('\n');
1487 #define CLEANUP_pgrep
1489 #include "generated/flags.h"
1491 void pkill_main(void)
1493 if (!TT.pgrep.L) TT.pgrep.signal = SIGTERM;
1494 if (toys.optflags & FLAG_V) TT.tty = 1;