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