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