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