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