From 9bdd8fd4549e8392b5bec0298e9978436392e342 Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Wed, 28 Oct 2015 23:30:36 -0500 Subject: Promote ps to posix. --- toys/pending/ps.c | 613 ------------------------------------------------------ toys/posix/ps.c | 613 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 613 insertions(+), 613 deletions(-) delete mode 100644 toys/pending/ps.c create mode 100644 toys/posix/ps.c (limited to 'toys') diff --git a/toys/pending/ps.c b/toys/pending/ps.c deleted file mode 100644 index 6c63d550..00000000 --- a/toys/pending/ps.c +++ /dev/null @@ -1,613 +0,0 @@ -/* ps.c - show process list - * - * Copyright 2015 Rob Landley - * - * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ps.html - * And http://kernel.org/doc/Documentation/filesystems/proc.txt Table 1-4 - * And linux kernel source fs/proc/array.c function do_task_stat() - * - * Deviations from posix: no -n because /proc/self/wchan exists. - * Posix says default output should have field named "TTY" but if you "-o tty" - * the same field should be called "TT" which is _INSANE_ and I'm not doing it. - * Similarly -f outputs USER but calls it UID (we call it USER). - * It also says that -o "args" and "comm" should behave differently but use - * the same title, which is not the same title as the default output. (No.) - * Select by session id is -s not -g. - * - * Posix defines -o ADDR as "The address of the process" but the process - * start address is a constant on any elf system with mmu. The procps ADDR - * field always prints "-" with an alignment of 1, which is why it has 11 - * characters left for "cmd" in in 80 column "ps -l" mode. On x86-64 you - * need 12 chars, leaving nothing for cmd: I.E. posix 2008 ps -l mode can't - * be sanely implemented on 64 bit Linux systems. In procps there's ps -y - * which changes -l by removing the "F" column and swapping RSS for ADDR, - * leaving 9 chars for cmd, so we're using that as our -l output. - * - * TODO: ps aux (att & bsd style "ps -ax" vs "ps ax" behavior difference) - * TODO: finalize F, remove C - * switch -fl to -y, use "string" instead of constants to set, remove C - * TODO: --sort -Z - * TODO: way too many hardwired constants here, how can I generate them? - * TODO: thread support /proc/$d/task/%d/stat (and -o stat has "l") - * - * Design issue: the -o fields are an ordered array, and the order is - * significant. The array index is used in strawberry->which (consumed - * in do_ps()) and in the bitmasks enabling default fields in ps_main(). - -USE_PS(NEWTOY(ps, "aAdeflo*p*s*t*u*U*g*G*w[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN)) - -config PS - bool "ps" - default n - help - usage: ps [-Aadeflw] [-gG GROUP] [-o FIELD] [-p PID] [-t TTY] [-uU USER] - - List processes. - - Which processes to show (selections may be comma separated lists): - - -A All processes - -a Processes with terminals that aren't session leaders - -d All processes that aren't session leaders - -e Same as -A - -g belonging to GROUPs - -G belonging to real GROUPs (before sgid) - -p PIDs - -s in session IDs - -t attached to selected TTYs - -u owned by USERs - -U owned by real USERs (before suid) - -w Wide output (don't truncate at terminal width) - - Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD) - - -f Full listing (-o USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD) - -l Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD) - -o Output the listed FIELDs, each with optional :size and/or =title - - Available -o FIELDs: - - ADDR Instruction pointer - CMD Command line - ETIME Elapsed time since process start - F Process flags (PF_*) from linux source file include/sched.h - (in octal rather than hex because posix) - GID Group id - GROUP Group name - MAJFL Major page faults - MINFL Minor page faults - NI Niceness of process (lower niceness is higher priority) - PCPU Percentage of CPU time used - PGID Process Group ID - PID Process ID - PPID Parent Process ID - PRI Priority - RGID Real (before sgid) group ID - RGROUP Real (before sgid) group name - RSS Resident Set Size (memory currently used) - RUID Real (before suid) user ID - RUSER Real (before suid) user name - S Process state: - R (running) S (sleeping) D (disk sleep) T (stopped) t (traced) - Z (zombie) X (dead) x (dead) K (wakekill) W (waking) - STAT Process state (S) plus: - < high priority N low priority L locked memory - s session leader + foreground l multithreaded - STIME Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss) - SZ Memory Size (4k pages needed to completely swap out process) - TIME CPU time consumed - TTY Controlling terminal - UID User id - USER User name - VSZ Virtual memory size (1k units) - WCHAN Waiting in kernel for -*/ - -#define FOR_ps -#include "toys.h" - -GLOBALS( - struct arg_list *G; - struct arg_list *g; - struct arg_list *U; - struct arg_list *u; - struct arg_list *t; - struct arg_list *s; - struct arg_list *p; - struct arg_list *o; - - struct ptr_len gg, GG, pp, ss, tt, uu, UU, *parsing; - unsigned width; - dev_t tty; - void *fields; - long bits; - long long ticks; -) - -struct strawberry { - struct strawberry *next, *prev; - short which, len; - char *title; - char forever[]; -}; - -static time_t get_uptime(void) -{ - struct sysinfo si; - - sysinfo(&si); - - return si.uptime; -} - -// Return 1 to display, 0 to skip -static int match_process(long long *slot) -{ - struct ptr_len *match[] = {&TT.gg,&TT.GG,&TT.pp,&TT.ss,&TT.tt,&TT.uu,&TT.UU}; - int i, j, mslot[] = {33, 34, 0, 3, 4, 31, 32}; - long *ll = 0; - - // Do we have -g -G -p -s -t -u -U options selecting processes? - for (i = 0; i < ARRAY_LEN(match); i++) { - if (match[i]->len) { - ll = match[i]->ptr; - for (j = 0; jlen; j++) if (ll[j] == slot[mslot[i]]) return 1; - } - } - - // If we had selections and didn't match them, don't display - if (ll) return 0; - - // Filter implicit categories for other display types - if ((toys.optflags&(FLAG_a|FLAG_d)) && slot[3]==*slot) return 0; - if ((toys.optflags&FLAG_a) && !slot[4]) return 0; - if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e)) && TT.tty!=slot[4]) - return 0; - - return 1; -} - -// dirtree callback. -// toybuf used as: 1024 /proc/$PID/stat, 1024 slot[], 2048 /proc/$PID/cmdline -static int do_ps(struct dirtree *new) -{ - struct strawberry *field; - long long *slot = (void *)(toybuf+1024), ll; - char *name, *s, state; - int nlen, i, fd, len, width = TT.width; - - if (!new->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP; - if (!(*slot = atol(new->name))) return 0; - - // name field limited to 256 bytes by VFS, plus 40 fields * max 20 chars: - // 1000-ish total, but some forced zero so actually there's headroom. - sprintf(toybuf, "%lld/stat", *slot); - if (!readfileat(dirtree_parentfd(new), toybuf, toybuf, 1024)) return 0; - - // parse oddball fields (name and state) - if (!(s = strchr(toybuf, '('))) return 0; - for (name = ++s; *s != ')'; s++) if (!*s) return 0; - nlen = s++-name; - if (1>sscanf(++s, " %c%n", &state, &i)) return 0; - - // parse numeric fields (PID = 0, skip 2, then 4th field goes in slot[1]) - for (len = 1; len<100; len++) - if (1>sscanf(s += i, " %lld%n", slot+len, &i)) break; - - // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch - // or numeric wchan, and the remaining two are always zero). - slot[31] = new->st.st_uid; - slot[33] = new->st.st_gid; - - // If RGROUP RUSER STAT RUID RGID - // Save ruid in slot[34] and rgid in slot[35], which are otherwise zero, - // and vmlck into slot[18] (it_real_value, also always zero). - if ((TT.bits & 0x38300000) || TT.GG.len || TT.UU.len) { - char *out = toybuf+2048; - - sprintf(out, "%lld/status", *slot); - if (!readfileat(dirtree_parentfd(new), out, out, 2048)) *out = 0; - s = strstr(out, "\nUid:"); - slot[32] = s ? atol(s+5) : new->st.st_uid; - s = strstr(out, "\nGid:"); - slot[34] = s ? atol(s+5) : new->st.st_gid; - s = strstr(out, "\nVmLck:"); - if (s) slot[18] = atoll(s+5); - } - - // skip processes we don't care about. - if (!match_process(slot)) return 0; - - // At this point 512 bytes at toybuf+512 are free (already parsed). - // Start of toybuf still has name in it. - - // Loop through fields - for (field = TT.fields; field; field = field->next) { - char *out = toybuf+2048, *scratch = toybuf+512; - - // Default: unsupported (5 "C") - sprintf(out, "-"); - - // PID, PPID, PRI, NI, ADDR, SZ, RSS, PGID, VSS, MAJFL, MINFL - if (-1!=(i = stridx((char[]){3,4,6,7,8,9,24,19,23,25,30,0}, field->which))) - { - char *fmt = "%lld"; - - ll = slot[((char[]){0,1,15,16,27,20,21,2,20,9,7})[i]]; - if (i==2) ll--; - if (i==4) fmt = "%llx"; - else if (i==5) ll >>= 12; - else if (i==6) ll <<= 2; - else if (i==8) ll >>= 10; - sprintf(out, fmt, ll); - // UID USER RUID RUSER GID GROUP RGID RGROUP - } else if (-1!=(i = stridx((char[]){2,22,28,21,26,17,29,20}, field->which))) - { - int id = slot[31+i/2]; // uid, ruid, gid, rgid - - // Even entries are numbers, odd are names - sprintf(out, "%d", id); - if (i&1) { - if (i>3) { - struct group *gr = getgrgid(id); - - if (gr) out = gr->gr_name; - } else { - struct passwd *pw = getpwuid(id); - - if (pw) out = pw->pw_name; - } - } - - // F (also assignment of i used by later tests) - // Posix doesn't specify what flags should say. Man page says - // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h - } else if (!(i = field->which)) sprintf(out, "%llo", (slot[6]>>6)&5); - // S STAT - else if (i==1 || i==27) { - sprintf(out, "%c", state); - if (i==27) { - // TODO l = multithreaded - s = out+1; - if (slot[16]<0) *s++ = '<'; - else if (slot[16]>0) *s++ = 'N'; - if (slot[3]==*slot) *s++ = 's'; - if (slot[18]) *s++ = 'L'; - if (slot[5]==*slot) *s++ = '+'; - *s = 0; - } - // WCHAN - } else if (i==10) { - sprintf(scratch, "%lld/wchan", *slot); - readfileat(dirtree_parentfd(new), scratch, out, 2047); - - // STIME - } else if (i==11) { - time_t t = time(0) - get_uptime() + slot[19]/sysconf(_SC_CLK_TCK); - - // Padding behavior's a bit odd: default field size is just hh:mm. - // Increasing stime:size reveals more data at left until full - // yyyy-mm-dd hh:mm revealed at :16, then adds :ss at end for :19. But - // expanding last field just adds :ss. - strftime(scratch, 512, "%F %T", localtime(&t)); - out = scratch+strlen(scratch)-3-abs(field->len); - if (out>8)&0xfff, ((i>>12)&0xfff00)|(i&0xff)); - } - - // TIME ELAPSED - } else if (i==13 || i==16) { - int unit = 60*60*24, j = sysconf(_SC_CLK_TCK); - time_t seconds = (i==16) ? (get_uptime()*j)-slot[19] : slot[11]+slot[12]; - - seconds /= j; - for (s = 0, j = 0; j<4; j++) { - // TIME has 3 required fields, ETIME has 2. (Posix!) - if (!s && (seconds>unit || j == 1+(i==16))) s = out; - if (s) { - s += sprintf(s, j ? "%02ld": "%2ld", (long)(seconds/unit)); - if ((*s = "-::"[j])) s++; - } - seconds %= unit; - unit /= j ? 60 : 24; - } - - // COMMAND CMD - // Command line limited to 2k displayable. We could dynamically malloc, but - // it'd almost never get used, querying length of a proc file is awkward, - // fixed buffer is nommu friendly... Wait for somebody to complain. :) - } else if (i==14 || i==15) { - int fd; - - len = 0; - sprintf(out, "%lld/cmdline", *slot); - fd = openat(dirtree_parentfd(new), out, O_RDONLY); - - if (fd != -1) { - if (0<(len = read(fd, out, 2047))) { - if (!out[len-1]) len--; - else out[len] = 0; - for (i = 0; inext && field->len<0) i = 0; - else { - i = lenlen) ? len : field->len; - len = abs(i); - } - - // TODO test utf8 fontmetrics - width -= printf(" %*.*s" + (field == TT.fields), i, len, out); - if (!width) break; - } - xputc('\n'); - - return 0; -} - -// Traverse arg_list of csv, calling callback on each value -void comma_args(struct arg_list *al, char *err, - char *(*callback)(char *str, int len)) -{ - char *next, *arg; - int len; - - while (al) { - arg = al->arg; - while ((next = comma_iterate(&arg, &len))) - if ((next = callback(next, len))) - perror_exit("%s '%s'@%ld", err, al->arg, 1+next-al->arg); - al = al->next; - } -} - -static char *parse_o(char *type, int length) -{ - struct strawberry *field; - char *width, *title, *end, *s, *typos[] = { - "F", "S", "UID", "PID", "PPID", "C", "PRI", "NI", "ADDR", "SZ", - "WCHAN", "STIME", "TTY", "TIME", "CMD", "COMMAND", "ELAPSED", "GROUP", - "%CPU", "PGID", "RGROUP", "RUSER", "USER", "VSZ", "RSS", "MAJFL", - "GID", "STAT", "RUID", "RGID", "MINFL" - }; - signed char widths[] = {1,-1,5,5,5,2,3,3,4+sizeof(long),5, - -6,5,-8,8,-27,-27,11,-8, - 4,5,-8,-8,-8,6,5,6, - 8,-5,4,4,6}; - int i, j, k; - - // Get title, length of title, type, end of type, and display width - - // Chip off =name to display - if ((end = strchr(type, '=')) && length>(end-type)) { - title = end+1; - length -= (end-type)+1; - } else { - end = type+length; - title = 0; - } - - // Chip off :width to display - if ((width = strchr(type, ':')) && widthtitle = field->forever, title, length); - field->title[field->len = length] = 0; - } - - if (width) { - field->len = strtol(++width, &title, 10); - if (!isdigit(*width) || title != end) return title; - end = --width; - } - - // Find type - for (i = 0; iwhich = i; - for (j = 0; j<2; j++) { - if (!j) s = typos[i]; - // posix requires alternate names for some fields - else if (-1==(k = stridx((char []){7,14,15,16,18,23,22,0}, i))) continue; - else s = ((char *[]){"NICE","ARGS","COMM","ETIME","PCPU", - "VSIZE","UNAME"})[k]; - - if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break; - } - if (j!=2) break; - } - if (i==ARRAY_LEN(typos)) return type; - if (!field->title) field->title = typos[field->which]; - if (!field->len) field->len = widths[field->which]; - else if (widths[field->which]<0) field->len *= -1; - dlist_add_nomalloc((void *)&TT.fields, (void *)field); - - // Print padded header. - printf(" %*s" + (field == TT.fields), field->len, field->title); - TT.bits |= (i = 1<which); - - return 0; -} - -// Parse -p -s -t -u -U -g -G -static char *parse_rest(char *str, int len) -{ - struct ptr_len *pl = TT.parsing; - long *ll = pl->ptr; - char *end; - int num = 0; - - // numeric: -p, -s - // gg, GG, pp, ss, tt, uu, UU, *parsing; - - // Allocate next chunk of data - if (!(15&pl->len)) - ll = pl->ptr = xrealloc(pl->ptr, sizeof(long)*(pl->len+16)); - - // Parse numerical input - if (isdigit(*str)) { - ll[pl->len] = xstrtol(str, &end, 10); - if (end==(len+str)) num++; - } - - if (pl==&TT.pp || pl==&TT.ss) { - if (num && ll[pl->len]>0) { - pl->len++; - - return 0; - } - } else if (pl==&TT.tt) { - // -t pts = 12,pts/12 tty = /dev/tty2,tty2,S0 - if (!num) { - if (strstart(&str, strcpy(toybuf, "/dev/"))) len -= 5; - if (strstart(&str, "pts/")) { - len -= 4; - num++; - } else if (strstart(&str, "tty")) len -= 3; - } - if (len<256 && (!(end = strchr(str, '/')) || end-str>len)) { - struct stat st; - - end = toybuf + sprintf(toybuf, "/dev/%s", num ? "pts/" : "tty"); - memcpy(end, str, len); - end[len] = 0; - xstat(toybuf, &st); - ll[pl->len++] = st.st_rdev; - - return 0; - } - } else if (len<255) { - char name[256]; - - if (num) { - pl->len++; - - return 0; - } - - memcpy(name, str, len); - name[len] = 0; - if (pl==&TT.gg || pl==&TT.GG) { - struct group *gr = getgrnam(name); - if (gr) { - ll[pl->len++] = gr->gr_gid; - - return 0; - } - } else if (pl==&TT.uu || pl==&TT.UU) { - struct passwd *pw = getpwnam(name); - if (pw) { - ll[pl->len++] = pw->pw_uid; - - return 0; - } - } - } - - // Return error - return str; -} - -void ps_main(void) -{ - int i; - - TT.width = 99999; - if (!(toys.optflags&FLAG_w)) terminal_size(&TT.width, 0); - - // find controlling tty, falling back to /dev/tty if none - for (i = 0; !TT.tty && i<4; i++) { - struct stat st; - int fd = i; - - if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break; - - if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev; - if (i==3) close(fd); - } - - // parse command line options other than -o - TT.parsing = &TT.pp; - comma_args(TT.p, "bad -p", parse_rest); - TT.parsing = &TT.tt; - comma_args(TT.t, "bad -t", parse_rest); - TT.parsing = &TT.ss; - comma_args(TT.s, "bad -s", parse_rest); - TT.parsing = &TT.uu; - comma_args(TT.u, "bad -u", parse_rest); - TT.parsing = &TT.UU; - comma_args(TT.U, "bad -u", parse_rest); - TT.parsing = &TT.gg; - comma_args(TT.g, "bad -g", parse_rest); - TT.parsing = &TT.GG; - comma_args(TT.G, "bad -G", parse_rest); - - // Manual field selection, or default/-f/-l. Also prints header. - if (TT.o) comma_args(TT.o, "-o", parse_o); - else { - struct arg_list al; - - al.next = 0; - if (toys.optflags&FLAG_f) - al.arg = "USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD"; - else if (toys.optflags&FLAG_l) - al.arg = "F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD"; - else al.arg = "PID,TTY,TIME,CMD"; - - comma_args(&al, 0, parse_o); - } - dlist_terminate(TT.fields); - xputc('\n'); - - dirtree_read("/proc", do_ps); - - if (CFG_TOYBOX_FREE) { - free(TT.gg.ptr); - free(TT.GG.ptr); - free(TT.pp.ptr); - free(TT.ss.ptr); - free(TT.tt.ptr); - free(TT.uu.ptr); - free(TT.UU.ptr); - llist_traverse(TT.fields, free); - } -} diff --git a/toys/posix/ps.c b/toys/posix/ps.c new file mode 100644 index 00000000..0dd4952d --- /dev/null +++ b/toys/posix/ps.c @@ -0,0 +1,613 @@ +/* ps.c - show process list + * + * Copyright 2015 Rob Landley + * + * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ps.html + * And http://kernel.org/doc/Documentation/filesystems/proc.txt Table 1-4 + * And linux kernel source fs/proc/array.c function do_task_stat() + * + * Deviations from posix: no -n because /proc/self/wchan exists. + * Posix says default output should have field named "TTY" but if you "-o tty" + * the same field should be called "TT" which is _INSANE_ and I'm not doing it. + * Similarly -f outputs USER but calls it UID (we call it USER). + * It also says that -o "args" and "comm" should behave differently but use + * the same title, which is not the same title as the default output. (No.) + * Select by session id is -s not -g. + * + * Posix defines -o ADDR as "The address of the process" but the process + * start address is a constant on any elf system with mmu. The procps ADDR + * field always prints "-" with an alignment of 1, which is why it has 11 + * characters left for "cmd" in in 80 column "ps -l" mode. On x86-64 you + * need 12 chars, leaving nothing for cmd: I.E. posix 2008 ps -l mode can't + * be sanely implemented on 64 bit Linux systems. In procps there's ps -y + * which changes -l by removing the "F" column and swapping RSS for ADDR, + * leaving 9 chars for cmd, so we're using that as our -l output. + * + * TODO: ps aux (att & bsd style "ps -ax" vs "ps ax" behavior difference) + * TODO: finalize F, remove C + * switch -fl to -y, use "string" instead of constants to set, remove C + * TODO: --sort -Z + * TODO: way too many hardwired constants here, how can I generate them? + * TODO: thread support /proc/$d/task/%d/stat (and -o stat has "l") + * + * Design issue: the -o fields are an ordered array, and the order is + * significant. The array index is used in strawberry->which (consumed + * in do_ps()) and in the bitmasks enabling default fields in ps_main(). + +USE_PS(NEWTOY(ps, "aAdeflo*p*s*t*u*U*g*G*w[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN)) + +config PS + bool "ps" + default y + help + usage: ps [-Aadeflw] [-gG GROUP] [-o FIELD] [-p PID] [-t TTY] [-uU USER] + + List processes. + + Which processes to show (selections may be comma separated lists): + + -A All processes + -a Processes with terminals that aren't session leaders + -d All processes that aren't session leaders + -e Same as -A + -g belonging to GROUPs + -G belonging to real GROUPs (before sgid) + -p PIDs + -s in session IDs + -t attached to selected TTYs + -u owned by USERs + -U owned by real USERs (before suid) + -w Wide output (don't truncate at terminal width) + + Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD) + + -f Full listing (-o USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD) + -l Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD) + -o Output the listed FIELDs, each with optional :size and/or =title + + Available -o FIELDs: + + ADDR Instruction pointer + CMD Command line + ETIME Elapsed time since process start + F Process flags (PF_*) from linux source file include/sched.h + (in octal rather than hex because posix) + GID Group id + GROUP Group name + MAJFL Major page faults + MINFL Minor page faults + NI Niceness of process (lower niceness is higher priority) + PCPU Percentage of CPU time used + PGID Process Group ID + PID Process ID + PPID Parent Process ID + PRI Priority + RGID Real (before sgid) group ID + RGROUP Real (before sgid) group name + RSS Resident Set Size (memory currently used) + RUID Real (before suid) user ID + RUSER Real (before suid) user name + S Process state: + R (running) S (sleeping) D (disk sleep) T (stopped) t (traced) + Z (zombie) X (dead) x (dead) K (wakekill) W (waking) + STAT Process state (S) plus: + < high priority N low priority L locked memory + s session leader + foreground l multithreaded + STIME Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss) + SZ Memory Size (4k pages needed to completely swap out process) + TIME CPU time consumed + TTY Controlling terminal + UID User id + USER User name + VSZ Virtual memory size (1k units) + WCHAN Waiting in kernel for +*/ + +#define FOR_ps +#include "toys.h" + +GLOBALS( + struct arg_list *G; + struct arg_list *g; + struct arg_list *U; + struct arg_list *u; + struct arg_list *t; + struct arg_list *s; + struct arg_list *p; + struct arg_list *o; + + struct ptr_len gg, GG, pp, ss, tt, uu, UU, *parsing; + unsigned width; + dev_t tty; + void *fields; + long bits; + long long ticks; +) + +struct strawberry { + struct strawberry *next, *prev; + short which, len; + char *title; + char forever[]; +}; + +static time_t get_uptime(void) +{ + struct sysinfo si; + + sysinfo(&si); + + return si.uptime; +} + +// Return 1 to display, 0 to skip +static int match_process(long long *slot) +{ + struct ptr_len *match[] = {&TT.gg,&TT.GG,&TT.pp,&TT.ss,&TT.tt,&TT.uu,&TT.UU}; + int i, j, mslot[] = {33, 34, 0, 3, 4, 31, 32}; + long *ll = 0; + + // Do we have -g -G -p -s -t -u -U options selecting processes? + for (i = 0; i < ARRAY_LEN(match); i++) { + if (match[i]->len) { + ll = match[i]->ptr; + for (j = 0; jlen; j++) if (ll[j] == slot[mslot[i]]) return 1; + } + } + + // If we had selections and didn't match them, don't display + if (ll) return 0; + + // Filter implicit categories for other display types + if ((toys.optflags&(FLAG_a|FLAG_d)) && slot[3]==*slot) return 0; + if ((toys.optflags&FLAG_a) && !slot[4]) return 0; + if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e)) && TT.tty!=slot[4]) + return 0; + + return 1; +} + +// dirtree callback. +// toybuf used as: 1024 /proc/$PID/stat, 1024 slot[], 2048 /proc/$PID/cmdline +static int do_ps(struct dirtree *new) +{ + struct strawberry *field; + long long *slot = (void *)(toybuf+1024), ll; + char *name, *s, state; + int nlen, i, fd, len, width = TT.width; + + if (!new->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP; + if (!(*slot = atol(new->name))) return 0; + + // name field limited to 256 bytes by VFS, plus 40 fields * max 20 chars: + // 1000-ish total, but some forced zero so actually there's headroom. + sprintf(toybuf, "%lld/stat", *slot); + if (!readfileat(dirtree_parentfd(new), toybuf, toybuf, 1024)) return 0; + + // parse oddball fields (name and state) + if (!(s = strchr(toybuf, '('))) return 0; + for (name = ++s; *s != ')'; s++) if (!*s) return 0; + nlen = s++-name; + if (1>sscanf(++s, " %c%n", &state, &i)) return 0; + + // parse numeric fields (PID = 0, skip 2, then 4th field goes in slot[1]) + for (len = 1; len<100; len++) + if (1>sscanf(s += i, " %lld%n", slot+len, &i)) break; + + // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch + // or numeric wchan, and the remaining two are always zero). + slot[31] = new->st.st_uid; + slot[33] = new->st.st_gid; + + // If RGROUP RUSER STAT RUID RGID + // Save ruid in slot[34] and rgid in slot[35], which are otherwise zero, + // and vmlck into slot[18] (it_real_value, also always zero). + if ((TT.bits & 0x38300000) || TT.GG.len || TT.UU.len) { + char *out = toybuf+2048; + + sprintf(out, "%lld/status", *slot); + if (!readfileat(dirtree_parentfd(new), out, out, 2048)) *out = 0; + s = strstr(out, "\nUid:"); + slot[32] = s ? atol(s+5) : new->st.st_uid; + s = strstr(out, "\nGid:"); + slot[34] = s ? atol(s+5) : new->st.st_gid; + s = strstr(out, "\nVmLck:"); + if (s) slot[18] = atoll(s+5); + } + + // skip processes we don't care about. + if (!match_process(slot)) return 0; + + // At this point 512 bytes at toybuf+512 are free (already parsed). + // Start of toybuf still has name in it. + + // Loop through fields + for (field = TT.fields; field; field = field->next) { + char *out = toybuf+2048, *scratch = toybuf+512; + + // Default: unsupported (5 "C") + sprintf(out, "-"); + + // PID, PPID, PRI, NI, ADDR, SZ, RSS, PGID, VSS, MAJFL, MINFL + if (-1!=(i = stridx((char[]){3,4,6,7,8,9,24,19,23,25,30,0}, field->which))) + { + char *fmt = "%lld"; + + ll = slot[((char[]){0,1,15,16,27,20,21,2,20,9,7})[i]]; + if (i==2) ll--; + if (i==4) fmt = "%llx"; + else if (i==5) ll >>= 12; + else if (i==6) ll <<= 2; + else if (i==8) ll >>= 10; + sprintf(out, fmt, ll); + // UID USER RUID RUSER GID GROUP RGID RGROUP + } else if (-1!=(i = stridx((char[]){2,22,28,21,26,17,29,20}, field->which))) + { + int id = slot[31+i/2]; // uid, ruid, gid, rgid + + // Even entries are numbers, odd are names + sprintf(out, "%d", id); + if (i&1) { + if (i>3) { + struct group *gr = getgrgid(id); + + if (gr) out = gr->gr_name; + } else { + struct passwd *pw = getpwuid(id); + + if (pw) out = pw->pw_name; + } + } + + // F (also assignment of i used by later tests) + // Posix doesn't specify what flags should say. Man page says + // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h + } else if (!(i = field->which)) sprintf(out, "%llo", (slot[6]>>6)&5); + // S STAT + else if (i==1 || i==27) { + sprintf(out, "%c", state); + if (i==27) { + // TODO l = multithreaded + s = out+1; + if (slot[16]<0) *s++ = '<'; + else if (slot[16]>0) *s++ = 'N'; + if (slot[3]==*slot) *s++ = 's'; + if (slot[18]) *s++ = 'L'; + if (slot[5]==*slot) *s++ = '+'; + *s = 0; + } + // WCHAN + } else if (i==10) { + sprintf(scratch, "%lld/wchan", *slot); + readfileat(dirtree_parentfd(new), scratch, out, 2047); + + // STIME + } else if (i==11) { + time_t t = time(0) - get_uptime() + slot[19]/sysconf(_SC_CLK_TCK); + + // Padding behavior's a bit odd: default field size is just hh:mm. + // Increasing stime:size reveals more data at left until full + // yyyy-mm-dd hh:mm revealed at :16, then adds :ss at end for :19. But + // expanding last field just adds :ss. + strftime(scratch, 512, "%F %T", localtime(&t)); + out = scratch+strlen(scratch)-3-abs(field->len); + if (out>8)&0xfff, ((i>>12)&0xfff00)|(i&0xff)); + } + + // TIME ELAPSED + } else if (i==13 || i==16) { + int unit = 60*60*24, j = sysconf(_SC_CLK_TCK); + time_t seconds = (i==16) ? (get_uptime()*j)-slot[19] : slot[11]+slot[12]; + + seconds /= j; + for (s = 0, j = 0; j<4; j++) { + // TIME has 3 required fields, ETIME has 2. (Posix!) + if (!s && (seconds>unit || j == 1+(i==16))) s = out; + if (s) { + s += sprintf(s, j ? "%02ld": "%2ld", (long)(seconds/unit)); + if ((*s = "-::"[j])) s++; + } + seconds %= unit; + unit /= j ? 60 : 24; + } + + // COMMAND CMD + // Command line limited to 2k displayable. We could dynamically malloc, but + // it'd almost never get used, querying length of a proc file is awkward, + // fixed buffer is nommu friendly... Wait for somebody to complain. :) + } else if (i==14 || i==15) { + int fd; + + len = 0; + sprintf(out, "%lld/cmdline", *slot); + fd = openat(dirtree_parentfd(new), out, O_RDONLY); + + if (fd != -1) { + if (0<(len = read(fd, out, 2047))) { + if (!out[len-1]) len--; + else out[len] = 0; + for (i = 0; inext && field->len<0) i = 0; + else { + i = lenlen) ? len : field->len; + len = abs(i); + } + + // TODO test utf8 fontmetrics + width -= printf(" %*.*s" + (field == TT.fields), i, len, out); + if (!width) break; + } + xputc('\n'); + + return 0; +} + +// Traverse arg_list of csv, calling callback on each value +void comma_args(struct arg_list *al, char *err, + char *(*callback)(char *str, int len)) +{ + char *next, *arg; + int len; + + while (al) { + arg = al->arg; + while ((next = comma_iterate(&arg, &len))) + if ((next = callback(next, len))) + perror_exit("%s '%s'@%ld", err, al->arg, 1+next-al->arg); + al = al->next; + } +} + +static char *parse_o(char *type, int length) +{ + struct strawberry *field; + char *width, *title, *end, *s, *typos[] = { + "F", "S", "UID", "PID", "PPID", "C", "PRI", "NI", "ADDR", "SZ", + "WCHAN", "STIME", "TTY", "TIME", "CMD", "COMMAND", "ELAPSED", "GROUP", + "%CPU", "PGID", "RGROUP", "RUSER", "USER", "VSZ", "RSS", "MAJFL", + "GID", "STAT", "RUID", "RGID", "MINFL" + }; + signed char widths[] = {1,-1,5,5,5,2,3,3,4+sizeof(long),5, + -6,5,-8,8,-27,-27,11,-8, + 4,5,-8,-8,-8,6,5,6, + 8,-5,4,4,6}; + int i, j, k; + + // Get title, length of title, type, end of type, and display width + + // Chip off =name to display + if ((end = strchr(type, '=')) && length>(end-type)) { + title = end+1; + length -= (end-type)+1; + } else { + end = type+length; + title = 0; + } + + // Chip off :width to display + if ((width = strchr(type, ':')) && widthtitle = field->forever, title, length); + field->title[field->len = length] = 0; + } + + if (width) { + field->len = strtol(++width, &title, 10); + if (!isdigit(*width) || title != end) return title; + end = --width; + } + + // Find type + for (i = 0; iwhich = i; + for (j = 0; j<2; j++) { + if (!j) s = typos[i]; + // posix requires alternate names for some fields + else if (-1==(k = stridx((char []){7,14,15,16,18,23,22,0}, i))) continue; + else s = ((char *[]){"NICE","ARGS","COMM","ETIME","PCPU", + "VSIZE","UNAME"})[k]; + + if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break; + } + if (j!=2) break; + } + if (i==ARRAY_LEN(typos)) return type; + if (!field->title) field->title = typos[field->which]; + if (!field->len) field->len = widths[field->which]; + else if (widths[field->which]<0) field->len *= -1; + dlist_add_nomalloc((void *)&TT.fields, (void *)field); + + // Print padded header. + printf(" %*s" + (field == TT.fields), field->len, field->title); + TT.bits |= (i = 1<which); + + return 0; +} + +// Parse -p -s -t -u -U -g -G +static char *parse_rest(char *str, int len) +{ + struct ptr_len *pl = TT.parsing; + long *ll = pl->ptr; + char *end; + int num = 0; + + // numeric: -p, -s + // gg, GG, pp, ss, tt, uu, UU, *parsing; + + // Allocate next chunk of data + if (!(15&pl->len)) + ll = pl->ptr = xrealloc(pl->ptr, sizeof(long)*(pl->len+16)); + + // Parse numerical input + if (isdigit(*str)) { + ll[pl->len] = xstrtol(str, &end, 10); + if (end==(len+str)) num++; + } + + if (pl==&TT.pp || pl==&TT.ss) { + if (num && ll[pl->len]>0) { + pl->len++; + + return 0; + } + } else if (pl==&TT.tt) { + // -t pts = 12,pts/12 tty = /dev/tty2,tty2,S0 + if (!num) { + if (strstart(&str, strcpy(toybuf, "/dev/"))) len -= 5; + if (strstart(&str, "pts/")) { + len -= 4; + num++; + } else if (strstart(&str, "tty")) len -= 3; + } + if (len<256 && (!(end = strchr(str, '/')) || end-str>len)) { + struct stat st; + + end = toybuf + sprintf(toybuf, "/dev/%s", num ? "pts/" : "tty"); + memcpy(end, str, len); + end[len] = 0; + xstat(toybuf, &st); + ll[pl->len++] = st.st_rdev; + + return 0; + } + } else if (len<255) { + char name[256]; + + if (num) { + pl->len++; + + return 0; + } + + memcpy(name, str, len); + name[len] = 0; + if (pl==&TT.gg || pl==&TT.GG) { + struct group *gr = getgrnam(name); + if (gr) { + ll[pl->len++] = gr->gr_gid; + + return 0; + } + } else if (pl==&TT.uu || pl==&TT.UU) { + struct passwd *pw = getpwnam(name); + if (pw) { + ll[pl->len++] = pw->pw_uid; + + return 0; + } + } + } + + // Return error + return str; +} + +void ps_main(void) +{ + int i; + + TT.width = 99999; + if (!(toys.optflags&FLAG_w)) terminal_size(&TT.width, 0); + + // find controlling tty, falling back to /dev/tty if none + for (i = 0; !TT.tty && i<4; i++) { + struct stat st; + int fd = i; + + if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break; + + if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev; + if (i==3) close(fd); + } + + // parse command line options other than -o + TT.parsing = &TT.pp; + comma_args(TT.p, "bad -p", parse_rest); + TT.parsing = &TT.tt; + comma_args(TT.t, "bad -t", parse_rest); + TT.parsing = &TT.ss; + comma_args(TT.s, "bad -s", parse_rest); + TT.parsing = &TT.uu; + comma_args(TT.u, "bad -u", parse_rest); + TT.parsing = &TT.UU; + comma_args(TT.U, "bad -u", parse_rest); + TT.parsing = &TT.gg; + comma_args(TT.g, "bad -g", parse_rest); + TT.parsing = &TT.GG; + comma_args(TT.G, "bad -G", parse_rest); + + // Manual field selection, or default/-f/-l. Also prints header. + if (TT.o) comma_args(TT.o, "-o", parse_o); + else { + struct arg_list al; + + al.next = 0; + if (toys.optflags&FLAG_f) + al.arg = "USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD"; + else if (toys.optflags&FLAG_l) + al.arg = "F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD"; + else al.arg = "PID,TTY,TIME,CMD"; + + comma_args(&al, 0, parse_o); + } + dlist_terminate(TT.fields); + xputc('\n'); + + dirtree_read("/proc", do_ps); + + if (CFG_TOYBOX_FREE) { + free(TT.gg.ptr); + free(TT.GG.ptr); + free(TT.pp.ptr); + free(TT.ss.ptr); + free(TT.tt.ptr); + free(TT.uu.ptr); + free(TT.UU.ptr); + llist_traverse(TT.fields, free); + } +} -- cgit v1.2.3