getopt32: factor out code to treat all args as options
[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 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 func_sid(char *buf, int size, const procps_status_t *ps)
344 {
345         sprintf(buf, "%*u", size, ps->sid);
346 }
347
348 static void put_lu(char *buf, int size, unsigned long u)
349 {
350         char buf4[5];
351
352         /* see http://en.wikipedia.org/wiki/Tera */
353         smart_ulltoa4(u, buf4, " mgtpezy")[0] = '\0';
354         sprintf(buf, "%.*s", size, buf4);
355 }
356
357 static void func_vsz(char *buf, int size, const procps_status_t *ps)
358 {
359         put_lu(buf, size, ps->vsz);
360 }
361
362 static void func_rss(char *buf, int size, const procps_status_t *ps)
363 {
364         put_lu(buf, size, ps->rss);
365 }
366
367 static void func_tty(char *buf, int size, const procps_status_t *ps)
368 {
369         buf[0] = '?';
370         buf[1] = '\0';
371         if (ps->tty_major) /* tty field of "0" means "no tty" */
372                 snprintf(buf, size+1, "%u,%u", ps->tty_major, ps->tty_minor);
373 }
374
375 #if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
376
377 static void func_rgroup(char *buf, int size, const procps_status_t *ps)
378 {
379         safe_strncpy(buf, get_cached_groupname(ps->rgid), size+1);
380 }
381
382 static void func_ruser(char *buf, int size, const procps_status_t *ps)
383 {
384         safe_strncpy(buf, get_cached_username(ps->ruid), size+1);
385 }
386
387 static void func_nice(char *buf, int size, const procps_status_t *ps)
388 {
389         sprintf(buf, "%*d", size, ps->niceness);
390 }
391
392 #endif
393
394 #if ENABLE_FEATURE_PS_TIME
395
396 static void func_etime(char *buf, int size, const procps_status_t *ps)
397 {
398         /* elapsed time [[dd-]hh:]mm:ss; here only mm:ss */
399         unsigned long mm;
400         unsigned ss;
401
402         mm = ps->start_time / get_kernel_HZ();
403         /* must be after get_kernel_HZ()! */
404         mm = G.seconds_since_boot - mm;
405         ss = mm % 60;
406         mm /= 60;
407         snprintf(buf, size+1, "%3lu:%02u", mm, ss);
408 }
409
410 static void func_time(char *buf, int size, const procps_status_t *ps)
411 {
412         /* cumulative time [[dd-]hh:]mm:ss; here only mm:ss */
413         unsigned long mm;
414         unsigned ss;
415
416         mm = (ps->utime + ps->stime) / get_kernel_HZ();
417         ss = mm % 60;
418         mm /= 60;
419         snprintf(buf, size+1, "%3lu:%02u", mm, ss);
420 }
421
422 #endif
423
424 #if ENABLE_SELINUX
425 static void func_label(char *buf, int size, const procps_status_t *ps)
426 {
427         safe_strncpy(buf, ps->context ? ps->context : "unknown", size+1);
428 }
429 #endif
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         { 5                  , "sid"   ,"SID"    ,func_sid   ,PSSCAN_SID     },
462         { 4                  , "stat"  ,"STAT"   ,func_state ,PSSCAN_STATE   },
463         { 4                  , "rss"   ,"RSS"    ,func_rss   ,PSSCAN_RSS     },
464 #if ENABLE_SELINUX
465         { 35                 , "label" ,"LABEL"  ,func_label ,PSSCAN_CONTEXT },
466 #endif
467 };
468
469 static ps_out_t* new_out_t(void)
470 {
471         out = xrealloc_vector(out, 2, out_cnt);
472         return &out[out_cnt++];
473 }
474
475 static const ps_out_t* find_out_spec(const char *name)
476 {
477         unsigned i;
478         char buf[ARRAY_SIZE(out_spec)*7 + 1];
479         char *p = buf;
480
481         for (i = 0; i < ARRAY_SIZE(out_spec); i++) {
482                 if (strncmp(name, out_spec[i].name6, 6) == 0)
483                         return &out_spec[i];
484                 p += sprintf(p, "%.6s,", out_spec[i].name6);
485         }
486         p[-1] = '\0';
487         bb_error_msg_and_die("bad -o argument '%s', supported arguments: %s", name, buf);
488 }
489
490 static void parse_o(char* opt)
491 {
492         ps_out_t* new;
493         // POSIX: "-o is blank- or comma-separated list" (FIXME)
494         char *comma, *equal;
495         while (1) {
496                 comma = strchr(opt, ',');
497                 equal = strchr(opt, '=');
498                 if (comma && (!equal || equal > comma)) {
499                         *comma = '\0';
500                         *new_out_t() = *find_out_spec(opt);
501                         *comma = ',';
502                         opt = comma + 1;
503                         continue;
504                 }
505                 break;
506         }
507         // opt points to last spec in comma separated list.
508         // This one can have =HEADER part.
509         new = new_out_t();
510         if (equal)
511                 *equal = '\0';
512         *new = *find_out_spec(opt);
513         if (equal) {
514                 *equal = '=';
515                 new->header = equal + 1;
516                 // POSIX: the field widths shall be ... at least as wide as
517                 // the header text (default or overridden value).
518                 // If the header text is null, such as -o user=,
519                 // the field width shall be at least as wide as the
520                 // default header text
521                 if (new->header[0]) {
522                         new->width = strlen(new->header);
523                         print_header = 1;
524                 }
525         } else
526                 print_header = 1;
527 }
528
529 static void alloc_line_buffer(void)
530 {
531         int i;
532         int width = 0;
533         for (i = 0; i < out_cnt; i++) {
534                 need_flags |= out[i].ps_flags;
535                 if (out[i].header[0]) {
536                         print_header = 1;
537                 }
538                 width += out[i].width + 1; /* "FIELD " */
539                 if ((int)(width - terminal_width) > 0) {
540                         /* The rest does not fit on the screen */
541                         //out[i].width -= (width - terminal_width - 1);
542                         out_cnt = i + 1;
543                         break;
544                 }
545         }
546 #if ENABLE_SELINUX
547         if (!is_selinux_enabled())
548                 need_flags &= ~PSSCAN_CONTEXT;
549 #endif
550         buffer = xmalloc(width + 1); /* for trailing \0 */
551 }
552
553 static void format_header(void)
554 {
555         int i;
556         ps_out_t* op;
557         char *p;
558
559         if (!print_header)
560                 return;
561         p = buffer;
562         i = 0;
563         if (out_cnt) {
564                 while (1) {
565                         op = &out[i];
566                         if (++i == out_cnt) /* do not pad last field */
567                                 break;
568                         p += sprintf(p, "%-*s ", op->width, op->header);
569                 }
570                 strcpy(p, op->header);
571         }
572         printf("%.*s\n", terminal_width, buffer);
573 }
574
575 static void format_process(const procps_status_t *ps)
576 {
577         int i, len;
578         char *p = buffer;
579         i = 0;
580         if (out_cnt) while (1) {
581                 out[i].f(p, out[i].width, ps);
582                 // POSIX: Any field need not be meaningful in all
583                 // implementations. In such a case a hyphen ( '-' )
584                 // should be output in place of the field value.
585                 if (!p[0]) {
586                         p[0] = '-';
587                         p[1] = '\0';
588                 }
589                 len = strlen(p);
590                 p += len;
591                 len = out[i].width - len + 1;
592                 if (++i == out_cnt) /* do not pad last field */
593                         break;
594                 p += sprintf(p, "%*s", len, "");
595         }
596         printf("%.*s\n", terminal_width, buffer);
597 }
598
599 #if ENABLE_SELINUX
600 # define SELINUX_O_PREFIX "label,"
601 # define DEFAULT_O_STR    (SELINUX_O_PREFIX "pid,user" IF_FEATURE_PS_TIME(",time") ",args")
602 #else
603 # define DEFAULT_O_STR    ("pid,user" IF_FEATURE_PS_TIME(",time") ",args")
604 #endif
605
606 int ps_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
607 int ps_main(int argc UNUSED_PARAM, char **argv)
608 {
609         procps_status_t *p;
610         llist_t* opt_o = NULL;
611         char default_o[sizeof(DEFAULT_O_STR)];
612 #if ENABLE_SELINUX || ENABLE_FEATURE_SHOW_THREADS
613         int opt;
614 #endif
615         enum {
616                 OPT_Z = (1 << 0),
617                 OPT_o = (1 << 1),
618                 OPT_a = (1 << 2),
619                 OPT_A = (1 << 3),
620                 OPT_d = (1 << 4),
621                 OPT_e = (1 << 5),
622                 OPT_f = (1 << 6),
623                 OPT_l = (1 << 7),
624                 OPT_T = (1 << 8) * ENABLE_FEATURE_SHOW_THREADS,
625         };
626
627         INIT_G();
628
629         // POSIX:
630         // -a  Write information for all processes associated with terminals
631         //     Implementations may omit session leaders from this list
632         // -A  Write information for all processes
633         // -d  Write information for all processes, except session leaders
634         // -e  Write information for all processes (equivalent to -A)
635         // -f  Generate a full listing
636         // -l  Generate a long listing
637         // -o col1,col2,col3=header
638         //     Select which columns to display
639         /* We allow (and ignore) most of the above. FIXME.
640          * -T is picked for threads (POSIX hasn't standardized it).
641          * procps v3.2.7 supports -T and shows tids as SPID column,
642          * it also supports -L where it shows tids as LWP column.
643          */
644 #if ENABLE_SELINUX || ENABLE_FEATURE_SHOW_THREADS
645         opt =
646 #endif
647                 getopt32(argv, "Zo:*aAdefl"IF_FEATURE_SHOW_THREADS("T"), &opt_o);
648
649         if (opt_o) {
650                 do {
651                         parse_o(llist_pop(&opt_o));
652                 } while (opt_o);
653         } else {
654                 /* Below: parse_o() needs char*, NOT const char*,
655                  * can't pass it constant string. Need to make a copy first.
656                  */
657 #if ENABLE_SELINUX
658                 if (!(opt & OPT_Z) || !is_selinux_enabled()) {
659                         /* no -Z or no SELinux: do not show LABEL */
660                         strcpy(default_o, DEFAULT_O_STR + sizeof(SELINUX_O_PREFIX)-1);
661                 } else
662 #endif
663                 {
664                         strcpy(default_o, DEFAULT_O_STR);
665                 }
666                 parse_o(default_o);
667         }
668 #if ENABLE_FEATURE_SHOW_THREADS
669         if (opt & OPT_T)
670                 need_flags |= PSSCAN_TASKS;
671 #endif
672
673         /* Was INT_MAX, but some libc's go belly up with printf("%.*s")
674          * and such large widths */
675         terminal_width = MAX_WIDTH;
676         if (isatty(1)) {
677                 terminal_width = get_terminal_width(0);
678                 if (--terminal_width > MAX_WIDTH)
679                         terminal_width = MAX_WIDTH;
680         }
681         alloc_line_buffer();
682         format_header();
683
684         p = NULL;
685         while ((p = procps_scan(p, need_flags)) != NULL) {
686                 format_process(p);
687         }
688
689         return EXIT_SUCCESS;
690 }
691
692
693 #else /* !ENABLE_DESKTOP */
694
695
696 int ps_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
697 int ps_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
698 {
699         procps_status_t *p;
700         int psscan_flags = PSSCAN_PID | PSSCAN_UIDGID
701                         | PSSCAN_STATE | PSSCAN_VSZ | PSSCAN_COMM;
702         unsigned terminal_width IF_NOT_FEATURE_PS_WIDE(= 79);
703         enum {
704                 OPT_Z = (1 << 0) * ENABLE_SELINUX,
705                 OPT_T = (1 << ENABLE_SELINUX) * ENABLE_FEATURE_SHOW_THREADS,
706                 OPT_l = (1 << ENABLE_SELINUX) * (1 << ENABLE_FEATURE_SHOW_THREADS) * ENABLE_FEATURE_PS_LONG,
707         };
708 #if ENABLE_FEATURE_PS_LONG
709         time_t now = now; /* for compiler */
710         unsigned long uptime = uptime;
711 #endif
712         /* If we support any options, parse argv */
713 #if ENABLE_SELINUX || ENABLE_FEATURE_SHOW_THREADS || ENABLE_FEATURE_PS_WIDE || ENABLE_FEATURE_PS_LONG
714         int opts = 0;
715 # if ENABLE_FEATURE_PS_WIDE
716         /* -w is a bit complicated */
717         int w_count = 0;
718         make_all_argv_opts(argv);
719         opt_complementary = "ww";
720         opts = getopt32(argv, IF_SELINUX("Z")IF_FEATURE_SHOW_THREADS("T")IF_FEATURE_PS_LONG("l")
721                                         "w", &w_count);
722         /* if w is given once, GNU ps sets the width to 132,
723          * if w is given more than once, it is "unlimited"
724          */
725         if (w_count) {
726                 terminal_width = (w_count == 1) ? 132 : MAX_WIDTH;
727         } else {
728                 terminal_width = get_terminal_width(0);
729                 /* Go one less... */
730                 if (--terminal_width > MAX_WIDTH)
731                         terminal_width = MAX_WIDTH;
732         }
733 # else
734         /* -w is not supported, only -Z and/or -T */
735         make_all_argv_opts(argv);
736         opts = getopt32(argv, IF_SELINUX("Z")IF_FEATURE_SHOW_THREADS("T")IF_FEATURE_PS_LONG("l"));
737 # endif
738
739 # if ENABLE_SELINUX
740         if ((opts & OPT_Z) && is_selinux_enabled()) {
741                 psscan_flags = PSSCAN_PID | PSSCAN_CONTEXT
742                                 | PSSCAN_STATE | PSSCAN_COMM;
743                 puts("  PID CONTEXT                          STAT COMMAND");
744         } else
745 # endif
746         if (opts & OPT_l) {
747                 psscan_flags = PSSCAN_STATE | PSSCAN_UIDGID | PSSCAN_PID | PSSCAN_PPID
748                         | PSSCAN_TTY | PSSCAN_STIME | PSSCAN_UTIME | PSSCAN_COMM
749                         | PSSCAN_VSZ | PSSCAN_RSS;
750 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ps.html
751  * mandates for -l:
752  * -F     Flags (?)
753  * S      State
754  * UID,PID,PPID
755  * -C     CPU usage
756  * -PRI   The priority of the process; higher numbers mean lower priority
757  * -NI    Nice value
758  * -ADDR  The address of the process (?)
759  * SZ     The size in blocks of the core image
760  * -WCHAN The event for which the process is waiting or sleeping
761  * TTY
762  * TIME   The cumulative execution time
763  * CMD
764  * We don't show fields marked with '-'.
765  * We show VSZ and RSS instead of SZ.
766  * We also show STIME (standard says that -f shows it, -l doesn't).
767  */
768                 puts("S   UID   PID  PPID   VSZ   RSS TTY   STIME TIME     CMD");
769 # if ENABLE_FEATURE_PS_LONG
770                 now = time(NULL);
771                 uptime = get_uptime();
772 # endif
773         }
774         else {
775                 puts("  PID USER       VSZ STAT COMMAND");
776         }
777         if (opts & OPT_T) {
778                 psscan_flags |= PSSCAN_TASKS;
779         }
780 #endif
781
782         p = NULL;
783         while ((p = procps_scan(p, psscan_flags)) != NULL) {
784                 int len;
785 #if ENABLE_SELINUX
786                 if (psscan_flags & PSSCAN_CONTEXT) {
787                         len = printf("%5u %-32.32s %s  ",
788                                         p->pid,
789                                         p->context ? p->context : "unknown",
790                                         p->state);
791                 } else
792 #endif
793                 {
794                         char buf6[6];
795                         smart_ulltoa5(p->vsz, buf6, " mgtpezy")[0] = '\0';
796 #if ENABLE_FEATURE_PS_LONG
797                         if (opts & OPT_l) {
798                                 char bufr[6], stime_str[6];
799                                 char tty[2 * sizeof(int)*3 + 2];
800                                 char *endp;
801                                 unsigned sut = (p->stime + p->utime) / 100;
802                                 unsigned elapsed = uptime - (p->start_time / 100);
803                                 time_t start = now - elapsed;
804                                 struct tm *tm = localtime(&start);
805
806                                 smart_ulltoa5(p->rss, bufr, " mgtpezy")[0] = '\0';
807
808                                 if (p->tty_major == 136)
809                                         /* It should be pts/N, not ptsN, but N > 9
810                                          * will overflow field width...
811                                          */
812                                         endp = stpcpy(tty, "pts");
813                                 else
814                                 if (p->tty_major == 4) {
815                                         endp = stpcpy(tty, "tty");
816                                         if (p->tty_minor >= 64) {
817                                                 p->tty_minor -= 64;
818                                                 *endp++ = 'S';
819                                         }
820                                 }
821                                 else
822                                         endp = tty + sprintf(tty, "%d:", p->tty_major);
823                                 strcpy(endp, utoa(p->tty_minor));
824
825                                 strftime(stime_str, 6, (elapsed >= (24 * 60 * 60)) ? "%b%d" : "%H:%M", tm);
826                                 stime_str[5] = '\0';
827                                 //            S  UID PID PPID VSZ RSS TTY STIME TIME        CMD
828                                 len = printf("%c %5u %5u %5u %5s %5s %-5s %s %02u:%02u:%02u ",
829                                         p->state[0], p->uid, p->pid, p->ppid, buf6, bufr, tty,
830                                         stime_str, sut / 3600, (sut % 3600) / 60, sut % 60);
831                         } else
832 #endif
833                         {
834                                 const char *user = get_cached_username(p->uid);
835                                 len = printf("%5u %-8.8s %s %s  ",
836                                         p->pid, user, buf6, p->state);
837                         }
838                 }
839
840                 {
841                         int sz = terminal_width - len;
842                         if (sz >= 0) {
843                                 char buf[sz + 1];
844                                 read_cmdline(buf, sz, p->pid, p->comm);
845                                 puts(buf);
846                         }
847                 }
848         }
849         if (ENABLE_FEATURE_CLEAN_UP)
850                 clear_username_cache();
851         return EXIT_SUCCESS;
852 }
853
854 #endif /* !ENABLE_DESKTOP */