From 8b64a3e64775ddf779f828560543393bdcf2f31d Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Fri, 18 Dec 2015 16:39:59 -0600 Subject: Next giant chunk of ps work: add _NAME_TAG bitmask values to tags.h (leading underscore version is 1<31 shift), suck lots of magic constants out of ps and use tag macros instead, redo command line display so there's now 6 variants (CMD COMM ARGS from posix, NAME CMDLINE from android, and COMMAND for completeness). Document more cases where posix is nuts or widely ignored. --- scripts/mktags.c | 6 +- toys/posix/ps.c | 272 ++++++++++++++++++++++++++++++------------------------- 2 files changed, 153 insertions(+), 125 deletions(-) diff --git a/scripts/mktags.c b/scripts/mktags.c index e6fceeab..3604260c 100644 --- a/scripts/mktags.c +++ b/scripts/mktags.c @@ -47,8 +47,10 @@ int main(int argc, char *argv[]) if (!isalpha(*s) && !isdigit(*s)) *s = '_'; s++; } - printf("% *d\n", - 30-printf("#define %s_%.*s", tag, (int)(s-start), start), ++idx); + printf("#define %s_%*.*s %d\n", tag, -40, (int)(s-start), start, idx); + printf("#define _%s_%*.*s (1%s<<%d)\n", tag, -39, (int)(s-start), start, + idx>31 ? "LL": "", idx); + idx++; } free(line); } diff --git a/toys/posix/ps.c b/toys/posix/ps.c index be8d586a..2c1843ba 100644 --- a/toys/posix/ps.c +++ b/toys/posix/ps.c @@ -24,14 +24,14 @@ * 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. * + * Added a bunch of new -o fields posix doesn't mention, and we don't + * label "ps -o command,args,comm" as "COMMAND COMMAND COMMAND". We don't + * output argv[0] unmodified for -o comm or -o args (but procps violates + * posix for -o comm anyway, it's stat[2] not argv[0]). + * * TODO: ps aux (att & bsd style "ps -ax" vs "ps ax" behavior difference) * TODO: switch -fl to -y - * 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 TT.bits bitmask. USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflno*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN)) USE_TTOP(NEWTOY(ttop, ">0d#=3n#<1mb", TOYFLAG_USR|TOYFLAG_BIN)) @@ -75,32 +75,34 @@ config PS Available -o FIELDs: ADDR Instruction pointer - CMD Command name (original) - CMDLINE Command name (current argv[0]) - COMM Command line (with arguments) - CPU Which processor is process running on + ARGS Command line (argv[0-X] minus path) + CMD COMM without -f, ARGS with -f + CMDLINE Command line (argv[0-X]) + COMM Original command name + COMMAND Original command path + CPU Which processor running on ETIME Elapsed time since process start - F Process flags (PF_*) from linux source file include/sched.h - (in octal rather than hex because posix) + F Flags (1=FORKNOEXEC, 4=SUPERPRIV) GID Group id GROUP Group name LABEL Security label MAJFL Major page faults MINFL Minor page faults - NI Niceness of process (lower niceness is higher priority) + NAME Command name (argv[0]) + NI Niceness (lower is faster) PCPU Percentage of CPU time used PGID Process Group ID PID Process ID PPID Parent Process ID - PRI Priority + PRI Priority (higher is faster) RGID Real (before sgid) group ID RGROUP Real (before sgid) group name - RSS Resident Set Size (memory currently used) + RSS Resident Set Size (memory in use) 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) + R (running) S (sleeping) D (device I/O) T (stopped) t (traced) + Z (zombie) X (deader) 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 @@ -209,26 +211,38 @@ struct strawberry { // Data layout in toybuf struct carveup { long long slot[50]; // data from /proc, skippint #2 and #3 - unsigned short offset[4]; // offset of fields in str[] (skip name, always 0) + unsigned short offset[5]; // offset of fields in str[] (skip name, always 0) char state; - char str[]; // name, tty, wchan, attr, cmdline + char str[]; // name, tty, command, wchan, attr, cmdline }; // TODO: Android uses -30 for LABEL, but ideally it would auto-size. struct typography { char *name; signed char width, slot; + } static const typos[] = TAGGED_ARRAY(PS, - {"F", 1, 64|6}, {"S", -1, 64}, {"UID", 5, 31}, {"PID", 5, 0}, - {"PPID", 5, 1}, {"C", 2, 0}, {"PRI", 3, 15}, {"NI", 3, 16}, - {"ADDR", 4+sizeof(long), 27}, {"SZ", 5, 20}, {"WCHAN", -6, -3}, {"STIME", 5, 19}, - {"TTY", -8, -2}, {"TIME", 8, 11}, {"CMD", -27, -1}, {"COMMAND", -27, -5}, - {"ELAPSED", 11, 19}, {"GROUP", -8, 64|33}, {"%CPU", 4, 64}, {"PGID", 5, 2}, - {"RGROUP", -8, 64|34}, {"RUSER", -8, 64|32}, {"USER", -8, 64|31}, {"VSZ", 6, 20}, - {"RSS", 5, 21}, {"MAJFL", 6, 9}, {"GID", 8, 33}, {"STAT", -5, 64}, - {"RUID", 4, 32}, {"RGID", 4, 34}, {"MINFL", 6, 7}, {"LABEL", -30, -4}, - {"CMDLINE", -27, -5}, {"%VSZ", 5, 23}, {"PR", 2, 15}, {"VIRT", 4, 47}, - {"RES", 4, 48}, {"SHR", 4, 49}, {"TIME+", 9, 11} + // stat#s: PID, PPID, PRI, NI, ADDR, SZ, RSS, PGID, VSZ, MAJFL, MINFL, PR + {"PID", 5, 0}, {"PPID", 5, 1}, {"PRI", 3, 15}, {"NI", 3, 16}, + {"ADDR", 4+sizeof(long), 27}, {"SZ", 5, 20}, {"RSS", 5, 21}, {"PGID", 5, 2}, + {"VSZ", 6, 20}, {"MAJFL", 6, 9}, {"MINFL", 6, 7}, {"PR", 2, 15}, + + // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP + {"UID", 5, 31}, {"USER", -8, 64|31}, {"RUID", 4, 32}, {"RUSER", -8, 64|32}, + {"GID", 8, 33}, {"GROUP", -8, 64|33}, {"RGID", 4, 34}, {"RGROUP", -8, 64|34}, + + // CMD TTY WCHAN LABEL CMDLINE COMMAND + {"COMM", -15, -1}, {"TTY", -8, -2}, {"WCHAN", -6, -3}, {"LABEL", -30, -4}, + {"COMMAND", -27, -5}, {"CMDLINE", -27, -6}, {"ARGS", -27, -6}, + {"NAME", -15, -6}, {"CMD", -27, -1}, + + // TIME ELAPSED TIME+ + {"TIME", 8, 11}, {"ELAPSED", 11, 19}, {"TIME+", 9, 11}, + + // Remaining ungrouped + {"STIME", 5, 19}, {"F", 1, 64|6}, {"S", -1, 64}, {"C", 1, 0}, {"%CPU", 4, 64}, + {"STAT", -5, 64}, {"%VSZ", 5, 23}, {"VIRT", 4, 47}, {"RES", 4, 48}, + {"SHR", 4, 49} ); // Return 1 to keep, 0 to discard @@ -262,80 +276,88 @@ static int match_process(long long *slot) return 1; } +// Convert field to string representation static char *string_field(struct carveup *tb, struct strawberry *field) { char *buf = toybuf+sizeof(toybuf)-260, *out = buf, *s; - long long ll, *slot = tb->slot; - int i, which = field->which; + int which = field->which, sl = typos[which].slot; + long long *slot = tb->slot, ll = (sl >= 0) ? slot[sl&63] : 0; // Default: unsupported (5 "C") sprintf(out, "-"); // stat#s: PID, PPID, PRI, NI, ADDR, SZ, RSS, PGID, VSZ, MAJFL, MINFL, PR - if (-1!=(i = stridx((char[]){3,4,6,7,8,9,24,19,23,25,30,34,0}, which))) - { + if (which <= PS_PR) { char *fmt = "%lld"; - ll = slot[((char[]){0,1,15,16,27,20,21,2,20,9,7,15})[i]]; - if (i==2) ll = 39-ll; - if (i==4) fmt = "%llx"; - else if (i==5) ll >>= 12; - else if (i==6) ll <<= 2; - else if (i==8) ll >>= 10; - else if (i==11) if (ll<-9) fmt="RT"; + if (which==PS_PRI) ll = 39-ll; + if (which==PS_ADDR) fmt = "%llx"; + else if (which==PS_SZ) ll >>= 12; + else if (which==PS_RSS) ll <<= 2; + else if (which==PS_VSZ) ll >>= 10; + else if (which==PS_PR) if (ll<-9) fmt="RT"; sprintf(out, fmt, ll); // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP - } else if (-1!=(i = stridx((char[]){2,22,28,21,26,17,29,20,0}, which))) - { - int id = slot[31+i/2]; // uid, ruid, gid, rgid - - // Even entries are numbers, odd are names - sprintf(out, "%d", id); - if (!(toys.optflags&FLAG_n) && i&1) { - if (i>3) { - struct group *gr = getgrgid(id); + } else if (which <= PS_RGROUP) { + sprintf(out, "%lld", ll); + if (!(toys.optflags&FLAG_n) && (sl&64)) { + if (which > PS_RUSER) { + struct group *gr = getgrgid(ll); if (gr) out = gr->gr_name; } else { - struct passwd *pw = getpwuid(id); + struct passwd *pw = getpwuid(ll); if (pw) out = pw->pw_name; } } - // CMD TTY WCHAN LABEL (CMDLINE handled elsewhere) - } else if (-1!=(i = stridx((char[]){15,12,10,31,0}, which))) { - out = tb->str; - if (i) out += tb->offset[i-1]; + // COMM TTY WCHAN LABEL COMMAND CMDLINE ARGS NAME CMD + + // CMD TTY WCHAN LABEL CMDLINE COMMAND COMM NAME + } else if (sl < 0) { + if (which==PS_CMD && (toys.optflags&FLAG_f)) sl = typos[which=PS_ARGS].slot; + if (slot[45]) + tb->str[tb->offset[4]+slot[45]] = (which == PS_NAME) ? 0 : ' '; + out = tb->str; + sl *= -1; + if (--sl) out += tb->offset[--sl]; + if (which==PS_ARGS) + for (s = out; *s && *s != ' '; s++) if (*s == '/') out = s+1; + if (which>=PS_COMMAND && !*out) sprintf(out = buf, "[%s]", tb->str); // TIME ELAPSED TIME+ - } else if (-1!=(i = stridx((char[]){13,16,38,0}, which))) { - int unit = 60*60*24, j = TT.ticks; - time_t seconds = (i==1) ? (slot[46]*j)-slot[19] : slot[11]; - - 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)) s = out; + } else if (which <= PS_TIME_) { + int unit = 60, pad = 2, j = TT.ticks; + time_t seconds; + + if (which!=PS_TIME_) unit *= 60*24; + else pad = 0; + if (which==PS_ELAPSED) ll = (slot[46]*j)-slot[19]; + seconds = ll/j; + + // Output days-hours:mins:secs, skipping non-required fields with zero + // TIME has 3 required fields, ETIME has 2. (Posix!) TIME+ is from top + for (s = 0, j = 2*(which==PS_TIME_); j<4; j++) { + if (!s && (seconds>unit || j == 1+(which!=PS_TIME))) s = out; if (s) { - s += sprintf(s, j ? "%02ld": "%2ld", (long)(seconds/unit)); + s += sprintf(s, j ? "%0*ld": "%*ld", pad, (long)(seconds/unit)); + pad = 2; if ((*s = "-::"[j])) s++; } seconds %= unit; unit /= j ? 60 : 24; } - if (i==2 && s-out<8) - sprintf(s, ".%02lld", (100*(slot[11]%TT.ticks))/TT.ticks); + if (which==PS_TIME_ && s-out<8) + sprintf(s, ".%02lld", (100*(ll%TT.ticks))/TT.ticks); - // 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 (!which) sprintf(out, "%llo", (slot[6]>>6)&5); - // S STAT - else if (which==1 || which==27) { + } else if (which==PS_F) sprintf(out, "%llo", (slot[6]>>6)&5); + else if (which==PS_S || which==PS_STAT) { s = out; *s++ = tb->state; - if (which==27) { + if (which==PS_STAT) { // TODO l = multithreaded if (slot[16]<0) *s++ = '<'; else if (slot[16]>0) *s++ = 'N'; @@ -344,8 +366,7 @@ static char *string_field(struct carveup *tb, struct strawberry *field) if (slot[5]==*slot) *s++ = '+'; } *s = 0; - // STIME - } else if (which==11) { + } else if (which==PS_STIME) { time_t t = time(0)-slot[46]+slot[19]/TT.ticks; // Padding behavior's a bit odd: default field size is just hh:mm. @@ -355,26 +376,14 @@ static char *string_field(struct carveup *tb, struct strawberry *field) strftime(out, 260, "%F %T", localtime(&t)); out = out+strlen(out)-3-abs(field->len); if (outstr); - else { - out = tb->str+tb->offset[3]; - if (slot[45]!=INT_MAX) out[slot[45]] = ' '*(i==14); - } - - // %CPU %VSZ - } else if (which==18 || which==33) { - if (which==18) { + } else if (which==PS__CPU || which==PS__VSZ) { + if (which==PS__CPU) { ll = (slot[46]*TT.ticks-slot[19]); - i = (slot[11]*1000)/ll; - } else i = (slot[23]*1000)/TT.si.totalram; - sprintf(out, "%d.%d", i/10, i%10); - } else if (which>=35 && which<=37) - human_readable(out, slot[which-35+47]*sysconf(_SC_PAGESIZE), 0); + sl = (slot[11]*1000)/ll; + } else sl = (slot[23]*1000)/TT.si.totalram; + sprintf(out, "%d.%d", sl/10, sl%10); + } else if (which==PS_VIRT || which==PS_RES || which==PS_SHR) + human_readable(out, slot[typos[which].slot]*sysconf(_SC_PAGESIZE), 0); return out; } @@ -412,8 +421,11 @@ static int get_ps(struct dirtree *new) struct { char *name; long long bits; - } fetch[] = {{"fd/", 1<<12}, {"wchan", 1<<10}, {"attr/current", 1<<31}, - {"cmdline", (1<<14)|(1LL<<32)}}; + } fetch[] = { + {"fd/", _PS_TTY}, {"wchan", _PS_WCHAN}, {"attr/current", _PS_LABEL}, + {"exe", _PS_COMMAND}, {"cmdline", + (_PS_CMD*!!(toys.optflags&FLAG_f))|_PS_CMDLINE|_PS_ARGS|_PS_NAME} + }; struct carveup *tb = (void *)toybuf; long long *slot = tb->slot; char *name, *s, *buf = tb->str, *end = 0; @@ -441,9 +453,9 @@ static int get_ps(struct dirtree *new) for (j = 1; j<50; j++) if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break; // Now we've read the data, move status and name right after slot[] array, - // and convert low chars to spaces while we're at it. + // and convert low chars to ? while we're at it. for (i = 0; istr[i] = name[i]) < ' ') tb->str[i] = ' '; + if ((tb->str[i] = name[i]) < ' ') tb->str[i] = '?'; buf = tb->str+i; *buf++ = 0; len = sizeof(toybuf)-(buf-toybuf); @@ -459,7 +471,9 @@ static int get_ps(struct dirtree *new) // If RGROUP RUSER STAT RUID RGID happening, or -G or -U, parse "status" // and save ruid, rgid, and vmlck. - if ((TT.bits & 0x38300000) || TT.GG.len || TT.UU.len) { + if ((TT.bits&(_PS_RGROUP|_PS_RUSER|_PS_STAT|_PS_RUID|_PS_RGID)) + || TT.GG.len || TT.UU.len) + { off_t temp = len; sprintf(buf, "%lld/status", *slot); @@ -475,8 +489,8 @@ static int get_ps(struct dirtree *new) // We now know enough to skip processes we don't care about. if (!match_process(slot)) return 0; - // Fetch VIRT RES SHR (for top) - if (TT.bits & (7LL<<35)) { + // Do we need to read "statm"? + if (TT.bits&(_PS_VIRT|_PS_RES|_PS_SHR)) { off_t temp = len; sprintf(buf, "%lld/statm", *slot); @@ -492,10 +506,11 @@ static int get_ps(struct dirtree *new) sysinfo(&TT.si); slot[46] = TT.si.uptime; - // fetch remaining data while parentfd still available, appending to buf. + // Fetch string data while parentfd still available, appending to buf. // (There's well over 3k of toybuf left. 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. :) + slot[45] = 0; for (j = 0; joffset[j] = buf-(tb->str); if (!(TT.bits&fetch[j].bits)) { @@ -508,29 +523,13 @@ static int get_ps(struct dirtree *new) len = sizeof(toybuf)-(buf-toybuf)-260-256*(ARRAY_LEN(fetch)-j); sprintf(buf, "%lld/%s", *slot, fetch[j].name); + // For cmdline we readlink instead of read contents + if (j==3) { + if (0>=(len = readlinkat(fd, buf, buf, len))) buf[len] = 0; + // If it's not the TTY field, data we want is in a file. // Last length saved in slot[] is command line (which has embedded NULs) - if (j) { - readfileat(fd, buf, buf, &len); - - // When command has no arguments, don't space over the NUL - if (len>0) { - int temp = 0; - - if (buf[len-1]=='\n') buf[--len] = 0; - - // Always escape spaces because an executable named esc[0m would be bad. - // Escaping low ascii does not affect utf8. - for (i=0; i0) { + int temp = 0; + + if (buf[len-1]=='\n') buf[--len] = 0; + + // Turn NUL to space, other low ascii to ? + for (i=0; i