source: branches/3.2/mindi-busybox/libbb/procps.c @ 3232

Last change on this file since 3232 was 3232, checked in by bruno, 5 years ago
  • Update mindi-busybox to 1.21.1
File size: 16.7 KB
Line 
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 id_to_name_map_t {
16    uid_t id;
17    char name[USERNAME_MAX_SIZE];
18} id_to_name_map_t;
19
20typedef struct cache_t {
21    id_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, uid_t 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, uid_t id,
56            char* FAST_FUNC x2x_utoa(uid_t 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    /* Need to stop on both ' ' and '\n' */
131    while ((c = *str++) > ' ') {
132        c = ((c|0x20) - '0');
133        if (c > 9)
134            /* c = c + '0' - 'a' + 10: */
135            c = c - ('a' - '0' - 10);
136        n = n*16 + c;
137    }
138    *endptr = str; /* We skip trailing space! */
139    return n;
140}
141#endif
142
143#if ENABLE_FEATURE_FAST_TOP || ENABLE_FEATURE_TOPMEM || ENABLE_PMAP
144/* We cut a lot of corners here for speed */
145static unsigned long fast_strtoul_10(char **endptr)
146{
147    unsigned char c;
148    char *str = *endptr;
149    unsigned long n = *str - '0';
150
151    /* Need to stop on both ' ' and '\n' */
152    while ((c = *++str) > ' ')
153        n = n*10 + (c - '0');
154
155    *endptr = str + 1; /* We skip trailing space! */
156    return n;
157}
158
159# if ENABLE_FEATURE_FAST_TOP
160static long fast_strtol_10(char **endptr)
161{
162    if (**endptr != '-')
163        return fast_strtoul_10(endptr);
164
165    (*endptr)++;
166    return - (long)fast_strtoul_10(endptr);
167}
168# endif
169
170static char *skip_fields(char *str, int count)
171{
172    do {
173        while (*str++ != ' ')
174            continue;
175        /* we found a space char, str points after it */
176    } while (--count);
177    return str;
178}
179#endif
180
181#if ENABLE_FEATURE_TOPMEM || ENABLE_PMAP
182int FAST_FUNC procps_read_smaps(pid_t pid, struct smaprec *total,
183        void (*cb)(struct smaprec *, void *), void *data)
184{
185    FILE *file;
186    struct smaprec currec;
187    char filename[sizeof("/proc/%u/smaps") + sizeof(int)*3];
188    char buf[PROCPS_BUFSIZE];
189#if !ENABLE_PMAP
190    void (*cb)(struct smaprec *, void *) = NULL;
191    void *data = NULL;
192#endif
193
194    sprintf(filename, "/proc/%u/smaps", (int)pid);
195
196    file = fopen_for_read(filename);
197    if (!file)
198        return 1;
199
200    memset(&currec, 0, sizeof(currec));
201    while (fgets(buf, PROCPS_BUFSIZE, file)) {
202        // Each mapping datum has this form:
203        // f7d29000-f7d39000 rw-s FILEOFS M:m INODE FILENAME
204        // Size:                nnn kB
205        // Rss:                 nnn kB
206        // .....
207
208        char *tp = buf, *p;
209
210#define SCAN(S, X) \
211        if (strncmp(tp, S, sizeof(S)-1) == 0) {              \
212            tp = skip_whitespace(tp + sizeof(S)-1);      \
213            total->X += currec.X = fast_strtoul_10(&tp); \
214            continue;                                    \
215        }
216        if (cb) {
217            SCAN("Pss:"  , smap_pss     );
218            SCAN("Swap:" , smap_swap    );
219        }
220        SCAN("Private_Dirty:", private_dirty);
221        SCAN("Private_Clean:", private_clean);
222        SCAN("Shared_Dirty:" , shared_dirty );
223        SCAN("Shared_Clean:" , shared_clean );
224#undef SCAN
225        tp = strchr(buf, '-');
226        if (tp) {
227            // We reached next mapping - the line of this form:
228            // f7d29000-f7d39000 rw-s FILEOFS M:m INODE FILENAME
229
230            if (cb) {
231                /* If we have a previous record, there's nothing more
232                 * for it, call the callback and clear currec
233                 */
234                if (currec.smap_size)
235                    cb(&currec, data);
236                free(currec.smap_name);
237            }
238            memset(&currec, 0, sizeof(currec));
239
240            *tp = ' ';
241            tp = buf;
242            currec.smap_start = fast_strtoul_16(&tp);
243            currec.smap_size = (fast_strtoul_16(&tp) - currec.smap_start) >> 10;
244
245            strncpy(currec.smap_mode, tp, sizeof(currec.smap_mode)-1);
246
247            // skipping "rw-s FILEOFS M:m INODE "
248            tp = skip_whitespace(skip_fields(tp, 4));
249            // filter out /dev/something (something != zero)
250            if (strncmp(tp, "/dev/", 5) != 0 || strcmp(tp, "/dev/zero\n") == 0) {
251                if (currec.smap_mode[1] == 'w') {
252                    currec.mapped_rw = currec.smap_size;
253                    total->mapped_rw += currec.smap_size;
254                } else if (currec.smap_mode[1] == '-') {
255                    currec.mapped_ro = currec.smap_size;
256                    total->mapped_ro += currec.smap_size;
257                }
258            }
259
260            if (strcmp(tp, "[stack]\n") == 0)
261                total->stack += currec.smap_size;
262            if (cb) {
263                p = skip_non_whitespace(tp);
264                if (p == tp) {
265                    currec.smap_name = xstrdup("  [ anon ]");
266                } else {
267                    *p = '\0';
268                    currec.smap_name = xstrdup(tp);
269                }
270            }
271            total->smap_size += currec.smap_size;
272        }
273    }
274    fclose(file);
275
276    if (cb) {
277        if (currec.smap_size)
278            cb(&currec, data);
279        free(currec.smap_name);
280    }
281
282    return 0;
283}
284#endif
285
286void BUG_comm_size(void);
287procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags)
288{
289    if (!sp)
290        sp = alloc_procps_scan();
291
292    for (;;) {
293        struct dirent *entry;
294        char buf[PROCPS_BUFSIZE];
295        long tasknice;
296        unsigned pid;
297        int n;
298        char filename[sizeof("/proc/%u/task/%u/cmdline") + sizeof(int)*3 * 2];
299        char *filename_tail;
300
301#if ENABLE_FEATURE_SHOW_THREADS
302        if (sp->task_dir) {
303            entry = readdir(sp->task_dir);
304            if (entry)
305                goto got_entry;
306            closedir(sp->task_dir);
307            sp->task_dir = NULL;
308        }
309#endif
310        entry = readdir(sp->dir);
311        if (entry == NULL) {
312            free_procps_scan(sp);
313            return NULL;
314        }
315 IF_FEATURE_SHOW_THREADS(got_entry:)
316        pid = bb_strtou(entry->d_name, NULL, 10);
317        if (errno)
318            continue;
319#if ENABLE_FEATURE_SHOW_THREADS
320        if ((flags & PSSCAN_TASKS) && !sp->task_dir) {
321            /* We found another /proc/PID. Do not use it,
322             * there will be /proc/PID/task/PID (same PID!),
323             * so just go ahead and dive into /proc/PID/task. */
324            sprintf(filename, "/proc/%u/task", pid);
325            /* Note: if opendir fails, we just go to next /proc/XXX */
326            sp->task_dir = opendir(filename);
327            sp->main_thread_pid = pid;
328            continue;
329        }
330#endif
331
332        /* After this point we can:
333         * "break": stop parsing, return the data
334         * "continue": try next /proc/XXX
335         */
336
337        memset(&sp->vsz, 0, sizeof(*sp) - offsetof(procps_status_t, vsz));
338
339        sp->pid = pid;
340        if (!(flags & ~PSSCAN_PID))
341            break; /* we needed only pid, we got it */
342
343#if ENABLE_SELINUX
344        if (flags & PSSCAN_CONTEXT) {
345            if (getpidcon(sp->pid, &sp->context) < 0)
346                sp->context = NULL;
347        }
348#endif
349
350#if ENABLE_FEATURE_SHOW_THREADS
351        if (sp->task_dir)
352            filename_tail = filename + sprintf(filename, "/proc/%u/task/%u/", sp->main_thread_pid, pid);
353        else
354#endif
355            filename_tail = filename + sprintf(filename, "/proc/%u/", pid);
356
357        if (flags & PSSCAN_UIDGID) {
358            struct stat sb;
359            if (stat(filename, &sb))
360                continue; /* process probably exited */
361            /* Effective UID/GID, not real */
362            sp->uid = sb.st_uid;
363            sp->gid = sb.st_gid;
364        }
365
366        /* These are all retrieved from proc/NN/stat in one go: */
367        if (flags & (PSSCAN_PPID | PSSCAN_PGID | PSSCAN_SID
368            | PSSCAN_COMM | PSSCAN_STATE
369            | PSSCAN_VSZ | PSSCAN_RSS
370            | PSSCAN_STIME | PSSCAN_UTIME | PSSCAN_START_TIME
371            | PSSCAN_TTY | PSSCAN_NICE
372            | PSSCAN_CPU)
373        ) {
374            char *cp, *comm1;
375            int tty;
376#if !ENABLE_FEATURE_FAST_TOP
377            unsigned long vsz, rss;
378#endif
379            /* see proc(5) for some details on this */
380            strcpy(filename_tail, "stat");
381            n = read_to_buf(filename, buf);
382            if (n < 0)
383                continue; /* process probably exited */
384            cp = strrchr(buf, ')'); /* split into "PID (cmd" and "<rest>" */
385            /*if (!cp || cp[1] != ' ')
386                continue;*/
387            cp[0] = '\0';
388            if (sizeof(sp->comm) < 16)
389                BUG_comm_size();
390            comm1 = strchr(buf, '(');
391            /*if (comm1)*/
392                safe_strncpy(sp->comm, comm1 + 1, sizeof(sp->comm));
393
394#if !ENABLE_FEATURE_FAST_TOP
395            n = sscanf(cp+2,
396                "%c %u "               /* state, ppid */
397                "%u %u %d %*s "        /* pgid, sid, tty, tpgid */
398                "%*s %*s %*s %*s %*s " /* flags, min_flt, cmin_flt, maj_flt, cmaj_flt */
399                "%lu %lu "             /* utime, stime */
400                "%*s %*s %*s "         /* cutime, cstime, priority */
401                "%ld "                 /* nice */
402                "%*s %*s "             /* timeout, it_real_value */
403                "%lu "                 /* start_time */
404                "%lu "                 /* vsize */
405                "%lu "                 /* rss */
406# if ENABLE_FEATURE_TOP_SMP_PROCESS
407                "%*s %*s %*s %*s %*s %*s " /*rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */
408                "%*s %*s %*s %*s "         /*signal, blocked, sigignore, sigcatch */
409                "%*s %*s %*s %*s "         /*wchan, nswap, cnswap, exit_signal */
410                "%d"                       /*cpu last seen on*/
411# endif
412                ,
413                sp->state, &sp->ppid,
414                &sp->pgid, &sp->sid, &tty,
415                &sp->utime, &sp->stime,
416                &tasknice,
417                &sp->start_time,
418                &vsz,
419                &rss
420# if ENABLE_FEATURE_TOP_SMP_PROCESS
421                , &sp->last_seen_on_cpu
422# endif
423                );
424
425            if (n < 11)
426                continue; /* bogus data, get next /proc/XXX */
427# if ENABLE_FEATURE_TOP_SMP_PROCESS
428            if (n == 11)
429                sp->last_seen_on_cpu = 0;
430# endif
431
432            /* vsz is in bytes and we want kb */
433            sp->vsz = vsz >> 10;
434            /* vsz is in bytes but rss is in *PAGES*! Can you believe that? */
435            sp->rss = rss << sp->shift_pages_to_kb;
436            sp->tty_major = (tty >> 8) & 0xfff;
437            sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00);
438#else
439/* This costs ~100 bytes more but makes top faster by 20%
440 * If you run 10000 processes, this may be important for you */
441            sp->state[0] = cp[2];
442            cp += 4;
443            sp->ppid = fast_strtoul_10(&cp);
444            sp->pgid = fast_strtoul_10(&cp);
445            sp->sid = fast_strtoul_10(&cp);
446            tty = fast_strtoul_10(&cp);
447            sp->tty_major = (tty >> 8) & 0xfff;
448            sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00);
449            cp = skip_fields(cp, 6); /* tpgid, flags, min_flt, cmin_flt, maj_flt, cmaj_flt */
450            sp->utime = fast_strtoul_10(&cp);
451            sp->stime = fast_strtoul_10(&cp);
452            cp = skip_fields(cp, 3); /* cutime, cstime, priority */
453            tasknice = fast_strtol_10(&cp);
454            cp = skip_fields(cp, 2); /* timeout, it_real_value */
455            sp->start_time = fast_strtoul_10(&cp);
456            /* vsz is in bytes and we want kb */
457            sp->vsz = fast_strtoul_10(&cp) >> 10;
458            /* vsz is in bytes but rss is in *PAGES*! Can you believe that? */
459            sp->rss = fast_strtoul_10(&cp) << sp->shift_pages_to_kb;
460# if ENABLE_FEATURE_TOP_SMP_PROCESS
461            /* (6): rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */
462            /* (4): signal, blocked, sigignore, sigcatch */
463            /* (4): wchan, nswap, cnswap, exit_signal */
464            cp = skip_fields(cp, 14);
465//FIXME: is it safe to assume this field exists?
466            sp->last_seen_on_cpu = fast_strtoul_10(&cp);
467# endif
468#endif /* FEATURE_FAST_TOP */
469
470#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
471            sp->niceness = tasknice;
472#endif
473
474            if (sp->vsz == 0 && sp->state[0] != 'Z')
475                sp->state[1] = 'W';
476            else
477                sp->state[1] = ' ';
478            if (tasknice < 0)
479                sp->state[2] = '<';
480            else if (tasknice) /* > 0 */
481                sp->state[2] = 'N';
482            else
483                sp->state[2] = ' ';
484        }
485
486#if ENABLE_FEATURE_TOPMEM
487        if (flags & PSSCAN_SMAPS)
488            procps_read_smaps(pid, &sp->smaps, NULL, NULL);
489#endif /* TOPMEM */
490#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
491        if (flags & PSSCAN_RUIDGID) {
492            FILE *file;
493
494            strcpy(filename_tail, "status");
495            file = fopen_for_read(filename);
496            if (file) {
497                while (fgets(buf, sizeof(buf), file)) {
498                    char *tp;
499#define SCAN_TWO(str, name, statement) \
500    if (strncmp(buf, str, sizeof(str)-1) == 0) { \
501        tp = skip_whitespace(buf + sizeof(str)-1); \
502        sscanf(tp, "%u", &sp->name); \
503        statement; \
504    }
505                    SCAN_TWO("Uid:", ruid, continue);
506                    SCAN_TWO("Gid:", rgid, break);
507#undef SCAN_TWO
508                }
509                fclose(file);
510            }
511        }
512#endif /* PS_ADDITIONAL_COLUMNS */
513        if (flags & PSSCAN_EXE) {
514            strcpy(filename_tail, "exe");
515            free(sp->exe);
516            sp->exe = xmalloc_readlink(filename);
517        }
518        /* Note: if /proc/PID/cmdline is empty,
519         * code below "breaks". Therefore it must be
520         * the last code to parse /proc/PID/xxx data
521         * (we used to have /proc/PID/exe parsing after it
522         * and were getting stale sp->exe).
523         */
524#if 0 /* PSSCAN_CMD is not used */
525        if (flags & (PSSCAN_CMD|PSSCAN_ARGV0)) {
526            free(sp->argv0);
527            sp->argv0 = NULL;
528            free(sp->cmd);
529            sp->cmd = NULL;
530            strcpy(filename_tail, "cmdline");
531            /* TODO: to get rid of size limits, read into malloc buf,
532             * then realloc it down to real size. */
533            n = read_to_buf(filename, buf);
534            if (n <= 0)
535                break;
536            if (flags & PSSCAN_ARGV0)
537                sp->argv0 = xstrdup(buf);
538            if (flags & PSSCAN_CMD) {
539                do {
540                    n--;
541                    if ((unsigned char)(buf[n]) < ' ')
542                        buf[n] = ' ';
543                } while (n);
544                sp->cmd = xstrdup(buf);
545            }
546        }
547#else
548        if (flags & (PSSCAN_ARGV0|PSSCAN_ARGVN)) {
549            free(sp->argv0);
550            sp->argv0 = NULL;
551            strcpy(filename_tail, "cmdline");
552            n = read_to_buf(filename, buf);
553            if (n <= 0)
554                break;
555            if (flags & PSSCAN_ARGVN) {
556                sp->argv_len = n;
557                sp->argv0 = xmalloc(n + 1);
558                memcpy(sp->argv0, buf, n + 1);
559                /* sp->argv0[n] = '\0'; - buf has it */
560            } else {
561                sp->argv_len = 0;
562                sp->argv0 = xstrdup(buf);
563            }
564        }
565#endif
566        break;
567    } /* for (;;) */
568
569    return sp;
570}
571
572void FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm)
573{
574    int sz;
575    char filename[sizeof("/proc/%u/cmdline") + sizeof(int)*3];
576
577    sprintf(filename, "/proc/%u/cmdline", pid);
578    sz = open_read_close(filename, buf, col - 1);
579    if (sz > 0) {
580        const char *base;
581        int comm_len;
582
583        buf[sz] = '\0';
584        while (--sz >= 0 && buf[sz] == '\0')
585            continue;
586        /* Prevent basename("process foo/bar") = "bar" */
587        strchrnul(buf, ' ')[0] = '\0';
588        base = bb_basename(buf); /* before we replace argv0's NUL with space */
589        while (sz >= 0) {
590            if ((unsigned char)(buf[sz]) < ' ')
591                buf[sz] = ' ';
592            sz--;
593        }
594
595        /* If comm differs from argv0, prepend "{comm} ".
596         * It allows to see thread names set by prctl(PR_SET_NAME).
597         */
598        if (base[0] == '-') /* "-sh" (login shell)? */
599            base++;
600        comm_len = strlen(comm);
601        /* Why compare up to comm_len, not COMM_LEN-1?
602         * Well, some processes rewrite argv, and use _spaces_ there
603         * while rewriting. (KDE is observed to do it).
604         * I prefer to still treat argv0 "process foo bar"
605         * as 'equal' to comm "process".
606         */
607        if (strncmp(base, comm, comm_len) != 0) {
608            comm_len += 3;
609            if (col > comm_len)
610                memmove(buf + comm_len, buf, col - comm_len);
611            snprintf(buf, col, "{%s}", comm);
612            if (col <= comm_len)
613                return;
614            buf[comm_len - 1] = ' ';
615            buf[col - 1] = '\0';
616        }
617
618    } else {
619        snprintf(buf, col, "[%s]", comm);
620    }
621}
622
623/* from kernel:
624    //             pid comm S ppid pgid sid tty_nr tty_pgrp flg
625    sprintf(buffer,"%d (%s) %c %d  %d   %d  %d     %d       %lu %lu \
626%lu %lu %lu %lu %lu %ld %ld %ld %ld %d 0 %llu %lu %ld %lu %lu %lu %lu %lu \
627%lu %lu %lu %lu %lu %lu %lu %lu %d %d %lu %lu %llu\n",
628        task->pid,
629        tcomm,
630        state,
631        ppid,
632        pgid,
633        sid,
634        tty_nr,
635        tty_pgrp,
636        task->flags,
637        min_flt,
638        cmin_flt,
639        maj_flt,
640        cmaj_flt,
641        cputime_to_clock_t(utime),
642        cputime_to_clock_t(stime),
643        cputime_to_clock_t(cutime),
644        cputime_to_clock_t(cstime),
645        priority,
646        nice,
647        num_threads,
648        // 0,
649        start_time,
650        vsize,
651        mm ? get_mm_rss(mm) : 0,
652        rsslim,
653        mm ? mm->start_code : 0,
654        mm ? mm->end_code : 0,
655        mm ? mm->start_stack : 0,
656        esp,
657        eip,
658the rest is some obsolete cruft
659*/
Note: See TracBrowser for help on using the repository browser.