busybox/libbb/procps.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Utility routines.
   4 *
   5 * Copyright 1998 by Albert Cahalan; all rights reserved.
   6 * Copyright (C) 2002 by Vladimir Oleynik <dzo@simtreas.ru>
   7 * SELinux support: (c) 2007 by Yuichi Nakamura <ynakam@hitachisoft.jp>
   8 *
   9 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  10 */
  11
  12#include "libbb.h"
  13
  14
  15typedef struct unsigned_to_name_map_t {
  16        long id;
  17        char name[USERNAME_MAX_SIZE];
  18} unsigned_to_name_map_t;
  19
  20typedef struct cache_t {
  21        unsigned_to_name_map_t *cache;
  22        int size;
  23} cache_t;
  24
  25static cache_t username, groupname;
  26
  27static void clear_cache(cache_t *cp)
  28{
  29        free(cp->cache);
  30        cp->cache = NULL;
  31        cp->size = 0;
  32}
  33void FAST_FUNC clear_username_cache(void)
  34{
  35        clear_cache(&username);
  36        clear_cache(&groupname);
  37}
  38
  39#if 0 /* more generic, but we don't need that yet */
  40/* Returns -N-1 if not found. */
  41/* cp->cache[N] is allocated and must be filled in this case */
  42static int get_cached(cache_t *cp, unsigned id)
  43{
  44        int i;
  45        for (i = 0; i < cp->size; i++)
  46                if (cp->cache[i].id == id)
  47                        return i;
  48        i = cp->size++;
  49        cp->cache = xrealloc_vector(cp->cache, 2, i);
  50        cp->cache[i++].id = id;
  51        return -i;
  52}
  53#endif
  54
  55static char* get_cached(cache_t *cp, long id,
  56                        char* FAST_FUNC x2x_utoa(long id))
  57{
  58        int i;
  59        for (i = 0; i < cp->size; i++)
  60                if (cp->cache[i].id == id)
  61                        return cp->cache[i].name;
  62        i = cp->size++;
  63        cp->cache = xrealloc_vector(cp->cache, 2, i);
  64        cp->cache[i].id = id;
  65        /* Never fails. Generates numeric string if name isn't found */
  66        safe_strncpy(cp->cache[i].name, x2x_utoa(id), sizeof(cp->cache[i].name));
  67        return cp->cache[i].name;
  68}
  69const char* FAST_FUNC get_cached_username(uid_t uid)
  70{
  71        return get_cached(&username, uid, uid2uname_utoa);
  72}
  73const char* FAST_FUNC get_cached_groupname(gid_t gid)
  74{
  75        return get_cached(&groupname, gid, gid2group_utoa);
  76}
  77
  78
  79#define PROCPS_BUFSIZE 1024
  80
  81static int read_to_buf(const char *filename, void *buf)
  82{
  83        int fd;
  84        /* open_read_close() would do two reads, checking for EOF.
  85         * When you have 10000 /proc/$NUM/stat to read, it isn't desirable */
  86        ssize_t ret = -1;
  87        fd = open(filename, O_RDONLY);
  88        if (fd >= 0) {
  89                ret = read(fd, buf, PROCPS_BUFSIZE-1);
  90                close(fd);
  91        }
  92        ((char *)buf)[ret > 0 ? ret : 0] = '\0';
  93        return ret;
  94}
  95
  96static procps_status_t* FAST_FUNC alloc_procps_scan(void)
  97{
  98        unsigned n = getpagesize();
  99        procps_status_t* sp = xzalloc(sizeof(procps_status_t));
 100        sp->dir = xopendir("/proc");
 101        while (1) {
 102                n >>= 1;
 103                if (!n) break;
 104                sp->shift_pages_to_bytes++;
 105        }
 106        sp->shift_pages_to_kb = sp->shift_pages_to_bytes - 10;
 107        return sp;
 108}
 109
 110void FAST_FUNC free_procps_scan(procps_status_t* sp)
 111{
 112        closedir(sp->dir);
 113#if ENABLE_FEATURE_SHOW_THREADS
 114        if (sp->task_dir)
 115                closedir(sp->task_dir);
 116#endif
 117        free(sp->argv0);
 118        free(sp->exe);
 119        IF_SELINUX(free(sp->context);)
 120        free(sp);
 121}
 122
 123#if ENABLE_FEATURE_TOPMEM || ENABLE_PMAP
 124static unsigned long fast_strtoul_16(char **endptr)
 125{
 126        unsigned char c;
 127        char *str = *endptr;
 128        unsigned long n = 0;
 129
 130        while ((c = *str++) != ' ') {
 131                c = ((c|0x20) - '0');
 132                if (c > 9)
 133                        // c = c + '0' - 'a' + 10:
 134                        c = c - ('a' - '0' - 10);
 135                n = n*16 + c;
 136        }
 137        *endptr = str; /* We skip trailing space! */
 138        return n;
 139}
 140#endif
 141
 142#if ENABLE_FEATURE_FAST_TOP || ENABLE_FEATURE_TOPMEM || ENABLE_PMAP
 143/* We cut a lot of corners here for speed */
 144static unsigned long fast_strtoul_10(char **endptr)
 145{
 146        char c;
 147        char *str = *endptr;
 148        unsigned long n = *str - '0';
 149
 150        while ((c = *++str) != ' ')
 151                n = n*10 + (c - '0');
 152
 153        *endptr = str + 1; /* We skip trailing space! */
 154        return n;
 155}
 156
 157# if ENABLE_FEATURE_FAST_TOP
 158static long fast_strtol_10(char **endptr)
 159{
 160        if (**endptr != '-')
 161                return fast_strtoul_10(endptr);
 162
 163        (*endptr)++;
 164        return - (long)fast_strtoul_10(endptr);
 165}
 166# endif
 167
 168static char *skip_fields(char *str, int count)
 169{
 170        do {
 171                while (*str++ != ' ')
 172                        continue;
 173                /* we found a space char, str points after it */
 174        } while (--count);
 175        return str;
 176}
 177#endif
 178
 179#if ENABLE_FEATURE_TOPMEM || ENABLE_PMAP
 180int FAST_FUNC procps_read_smaps(pid_t pid, struct smaprec *total,
 181                      void (*cb)(struct smaprec *, void *), void *data)
 182{
 183        FILE *file;
 184        struct smaprec currec;
 185        char filename[sizeof("/proc/%u/smaps") + sizeof(int)*3];
 186        char buf[PROCPS_BUFSIZE];
 187#if !ENABLE_PMAP
 188        void (*cb)(struct smaprec *, void *) = NULL;
 189        void *data = NULL;
 190#endif
 191
 192        sprintf(filename, "/proc/%u/smaps", (int)pid);
 193
 194        file = fopen_for_read(filename);
 195        if (!file)
 196                return 1;
 197
 198        memset(&currec, 0, sizeof(currec));
 199        while (fgets(buf, PROCPS_BUFSIZE, file)) {
 200                // Each mapping datum has this form:
 201                // f7d29000-f7d39000 rw-s ADR M:m OFS FILE
 202                // Size:                nnn kB
 203                // Rss:                 nnn kB
 204                // .....
 205
 206                char *tp = buf, *p;
 207
 208#define SCAN(S, X) \
 209                if (strncmp(tp, S, sizeof(S)-1) == 0) {              \
 210                        tp = skip_whitespace(tp + sizeof(S)-1);      \
 211                        total->X += currec.X = fast_strtoul_10(&tp); \
 212                        continue;                                    \
 213                }
 214                if (cb) {
 215                        SCAN("Pss:"  , smap_pss     );
 216                        SCAN("Swap:" , smap_swap    );
 217                }
 218                SCAN("Private_Dirty:", private_dirty);
 219                SCAN("Private_Clean:", private_clean);
 220                SCAN("Shared_Dirty:" , shared_dirty );
 221                SCAN("Shared_Clean:" , shared_clean );
 222#undef SCAN
 223                tp = strchr(buf, '-');
 224                if (tp) {
 225                        // We reached next mapping - the line of this form:
 226                        // f7d29000-f7d39000 rw-s ADR M:m OFS FILE
 227
 228                        if (cb) {
 229                                /* If we have a previous record, there's nothing more
 230                                 * for it, call the callback and clear currec
 231                                 */
 232                                if (currec.smap_size)
 233                                        cb(&currec, data);
 234                                free(currec.smap_name);
 235                        }
 236                        memset(&currec, 0, sizeof(currec));
 237
 238                        *tp = ' ';
 239                        tp = buf;
 240                        currec.smap_start = fast_strtoul_16(&tp);
 241                        currec.smap_size = (fast_strtoul_16(&tp) - currec.smap_start) >> 10;
 242
 243                        strncpy(currec.smap_mode, tp, sizeof(currec.smap_mode)-1);
 244
 245                        // skipping "rw-s ADR M:m OFS "
 246                        tp = skip_whitespace(skip_fields(tp, 4));
 247                        // filter out /dev/something (something != zero)
 248                        if (strncmp(tp, "/dev/", 5) != 0 || strcmp(tp, "/dev/zero\n") == 0) {
 249                                if (currec.smap_mode[1] == 'w') {
 250                                        currec.mapped_rw = currec.smap_size;
 251                                        total->mapped_rw += currec.smap_size;
 252                                } else if (currec.smap_mode[1] == '-') {
 253                                        currec.mapped_ro = currec.smap_size;
 254                                        total->mapped_ro += currec.smap_size;
 255                                }
 256                        }
 257
 258                        if (strcmp(tp, "[stack]\n") == 0)
 259                                total->stack += currec.smap_size;
 260                        if (cb) {
 261                                p = skip_non_whitespace(tp);
 262                                if (p == tp) {
 263                                        currec.smap_name = xstrdup("  [ anon ]");
 264                                } else {
 265                                        *p = '\0';
 266                                        currec.smap_name = xstrdup(tp);
 267                                }
 268                        }
 269                        total->smap_size += currec.smap_size;
 270                }
 271        }
 272        fclose(file);
 273
 274        if (cb) {
 275                if (currec.smap_size)
 276                        cb(&currec, data);
 277                free(currec.smap_name);
 278        }
 279
 280        return 0;
 281}
 282#endif
 283
 284void BUG_comm_size(void);
 285procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags)
 286{
 287        struct dirent *entry;
 288        char buf[PROCPS_BUFSIZE];
 289        char filename[sizeof("/proc//cmdline") + sizeof(int)*3];
 290        char *filename_tail;
 291        long tasknice;
 292        unsigned pid;
 293        int n;
 294        struct stat sb;
 295
 296        if (!sp)
 297                sp = alloc_procps_scan();
 298
 299        for (;;) {
 300#if ENABLE_FEATURE_SHOW_THREADS
 301                if ((flags & PSSCAN_TASKS) && sp->task_dir) {
 302                        entry = readdir(sp->task_dir);
 303                        if (entry)
 304                                goto got_entry;
 305                        closedir(sp->task_dir);
 306                        sp->task_dir = NULL;
 307                }
 308#endif
 309                entry = readdir(sp->dir);
 310                if (entry == NULL) {
 311                        free_procps_scan(sp);
 312                        return NULL;
 313                }
 314 IF_FEATURE_SHOW_THREADS(got_entry:)
 315                pid = bb_strtou(entry->d_name, NULL, 10);
 316                if (errno)
 317                        continue;
 318#if ENABLE_FEATURE_SHOW_THREADS
 319                if ((flags & PSSCAN_TASKS) && !sp->task_dir) {
 320                        /* We found another /proc/PID. Do not use it,
 321                         * there will be /proc/PID/task/PID (same PID!),
 322                         * so just go ahead and dive into /proc/PID/task. */
 323                        char task_dir[sizeof("/proc/%u/task") + sizeof(int)*3];
 324                        sprintf(task_dir, "/proc/%u/task", pid);
 325                        sp->task_dir = xopendir(task_dir);
 326                        continue;
 327                }
 328#endif
 329
 330                /* After this point we can:
 331                 * "break": stop parsing, return the data
 332                 * "continue": try next /proc/XXX
 333                 */
 334
 335                memset(&sp->vsz, 0, sizeof(*sp) - offsetof(procps_status_t, vsz));
 336
 337                sp->pid = pid;
 338                if (!(flags & ~PSSCAN_PID))
 339                        break; /* we needed only pid, we got it */
 340
 341#if ENABLE_SELINUX
 342                if (flags & PSSCAN_CONTEXT) {
 343                        if (getpidcon(sp->pid, &sp->context) < 0)
 344                                sp->context = NULL;
 345                }
 346#endif
 347
 348                filename_tail = filename + sprintf(filename, "/proc/%u/", pid);
 349
 350                if (flags & PSSCAN_UIDGID) {
 351                        if (stat(filename, &sb))
 352                                continue; /* process probably exited */
 353                        /* Effective UID/GID, not real */
 354                        sp->uid = sb.st_uid;
 355                        sp->gid = sb.st_gid;
 356                }
 357
 358                if (flags & PSSCAN_STAT) {
 359                        char *cp, *comm1;
 360                        int tty;
 361#if !ENABLE_FEATURE_FAST_TOP
 362                        unsigned long vsz, rss;
 363#endif
 364                        /* see proc(5) for some details on this */
 365                        strcpy(filename_tail, "stat");
 366                        n = read_to_buf(filename, buf);
 367                        if (n < 0)
 368                                continue; /* process probably exited */
 369                        cp = strrchr(buf, ')'); /* split into "PID (cmd" and "<rest>" */
 370                        /*if (!cp || cp[1] != ' ')
 371                                continue;*/
 372                        cp[0] = '\0';
 373                        if (sizeof(sp->comm) < 16)
 374                                BUG_comm_size();
 375                        comm1 = strchr(buf, '(');
 376                        /*if (comm1)*/
 377                                safe_strncpy(sp->comm, comm1 + 1, sizeof(sp->comm));
 378
 379#if !ENABLE_FEATURE_FAST_TOP
 380                        n = sscanf(cp+2,
 381                                "%c %u "               /* state, ppid */
 382                                "%u %u %d %*s "        /* pgid, sid, tty, tpgid */
 383                                "%*s %*s %*s %*s %*s " /* flags, min_flt, cmin_flt, maj_flt, cmaj_flt */
 384                                "%lu %lu "             /* utime, stime */
 385                                "%*s %*s %*s "         /* cutime, cstime, priority */
 386                                "%ld "                 /* nice */
 387                                "%*s %*s "             /* timeout, it_real_value */
 388                                "%lu "                 /* start_time */
 389                                "%lu "                 /* vsize */
 390                                "%lu "                 /* rss */
 391# if ENABLE_FEATURE_TOP_SMP_PROCESS
 392                                "%*s %*s %*s %*s %*s %*s " /*rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */
 393                                "%*s %*s %*s %*s "         /*signal, blocked, sigignore, sigcatch */
 394                                "%*s %*s %*s %*s "         /*wchan, nswap, cnswap, exit_signal */
 395                                "%d"                       /*cpu last seen on*/
 396# endif
 397                                ,
 398                                sp->state, &sp->ppid,
 399                                &sp->pgid, &sp->sid, &tty,
 400                                &sp->utime, &sp->stime,
 401                                &tasknice,
 402                                &sp->start_time,
 403                                &vsz,
 404                                &rss
 405# if ENABLE_FEATURE_TOP_SMP_PROCESS
 406                                , &sp->last_seen_on_cpu
 407# endif
 408                                );
 409
 410                        if (n < 11)
 411                                continue; /* bogus data, get next /proc/XXX */
 412# if ENABLE_FEATURE_TOP_SMP_PROCESS
 413                        if (n < 11+15)
 414                                sp->last_seen_on_cpu = 0;
 415# endif
 416
 417                        /* vsz is in bytes and we want kb */
 418                        sp->vsz = vsz >> 10;
 419                        /* vsz is in bytes but rss is in *PAGES*! Can you believe that? */
 420                        sp->rss = rss << sp->shift_pages_to_kb;
 421                        sp->tty_major = (tty >> 8) & 0xfff;
 422                        sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00);
 423#else
 424/* This costs ~100 bytes more but makes top faster by 20%
 425 * If you run 10000 processes, this may be important for you */
 426                        sp->state[0] = cp[2];
 427                        cp += 4;
 428                        sp->ppid = fast_strtoul_10(&cp);
 429                        sp->pgid = fast_strtoul_10(&cp);
 430                        sp->sid = fast_strtoul_10(&cp);
 431                        tty = fast_strtoul_10(&cp);
 432                        sp->tty_major = (tty >> 8) & 0xfff;
 433                        sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00);
 434                        cp = skip_fields(cp, 6); /* tpgid, flags, min_flt, cmin_flt, maj_flt, cmaj_flt */
 435                        sp->utime = fast_strtoul_10(&cp);
 436                        sp->stime = fast_strtoul_10(&cp);
 437                        cp = skip_fields(cp, 3); /* cutime, cstime, priority */
 438                        tasknice = fast_strtol_10(&cp);
 439                        cp = skip_fields(cp, 2); /* timeout, it_real_value */
 440                        sp->start_time = fast_strtoul_10(&cp);
 441                        /* vsz is in bytes and we want kb */
 442                        sp->vsz = fast_strtoul_10(&cp) >> 10;
 443                        /* vsz is in bytes but rss is in *PAGES*! Can you believe that? */
 444                        sp->rss = fast_strtoul_10(&cp) << sp->shift_pages_to_kb;
 445# if ENABLE_FEATURE_TOP_SMP_PROCESS
 446                        /* (6): rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */
 447                        /* (4): signal, blocked, sigignore, sigcatch */
 448                        /* (4): wchan, nswap, cnswap, exit_signal */
 449                        cp = skip_fields(cp, 14);
 450//FIXME: is it safe to assume this field exists?
 451                        sp->last_seen_on_cpu = fast_strtoul_10(&cp);
 452# endif
 453#endif /* FEATURE_FAST_TOP */
 454
 455#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
 456                        sp->niceness = tasknice;
 457#endif
 458
 459                        if (sp->vsz == 0 && sp->state[0] != 'Z')
 460                                sp->state[1] = 'W';
 461                        else
 462                                sp->state[1] = ' ';
 463                        if (tasknice < 0)
 464                                sp->state[2] = '<';
 465                        else if (tasknice) /* > 0 */
 466                                sp->state[2] = 'N';
 467                        else
 468                                sp->state[2] = ' ';
 469                }
 470
 471#if ENABLE_FEATURE_TOPMEM
 472                if (flags & PSSCAN_SMAPS)
 473                        procps_read_smaps(pid, &sp->smaps, NULL, NULL);
 474#endif /* TOPMEM */
 475#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
 476                if (flags & PSSCAN_RUIDGID) {
 477                        FILE *file;
 478
 479                        strcpy(filename_tail, "status");
 480                        file = fopen_for_read(filename);
 481                        if (file) {
 482                                while (fgets(buf, sizeof(buf), file)) {
 483                                        char *tp;
 484#define SCAN_TWO(str, name, statement) \
 485        if (strncmp(buf, str, sizeof(str)-1) == 0) { \
 486                tp = skip_whitespace(buf + sizeof(str)-1); \
 487                sscanf(tp, "%u", &sp->name); \
 488                statement; \
 489        }
 490                                        SCAN_TWO("Uid:", ruid, continue);
 491                                        SCAN_TWO("Gid:", rgid, break);
 492#undef SCAN_TWO
 493                                }
 494                                fclose(file);
 495                        }
 496                }
 497#endif /* PS_ADDITIONAL_COLUMNS */
 498                if (flags & PSSCAN_EXE) {
 499                        strcpy(filename_tail, "exe");
 500                        free(sp->exe);
 501                        sp->exe = xmalloc_readlink(filename);
 502                }
 503                /* Note: if /proc/PID/cmdline is empty,
 504                 * code below "breaks". Therefore it must be
 505                 * the last code to parse /proc/PID/xxx data
 506                 * (we used to have /proc/PID/exe parsing after it
 507                 * and were getting stale sp->exe).
 508                 */
 509#if 0 /* PSSCAN_CMD is not used */
 510                if (flags & (PSSCAN_CMD|PSSCAN_ARGV0)) {
 511                        free(sp->argv0);
 512                        sp->argv0 = NULL;
 513                        free(sp->cmd);
 514                        sp->cmd = NULL;
 515                        strcpy(filename_tail, "cmdline");
 516                        /* TODO: to get rid of size limits, read into malloc buf,
 517                         * then realloc it down to real size. */
 518                        n = read_to_buf(filename, buf);
 519                        if (n <= 0)
 520                                break;
 521                        if (flags & PSSCAN_ARGV0)
 522                                sp->argv0 = xstrdup(buf);
 523                        if (flags & PSSCAN_CMD) {
 524                                do {
 525                                        n--;
 526                                        if ((unsigned char)(buf[n]) < ' ')
 527                                                buf[n] = ' ';
 528                                } while (n);
 529                                sp->cmd = xstrdup(buf);
 530                        }
 531                }
 532#else
 533                if (flags & (PSSCAN_ARGV0|PSSCAN_ARGVN)) {
 534                        free(sp->argv0);
 535                        sp->argv0 = NULL;
 536                        strcpy(filename_tail, "cmdline");
 537                        n = read_to_buf(filename, buf);
 538                        if (n <= 0)
 539                                break;
 540                        if (flags & PSSCAN_ARGVN) {
 541                                sp->argv_len = n;
 542                                sp->argv0 = xmalloc(n + 1);
 543                                memcpy(sp->argv0, buf, n + 1);
 544                                /* sp->argv0[n] = '\0'; - buf has it */
 545                        } else {
 546                                sp->argv_len = 0;
 547                                sp->argv0 = xstrdup(buf);
 548                        }
 549                }
 550#endif
 551                break;
 552        } /* for (;;) */
 553
 554        return sp;
 555}
 556
 557void FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm)
 558{
 559        int sz;
 560        char filename[sizeof("/proc//cmdline") + sizeof(int)*3];
 561
 562        sprintf(filename, "/proc/%u/cmdline", pid);
 563        sz = open_read_close(filename, buf, col - 1);
 564        if (sz > 0) {
 565                buf[sz] = '\0';
 566                while (--sz >= 0 && buf[sz] == '\0')
 567                        continue;
 568                do {
 569                        if ((unsigned char)(buf[sz]) < ' ')
 570                                buf[sz] = ' ';
 571                } while (--sz >= 0);
 572        } else {
 573                snprintf(buf, col, "[%s]", comm);
 574        }
 575}
 576
 577/* from kernel:
 578        //             pid comm S ppid pgid sid tty_nr tty_pgrp flg
 579        sprintf(buffer,"%d (%s) %c %d  %d   %d  %d     %d       %lu %lu \
 580%lu %lu %lu %lu %lu %ld %ld %ld %ld %d 0 %llu %lu %ld %lu %lu %lu %lu %lu \
 581%lu %lu %lu %lu %lu %lu %lu %lu %d %d %lu %lu %llu\n",
 582                task->pid,
 583                tcomm,
 584                state,
 585                ppid,
 586                pgid,
 587                sid,
 588                tty_nr,
 589                tty_pgrp,
 590                task->flags,
 591                min_flt,
 592                cmin_flt,
 593                maj_flt,
 594                cmaj_flt,
 595                cputime_to_clock_t(utime),
 596                cputime_to_clock_t(stime),
 597                cputime_to_clock_t(cutime),
 598                cputime_to_clock_t(cstime),
 599                priority,
 600                nice,
 601                num_threads,
 602                // 0,
 603                start_time,
 604                vsize,
 605                mm ? get_mm_rss(mm) : 0,
 606                rsslim,
 607                mm ? mm->start_code : 0,
 608                mm ? mm->end_code : 0,
 609                mm ? mm->start_stack : 0,
 610                esp,
 611                eip,
 612the rest is some obsolete cruft
 613*/
 614