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")
36 USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflno*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN))
37 USE_TTOP(NEWTOY(ttop, ">0d#=3n#<1mb", TOYFLAG_USR|TOYFLAG_BIN))
43 usage: ps [-AadeflnwZ] [-gG GROUP,] [-k FIELD,] [-o FIELD,] [-p PID,] [-t TTY,] [-uU USER,]
47 Which processes to show (selections may be comma separated lists):
50 -a Processes with terminals that aren't session leaders
51 -d All processes that aren't session leaders
53 -g Belonging to GROUPs
54 -G Belonging to real GROUPs (before sgid)
56 -P Parent PIDs (--ppid)
58 -t Attached to selected TTYs
60 -U Owned by real USERs (before suid)
64 -k Sort FIELDs in +increasing or -decreasting order (--sort)
65 -n Show numeric USER and GROUP
66 -w Wide output (don't truncate at terminal width)
68 Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD)
70 -f Full listing (-o USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD)
71 -l Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD)
72 -o Output the listed FIELDs, each with optional :size and/or =title
77 ADDR Instruction pointer
78 ARGS Command line (argv[0-X] minus path)
79 CMD COMM without -f, ARGS with -f
80 CMDLINE Command line (argv[0-X])
81 COMM Original command name
82 COMMAND Original command path
83 CPU Which processor running on
84 ETIME Elapsed time since process start
85 F Flags (1=FORKNOEXEC, 4=SUPERPRIV)
89 MAJFL Major page faults
90 MINFL Minor page faults
91 NAME Command name (argv[0])
92 NI Niceness (lower is faster)
93 PCPU Percentage of CPU time used
96 PPID Parent Process ID
97 PRI Priority (higher is faster)
98 PSR Processor last executed on
99 RGID Real (before sgid) group ID
100 RGROUP Real (before sgid) group name
101 RSS Resident Set Size (memory in use)
102 RTPRIO Realtime priority
103 RUID Real (before suid) user ID
104 RUSER Real (before suid) user name
106 R (running) S (sleeping) D (device I/O) T (stopped) t (traced)
107 Z (zombie) X (deader) x (dead) K (wakekill) W (waking)
108 SCHED Scheduling policy (0=other, 1=fifo, 2=rr, 3=batch, 4=iso, 5=idle)
109 STAT Process state (S) plus:
110 < high priority N low priority L locked memory
111 s session leader + foreground l multithreaded
112 STIME Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss)
113 SZ Memory Size (4k pages needed to completely swap out process)
114 TIME CPU time consumed
115 TTY Controlling terminal
118 VSZ Virtual memory size (1k units)
119 WCHAN Waiting in kernel for
126 usage: ttop [-mb] [ -d seconds ] [ -n iterations ]
128 Provide a view of process activity in real time.
130 N/M/P/T show CPU usage, sort by pid/mem/cpu/time
138 -n Iterations before exiting
139 -d Delay between updates
168 struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU;
171 void *fields, *kfields;
172 long long ticks, bits;
178 struct strawberry *next, *prev;
179 short which, len, reverse;
184 /* The slot[] array is mostly populated from /proc/$PID/stat (kernel proc.txt
185 * table 1-4) but we shift and repurpose fields, with the result being:
187 * 0 pid process id 1 ppid parent process id
188 * 2 pgrp process group 3 sid session id
189 * 4 tty_nr tty the process uses 5 tty_pgrp pgrp of the tty
190 * 6 flags task flags 7 min_flt minor faults
191 * 8 cmin_flt minor faults+child 9 maj_flt major faults
192 * 10 cmaj_flt major faults+child 11 utime user+kernel jiffies
193 * 12 stime kernel mode jiffies 13 cutime user jiffies+child
194 * 14 cstime kernel mode jiffies+child 15 priority priority level
195 * 16 nice nice level 17 num_threads number of threads
196 * 18 vmlck locked memory 19 start_time jiffies after boot
197 * 20 vsize virtual memory size 21 rss resident set size
198 * 22 rsslim limit in bytes on rss 23 start_code code segment addr
199 * 24 end_code code segment address 25 start_stack stack address
200 * 26 esp current value of ESP 27 eip current value of EIP
201 * 28 pending bitmap of pending signals 29 blocked blocked signal bmap
202 * 30 sigign bitmap of ignored signals 31 uid user id
203 * 32 ruid real user id 33 gid group id
204 * 34 rgid real group id 35 exit_signal sent to parent thread
205 * 36 task_cpu CPU task is scheduled on 37 rt_priority realtime priority
206 * 38 policy man sched_setscheduler 39 blkio_ticks spent wait block IO
207 * 40 gtime guest jiffies of task 41 cgtime guest jiff of child
208 * 42 start_data program data+bss address 43 end_data program data+bss
209 * 44 start_brk heap expand with brk() 45 argv0len argv[0] length
210 * 46 uptime sysinfo.uptime @read time 47 vsz Virtual Size
211 * 48 rss Resident Set Size 49 shr Shared memory
214 // Data layout in toybuf
216 long long slot[50]; // data from /proc, skippint #2 and #3
217 unsigned short offset[5]; // offset of fields in str[] (skip name, always 0)
219 char str[]; // name, tty, command, wchan, attr, cmdline
222 // TODO: Android uses -30 for LABEL, but ideally it would auto-size.
225 signed char width, slot;
227 } static const typos[] = TAGGED_ARRAY(PS,
228 // stat#s: PID PPID PRI NI ADDR SZ RSS PGID VSZ MAJFL MINFL PR PSR RTPRIO
230 {"PID", 5, 0}, {"PPID", 5, 1}, {"PRI", 3, 15}, {"NI", 3, 16},
231 {"ADDR", 4+sizeof(long), 27}, {"SZ", 5, 20}, {"RSS", 5, 21}, {"PGID", 5, 2},
232 {"VSZ", 6, 20}, {"MAJFL", 6, 9}, {"MINFL", 6, 7}, {"PR", 2, 15},
233 {"PSR", 3, 36}, {"RTPRIO", 6, 37}, {"SCH", 3, 38},
235 // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP
236 {"UID", 5, 31}, {"USER", -8, 64|31}, {"RUID", 4, 32}, {"RUSER", -8, 64|32},
237 {"GID", 8, 33}, {"GROUP", -8, 64|33}, {"RGID", 4, 34}, {"RGROUP", -8, 64|34},
239 // CMD TTY WCHAN LABEL CMDLINE COMMAND
240 {"COMM", -15, -1}, {"TTY", -8, -2}, {"WCHAN", -6, -3}, {"LABEL", -30, -4},
241 {"COMMAND", -27, -5}, {"CMDLINE", -27, -6}, {"ARGS", -27, -6},
242 {"NAME", -15, -6}, {"CMD", -27, -1},
244 // TIME ELAPSED TIME+
245 {"TIME", 8, 11}, {"ELAPSED", 11, 19}, {"TIME+", 9, 11},
247 // Remaining ungrouped
248 {"STIME", 5, 19}, {"F", 1, 64|6}, {"S", -1, 64}, {"C", 1, 0}, {"%CPU", 4, 64},
249 {"STAT", -5, 64}, {"%VSZ", 5, 23}, {"VIRT", 4, 47}, {"RES", 4, 48},
253 // Return 1 to keep, 0 to discard
254 static int match_process(long long *slot)
256 struct ptr_len match[] = {
257 {&TT.gg, 33}, {&TT.GG, 34}, {&TT.pp, 0}, {&TT.PP, 1}, {&TT.ss, 3},
258 {&TT.tt, 4}, {&TT.uu, 31}, {&TT.UU, 32}
263 // Do we have -g -G -p -P -s -t -u -U options selecting processes?
264 for (i = 0; i < ARRAY_LEN(match); i++) {
265 struct ptr_len *mm = match[i].ptr;
268 for (j = 0; j<mm->len; j++) if (ll[j] == slot[match[i].len]) return 1;
272 // If we had selections and didn't match them, don't display
275 // Filter implicit categories for other display types
276 if ((toys.optflags&(FLAG_a|FLAG_d)) && slot[3]==*slot) return 0;
277 if ((toys.optflags&FLAG_a) && !slot[4]) return 0;
278 if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e)) && TT.tty!=slot[4])
284 // Convert field to string representation
285 static char *string_field(struct carveup *tb, struct strawberry *field)
287 char *buf = toybuf+sizeof(toybuf)-260, *out = buf, *s;
288 int which = field->which, sl = typos[which].slot;
289 long long *slot = tb->slot, ll = (sl >= 0) ? slot[sl&63] : 0;
291 // Default: unsupported (5 "C")
294 // stat#s: PID PPID PRI NI ADDR SZ RSS PGID VSZ MAJFL MINFL PR PSR RTPRIO SCH
295 if (which <= PS_SCH) {
298 if (which==PS_PRI) ll = 39-ll;
299 if (which==PS_ADDR) fmt = "%llx";
300 else if (which==PS_SZ) ll >>= 12;
301 else if (which==PS_RSS) ll <<= 2;
302 else if (which==PS_VSZ) ll >>= 10;
303 else if (which==PS_PR && ll<-9) fmt="RT";
304 else if (which==PS_RTPRIO && ll == 0) fmt="-";
305 sprintf(out, fmt, ll);
307 // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP
308 } else if (which <= PS_RGROUP) {
309 sprintf(out, "%lld", ll);
310 if (!(toys.optflags&FLAG_n) && (sl&64)) {
311 if (which > PS_RUSER) {
312 struct group *gr = getgrgid(ll);
314 if (gr) out = gr->gr_name;
316 struct passwd *pw = getpwuid(ll);
318 if (pw) out = pw->pw_name;
322 // COMM TTY WCHAN LABEL COMMAND CMDLINE ARGS NAME CMD
324 // CMD TTY WCHAN LABEL CMDLINE COMMAND COMM NAME
326 if (which==PS_CMD && (toys.optflags&FLAG_f)) sl = typos[which=PS_ARGS].slot;
328 tb->str[tb->offset[4]+slot[45]] = (which == PS_NAME) ? 0 : ' ';
331 if (--sl) out += tb->offset[--sl];
333 for (s = out; *s && *s != ' '; s++) if (*s == '/') out = s+1;
334 if (which>=PS_COMMAND && !*out) sprintf(out = buf, "[%s]", tb->str);
335 // TIME ELAPSED TIME+
336 } else if (which <= PS_TIME_) {
337 int unit = 60, pad = 2, j = TT.ticks;
340 if (which!=PS_TIME_) unit *= 60*24;
342 if (which==PS_ELAPSED) ll = (slot[46]*j)-slot[19];
345 // Output days-hours:mins:secs, skipping non-required fields with zero
346 // TIME has 3 required fields, ETIME has 2. (Posix!) TIME+ is from top
347 for (s = 0, j = 2*(which==PS_TIME_); j<4; j++) {
348 if (!s && (seconds>unit || j == 1+(which!=PS_TIME))) s = out;
350 s += sprintf(s, j ? "%0*ld": "%*ld", pad, (long)(seconds/unit));
352 if ((*s = "-::"[j])) s++;
357 if (which==PS_TIME_ && s-out<8)
358 sprintf(s, ".%02lld", (100*(ll%TT.ticks))/TT.ticks);
360 // Posix doesn't specify what flags should say. Man page says
361 // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h
362 } else if (which==PS_F) sprintf(out, "%llo", (slot[6]>>6)&5);
363 else if (which==PS_S || which==PS_STAT) {
366 if (which==PS_STAT) {
367 // TODO l = multithreaded
368 if (slot[16]<0) *s++ = '<';
369 else if (slot[16]>0) *s++ = 'N';
370 if (slot[3]==*slot) *s++ = 's';
371 if (slot[18]) *s++ = 'L';
372 if (slot[5]==*slot) *s++ = '+';
375 } else if (which==PS_STIME) {
376 time_t t = time(0)-slot[46]+slot[19]/TT.ticks;
378 // Padding behavior's a bit odd: default field size is just hh:mm.
379 // Increasing stime:size reveals more data at left until full,
380 // so move start address so yyyy-mm-dd hh:mm revealed on left at :16,
381 // then add :ss on right for :19.
382 strftime(out, 260, "%F %T", localtime(&t));
383 out = out+strlen(out)-3-abs(field->len);
384 if (out<buf) out = buf;
385 } else if (which==PS__CPU || which==PS__VSZ) {
386 if (which==PS__CPU) {
387 ll = (slot[46]*TT.ticks-slot[19]);
388 sl = (slot[11]*1000)/ll;
389 } else sl = (slot[23]*1000)/TT.si.totalram;
390 sprintf(out, "%d.%d", sl/10, sl%10);
391 } else if (which==PS_VIRT || which==PS_RES || which==PS_SHR)
392 human_readable(out, slot[typos[which].slot]*sysconf(_SC_PAGESIZE), 0);
397 // Display process data that get_ps() read from /proc, formatting with TT.fields
398 static void show_ps(struct carveup *tb)
400 struct strawberry *field;
401 int i, len, width = TT.width;
403 // Loop through fields to display
404 for (field = TT.fields; field; field = field->next) {
405 char *out = string_field(tb, field);
407 // Output the field, appropriately padded
408 len = width - (field != TT.fields);
409 if (!field->next && field->len<0) i = 0;
411 i = len<abs(field->len) ? len : field->len;
415 // TODO test utf8 fontmetrics
416 width -= printf(" %*.*s" + (field == TT.fields), i, len, out);
422 // dirtree callback: read data about process to display, store, or discard it.
423 // Fills toybuf with struct carveup and either DIRTREE_SAVEs a copy to ->extra
424 // (in -k mode) or calls show_ps on toybuf (no malloc/copy/free there).
425 static int get_ps(struct dirtree *new)
431 {"fd/", _PS_TTY}, {"wchan", _PS_WCHAN}, {"attr/current", _PS_LABEL},
432 {"exe", _PS_COMMAND}, {"cmdline",
433 (_PS_CMD*!!(toys.optflags&FLAG_f))|_PS_CMDLINE|_PS_ARGS|_PS_NAME}
435 struct carveup *tb = (void *)toybuf;
436 long long *slot = tb->slot;
437 char *name, *s, *buf = tb->str, *end = 0;
438 int i, j, fd, ksave = DIRTREE_SAVE*!!(toys.optflags&FLAG_k);
441 // Recurse one level into /proc children, skip non-numeric entries
442 if (!new->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP|ksave;
443 if (!(*slot = atol(new->name))) return 0;
444 fd = dirtree_parentfd(new);
447 sprintf(buf, "%lld/stat", *slot);
448 if (!readfileat(fd, buf, buf, &len)) return 0;
450 // parse oddball fields (name and state). Name can have embedded ')' so match
451 // _last_ ')' in stat (although VFS limits filenames to 255 bytes max).
452 // All remaining fields should be numeric.
453 if (!(name = strchr(buf, '('))) return 0;
454 for (s = ++name; *s; s++) if (*s == ')') end = s;
455 if (!end || end-name>255) return 0;
457 // Parse numeric fields (starting at 4th field in slot[1])
458 if (1>sscanf(s = end, ") %c%n", &tb->state, &i)) return 0;
459 for (j = 1; j<50; j++) if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break;
461 // Now we've read the data, move status and name right after slot[] array,
462 // and convert low chars to ? while we're at it.
463 for (i = 0; i<end-name; i++)
464 if ((tb->str[i] = name[i]) < ' ') tb->str[i] = '?';
467 len = sizeof(toybuf)-(buf-toybuf);
469 // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch
470 // or numeric wchan, and the remaining two are always zero), and vmlck into
471 // 18 (which is "obsolete, always 0" from stat)
472 slot[31] = new->st.st_uid;
473 slot[33] = new->st.st_gid;
475 // TIME and TIME+ use combined value, ksort needs 'em added.
476 slot[11] += slot[12];
478 // If RGROUP RUSER STAT RUID RGID happening, or -G or -U, parse "status"
479 // and save ruid, rgid, and vmlck.
480 if ((TT.bits&(_PS_RGROUP|_PS_RUSER|_PS_STAT|_PS_RUID|_PS_RGID))
481 || TT.GG.len || TT.UU.len)
485 sprintf(buf, "%lld/status", *slot);
486 if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
487 s = strstr(buf, "\nUid:");
488 slot[32] = s ? atol(s+5) : new->st.st_uid;
489 s = strstr(buf, "\nGid:");
490 slot[34] = s ? atol(s+5) : new->st.st_gid;
491 s = strstr(buf, "\nVmLck:");
492 if (s) slot[18] = atoll(s+5);
495 // We now know enough to skip processes we don't care about.
496 if (!match_process(slot)) return 0;
498 // Do we need to read "statm"?
499 if (TT.bits&(_PS_VIRT|_PS_RES|_PS_SHR)) {
502 sprintf(buf, "%lld/statm", *slot);
503 if (!readfileat(fd, buf, buf, &temp)) *buf = 0;
505 for (s = buf, i=0; i<3; i++)
506 if (!sscanf(s, " %lld%n", slot+47+i, &j)) slot[47+i] = 0;
510 // /proc data is generated as it's read, so for maximum accuracy on slow
511 // systems (or ps | more) we re-fetch uptime as we fetch each /proc line.
513 slot[46] = TT.si.uptime;
515 // Fetch string data while parentfd still available, appending to buf.
516 // (There's well over 3k of toybuf left. We could dynamically malloc, but
517 // it'd almost never get used, querying length of a proc file is awkward,
518 // fixed buffer is nommu friendly... Wait for somebody to complain. :)
520 for (j = 0; j<ARRAY_LEN(fetch); j++) {
521 tb->offset[j] = buf-(tb->str);
522 if (!(TT.bits&fetch[j].bits)) {
527 // Determine remaining space, reserving minimum of 256 bytes/field and
528 // 260 bytes scratch space at the end (for output conversion later).
529 len = sizeof(toybuf)-(buf-toybuf)-260-256*(ARRAY_LEN(fetch)-j);
530 sprintf(buf, "%lld/%s", *slot, fetch[j].name);
532 // For cmdline we readlink instead of read contents
534 if (0>=(len = readlinkat(fd, buf, buf, len))) buf[len] = 0;
536 // If it's not the TTY field, data we want is in a file.
537 // Last length saved in slot[] is command line (which has embedded NULs)
542 // Call no tty "?" rather than "0:0".
545 // Can we readlink() our way to a name?
546 for (i = 0; i<3; i++) {
547 sprintf(buf, "%lld/fd/%i", *slot, i);
548 if (!fstatat(fd, buf, &st, 0) && S_ISCHR(st.st_mode)
549 && st.st_rdev == rdev && 0<(len = readlinkat(fd, buf, buf, len)))
556 // Couldn't find it, try all the tty drivers.
558 FILE *fp = fopen("/proc/tty/drivers", "r");
559 int tty_major = 0, maj = major(rdev), min = minor(rdev);
562 while (fscanf(fp, "%*s %256s %d %*s %*s", buf, &tty_major) == 2) {
563 // TODO: we could parse the minor range too.
564 if (tty_major == maj) {
565 sprintf(buf+strlen(buf), "%d", min);
566 if (!stat(buf, &st) && S_ISCHR(st.st_mode) && st.st_rdev==rdev)
574 // Really couldn't find it, so just show major:minor.
575 if (!tty_major) sprintf(buf, "%d:%d", maj, min);
579 if (strstart(&s, "/dev/")) memmove(buf, s, strlen(s)+1);
582 // Data we want is in a file.
583 // Last length saved in slot[] is command line (which has embedded NULs)
585 readfileat(fd, buf, buf, &len);
587 // When command has no arguments, don't space over the NUL
591 if (buf[len-1]=='\n') buf[--len] = 0;
593 // Turn NUL to space, other low ascii to ?
594 for (i=0; i<len; i++) {
600 } else if (c<' ') c = '?';
603 len = temp; // position of _first_ NUL
604 } else *buf = len = 0;
605 // Store end of argv[0] so NAME and CMDLINE can differ.
609 buf += strlen(buf)+1;
612 // If we need to sort the output, add it to the list and return.
614 s = xmalloc(buf-toybuf);
615 new->extra = (long)s;
616 memcpy(s, toybuf, buf-toybuf);
619 // Otherwise display it now
625 static char *parse_ko(void *data, char *type, int length)
627 struct strawberry *field;
628 char *width, *title, *end, *s;
631 // Get title, length of title, type, end of type, and display width
633 // Chip off =name to display
634 if ((end = strchr(type, '=')) && length>(end-type)) {
636 length -= (end-type)+1;
642 // Chip off :width to display
643 if ((width = strchr(type, ':')) && width<end) {
644 if (!title) length = width-type;
647 // Allocate structure, copy title
648 field = xzalloc(sizeof(struct strawberry)+(length+1)*!!title);
650 memcpy(field->title = field->forever, title, length);
651 field->title[field->len = length] = 0;
655 field->len = strtol(++width, &title, 10);
656 if (!isdigit(*width) || title != end) return title;
661 if (*(struct strawberry **)data == TT.kfields) {
663 if (*type == '-') field->reverse = -1;
664 else if (*type != '+') type--;
667 for (i = 0; i<ARRAY_LEN(typos); i++) {
669 for (j = 0; j<2; j++) {
670 if (!j) s = typos[i].name;
671 // posix requires alternate names for some fields
672 else if (-1==(k = stridx((char []){PS_NI, PS_SCH, PS_ELAPSED, PS__CPU,
673 PS_VSZ, PS_USER, 0}, i))) continue;
675 s = ((char *[]){"NICE", "SCHED", "ETIME", "PCPU", "VSIZE", "UNAME"})[k];
678 if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break;
682 if (i==ARRAY_LEN(typos)) return type;
683 if (!field->title) field->title = typos[field->which].name;
684 if (!field->len) field->len = typos[field->which].width;
685 else if (typos[field->which].width<0) field->len *= -1;
686 dlist_add_nomalloc(data, (void *)field);
688 // Print padded header for -o.
689 if (*(struct strawberry **)data == TT.fields) {
691 snprintf(toybuf + TT.header_len, sizeof(toybuf) - TT.header_len,
692 " %*s" + (field == TT.fields), field->len, field->title);
693 TT.bits |= 1LL<<field->which;
699 // Parse -p -s -t -u -U -g -G
700 static char *parse_rest(void *data, char *str, int len)
702 struct ptr_len *pl = (struct ptr_len *)data;
708 // gg, GG, pp, ss, tt, uu, UU, *parsing;
710 // Allocate next chunk of data
712 ll = pl->ptr = xrealloc(pl->ptr, sizeof(long)*(pl->len+16));
714 // Parse numerical input
716 ll[pl->len] = xstrtol(str, &end, 10);
717 if (end==(len+str)) num++;
720 if (pl==&TT.pp || pl==&TT.ss) {
721 if (num && ll[pl->len]>0) {
726 } else if (pl==&TT.tt) {
727 // -t pts = 12,pts/12 tty = /dev/tty2,tty2,S0
729 if (strstart(&str, strcpy(toybuf, "/dev/"))) len -= 5;
730 if (strstart(&str, "pts/")) {
733 } else if (strstart(&str, "tty")) len -= 3;
735 if (len<256 && (!(end = strchr(str, '/')) || end-str>len)) {
738 end = toybuf + sprintf(toybuf, "/dev/%s", num ? "pts/" : "tty");
739 memcpy(end, str, len);
742 ll[pl->len++] = st.st_rdev;
746 } else if (len<255) {
755 memcpy(name, str, len);
757 if (pl==&TT.gg || pl==&TT.GG) {
758 struct group *gr = getgrnam(name);
760 ll[pl->len++] = gr->gr_gid;
764 } else if (pl==&TT.uu || pl==&TT.UU) {
765 struct passwd *pw = getpwnam(name);
767 ll[pl->len++] = pw->pw_uid;
779 static int ksort(void *aa, void *bb)
781 struct strawberry *field;
782 struct carveup *ta = *(struct carveup **)aa, *tb = *(struct carveup **)bb;
785 for (field = TT.kfields; field; field = field->next) {
787 slot = typos[field->which].slot;
788 // Compare as strings?
790 memccpy(toybuf, string_field(ta, field), 0, 2048);
792 ret = strcmp(toybuf, string_field(tb, field));
794 if (ta->slot[slot]<tb->slot[slot]) ret = -1;
795 if (ta->slot[slot]>tb->slot[slot]) ret = 1;
798 if (ret) return ret*field->reverse;
809 TT.ticks = sysconf(_SC_CLK_TCK);
811 if (!(toys.optflags&FLAG_w)) terminal_size(&TT.width, 0);
813 // find controlling tty, falling back to /dev/tty if none
814 for (i = 0; !TT.tty && i<4; i++) {
818 if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break;
820 if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev;
824 // parse command line options other than -o
825 comma_args(TT.ps.P, &TT.PP, "bad -P", parse_rest);
826 comma_args(TT.ps.p, &TT.pp, "bad -p", parse_rest);
827 comma_args(TT.ps.t, &TT.tt, "bad -t", parse_rest);
828 comma_args(TT.ps.s, &TT.ss, "bad -s", parse_rest);
829 comma_args(TT.ps.u, &TT.uu, "bad -u", parse_rest);
830 comma_args(TT.ps.U, &TT.UU, "bad -u", parse_rest);
831 comma_args(TT.ps.g, &TT.gg, "bad -g", parse_rest);
832 comma_args(TT.ps.G, &TT.GG, "bad -G", parse_rest);
833 comma_args(TT.ps.k, &TT.kfields, "bad -k", parse_ko);
834 dlist_terminate(TT.kfields);
836 // Parse manual field selection, or default/-f/-l, plus -Z,
837 // constructing the header line in toybuf as we go.
838 if (toys.optflags&FLAG_Z) {
839 struct arg_list Z = { 0, "LABEL" };
841 comma_args(&Z, &TT.fields, "-Z", parse_ko);
843 if (TT.ps.o) comma_args(TT.ps.o, &TT.fields, "bad -o field", parse_ko);
848 if (toys.optflags&FLAG_f)
849 al.arg = "USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD";
850 else if (toys.optflags&FLAG_l)
851 al.arg = "F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD";
852 else if (CFG_TOYBOX_ON_ANDROID)
853 al.arg = "USER,PID,PPID,VSIZE,RSS,WCHAN:10,ADDR:10=PC,S,CMDLINE";
854 else al.arg = "PID,TTY,TIME,CMD";
856 comma_args(&al, &TT.fields, 0, parse_ko);
858 dlist_terminate(TT.fields);
859 printf("%s\n", toybuf);
861 dt = dirtree_read("/proc", get_ps);
863 if (toys.optflags&FLAG_k) {
864 struct carveup **tbsort = xmalloc(TT.kcount*sizeof(struct carveup *));
866 // descend into child list
867 *tbsort = (void *)dt;
874 void *temp = dt->next;
876 tbsort[i++] = (void *)dt->extra;
882 qsort(tbsort, TT.kcount, sizeof(struct carveup *), (void *)ksort);
883 for (i = 0; i<TT.kcount; i++) {
887 if (CFG_TOYBOX_FREE) free(tbsort);
890 if (CFG_TOYBOX_FREE) {
899 llist_traverse(TT.fields, free);
905 #include "generated/flags.h"