/*
* 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;
}