# HG changeset patch # User Rob Landley # Date 1428005256 18000 # Node ID 22c10aa56cfb04dc66360ab7019c20c29716fd5c # Parent ebc947f27ef3ff27897bed59bae67ec81846e1d0 Redoing ps from scratch. Random checkpoint that probably doesn't even compile. Checking in a stopping point where I figured out what I did wrong, before changing it. diff -r ebc947f27ef3 -r 22c10aa56cfb toys/pending/ps.c --- a/toys/pending/ps.c Sun Mar 29 12:03:46 2015 -0500 +++ b/toys/pending/ps.c Thu Apr 02 15:07:36 2015 -0500 @@ -1,410 +1,217 @@ -/* ps.c - Show running process statistics. +/* ps.c - show process list * - * Copyright 2013 Sandeep Sharma - * Copyright 2013 Kyungwan Han + * 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() + * + * Deviation from posix: no -n -USE_PS(NEWTOY(ps, ">0o*T", TOYFLAG_BIN)) +USE_PS(NEWTOY(ps, "aAdeo*", TOYFLAG_USR|TOYFLAG_BIN)) config PS bool "ps" default n help - usage: ps [-o COL1,COL2=HEADER] [-T] - - Show list of processes + usage: ps [-Aade] [-fl] [-gG GROUP] [-o FIELD] [-p PID] [-t TTY] [-u USER] + + List processes. + + -A All processes + -a Processes with terminals, except session leaders + -d Processes that aren't session leaders + -e Same as -A + - -a Show all processes with a tty - -A Show all processes - -o Select columns for display - -T Show threads + -f Full listing + -g Processes belonging to these session leaders + -G Processes with these real group IDs + -l Long listing + -o Show FIELDS for each process + -p select by PID + -t select by TTY + -u select by USER + -U select by USER + + GROUP, FIELD, PID, TTY, and USER are comma separated lists. + + OUTPUT (-o) FIELDS: + + S Linux defines the process state letters as: + R (running) S (sleeping) D (disk sleep) T (stopped) t (tracing stop) + Z (zombie) X (dead) x (dead) K (wakekill) W (waking) + F Process flags (PF_*) from linux source file include/sched.h + (in octal rather than hex because posix inexplicably says so) + + Default is PID,TTY,TIME,CMD With -f UID,PID,PPID,C,STIME,TTY,TIME,CMD + With -l F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD */ #define FOR_ps #include "toys.h" GLOBALS( - struct arg_list *llist_o; - unsigned screen_width; + struct arg_list *o; - void *o; + unsigned width; + dev_t tty; + void *oo; ) -#define BUFF_SIZE 1024 -struct header_list { - struct header_list *next; - char *name, *header, *format; - int width, position; -}; - -// create list of header attributes taking care of -o (-o ooid=MOM..) -// and width of attributes. -static void list_add(struct header_list *data, char *c_data) -{ - struct header_list *temp = TT.o, *new; - - memcpy(new = xmalloc(sizeof(*new)), data, sizeof(*new)); - if (c_data) new->header = c_data; - else new->header = xstrdup(data->header); - if (c_data && (strlen(c_data) > data->width)) new->width = strlen(c_data); - - if (temp) { - while (temp->next) temp = temp->next; - temp->next = new; - } else TT.o = new; -} - -// parse -o arguments -static void parse_o(struct header_list *hdr) -{ - int i; - char *ptr, *str, *temp; - struct arg_list *node = TT.llist_o; - - while (node) { - char *s = str = xstrdup(node->arg); - - i = 0; - while (str) { - if ((ptr = strsep(&str, ","))) { //seprate list - if ((temp = strchr(ptr, '='))) { // Handle ppid = MOM - *temp++ = 0; - while (hdr[i].name) { - // search from default header - if (!(strcmp(hdr[i].name, ptr))) { - //handle condition like ppid = M,OM - if (str) ptr = xmprintf("%s,%s", temp, str); - else ptr = xmprintf("%s", temp); - list_add(hdr+i, ptr); - break; - } - i++; - } - if (!hdr[i].name) perror_exit("Invalid arg for -o option"); - break; - } else { - while (hdr[i].name) { - if (!(strcmp(hdr[i].name, ptr))) { - list_add(hdr+i, 0); - break; - } - i++; - } - if (!hdr[i].name) error_exit("bad -o"); - i = 0; - } - } - } - free(s); - node = node->next; - } -} +/* + l l fl a fl fl l l l l l f a a a + F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD + ruser user rgroup group pid ppid pgid pcpu vsz nice etime time tty comm args -//get uid/gid for processes. -static void get_uid_gid(char *p, char *id_str, unsigned *id) -{ - FILE *f; - - if(!p) return; - f = xfopen(p, "r"); - while (fgets(toybuf, BUFF_SIZE, f)) { - if (!strncmp(toybuf, id_str, strlen(id_str))) { - sscanf(toybuf, "%*s %u", id); - break; - } - } - fclose(f); -} - -//get etime for processes. -void get_etime(unsigned long s_time) -{ - unsigned long min; - unsigned sec; - struct sysinfo info; - char *temp; - - sysinfo(&info); - min = s_time/sysconf(_SC_CLK_TCK); - min = info.uptime - min; - sec = min % 60; - min = min / 60; - temp = xmprintf("%3lu:%02u", min,sec); - xprintf("%*.*s",7,7,temp); - free(temp); -} + todo: thread support /proc/$d/task/%d/stat + man page: F flags mean... +*/ -//get time attributes for processes. -void get_time(unsigned long s_time, unsigned long u_time) -{ - unsigned long min; - unsigned sec; - char *temp; - - min = (s_time + u_time)/sysconf(_SC_CLK_TCK); - sec = min % 60; - min = min / 60; - temp = xmprintf("%3lu:%02u", min,sec); - xprintf("%*.*s",6,6,temp); - free(temp); -} - -// read command line taking care of in between NUL's in command line -static void read_cmdline(int fd, char *cmd_ptr) +// dirtree callback. +// toybuf used as: 1024 /proc/$PID/stat, 1024 slot[], 2048 /proc/$PID/cmdline +static int do_ps(struct dirtree *new) { - int size = read(fd, cmd_ptr, BUFF_SIZE); //sizeof(cmd_buf) - - cmd_ptr[size] = '\0'; - while (--size > 0 && cmd_ptr[size] == '\0'); //reach to last char - - while (size >= 0) { - if ((unsigned char)cmd_ptr[size] < ' ') cmd_ptr[size] = ' '; - size--; - } -} - -// get the processes stats and print the stats -// corresponding to header attributes. -static void do_ps_line(int pid, int tid) -{ - char *stat_buff = toybuf + BUFF_SIZE, *cmd_buff = toybuf + (2*BUFF_SIZE); - char state[4] = {0,}; - int tty, tty_major, tty_minor, fd, n, nice, width_counter = 0; - struct stat stats; + long long *slot = (void *)(toybuf+1024); + char *name, *s, state; + int nlen, slots, i, field, width = TT.width, idxes[] = {0, -1, -2, -3}; struct passwd *pw; struct group *gr; - char *name, *user, *group, *ruser, *rgroup, *ptr; - long rss; - unsigned long stime, utime, start_time, vsz; - unsigned ppid, ruid, rgid, pgid; - struct header_list *p = TT.o; + +width=18; + + if (!new->parent) return DIRTREE_RECURSE; + if (!(*slot = atol(new->name))) return 0; - sprintf(stat_buff, "/proc/%d", pid); - if(stat(stat_buff, &stats)) return; + // 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; - if (tid) { - if (snprintf(stat_buff, BUFF_SIZE, "/proc/%d/task/%d/stat", pid, tid) >= BUFF_SIZE) return; - if (snprintf(cmd_buff, BUFF_SIZE, "/proc/%d/task/%d/cmdline", pid, tid) >= BUFF_SIZE) return; - } else { - if (snprintf(stat_buff, BUFF_SIZE, "/proc/%d/stat", pid) >= BUFF_SIZE) return; - if (snprintf(cmd_buff, BUFF_SIZE, "/proc/%d/cmdline", pid) >= BUFF_SIZE) return; - } + // 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 + for (slots = 1; slots<100; slots++) + if (1>sscanf(s += i, " %lld%n", slot+slots, &i)) break; - fd = xopen(stat_buff, O_RDONLY); - n = readall(fd, stat_buff, BUFF_SIZE); - xclose(fd); - if (n < 0) return; - stat_buff[n] = 0; //Null terminate the buffer. - ptr = strchr(stat_buff, '('); - ptr++; - name = ptr; - ptr = strrchr(stat_buff, ')'); - *ptr = '\0'; //unecessary if? - name = xmprintf("[%s]", name); - ptr += 2; // goto STATE - n = sscanf(ptr, "%c %u %u %*u %d %*s %*s %*s %*s %*s %*s " - "%lu %lu %*s %*s %*s %d %*s %*s %lu %lu %ld", - &state[0],&ppid, &pgid, &tty, &utime, &stime, - &nice,&start_time, &vsz,&rss); + // skip entries we don't care about. + if ((toys.optflags&(FLAG_a|FLAG_d)) && getsid(*slot)==*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; + + // default: pidi=1 user time args - if (tid) pid = tid; - vsz >>= 10; //Convert into KB - rss = rss * 4; //Express in pages - tty_major = (tty >> 8) & 0xfff; - tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00); + for (field = 0; field= BUFF_SIZE) - goto clean; - } else { - if (snprintf(stat_buff, BUFF_SIZE, "/proc/%d/status", pid) >= BUFF_SIZE) - goto clean; - } + // Our tests here are in octal to match \123 character escapes in typos[] + + // Print a raw stat field? + if (idx<0300) + sprintf(out, (char *[]){"%lld","%llx","%llo"}[idx>>6], slot[idx&63]); - fd = -1; - while (p) { - int width; - width = p->width; - width_counter += (width + 1); //how much screen we hv filled, +1, extra space b/w headers - switch (p->position) { - case 0: - pw = getpwuid(stats.st_uid); - if (!pw) user = xmprintf("%d",(int)stats.st_uid); - else user = xmprintf("%s", pw->pw_name); - printf("%-*.*s", width, width, user); - free(user); - break; - case 1: - gr = getgrgid(stats.st_gid); - if (!gr) group = xmprintf("%d",(int)stats.st_gid); - else group = xmprintf("%s", gr->gr_name); - printf("%-*.*s", width, width, group); - free(group); - break; - case 2: - name[strlen(name) - 1] = '\0'; - printf("%-*.*s", width,width, name + 1); - name[strlen(name)] = ']'; //Refill it for further process. - break; - case 3: - { - int j = 0; - width_counter -= width; - if(p->next) j = width; //is args is in middle. ( -o pid,args,ppid) - else j = (TT.screen_width - width_counter % TT.screen_width); //how much screen left. - if (fd == -1) fd = open(cmd_buff, O_RDONLY); //don't want to die - else xlseek(fd, 0, SEEK_SET); - if (fd < 0) cmd_buff[0] = 0; - else read_cmdline(fd, cmd_buff); - if (cmd_buff[0]) printf("%-*.*s", j, j, cmd_buff); - else printf("%-*.*s", j, j, name); - width_counter += width; - break; + else if (idx == 0300) sprintf(out, "%c", state); // S + else if (idx == 0301 || idx == 0302) { // UID and USER + sprintf(out, "%d", new->st.st_uid); + if (idx == 302 || (toys.optflags&FLAG_f)) { + struct passwd *pw = getpwuid(new->st.st_uid); + + if (pw) out = pw->pw_name; + } + } else if (idx == 0303); // C (unsupported for now) + else if (idx == 0304) // SZ + sprintf(out, "%lld", slot[22]/4096); + else if (idx == 0305) { // WCHAN + sprintf(toybuf+512, "%lld/wchan"); + readfileat(dirtree_parentfd(new), toybuf+512, out, 2047); + + // time + } else if (idx == -2) { + long seconds = (slot[11]+(idx==-3)*slot[12])/sysconf(_SC_CLK_TCK), + ll = 60*60*24; + + for (s = out, i = 0; i<4; i++) { + if (i>1 || seconds > ll) + s += sprintf(s, "%*ld%c", 2*(i==3), seconds/ll, "-::"[i]); + ll /= i ? 60 : 24; + } + + // 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... Waiting for somebody to complain. :) + } else if (idx == -3) { + 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))) { + out[len] = 0; + for (i = 0; igr_name); - printf("%-*.*s", width, width, rgroup); - free(rgroup); - break; - case 10: - get_uid_gid(stat_buff, "Uid:", &ruid); - pw = getpwuid(ruid); - if (!pw) ruser = xmprintf("%d",(int)stats.st_uid); - else ruser = xmprintf("%s", pw->pw_name); - printf("%-*.*s", width, width, ruser); - free(ruser); - break; - case 11: - get_time(utime, stime); - break; - case 12: - if (tty_major) { - char *temp = xmprintf("%d,%d", tty_major,tty_minor); - printf("%-*s", width, temp); - free(temp); - } else printf("%-*s", width, "?"); - break; - case 13: - printf("%*lu", width, vsz); - break; - case 14: - printf("%-*s", width, state); - break; - case 15: - printf("%*lu", width, rss); - break; + close(fd); + } + if (len<1) sprintf(out, "[%.*s]", nlen, name); } - p = p->next; - xputc(' '); //space char - } - if (fd >= 0) xclose(fd); - xputc('\n'); -clean: - free(name); -} -// Do stats for threads (for -T option) -void do_ps_threads(int pid) -{ - DIR *d; - int tid; - struct dirent *de; - char *tmp = xmprintf("/proc/%d/task",pid); + printf(" %*.*s", width, width, out); + } + xputc('\n'); - if (!(d = opendir(tmp))) { - free(tmp); - return; - } - while ((de = readdir(d))) { - if (isdigit(de->d_name[0])) { - tid = atoi(de->d_name); - if (tid == pid) continue; - do_ps_line(pid, tid); - } - } - closedir(d); - free(tmp); + return 0; } void ps_main(void) { - DIR *dp; - struct dirent *entry; - int pid; - struct header_list *hdr, def_header[] = { - {0, "user", "USER", "%-*s ", 8, 0}, - {0, "group", "GROUP", "%-*s ", 8, 1}, - {0, "comm", "COMMAND", "%-*s ",16, 2}, - {0, "args", "COMMAND", "%-*s ",30, 3}, - {0, "pid", "PID", "%*s ", 5, 4}, - {0, "ppid","PPID", "%*s ", 5, 5}, - {0, "pgid", "PGID", "%*s ", 5, 6}, - {0, "etime","ELAPSED", "%*s ", 7, 7}, - {0, "nice", "NI", "%*s ", 5, 8}, - {0, "rgroup","RGROUP", "%-*s ", 8, 9}, - {0, "ruser","RUSER", "%-*s ", 8, 10}, - {0, "time", "TIME", "%*s ", 6, 11}, - {0, "tty", "TT", "%-*s ", 6, 12}, - {0, "vsz","VSZ", "%*s ", 7, 13}, - {0, "stat", "STAT", "%-*s ", 4, 14}, - {0, "rss", "RSS", "%*s ", 4, 15}, -{0,0,0,0,0,0} + int i, fd = -1; + // Octal output code followed by header name + char *typos[] = { + "\207F", "\300S", "\301UID", "\0PID", "\02PPID", "\303C", "\20PRI", + "\21NI", "\34ADDR", "\304SZ", "\305WCHAN", "STIME", "TTY", "TIME", "CMD" }; - - TT.screen_width = 80; //default width - terminal_size(&TT.screen_width, NULL); + + // l l fl a fl fl l l l l l f a a a + // F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD + // 7 + + TT.width = 80; + terminal_size(&TT.width, 0); + + // find controlling tty, falling back to /dev/tty if none + for (i = fd = 0; i < 4; i++) { + struct stat st; - // Default pid, user, time, comm - if (!TT.llist_o) { - list_add(def_header+4, 0); - list_add(def_header, 0); - list_add(def_header+11, 0); - list_add(def_header+3, 0); - } else parse_o(def_header); // ARRAY_LEN(def_header) - - for (hdr = TT.o; hdr; hdr = hdr->next) - printf(hdr->format , hdr->width, hdr->header); - xputc('\n'); + if (i != 3 || -1 != (i = fd = open("/dev/tty", O_RDONLY))) { + if (isatty(i) && !fstat(i, &st)) { + TT.tty = st.st_rdev; + break; + } + } + } + if (fd != -1) close(fd); - if (!(dp = opendir("/proc"))) perror_exit("opendir"); - while ((entry = readdir(dp))) { - if (!isdigit(*entry->d_name)) continue; - pid = atoi(entry->d_name); - do_ps_line(pid, 0); - if (toys.optflags & FLAG_T) do_ps_threads(pid); - } - closedir(dp); + if (FLAG_o) { + printf("todo\n"); + } else { + short def = 0x0807; + + if (toys.optflags&FLAG_f) def = 0x1e0f; + if (toys.optflags&FLAG_l) def = 0x7ff7; - while (CFG_TOYBOX_FREE) { - struct header_list *temp = llist_pop(&TT.o); + // order of fields[] matches posix STDOUT section, so add enabled XSI + // defaults according to bitmask + } - if (!temp) break; - free(temp->header); - free(temp); - } + printf("pid user time args\n"); + dirtree_read("/proc", do_ps); }