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