X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;ds=sidebyside;f=libbb%2Fprocps.c;h=5833a1f0d84fb0730ddc2c8fa2ef8d755b60115c;hb=8345b2bd5ea28bd8d07886e615b9c2d5ab35477d;hp=3e863b0deaaaa0a0e3613d3ad6b1337e5b19d9bf;hpb=d5bd137a247145afabfbc3a3c376ad8787381d8f;p=oweals%2Fbusybox.git diff --git a/libbb/procps.c b/libbb/procps.c index 3e863b0de..5833a1f0d 100644 --- a/libbb/procps.c +++ b/libbb/procps.c @@ -4,144 +4,612 @@ * * Copyright 1998 by Albert Cahalan; all rights reserved. * Copyright (C) 2002 by Vladimir Oleynik - * GNU Library General Public License Version 2, or any later version + * SELinux support: (c) 2007 by Yuichi Nakamura * + * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ -#include -#include -#include -#include -#include - #include "libbb.h" -extern procps_status_t * procps_scan(int save_user_arg0) + +typedef struct id_to_name_map_t { + uid_t id; + char name[USERNAME_MAX_SIZE]; +} id_to_name_map_t; + +typedef struct cache_t { + id_to_name_map_t *cache; + int size; +} cache_t; + +static cache_t username, groupname; + +static void clear_cache(cache_t *cp) { - static DIR *dir; - struct dirent *entry; - static procps_status_t ret_status; - char *name; - int n; - char status[32]; - char buf[1024]; - FILE *fp; - procps_status_t curstatus; - int pid; - long tasknice; - struct stat sb; + free(cp->cache); + cp->cache = NULL; + cp->size = 0; +} +void FAST_FUNC clear_username_cache(void) +{ + clear_cache(&username); + clear_cache(&groupname); +} + +#if 0 /* more generic, but we don't need that yet */ +/* Returns -N-1 if not found. */ +/* cp->cache[N] is allocated and must be filled in this case */ +static int get_cached(cache_t *cp, uid_t id) +{ + int i; + for (i = 0; i < cp->size; i++) + if (cp->cache[i].id == id) + return i; + i = cp->size++; + cp->cache = xrealloc_vector(cp->cache, 2, i); + cp->cache[i++].id = id; + return -i; +} +#endif - if (!dir) { - dir = opendir("/proc"); - if(!dir) - bb_error_msg_and_die("Can't open /proc"); +static char* get_cached(cache_t *cp, uid_t id, + char* FAST_FUNC x2x_utoa(uid_t id)) +{ + int i; + for (i = 0; i < cp->size; i++) + if (cp->cache[i].id == id) + return cp->cache[i].name; + i = cp->size++; + cp->cache = xrealloc_vector(cp->cache, 2, i); + cp->cache[i].id = id; + /* Never fails. Generates numeric string if name isn't found */ + safe_strncpy(cp->cache[i].name, x2x_utoa(id), sizeof(cp->cache[i].name)); + return cp->cache[i].name; +} +const char* FAST_FUNC get_cached_username(uid_t uid) +{ + return get_cached(&username, uid, uid2uname_utoa); +} +const char* FAST_FUNC get_cached_groupname(gid_t gid) +{ + return get_cached(&groupname, gid, gid2group_utoa); +} + + +#define PROCPS_BUFSIZE 1024 + +static int read_to_buf(const char *filename, void *buf) +{ + int fd; + /* open_read_close() would do two reads, checking for EOF. + * When you have 10000 /proc/$NUM/stat to read, it isn't desirable */ + ssize_t ret = -1; + fd = open(filename, O_RDONLY); + if (fd >= 0) { + ret = read(fd, buf, PROCPS_BUFSIZE-1); + close(fd); } - for(;;) { - if((entry = readdir(dir)) == NULL) { - closedir(dir); - dir = 0; - return 0; - } - name = entry->d_name; - if (!(*name >= '0' && *name <= '9')) - continue; + ((char *)buf)[ret > 0 ? ret : 0] = '\0'; + return ret; +} + +static procps_status_t* FAST_FUNC alloc_procps_scan(void) +{ + unsigned n = getpagesize(); + procps_status_t* sp = xzalloc(sizeof(procps_status_t)); + sp->dir = xopendir("/proc"); + while (1) { + n >>= 1; + if (!n) break; + sp->shift_pages_to_bytes++; + } + sp->shift_pages_to_kb = sp->shift_pages_to_bytes - 10; + return sp; +} + +void FAST_FUNC free_procps_scan(procps_status_t* sp) +{ + closedir(sp->dir); +#if ENABLE_FEATURE_SHOW_THREADS + if (sp->task_dir) + closedir(sp->task_dir); +#endif + free(sp->argv0); + free(sp->exe); + IF_SELINUX(free(sp->context);) + free(sp); +} - memset(&curstatus, 0, sizeof(procps_status_t)); - pid = atoi(name); - curstatus.pid = pid; +#if ENABLE_FEATURE_TOPMEM || ENABLE_PMAP +static unsigned long fast_strtoul_16(char **endptr) +{ + unsigned char c; + char *str = *endptr; + unsigned long n = 0; - sprintf(status, "/proc/%d", pid); - if(stat(status, &sb)) - continue; - bb_getpwuid(curstatus.user, sb.st_uid, sizeof(curstatus.user)); + while ((c = *str++) != ' ') { + c = ((c|0x20) - '0'); + if (c > 9) + // c = c + '0' - 'a' + 10: + c = c - ('a' - '0' - 10); + n = n*16 + c; + } + *endptr = str; /* We skip trailing space! */ + return n; +} +#endif + +#if ENABLE_FEATURE_FAST_TOP || ENABLE_FEATURE_TOPMEM || ENABLE_PMAP +/* We cut a lot of corners here for speed */ +static unsigned long fast_strtoul_10(char **endptr) +{ + char c; + char *str = *endptr; + unsigned long n = *str - '0'; + + while ((c = *++str) != ' ') + n = n*10 + (c - '0'); + + *endptr = str + 1; /* We skip trailing space! */ + return n; +} + +# if ENABLE_FEATURE_FAST_TOP +static long fast_strtol_10(char **endptr) +{ + if (**endptr != '-') + return fast_strtoul_10(endptr); - sprintf(status, "/proc/%d/stat", pid); + (*endptr)++; + return - (long)fast_strtoul_10(endptr); +} +# endif - if((fp = fopen(status, "r")) == NULL) +static char *skip_fields(char *str, int count) +{ + do { + while (*str++ != ' ') continue; - name = fgets(buf, sizeof(buf), fp); - fclose(fp); - if(name == NULL) + /* we found a space char, str points after it */ + } while (--count); + return str; +} +#endif + +#if ENABLE_FEATURE_TOPMEM || ENABLE_PMAP +int FAST_FUNC procps_read_smaps(pid_t pid, struct smaprec *total, + void (*cb)(struct smaprec *, void *), void *data) +{ + FILE *file; + struct smaprec currec; + char filename[sizeof("/proc/%u/smaps") + sizeof(int)*3]; + char buf[PROCPS_BUFSIZE]; +#if !ENABLE_PMAP + void (*cb)(struct smaprec *, void *) = NULL; + void *data = NULL; +#endif + + sprintf(filename, "/proc/%u/smaps", (int)pid); + + file = fopen_for_read(filename); + if (!file) + return 1; + + memset(&currec, 0, sizeof(currec)); + while (fgets(buf, PROCPS_BUFSIZE, file)) { + // Each mapping datum has this form: + // f7d29000-f7d39000 rw-s ADR M:m OFS FILE + // Size: nnn kB + // Rss: nnn kB + // ..... + + char *tp = buf, *p; + +#define SCAN(S, X) \ + if (strncmp(tp, S, sizeof(S)-1) == 0) { \ + tp = skip_whitespace(tp + sizeof(S)-1); \ + total->X += currec.X = fast_strtoul_10(&tp); \ + continue; \ + } + if (cb) { + SCAN("Pss:" , smap_pss ); + SCAN("Swap:" , smap_swap ); + } + SCAN("Private_Dirty:", private_dirty); + SCAN("Private_Clean:", private_clean); + SCAN("Shared_Dirty:" , shared_dirty ); + SCAN("Shared_Clean:" , shared_clean ); +#undef SCAN + tp = strchr(buf, '-'); + if (tp) { + // We reached next mapping - the line of this form: + // f7d29000-f7d39000 rw-s ADR M:m OFS FILE + + if (cb) { + /* If we have a previous record, there's nothing more + * for it, call the callback and clear currec + */ + if (currec.smap_size) + cb(&currec, data); + free(currec.smap_name); + } + memset(&currec, 0, sizeof(currec)); + + *tp = ' '; + tp = buf; + currec.smap_start = fast_strtoul_16(&tp); + currec.smap_size = (fast_strtoul_16(&tp) - currec.smap_start) >> 10; + + strncpy(currec.smap_mode, tp, sizeof(currec.smap_mode)-1); + + // skipping "rw-s ADR M:m OFS " + tp = skip_whitespace(skip_fields(tp, 4)); + // filter out /dev/something (something != zero) + if (strncmp(tp, "/dev/", 5) != 0 || strcmp(tp, "/dev/zero\n") == 0) { + if (currec.smap_mode[1] == 'w') { + currec.mapped_rw = currec.smap_size; + total->mapped_rw += currec.smap_size; + } else if (currec.smap_mode[1] == '-') { + currec.mapped_ro = currec.smap_size; + total->mapped_ro += currec.smap_size; + } + } + + if (strcmp(tp, "[stack]\n") == 0) + total->stack += currec.smap_size; + if (cb) { + p = skip_non_whitespace(tp); + if (p == tp) { + currec.smap_name = xstrdup(" [ anon ]"); + } else { + *p = '\0'; + currec.smap_name = xstrdup(tp); + } + } + total->smap_size += currec.smap_size; + } + } + fclose(file); + + if (cb) { + if (currec.smap_size) + cb(&currec, data); + free(currec.smap_name); + } + + return 0; +} +#endif + +void BUG_comm_size(void); +procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) +{ + struct dirent *entry; + char buf[PROCPS_BUFSIZE]; + char filename[sizeof("/proc//cmdline") + sizeof(int)*3]; + char *filename_tail; + long tasknice; + unsigned pid; + int n; + struct stat sb; + + if (!sp) + sp = alloc_procps_scan(); + + for (;;) { +#if ENABLE_FEATURE_SHOW_THREADS + if ((flags & PSSCAN_TASKS) && sp->task_dir) { + entry = readdir(sp->task_dir); + if (entry) + goto got_entry; + closedir(sp->task_dir); + sp->task_dir = NULL; + sp->main_thread_pid = 0; + } +#endif + entry = readdir(sp->dir); + if (entry == NULL) { + free_procps_scan(sp); + return NULL; + } + IF_FEATURE_SHOW_THREADS(got_entry:) + pid = bb_strtou(entry->d_name, NULL, 10); + if (errno) continue; - name = strrchr(buf, ')'); /* split into "PID (cmd" and "" */ - if(name == 0 || name[1] != ' ') +#if ENABLE_FEATURE_SHOW_THREADS + if ((flags & PSSCAN_TASKS) && !sp->task_dir) { + /* We found another /proc/PID. Do not use it, + * there will be /proc/PID/task/PID (same PID!), + * so just go ahead and dive into /proc/PID/task. */ + char task_dir[sizeof("/proc/%u/task") + sizeof(int)*3]; + sprintf(task_dir, "/proc/%u/task", pid); + sp->task_dir = xopendir(task_dir); + sp->main_thread_pid = pid; continue; - *name = 0; - sscanf(buf, "%*s (%15c", curstatus.short_cmd); - n = sscanf(name+2, - "%c %d " - "%*s %*s %*s %*s " /* pgrp, session, tty, tpgid */ - "%*s %*s %*s %*s %*s " /* flags, min_flt, cmin_flt, maj_flt, cmaj_flt */ -#ifdef CONFIG_FEATURE_TOP_CPU_USAGE_PERCENTAGE - "%lu %lu " -#else - "%*s %*s " + } #endif - "%*s %*s %*s " /* cutime, cstime, priority */ - "%ld " - "%*s %*s %*s " /* timeout, it_real_value, start_time */ - "%*s " /* vsize */ - "%ld", - curstatus.state, &curstatus.ppid, -#ifdef CONFIG_FEATURE_TOP_CPU_USAGE_PERCENTAGE - &curstatus.utime, &curstatus.stime, + + /* After this point we can: + * "break": stop parsing, return the data + * "continue": try next /proc/XXX + */ + + memset(&sp->vsz, 0, sizeof(*sp) - offsetof(procps_status_t, vsz)); + + sp->pid = pid; + if (!(flags & ~PSSCAN_PID)) + break; /* we needed only pid, we got it */ + +#if ENABLE_SELINUX + if (flags & PSSCAN_CONTEXT) { + if (getpidcon(sp->pid, &sp->context) < 0) + sp->context = NULL; + } #endif - &tasknice, - &curstatus.rss); -#ifdef CONFIG_FEATURE_TOP_CPU_USAGE_PERCENTAGE - if(n != 6) -#else - if(n != 4) + + filename_tail = filename + sprintf(filename, "/proc/%u/", pid); + + if (flags & PSSCAN_UIDGID) { + if (stat(filename, &sb)) + continue; /* process probably exited */ + /* Effective UID/GID, not real */ + sp->uid = sb.st_uid; + sp->gid = sb.st_gid; + } + + if (flags & PSSCAN_STAT) { + char *cp, *comm1; + int tty; +#if !ENABLE_FEATURE_FAST_TOP + unsigned long vsz, rss; #endif - continue; + /* see proc(5) for some details on this */ + strcpy(filename_tail, "stat"); + n = read_to_buf(filename, buf); + if (n < 0) + continue; /* process probably exited */ + cp = strrchr(buf, ')'); /* split into "PID (cmd" and "" */ + /*if (!cp || cp[1] != ' ') + continue;*/ + cp[0] = '\0'; + if (sizeof(sp->comm) < 16) + BUG_comm_size(); + comm1 = strchr(buf, '('); + /*if (comm1)*/ + safe_strncpy(sp->comm, comm1 + 1, sizeof(sp->comm)); - if (curstatus.rss == 0 && curstatus.state[0] != 'Z') - curstatus.state[1] = 'W'; - else - curstatus.state[1] = ' '; - if (tasknice < 0) - curstatus.state[2] = '<'; - else if (tasknice > 0) - curstatus.state[2] = 'N'; - else - curstatus.state[2] = ' '; - -#ifdef PAGE_SHIFT - curstatus.rss <<= (PAGE_SHIFT - 10); /* 2**10 = 1kb */ +#if !ENABLE_FEATURE_FAST_TOP + n = sscanf(cp+2, + "%c %u " /* state, ppid */ + "%u %u %d %*s " /* pgid, sid, tty, tpgid */ + "%*s %*s %*s %*s %*s " /* flags, min_flt, cmin_flt, maj_flt, cmaj_flt */ + "%lu %lu " /* utime, stime */ + "%*s %*s %*s " /* cutime, cstime, priority */ + "%ld " /* nice */ + "%*s %*s " /* timeout, it_real_value */ + "%lu " /* start_time */ + "%lu " /* vsize */ + "%lu " /* rss */ +# if ENABLE_FEATURE_TOP_SMP_PROCESS + "%*s %*s %*s %*s %*s %*s " /*rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */ + "%*s %*s %*s %*s " /*signal, blocked, sigignore, sigcatch */ + "%*s %*s %*s %*s " /*wchan, nswap, cnswap, exit_signal */ + "%d" /*cpu last seen on*/ +# endif + , + sp->state, &sp->ppid, + &sp->pgid, &sp->sid, &tty, + &sp->utime, &sp->stime, + &tasknice, + &sp->start_time, + &vsz, + &rss +# if ENABLE_FEATURE_TOP_SMP_PROCESS + , &sp->last_seen_on_cpu +# endif + ); + + if (n < 11) + continue; /* bogus data, get next /proc/XXX */ +# if ENABLE_FEATURE_TOP_SMP_PROCESS + if (n < 11+15) + sp->last_seen_on_cpu = 0; +# endif + + /* vsz is in bytes and we want kb */ + sp->vsz = vsz >> 10; + /* vsz is in bytes but rss is in *PAGES*! Can you believe that? */ + sp->rss = rss << sp->shift_pages_to_kb; + sp->tty_major = (tty >> 8) & 0xfff; + sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00); #else - curstatus.rss *= (getpagesize() >> 10); /* 2**10 = 1kb */ +/* This costs ~100 bytes more but makes top faster by 20% + * If you run 10000 processes, this may be important for you */ + sp->state[0] = cp[2]; + cp += 4; + sp->ppid = fast_strtoul_10(&cp); + sp->pgid = fast_strtoul_10(&cp); + sp->sid = fast_strtoul_10(&cp); + tty = fast_strtoul_10(&cp); + sp->tty_major = (tty >> 8) & 0xfff; + sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00); + cp = skip_fields(cp, 6); /* tpgid, flags, min_flt, cmin_flt, maj_flt, cmaj_flt */ + sp->utime = fast_strtoul_10(&cp); + sp->stime = fast_strtoul_10(&cp); + cp = skip_fields(cp, 3); /* cutime, cstime, priority */ + tasknice = fast_strtol_10(&cp); + cp = skip_fields(cp, 2); /* timeout, it_real_value */ + sp->start_time = fast_strtoul_10(&cp); + /* vsz is in bytes and we want kb */ + sp->vsz = fast_strtoul_10(&cp) >> 10; + /* vsz is in bytes but rss is in *PAGES*! Can you believe that? */ + sp->rss = fast_strtoul_10(&cp) << sp->shift_pages_to_kb; +# if ENABLE_FEATURE_TOP_SMP_PROCESS + /* (6): rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */ + /* (4): signal, blocked, sigignore, sigcatch */ + /* (4): wchan, nswap, cnswap, exit_signal */ + cp = skip_fields(cp, 14); +//FIXME: is it safe to assume this field exists? + sp->last_seen_on_cpu = fast_strtoul_10(&cp); +# endif +#endif /* FEATURE_FAST_TOP */ + +#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS + sp->niceness = tasknice; #endif - if(save_user_arg0) { - sprintf(status, "/proc/%d/cmdline", pid); - if((fp = fopen(status, "r")) == NULL) - continue; - if((n=fread(buf, 1, sizeof(buf)-1, fp)) > 0) { - if(buf[n-1]=='\n') - buf[--n] = 0; - name = buf; - while(n) { - if(((unsigned char)*name) < ' ') - *name = ' '; - name++; - n--; + if (sp->vsz == 0 && sp->state[0] != 'Z') + sp->state[1] = 'W'; + else + sp->state[1] = ' '; + if (tasknice < 0) + sp->state[2] = '<'; + else if (tasknice) /* > 0 */ + sp->state[2] = 'N'; + else + sp->state[2] = ' '; + } + +#if ENABLE_FEATURE_TOPMEM + if (flags & PSSCAN_SMAPS) + procps_read_smaps(pid, &sp->smaps, NULL, NULL); +#endif /* TOPMEM */ +#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS + if (flags & PSSCAN_RUIDGID) { + FILE *file; + + strcpy(filename_tail, "status"); + file = fopen_for_read(filename); + if (file) { + while (fgets(buf, sizeof(buf), file)) { + char *tp; +#define SCAN_TWO(str, name, statement) \ + if (strncmp(buf, str, sizeof(str)-1) == 0) { \ + tp = skip_whitespace(buf + sizeof(str)-1); \ + sscanf(tp, "%u", &sp->name); \ + statement; \ + } + SCAN_TWO("Uid:", ruid, continue); + SCAN_TWO("Gid:", rgid, break); +#undef SCAN_TWO } - *name = 0; - if(buf[0]) - curstatus.cmd = strdup(buf); - /* if NULL it work true also */ + fclose(file); } - fclose(fp); } - return memcpy(&ret_status, &curstatus, sizeof(procps_status_t)); +#endif /* PS_ADDITIONAL_COLUMNS */ + if (flags & PSSCAN_EXE) { + strcpy(filename_tail, "exe"); + free(sp->exe); + sp->exe = xmalloc_readlink(filename); + } + /* Note: if /proc/PID/cmdline is empty, + * code below "breaks". Therefore it must be + * the last code to parse /proc/PID/xxx data + * (we used to have /proc/PID/exe parsing after it + * and were getting stale sp->exe). + */ +#if 0 /* PSSCAN_CMD is not used */ + if (flags & (PSSCAN_CMD|PSSCAN_ARGV0)) { + free(sp->argv0); + sp->argv0 = NULL; + free(sp->cmd); + sp->cmd = NULL; + strcpy(filename_tail, "cmdline"); + /* TODO: to get rid of size limits, read into malloc buf, + * then realloc it down to real size. */ + n = read_to_buf(filename, buf); + if (n <= 0) + break; + if (flags & PSSCAN_ARGV0) + sp->argv0 = xstrdup(buf); + if (flags & PSSCAN_CMD) { + do { + n--; + if ((unsigned char)(buf[n]) < ' ') + buf[n] = ' '; + } while (n); + sp->cmd = xstrdup(buf); + } + } +#else + if (flags & (PSSCAN_ARGV0|PSSCAN_ARGVN)) { + free(sp->argv0); + sp->argv0 = NULL; + strcpy(filename_tail, "cmdline"); + n = read_to_buf(filename, buf); + if (n <= 0) + break; + if (flags & PSSCAN_ARGVN) { + sp->argv_len = n; + sp->argv0 = xmalloc(n + 1); + memcpy(sp->argv0, buf, n + 1); + /* sp->argv0[n] = '\0'; - buf has it */ + } else { + sp->argv_len = 0; + sp->argv0 = xstrdup(buf); + } + } +#endif + break; + } /* for (;;) */ + + return sp; +} + +void FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm) +{ + int sz; + char filename[sizeof("/proc//cmdline") + sizeof(int)*3]; + + sprintf(filename, "/proc/%u/cmdline", pid); + sz = open_read_close(filename, buf, col - 1); + if (sz > 0) { + buf[sz] = '\0'; + while (--sz >= 0 && buf[sz] == '\0') + continue; + do { + if ((unsigned char)(buf[sz]) < ' ') + buf[sz] = ' '; + } while (--sz >= 0); + } else { + snprintf(buf, col, "[%s]", comm); } } -/* END CODE */ -/* -Local Variables: -c-file-style: "linux" -c-basic-offset: 4 -tab-width: 4 -End: +/* from kernel: + // pid comm S ppid pgid sid tty_nr tty_pgrp flg + sprintf(buffer,"%d (%s) %c %d %d %d %d %d %lu %lu \ +%lu %lu %lu %lu %lu %ld %ld %ld %ld %d 0 %llu %lu %ld %lu %lu %lu %lu %lu \ +%lu %lu %lu %lu %lu %lu %lu %lu %d %d %lu %lu %llu\n", + task->pid, + tcomm, + state, + ppid, + pgid, + sid, + tty_nr, + tty_pgrp, + task->flags, + min_flt, + cmin_flt, + maj_flt, + cmaj_flt, + cputime_to_clock_t(utime), + cputime_to_clock_t(stime), + cputime_to_clock_t(cutime), + cputime_to_clock_t(cstime), + priority, + nice, + num_threads, + // 0, + start_time, + vsize, + mm ? get_mm_rss(mm) : 0, + rsslim, + mm ? mm->start_code : 0, + mm ? mm->end_code : 0, + mm ? mm->start_stack : 0, + esp, + eip, +the rest is some obsolete cruft */