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.
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;
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) {
850 slot = typos[field->which].slot;
851 // Compare as strings?
853 memccpy(toybuf, string_field(ta, field), 0, 2048);
855 ret = strcmp(toybuf, string_field(tb, field));
857 if (ta->slot[slot]<tb->slot[slot]) ret = -1;
858 if (ta->slot[slot]>tb->slot[slot]) ret = 1;
861 if (ret) return ret*field->reverse;
867 static struct carveup **collate(int count, struct dirtree *dt,
868 int (*sort)(void *a, void *b))
870 struct dirtree *temp;
871 struct carveup **tbsort = xmalloc(count*sizeof(struct carveup *));
874 // descend into child list
875 *tbsort = (void *)dt;
880 for (i = 0; i < count; i++) {
882 tbsort[i] = (void *)dt->extra;
891 static void shared_main(void)
895 TT.ticks = sysconf(_SC_CLK_TCK);
899 terminal_size(&TT.width, &TT.height);
902 // find controlling tty, falling back to /dev/tty if none
903 for (i = 0; !TT.tty && i<4; i++) {
907 if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break;
909 if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev;
919 if (toys.optflags&FLAG_w) TT.width = 99999;
922 // parse command line options other than -o
923 comma_args(TT.ps.P, &TT.PP, "bad -P", parse_rest);
924 comma_args(TT.ps.p, &TT.pp, "bad -p", parse_rest);
925 comma_args(TT.ps.t, &TT.tt, "bad -t", parse_rest);
926 comma_args(TT.ps.s, &TT.ss, "bad -s", parse_rest);
927 comma_args(TT.ps.u, &TT.uu, "bad -u", parse_rest);
928 comma_args(TT.ps.U, &TT.UU, "bad -U", parse_rest);
929 comma_args(TT.ps.g, &TT.gg, "bad -g", parse_rest);
930 comma_args(TT.ps.G, &TT.GG, "bad -G", parse_rest);
931 comma_args(TT.ps.k, &TT.kfields, "bad -k", parse_ko);
932 dlist_terminate(TT.kfields);
934 // Parse manual field selection, or default/-f/-l, plus -Z,
935 // constructing the header line in toybuf as we go.
936 if (toys.optflags&FLAG_Z) {
937 struct arg_list Z = { 0, "LABEL" };
939 comma_args(&Z, &TT.fields, "-Z", parse_ko);
941 if (TT.ps.o) comma_args(TT.ps.o, &TT.fields, "bad -o field", parse_ko);
946 if (toys.optflags&FLAG_f)
947 al.arg = "USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD";
948 else if (toys.optflags&FLAG_l)
949 al.arg = "F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD";
950 else if (CFG_TOYBOX_ON_ANDROID)
951 al.arg = "USER,PID,PPID,VSIZE,RSS,WCHAN:10,ADDR:10=PC,S,CMDLINE";
952 else al.arg = "PID,TTY,TIME,CMD";
954 comma_args(&al, &TT.fields, 0, parse_ko);
956 dlist_terminate(TT.fields);
957 printf("%s\n", toybuf);
959 // misunderstand fields the flags say to
960 if (toys.optflags&(FLAG_f|FLAG_n)) {
961 struct strawberry *ever;
963 for (ever = TT.fields; ever; ever = ever->next) {
964 int alluc = ever->which;
966 if ((toys.optflags&FLAG_f) && alluc==PS_CMD) alluc = PS_ARGS;
967 if ((toys.optflags&FLAG_n) && alluc>=PS_UID && alluc<=PS_RGROUP
968 && (typos[alluc].slot&64)) alluc--;
969 if (alluc != ever->which) {
970 TT.bits &= 1LL<<ever->which;
971 TT.bits |= 1LL<<(ever->which = alluc);
976 TT.ksave = DIRTREE_SAVE*!!(toys.optflags&FLAG_k);
977 TT.match_process = ps_match_process;
978 dt = dirtree_read("/proc", get_ps);
980 if (toys.optflags&FLAG_k) {
981 struct carveup **tbsort = collate(TT.kcount, dt, ksort);
983 qsort(tbsort, TT.kcount, sizeof(struct carveup *), (void *)ksort);
984 for (i = 0; i<TT.kcount; i++) {
988 if (CFG_TOYBOX_FREE) free(tbsort);
991 if (CFG_TOYBOX_FREE) {
1000 llist_traverse(TT.fields, free);
1006 #include "generated/flags.h"
1008 void ttop_main(void)
1010 printf("hello world\n");
1013 #define CLEANUP_ttop
1015 #include "generated/flags.h"
1017 void iotop_main(void)
1020 struct carveup **tb;
1022 } plist[2], *plold, *plnew, old, new, mix;
1024 char *d = "D"+!!(toys.optflags&FLAG_A), *header,
1025 deltas[] = {11,28,29,44,50,51,52,53,54};
1029 if (!TT.iotop.d) TT.iotop.d = 3;
1030 if (toys.optflags&FLAG_k) TT.forcek++;
1031 if (toys.optflags&FLAG_b) TT.width = TT.height = 99999;
1034 // TODO: usage: iotop [-oq]
1036 comma_args(TT.iotop.u, &TT.uu, "bad -u", parse_rest);
1037 comma_args(TT.iotop.p, &TT.pp, "bad -p", parse_rest);
1040 al.arg = xmprintf("PID,PR,USER,%sREAD,%sWRITE,SWAP,%sIO,COMM", d, d, d);
1041 comma_args(&al, &TT.fields, 0, parse_ko);
1043 dlist_terminate(TT.fields);
1044 header = strdup(toybuf);
1046 al.arg = xmprintf("-%sIO,-%sREAD,-%sWRITE,-SWAP,-ETIME,-PID",d,d,d);
1047 comma_args(&al, &TT.kfields, 0, parse_ko);
1049 dlist_terminate(TT.kfields);
1051 TT.ksave = DIRTREE_SAVE;
1052 TT.match_process = shared_match_process;
1053 memset(plist, 0, sizeof(plist));
1055 struct dirtree *dt = dirtree_read("/proc", get_ps);
1057 plold = plist+(tock++&1);
1058 plnew = plist+(tock&1);
1059 plnew->tb = collate(plnew->count = TT.kcount, dt, ksort);
1062 // First time, wait a quarter of a second to collect a little delta data.
1068 // Collate old and new into "mix", depends on /proc read in pid sort order
1071 mix.tb = xmalloc((old.count+new.count)*sizeof(struct carveup));
1074 while (old.count || new.count) {
1075 struct carveup *otb = *old.tb, *ntb = *new.tb;
1077 // If we just have old, discard it.
1078 if (old.count && (!new.count || *otb->slot < *ntb->slot)) {
1085 // If we just have new, use it verbatim
1086 if (!old.count || *otb->slot > *ntb->slot) mix.tb[mix.count] = ntb;
1089 // If we have both, adjust deltas. Stomping old data is fine because
1090 // we free it after displaying.
1091 if (!(toys.optflags&FLAG_a))
1092 for (i = 0; i<ARRAY_LEN(deltas); i++)
1093 otb->slot[deltas[i]] = ntb->slot[deltas[i]] - otb->slot[deltas[i]];
1094 if (!(toys.optflags&FLAG_o) || otb->slot[28+!(toys.optflags&FLAG_A)]) {
1095 mix.tb[mix.count] = otb;
1105 qsort(mix.tb, mix.count, sizeof(struct carveup *), (void *)ksort);
1106 if (!(toys.optflags&FLAG_q))
1107 printf("%s%s\n", (toys.optflags&FLAG_b) ? "" : "\033[H\033[J", header);
1108 lines = TT.height-2;
1110 for (i=0; i<lines && i<mix.count; i++) show_ps(mix.tb[i]);
1112 for (i=0; i<plold->count; i++) free(plold->tb[i]);
1115 if (TT.iotop.n) if (!--TT.iotop.n) break;
1116 msleep(1000*TT.iotop.d);