/* * New Interface to Process Table -- PROCTAB Stream (a la Directory streams) * Copyright (C) 1996 Charles L. Blake. * Copyright (C) 1998 Michael K. Johnson * Copyright 1998-2002 Albert Cahalan * May be distributed under the conditions of the * GNU Library General Public License; a copy is in COPYING */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "version.h" #include "readproc.h" #include "alloc.h" #include "pwcache.h" #include "devname.h" #include "procps.h" #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <stdarg.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <fcntl.h> #include <sys/dir.h> #include <sys/types.h> #include <sys/stat.h> #ifdef FLASK_LINUX #include <fs_secure.h> #endif /* initiate a process table scan */ PROCTAB *openproc(int flags, ...) { va_list ap; PROCTAB *PT = xmalloc(sizeof(PROCTAB)); if (flags & PROC_PID) PT->procfs = NULL; else if (!(PT->procfs = opendir("/proc"))) return NULL; PT->flags = flags; va_start(ap, flags); /* Init args list */ if (flags & PROC_PID) PT->pids = va_arg(ap, pid_t *); else if (flags & PROC_UID) { PT->uids = va_arg(ap, uid_t *); PT->nuid = va_arg(ap, int); } va_end(ap); /* Clean up args list */ return PT; } /* terminate a process table scan */ void closeproc(PROCTAB * PT) { if (PT) { if (PT->procfs) closedir(PT->procfs); free(PT); } } /* deallocate the space allocated by readproc if the passed rbuf was NULL */ void freeproc(proc_t * p) { if (!p) /* in case p is NULL */ return; /* ptrs are after strings to avoid copying memory when building them. */ /* so free is called on the address of the address of strvec[0]. */ if (p->cmdline) free((void *)*p->cmdline); if (p->environ) free((void *)*p->environ); free(p); } // 2.5.xx looks like: // // "State:\t%s\n" // "Tgid:\t%d\n" // "Pid:\t%d\n" // "PPid:\t%d\n" // "TracerPid:\t%d\n" static void status2proc(const char *S, proc_t * restrict P) { char *tmp; unsigned i; // The cmd is escaped, with \\ and \n for backslash and newline. // It certainly may contain "VmSize:" and similar crap. if (unlikely(strncmp("Name:\t", S, 6))) fprintf(stderr, "Internal error!\n"); S += 6; i = 0; while (i < sizeof P->cmd - 1) { int c = *S++; if (unlikely(c == '\n')) break; if (unlikely(c == '\0')) return; // should never happen if (unlikely(c == '\\')) { c = *S++; if (c == '\n') break; // should never happen if (!c) break; // should never happen if (c == 'n') c = '\n'; // else we assume it is '\\' } P->cmd[i++] = c; } P->cmd[i] = '\0'; tmp = strstr(S, "State:\t"); if (likely(tmp)) P->state = tmp[7]; else fprintf(stderr, "Internal error!\n"); tmp = strstr(S, "PPid:"); if (likely(tmp)) sscanf(tmp, "PPid:\t%d\n", &P->ppid); else fprintf(stderr, "Internal error!\n"); tmp = strstr(S, "Uid:"); if (likely(tmp)) sscanf(tmp, "Uid:\t%d\t%d\t%d\t%d", &P->ruid, &P->euid, &P->suid, &P->fuid); else fprintf(stderr, "Internal error!\n"); tmp = strstr(S, "Gid:"); if (likely(tmp)) sscanf(tmp, "Gid:\t%d\t%d\t%d\t%d", &P->rgid, &P->egid, &P->sgid, &P->fgid); else fprintf(stderr, "Internal error!\n"); tmp = strstr(S, "VmSize:"); if (likely(tmp)) sscanf(tmp, "VmSize: %lu kB\n" "VmLck: %lu kB\n" "VmRSS: %lu kB\n" "VmData: %lu kB\n" "VmStk: %lu kB\n" "VmExe: %lu kB\n" "VmLib: %lu kB\n", &P->vm_size, &P->vm_lock, &P->vm_rss, &P->vm_data, &P->vm_stack, &P->vm_exe, &P->vm_lib); else { /* looks like an annoying kernel thread */ P->vm_size = 0; P->vm_lock = 0; P->vm_rss = 0; P->vm_data = 0; P->vm_stack = 0; P->vm_exe = 0; P->vm_lib = 0; } tmp = strstr(S, "SigPnd:"); if (likely(tmp)) sscanf(tmp, #ifdef SIGNAL_STRING "SigPnd: %s SigBlk: %s SigIgn: %s %*s %s", P->signal, P->blocked, P->sigignore, P->sigcatch #else "SigPnd: %Lx SigBlk: %Lx SigIgn: %Lx %*s %Lx", &P->signal, &P->blocked, &P->sigignore, &P->sigcatch #endif ); else fprintf(stderr, "Internal error!\n"); } // Reads /proc/*/stat files, being careful not to trip over processes with // names like ":-) 1 2 3 4 5 6". static void stat2proc(const char *S, proc_t * restrict P) { unsigned num; char *tmp; /* fill in default values for older kernels */ P->exit_signal = SIGCHLD; P->processor = 0; P->rtprio = -1; P->sched = -1; S = strchr(S, '(') + 1; tmp = strrchr(S, ')'); num = tmp - S; if (unlikely(num >= sizeof P->cmd)) num = sizeof P->cmd - 1; memcpy(P->cmd, S, num); P->cmd[num] = '\0'; S = tmp + 2; // skip ") " num = sscanf(S, "%c " "%d %d %d %d %d " "%lu %lu %lu %lu %lu " "%Lu %Lu %Lu %Lu " /* utime stime cutime cstime */ "%ld %ld %ld %ld " "%Lu " /* start_time */ "%lu " "%ld " "%lu %lu %lu %lu %lu %lu " "%*s %*s %*s %*s " /* discard, no RT signals & Linux 2.1 used hex */ "%lu %lu %lu " "%d %d " "%lu %lu", &P->state, &P->ppid, &P->pgrp, &P->session, &P->tty, &P->tpgid, &P->flags, &P->min_flt, &P->cmin_flt, &P->maj_flt, &P->cmaj_flt, &P->utime, &P->stime, &P->cutime, &P->cstime, &P->priority, &P->nice, &P->timeout, &P->it_real_value, &P->start_time, &P->vsize, &P->rss, &P->rss_rlim, &P->start_code, &P->end_code, &P->start_stack, &P->kstk_esp, &P->kstk_eip, /* P->signal, P->blocked, P->sigignore, P->sigcatch, *//* can't use */ &P->wchan, &P->nswap, &P->cnswap, /* -- Linux 2.0.35 ends here -- */ &P->exit_signal, &P->processor, /* 2.2.1 ends with "exit_signal" */ /* -- Linux 2.2.8 to 2.5.17 end here -- */ &P->rtprio, &P->sched /* both added to 2.5.18 */ ); } static void statm2proc(const char *s, proc_t * restrict P) { int num; num = sscanf(s, "%ld %ld %ld %ld %ld %ld %ld", &P->size, &P->resident, &P->share, &P->trs, &P->lrs, &P->drs, &P->dt); /* fprintf(stderr, "statm2proc converted %d fields.\n",num); */ } static int file2str(const char *directory, const char *what, char *ret, int cap) { static char filename[80]; int fd, num_read; sprintf(filename, "%s/%s", directory, what); fd = open(filename, O_RDONLY, 0); if (unlikely(fd == -1)) return -1; num_read = read(fd, ret, cap - 1); if (unlikely(num_read <= 0)) num_read = -1; else ret[num_read] = 0; close(fd); return num_read; } static char **file2strvec(const char *directory, const char *what) { char buf[2048]; /* read buf bytes at a time */ char *p, *rbuf = 0, *endbuf, **q, **ret; int fd, tot = 0, n, c, end_of_file = 0; int align; sprintf(buf, "%s/%s", directory, what); fd = open(buf, O_RDONLY, 0); if (fd == -1) return NULL; /* read whole file into a memory buffer, allocating as we go */ while ((n = read(fd, buf, sizeof buf - 1)) > 0) { if (n < (int)(sizeof buf - 1)) end_of_file = 1; if (n == 0 && rbuf == 0) return NULL; /* process died between our open and read */ if (n < 0) { free(rbuf); return NULL; /* read error */ } if (end_of_file && buf[n - 1]) /* last read char not null */ buf[n++] = '\0'; /* so append null-terminator */ rbuf = xrealloc(rbuf, tot + n); /* allocate more memory */ memcpy(rbuf + tot, buf, n); /* copy buffer into it */ tot += n; /* increment total byte ctr */ if (end_of_file) break; } close(fd); if (n <= 0 && !end_of_file) { free(rbuf); return NULL; /* read error */ } endbuf = rbuf + tot; /* count space for pointers */ align = (sizeof(char *) - 1) - ((tot + sizeof(char *) - 1) & (sizeof(char *) - 1)); for (c = 0, p = rbuf; p < endbuf; p++) if (!*p) c += sizeof(char *); c += sizeof(char *); /* one extra for NULL term */ rbuf = xrealloc(rbuf, tot + c + align); /* make room for ptrs AT END */ endbuf = rbuf + tot; /* addr just past data buf */ q = ret = (char **)(endbuf + align); /* ==> free(*ret) to dealloc */ *q++ = p = rbuf; /* point ptrs to the strings */ endbuf--; /* do not traverse final NUL */ while (++p < endbuf) if (!*p) /* NUL char implies that */ *q++ = p + 1; /* next string -> next char */ *q = 0; /* null ptr list terminator */ return ret; } // warning: interface may change int read_cmdline(char *restrict const dst, unsigned sz, unsigned pid) { char name[32]; int fd; unsigned n = 0; dst[0] = '\0'; snprintf(name, sizeof name, "/proc/%u/cmdline", pid); fd = open(name, O_RDONLY); if (fd == -1) return 0; for (;;) { ssize_t r = read(fd, dst + n, sz - n); if (r == -1) { if (errno == EINTR) continue; break; } n += r; if (n == sz) break; // filled the buffer if (r == 0) break; // EOF } if (n) { int i; if (n == sz) n--; dst[n] = '\0'; i = n; while (i--) { int c = dst[i]; if (c < ' ' || c > '~') dst[i] = ' '; } } return n; } /* These are some nice GNU C expression subscope "inline" functions. * The can be used with arbitrary types and evaluate their arguments * exactly once. */ /* Test if item X of type T is present in the 0 terminated list L */ #define XinL(T, X, L) ( { \ T x = (X), *l = (L); \ while (*l && *l != x) l++; \ *l == x; \ } ) /* Test if item X of type T is present in the list L of length N */ #define XinLN(T, X, L, N) ( { \ T x = (X), *l = (L); \ int i = 0, n = (N); \ while (i < n && l[i] != x) i++; \ i < n && l[i] == x; \ } ) /* readproc: return a pointer to a proc_t filled with requested info about the * next process available matching the restriction set. If no more such * processes are available, return a null pointer (boolean false). Use the * passed buffer instead of allocating space if it is non-NULL. */ /* This is optimized so that if a PID list is given, only those files are * searched for in /proc. If other lists are given in addition to the PID list, * the same logic can follow through as for the no-PID list case. This is * fairly complex, but it does try to not to do any unnecessary work. */ proc_t *readproc(PROCTAB * PT, proc_t * p) { static struct direct *ent; /* dirent handle */ static struct stat sb; /* stat buffer */ static char path[32], sbuf[1024]; /* bufs for stat,statm */ #ifdef FLASK_LINUX security_id_t secsid; #endif pid_t pid; // saved until we have a proc_t allocated for sure /* loop until a proc matching restrictions is found or no more processes */ /* I know this could be a while loop -- this way is easier to indent ;-) */ next_proc: /* get next PID for consideration */ /*printf("PT->flags is 0x%08x\n", PT->flags);*/ #define flags (PT->flags) if (flags & PROC_PID) { pid = *(PT->pids)++; if (unlikely(!pid)) return NULL; snprintf(path, sizeof path, "/proc/%d", pid); } else { /* get next numeric /proc ent */ for (;;) { ent = readdir(PT->procfs); if (unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return NULL; if (likely (likely(*ent->d_name > '0') && likely(*ent->d_name <= '9'))) break; } pid = strtoul(ent->d_name, NULL, 10); memcpy(path, "/proc/", 6); strcpy(path + 6, ent->d_name); // trust /proc to not contain evil top-level entries // snprintf(path, sizeof path, "/proc/%s", ent->d_name); } #ifdef FLASK_LINUX if (stat_secure(path, &sb, &secsid) == -1) /* no such dirent (anymore) */ #else if (unlikely(stat(path, &sb) == -1)) /* no such dirent (anymore) */ #endif goto next_proc; if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid)) goto next_proc; /* not one of the requested uids */ if (!p) p = xcalloc(p, sizeof *p); /* passed buf or alloced mem */ p->euid = sb.st_uid; /* need a way to get real uid */ #ifdef FLASK_LINUX p->secsid = secsid; #endif p->pid = pid; if (flags & PROC_FILLSTAT) { /* read, parse /proc/#/stat */ if (unlikely(file2str(path, "stat", sbuf, sizeof sbuf) == -1)) goto next_proc; /* error reading /proc/#/stat */ stat2proc(sbuf, p); /* parse /proc/#/stat */ } if (unlikely(flags & PROC_FILLMEM)) { /* read, parse /proc/#/statm */ if (likely(file2str(path, "statm", sbuf, sizeof sbuf) != -1)) statm2proc(sbuf, p); /* ignore statm errors here */ } /* statm fields just zero */ if (flags & PROC_FILLSTATUS) { /* read, parse /proc/#/status */ if (likely(file2str(path, "status", sbuf, sizeof sbuf) != -1)) { status2proc(sbuf, p); } } /* some number->text resolving which is time consuming */ if (flags & PROC_FILLUSR) { strncpy(p->euser, user_from_uid(p->euid), sizeof p->euser); if (flags & PROC_FILLSTATUS) { strncpy(p->ruser, user_from_uid(p->ruid), sizeof p->ruser); strncpy(p->suser, user_from_uid(p->suid), sizeof p->suser); strncpy(p->fuser, user_from_uid(p->fuid), sizeof p->fuser); } } /* some number->text resolving which is time consuming */ if (flags & PROC_FILLGRP) { strncpy(p->egroup, group_from_gid(p->egid), sizeof p->egroup); if (flags & PROC_FILLSTATUS) { strncpy(p->rgroup, group_from_gid(p->rgid), sizeof p->rgroup); strncpy(p->sgroup, group_from_gid(p->sgid), sizeof p->sgroup); strncpy(p->fgroup, group_from_gid(p->fgid), sizeof p->fgroup); } } if ((flags & PROC_FILLCOM) || (flags & PROC_FILLARG)) /* read+parse /proc/#/cmdline */ p->cmdline = file2strvec(path, "cmdline"); else p->cmdline = NULL; if (unlikely(flags & PROC_FILLENV)) /* read+parse /proc/#/environ */ p->environ = file2strvec(path, "environ"); else p->environ = NULL; return p; } #undef flags /* ps_readproc: return a pointer to a proc_t filled with requested info about the * next process available matching the restriction set. If no more such * processes are available, return a null pointer (boolean false). Use the * passed buffer instead of allocating space if it is non-NULL. */ /* This is optimized so that if a PID list is given, only those files are * searched for in /proc. If other lists are given in addition to the PID list, * the same logic can follow through as for the no-PID list case. This is * fairly complex, but it does try to not to do any unnecessary work. */ proc_t *ps_readproc(PROCTAB * PT, proc_t * p) { static struct direct *ent; /* dirent handle */ static struct stat sb; /* stat buffer */ static char path[32], sbuf[1024]; /* bufs for stat,statm */ #ifdef FLASK_LINUX security_id_t secsid; #endif pid_t pid; // saved until we have a proc_t allocated for sure /* loop until a proc matching restrictions is found or no more processes */ /* I know this could be a while loop -- this way is easier to indent ;-) */ next_proc: /* get next PID for consideration */ /*printf("PT->flags is 0x%08x\n", PT->flags);*/ #define flags (PT->flags) for (;;) { ent = readdir(PT->procfs); if (unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return NULL; if (likely (likely(*ent->d_name > '0') && likely(*ent->d_name <= '9'))) break; } pid = strtoul(ent->d_name, NULL, 10); memcpy(path, "/proc/", 6); strcpy(path + 6, ent->d_name); // trust /proc to not contain evil top-level entries // snprintf(path, sizeof path, "/proc/%s", ent->d_name); #ifdef FLASK_LINUX if (stat_secure(path, &sb, &secsid) == -1) /* no such dirent (anymore) */ #else if (stat(path, &sb) == -1) /* no such dirent (anymore) */ #endif goto next_proc; if (!p) p = xcalloc(p, sizeof *p); /* passed buf or alloced mem */ p->euid = sb.st_uid; /* need a way to get real uid */ #ifdef FLASK_LINUX p->secsid = secsid; #endif p->pid = pid; if ((file2str(path, "stat", sbuf, sizeof sbuf)) == -1) goto next_proc; /* error reading /proc/#/stat */ stat2proc(sbuf, p); /* parse /proc/#/stat */ if (flags & PROC_FILLMEM) { /* read, parse /proc/#/statm */ if ((file2str(path, "statm", sbuf, sizeof sbuf)) != -1) statm2proc(sbuf, p); /* ignore statm errors here */ } /* statm fields just zero */ /* if (flags & PROC_FILLSTATUS) { */ /* read, parse /proc/#/status */ if ((file2str(path, "status", sbuf, sizeof sbuf)) != -1) { status2proc(sbuf, p); } /* }*/ /* some number->text resolving which is time consuming */ if (flags & PROC_FILLUSR) { strncpy(p->euser, user_from_uid(p->euid), sizeof p->euser); /* if (flags & PROC_FILLSTATUS) { */ strncpy(p->ruser, user_from_uid(p->ruid), sizeof p->ruser); strncpy(p->suser, user_from_uid(p->suid), sizeof p->suser); strncpy(p->fuser, user_from_uid(p->fuid), sizeof p->fuser); /* }*/ } /* some number->text resolving which is time consuming */ if (flags & PROC_FILLGRP) { strncpy(p->egroup, group_from_gid(p->egid), sizeof p->egroup); /* if (flags & PROC_FILLSTATUS) { */ strncpy(p->rgroup, group_from_gid(p->rgid), sizeof p->rgroup); strncpy(p->sgroup, group_from_gid(p->sgid), sizeof p->sgroup); strncpy(p->fgroup, group_from_gid(p->fgid), sizeof p->fgroup); /* }*/ } if ((flags & PROC_FILLCOM) || (flags & PROC_FILLARG)) /* read+parse /proc/#/cmdline */ p->cmdline = file2strvec(path, "cmdline"); else p->cmdline = NULL; if (flags & PROC_FILLENV) /* read+parse /proc/#/environ */ p->environ = file2strvec(path, "environ"); else p->environ = NULL; return p; } #undef flags void look_up_our_self(proc_t * p) { static char path[32], sbuf[1024]; /* bufs for stat,statm */ sprintf(path, "/proc/%d", getpid()); file2str(path, "stat", sbuf, sizeof sbuf); stat2proc(sbuf, p); /* parse /proc/#/stat */ file2str(path, "statm", sbuf, sizeof sbuf); statm2proc(sbuf, p); /* ignore statm errors here */ file2str(path, "status", sbuf, sizeof sbuf); status2proc(sbuf, p); } /* Convenient wrapper around openproc and readproc to slurp in the whole process * table subset satisfying the constraints of flags and the optional PID list. * Free allocated memory with freeproctab(). Access via tab[N]->member. The * pointer list is NULL terminated. */ proc_t **readproctab(int flags, ...) { PROCTAB *PT = NULL; proc_t **tab = NULL; int n = 0; va_list ap; va_start(ap, flags); /* pass through args to openproc */ if (flags & PROC_UID) { /* temporary variables to ensure that va_arg() instances * are called in the right order */ uid_t *u; int i; u = va_arg(ap, uid_t *); i = va_arg(ap, int); PT = openproc(flags, u, i); } else if (flags & PROC_PID) PT = openproc(flags, va_arg(ap, void *)); /* assume ptr sizes same */ else PT = openproc(flags); va_end(ap); do { /* read table: */ tab = xrealloc(tab, (n + 1) * sizeof(proc_t *)); /* realloc as we go, using */ tab[n] = readproc(PT, NULL); /* final null to terminate */ } while (tab[n++]); /* stop when NULL reached */ closeproc(PT); return tab; }