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 to specify an alternate /etc/passwd (??!?)
10 * Posix says default output should have field named "TTY" but if you "-o tty"
11 * the same field should be called "TT" which is _INSANE_ and I'm not doing it.
12 * It also says that -o "args" and "comm" should behave differently but use
13 * the same title, which is not the same title as the default output. No.
15 USE_PS(NEWTOY(ps, "aAdeflo*[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN))
21 usage: ps [-Aade] [-fl] [-gG GROUP] [-o FIELD] [-p PID] [-t TTY] [-u USER]
26 -a Processes with terminals, except session leaders
27 -d Processes that aren't session leaders
32 -g Processes belonging to these session leaders
33 -G Processes with these real group IDs
34 -o Show FIELDS for each process
40 GROUP, FIELD, PID, TTY, and USER are comma separated lists.
44 "UID", "PID", "PPID", "C", "PRI", "NI", "ADDR", "SZ",
45 "WCHAN", "STIME", "TTY", "TIME", "CMD", "COMMAND", "ELAPSED", "GROUP",
46 "%CPU", "PGID", "RGROUP", "RUSER", "USER", "VSZ"
48 C Processor utilization for scheduling
49 F Process flags (PF_*) from linux source file include/sched.h
50 (in octal rather than hex because posix)
52 R (running) S (sleeping) D (disk sleep) T (stopped) t (tracing stop)
53 Z (zombie) X (dead) x (dead) K (wakekill) W (waking)
55 PPID Parent process id
57 UID User id of process owner
59 Default output is -o PID,TTY,TIME,CMD
60 With -f USER=UID,PID,PPID,C,STIME,TTY,TIME,CMD
61 With -l F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD
77 l l fl a fl fl l l l l l f a a a
78 F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD
79 ruser user rgroup group pid ppid pgid pcpu vsz nice etime time tty comm args
81 todo: thread support /proc/$d/task/%d/stat
82 man page: F flags mean...
86 struct strawberry *next, *prev;
92 // toybuf used as: 1024 /proc/$PID/stat, 1024 slot[], 2048 /proc/$PID/cmdline
93 static int do_ps(struct dirtree *new)
95 struct strawberry *field;
96 long long *slot = (void *)(toybuf+1024);
97 char *name, *s, state;
98 int nlen, i, fd, len, width = TT.width;
100 if (!new->parent) return DIRTREE_RECURSE;
101 if (!(*slot = atol(new->name))) return 0;
103 // name field limited to 256 bytes by VFS, plus 40 fields * max 20 chars:
104 // 1000-ish total, but some forced zero so actually there's headroom.
105 sprintf(toybuf, "%lld/stat", *slot);
106 if (!readfileat(dirtree_parentfd(new), toybuf, toybuf, 1024)) return 0;
108 // parse oddball fields (name and state)
109 if (!(s = strchr(toybuf, '('))) return 0;
110 for (name = ++s; *s != ')'; s++) if (!*s) return 0;
112 if (1>sscanf(++s, " %c%n", &state, &i)) return 0;
114 // parse numeric fields
115 for (len = 1; len<100; len++)
116 if (1>sscanf(s += i, " %lld%n", slot+len, &i)) break;
118 // skip entries we don't care about.
119 if ((toys.optflags&(FLAG_a|FLAG_d)) && getsid(*slot)==*slot) return 0;
120 if ((toys.optflags&FLAG_a) && !slot[4]) return 0;
121 if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e)) && TT.tty!=slot[4])
124 for (field = TT.fields; field; field = field->next) {
125 char *out = toybuf+2048;
127 // Default: unsupported (5 "C")
130 // PID, PPID, PRI, NI, ADDR, SZ
131 if (-1 != (i = stridx((char[]){3,4,6,7,8,9,0}, field->which)))
132 sprintf(out, ((1<<i)&0x10) ? "%llx" : "%lld",
133 slot[((char[]){0,2,16,17,22})[i]]>>(((1<<i)&0x20) ? 12 : 0));
135 else if (!(i = field->which)) sprintf(out, "%llo", slot[7]);
138 sprintf(out, "%c", state);
140 else if (i == 2 || i == 22) {
141 sprintf(out, "%d", new->st.st_uid);
143 struct passwd *pw = getpwuid(new->st.st_uid);
145 if (pw) out = pw->pw_name;
149 sprintf(toybuf+512, "%lld/wchan", *slot);
150 readfileat(dirtree_parentfd(new), toybuf+512, out, 2047);
156 // Can we readlink() our way to a name?
157 for (i=0; i<3; i++) {
160 sprintf(toybuf+512, "%lld/fd/%i", *slot, i);
161 fd = dirtree_parentfd(new);
162 if (!fstatat(fd, toybuf+512, &st, 0) && S_ISCHR(st.st_mode)
163 && st.st_rdev == slot[4]
164 && 0<(len = readlinkat(fd, toybuf+512, out, 2047)))
167 if (!strncmp(out, "/dev/", 5)) out += 5;
173 // Couldn't find it, show major:minor
176 sprintf(out, "%d:%d", (i>>8)&0xfff, ((i>>12)&0xfff00)|(i&0xff));
180 } else if (i==13 || i==16) {
181 long seconds = (i==16) ? slot[20] : slot[11]+slot[12], ll = 60*60*24;
183 seconds /= sysconf(_SC_CLK_TCK);
184 if (i==16) seconds = TT.uptime-seconds;
185 for (s = out, i = 0; i<4; i++) {
186 if (i>1 || seconds > ll)
187 s += sprintf(s, (i==3) ? "%02ld" : "%ld%c", seconds/ll, "-::"[i]);
192 //16 "ELAPSED", "GROUP", "%CPU", "PGID", "RGROUP",
193 //21 "RUSER", -, "VSZ"
195 // Command line limited to 2k displayable. We could dynamically malloc, but
196 // it'd almost never get used, querying length of a proc file is awkward,
197 // fixed buffer is nommu friendly... Wait for somebody to complain. :)
198 } else if (i == 14 || i == 15) {
202 sprintf(out, "%lld/cmdline", *slot);
203 fd = openat(dirtree_parentfd(new), out, O_RDONLY);
206 if (0<(len = read(fd, out, 2047))) {
207 if (!out[len-1]) len--;
209 for (i = 0; i<len; i++) if (out[i] < ' ') out[i] = ' ';
213 if (len<1) sprintf(out, "[%.*s]", nlen, name);
216 i = width<field->len ? width : field->len;
217 width -= printf(" %*.*s", i, field->next ? i : width, out);
226 struct strawberry *field;
227 // Octal output code followed by header name
228 char widths[] = {1,1,5,5,5,2,3,3,4,5,6,5,8,8,27,27,11,8,4,5,8,8,8,6},
230 "F", "S", "UID", "PID", "PPID", "C", "PRI", "NI", "ADDR", "SZ",
231 "WCHAN", "STIME", "TTY", "TIME", "CMD", "COMMAND", "ELAPSED", "GROUP",
232 "%CPU", "PGID", "RGROUP", "RUSER", "USER", "VSZ"
237 terminal_size(&TT.width, 0);
240 // find controlling tty, falling back to /dev/tty if none
241 for (i = fd = 0; i < 4; i++) {
244 if (i != 3 || -1 != (i = fd = open("/dev/tty", O_RDONLY))) {
245 if (isatty(i) && !fstat(i, &st)) {
251 if (fd != -1) close(fd);
253 sysinfo((void *)toybuf);
254 // Because "TT.uptime = *(long *)toybuf;" triggers a bug in gcc.
256 long *sigh = (long *)toybuf;
260 // Manual field selection via -o
261 if (toys.optflags&FLAG_o) {
265 for (ol = TT.o; ol; ol = ol->next) {
266 char *width, *type, *title, *end, *arg = ol->arg;
268 // Set title, length of title, type, end of type, and display width
269 while ((type = comma_iterate(&arg, &length))) {
270 if ((end = strchr(type, '=')) && length<(end-type)) {
272 length = (end-type)-1;
278 // If changing display width, trim title at the :
279 if ((width = strchr(type, ':')) && width<end) {
280 if (!title) length = width-type;
283 // Allocate structure, copy title
284 field = xmalloc(sizeof(struct strawberry)+length+1);
286 memcpy(field->title, title, length);
287 field->title[length] = 0;
292 field->len = strtol(++width, &title, 10);
293 if (!isdigit(*width) || title != end)
294 error_exit("bad : in -o %s@%ld", ol->arg, title-ol->arg);
298 // Find type (reuse width as temp because we're done with it)
299 for (i = 0; i<ARRAY_LEN(typos) && !field->which; i++) {
304 for (j = 0; j < 2; j++) {
305 if (!j) s = typos[i];
306 // posix requires alternate names for some fields
307 else if (-1 == (k = stridx((char []){7, 14, 15, 16}, i))) break;
308 else s = ((char *[]){"nice", "args", "comm", "etime"})[k];
310 if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break;
314 if (i == ARRAY_LEN(typos)) error_exit("bad -o %.*s", end-type, type);
315 if (!field->title) strcpy(field->title, typos[field->which]);
316 dlist_add_nomalloc((void *)&TT.fields, (void *)field);
320 // Default fields (also with -f and -l)
322 unsigned short def = 0x7008;
324 if (toys.optflags&FLAG_f) def = 0x783c;
325 if (toys.optflags&FLAG_l) def = 0x77ff;
327 // order of fields[] matches posix STDOUT section, so add enabled XSI
328 // defaults according to bitmask
330 for (i=0; def>>i; i++) {
331 if (!((def>>i)&1)) continue;
333 field = xmalloc(sizeof(struct strawberry)+strlen(typos[i])+1);
335 field->len = widths[i];
336 strcpy(field->title, typos[i]);
337 dlist_add_nomalloc((void *)&TT.fields, (void *)field);
340 dlist_terminate(TT.fields);
342 // Print padded headers. (Numbers are right justified, everyting else left.
343 // time and pcpu count as numbers, tty does not)
344 for (field = TT.fields; field; field = field->next) {
346 // right justify F, UID, PID, PPID, PRI, NI, ADDR SZ, TIME, ELAPSED, %CPU
348 if (!((1<<field->which)&0x523dd)) field->len *= -1;
349 printf(" %*s", field->len, field->title);
351 // -f prints USER but calls it UID (but "ps -o uid -f" is numeric...?)
352 if ((toys.optflags&(FLAG_f|FLAG_o))==FLAG_f && field->which==2)
357 dirtree_read("/proc", do_ps);