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 * TODO: ps aux (att & bsd style "ps -ax" vs "ps ax" behavior difference)
33 * TODO: switch -fl to -y
34 * TODO: thread support /proc/$d/task/%d/stat (and -o stat has "l")
35 * TODO: iotop: Window size change: respond immediately. Why not padding
36 * at right edge? (Not adjusting to screen size at all? Header wraps?)
37 * TODO: utf8 fontmetrics
39 USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflno*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN))
40 USE_TTOP(NEWTOY(ttop, ">0d#=3n#<1mb", TOYFLAG_USR|TOYFLAG_BIN))
41 USE_IOTOP(NEWTOY(iotop, "Aabkoqp*u*d#n#", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
42 USE_PGREP(NEWTOY(pgrep, "?cld:u*U*t*s*P*g*G*fnovxL:", TOYFLAG_USR|TOYFLAG_BIN))
43 USE_PKILL(NEWTOY(pkill, "u*U*t*s*P*g*G*fnovxl:", TOYFLAG_USR|TOYFLAG_BIN))
49 usage: ps [-AadeflnwZ] [-gG GROUP,] [-k FIELD,] [-o FIELD,] [-p PID,] [-t TTY,] [-uU USER,]
53 Which processes to show (selections may be comma separated lists):
56 -a Processes with terminals that aren't session leaders
57 -d All processes that aren't session leaders
59 -g Belonging to GROUPs
60 -G Belonging to real GROUPs (before sgid)
62 -P Parent PIDs (--ppid)
64 -t Attached to selected TTYs
66 -U Owned by real USERs (before suid)
70 -k Sort FIELDs in +increasing or -decreasting order (--sort)
71 -n Show numeric USER and GROUP
72 -w Wide output (don't truncate at terminal width)
74 Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD)
76 -f Full listing (-o USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD)
77 -l Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD)
78 -o Output the listed FIELDs, each with optional :size and/or =title
83 ADDR Instruction pointer
84 ARGS Command line (argv[0-X] minus path)
85 CMD COMM without -f, ARGS with -f
86 CMDLINE Command line (argv[0-X])
87 COMM Original command name
88 COMMAND Original command path
89 CPU Which processor running on
90 ETIME Elapsed time since process start
91 F Flags (1=FORKNOEXEC, 4=SUPERPRIV)
95 MAJFL Major page faults
96 MINFL Minor page faults
97 NAME Command name (argv[0])
98 NI Niceness (lower is faster)
99 PCPU Percentage of CPU time used
100 PGID Process Group ID
102 PPID Parent Process ID
103 PRI Priority (higher is faster)
104 PSR Processor last executed on
105 RGID Real (before sgid) group ID
106 RGROUP Real (before sgid) group name
107 RSS Resident Set Size (memory in use, 4k pages)
108 RTPRIO Realtime priority
109 RUID Real (before suid) user ID
110 RUSER Real (before suid) user name
112 R (running) S (sleeping) D (device I/O) T (stopped) t (traced)
113 Z (zombie) X (deader) x (dead) K (wakekill) W (waking)
114 SCHED Scheduling policy (0=other, 1=fifo, 2=rr, 3=batch, 4=iso, 5=idle)
115 STAT Process state (S) plus:
116 < high priority N low priority L locked memory
117 s session leader + foreground l multithreaded
118 STIME Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss)
119 SZ Memory Size (4k pages needed to completely swap out process)
120 TIME CPU time consumed
121 TTY Controlling terminal
124 VSZ Virtual memory size (1k units)
125 WCHAN Waiting in kernel for
127 You can put a % after SZ, RSS
134 usage: ttop [-mb] [ -d seconds ] [ -n iterations ]
138 Provide a view of process activity in real time.
140 N/M/P/T show CPU usage, sort by pid/mem/cpu/time
148 -n Iterations before exiting
149 -d Delay between updates
153 # Requires CONFIG_IRQ_TIME_ACCOUNTING in the kernel for /proc/$$/io
158 usage: iotop [-Aabkoq] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,]
160 Rank processes by I/O. Cursor left/right to change sort, Q to exit.
162 -A All I/O, not just disk
163 -a Accumulated I/O (not percentage)
164 -b Batch mode (no tty)
165 -d Delay SECONDS between each cycle (default 3)
167 -n Exit after NUMBER iterations
168 -o Only show processes doing I/O
170 -q Quiet (no header lines)
176 depends on PGKILL_COMMON
178 usage: pgrep [-cL] [-d DELIM] [-L SIGNAL] [PATTERN]
180 Search for process(es). PATTERN is an extended regular expression checked
181 against command names.
183 -c Show only count of matches
184 -d Use DELIM instead of newline
185 -L Send SIGNAL instead of printing name
192 usage: pgrep [-fnovx] [-G GID,] [-g PGRP,] [-P PPID,] [-s SID,] [-t TERM,] [-U UID,] [-u EUID,]
194 -f Check full command line for PATTERN
195 -G Match real Group ID(s)
196 -g Match Process Group(s) (0 is current user)
199 -P Match Parent Process ID(s)
200 -s Match Session ID(s) (0 for current)
202 -U Match real User ID(s)
203 -u Match effective User ID(s)
205 -x Match whole command (not substring)
211 usage: pkill [-l SIGNAL] [PATTERN]
260 struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU;
261 unsigned width, height;
263 void *fields, *kfields;
264 long long ticks, bits, ioread, iowrite, aioread, aiowrite;
266 int kcount, forcek, sortpos;
267 int (*match_process)(long long *slot);
268 void (*show_process)(void *tb);
272 struct strawberry *next, *prev;
273 short which, len, reverse;
278 /* The slot[] array is mostly populated from /proc/$PID/stat (kernel proc.txt
279 * table 1-4) but we shift and repurpose fields, with the result being:
281 * 0 pid process id 1 ppid parent process id
282 * 2 pgrp process group 3 sid session id
283 * 4 tty_nr tty the process uses 5 tty_pgrp pgrp of the tty
284 * 6 flags task flags 7 min_flt minor faults
285 * 8 cmin_flt minor faults+child 9 maj_flt major faults
286 * 10 cmaj_flt major faults+child 11 utime user+kernel jiffies
287 * 12 stime kernel mode jiffies 13 cutime user jiffies+child
288 * 14 cstime kernel mode jiffies+child 15 priority priority level
289 * 16 nice nice level 17 num_threads number of threads
290 * 18 vmlck locked memory 19 start_time jiffies after boot
291 * 20 vsize virtual memory size 21 rss resident set size
292 * 22 rsslim limit in bytes on rss 23 start_code code segment addr
293 * 24 end_code code segment address 25 start_stack stack address
294 * 26 esp current value of ESP 27 eip current value of EIP
295 * 28 iobytes All I/O bytes 29 diobytes disk I/O bytes
296 * 30 sigign bitmap of ignored signals 31 uid user id
297 * 32 ruid real user id 33 gid group id
298 * 34 rgid real group id 35 exit_signal sent to parent thread
299 * 36 task_cpu CPU task is scheduled on 37 rt_priority realtime priority
300 * 38 policy man sched_setscheduler 39 blkio_ticks spent wait block IO
301 * 40 gtime guest jiffies of task 41 cgtime guest jiff of child
302 * 42 start_data program data+bss address 43 end_data program data+bss
303 * 44 upticks 46-19 (divisor for %) 45 argv0len argv[0] length
304 * 46 uptime sysinfo.uptime @read time 47 vsz Virtual Size
305 * 48 rss Resident Set Size 49 shr Shared memory
306 * 50 rchar All bytes read 51 wchar All bytes written
307 * 52 rbytes Disk bytes read 53 rbytes Disk bytes written
308 * 54 swap Swap pages used
311 // Data layout in toybuf
313 long long slot[55]; // data from /proc
314 unsigned short offset[5]; // offset of fields in str[] (skip name, always 0)
316 char str[]; // name, tty, command, wchan, attr, cmdline
319 // TODO: Android uses -30 for LABEL, but ideally it would auto-size.
320 // 64|slot means compare as string when sorting
323 signed char width, slot;
324 } static const typos[] = TAGGED_ARRAY(PS,
325 // stat#s: PID PPID PRI NI ADDR SZ RSS PGID VSZ MAJFL MINFL PR PSR RTPRIO
327 {"PID", 5, 0}, {"PPID", 5, 1}, {"PRI", 3, 15}, {"NI", 3, 16},
328 {"ADDR", 4+sizeof(long), 27}, {"SZ", 5, 20}, {"RSS", 5, 21}, {"PGID", 5, 2},
329 {"VSZ", 6, 20}, {"MAJFL", 6, 9}, {"MINFL", 6, 7}, {"PR", 2, 15},
330 {"PSR", 3, 36}, {"RTPRIO", 6, 37}, {"SCH", 3, 38},
332 // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP
333 {"UID", 5, 31}, {"USER", -8, 64|31}, {"RUID", 4, 32}, {"RUSER", -8, 64|32},
334 {"GID", 8, 33}, {"GROUP", -8, 64|33}, {"RGID", 4, 34}, {"RGROUP", -8, 64|34},
336 // CMD TTY WCHAN LABEL CMDLINE COMMAND
337 {"COMM", -15, -1}, {"TTY", -8, -2}, {"WCHAN", -6, -3}, {"LABEL", -30, -4},
338 {"COMMAND", -27, -5}, {"CMDLINE", -27, -6}, {"ARGS", -27, -6},
339 {"NAME", -15, -6}, {"CMD", -27, -1},
341 // TIME ELAPSED TIME+
342 {"TIME", 8, 11}, {"ELAPSED", 11, 19}, {"TIME+", 9, 11},
344 // Remaining ungrouped
345 {"STIME", 5, 19}, {"F", 1, 64|6}, {"S", -1, 64}, {"C", 1, 0}, {"%CPU", 4, 64},
346 {"STAT", -5, 64}, {"%VSZ", 5, 23}, {"VIRT", 4, 47}, {"RES", 4, 48},
347 {"SHR", 4, 49}, {"READ", 6, 50}, {"WRITE", 6, 51}, {"IO", 6, 28},
348 {"DREAD", 6, 52}, {"DWRITE", 6, 53}, {"SWAP", 6, 54}, {"DIO", 6, 29}
351 // Return 0 to discard, nonzero to keep
352 static int shared_match_process(long long *slot)
354 struct ptr_len match[] = {
355 {&TT.gg, 33}, {&TT.GG, 34}, {&TT.pp, 0}, {&TT.PP, 1}, {&TT.ss, 3},
356 {&TT.tt, 4}, {&TT.uu, 31}, {&TT.UU, 32}
361 // Do we have -g -G -p -P -s -t -u -U options selecting processes?
362 for (i = 0; i < ARRAY_LEN(match); i++) {
363 struct ptr_len *mm = match[i].ptr;
366 for (j = 0; j<mm->len; j++) if (ll[j] == slot[match[i].len]) return 1;
374 // Return 0 to discard, nonzero to keep
375 static int ps_match_process(long long *slot)
377 int i = shared_match_process(slot);
380 // If we had selections and didn't match them, don't display
383 // Filter implicit categories for other display types
384 if ((toys.optflags&(FLAG_a|FLAG_d)) && slot[3]==*slot) return 0;
385 if ((toys.optflags&FLAG_a) && !slot[4]) return 0;
386 if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e)) && TT.tty!=slot[4])
392 // Convert field to string representation
393 static char *string_field(struct carveup *tb, struct strawberry *field)
395 char *buf = toybuf+sizeof(toybuf)-260, *out = buf, *s;
396 int which = field->which, sl = typos[which].slot;
397 long long *slot = tb->slot, ll = (sl >= 0) ? slot[sl&63] : 0;
399 // Default: unsupported (5 "C")
402 // stat#s: PID PPID PRI NI ADDR SZ RSS PGID VSZ MAJFL MINFL PR PSR RTPRIO SCH
403 if (which <= PS_SCH) {
406 if (which==PS_PRI) ll = 39-ll;
407 if (which==PS_ADDR) fmt = "%llx";
408 else if (which==PS_SZ) ll >>= 12;
409 else if (which==PS_RSS) ll <<= 2;
410 else if (which==PS_VSZ) ll >>= 10;
411 else if (which==PS_PR && ll<-9) fmt="RT";
412 else if (which==PS_RTPRIO && ll == 0) fmt="-";
413 sprintf(out, fmt, ll);
415 // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP
416 } else if (which <= PS_RGROUP) {
417 sprintf(out, "%lld", ll);
419 if (which > PS_RUSER) {
420 struct group *gr = getgrgid(ll);
422 if (gr) out = gr->gr_name;
424 struct passwd *pw = getpwuid(ll);
426 if (pw) out = pw->pw_name;
430 // COMM TTY WCHAN LABEL COMMAND CMDLINE ARGS NAME CMD
433 tb->str[tb->offset[4]+slot[45]] = (which == PS_NAME) ? 0 : ' ';
436 if (--sl) out += tb->offset[--sl];
438 for (s = out; *s && *s != ' '; s++) if (*s == '/') out = s+1;
439 if (which>=PS_COMMAND && !*out) sprintf(out = buf, "[%s]", tb->str);
440 // TIME ELAPSED TIME+
441 } else if (which <= PS_TIME_) {
442 int unit = 60, pad = 2, j = TT.ticks;
445 if (which!=PS_TIME_) unit *= 60*24;
447 // top adjusts slot[44], we want original meaning.
448 if (which==PS_ELAPSED) ll = (slot[46]*j)-slot[19];
451 // Output days-hours:mins:secs, skipping non-required fields with zero
452 // TIME has 3 required fields, ETIME has 2. (Posix!) TIME+ is from top
453 for (s = 0, j = 2*(which==PS_TIME_); j<4; j++) {
454 if (!s && (seconds>unit || j == 1+(which!=PS_TIME))) s = out;
456 s += sprintf(s, j ? "%0*ld": "%*ld", pad, (long)(seconds/unit));
458 if ((*s = "-::"[j])) s++;
463 if (which==PS_TIME_ && s-out<8)
464 sprintf(s, ".%02lld", (100*(ll%TT.ticks))/TT.ticks);
466 // Posix doesn't specify what flags should say. Man page says
467 // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h
468 } else if (which==PS_F) sprintf(out, "%llo", (slot[6]>>6)&5);
469 else if (which==PS_S || which==PS_STAT) {
472 if (which==PS_STAT) {
473 // TODO l = multithreaded
474 if (slot[16]<0) *s++ = '<';
475 else if (slot[16]>0) *s++ = 'N';
476 if (slot[3]==*slot) *s++ = 's';
477 if (slot[18]) *s++ = 'L';
478 if (slot[5]==*slot) *s++ = '+';
481 } else if (which==PS_STIME) {
482 time_t t = time(0)-slot[46]+slot[19]/TT.ticks;
484 // Padding behavior's a bit odd: default field size is just hh:mm.
485 // Increasing stime:size reveals more data at left until full,
486 // so move start address so yyyy-mm-dd hh:mm revealed on left at :16,
487 // then add :ss on right for :19.
488 strftime(out, 260, "%F %T", localtime(&t));
489 out = out+strlen(out)-3-abs(field->len);
490 if (out<buf) out = buf;
491 } else if (which==PS__CPU || which==PS__VSZ) {
492 if (which==PS__CPU) sl = (slot[11]*1000)/slot[44];
493 else sl = (slot[23]*1000)/TT.si.totalram;
494 sprintf(out, "%d.%d", sl/10, sl%10);
495 } else if (which>=PS_VIRT && which <= PS_DIO) {
496 ll = slot[typos[which].slot];
497 if (which <= PS_SHR) ll *= sysconf(_SC_PAGESIZE);
498 if (TT.forcek) sprintf(out, "%lldk", ll/1024);
499 else human_readable(out, ll, 0);
505 // Display process data that get_ps() read from /proc, formatting with TT.fields
506 static void show_ps(struct carveup *tb)
508 struct strawberry *field;
509 int i, len, width = TT.width;
511 // Loop through fields to display
512 for (field = TT.fields; field; field = field->next) {
513 char *out = string_field(tb, field);
515 // Output the field, appropriately padded
516 len = width - (field != TT.fields);
517 if (!field->next && field->len<0) i = 0;
519 i = len<abs(field->len) ? len : field->len;
523 // TODO test utf8 fontmetrics
524 width -= printf(" %*.*s" + (field == TT.fields), i, len, out);
530 // dirtree callback: read data about process to display, store, or discard it.
531 // Fills toybuf with struct carveup and either DIRTREE_SAVEs a copy to ->extra
532 // (in -k mode) or calls show_ps on toybuf (no malloc/copy/free there).
533 static int get_ps(struct dirtree *new)
539 {"fd/", _PS_TTY}, {"wchan", _PS_WCHAN}, {"attr/current", _PS_LABEL},
540 {"exe", _PS_COMMAND}, {"cmdline", _PS_CMDLINE|_PS_ARGS|_PS_NAME}
542 struct carveup *tb = (void *)toybuf;
543 long long *slot = tb->slot;
544 char *name, *s, *buf = tb->str, *end = 0;
548 // Recurse one level into /proc children, skip non-numeric entries
550 return DIRTREE_RECURSE|DIRTREE_SHUTUP|(DIRTREE_SAVE*!TT.show_process);
552 memset(slot, 0, sizeof(tb->slot));
553 if (!(*slot = atol(new->name))) return 0;
554 fd = dirtree_parentfd(new);
557 sprintf(buf, "%lld/stat", *slot);
558 if (!readfileat(fd, buf, buf, &len)) return 0;
560 // parse oddball fields (name and state). Name can have embedded ')' so match
561 // _last_ ')' in stat (although VFS limits filenames to 255 bytes max).
562 // All remaining fields should be numeric.
563 if (!(name = strchr(buf, '('))) return 0;
564 for (s = ++name; *s; s++) if (*s == ')') end = s;
565 if (!end || end-name>255) return 0;
567 // Parse numeric fields (starting at 4th field in slot[1])
568 if (1>sscanf(s = end, ") %c%n", &tb->state, &i)) return 0;
569 for (j = 1; j<50; j++) if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break;
571 // Now we've read the data, move status and name right after slot[] array,
572 // and convert low chars to ? while we're at it.
573 for (i = 0; i<end-name; i++)
574 if ((tb->str[i] = name[i]) < ' ') tb->str[i] = '?';
577 len = sizeof(toybuf)-(buf-toybuf);
579 // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch
580 // or numeric wchan, and the remaining two are always zero), and vmlck into
581 // 18 (which is "obsolete, always 0" from stat)
582 slot[31] = new->st.st_uid;
583 slot[33] = new->st.st_gid;
585 // TIME and TIME+ use combined value, ksort needs 'em added.
586 slot[11] += slot[12];
588 // If RGROUP RUSER STAT RUID RGID SWAP happening, or -G or -U, parse "status"
589 // and save ruid, rgid, and vmlck.
590 if ((TT.bits&(_PS_RGROUP|_PS_RUSER|_PS_STAT|_PS_RUID|_PS_RGID|_PS_SWAP
591 |_PS_IO|_PS_DIO)) || TT.GG.len || TT.UU.len)
595 sprintf(buf, "%lld/status", *slot);
596 if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
597 s = strafter(buf, "\nUid:");
598 slot[32] = s ? atol(s) : new->st.st_uid;
599 s = strafter(buf, "\nGid:");
600 slot[34] = s ? atol(s) : new->st.st_gid;
601 if ((s = strafter(buf, "\nVmLck:"))) slot[18] = atoll(s);
602 if ((s = strafter(buf, "\nVmSwap:"))) slot[54] = atoll(s);
605 // Do we need to read "io"?
606 if (TT.bits&(_PS_READ|_PS_WRITE|_PS_DREAD|_PS_DWRITE|_PS_IO|_PS_DIO)) {
609 sprintf(buf, "%lld/io", *slot);
610 if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
611 if ((s = strafter(buf, "rchar:"))) slot[50] = atoll(s);
612 if ((s = strafter(buf, "wchar:"))) slot[51] = atoll(s);
613 if ((s = strafter(buf, "read_bytes:"))) slot[52] = atoll(s);
614 if ((s = strafter(buf, "write_bytes:"))) slot[53] = atoll(s);
615 slot[28] = slot[50]+slot[51]+slot[54];
616 slot[29] = slot[52]+slot[53]+slot[54];
619 // We now know enough to skip processes we don't care about.
620 if (TT.match_process && !TT.match_process(slot)) return 0;
622 // /proc data is generated as it's read, so for maximum accuracy on slow
623 // systems (or ps | more) we re-fetch uptime as we fetch each /proc line.
625 slot[44] = ((slot[46] = TT.si.uptime)*TT.ticks) - slot[19];
627 // Do we need to read "statm"?
628 if (TT.bits&(_PS_VIRT|_PS_RES|_PS_SHR)) {
631 sprintf(buf, "%lld/statm", *slot);
632 if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
634 for (s = buf, i=0; i<3; i++)
635 if (!sscanf(s, " %lld%n", slot+47+i, &j)) slot[47+i] = 0;
639 // Fetch string data while parentfd still available, appending to buf.
640 // (There's well over 3k of toybuf left. We could dynamically malloc, but
641 // it'd almost never get used, querying length of a proc file is awkward,
642 // fixed buffer is nommu friendly... Wait for somebody to complain. :)
644 for (j = 0; j<ARRAY_LEN(fetch); j++) {
645 tb->offset[j] = buf-(tb->str);
646 if (!(TT.bits&fetch[j].bits)) {
651 // Determine remaining space, reserving minimum of 256 bytes/field and
652 // 260 bytes scratch space at the end (for output conversion later).
653 len = sizeof(toybuf)-(buf-toybuf)-260-256*(ARRAY_LEN(fetch)-j);
654 sprintf(buf, "%lld/%s", *slot, fetch[j].name);
656 // For cmdline we readlink instead of read contents
658 if ((len = readlinkat(fd, buf, buf, len))>0) buf[len] = 0;
661 // If it's not the TTY field, data we want is in a file.
662 // Last length saved in slot[] is command line (which has embedded NULs)
667 // Call no tty "?" rather than "0:0".
670 // Can we readlink() our way to a name?
671 for (i = 0; i<3; i++) {
672 sprintf(buf, "%lld/fd/%i", *slot, i);
673 if (!fstatat(fd, buf, &st, 0) && S_ISCHR(st.st_mode)
674 && st.st_rdev == rdev && 0<(len = readlinkat(fd, buf, buf, len)))
681 // Couldn't find it, try all the tty drivers.
683 FILE *fp = fopen("/proc/tty/drivers", "r");
684 int tty_major = 0, maj = major(rdev), min = minor(rdev);
687 while (fscanf(fp, "%*s %256s %d %*s %*s", buf, &tty_major) == 2) {
688 // TODO: we could parse the minor range too.
689 if (tty_major == maj) {
690 sprintf(buf+strlen(buf), "%d", min);
691 if (!stat(buf, &st) && S_ISCHR(st.st_mode) && st.st_rdev==rdev)
699 // Really couldn't find it, so just show major:minor.
700 if (!tty_major) sprintf(buf, "%d:%d", maj, min);
704 if (strstart(&s, "/dev/")) memmove(buf, s, strlen(s)+1);
707 // Data we want is in a file.
708 // Last length saved in slot[] is command line (which has embedded NULs)
711 // When command has no arguments, don't space over the NUL
712 if (readfileat(fd, buf, buf, &len) && len>0) {
715 if (buf[len-1]=='\n') buf[--len] = 0;
717 // Turn NUL to space, other low ascii to ?
718 for (i=0; i<len; i++) {
724 } else if (c<' ') c = '?';
727 len = temp; // position of _first_ NUL
728 } else *buf = len = 0;
729 // Store end of argv[0] so NAME and CMDLINE can differ.
733 buf += strlen(buf)+1;
736 if (TT.show_process) {
742 // If we need to sort the output, add it to the list and return.
743 s = xmalloc(buf-toybuf);
744 new->extra = (long)s;
745 memcpy(s, toybuf, buf-toybuf);
751 static char *parse_ko(void *data, char *type, int length)
753 struct strawberry *field;
754 char *width, *title, *end, *s;
757 // Get title, length of title, type, end of type, and display width
759 // Chip off =name to display
760 if ((end = strchr(type, '=')) && length>(end-type)) {
762 length -= (end-type)+1;
768 // Chip off :width to display
769 if ((width = strchr(type, ':')) && width<end) {
770 if (!title) length = width-type;
773 // Allocate structure, copy title
774 field = xzalloc(sizeof(struct strawberry)+(length+1)*!!title);
776 memcpy(field->title = field->forever, title, length);
777 field->title[field->len = length] = 0;
781 field->len = strtol(++width, &title, 10);
782 if (!isdigit(*width) || title != end) return title;
787 if (*(struct strawberry **)data == TT.kfields) {
789 if (*type == '-') field->reverse = -1;
790 else if (*type != '+') type--;
793 for (i = 0; i<ARRAY_LEN(typos); i++) {
795 for (j = 0; j<2; j++) {
796 if (!j) s = typos[i].name;
797 // posix requires alternate names for some fields
798 else if (-1==(k = stridx((char []){PS_NI, PS_SCH, PS_ELAPSED, PS__CPU,
799 PS_VSZ, PS_USER, 0}, i))) continue;
801 s = ((char *[]){"NICE", "SCHED", "ETIME", "PCPU", "VSIZE", "UNAME"})[k];
803 if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break;
807 if (i==ARRAY_LEN(typos)) return type;
808 if (!field->title) field->title = typos[field->which].name;
809 if (!field->len) field->len = typos[field->which].width;
810 else if (typos[field->which].width<0) field->len *= -1;
811 dlist_add_nomalloc(data, (void *)field);
813 // Print padded header for -o.
814 if (*(struct strawberry **)data == TT.fields) {
816 snprintf(toybuf + TT.header_len, sizeof(toybuf) - TT.header_len,
817 " %*s" + (field == TT.fields), field->len, field->title);
818 TT.bits |= 1LL<<field->which;
824 // Parse -p -s -t -u -U -g -G
825 static char *parse_rest(void *data, char *str, int len)
827 struct ptr_len *pl = (struct ptr_len *)data;
833 // gg, GG, pp, ss, tt, uu, UU, *parsing;
835 // Allocate next chunk of data
837 ll = pl->ptr = xrealloc(pl->ptr, sizeof(long)*(pl->len+16));
839 // Parse numerical input
841 ll[pl->len] = xstrtol(str, &end, 10);
842 if (end==(len+str)) num++;
845 if (pl==&TT.pp || pl==&TT.ss) {
846 if (num && ll[pl->len]>0) {
851 } else if (pl==&TT.tt) {
852 // -t pts = 12,pts/12 tty = /dev/tty2,tty2,S0
854 if (strstart(&str, strcpy(toybuf, "/dev/"))) len -= 5;
855 if (strstart(&str, "pts/")) {
858 } else if (strstart(&str, "tty")) len -= 3;
860 if (len<256 && (!(end = strchr(str, '/')) || end-str>len)) {
863 end = toybuf + sprintf(toybuf, "/dev/%s", num ? "pts/" : "tty");
864 memcpy(end, str, len);
867 ll[pl->len++] = st.st_rdev;
871 } else if (len<255) {
880 memcpy(name, str, len);
882 if (pl==&TT.gg || pl==&TT.GG) {
883 struct group *gr = getgrnam(name);
885 ll[pl->len++] = gr->gr_gid;
889 } else if (pl==&TT.uu || pl==&TT.UU) {
890 struct passwd *pw = getpwnam(name);
892 ll[pl->len++] = pw->pw_uid;
904 static int ksort(void *aa, void *bb)
906 struct strawberry *field;
907 struct carveup *ta = *(struct carveup **)aa, *tb = *(struct carveup **)bb;
910 for (field = TT.kfields; field; field = field->next) {
911 slot = typos[field->which].slot;
912 // Compare as strings?
914 memccpy(toybuf, string_field(ta, field), 0, 2048);
916 ret = strcmp(toybuf, string_field(tb, field));
918 if (ta->slot[slot]<tb->slot[slot]) ret = -1;
919 if (ta->slot[slot]>tb->slot[slot]) ret = 1;
922 if (ret) return ret*field->reverse;
928 static struct carveup **collate(int count, struct dirtree *dt,
929 int (*sort)(void *a, void *b))
931 struct dirtree *temp;
932 struct carveup **tbsort = xmalloc(count*sizeof(struct carveup *));
935 // descend into child list
936 *tbsort = (void *)dt;
941 for (i = 0; i < count; i++) {
943 tbsort[i] = (void *)dt->extra;
951 static void shared_main(void)
955 TT.ticks = sysconf(_SC_CLK_TCK);
959 terminal_size(&TT.width, &TT.height);
962 // find controlling tty, falling back to /dev/tty if none
963 for (i = 0; !TT.tty && i<4; i++) {
967 if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break;
969 if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev;
979 if (toys.optflags&FLAG_w) TT.width = 99999;
982 // parse command line options other than -o
983 comma_args(TT.ps.P, &TT.PP, "bad -P", parse_rest);
984 comma_args(TT.ps.p, &TT.pp, "bad -p", parse_rest);
985 comma_args(TT.ps.t, &TT.tt, "bad -t", parse_rest);
986 comma_args(TT.ps.s, &TT.ss, "bad -s", parse_rest);
987 comma_args(TT.ps.u, &TT.uu, "bad -u", parse_rest);
988 comma_args(TT.ps.U, &TT.UU, "bad -U", parse_rest);
989 comma_args(TT.ps.g, &TT.gg, "bad -g", parse_rest);
990 comma_args(TT.ps.G, &TT.GG, "bad -G", parse_rest);
991 comma_args(TT.ps.k, &TT.kfields, "bad -k", parse_ko);
992 dlist_terminate(TT.kfields);
994 // Parse manual field selection, or default/-f/-l, plus -Z,
995 // constructing the header line in toybuf as we go.
996 if (toys.optflags&FLAG_Z) {
997 struct arg_list Z = { 0, "LABEL" };
999 comma_args(&Z, &TT.fields, "-Z", parse_ko);
1001 if (TT.ps.o) comma_args(TT.ps.o, &TT.fields, "bad -o field", parse_ko);
1006 if (toys.optflags&FLAG_f)
1007 al.arg = "USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD";
1008 else if (toys.optflags&FLAG_l)
1009 al.arg = "F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD";
1010 else if (CFG_TOYBOX_ON_ANDROID)
1011 al.arg = "USER,PID,PPID,VSIZE,RSS,WCHAN:10,ADDR:10=PC,S,CMDLINE";
1012 else al.arg = "PID,TTY,TIME,CMD";
1014 comma_args(&al, &TT.fields, 0, parse_ko);
1016 dlist_terminate(TT.fields);
1017 printf("%s\n", toybuf);
1019 // misunderstand fields the flags say to
1020 if (toys.optflags&(FLAG_f|FLAG_n)) {
1021 struct strawberry *ever;
1023 for (ever = TT.fields; ever; ever = ever->next) {
1024 int alluc = ever->which;
1026 if ((toys.optflags&FLAG_f) && alluc==PS_CMD) alluc = PS_ARGS;
1027 if ((toys.optflags&FLAG_n) && alluc>=PS_UID && alluc<=PS_RGROUP
1028 && (typos[alluc].slot&64)) alluc--;
1029 if (alluc != ever->which) {
1030 TT.bits &= 1LL<<ever->which;
1031 TT.bits |= 1LL<<(ever->which = alluc);
1036 if (!(toys.optflags&FLAG_k)) TT.show_process = (void *)show_ps;
1037 TT.match_process = ps_match_process;
1038 dt = dirtree_read("/proc", get_ps);
1040 if (toys.optflags&FLAG_k) {
1041 struct carveup **tbsort = collate(TT.kcount, dt, ksort);
1043 qsort(tbsort, TT.kcount, sizeof(struct carveup *), (void *)ksort);
1044 for (i = 0; i<TT.kcount; i++) {
1048 if (CFG_TOYBOX_FREE) free(tbsort);
1051 if (CFG_TOYBOX_FREE) {
1060 llist_traverse(TT.fields, free);
1066 #include "generated/flags.h"
1068 void ttop_main(void)
1070 printf("hello world\n");
1073 #define CLEANUP_ttop
1075 #include "generated/flags.h"
1077 // select which of the -o fields to sort by
1078 static void setsort(int pos)
1080 struct strawberry *field, *going2;
1085 for (field = TT.fields; field; field = field->next) {
1086 if ((TT.sortpos = i++)<pos) continue;
1087 going2 = TT.kfields;
1088 going2->which = field->which;
1089 going2->len = field->len;
1094 void iotop_main(void)
1097 long long timeout = 0, now;
1099 struct carveup **tb;
1101 } plist[2], *plold, *plnew, old, new, mix;
1103 char *d = "D"+!!(toys.optflags&FLAG_A), *header, scratch[16],
1104 deltas[] = {11,28,29,44,50,51,52,53,54};
1106 int i, lines, done = 0;
1108 if (!TT.iotop.d) TT.iotop.d = 3;
1110 if (toys.optflags&FLAG_k) TT.forcek++;
1111 if (toys.optflags&FLAG_b) TT.width = TT.height = 99999;
1113 xset_terminal(0, 1, 0);
1114 sigatexit(tty_sigreset);
1118 // TODO: usage: iotop [-oq]
1120 comma_args(TT.iotop.u, &TT.uu, "bad -u", parse_rest);
1121 comma_args(TT.iotop.p, &TT.pp, "bad -p", parse_rest);
1124 al.arg = xmprintf("PID,PR,USER,%sREAD,%sWRITE,SWAP,%sIO,COMM", d, d, d);
1125 comma_args(&al, &TT.fields, 0, parse_ko);
1127 dlist_terminate(TT.fields);
1128 header = strdup(toybuf);
1130 // Fallback sorts. First (dummy) field gets overwritten by setsort()
1131 al.arg = xmprintf("-S,-%sIO,-ETIME,-PID",d);
1132 comma_args(&al, &TT.kfields, 0, parse_ko);
1134 dlist_terminate(TT.kfields);
1137 TT.match_process = shared_match_process;
1138 memset(plist, 0, sizeof(plist));
1140 struct dirtree *dt = dirtree_read("/proc", get_ps);
1142 plold = plist+(tock++&1);
1143 plnew = plist+(tock&1);
1144 plnew->tb = collate(plnew->count = TT.kcount, dt, ksort);
1147 // First time, wait a quarter of a second to collect a little delta data.
1153 // Collate old and new into "mix", depends on /proc read in pid sort order
1156 mix.tb = xmalloc((old.count+new.count)*sizeof(struct carveup));
1159 while (old.count || new.count) {
1160 struct carveup *otb = *old.tb, *ntb = *new.tb;
1162 // If we just have old, discard it.
1163 if (old.count && (!new.count || *otb->slot < *ntb->slot)) {
1170 // If we just have new, use it verbatim
1171 if (!old.count || *otb->slot > *ntb->slot) mix.tb[mix.count] = ntb;
1174 // If we have both, adjust slot[deltas[]] to be relative to previous
1175 // measurement rather than process start. Stomping old.data is fine
1176 // because we free it after displaying.
1177 if (!(toys.optflags&FLAG_a))
1178 for (i = 0; i<ARRAY_LEN(deltas); i++)
1179 otb->slot[deltas[i]] = ntb->slot[deltas[i]] - otb->slot[deltas[i]];
1180 if (!(toys.optflags&FLAG_o) || otb->slot[28+!(toys.optflags&FLAG_A)]) {
1181 mix.tb[mix.count] = otb;
1191 // Will will re-fetch no data before its time. - Mork calling Orson Welles
1195 qsort(mix.tb, mix.count, sizeof(struct carveup *), (void *)ksort);
1196 if (!(toys.optflags&FLAG_b)) printf("\033[H\033[J");
1197 if (!(toys.optflags&FLAG_q)) {
1199 strcpy(pos = toybuf, header);
1200 for (i=0, is = *pos; *pos; pos++) {
1203 if (isspace(was) && !isspace(is) && i++==TT.sortpos) pos[-1] = '[';
1204 if (!isspace(was) && isspace(is) && i==TT.sortpos+1) *pos = ']';
1206 printf("\033[7m%s\033[0m\n\r", toybuf);
1208 if (!(toys.optflags&FLAG_b))
1209 terminal_probesize(&TT.width, &TT.height);
1211 lines = TT.height-2;
1213 for (i=0; i<lines && i<mix.count; i++) {
1218 if (TT.iotop.n && !--TT.iotop.n) {
1223 // Get current time in miliseconds
1224 clock_gettime(CLOCK_MONOTONIC, &ts);
1225 now = ts.tv_sec*1000+ts.tv_nsec/1000000;
1226 if (timeout<=now) timeout += TT.iotop.d;
1227 if (timeout<=now) timeout = now+TT.iotop.d;
1229 i = scan_key_getsize(scratch, timeout-now, &TT.width, &TT.height);
1230 if (i==-1 || i==3 || toupper(i)=='Q') {
1236 // Flush unknown escape sequences.
1238 while (0<scan_key_getsize(scratch, 0, &TT.width, &TT.height));
1243 if (i == KEY_LEFT) setsort(TT.sortpos-1);
1244 else if (i == KEY_RIGHT) setsort(TT.sortpos+1);
1250 for (i=0; i<plold->count; i++) free(plold->tb[i]);
1253 if (!(toys.optflags&FLAG_b)) tty_reset();
1256 #define CLEANUP_iotop
1258 #include "generated/flags.h"
1261 struct regex_list *next;
1265 static void show_pgrep(struct carveup *tb)
1268 struct regex_list *reg;
1269 char *name = tb->str;
1271 if (toys.optflags&FLAG_f) name += tb->offset[4];
1273 if (TT.pgrep.regexes) {
1274 for (reg = TT.pgrep.regexes; reg; reg = reg->next) {
1275 if (regexec(®->reg, name, 1, &match, 0)) continue;
1276 if (toys.optflags&FLAG_x)
1277 if (match.rm_so || match.rm_eo!=strlen(name)) continue;
1283 printf("%lld%s", *tb->slot, TT.pgrep.d ? TT.pgrep.d : "\n");
1286 void pgrep_main(void)
1289 struct regex_list *reg;
1291 comma_args(TT.pgrep.G, &TT.GG, "bad -G", parse_rest);
1292 comma_args(TT.pgrep.g, &TT.gg, "bad -g", parse_rest);
1293 comma_args(TT.pgrep.P, &TT.PP, "bad -P", parse_rest);
1294 comma_args(TT.pgrep.s, &TT.ss, "bad -s", parse_rest);
1295 comma_args(TT.pgrep.t, &TT.tt, "bad -t", parse_rest);
1296 comma_args(TT.pgrep.U, &TT.UU, "bad -U", parse_rest);
1297 comma_args(TT.pgrep.u, &TT.uu, "bad -u", parse_rest);
1299 if ((toys.optflags&(FLAG_x|FLAG_f)) ||
1300 !(toys.optflags&(FLAG_G|FLAG_g|FLAG_P|FLAG_s|FLAG_t|FLAG_U|FLAG_u)))
1301 if (!toys.optc) help_exit("No PATTERN");
1303 if (toys.optflags&FLAG_f) TT.bits |= _PS_CMDLINE;
1305 for (arg = toys.optargs; *arg; arg++) {
1306 reg = xmalloc(sizeof(struct regex_list));
1307 xregcomp(®->reg, *arg, REG_EXTENDED);
1308 reg->next = TT.pgrep.regexes;
1309 TT.pgrep.regexes = reg;
1311 TT.match_process = shared_match_process;
1312 TT.show_process = (void *)show_pgrep;
1314 dirtree_read("/proc", get_ps);
1315 if (TT.pgrep.d) xputc('\n');
1318 void pkill_main(void)
1320 if (!TT.pgrep.L) TT.pgrep.signal = SIGTERM;