blob: e291ecd7ed40d78bff8848ad15566e157c89ac95
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * Mini ps implementation(s) for busybox |
4 | * |
5 | * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> |
6 | * Fix for SELinux Support:(c)2007 Hiroshi Shinji <shiroshi@my.email.ne.jp> |
7 | * (c)2007 Yuichi Nakamura <ynakam@hitachisoft.jp> |
8 | * |
9 | * Licensed under GPLv2, see file LICENSE in this source tree. |
10 | */ |
11 | //config:config PS |
12 | //config: bool "ps" |
13 | //config: default y |
14 | //config: help |
15 | //config: ps gives a snapshot of the current processes. |
16 | //config: |
17 | //config:config FEATURE_PS_WIDE |
18 | //config: bool "Enable wide output option (-w)" |
19 | //config: default y |
20 | //config: depends on PS && !DESKTOP |
21 | //config: help |
22 | //config: Support argument 'w' for wide output. |
23 | //config: If given once, 132 chars are printed, and if given more |
24 | //config: than once, the length is unlimited. |
25 | //config: |
26 | //config:config FEATURE_PS_LONG |
27 | //config: bool "Enable long output option (-l)" |
28 | //config: default y |
29 | //config: depends on PS && !DESKTOP |
30 | //config: help |
31 | //config: Support argument 'l' for long output. |
32 | //config: Adds fields PPID, RSS, START, TIME & TTY |
33 | //config: |
34 | //config:config FEATURE_PS_TIME |
35 | //config: bool "Enable time and elapsed time output" |
36 | //config: default y |
37 | //config: depends on PS && DESKTOP |
38 | //config: select PLATFORM_LINUX |
39 | //config: help |
40 | //config: Support -o time and -o etime output specifiers. |
41 | //config: |
42 | //config:config FEATURE_PS_ADDITIONAL_COLUMNS |
43 | //config: bool "Enable additional ps columns" |
44 | //config: default y |
45 | //config: depends on PS && DESKTOP |
46 | //config: help |
47 | //config: Support -o rgroup, -o ruser, -o nice output specifiers. |
48 | //config: |
49 | //config:config FEATURE_PS_UNUSUAL_SYSTEMS |
50 | //config: bool "Support Linux prior to 2.4.0 and non-ELF systems" |
51 | //config: default n |
52 | //config: depends on FEATURE_PS_TIME |
53 | //config: help |
54 | //config: Include support for measuring HZ on old kernels and non-ELF systems |
55 | //config: (if you are on Linux 2.4.0+ and use ELF, you don't need this) |
56 | |
57 | //applet:IF_PS(APPLET(ps, BB_DIR_BIN, BB_SUID_DROP)) |
58 | |
59 | //kbuild:lib-$(CONFIG_PS) += ps.o |
60 | |
61 | //usage:#if ENABLE_DESKTOP |
62 | //usage: |
63 | //usage:#define ps_trivial_usage |
64 | //usage: "[-o COL1,COL2=HEADER]" IF_FEATURE_SHOW_THREADS(" [-T]") |
65 | //usage:#define ps_full_usage "\n\n" |
66 | //usage: "Show list of processes\n" |
67 | //usage: "\n -o COL1,COL2=HEADER Select columns for display" |
68 | //usage: IF_FEATURE_SHOW_THREADS( |
69 | //usage: "\n -T Show threads" |
70 | //usage: ) |
71 | //usage: |
72 | //usage:#else /* !ENABLE_DESKTOP */ |
73 | //usage: |
74 | //usage:#if !ENABLE_SELINUX && !ENABLE_FEATURE_PS_WIDE |
75 | //usage:#define USAGE_PS "\nThis version of ps accepts no options" |
76 | //usage:#else |
77 | //usage:#define USAGE_PS "" |
78 | //usage:#endif |
79 | //usage: |
80 | //usage:#define ps_trivial_usage |
81 | //usage: "" |
82 | //usage:#define ps_full_usage "\n\n" |
83 | //usage: "Show list of processes\n" |
84 | //usage: USAGE_PS |
85 | //usage: IF_SELINUX( |
86 | //usage: "\n -Z Show selinux context" |
87 | //usage: ) |
88 | //usage: IF_FEATURE_PS_WIDE( |
89 | //usage: "\n w Wide output" |
90 | //usage: ) |
91 | //usage: IF_FEATURE_PS_LONG( |
92 | //usage: "\n l Long output" |
93 | //usage: ) |
94 | //usage: IF_FEATURE_SHOW_THREADS( |
95 | //usage: "\n T Show threads" |
96 | //usage: ) |
97 | //usage: |
98 | //usage:#endif /* ENABLE_DESKTOP */ |
99 | //usage: |
100 | //usage:#define ps_example_usage |
101 | //usage: "$ ps\n" |
102 | //usage: " PID Uid Gid State Command\n" |
103 | //usage: " 1 root root S init\n" |
104 | //usage: " 2 root root S [kflushd]\n" |
105 | //usage: " 3 root root S [kupdate]\n" |
106 | //usage: " 4 root root S [kpiod]\n" |
107 | //usage: " 5 root root S [kswapd]\n" |
108 | //usage: " 742 andersen andersen S [bash]\n" |
109 | //usage: " 743 andersen andersen S -bash\n" |
110 | //usage: " 745 root root S [getty]\n" |
111 | //usage: " 2990 andersen andersen R ps\n" |
112 | |
113 | #include "libbb.h" |
114 | #include "common_bufsiz.h" |
115 | #ifdef __linux__ |
116 | # include <sys/sysinfo.h> |
117 | #endif |
118 | |
119 | /* Absolute maximum on output line length */ |
120 | enum { MAX_WIDTH = 2*1024 }; |
121 | |
122 | #if ENABLE_FEATURE_PS_TIME || ENABLE_FEATURE_PS_LONG |
123 | static unsigned long get_uptime(void) |
124 | { |
125 | #ifdef __linux__ |
126 | struct sysinfo info; |
127 | if (sysinfo(&info) < 0) |
128 | return 0; |
129 | return info.uptime; |
130 | #elif 1 |
131 | unsigned long uptime; |
132 | char buf[sizeof(uptime)*3 + 2]; |
133 | /* /proc/uptime is "UPTIME_SEC.NN IDLE_SEC.NN\n" |
134 | * (where IDLE is cumulative over all CPUs) |
135 | */ |
136 | if (open_read_close("/proc/uptime", buf, sizeof(buf)) <= 0) |
137 | bb_perror_msg_and_die("can't read '%s'", "/proc/uptime"); |
138 | buf[sizeof(buf)-1] = '\0'; |
139 | sscanf(buf, "%lu", &uptime); |
140 | return uptime; |
141 | #else |
142 | struct timespec ts; |
143 | if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) |
144 | return 0; |
145 | return ts.tv_sec; |
146 | #endif |
147 | } |
148 | #endif |
149 | |
150 | #if ENABLE_DESKTOP |
151 | |
152 | #include <sys/times.h> /* for times() */ |
153 | #ifndef AT_CLKTCK |
154 | # define AT_CLKTCK 17 |
155 | #endif |
156 | |
157 | /* TODO: |
158 | * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ps.html |
159 | * specifies (for XSI-conformant systems) following default columns |
160 | * (l and f mark columns shown with -l and -f respectively): |
161 | * F l Flags (octal and additive) associated with the process (??) |
162 | * S l The state of the process |
163 | * UID f,l The user ID; the login name is printed with -f |
164 | * PID The process ID |
165 | * PPID f,l The parent process |
166 | * C f,l Processor utilization |
167 | * PRI l The priority of the process; higher numbers mean lower priority |
168 | * NI l Nice value |
169 | * ADDR l The address of the process |
170 | * SZ l The size in blocks of the core image of the process |
171 | * WCHAN l The event for which the process is waiting or sleeping |
172 | * STIME f Starting time of the process |
173 | * TTY The controlling terminal for the process |
174 | * TIME The cumulative execution time for the process |
175 | * CMD The command name; the full command line is shown with -f |
176 | */ |
177 | typedef struct { |
178 | uint16_t width; |
179 | char name6[6]; |
180 | const char *header; |
181 | void (*f)(char *buf, int size, const procps_status_t *ps); |
182 | int ps_flags; |
183 | } ps_out_t; |
184 | |
185 | struct globals { |
186 | ps_out_t* out; |
187 | int out_cnt; |
188 | int print_header; |
189 | int need_flags; |
190 | char *buffer; |
191 | unsigned terminal_width; |
192 | #if ENABLE_FEATURE_PS_TIME |
193 | unsigned kernel_HZ; |
194 | unsigned long seconds_since_boot; |
195 | #endif |
196 | } FIX_ALIASING; |
197 | #define G (*(struct globals*)bb_common_bufsiz1) |
198 | #define out (G.out ) |
199 | #define out_cnt (G.out_cnt ) |
200 | #define print_header (G.print_header ) |
201 | #define need_flags (G.need_flags ) |
202 | #define buffer (G.buffer ) |
203 | #define terminal_width (G.terminal_width ) |
204 | #define kernel_HZ (G.kernel_HZ ) |
205 | #define INIT_G() do { setup_common_bufsiz(); } while (0) |
206 | |
207 | #if ENABLE_FEATURE_PS_TIME |
208 | /* for ELF executables, notes are pushed before environment and args */ |
209 | static uintptr_t find_elf_note(uintptr_t findme) |
210 | { |
211 | uintptr_t *ep = (uintptr_t *) environ; |
212 | |
213 | while (*ep++) |
214 | continue; |
215 | while (*ep) { |
216 | if (ep[0] == findme) { |
217 | return ep[1]; |
218 | } |
219 | ep += 2; |
220 | } |
221 | return -1; |
222 | } |
223 | |
224 | #if ENABLE_FEATURE_PS_UNUSUAL_SYSTEMS |
225 | static unsigned get_HZ_by_waiting(void) |
226 | { |
227 | struct timeval tv1, tv2; |
228 | unsigned t1, t2, r, hz; |
229 | unsigned cnt = cnt; /* for compiler */ |
230 | int diff; |
231 | |
232 | r = 0; |
233 | |
234 | /* Wait for times() to reach new tick */ |
235 | t1 = times(NULL); |
236 | do { |
237 | t2 = times(NULL); |
238 | } while (t2 == t1); |
239 | gettimeofday(&tv2, NULL); |
240 | |
241 | do { |
242 | t1 = t2; |
243 | tv1.tv_usec = tv2.tv_usec; |
244 | |
245 | /* Wait exactly one times() tick */ |
246 | do { |
247 | t2 = times(NULL); |
248 | } while (t2 == t1); |
249 | gettimeofday(&tv2, NULL); |
250 | |
251 | /* Calculate ticks per sec, rounding up to even */ |
252 | diff = tv2.tv_usec - tv1.tv_usec; |
253 | if (diff <= 0) diff += 1000000; |
254 | hz = 1000000u / (unsigned)diff; |
255 | hz = (hz+1) & ~1; |
256 | |
257 | /* Count how many same hz values we saw */ |
258 | if (r != hz) { |
259 | r = hz; |
260 | cnt = 0; |
261 | } |
262 | cnt++; |
263 | } while (cnt < 3); /* exit if saw 3 same values */ |
264 | |
265 | return r; |
266 | } |
267 | #else |
268 | static inline unsigned get_HZ_by_waiting(void) |
269 | { |
270 | /* Better method? */ |
271 | return 100; |
272 | } |
273 | #endif |
274 | |
275 | static unsigned get_kernel_HZ(void) |
276 | { |
277 | if (kernel_HZ) |
278 | return kernel_HZ; |
279 | |
280 | /* Works for ELF only, Linux 2.4.0+ */ |
281 | kernel_HZ = find_elf_note(AT_CLKTCK); |
282 | if (kernel_HZ == (unsigned)-1) |
283 | kernel_HZ = get_HZ_by_waiting(); |
284 | |
285 | G.seconds_since_boot = get_uptime(); |
286 | |
287 | return kernel_HZ; |
288 | } |
289 | #endif |
290 | |
291 | /* Print value to buf, max size+1 chars (including trailing '\0') */ |
292 | |
293 | static void func_user(char *buf, int size, const procps_status_t *ps) |
294 | { |
295 | #if 1 |
296 | safe_strncpy(buf, get_cached_username(ps->uid), size+1); |
297 | #else |
298 | /* "compatible" version, but it's larger */ |
299 | /* procps 2.18 shows numeric UID if name overflows the field */ |
300 | /* TODO: get_cached_username() returns numeric string if |
301 | * user has no passwd record, we will display it |
302 | * left-justified here; too long usernames are shown |
303 | * as _right-justified_ IDs. Is it worth fixing? */ |
304 | const char *user = get_cached_username(ps->uid); |
305 | if (strlen(user) <= size) |
306 | safe_strncpy(buf, user, size+1); |
307 | else |
308 | sprintf(buf, "%*u", size, (unsigned)ps->uid); |
309 | #endif |
310 | } |
311 | |
312 | static void func_group(char *buf, int size, const procps_status_t *ps) |
313 | { |
314 | safe_strncpy(buf, get_cached_groupname(ps->gid), size+1); |
315 | } |
316 | |
317 | static void func_comm(char *buf, int size, const procps_status_t *ps) |
318 | { |
319 | safe_strncpy(buf, ps->comm, size+1); |
320 | } |
321 | |
322 | static void func_state(char *buf, int size, const procps_status_t *ps) |
323 | { |
324 | safe_strncpy(buf, ps->state, size+1); |
325 | } |
326 | |
327 | static void func_args(char *buf, int size, const procps_status_t *ps) |
328 | { |
329 | read_cmdline(buf, size+1, ps->pid, ps->comm); |
330 | } |
331 | |
332 | static void func_pid(char *buf, int size, const procps_status_t *ps) |
333 | { |
334 | sprintf(buf, "%*u", size, ps->pid); |
335 | } |
336 | |
337 | static void func_ppid(char *buf, int size, const procps_status_t *ps) |
338 | { |
339 | sprintf(buf, "%*u", size, ps->ppid); |
340 | } |
341 | |
342 | static void func_pgid(char *buf, int size, const procps_status_t *ps) |
343 | { |
344 | sprintf(buf, "%*u", size, ps->pgid); |
345 | } |
346 | |
347 | static void put_lu(char *buf, int size, unsigned long u) |
348 | { |
349 | char buf4[5]; |
350 | |
351 | /* see http://en.wikipedia.org/wiki/Tera */ |
352 | smart_ulltoa4(u, buf4, " mgtpezy")[0] = '\0'; |
353 | sprintf(buf, "%.*s", size, buf4); |
354 | } |
355 | |
356 | static void func_vsz(char *buf, int size, const procps_status_t *ps) |
357 | { |
358 | put_lu(buf, size, ps->vsz); |
359 | } |
360 | |
361 | static void func_rss(char *buf, int size, const procps_status_t *ps) |
362 | { |
363 | put_lu(buf, size, ps->rss); |
364 | } |
365 | |
366 | static void func_tty(char *buf, int size, const procps_status_t *ps) |
367 | { |
368 | buf[0] = '?'; |
369 | buf[1] = '\0'; |
370 | if (ps->tty_major) /* tty field of "0" means "no tty" */ |
371 | snprintf(buf, size+1, "%u,%u", ps->tty_major, ps->tty_minor); |
372 | } |
373 | |
374 | #if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS |
375 | |
376 | static void func_rgroup(char *buf, int size, const procps_status_t *ps) |
377 | { |
378 | safe_strncpy(buf, get_cached_groupname(ps->rgid), size+1); |
379 | } |
380 | |
381 | static void func_ruser(char *buf, int size, const procps_status_t *ps) |
382 | { |
383 | safe_strncpy(buf, get_cached_username(ps->ruid), size+1); |
384 | } |
385 | |
386 | static void func_nice(char *buf, int size, const procps_status_t *ps) |
387 | { |
388 | sprintf(buf, "%*d", size, ps->niceness); |
389 | } |
390 | |
391 | #endif |
392 | |
393 | #if ENABLE_FEATURE_PS_TIME |
394 | |
395 | static void func_etime(char *buf, int size, const procps_status_t *ps) |
396 | { |
397 | /* elapsed time [[dd-]hh:]mm:ss; here only mm:ss */ |
398 | unsigned long mm; |
399 | unsigned ss; |
400 | |
401 | mm = ps->start_time / get_kernel_HZ(); |
402 | /* must be after get_kernel_HZ()! */ |
403 | mm = G.seconds_since_boot - mm; |
404 | ss = mm % 60; |
405 | mm /= 60; |
406 | snprintf(buf, size+1, "%3lu:%02u", mm, ss); |
407 | } |
408 | |
409 | static void func_time(char *buf, int size, const procps_status_t *ps) |
410 | { |
411 | /* cumulative time [[dd-]hh:]mm:ss; here only mm:ss */ |
412 | unsigned long mm; |
413 | unsigned ss; |
414 | |
415 | mm = (ps->utime + ps->stime) / get_kernel_HZ(); |
416 | ss = mm % 60; |
417 | mm /= 60; |
418 | snprintf(buf, size+1, "%3lu:%02u", mm, ss); |
419 | } |
420 | |
421 | #endif |
422 | |
423 | #if ENABLE_SELINUX |
424 | static void func_label(char *buf, int size, const procps_status_t *ps) |
425 | { |
426 | safe_strncpy(buf, ps->context ? ps->context : "unknown", size+1); |
427 | } |
428 | #endif |
429 | |
430 | /* |
431 | static void func_nice(char *buf, int size, const procps_status_t *ps) |
432 | { |
433 | ps->??? |
434 | } |
435 | |
436 | static void func_pcpu(char *buf, int size, const procps_status_t *ps) |
437 | { |
438 | } |
439 | */ |
440 | |
441 | static const ps_out_t out_spec[] = { |
442 | /* Mandated by http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ps.html: */ |
443 | { 8 , "user" ,"USER" ,func_user ,PSSCAN_UIDGID }, |
444 | { 8 , "group" ,"GROUP" ,func_group ,PSSCAN_UIDGID }, |
445 | { 16 , "comm" ,"COMMAND",func_comm ,PSSCAN_COMM }, |
446 | { MAX_WIDTH , "args" ,"COMMAND",func_args ,PSSCAN_COMM }, |
447 | { 5 , "pid" ,"PID" ,func_pid ,PSSCAN_PID }, |
448 | { 5 , "ppid" ,"PPID" ,func_ppid ,PSSCAN_PPID }, |
449 | { 5 , "pgid" ,"PGID" ,func_pgid ,PSSCAN_PGID }, |
450 | #if ENABLE_FEATURE_PS_TIME |
451 | { sizeof("ELAPSED")-1, "etime" ,"ELAPSED",func_etime ,PSSCAN_START_TIME }, |
452 | #endif |
453 | #if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS |
454 | { 5 , "nice" ,"NI" ,func_nice ,PSSCAN_NICE }, |
455 | { 8 , "rgroup","RGROUP" ,func_rgroup,PSSCAN_RUIDGID }, |
456 | { 8 , "ruser" ,"RUSER" ,func_ruser ,PSSCAN_RUIDGID }, |
457 | // { 5 , "pcpu" ,"%CPU" ,func_pcpu ,PSSCAN_ }, |
458 | #endif |
459 | #if ENABLE_FEATURE_PS_TIME |
460 | { 6 , "time" ,"TIME" ,func_time ,PSSCAN_STIME | PSSCAN_UTIME }, |
461 | #endif |
462 | { 6 , "tty" ,"TT" ,func_tty ,PSSCAN_TTY }, |
463 | { 4 , "vsz" ,"VSZ" ,func_vsz ,PSSCAN_VSZ }, |
464 | /* Not mandated, but useful: */ |
465 | { 4 , "stat" ,"STAT" ,func_state ,PSSCAN_STATE }, |
466 | { 4 , "rss" ,"RSS" ,func_rss ,PSSCAN_RSS }, |
467 | #if ENABLE_SELINUX |
468 | { 35 , "label" ,"LABEL" ,func_label ,PSSCAN_CONTEXT }, |
469 | #endif |
470 | }; |
471 | |
472 | static ps_out_t* new_out_t(void) |
473 | { |
474 | out = xrealloc_vector(out, 2, out_cnt); |
475 | return &out[out_cnt++]; |
476 | } |
477 | |
478 | static const ps_out_t* find_out_spec(const char *name) |
479 | { |
480 | unsigned i; |
481 | char buf[ARRAY_SIZE(out_spec)*7 + 1]; |
482 | char *p = buf; |
483 | |
484 | for (i = 0; i < ARRAY_SIZE(out_spec); i++) { |
485 | if (strncmp(name, out_spec[i].name6, 6) == 0) |
486 | return &out_spec[i]; |
487 | p += sprintf(p, "%.6s,", out_spec[i].name6); |
488 | } |
489 | p[-1] = '\0'; |
490 | bb_error_msg_and_die("bad -o argument '%s', supported arguments: %s", name, buf); |
491 | } |
492 | |
493 | static void parse_o(char* opt) |
494 | { |
495 | ps_out_t* new; |
496 | // POSIX: "-o is blank- or comma-separated list" (FIXME) |
497 | char *comma, *equal; |
498 | while (1) { |
499 | comma = strchr(opt, ','); |
500 | equal = strchr(opt, '='); |
501 | if (comma && (!equal || equal > comma)) { |
502 | *comma = '\0'; |
503 | *new_out_t() = *find_out_spec(opt); |
504 | *comma = ','; |
505 | opt = comma + 1; |
506 | continue; |
507 | } |
508 | break; |
509 | } |
510 | // opt points to last spec in comma separated list. |
511 | // This one can have =HEADER part. |
512 | new = new_out_t(); |
513 | if (equal) |
514 | *equal = '\0'; |
515 | *new = *find_out_spec(opt); |
516 | if (equal) { |
517 | *equal = '='; |
518 | new->header = equal + 1; |
519 | // POSIX: the field widths shall be ... at least as wide as |
520 | // the header text (default or overridden value). |
521 | // If the header text is null, such as -o user=, |
522 | // the field width shall be at least as wide as the |
523 | // default header text |
524 | if (new->header[0]) { |
525 | new->width = strlen(new->header); |
526 | print_header = 1; |
527 | } |
528 | } else |
529 | print_header = 1; |
530 | } |
531 | |
532 | static void alloc_line_buffer(void) |
533 | { |
534 | int i; |
535 | int width = 0; |
536 | for (i = 0; i < out_cnt; i++) { |
537 | need_flags |= out[i].ps_flags; |
538 | if (out[i].header[0]) { |
539 | print_header = 1; |
540 | } |
541 | width += out[i].width + 1; /* "FIELD " */ |
542 | if ((int)(width - terminal_width) > 0) { |
543 | /* The rest does not fit on the screen */ |
544 | //out[i].width -= (width - terminal_width - 1); |
545 | out_cnt = i + 1; |
546 | break; |
547 | } |
548 | } |
549 | #if ENABLE_SELINUX |
550 | if (!is_selinux_enabled()) |
551 | need_flags &= ~PSSCAN_CONTEXT; |
552 | #endif |
553 | buffer = xmalloc(width + 1); /* for trailing \0 */ |
554 | } |
555 | |
556 | static void format_header(void) |
557 | { |
558 | int i; |
559 | ps_out_t* op; |
560 | char *p; |
561 | |
562 | if (!print_header) |
563 | return; |
564 | p = buffer; |
565 | i = 0; |
566 | if (out_cnt) { |
567 | while (1) { |
568 | op = &out[i]; |
569 | if (++i == out_cnt) /* do not pad last field */ |
570 | break; |
571 | p += sprintf(p, "%-*s ", op->width, op->header); |
572 | } |
573 | strcpy(p, op->header); |
574 | } |
575 | printf("%.*s\n", terminal_width, buffer); |
576 | } |
577 | |
578 | static void format_process(const procps_status_t *ps) |
579 | { |
580 | int i, len; |
581 | char *p = buffer; |
582 | i = 0; |
583 | if (out_cnt) while (1) { |
584 | out[i].f(p, out[i].width, ps); |
585 | // POSIX: Any field need not be meaningful in all |
586 | // implementations. In such a case a hyphen ( '-' ) |
587 | // should be output in place of the field value. |
588 | if (!p[0]) { |
589 | p[0] = '-'; |
590 | p[1] = '\0'; |
591 | } |
592 | len = strlen(p); |
593 | p += len; |
594 | len = out[i].width - len + 1; |
595 | if (++i == out_cnt) /* do not pad last field */ |
596 | break; |
597 | p += sprintf(p, "%*s", len, ""); |
598 | } |
599 | printf("%.*s\n", terminal_width, buffer); |
600 | } |
601 | |
602 | #if ENABLE_SELINUX |
603 | # define SELINUX_O_PREFIX "label," |
604 | # define DEFAULT_O_STR (SELINUX_O_PREFIX "pid,user" IF_FEATURE_PS_TIME(",time") ",args") |
605 | #else |
606 | # define DEFAULT_O_STR ("pid,user" IF_FEATURE_PS_TIME(",time") ",args") |
607 | #endif |
608 | |
609 | int ps_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
610 | int ps_main(int argc UNUSED_PARAM, char **argv) |
611 | { |
612 | procps_status_t *p; |
613 | llist_t* opt_o = NULL; |
614 | char default_o[sizeof(DEFAULT_O_STR)]; |
615 | int opt; |
616 | enum { |
617 | OPT_Z = (1 << 0), |
618 | OPT_o = (1 << 1), |
619 | OPT_a = (1 << 2), |
620 | OPT_A = (1 << 3), |
621 | OPT_d = (1 << 4), |
622 | OPT_e = (1 << 5), |
623 | OPT_f = (1 << 6), |
624 | OPT_l = (1 << 7), |
625 | OPT_T = (1 << 8) * ENABLE_FEATURE_SHOW_THREADS, |
626 | }; |
627 | |
628 | INIT_G(); |
629 | |
630 | // POSIX: |
631 | // -a Write information for all processes associated with terminals |
632 | // Implementations may omit session leaders from this list |
633 | // -A Write information for all processes |
634 | // -d Write information for all processes, except session leaders |
635 | // -e Write information for all processes (equivalent to -A) |
636 | // -f Generate a full listing |
637 | // -l Generate a long listing |
638 | // -o col1,col2,col3=header |
639 | // Select which columns to display |
640 | /* We allow (and ignore) most of the above. FIXME. |
641 | * -T is picked for threads (POSIX hasn't standardized it). |
642 | * procps v3.2.7 supports -T and shows tids as SPID column, |
643 | * it also supports -L where it shows tids as LWP column. |
644 | */ |
645 | opt = getopt32(argv, "Zo:*aAdefl"IF_FEATURE_SHOW_THREADS("T"), &opt_o); |
646 | if (opt_o) { |
647 | do { |
648 | parse_o(llist_pop(&opt_o)); |
649 | } while (opt_o); |
650 | } else { |
651 | /* Below: parse_o() needs char*, NOT const char*, |
652 | * can't pass it constant string. Need to make a copy first. |
653 | */ |
654 | #if ENABLE_SELINUX |
655 | if (!(opt & OPT_Z) || !is_selinux_enabled()) { |
656 | /* no -Z or no SELinux: do not show LABEL */ |
657 | strcpy(default_o, DEFAULT_O_STR + sizeof(SELINUX_O_PREFIX)-1); |
658 | } else |
659 | #endif |
660 | { |
661 | strcpy(default_o, DEFAULT_O_STR); |
662 | } |
663 | parse_o(default_o); |
664 | } |
665 | #if ENABLE_FEATURE_SHOW_THREADS |
666 | if (opt & OPT_T) |
667 | need_flags |= PSSCAN_TASKS; |
668 | #endif |
669 | |
670 | /* Was INT_MAX, but some libc's go belly up with printf("%.*s") |
671 | * and such large widths */ |
672 | terminal_width = MAX_WIDTH; |
673 | if (isatty(1)) { |
674 | terminal_width = get_terminal_width(0); |
675 | if (--terminal_width > MAX_WIDTH) |
676 | terminal_width = MAX_WIDTH; |
677 | } |
678 | alloc_line_buffer(); |
679 | format_header(); |
680 | |
681 | p = NULL; |
682 | while ((p = procps_scan(p, need_flags)) != NULL) { |
683 | format_process(p); |
684 | } |
685 | |
686 | return EXIT_SUCCESS; |
687 | } |
688 | |
689 | |
690 | #else /* !ENABLE_DESKTOP */ |
691 | |
692 | |
693 | int ps_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
694 | int ps_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) |
695 | { |
696 | procps_status_t *p; |
697 | int psscan_flags = PSSCAN_PID | PSSCAN_UIDGID |
698 | | PSSCAN_STATE | PSSCAN_VSZ | PSSCAN_COMM; |
699 | unsigned terminal_width IF_NOT_FEATURE_PS_WIDE(= 79); |
700 | enum { |
701 | OPT_Z = (1 << 0) * ENABLE_SELINUX, |
702 | OPT_T = (1 << ENABLE_SELINUX) * ENABLE_FEATURE_SHOW_THREADS, |
703 | OPT_l = (1 << ENABLE_SELINUX) * (1 << ENABLE_FEATURE_SHOW_THREADS) * ENABLE_FEATURE_PS_LONG, |
704 | }; |
705 | #if ENABLE_FEATURE_PS_LONG |
706 | time_t now = now; /* for compiler */ |
707 | unsigned long uptime = uptime; |
708 | #endif |
709 | /* If we support any options, parse argv */ |
710 | #if ENABLE_SELINUX || ENABLE_FEATURE_SHOW_THREADS || ENABLE_FEATURE_PS_WIDE || ENABLE_FEATURE_PS_LONG |
711 | int opts = 0; |
712 | # if ENABLE_FEATURE_PS_WIDE |
713 | /* -w is a bit complicated */ |
714 | int w_count = 0; |
715 | opt_complementary = "-:ww"; |
716 | opts = getopt32(argv, IF_SELINUX("Z")IF_FEATURE_SHOW_THREADS("T")IF_FEATURE_PS_LONG("l") |
717 | "w", &w_count); |
718 | /* if w is given once, GNU ps sets the width to 132, |
719 | * if w is given more than once, it is "unlimited" |
720 | */ |
721 | if (w_count) { |
722 | terminal_width = (w_count == 1) ? 132 : MAX_WIDTH; |
723 | } else { |
724 | terminal_width = get_terminal_width(0); |
725 | /* Go one less... */ |
726 | if (--terminal_width > MAX_WIDTH) |
727 | terminal_width = MAX_WIDTH; |
728 | } |
729 | # else |
730 | /* -w is not supported, only -Z and/or -T */ |
731 | opt_complementary = "-"; |
732 | opts = getopt32(argv, IF_SELINUX("Z")IF_FEATURE_SHOW_THREADS("T")IF_FEATURE_PS_LONG("l")); |
733 | # endif |
734 | |
735 | # if ENABLE_SELINUX |
736 | if ((opts & OPT_Z) && is_selinux_enabled()) { |
737 | psscan_flags = PSSCAN_PID | PSSCAN_CONTEXT |
738 | | PSSCAN_STATE | PSSCAN_COMM; |
739 | puts(" PID CONTEXT STAT COMMAND"); |
740 | } else |
741 | # endif |
742 | if (opts & OPT_l) { |
743 | psscan_flags = PSSCAN_STATE | PSSCAN_UIDGID | PSSCAN_PID | PSSCAN_PPID |
744 | | PSSCAN_TTY | PSSCAN_STIME | PSSCAN_UTIME | PSSCAN_COMM |
745 | | PSSCAN_VSZ | PSSCAN_RSS; |
746 | /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ps.html |
747 | * mandates for -l: |
748 | * -F Flags (?) |
749 | * S State |
750 | * UID,PID,PPID |
751 | * -C CPU usage |
752 | * -PRI The priority of the process; higher numbers mean lower priority |
753 | * -NI Nice value |
754 | * -ADDR The address of the process (?) |
755 | * SZ The size in blocks of the core image |
756 | * -WCHAN The event for which the process is waiting or sleeping |
757 | * TTY |
758 | * TIME The cumulative execution time |
759 | * CMD |
760 | * We don't show fields marked with '-'. |
761 | * We show VSZ and RSS instead of SZ. |
762 | * We also show STIME (standard says that -f shows it, -l doesn't). |
763 | */ |
764 | puts("S UID PID PPID VSZ RSS TTY STIME TIME CMD"); |
765 | # if ENABLE_FEATURE_PS_LONG |
766 | now = time(NULL); |
767 | uptime = get_uptime(); |
768 | # endif |
769 | } |
770 | else { |
771 | puts(" PID USER VSZ STAT COMMAND"); |
772 | } |
773 | if (opts & OPT_T) { |
774 | psscan_flags |= PSSCAN_TASKS; |
775 | } |
776 | #endif |
777 | |
778 | p = NULL; |
779 | while ((p = procps_scan(p, psscan_flags)) != NULL) { |
780 | int len; |
781 | #if ENABLE_SELINUX |
782 | if (psscan_flags & PSSCAN_CONTEXT) { |
783 | len = printf("%5u %-32.32s %s ", |
784 | p->pid, |
785 | p->context ? p->context : "unknown", |
786 | p->state); |
787 | } else |
788 | #endif |
789 | { |
790 | char buf6[6]; |
791 | smart_ulltoa5(p->vsz, buf6, " mgtpezy")[0] = '\0'; |
792 | #if ENABLE_FEATURE_PS_LONG |
793 | if (opts & OPT_l) { |
794 | char bufr[6], stime_str[6]; |
795 | char tty[2 * sizeof(int)*3 + 2]; |
796 | char *endp; |
797 | unsigned sut = (p->stime + p->utime) / 100; |
798 | unsigned elapsed = uptime - (p->start_time / 100); |
799 | time_t start = now - elapsed; |
800 | struct tm *tm = localtime(&start); |
801 | |
802 | smart_ulltoa5(p->rss, bufr, " mgtpezy")[0] = '\0'; |
803 | |
804 | if (p->tty_major == 136) |
805 | /* It should be pts/N, not ptsN, but N > 9 |
806 | * will overflow field width... |
807 | */ |
808 | endp = stpcpy(tty, "pts"); |
809 | else |
810 | if (p->tty_major == 4) { |
811 | endp = stpcpy(tty, "tty"); |
812 | if (p->tty_minor >= 64) { |
813 | p->tty_minor -= 64; |
814 | *endp++ = 'S'; |
815 | } |
816 | } |
817 | else |
818 | endp = tty + sprintf(tty, "%d:", p->tty_major); |
819 | strcpy(endp, utoa(p->tty_minor)); |
820 | |
821 | strftime(stime_str, 6, (elapsed >= (24 * 60 * 60)) ? "%b%d" : "%H:%M", tm); |
822 | stime_str[5] = '\0'; |
823 | // S UID PID PPID VSZ RSS TTY STIME TIME CMD |
824 | len = printf("%c %5u %5u %5u %5s %5s %-5s %s %02u:%02u:%02u ", |
825 | p->state[0], p->uid, p->pid, p->ppid, buf6, bufr, tty, |
826 | stime_str, sut / 3600, (sut % 3600) / 60, sut % 60); |
827 | } else |
828 | #endif |
829 | { |
830 | const char *user = get_cached_username(p->uid); |
831 | len = printf("%5u %-8.8s %s %s ", |
832 | p->pid, user, buf6, p->state); |
833 | } |
834 | } |
835 | |
836 | { |
837 | int sz = terminal_width - len; |
838 | if (sz >= 0) { |
839 | char buf[sz + 1]; |
840 | read_cmdline(buf, sz, p->pid, p->comm); |
841 | puts(buf); |
842 | } |
843 | } |
844 | } |
845 | if (ENABLE_FEATURE_CLEAN_UP) |
846 | clear_username_cache(); |
847 | return EXIT_SUCCESS; |
848 | } |
849 | |
850 | #endif /* !ENABLE_DESKTOP */ |
851 |