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 * TODO: ps aux (att & bsd style "ps -ax" vs "ps ax" behavior difference)
28 * TODO: finalize F, remove C
29 * switch -fl to -y, use "string" instead of constants to set, remove C
31 * TODO: way too many hardwired constants here, how can I generate them?
32 * TODO: thread support /proc/$d/task/%d/stat (and -o stat has "l")
34 * Design issue: the -o fields are an ordered array, and the order is
35 * significant. The array index is used in strawberry->which (consumed
36 * in do_ps()) and in the bitmasks enabling default fields in ps_main().
38 USE_PS(NEWTOY(ps, "k(sort)P(ppid)*aAdeflno*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN))
44 usage: ps [-AadeflnwZ] [-gG GROUP,] [-k FIELD,] [-o FIELD,] [-p PID,] [-t TTY,] [-uU USER,]
48 Which processes to show (selections may be comma separated lists):
51 -a Processes with terminals that aren't session leaders
52 -d All processes that aren't session leaders
54 -g Belonging to GROUPs
55 -G Belonging to real GROUPs (before sgid)
57 -P Parent PIDs (--ppid)
59 -t Attached to selected TTYs
61 -U Owned by real USERs (before suid)
65 -k Sort FIELDs in +increasing or -decreasting order (--sort)
66 -n Show numeric USER and GROUP
67 -w Wide output (don't truncate at terminal width)
69 Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD)
71 -f Full listing (-o USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD)
72 -l Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD)
73 -o Output the listed FIELDs, each with optional :size and/or =title
78 ADDR Instruction pointer
79 CMD Command name (original)
80 CMDLINE Command name (current argv[0])
81 COMM Command line (with arguments)
82 ETIME Elapsed time since process start
83 F Process flags (PF_*) from linux source file include/sched.h
84 (in octal rather than hex because posix)
88 MAJFL Major page faults
89 MINFL Minor page faults
90 NI Niceness of process (lower niceness is higher priority)
91 PCPU Percentage of CPU time used
94 PPID Parent Process ID
96 RGID Real (before sgid) group ID
97 RGROUP Real (before sgid) group name
98 RSS Resident Set Size (memory currently used)
99 RUID Real (before suid) user ID
100 RUSER Real (before suid) user name
102 R (running) S (sleeping) D (disk sleep) T (stopped) t (traced)
103 Z (zombie) X (dead) x (dead) K (wakekill) W (waking)
104 STAT Process state (S) plus:
105 < high priority N low priority L locked memory
106 s session leader + foreground l multithreaded
107 STIME Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss)
108 SZ Memory Size (4k pages needed to completely swap out process)
109 TIME CPU time consumed
110 TTY Controlling terminal
113 VSZ Virtual memory size (1k units)
114 WCHAN Waiting in kernel for
132 struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU, *parsing;
136 long long ticks, bits;
142 struct strawberry *next, *prev;
148 // Data layout in toybuf
150 long long slot[50]; // data from /proc, skippint #2 and #3
151 unsigned short offset[4]; // offset of fields in str[] (skip name, always 0)
153 char str[]; // name, tty, wchan, attr, cmdline
156 // Return 1 to keep, 0 to discard
157 static int match_process(long long *slot)
159 struct ptr_len *match[] = {
160 &TT.gg, &TT.GG, &TT.pp, &TT.PP, &TT.ss, &TT.tt, &TT.uu, &TT.UU
162 int i, j, mslot[] = {33, 34, 0, 1, 3, 4, 31, 32};
165 // Do we have -g -G -p -P -s -t -u -U options selecting processes?
166 for (i = 0; i < ARRAY_LEN(match); i++) {
169 for (j = 0; j<match[i]->len; j++) if (ll[j] == slot[mslot[i]]) return 1;
173 // If we had selections and didn't match them, don't display
176 // Filter implicit categories for other display types
177 if ((toys.optflags&(FLAG_a|FLAG_d)) && slot[3]==*slot) return 0;
178 if ((toys.optflags&FLAG_a) && !slot[4]) return 0;
179 if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e)) && TT.tty!=slot[4])
185 static char *string_field(struct carveup *tb, struct strawberry *field)
187 char *buf = toybuf+sizeof(toybuf)-64, *out = buf, *s;
188 long long ll, *slot = tb->slot;
191 // Default: unsupported (5 "C")
194 // stat#s: PID, PPID, PRI, NI, ADDR, SZ, RSS, PGID, VSS, MAJFL, MINFL
195 if (-1!=(i = stridx((char[]){3,4,6,7,8,9,24,19,23,25,30,0}, field->which)))
199 ll = slot[((char[]){0,1,15,16,27,20,21,2,20,9,7})[i]];
201 if (i==4) fmt = "%llx";
202 else if (i==5) ll >>= 12;
203 else if (i==6) ll <<= 2;
204 else if (i==8) ll >>= 10;
205 sprintf(out, fmt, ll);
206 // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP
207 } else if (-1!=(i = stridx((char[]){2,22,28,21,26,17,29,20}, field->which)))
209 int id = slot[31+i/2]; // uid, ruid, gid, rgid
211 // Even entries are numbers, odd are names
212 sprintf(out, "%d", id);
213 if (!(toys.optflags&FLAG_n) && i&1) {
215 struct group *gr = getgrgid(id);
217 if (gr) out = gr->gr_name;
219 struct passwd *pw = getpwuid(id);
221 if (pw) out = pw->pw_name;
224 // CMD TTY WCHAN LABEL (CMDLINE handled elsewhere)
225 } else if (-1!=(i = stridx((char[]){15,12,10,31}, field->which))) {
227 if (i) out += tb->offset[i-1];
229 // F (also assignment of i used by later tests)
230 // Posix doesn't specify what flags should say. Man page says
231 // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h
232 } else if (!(i = field->which)) sprintf(out, "%llo", (slot[6]>>6)&5);
234 else if (i==1 || i==27) {
238 // TODO l = multithreaded
239 if (slot[16]<0) *s++ = '<';
240 else if (slot[16]>0) *s++ = 'N';
241 if (slot[3]==*slot) *s++ = 's';
242 if (slot[18]) *s++ = 'L';
243 if (slot[5]==*slot) *s++ = '+';
248 time_t t = time(0)-slot[46]+slot[19]/sysconf(_SC_CLK_TCK);
250 // Padding behavior's a bit odd: default field size is just hh:mm.
251 // Increasing stime:size reveals more data at left until full,
252 // so move start address so yyyy-mm-dd hh:mm revealed on left at :16,
253 // then add :ss on right for :19.
254 strftime(out, 64, "%F %T", localtime(&t));
255 out = out+strlen(out)-3-abs(field->len);
256 if (out<buf) out = buf;
259 } else if (i==13 || i==16) {
260 int unit = 60*60*24, j = sysconf(_SC_CLK_TCK);
261 time_t seconds = (i==16) ? (slot[46]*j)-slot[19] : slot[11]+slot[12];
264 for (s = 0, j = 0; j<4; j++) {
265 // TIME has 3 required fields, ETIME has 2. (Posix!)
266 if (!s && (seconds>unit || j == 1+(i==16))) s = out;
268 s += sprintf(s, j ? "%02ld": "%2ld", (long)(seconds/unit));
269 if ((*s = "-::"[j])) s++;
275 // COMM - command line including arguments
276 // CMDLINE - command name from /proc/pid/cmdline (no arguments)
277 } else if (i==14 || i==32) {
279 out = toybuf+sizeof(toybuf)-300;
280 sprintf(out, "[%s]", tb->str); // kernel thread, use real name
282 out = tb->str+tb->offset[3];
283 if (slot[47]!=INT_MAX) out[slot[47]] = ' '*(i==14);
288 ll = (slot[46]*sysconf(_SC_CLK_TCK)-slot[19]);
289 i = ((slot[11]+slot[12])*1000)/ll;
290 sprintf(out, "%d.%d", i/10, i%10);
296 // Display process data that get_ps() read from /proc, formatting with TT.fields
297 static void show_ps(struct carveup *tb)
299 struct strawberry *field;
300 int i, len, width = TT.width;
302 // Loop through fields to display
303 for (field = TT.fields; field; field = field->next) {
304 char *out = string_field(tb, field);
306 // Output the field, appropriately padded
307 len = width - (field != TT.fields);
308 if (!field->next && field->len<0) i = 0;
310 i = len<abs(field->len) ? len : field->len;
314 // TODO test utf8 fontmetrics
315 width -= printf(" %*.*s" + (field == TT.fields), i, len, out);
321 // dirtree callback: read data about process to display, store, or discard it.
322 // Collects slot[50] plus stat, name, tty, wchan, attr, cmdline
323 static int get_ps(struct dirtree *new)
328 } fetch[] = {{"fd/", 1<<12}, {"wchan", 1<<10}, {"attr/current", 1<<31},
329 {"cmdline", (1<<14)|(1LL<<32)}};
330 struct carveup *tb = (void *)toybuf;
331 long long *slot = (void *)toybuf;
332 char *name, *s, *buf = tb->str, *end = 0;
333 int i, j, fd, ksave = DIRTREE_SAVE*!!(toys.optflags&FLAG_k);
336 // Recurse one level into /proc children, skip non-numeric entries
337 if (!new->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP|ksave;
338 if (!(*slot = atol(new->name))) return 0;
339 fd = dirtree_parentfd(new);
342 sprintf(buf, "%lld/stat", *slot);
343 if (!readfileat(fd, buf, buf, &len)) return 0;
345 // parse oddball fields (name and state). Name can have embedded ')' so match
346 // _last_ ')' in stat (although VFS limits filenames to 255 bytes max).
347 // All remaining fields should be numeric.
348 if (!(name = strchr(buf, '('))) return 0;
349 for (s = ++name; *s; s++) if (*s == ')') end = s;
350 if (!end || end-name>255) return 0;
352 // Move name right after slot[] array (pid already set, so stomping it's ok)
353 // and convert low chars to spaces while we're at it.
354 for (i = 0; i<end-name; i++)
355 if ((tb->str[i] = name[i]) < ' ') tb->str[i] = ' ';
359 // Parse numeric fields (starting at 4th field in slot[1])
360 if (1>sscanf(s = end, ") %c%n", &tb->state, &i)) return 0;
361 for (j = 1; j<50; j++) if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break;
363 // Now we've read the data, move status and name right after slot[] array,
364 // and convert low chars to spaces while we're at it.
365 for (i = 0; i<end-name; i++)
366 if ((tb->str[i] = name[i]) < ' ') tb->str[i] = ' ';
370 // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch
371 // or numeric wchan, and the remaining two are always zero), and vmlck into
372 // 18 (which is "obsolete, always 0")
373 slot[31] = new->st.st_uid;
374 slot[33] = new->st.st_gid;
376 // If RGROUP RUSER STAT RUID RGID happening, or -G or -U, parse "status"
377 // Save ruid in slot[34] and rgid in slot[35], which are otherwise zero,
378 // and vmlck into slot[18] (it_real_value, also always zero).
379 if ((TT.bits & 0x38300000) || TT.GG.len || TT.UU.len) {
380 len = sizeof(toybuf)-(buf-toybuf);
381 sprintf(buf, "%lld/status", *slot);
382 if (!readfileat(fd, buf, buf, &len)) *buf = 0;
383 s = strstr(buf, "\nUid:");
384 slot[32] = s ? atol(s+5) : new->st.st_uid;
385 s = strstr(buf, "\nGid:");
386 slot[34] = s ? atol(s+5) : new->st.st_gid;
387 s = strstr(buf, "\nVmLck:");
388 if (s) slot[18] = atoll(s+5);
391 // We now know enough to skip processes we don't care about.
392 if (!match_process(slot)) return 0;
394 // /proc data is generated as it's read, so for maximum accuracy on slow
395 // systems (or ps | more) we re-fetch uptime as we fetch each /proc line.
396 sysinfo((void *)(toybuf+2048));
397 slot[46] = ((struct sysinfo *)toybuf)->uptime;
399 // fetch remaining data while parentfd still available, appending to buf.
400 // (There's well over 3k of toybuf left. We could dynamically malloc, but
401 // it'd almost never get used, querying length of a proc file is awkward,
402 // fixed buffer is nommu friendly... Wait for somebody to complain. :)
403 for (j = 0; j<ARRAY_LEN(fetch); j++) {
404 tb->offset[j] = buf-(tb->str);
405 if (!(TT.bits&fetch[j].bits)) {
410 // Determine remaining space, reserving minimum of 256 bytes/field and
411 // 64 bytes scratch space at the end (for output conversion later).
412 len = sizeof(toybuf)-(buf-toybuf)-64-256*(ARRAY_LEN(fetch)-j);
413 sprintf(buf, "%lld/%s", *slot, fetch[j].name);
415 // If it's not the TTY field, data we want is in a file.
416 // Last length saved in slot[] is command line (which has embedded NULs)
418 readfileat(fd, buf, buf, &len);
420 // When command has no arguments, don't space over the NUL
424 if (buf[len-1]=='\n') buf[--len] = 0;
426 // Always escape spaces because an executable named esc[0m would be bad.
427 // Escaping low ascii does not affect utf8.
428 for (i=0; i<len; i++) {
429 if (!temp && !buf[i]) temp = i;
430 if (buf[i]<' ') buf[i] = ' ';
432 if (temp) len = temp; // position of _first_ NUL
434 } else *buf = len = 0;
435 // Store end of argv[0] so COMM and CMDLINE can differ.
441 // Call no tty "?" rather than "0:0".
444 // Can we readlink() our way to a name?
445 for (i = 0; i<3; i++) {
446 sprintf(buf, "%lld/fd/%i", *slot, i);
447 if (!fstatat(fd, buf, &st, 0) && S_ISCHR(st.st_mode)
448 && st.st_rdev == rdev && 0<(len = readlinkat(fd, buf, buf, len)))
455 // Couldn't find it, try all the tty drivers.
457 FILE *fp = fopen("/proc/tty/drivers", "r");
458 int tty_major = 0, maj = major(rdev), min = minor(rdev);
461 while (fscanf(fp, "%*s %256s %d %*s %*s", buf, &tty_major) == 2) {
462 // TODO: we could parse the minor range too.
463 if (tty_major == maj) {
464 sprintf(buf+strlen(buf), "%d", min);
465 if (!stat(buf, &st) && S_ISCHR(st.st_mode) && st.st_rdev==rdev)
473 // Really couldn't find it, so just show major:minor.
474 if (!tty_major) sprintf(buf, "%d:%d", maj, min);
478 if (strstart(&s, "/dev/")) memmove(buf, s, strlen(s)+1);;
481 buf += strlen(buf)+1;
484 // If we need to sort the output, add it to the list and return.
486 s = xmalloc(buf-toybuf);
487 new->extra = (long)s;
488 memcpy(s, toybuf, buf-toybuf);
491 // Otherwise display it now
497 // Traverse arg_list of csv, calling callback on each value
498 void comma_args(struct arg_list *al, char *err,
499 char *(*callback)(char *str, int len))
506 while ((next = comma_iterate(&arg, &len)))
507 if ((next = callback(next, len)))
508 perror_exit("%s '%s'\n%*c", err, al->arg,
509 (int)(5+strlen(toys.which->name)+strlen(err)+next-al->arg), '^');
514 static char *parse_o(char *type, int length)
516 struct strawberry *field;
517 char *width, *title, *end, *s, *typos[] = {
518 "F", "S", "UID", "PID", "PPID", "C", "PRI", "NI", "ADDR", "SZ",
519 "WCHAN", "STIME", "TTY", "TIME", "CMD", "COMMAND", "ELAPSED", "GROUP",
520 "%CPU", "PGID", "RGROUP", "RUSER", "USER", "VSZ", "RSS", "MAJFL",
521 "GID", "STAT", "RUID", "RGID", "MINFL", "LABEL", "CMDLINE"
523 // TODO: Android uses -30 for LABEL, but ideally it would auto-size.
524 signed char widths[] = {1,-1,5,5,5,2,3,3,4+sizeof(long),5,
525 -6,5,-8,8,-27,-27,11,-8,
530 // Get title, length of title, type, end of type, and display width
532 // Chip off =name to display
533 if ((end = strchr(type, '=')) && length>(end-type)) {
535 length -= (end-type)+1;
541 // Chip off :width to display
542 if ((width = strchr(type, ':')) && width<end) {
543 if (!title) length = width-type;
546 // Allocate structure, copy title
547 field = xzalloc(sizeof(struct strawberry)+(length+1)*!!title);
549 memcpy(field->title = field->forever, title, length);
550 field->title[field->len = length] = 0;
554 field->len = strtol(++width, &title, 10);
555 if (!isdigit(*width) || title != end) return title;
560 for (i = 0; i<ARRAY_LEN(typos); i++) {
562 for (j = 0; j<2; j++) {
563 if (!j) s = typos[i];
564 // posix requires alternate names for some fields
565 else if (-1==(k = stridx((char []){7,14,15,16,18,23,22,0}, i))) continue;
566 else s = ((char *[]){"NICE","ARGS","COMM","ETIME","PCPU",
567 "VSIZE","UNAME"})[k];
569 if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break;
573 if (i==ARRAY_LEN(typos)) return type;
574 if (!field->title) field->title = typos[field->which];
575 if (!field->len) field->len = widths[field->which];
576 else if (widths[field->which]<0) field->len *= -1;
577 dlist_add_nomalloc((void *)&TT.fields, (void *)field);
579 // Print padded header.
581 snprintf(toybuf + TT.header_len, sizeof(toybuf) - TT.header_len,
582 " %*s" + (field == TT.fields), field->len, field->title);
583 TT.bits |= (i = 1<<field->which);
588 // Parse -p -s -t -u -U -g -G
589 static char *parse_rest(char *str, int len)
591 struct ptr_len *pl = TT.parsing;
597 // gg, GG, pp, ss, tt, uu, UU, *parsing;
599 // Allocate next chunk of data
601 ll = pl->ptr = xrealloc(pl->ptr, sizeof(long)*(pl->len+16));
603 // Parse numerical input
605 ll[pl->len] = xstrtol(str, &end, 10);
606 if (end==(len+str)) num++;
609 if (pl==&TT.pp || pl==&TT.ss) {
610 if (num && ll[pl->len]>0) {
615 } else if (pl==&TT.tt) {
616 // -t pts = 12,pts/12 tty = /dev/tty2,tty2,S0
618 if (strstart(&str, strcpy(toybuf, "/dev/"))) len -= 5;
619 if (strstart(&str, "pts/")) {
622 } else if (strstart(&str, "tty")) len -= 3;
624 if (len<256 && (!(end = strchr(str, '/')) || end-str>len)) {
627 end = toybuf + sprintf(toybuf, "/dev/%s", num ? "pts/" : "tty");
628 memcpy(end, str, len);
631 ll[pl->len++] = st.st_rdev;
635 } else if (len<255) {
644 memcpy(name, str, len);
646 if (pl==&TT.gg || pl==&TT.GG) {
647 struct group *gr = getgrnam(name);
649 ll[pl->len++] = gr->gr_gid;
653 } else if (pl==&TT.uu || pl==&TT.UU) {
654 struct passwd *pw = getpwnam(name);
656 ll[pl->len++] = pw->pw_uid;
668 static int ksort(void *aa, void *bb)
673 // Convert fields to string version, saving first in toybuf
674 out = string_field((void *)aa, TT.fields);
675 memccpy(toybuf, out, 0, 2048);
677 out = string_field((void *)bb, TT.fields);
679 // Numeric comparison?
680 llb = strtoll(out, &end, 10);
682 lla = strtoll(toybuf, &end, 10);
684 if (lla<llb) return -1;
685 if (lla>llb) return 1;
691 return strcmp(toybuf, out);
700 if (!(toys.optflags&FLAG_w)) terminal_size(&TT.width, 0);
702 // find controlling tty, falling back to /dev/tty if none
703 for (i = 0; !TT.tty && i<4; i++) {
707 if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break;
709 if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev;
713 // parse command line options other than -o
715 comma_args(TT.P, "bad -P", parse_rest);
717 comma_args(TT.p, "bad -p", parse_rest);
719 comma_args(TT.t, "bad -t", parse_rest);
721 comma_args(TT.s, "bad -s", parse_rest);
723 comma_args(TT.u, "bad -u", parse_rest);
725 comma_args(TT.U, "bad -u", parse_rest);
727 comma_args(TT.g, "bad -g", parse_rest);
729 comma_args(TT.G, "bad -G", parse_rest);
731 // Parse manual field selection, or default/-f/-l, plus -Z,
732 // constructing the header line in toybuf as we go.
733 if (toys.optflags&FLAG_Z) {
734 struct arg_list Z = { 0, "LABEL" };
736 comma_args(&Z, "-Z", parse_o);
738 if (TT.o) comma_args(TT.o, "bad -o field", parse_o);
743 if (toys.optflags&FLAG_f)
744 al.arg = "USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD";
745 else if (toys.optflags&FLAG_l)
746 al.arg = "F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD";
747 else if (CFG_TOYBOX_ON_ANDROID)
748 al.arg = "USER,PID,PPID,VSIZE,RSS,WCHAN:10,ADDR:10=PC,S,CMDLINE";
749 else al.arg = "PID,TTY,TIME,CMD";
751 comma_args(&al, 0, parse_o);
753 dlist_terminate(TT.fields);
754 printf("%s\n", toybuf);
756 dt = dirtree_read("/proc", get_ps);
758 if (toys.optflags&FLAG_k) {
759 struct carveup **tbsort = xmalloc(TT.kcount*sizeof(struct carveup *));
761 // descend into child list
762 *tbsort = (void *)dt;
769 void *temp = dt->next;
771 tbsort[i++] = (void *)dt->extra;
776 qsort(tbsort, TT.kcount, sizeof(struct dirtree *), (void *)ksort);
777 for (i = 0; i<TT.kcount; i++) {
783 if (CFG_TOYBOX_FREE) {
792 llist_traverse(TT.fields, free);