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: TID PRIO USER DISK_READ DISK_WRITE SWAPIN IO% COMMAND
36 * DISK_READ in B/s, K/s..., length 11
37 * Total DISK READ: | Total DISK WRITE:
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))
47 usage: ps [-AadeflnwZ] [-gG GROUP,] [-k FIELD,] [-o FIELD,] [-p PID,] [-t TTY,] [-uU USER,]
51 Which processes to show (selections may be comma separated lists):
54 -a Processes with terminals that aren't session leaders
55 -d All processes that aren't session leaders
57 -g Belonging to GROUPs
58 -G Belonging to real GROUPs (before sgid)
60 -P Parent PIDs (--ppid)
62 -t Attached to selected TTYs
64 -U Owned by real USERs (before suid)
68 -k Sort FIELDs in +increasing or -decreasting order (--sort)
69 -n Show numeric USER and GROUP
70 -w Wide output (don't truncate at terminal width)
72 Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD)
74 -f Full listing (-o USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD)
75 -l Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD)
76 -o Output the listed FIELDs, each with optional :size and/or =title
81 ADDR Instruction pointer
82 ARGS Command line (argv[0-X] minus path)
83 CMD COMM without -f, ARGS with -f
84 CMDLINE Command line (argv[0-X])
85 COMM Original command name
86 COMMAND Original command path
87 CPU Which processor running on
88 ETIME Elapsed time since process start
89 F Flags (1=FORKNOEXEC, 4=SUPERPRIV)
93 MAJFL Major page faults
94 MINFL Minor page faults
95 NAME Command name (argv[0])
96 NI Niceness (lower is faster)
97 PCPU Percentage of CPU time used
100 PPID Parent Process ID
101 PRI Priority (higher is faster)
102 PSR Processor last executed on
103 RGID Real (before sgid) group ID
104 RGROUP Real (before sgid) group name
105 RSS Resident Set Size (memory in use, 4k pages)
106 RTPRIO Realtime priority
107 RUID Real (before suid) user ID
108 RUSER Real (before suid) user name
110 R (running) S (sleeping) D (device I/O) T (stopped) t (traced)
111 Z (zombie) X (deader) x (dead) K (wakekill) W (waking)
112 SCHED Scheduling policy (0=other, 1=fifo, 2=rr, 3=batch, 4=iso, 5=idle)
113 STAT Process state (S) plus:
114 < high priority N low priority L locked memory
115 s session leader + foreground l multithreaded
116 STIME Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss)
117 SZ Memory Size (4k pages needed to completely swap out process)
118 TIME CPU time consumed
119 TTY Controlling terminal
122 VSZ Virtual memory size (1k units)
123 WCHAN Waiting in kernel for
125 You can put a % after SZ, RSS
132 usage: ttop [-mb] [ -d seconds ] [ -n iterations ]
136 Provide a view of process activity in real time.
138 N/M/P/T show CPU usage, sort by pid/mem/cpu/time
146 -n Iterations before exiting
147 -d Delay between updates
151 # Requires CONFIG_IRQ_TIME_ACCOUNTING in the kernel for /proc/$$/io
156 usage: iotop [-Aabkoq] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,]
158 Rank processes by I/O. Cursor left/right to change sort, Q to exit.
160 -A All I/O, not just disk
161 -a Accumulated I/O (not percentage)
162 -b Batch mode (no tty)
163 -d Delay SECONDS between each cycle (default 3)
165 -n Exit after NUMBER iterations
166 -o Only show processes doing I/O
168 -q Quiet (no header lines)
202 struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU;
203 unsigned width, height;
205 void *fields, *kfields;
206 long long ticks, bits, ioread, iowrite, aioread, aiowrite;
208 int kcount, ksave, forcek, sortpos;
209 int (*match_process)(long long *slot);
213 struct strawberry *next, *prev;
214 short which, len, reverse;
219 /* The slot[] array is mostly populated from /proc/$PID/stat (kernel proc.txt
220 * table 1-4) but we shift and repurpose fields, with the result being:
222 * 0 pid process id 1 ppid parent process id
223 * 2 pgrp process group 3 sid session id
224 * 4 tty_nr tty the process uses 5 tty_pgrp pgrp of the tty
225 * 6 flags task flags 7 min_flt minor faults
226 * 8 cmin_flt minor faults+child 9 maj_flt major faults
227 * 10 cmaj_flt major faults+child 11 utime user+kernel jiffies
228 * 12 stime kernel mode jiffies 13 cutime user jiffies+child
229 * 14 cstime kernel mode jiffies+child 15 priority priority level
230 * 16 nice nice level 17 num_threads number of threads
231 * 18 vmlck locked memory 19 start_time jiffies after boot
232 * 20 vsize virtual memory size 21 rss resident set size
233 * 22 rsslim limit in bytes on rss 23 start_code code segment addr
234 * 24 end_code code segment address 25 start_stack stack address
235 * 26 esp current value of ESP 27 eip current value of EIP
236 * 28 iobytes All I/O bytes 29 diobytes disk I/O bytes
237 * 30 sigign bitmap of ignored signals 31 uid user id
238 * 32 ruid real user id 33 gid group id
239 * 34 rgid real group id 35 exit_signal sent to parent thread
240 * 36 task_cpu CPU task is scheduled on 37 rt_priority realtime priority
241 * 38 policy man sched_setscheduler 39 blkio_ticks spent wait block IO
242 * 40 gtime guest jiffies of task 41 cgtime guest jiff of child
243 * 42 start_data program data+bss address 43 end_data program data+bss
244 * 44 upticks 46-19 (divisor for %) 45 argv0len argv[0] length
245 * 46 uptime sysinfo.uptime @read time 47 vsz Virtual Size
246 * 48 rss Resident Set Size 49 shr Shared memory
247 * 50 rchar All bytes read 51 wchar All bytes written
248 * 52 rbytes Disk bytes read 53 rbytes Disk bytes written
249 * 54 swap Swap pages used
252 // Data layout in toybuf
254 long long slot[55]; // data from /proc
255 unsigned short offset[5]; // offset of fields in str[] (skip name, always 0)
257 char str[]; // name, tty, command, wchan, attr, cmdline
260 // TODO: Android uses -30 for LABEL, but ideally it would auto-size.
261 // 64|slot means compare as string when sorting
264 signed char width, slot;
265 } static const typos[] = TAGGED_ARRAY(PS,
266 // stat#s: PID PPID PRI NI ADDR SZ RSS PGID VSZ MAJFL MINFL PR PSR RTPRIO
268 {"PID", 5, 0}, {"PPID", 5, 1}, {"PRI", 3, 15}, {"NI", 3, 16},
269 {"ADDR", 4+sizeof(long), 27}, {"SZ", 5, 20}, {"RSS", 5, 21}, {"PGID", 5, 2},
270 {"VSZ", 6, 20}, {"MAJFL", 6, 9}, {"MINFL", 6, 7}, {"PR", 2, 15},
271 {"PSR", 3, 36}, {"RTPRIO", 6, 37}, {"SCH", 3, 38},
273 // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP
274 {"UID", 5, 31}, {"USER", -8, 64|31}, {"RUID", 4, 32}, {"RUSER", -8, 64|32},
275 {"GID", 8, 33}, {"GROUP", -8, 64|33}, {"RGID", 4, 34}, {"RGROUP", -8, 64|34},
277 // CMD TTY WCHAN LABEL CMDLINE COMMAND
278 {"COMM", -15, -1}, {"TTY", -8, -2}, {"WCHAN", -6, -3}, {"LABEL", -30, -4},
279 {"COMMAND", -27, -5}, {"CMDLINE", -27, -6}, {"ARGS", -27, -6},
280 {"NAME", -15, -6}, {"CMD", -27, -1},
282 // TIME ELAPSED TIME+
283 {"TIME", 8, 11}, {"ELAPSED", 11, 19}, {"TIME+", 9, 11},
285 // Remaining ungrouped
286 {"STIME", 5, 19}, {"F", 1, 64|6}, {"S", -1, 64}, {"C", 1, 0}, {"%CPU", 4, 64},
287 {"STAT", -5, 64}, {"%VSZ", 5, 23}, {"VIRT", 4, 47}, {"RES", 4, 48},
288 {"SHR", 4, 49}, {"READ", 6, 50}, {"WRITE", 6, 51}, {"IO", 6, 28},
289 {"DREAD", 6, 52}, {"DWRITE", 6, 53}, {"SWAP", 6, 54}, {"DIO", 6, 29}
292 // Return 0 to discard, nonzero to keep
293 static int shared_match_process(long long *slot)
295 struct ptr_len match[] = {
296 {&TT.gg, 33}, {&TT.GG, 34}, {&TT.pp, 0}, {&TT.PP, 1}, {&TT.ss, 3},
297 {&TT.tt, 4}, {&TT.uu, 31}, {&TT.UU, 32}
302 // Do we have -g -G -p -P -s -t -u -U options selecting processes?
303 for (i = 0; i < ARRAY_LEN(match); i++) {
304 struct ptr_len *mm = match[i].ptr;
307 for (j = 0; j<mm->len; j++) if (ll[j] == slot[match[i].len]) return 1;
315 // Return 0 to discard, nonzero to keep
316 static int ps_match_process(long long *slot)
318 int i = shared_match_process(slot);
321 // If we had selections and didn't match them, don't display
324 // Filter implicit categories for other display types
325 if ((toys.optflags&(FLAG_a|FLAG_d)) && slot[3]==*slot) return 0;
326 if ((toys.optflags&FLAG_a) && !slot[4]) return 0;
327 if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e)) && TT.tty!=slot[4])
333 // Convert field to string representation
334 static char *string_field(struct carveup *tb, struct strawberry *field)
336 char *buf = toybuf+sizeof(toybuf)-260, *out = buf, *s;
337 int which = field->which, sl = typos[which].slot;
338 long long *slot = tb->slot, ll = (sl >= 0) ? slot[sl&63] : 0;
340 // Default: unsupported (5 "C")
343 // stat#s: PID PPID PRI NI ADDR SZ RSS PGID VSZ MAJFL MINFL PR PSR RTPRIO SCH
344 if (which <= PS_SCH) {
347 if (which==PS_PRI) ll = 39-ll;
348 if (which==PS_ADDR) fmt = "%llx";
349 else if (which==PS_SZ) ll >>= 12;
350 else if (which==PS_RSS) ll <<= 2;
351 else if (which==PS_VSZ) ll >>= 10;
352 else if (which==PS_PR && ll<-9) fmt="RT";
353 else if (which==PS_RTPRIO && ll == 0) fmt="-";
354 sprintf(out, fmt, ll);
356 // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP
357 } else if (which <= PS_RGROUP) {
358 sprintf(out, "%lld", ll);
360 if (which > PS_RUSER) {
361 struct group *gr = getgrgid(ll);
363 if (gr) out = gr->gr_name;
365 struct passwd *pw = getpwuid(ll);
367 if (pw) out = pw->pw_name;
371 // COMM TTY WCHAN LABEL COMMAND CMDLINE ARGS NAME CMD
374 tb->str[tb->offset[4]+slot[45]] = (which == PS_NAME) ? 0 : ' ';
377 if (--sl) out += tb->offset[--sl];
379 for (s = out; *s && *s != ' '; s++) if (*s == '/') out = s+1;
380 if (which>=PS_COMMAND && !*out) sprintf(out = buf, "[%s]", tb->str);
381 // TIME ELAPSED TIME+
382 } else if (which <= PS_TIME_) {
383 int unit = 60, pad = 2, j = TT.ticks;
386 if (which!=PS_TIME_) unit *= 60*24;
388 // top adjusts slot[44], we want original meaning.
389 if (which==PS_ELAPSED) ll = (slot[46]*j)-slot[19];
392 // Output days-hours:mins:secs, skipping non-required fields with zero
393 // TIME has 3 required fields, ETIME has 2. (Posix!) TIME+ is from top
394 for (s = 0, j = 2*(which==PS_TIME_); j<4; j++) {
395 if (!s && (seconds>unit || j == 1+(which!=PS_TIME))) s = out;
397 s += sprintf(s, j ? "%0*ld": "%*ld", pad, (long)(seconds/unit));
399 if ((*s = "-::"[j])) s++;
404 if (which==PS_TIME_ && s-out<8)
405 sprintf(s, ".%02lld", (100*(ll%TT.ticks))/TT.ticks);
407 // Posix doesn't specify what flags should say. Man page says
408 // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h
409 } else if (which==PS_F) sprintf(out, "%llo", (slot[6]>>6)&5);
410 else if (which==PS_S || which==PS_STAT) {
413 if (which==PS_STAT) {
414 // TODO l = multithreaded
415 if (slot[16]<0) *s++ = '<';
416 else if (slot[16]>0) *s++ = 'N';
417 if (slot[3]==*slot) *s++ = 's';
418 if (slot[18]) *s++ = 'L';
419 if (slot[5]==*slot) *s++ = '+';
422 } else if (which==PS_STIME) {
423 time_t t = time(0)-slot[46]+slot[19]/TT.ticks;
425 // Padding behavior's a bit odd: default field size is just hh:mm.
426 // Increasing stime:size reveals more data at left until full,
427 // so move start address so yyyy-mm-dd hh:mm revealed on left at :16,
428 // then add :ss on right for :19.
429 strftime(out, 260, "%F %T", localtime(&t));
430 out = out+strlen(out)-3-abs(field->len);
431 if (out<buf) out = buf;
432 } else if (which==PS__CPU || which==PS__VSZ) {
433 if (which==PS__CPU) sl = (slot[11]*1000)/slot[44];
434 else sl = (slot[23]*1000)/TT.si.totalram;
435 sprintf(out, "%d.%d", sl/10, sl%10);
436 } else if (which>=PS_VIRT && which <= PS_DIO) {
437 ll = slot[typos[which].slot];
438 if (which <= PS_SHR) ll *= sysconf(_SC_PAGESIZE);
439 if (TT.forcek) sprintf(out, "%lldk", ll/1024);
440 else human_readable(out, ll, 0);
446 // Display process data that get_ps() read from /proc, formatting with TT.fields
447 static void show_ps(struct carveup *tb)
449 struct strawberry *field;
450 int i, len, width = TT.width;
452 // Loop through fields to display
453 for (field = TT.fields; field; field = field->next) {
454 char *out = string_field(tb, field);
456 // Output the field, appropriately padded
457 len = width - (field != TT.fields);
458 if (!field->next && field->len<0) i = 0;
460 i = len<abs(field->len) ? len : field->len;
464 // TODO test utf8 fontmetrics
465 width -= printf(" %*.*s" + (field == TT.fields), i, len, out);
471 // dirtree callback: read data about process to display, store, or discard it.
472 // Fills toybuf with struct carveup and either DIRTREE_SAVEs a copy to ->extra
473 // (in -k mode) or calls show_ps on toybuf (no malloc/copy/free there).
474 static int get_ps(struct dirtree *new)
480 {"fd/", _PS_TTY}, {"wchan", _PS_WCHAN}, {"attr/current", _PS_LABEL},
481 {"exe", _PS_COMMAND}, {"cmdline", _PS_CMDLINE|_PS_ARGS|_PS_NAME}
483 struct carveup *tb = (void *)toybuf;
484 long long *slot = tb->slot;
485 char *name, *s, *buf = tb->str, *end = 0;
489 // Recurse one level into /proc children, skip non-numeric entries
490 if (!new->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP|TT.ksave;
492 memset(slot, 0, sizeof(tb->slot));
493 if (!(*slot = atol(new->name))) return 0;
494 fd = dirtree_parentfd(new);
497 sprintf(buf, "%lld/stat", *slot);
498 if (!readfileat(fd, buf, buf, &len)) return 0;
500 // parse oddball fields (name and state). Name can have embedded ')' so match
501 // _last_ ')' in stat (although VFS limits filenames to 255 bytes max).
502 // All remaining fields should be numeric.
503 if (!(name = strchr(buf, '('))) return 0;
504 for (s = ++name; *s; s++) if (*s == ')') end = s;
505 if (!end || end-name>255) return 0;
507 // Parse numeric fields (starting at 4th field in slot[1])
508 if (1>sscanf(s = end, ") %c%n", &tb->state, &i)) return 0;
509 for (j = 1; j<50; j++) if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break;
511 // Now we've read the data, move status and name right after slot[] array,
512 // and convert low chars to ? while we're at it.
513 for (i = 0; i<end-name; i++)
514 if ((tb->str[i] = name[i]) < ' ') tb->str[i] = '?';
517 len = sizeof(toybuf)-(buf-toybuf);
519 // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch
520 // or numeric wchan, and the remaining two are always zero), and vmlck into
521 // 18 (which is "obsolete, always 0" from stat)
522 slot[31] = new->st.st_uid;
523 slot[33] = new->st.st_gid;
525 // TIME and TIME+ use combined value, ksort needs 'em added.
526 slot[11] += slot[12];
528 // If RGROUP RUSER STAT RUID RGID SWAP happening, or -G or -U, parse "status"
529 // and save ruid, rgid, and vmlck.
530 if ((TT.bits&(_PS_RGROUP|_PS_RUSER|_PS_STAT|_PS_RUID|_PS_RGID|_PS_SWAP
531 |_PS_IO|_PS_DIO)) || TT.GG.len || TT.UU.len)
535 sprintf(buf, "%lld/status", *slot);
536 if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
537 s = strafter(buf, "\nUid:");
538 slot[32] = s ? atol(s) : new->st.st_uid;
539 s = strafter(buf, "\nGid:");
540 slot[34] = s ? atol(s) : new->st.st_gid;
541 if ((s = strafter(buf, "\nVmLck:"))) slot[18] = atoll(s);
542 if ((s = strafter(buf, "\nVmSwap:"))) slot[54] = atoll(s);
545 // Do we need to read "io"?
546 if (TT.bits&(_PS_READ|_PS_WRITE|_PS_DREAD|_PS_DWRITE|_PS_IO|_PS_DIO)) {
549 sprintf(buf, "%lld/io", *slot);
550 if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
551 if ((s = strafter(buf, "rchar:"))) slot[50] = atoll(s);
552 if ((s = strafter(buf, "wchar:"))) slot[51] = atoll(s);
553 if ((s = strafter(buf, "read_bytes:"))) slot[52] = atoll(s);
554 if ((s = strafter(buf, "write_bytes:"))) slot[53] = atoll(s);
555 slot[28] = slot[50]+slot[51]+slot[54];
556 slot[29] = slot[52]+slot[53]+slot[54];
559 // We now know enough to skip processes we don't care about.
560 if (TT.match_process && !TT.match_process(slot)) return 0;
562 // /proc data is generated as it's read, so for maximum accuracy on slow
563 // systems (or ps | more) we re-fetch uptime as we fetch each /proc line.
565 slot[44] = ((slot[46] = TT.si.uptime)*TT.ticks) - slot[19];
567 // Do we need to read "statm"?
568 if (TT.bits&(_PS_VIRT|_PS_RES|_PS_SHR)) {
571 sprintf(buf, "%lld/statm", *slot);
572 if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
574 for (s = buf, i=0; i<3; i++)
575 if (!sscanf(s, " %lld%n", slot+47+i, &j)) slot[47+i] = 0;
579 // Fetch string data while parentfd still available, appending to buf.
580 // (There's well over 3k of toybuf left. We could dynamically malloc, but
581 // it'd almost never get used, querying length of a proc file is awkward,
582 // fixed buffer is nommu friendly... Wait for somebody to complain. :)
584 for (j = 0; j<ARRAY_LEN(fetch); j++) {
585 tb->offset[j] = buf-(tb->str);
586 if (!(TT.bits&fetch[j].bits)) {
591 // Determine remaining space, reserving minimum of 256 bytes/field and
592 // 260 bytes scratch space at the end (for output conversion later).
593 len = sizeof(toybuf)-(buf-toybuf)-260-256*(ARRAY_LEN(fetch)-j);
594 sprintf(buf, "%lld/%s", *slot, fetch[j].name);
596 // For cmdline we readlink instead of read contents
598 if ((len = readlinkat(fd, buf, buf, len))>0) buf[len] = 0;
601 // If it's not the TTY field, data we want is in a file.
602 // Last length saved in slot[] is command line (which has embedded NULs)
607 // Call no tty "?" rather than "0:0".
610 // Can we readlink() our way to a name?
611 for (i = 0; i<3; i++) {
612 sprintf(buf, "%lld/fd/%i", *slot, i);
613 if (!fstatat(fd, buf, &st, 0) && S_ISCHR(st.st_mode)
614 && st.st_rdev == rdev && 0<(len = readlinkat(fd, buf, buf, len)))
621 // Couldn't find it, try all the tty drivers.
623 FILE *fp = fopen("/proc/tty/drivers", "r");
624 int tty_major = 0, maj = major(rdev), min = minor(rdev);
627 while (fscanf(fp, "%*s %256s %d %*s %*s", buf, &tty_major) == 2) {
628 // TODO: we could parse the minor range too.
629 if (tty_major == maj) {
630 sprintf(buf+strlen(buf), "%d", min);
631 if (!stat(buf, &st) && S_ISCHR(st.st_mode) && st.st_rdev==rdev)
639 // Really couldn't find it, so just show major:minor.
640 if (!tty_major) sprintf(buf, "%d:%d", maj, min);
644 if (strstart(&s, "/dev/")) memmove(buf, s, strlen(s)+1);
647 // Data we want is in a file.
648 // Last length saved in slot[] is command line (which has embedded NULs)
651 // When command has no arguments, don't space over the NUL
652 if (readfileat(fd, buf, buf, &len) && len>0) {
655 if (buf[len-1]=='\n') buf[--len] = 0;
657 // Turn NUL to space, other low ascii to ?
658 for (i=0; i<len; i++) {
664 } else if (c<' ') c = '?';
667 len = temp; // position of _first_ NUL
668 } else *buf = len = 0;
669 // Store end of argv[0] so NAME and CMDLINE can differ.
673 buf += strlen(buf)+1;
676 // If we need to sort the output, add it to the list and return.
678 s = xmalloc(buf-toybuf);
679 new->extra = (long)s;
680 memcpy(s, toybuf, buf-toybuf);
683 // Otherwise display it now
689 static char *parse_ko(void *data, char *type, int length)
691 struct strawberry *field;
692 char *width, *title, *end, *s;
695 // Get title, length of title, type, end of type, and display width
697 // Chip off =name to display
698 if ((end = strchr(type, '=')) && length>(end-type)) {
700 length -= (end-type)+1;
706 // Chip off :width to display
707 if ((width = strchr(type, ':')) && width<end) {
708 if (!title) length = width-type;
711 // Allocate structure, copy title
712 field = xzalloc(sizeof(struct strawberry)+(length+1)*!!title);
714 memcpy(field->title = field->forever, title, length);
715 field->title[field->len = length] = 0;
719 field->len = strtol(++width, &title, 10);
720 if (!isdigit(*width) || title != end) return title;
725 if (*(struct strawberry **)data == TT.kfields) {
727 if (*type == '-') field->reverse = -1;
728 else if (*type != '+') type--;
731 for (i = 0; i<ARRAY_LEN(typos); i++) {
733 for (j = 0; j<2; j++) {
734 if (!j) s = typos[i].name;
735 // posix requires alternate names for some fields
736 else if (-1==(k = stridx((char []){PS_NI, PS_SCH, PS_ELAPSED, PS__CPU,
737 PS_VSZ, PS_USER, 0}, i))) continue;
739 s = ((char *[]){"NICE", "SCHED", "ETIME", "PCPU", "VSIZE", "UNAME"})[k];
741 if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break;
745 if (i==ARRAY_LEN(typos)) return type;
746 if (!field->title) field->title = typos[field->which].name;
747 if (!field->len) field->len = typos[field->which].width;
748 else if (typos[field->which].width<0) field->len *= -1;
749 dlist_add_nomalloc(data, (void *)field);
751 // Print padded header for -o.
752 if (*(struct strawberry **)data == TT.fields) {
754 snprintf(toybuf + TT.header_len, sizeof(toybuf) - TT.header_len,
755 " %*s" + (field == TT.fields), field->len, field->title);
756 TT.bits |= 1LL<<field->which;
762 // Parse -p -s -t -u -U -g -G
763 static char *parse_rest(void *data, char *str, int len)
765 struct ptr_len *pl = (struct ptr_len *)data;
771 // gg, GG, pp, ss, tt, uu, UU, *parsing;
773 // Allocate next chunk of data
775 ll = pl->ptr = xrealloc(pl->ptr, sizeof(long)*(pl->len+16));
777 // Parse numerical input
779 ll[pl->len] = xstrtol(str, &end, 10);
780 if (end==(len+str)) num++;
783 if (pl==&TT.pp || pl==&TT.ss) {
784 if (num && ll[pl->len]>0) {
789 } else if (pl==&TT.tt) {
790 // -t pts = 12,pts/12 tty = /dev/tty2,tty2,S0
792 if (strstart(&str, strcpy(toybuf, "/dev/"))) len -= 5;
793 if (strstart(&str, "pts/")) {
796 } else if (strstart(&str, "tty")) len -= 3;
798 if (len<256 && (!(end = strchr(str, '/')) || end-str>len)) {
801 end = toybuf + sprintf(toybuf, "/dev/%s", num ? "pts/" : "tty");
802 memcpy(end, str, len);
805 ll[pl->len++] = st.st_rdev;
809 } else if (len<255) {
818 memcpy(name, str, len);
820 if (pl==&TT.gg || pl==&TT.GG) {
821 struct group *gr = getgrnam(name);
823 ll[pl->len++] = gr->gr_gid;
827 } else if (pl==&TT.uu || pl==&TT.UU) {
828 struct passwd *pw = getpwnam(name);
830 ll[pl->len++] = pw->pw_uid;
842 static int ksort(void *aa, void *bb)
844 struct strawberry *field;
845 struct carveup *ta = *(struct carveup **)aa, *tb = *(struct carveup **)bb;
848 for (field = TT.kfields; field; field = field->next) {
849 slot = typos[field->which].slot;
850 // Compare as strings?
852 memccpy(toybuf, string_field(ta, field), 0, 2048);
854 ret = strcmp(toybuf, string_field(tb, field));
856 if (ta->slot[slot]<tb->slot[slot]) ret = -1;
857 if (ta->slot[slot]>tb->slot[slot]) ret = 1;
860 if (ret) return ret*field->reverse;
866 static struct carveup **collate(int count, struct dirtree *dt,
867 int (*sort)(void *a, void *b))
869 struct dirtree *temp;
870 struct carveup **tbsort = xmalloc(count*sizeof(struct carveup *));
873 // descend into child list
874 *tbsort = (void *)dt;
879 for (i = 0; i < count; i++) {
881 tbsort[i] = (void *)dt->extra;
889 static void shared_main(void)
893 TT.ticks = sysconf(_SC_CLK_TCK);
897 terminal_size(&TT.width, &TT.height);
900 // find controlling tty, falling back to /dev/tty if none
901 for (i = 0; !TT.tty && i<4; i++) {
905 if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break;
907 if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev;
917 if (toys.optflags&FLAG_w) TT.width = 99999;
920 // parse command line options other than -o
921 comma_args(TT.ps.P, &TT.PP, "bad -P", parse_rest);
922 comma_args(TT.ps.p, &TT.pp, "bad -p", parse_rest);
923 comma_args(TT.ps.t, &TT.tt, "bad -t", parse_rest);
924 comma_args(TT.ps.s, &TT.ss, "bad -s", parse_rest);
925 comma_args(TT.ps.u, &TT.uu, "bad -u", parse_rest);
926 comma_args(TT.ps.U, &TT.UU, "bad -U", parse_rest);
927 comma_args(TT.ps.g, &TT.gg, "bad -g", parse_rest);
928 comma_args(TT.ps.G, &TT.GG, "bad -G", parse_rest);
929 comma_args(TT.ps.k, &TT.kfields, "bad -k", parse_ko);
930 dlist_terminate(TT.kfields);
932 // Parse manual field selection, or default/-f/-l, plus -Z,
933 // constructing the header line in toybuf as we go.
934 if (toys.optflags&FLAG_Z) {
935 struct arg_list Z = { 0, "LABEL" };
937 comma_args(&Z, &TT.fields, "-Z", parse_ko);
939 if (TT.ps.o) comma_args(TT.ps.o, &TT.fields, "bad -o field", parse_ko);
944 if (toys.optflags&FLAG_f)
945 al.arg = "USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD";
946 else if (toys.optflags&FLAG_l)
947 al.arg = "F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD";
948 else if (CFG_TOYBOX_ON_ANDROID)
949 al.arg = "USER,PID,PPID,VSIZE,RSS,WCHAN:10,ADDR:10=PC,S,CMDLINE";
950 else al.arg = "PID,TTY,TIME,CMD";
952 comma_args(&al, &TT.fields, 0, parse_ko);
954 dlist_terminate(TT.fields);
955 printf("%s\n", toybuf);
957 // misunderstand fields the flags say to
958 if (toys.optflags&(FLAG_f|FLAG_n)) {
959 struct strawberry *ever;
961 for (ever = TT.fields; ever; ever = ever->next) {
962 int alluc = ever->which;
964 if ((toys.optflags&FLAG_f) && alluc==PS_CMD) alluc = PS_ARGS;
965 if ((toys.optflags&FLAG_n) && alluc>=PS_UID && alluc<=PS_RGROUP
966 && (typos[alluc].slot&64)) alluc--;
967 if (alluc != ever->which) {
968 TT.bits &= 1LL<<ever->which;
969 TT.bits |= 1LL<<(ever->which = alluc);
974 TT.ksave = DIRTREE_SAVE*!!(toys.optflags&FLAG_k);
975 TT.match_process = ps_match_process;
976 dt = dirtree_read("/proc", get_ps);
978 if (toys.optflags&FLAG_k) {
979 struct carveup **tbsort = collate(TT.kcount, dt, ksort);
981 qsort(tbsort, TT.kcount, sizeof(struct carveup *), (void *)ksort);
982 for (i = 0; i<TT.kcount; i++) {
986 if (CFG_TOYBOX_FREE) free(tbsort);
989 if (CFG_TOYBOX_FREE) {
998 llist_traverse(TT.fields, free);
1004 #include "generated/flags.h"
1006 void ttop_main(void)
1008 printf("hello world\n");
1011 #define CLEANUP_ttop
1013 #include "generated/flags.h"
1015 // select which of the -o fields to sort by
1016 static void setsort(int pos)
1018 struct strawberry *field, *going2;
1023 for (field = TT.fields; field; field = field->next) {
1024 if ((TT.sortpos = i++)<pos) continue;
1025 going2 = TT.kfields;
1026 going2->which = field->which;
1027 going2->len = field->len;
1032 void iotop_main(void)
1035 long long timeout = 0, now;
1037 struct carveup **tb;
1039 } plist[2], *plold, *plnew, old, new, mix;
1041 char *d = "D"+!!(toys.optflags&FLAG_A), *header, scratch[16],
1042 deltas[] = {11,28,29,44,50,51,52,53,54};
1044 int i, lines, done = 0;
1046 if (!TT.iotop.d) TT.iotop.d = 3;
1048 if (toys.optflags&FLAG_k) TT.forcek++;
1049 if (toys.optflags&FLAG_b) TT.width = TT.height = 99999;
1051 xset_terminal(0, 1, 0);
1052 sigatexit(tty_sigreset);
1056 // TODO: usage: iotop [-oq]
1058 comma_args(TT.iotop.u, &TT.uu, "bad -u", parse_rest);
1059 comma_args(TT.iotop.p, &TT.pp, "bad -p", parse_rest);
1062 al.arg = xmprintf("PID,PR,USER,%sREAD,%sWRITE,SWAP,%sIO,COMM", d, d, d);
1063 comma_args(&al, &TT.fields, 0, parse_ko);
1065 dlist_terminate(TT.fields);
1066 header = strdup(toybuf);
1068 // Fallback sorts. First (dummy) field gets overwritten by setsort()
1069 al.arg = xmprintf("-S,-%sIO,-ETIME,-PID",d);
1070 comma_args(&al, &TT.kfields, 0, parse_ko);
1072 dlist_terminate(TT.kfields);
1075 TT.ksave = DIRTREE_SAVE;
1076 TT.match_process = shared_match_process;
1077 memset(plist, 0, sizeof(plist));
1079 struct dirtree *dt = dirtree_read("/proc", get_ps);
1081 plold = plist+(tock++&1);
1082 plnew = plist+(tock&1);
1083 plnew->tb = collate(plnew->count = TT.kcount, dt, ksort);
1086 // First time, wait a quarter of a second to collect a little delta data.
1092 // Collate old and new into "mix", depends on /proc read in pid sort order
1095 mix.tb = xmalloc((old.count+new.count)*sizeof(struct carveup));
1098 while (old.count || new.count) {
1099 struct carveup *otb = *old.tb, *ntb = *new.tb;
1101 // If we just have old, discard it.
1102 if (old.count && (!new.count || *otb->slot < *ntb->slot)) {
1109 // If we just have new, use it verbatim
1110 if (!old.count || *otb->slot > *ntb->slot) mix.tb[mix.count] = ntb;
1113 // If we have both, adjust slot[deltas[]] to be relative to previous
1114 // measurement rather than process start. Stomping old.data is fine
1115 // because we free it after displaying.
1116 if (!(toys.optflags&FLAG_a))
1117 for (i = 0; i<ARRAY_LEN(deltas); i++)
1118 otb->slot[deltas[i]] = ntb->slot[deltas[i]] - otb->slot[deltas[i]];
1119 if (!(toys.optflags&FLAG_o) || otb->slot[28+!(toys.optflags&FLAG_A)]) {
1120 mix.tb[mix.count] = otb;
1130 // Will will re-fetch no data before its time. - Mork calling Orson Welles
1134 qsort(mix.tb, mix.count, sizeof(struct carveup *), (void *)ksort);
1135 if (!(toys.optflags&FLAG_b)) printf("\033[H\033[J");
1136 if (!(toys.optflags&FLAG_q)) {
1138 strcpy(pos = toybuf, header);
1139 for (i=0, is = *pos; *pos; pos++) {
1142 if (isspace(was) && !isspace(is) && i++==TT.sortpos) pos[-1] = '[';
1143 if (!isspace(was) && isspace(is) && i==TT.sortpos+1) *pos = ']';
1145 printf("\033[7m%s\033[0m\n\r", toybuf);
1147 if (!(toys.optflags&FLAG_b))
1148 terminal_probesize(&TT.width, &TT.height);
1150 lines = TT.height-2;
1152 for (i=0; i<lines && i<mix.count; i++) {
1157 if (TT.iotop.n && !--TT.iotop.n) {
1162 // Get current time in miliseconds
1163 clock_gettime(CLOCK_MONOTONIC, &ts);
1164 now = ts.tv_sec*1000+ts.tv_nsec/1000000;
1165 if (timeout<=now) timeout += TT.iotop.d;
1166 if (timeout<=now) timeout = now+TT.iotop.d;
1168 i = scan_key_getsize(scratch, timeout-now, &TT.width, &TT.height);
1169 if (i==-1 || i==3 || toupper(i)=='Q') {
1175 // Flush unknown escape sequences.
1177 while (0<scan_key_getsize(scratch, 0, &TT.width, &TT.height));
1182 if (i == KEY_LEFT) setsort(TT.sortpos-1);
1183 else if (i == KEY_RIGHT) setsort(TT.sortpos+1);
1189 for (i=0; i<plold->count; i++) free(plold->tb[i]);
1192 if (!(toys.optflags&FLAG_b)) tty_reset();