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 the GPL version 2, see the file LICENSE in this tarball.
  10 */
  11
  12#include "libbb.h"
  13
  14/* Absolute maximum on output line length */
  15enum { MAX_WIDTH = 2*1024 };
  16
  17#if ENABLE_DESKTOP
  18
  19#include <sys/times.h> /* for times() */
  20//#include <sys/sysinfo.h> /* for sysinfo() */
  21#ifndef AT_CLKTCK
  22#define AT_CLKTCK 17
  23#endif
  24
  25
  26#if ENABLE_SELINUX
  27#define SELINUX_O_PREFIX "label,"
  28#define DEFAULT_O_STR    (SELINUX_O_PREFIX "pid,user" USE_FEATURE_PS_TIME(",time") ",args")
  29#else
  30#define DEFAULT_O_STR    ("pid,user" USE_FEATURE_PS_TIME(",time") ",args")
  31#endif
  32
  33typedef struct {
  34        uint16_t width;
  35        char name[6];
  36        const char *header;
  37        void (*f)(char *buf, int size, const procps_status_t *ps);
  38        int ps_flags;
  39} ps_out_t;
  40
  41struct globals {
  42        ps_out_t* out;
  43        int out_cnt;
  44        int print_header;
  45        int need_flags;
  46        char *buffer;
  47        unsigned terminal_width;
  48#if ENABLE_FEATURE_PS_TIME
  49        unsigned kernel_HZ;
  50        unsigned long long seconds_since_boot;
  51#endif
  52        char default_o[sizeof(DEFAULT_O_STR)];
  53};
  54#define G (*(struct globals*)&bb_common_bufsiz1)
  55#define out                (G.out               )
  56#define out_cnt            (G.out_cnt           )
  57#define print_header       (G.print_header      )
  58#define need_flags         (G.need_flags        )
  59#define buffer             (G.buffer            )
  60#define terminal_width     (G.terminal_width    )
  61#define kernel_HZ          (G.kernel_HZ         )
  62#define seconds_since_boot (G.seconds_since_boot)
  63#define default_o          (G.default_o         )
  64
  65#if ENABLE_FEATURE_PS_TIME
  66/* for ELF executables, notes are pushed before environment and args */
  67static ptrdiff_t find_elf_note(ptrdiff_t findme)
  68{
  69        ptrdiff_t *ep = (ptrdiff_t *) environ;
  70
  71        while (*ep++);
  72        while (*ep) {
  73                if (ep[0] == findme) {
  74                        return ep[1];
  75                }
  76                ep += 2;
  77        }
  78        return -1;
  79}
  80
  81#if ENABLE_FEATURE_PS_UNUSUAL_SYSTEMS
  82static unsigned get_HZ_by_waiting(void)
  83{
  84        struct timeval tv1, tv2;
  85        unsigned t1, t2, r, hz;
  86        unsigned cnt = cnt; /* for compiler */
  87        int diff;
  88
  89        r = 0;
  90
  91        /* Wait for times() to reach new tick */
  92        t1 = times(NULL);
  93        do {
  94                t2 = times(NULL);
  95        } while (t2 == t1);
  96        gettimeofday(&tv2, NULL);
  97
  98        do {
  99                t1 = t2;
 100                tv1.tv_usec = tv2.tv_usec;
 101
 102                /* Wait exactly one times() tick */
 103                do {
 104                        t2 = times(NULL);
 105                } while (t2 == t1);
 106                gettimeofday(&tv2, NULL);
 107
 108                /* Calculate ticks per sec, rounding up to even */
 109                diff = tv2.tv_usec - tv1.tv_usec;
 110                if (diff <= 0) diff += 1000000;
 111                hz = 1000000u / (unsigned)diff;
 112                hz = (hz+1) & ~1;
 113
 114                /* Count how many same hz values we saw */
 115                if (r != hz) {
 116                        r = hz;
 117                        cnt = 0;
 118                }
 119                cnt++;
 120        } while (cnt < 3); /* exit if saw 3 same values */
 121
 122        return r;
 123}
 124#else
 125static inline unsigned get_HZ_by_waiting(void)
 126{
 127        /* Better method? */
 128        return 100;
 129}
 130#endif
 131
 132static unsigned get_kernel_HZ(void)
 133{
 134        //char buf[64];
 135        struct sysinfo info;
 136
 137        if (kernel_HZ)
 138                return kernel_HZ;
 139
 140        /* Works for ELF only, Linux 2.4.0+ */
 141        kernel_HZ = find_elf_note(AT_CLKTCK);
 142        if (kernel_HZ == (unsigned)-1)
 143                kernel_HZ = get_HZ_by_waiting();
 144
 145        //if (open_read_close("/proc/uptime", buf, sizeof(buf) <= 0)
 146        //      bb_perror_msg_and_die("cannot read %s", "/proc/uptime");
 147        //buf[sizeof(buf)-1] = '\0';
 148        ///sscanf(buf, "%llu", &seconds_since_boot);
 149        sysinfo(&info);
 150        seconds_since_boot = info.uptime;
 151
 152        return kernel_HZ;
 153}
 154#endif
 155
 156/* Print value to buf, max size+1 chars (including trailing '\0') */
 157
 158static void func_user(char *buf, int size, const procps_status_t *ps)
 159{
 160#if 1
 161        safe_strncpy(buf, get_cached_username(ps->uid), size+1);
 162#else
 163        /* "compatible" version, but it's larger */
 164        /* procps 2.18 shows numeric UID if name overflows the field */
 165        /* TODO: get_cached_username() returns numeric string if
 166         * user has no passwd record, we will display it
 167         * left-justified here; too long usernames are shown
 168         * as _right-justified_ IDs. Is it worth fixing? */
 169        const char *user = get_cached_username(ps->uid);
 170        if (strlen(user) <= size)
 171                safe_strncpy(buf, user, size+1);
 172        else
 173                sprintf(buf, "%*u", size, (unsigned)ps->uid);
 174#endif
 175}
 176
 177static void func_comm(char *buf, int size, const procps_status_t *ps)
 178{
 179        safe_strncpy(buf, ps->comm, size+1);
 180}
 181
 182static void func_args(char *buf, int size, const procps_status_t *ps)
 183{
 184        read_cmdline(buf, size, ps->pid, ps->comm);
 185}
 186
 187static void func_pid(char *buf, int size, const procps_status_t *ps)
 188{
 189        sprintf(buf, "%*u", size, ps->pid);
 190}
 191
 192static void func_ppid(char *buf, int size, const procps_status_t *ps)
 193{
 194        sprintf(buf, "%*u", size, ps->ppid);
 195}
 196
 197static void func_pgid(char *buf, int size, const procps_status_t *ps)
 198{
 199        sprintf(buf, "%*u", size, ps->pgid);
 200}
 201
 202static void put_lu(char *buf, int size, unsigned long u)
 203{
 204        char buf4[5];
 205
 206        /* see http://en.wikipedia.org/wiki/Tera */
 207        smart_ulltoa4(u, buf4, " mgtpezy");
 208        buf4[4] = '\0';
 209        sprintf(buf, "%.*s", size, buf4);
 210}
 211
 212static void func_vsz(char *buf, int size, const procps_status_t *ps)
 213{
 214        put_lu(buf, size, ps->vsz);
 215}
 216
 217static void func_rss(char *buf, int size, const procps_status_t *ps)
 218{
 219        put_lu(buf, size, ps->rss);
 220}
 221
 222static void func_tty(char *buf, int size, const procps_status_t *ps)
 223{
 224        buf[0] = '?';
 225        buf[1] = '\0';
 226        if (ps->tty_major) /* tty field of "0" means "no tty" */
 227                snprintf(buf, size+1, "%u,%u", ps->tty_major, ps->tty_minor);
 228}
 229
 230#if ENABLE_FEATURE_PS_TIME
 231static void func_etime(char *buf, int size, const procps_status_t *ps)
 232{
 233        /* elapsed time [[dd-]hh:]mm:ss; here only mm:ss */
 234        unsigned long mm;
 235        unsigned ss;
 236
 237        mm = ps->start_time / get_kernel_HZ();
 238        /* must be after get_kernel_HZ()! */
 239        mm = seconds_since_boot - mm;
 240        ss = mm % 60;
 241        mm /= 60;
 242        snprintf(buf, size+1, "%3lu:%02u", mm, ss);
 243}
 244
 245static void func_time(char *buf, int size, const procps_status_t *ps)
 246{
 247        /* cumulative time [[dd-]hh:]mm:ss; here only mm:ss */
 248        unsigned long mm;
 249        unsigned ss;
 250
 251        mm = (ps->utime + ps->stime) / get_kernel_HZ();
 252        ss = mm % 60;
 253        mm /= 60;
 254        snprintf(buf, size+1, "%3lu:%02u", mm, ss);
 255}
 256#endif
 257
 258#if ENABLE_SELINUX
 259static void func_label(char *buf, int size, const procps_status_t *ps)
 260{
 261        safe_strncpy(buf, ps->context ? ps->context : "unknown", size+1);
 262}
 263#endif
 264
 265/*
 266static void func_nice(char *buf, int size, const procps_status_t *ps)
 267{
 268        ps->???
 269}
 270
 271static void func_pcpu(char *buf, int size, const procps_status_t *ps)
 272{
 273}
 274*/
 275
 276static const ps_out_t out_spec[] = {
 277// Mandated by POSIX:
 278        { 8                  , "user"  ,"USER"   ,func_user  ,PSSCAN_UIDGID  },
 279        { 16                 , "comm"  ,"COMMAND",func_comm  ,PSSCAN_COMM    },
 280        { 256                , "args"  ,"COMMAND",func_args  ,PSSCAN_COMM    },
 281        { 5                  , "pid"   ,"PID"    ,func_pid   ,PSSCAN_PID     },
 282        { 5                  , "ppid"  ,"PPID"   ,func_ppid  ,PSSCAN_PPID    },
 283        { 5                  , "pgid"  ,"PGID"   ,func_pgid  ,PSSCAN_PGID    },
 284#if ENABLE_FEATURE_PS_TIME
 285        { sizeof("ELAPSED")-1, "etime" ,"ELAPSED",func_etime ,PSSCAN_START_TIME },
 286#endif
 287//      { sizeof("GROUP"  )-1, "group" ,"GROUP"  ,func_group ,PSSCAN_UIDGID  },
 288//      { sizeof("NI"     )-1, "nice"  ,"NI"     ,func_nice  ,PSSCAN_        },
 289//      { sizeof("%CPU"   )-1, "pcpu"  ,"%CPU"   ,func_pcpu  ,PSSCAN_        },
 290//      { sizeof("RGROUP" )-1, "rgroup","RGROUP" ,func_rgroup,PSSCAN_UIDGID  },
 291//      { sizeof("RUSER"  )-1, "ruser" ,"RUSER"  ,func_ruser ,PSSCAN_UIDGID  },
 292#if ENABLE_FEATURE_PS_TIME
 293        { 6                  , "time"  ,"TIME"   ,func_time  ,PSSCAN_STIME | PSSCAN_UTIME },
 294#endif
 295        { 6                  , "tty"   ,"TT"     ,func_tty   ,PSSCAN_TTY     },
 296        { 4                  , "vsz"   ,"VSZ"    ,func_vsz   ,PSSCAN_VSZ     },
 297// Not mandated by POSIX, but useful:
 298        { 4                  , "rss"   ,"RSS"    ,func_rss   ,PSSCAN_RSS     },
 299#if ENABLE_SELINUX
 300        { 35                 , "label" ,"LABEL"  ,func_label ,PSSCAN_CONTEXT },
 301#endif
 302};
 303
 304static ps_out_t* new_out_t(void)
 305{
 306        out = xrealloc_vector(out, 2, out_cnt);
 307        return &out[out_cnt++];
 308}
 309
 310static const ps_out_t* find_out_spec(const char *name)
 311{
 312        unsigned i;
 313        for (i = 0; i < ARRAY_SIZE(out_spec); i++) {
 314                if (!strcmp(name, out_spec[i].name))
 315                        return &out_spec[i];
 316        }
 317        bb_error_msg_and_die("bad -o argument '%s'", name);
 318}
 319
 320static void parse_o(char* opt)
 321{
 322        ps_out_t* new;
 323        // POSIX: "-o is blank- or comma-separated list" (FIXME)
 324        char *comma, *equal;
 325        while (1) {
 326                comma = strchr(opt, ',');
 327                equal = strchr(opt, '=');
 328                if (comma && (!equal || equal > comma)) {
 329                        *comma = '\0';
 330                        *new_out_t() = *find_out_spec(opt);
 331                        *comma = ',';
 332                        opt = comma + 1;
 333                        continue;
 334                }
 335                break;
 336        }
 337        // opt points to last spec in comma separated list.
 338        // This one can have =HEADER part.
 339        new = new_out_t();
 340        if (equal)
 341                *equal = '\0';
 342        *new = *find_out_spec(opt);
 343        if (equal) {
 344                *equal = '=';
 345                new->header = equal + 1;
 346                // POSIX: the field widths shall be ... at least as wide as
 347                // the header text (default or overridden value).
 348                // If the header text is null, such as -o user=,
 349                // the field width shall be at least as wide as the
 350                // default header text
 351                if (new->header[0]) {
 352                        new->width = strlen(new->header);
 353                        print_header = 1;
 354                }
 355        } else
 356                print_header = 1;
 357}
 358
 359static void post_process(void)
 360{
 361        int i;
 362        int width = 0;
 363        for (i = 0; i < out_cnt; i++) {
 364                need_flags |= out[i].ps_flags;
 365                if (out[i].header[0]) {
 366                        print_header = 1;
 367                }
 368                width += out[i].width + 1; /* "FIELD " */
 369        }
 370#if ENABLE_SELINUX
 371        if (!is_selinux_enabled())
 372                need_flags &= ~PSSCAN_CONTEXT;
 373#endif
 374        buffer = xmalloc(width + 1); /* for trailing \0 */
 375}
 376
 377static void format_header(void)
 378{
 379        int i;
 380        ps_out_t* op;
 381        char *p;
 382
 383        if (!print_header)
 384                return;
 385        p = buffer;
 386        i = 0;
 387        if (out_cnt) {
 388                while (1) {
 389                        op = &out[i];
 390                        if (++i == out_cnt) /* do not pad last field */
 391                                break;
 392                        p += sprintf(p, "%-*s ", op->width, op->header);
 393                }
 394                strcpy(p, op->header);
 395        }
 396        printf("%.*s\n", terminal_width, buffer);
 397}
 398
 399static void format_process(const procps_status_t *ps)
 400{
 401        int i, len;
 402        char *p = buffer;
 403        i = 0;
 404        if (out_cnt) while (1) {
 405                out[i].f(p, out[i].width, ps);
 406                // POSIX: Any field need not be meaningful in all
 407                // implementations. In such a case a hyphen ( '-' )
 408                // should be output in place of the field value.
 409                if (!p[0]) {
 410                        p[0] = '-';
 411                        p[1] = '\0';
 412                }
 413                len = strlen(p);
 414                p += len;
 415                len = out[i].width - len + 1;
 416                if (++i == out_cnt) /* do not pad last field */
 417                        break;
 418                p += sprintf(p, "%*s", len, "");
 419        }
 420        printf("%.*s\n", terminal_width, buffer);
 421}
 422
 423int ps_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 424int ps_main(int argc UNUSED_PARAM, char **argv)
 425{
 426        procps_status_t *p;
 427        llist_t* opt_o = NULL;
 428        USE_SELINUX(int opt;)
 429
 430        // POSIX:
 431        // -a  Write information for all processes associated with terminals
 432        //     Implementations may omit session leaders from this list
 433        // -A  Write information for all processes
 434        // -d  Write information for all processes, except session leaders
 435        // -e  Write information for all processes (equivalent to -A.)
 436        // -f  Generate a full listing
 437        // -l  Generate a long listing
 438        // -o col1,col2,col3=header
 439        //     Select which columns to display
 440        /* We allow (and ignore) most of the above. FIXME */
 441        opt_complementary = "o::";
 442        USE_SELINUX(opt =) getopt32(argv, "Zo:aAdefl", &opt_o);
 443        if (opt_o) {
 444                do {
 445                        parse_o(llist_pop(&opt_o));
 446                } while (opt_o);
 447        } else {
 448                /* Below: parse_o() needs char*, NOT const char*... */
 449#if ENABLE_SELINUX
 450                if (!(opt & 1) || !is_selinux_enabled()) {
 451                        /* no -Z or no SELinux: do not show LABEL */
 452                        strcpy(default_o, DEFAULT_O_STR + sizeof(SELINUX_O_PREFIX)-1);
 453                } else
 454#endif
 455                {
 456                        strcpy(default_o, DEFAULT_O_STR);
 457                }
 458                parse_o(default_o);
 459        }
 460        post_process();
 461
 462        /* Was INT_MAX, but some libc's go belly up with printf("%.*s")
 463         * and such large widths */
 464        terminal_width = MAX_WIDTH;
 465        if (isatty(1)) {
 466                get_terminal_width_height(0, &terminal_width, NULL);
 467                if (--terminal_width > MAX_WIDTH)
 468                        terminal_width = MAX_WIDTH;
 469        }
 470        format_header();
 471
 472        p = NULL;
 473        while ((p = procps_scan(p, need_flags))) {
 474                format_process(p);
 475        }
 476
 477        return EXIT_SUCCESS;
 478}
 479
 480
 481#else /* !ENABLE_DESKTOP */
 482
 483
 484int ps_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 485int ps_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 486{
 487        procps_status_t *p = NULL;
 488        int len;
 489        SKIP_SELINUX(const) int use_selinux = 0;
 490        USE_SELINUX(int i;)
 491#if !ENABLE_FEATURE_PS_WIDE
 492        enum { terminal_width = 79 };
 493#else
 494        unsigned terminal_width;
 495        int w_count = 0;
 496#endif
 497
 498#if ENABLE_FEATURE_PS_WIDE || ENABLE_SELINUX
 499#if ENABLE_FEATURE_PS_WIDE
 500        opt_complementary = "-:ww";
 501        USE_SELINUX(i =) getopt32(argv, USE_SELINUX("Z") "w", &w_count);
 502        /* if w is given once, GNU ps sets the width to 132,
 503         * if w is given more than once, it is "unlimited"
 504         */
 505        if (w_count) {
 506                terminal_width = (w_count==1) ? 132 : MAX_WIDTH;
 507        } else {
 508                get_terminal_width_height(0, &terminal_width, NULL);
 509                /* Go one less... */
 510                if (--terminal_width > MAX_WIDTH)
 511                        terminal_width = MAX_WIDTH;
 512        }
 513#else /* only ENABLE_SELINUX */
 514        i = getopt32(argv, "Z");
 515#endif
 516#if ENABLE_SELINUX
 517        if ((i & 1) && is_selinux_enabled())
 518                use_selinux = PSSCAN_CONTEXT;
 519#endif
 520#endif /* ENABLE_FEATURE_PS_WIDE || ENABLE_SELINUX */
 521
 522        if (use_selinux)
 523                puts("  PID CONTEXT                          STAT COMMAND");
 524        else
 525                puts("  PID USER       VSZ STAT COMMAND");
 526
 527        while ((p = procps_scan(p, 0
 528                        | PSSCAN_PID
 529                        | PSSCAN_UIDGID
 530                        | PSSCAN_STATE
 531                        | PSSCAN_VSZ
 532                        | PSSCAN_COMM
 533                        | use_selinux
 534        ))) {
 535#if ENABLE_SELINUX
 536                if (use_selinux) {
 537                        len = printf("%5u %-32.32s %s  ",
 538                                        p->pid,
 539                                        p->context ? p->context : "unknown",
 540                                        p->state);
 541                } else
 542#endif
 543                {
 544                        const char *user = get_cached_username(p->uid);
 545                        //if (p->vsz == 0)
 546                        //      len = printf("%5u %-8.8s        %s ",
 547                        //              p->pid, user, p->state);
 548                        //else
 549                        {
 550                                char buf6[6];
 551                                smart_ulltoa5(p->vsz, buf6, " mgtpezy");
 552                                buf6[5] = '\0';
 553                                len = printf("%5u %-8.8s %s %s  ",
 554                                        p->pid, user, buf6, p->state);
 555                        }
 556                }
 557
 558                {
 559                        int sz = terminal_width - len;
 560                        char buf[sz + 1];
 561                        read_cmdline(buf, sz, p->pid, p->comm);
 562                        puts(buf);
 563                }
 564        }
 565        if (ENABLE_FEATURE_CLEAN_UP)
 566                clear_username_cache();
 567        return EXIT_SUCCESS;
 568}
 569
 570#endif /* ENABLE_DESKTOP */
 571