1000 lines
30 KiB
C
1000 lines
30 KiB
C
/*
|
|
* 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-2003 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>
|
|
|
|
// sometimes it's easier to do this manually, w/o gcc helping
|
|
#ifdef PROF
|
|
extern void __cyg_profile_func_enter(void*,void*);
|
|
#define ENTER(x) __cyg_profile_func_enter((void*)x,(void*)x)
|
|
#define LEAVE(x) __cyg_profile_func_exit((void*)x,(void*)x)
|
|
#else
|
|
#define ENTER(x)
|
|
#define LEAVE(x)
|
|
#endif
|
|
|
|
static int task_dir_missing;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
typedef struct status_table_struct {
|
|
unsigned char name[7]; // /proc/*/status field name
|
|
unsigned char len; // name length
|
|
#ifdef LABEL_OFFSET
|
|
long offset; // jump address offset
|
|
#else
|
|
void *addr;
|
|
#endif
|
|
} status_table_struct;
|
|
|
|
#ifdef LABEL_OFFSET
|
|
#define F(x) {#x, sizeof(#x)-1, (long)(&&case_##x-&&base)},
|
|
#else
|
|
#define F(x) {#x, sizeof(#x)-1, &&case_##x},
|
|
#endif
|
|
#define NUL {"", 0, 0},
|
|
|
|
// Derived from:
|
|
// gperf -7 --language=ANSI-C --key-positions=1,3,4 -C -n -c sml.gperf
|
|
//
|
|
// Suggested method:
|
|
// Grep this file for "case_", then strip those down to the name.
|
|
// (leave the colon and newline) So "Pid:\n" and "Threads:\n"
|
|
// would be lines in the file. (no quote, no escape, etc.)
|
|
//
|
|
// Watch out for name size in the status_table_struct (grrr, expanding)
|
|
// and the number of entries (we mask with 63 for now). The table
|
|
// must be padded out to 64 entries, maybe 128 in the future.
|
|
|
|
static void status2proc(char *S, proc_t *restrict P, int is_proc){
|
|
char ShdPnd[16] = "";
|
|
long Threads = 0;
|
|
long Tgid = 0;
|
|
long Pid = 0;
|
|
|
|
static const unsigned char asso[] =
|
|
{
|
|
61, 61, 61, 61, 61, 61, 61, 61, 61, 61,
|
|
61, 61, 61, 61, 61, 61, 61, 61, 61, 61,
|
|
61, 61, 61, 61, 61, 61, 61, 61, 61, 61,
|
|
61, 61, 61, 61, 61, 61, 61, 61, 61, 61,
|
|
61, 61, 61, 61, 61, 61, 61, 61, 61, 61,
|
|
61, 61, 61, 61, 61, 61, 61, 61, 15, 61,
|
|
61, 61, 61, 61, 61, 61, 30, 3, 5, 5,
|
|
61, 5, 61, 8, 61, 61, 3, 61, 10, 61,
|
|
6, 61, 13, 0, 30, 25, 0, 61, 61, 61,
|
|
61, 61, 61, 61, 61, 61, 61, 3, 61, 13,
|
|
0, 0, 61, 30, 61, 25, 61, 61, 61, 0,
|
|
61, 61, 61, 61, 5, 61, 0, 61, 61, 61,
|
|
0, 61, 61, 61, 61, 61, 61, 61
|
|
};
|
|
|
|
static const status_table_struct table[] = {
|
|
F(VmStk)
|
|
NUL NUL
|
|
F(State)
|
|
NUL
|
|
F(VmExe)
|
|
F(ShdPnd)
|
|
NUL
|
|
F(VmData)
|
|
NUL
|
|
F(Name)
|
|
NUL NUL
|
|
F(VmRSS)
|
|
NUL NUL
|
|
F(VmLck)
|
|
NUL NUL NUL
|
|
F(Gid)
|
|
F(Pid)
|
|
NUL NUL NUL
|
|
F(VmSize)
|
|
NUL NUL
|
|
F(VmLib)
|
|
NUL NUL
|
|
F(PPid)
|
|
NUL
|
|
F(SigCgt)
|
|
NUL
|
|
F(Threads)
|
|
F(SigPnd)
|
|
NUL
|
|
F(SigIgn)
|
|
NUL
|
|
F(Uid)
|
|
NUL NUL NUL NUL NUL NUL NUL NUL NUL
|
|
NUL NUL NUL NUL NUL
|
|
F(Tgid)
|
|
NUL NUL NUL NUL
|
|
F(SigBlk)
|
|
NUL NUL NUL
|
|
};
|
|
|
|
#undef F
|
|
#undef NUL
|
|
|
|
ENTER(0x220);
|
|
|
|
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;
|
|
P->nlwp = 0;
|
|
|
|
goto base;
|
|
|
|
for(;;){
|
|
char *colon;
|
|
status_table_struct entry;
|
|
|
|
// advance to next line
|
|
S = strchr(S, '\n');
|
|
if(unlikely(!S)) break; // if no newline
|
|
S++;
|
|
|
|
// examine a field name (hash and compare)
|
|
base:
|
|
if(unlikely(!*S)) break;
|
|
entry = table[63 & (asso[S[3]] + asso[S[2]] + asso[S[0]])];
|
|
colon = strchr(S, ':');
|
|
if(unlikely(!colon)) break;
|
|
if(unlikely(colon[1]!='\t')) break;
|
|
if(unlikely(colon-S != entry.len)) continue;
|
|
if(unlikely(memcmp(entry.name,S,colon-S))) continue;
|
|
|
|
S = colon+2; // past the '\t'
|
|
|
|
#ifdef LABEL_OFFSET
|
|
goto *(&&base + entry.offset);
|
|
#else
|
|
goto *entry.addr;
|
|
#endif
|
|
|
|
case_Name:{
|
|
unsigned u = 0;
|
|
while(u < sizeof P->cmd - 1u){
|
|
int c = *S++;
|
|
if(unlikely(c=='\n')) break;
|
|
if(unlikely(c=='\0')) break; // 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[u++] = c;
|
|
}
|
|
P->cmd[u] = '\0';
|
|
S--; // put back the '\n' or '\0'
|
|
continue;
|
|
}
|
|
case_ShdPnd:
|
|
memcpy(ShdPnd, S, 16);
|
|
// we know it to be 16 char, so no '\0' needed
|
|
continue;
|
|
case_SigBlk:
|
|
memcpy(P->blocked, S, 16);
|
|
P->blocked[16] = '\0';
|
|
continue;
|
|
case_SigCgt:
|
|
memcpy(P->sigcatch, S, 16);
|
|
P->sigcatch[16] = '\0';
|
|
continue;
|
|
case_SigIgn:
|
|
memcpy(P->sigignore, S, 16);
|
|
P->sigignore[16] = '\0';
|
|
continue;
|
|
case_SigPnd:
|
|
memcpy(P->signal, S, 16);
|
|
P->signal[16] = '\0';
|
|
continue;
|
|
case_State:
|
|
P->state = *S;
|
|
continue;
|
|
case_Tgid:
|
|
Tgid = strtol(S,&S,10);
|
|
continue;
|
|
case_Pid:
|
|
Pid = strtol(S,&S,10);
|
|
continue;
|
|
case_PPid:
|
|
P->ppid = strtol(S,&S,10);
|
|
continue;
|
|
case_Threads:
|
|
Threads = strtol(S,&S,10);
|
|
continue;
|
|
case_Uid:
|
|
P->ruid = strtol(S,&S,10);
|
|
P->euid = strtol(S,&S,10);
|
|
P->suid = strtol(S,&S,10);
|
|
P->fuid = strtol(S,&S,10);
|
|
continue;
|
|
case_Gid:
|
|
P->rgid = strtol(S,&S,10);
|
|
P->egid = strtol(S,&S,10);
|
|
P->sgid = strtol(S,&S,10);
|
|
P->fgid = strtol(S,&S,10);
|
|
continue;
|
|
case_VmData:
|
|
P->vm_data = strtol(S,&S,10);
|
|
continue;
|
|
case_VmExe:
|
|
P->vm_exe = strtol(S,&S,10);
|
|
continue;
|
|
case_VmLck:
|
|
P->vm_lock = strtol(S,&S,10);
|
|
continue;
|
|
case_VmLib:
|
|
P->vm_lib = strtol(S,&S,10);
|
|
continue;
|
|
case_VmRSS:
|
|
P->vm_rss = strtol(S,&S,10);
|
|
continue;
|
|
case_VmSize:
|
|
P->vm_size = strtol(S,&S,10);
|
|
continue;
|
|
case_VmStk:
|
|
P->vm_stack = strtol(S,&S,10);
|
|
continue;
|
|
}
|
|
|
|
// recent kernels supply per-tgid pending signals
|
|
if(is_proc && *ShdPnd){
|
|
memcpy(P->signal, ShdPnd, 16);
|
|
P->signal[16] = '\0';
|
|
}
|
|
|
|
// Linux 2.4.13-pre1 to max 2.4.xx have a useless "Tgid"
|
|
// that is not initialized for built-in kernel tasks.
|
|
// Only 2.6.0 and above have "Threads" (nlwp) info.
|
|
|
|
if(Threads){
|
|
P->nlwp = Threads;
|
|
P->tgid = Tgid; // the POSIX PID value
|
|
P->tid = Pid; // the thread ID
|
|
}else{
|
|
P->nlwp = 1;
|
|
P->tgid = Pid;
|
|
P->tid = Pid;
|
|
}
|
|
|
|
LEAVE(0x220);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
// 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;
|
|
|
|
ENTER(0x160);
|
|
|
|
/* fill in default values for older kernels */
|
|
P->processor = 0;
|
|
P->rtprio = -1;
|
|
P->sched = -1;
|
|
P->nlwp = 0;
|
|
|
|
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 "
|
|
"%d "
|
|
"%ld "
|
|
"%Lu " /* start_time */
|
|
"%lu "
|
|
"%ld "
|
|
"%lu %"KLF"u %"KLF"u %"KLF"u %"KLF"u %"KLF"u "
|
|
"%*s %*s %*s %*s " /* discard, no RT signals & Linux 2.1 used hex */
|
|
"%"KLF"u %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->nlwp,
|
|
&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 */
|
|
);
|
|
|
|
if(!P->nlwp){
|
|
P->nlwp = 1;
|
|
}
|
|
|
|
LEAVE(0x160);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
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);
|
|
close(fd);
|
|
if(unlikely(num_read<=0)) return -1;
|
|
ret[num_read] = '\0';
|
|
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) {
|
|
if (rbuf)
|
|
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) {
|
|
if (rbuf) 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
|
|
}
|
|
close(fd);
|
|
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; \
|
|
} )
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// This reads process info from /proc in the traditional way, for one process.
|
|
// The pid (tgid? tid?) is already in p, and a path to it in path, with some
|
|
// room to spare.
|
|
static proc_t* simple_readproc(PROCTAB *restrict const PT, proc_t *restrict const p) {
|
|
static struct stat sb; // stat() buffer
|
|
static char sbuf[1024]; // buffer for stat,statm
|
|
char *restrict const path = PT->path;
|
|
unsigned flags = PT->flags;
|
|
|
|
if (unlikely(stat(path, &sb) == -1)) /* no such dirent (anymore) */
|
|
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 */
|
|
|
|
p->euid = sb.st_uid; /* need a way to get real uid */
|
|
p->egid = sb.st_gid; /* need a way to get real gid */
|
|
|
|
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, 1);
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
next_proc:
|
|
return NULL;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// This reads /proc/*/task/* data, for one task.
|
|
// p is the POSIX process (task group summary) (not needed by THIS implementation)
|
|
// t is the POSIX thread (task group member, generally not the leader)
|
|
// path is a path to the task, with some room to spare.
|
|
static proc_t* simple_readtask(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict const t, char *restrict const path) {
|
|
static struct stat sb; // stat() buffer
|
|
static char sbuf[1024]; // buffer for stat,statm
|
|
unsigned flags = PT->flags;
|
|
|
|
//printf("hhh\n");
|
|
if (unlikely(stat(path, &sb) == -1)) /* no such dirent (anymore) */
|
|
goto next_task;
|
|
|
|
// if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid))
|
|
// goto next_task; /* not one of the requested uids */
|
|
|
|
t->euid = sb.st_uid; /* need a way to get real uid */
|
|
t->egid = sb.st_gid; /* need a way to get real gid */
|
|
|
|
//printf("iii\n");
|
|
if (flags & PROC_FILLSTAT) { /* read, parse /proc/#/stat */
|
|
if (unlikely( file2str(path, "stat", sbuf, sizeof sbuf) == -1 ))
|
|
goto next_task; /* error reading /proc/#/stat */
|
|
stat2proc(sbuf, t); /* parse /proc/#/stat */
|
|
}
|
|
|
|
if (unlikely(flags & PROC_FILLMEM)) { /* read, parse /proc/#/statm */
|
|
#if 0
|
|
if (likely( file2str(path, "statm", sbuf, sizeof sbuf) != -1 ))
|
|
statm2proc(sbuf, t); /* ignore statm errors here */
|
|
#else
|
|
t->size = p->size;
|
|
t->resident = p->resident;
|
|
t->share = p->share;
|
|
t->trs = p->trs;
|
|
t->lrs = p->lrs;
|
|
t->drs = p->drs;
|
|
t->dt = p->dt;
|
|
#endif
|
|
} /* statm fields just zero */
|
|
|
|
if (flags & PROC_FILLSTATUS) { /* read, parse /proc/#/status */
|
|
if (likely( file2str(path, "status", sbuf, sizeof sbuf) != -1 )){
|
|
status2proc(sbuf, t, 0);
|
|
}
|
|
}
|
|
|
|
/* some number->text resolving which is time consuming */
|
|
if (flags & PROC_FILLUSR){
|
|
strncpy(t->euser, user_from_uid(t->euid), sizeof t->euser);
|
|
if(flags & PROC_FILLSTATUS) {
|
|
strncpy(t->ruser, user_from_uid(t->ruid), sizeof t->ruser);
|
|
strncpy(t->suser, user_from_uid(t->suid), sizeof t->suser);
|
|
strncpy(t->fuser, user_from_uid(t->fuid), sizeof t->fuser);
|
|
}
|
|
}
|
|
|
|
/* some number->text resolving which is time consuming */
|
|
if (flags & PROC_FILLGRP){
|
|
strncpy(t->egroup, group_from_gid(t->egid), sizeof t->egroup);
|
|
if(flags & PROC_FILLSTATUS) {
|
|
strncpy(t->rgroup, group_from_gid(t->rgid), sizeof t->rgroup);
|
|
strncpy(t->sgroup, group_from_gid(t->sgid), sizeof t->sgroup);
|
|
strncpy(t->fgroup, group_from_gid(t->fgid), sizeof t->fgroup);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
if ((flags & PROC_FILLCOM) || (flags & PROC_FILLARG)) /* read+parse /proc/#/cmdline */
|
|
t->cmdline = file2strvec(path, "cmdline");
|
|
else
|
|
t->cmdline = NULL;
|
|
|
|
if (unlikely(flags & PROC_FILLENV)) /* read+parse /proc/#/environ */
|
|
t->environ = file2strvec(path, "environ");
|
|
else
|
|
t->environ = NULL;
|
|
#else
|
|
t->cmdline = p->cmdline; // better not free these until done with all threads!
|
|
t->environ = p->environ;
|
|
#endif
|
|
|
|
t->ppid = p->ppid; // ought to put the per-task ppid somewhere
|
|
|
|
return t;
|
|
next_task:
|
|
return NULL;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// This finds processes in /proc in the traditional way.
|
|
// Return non-zero on success.
|
|
static int simple_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) {
|
|
static struct direct *ent; /* dirent handle */
|
|
char *restrict const path = PT->path;
|
|
for (;;) {
|
|
ent = readdir(PT->procfs);
|
|
if(unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return 0;
|
|
if(likely( likely(*ent->d_name > '0') && likely(*ent->d_name <= '9') )) break;
|
|
}
|
|
p->tgid = strtoul(ent->d_name, NULL, 10);
|
|
p->tid = p->tgid;
|
|
memcpy(path, "/proc/", 6);
|
|
strcpy(path+6, ent->d_name); // trust /proc to not contain evil top-level entries
|
|
return 1;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// This finds tasks in /proc/*/task/ in the traditional way.
|
|
// Return non-zero on success.
|
|
static int simple_nexttid(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict const t, char *restrict const path) {
|
|
static struct direct *ent; /* dirent handle */
|
|
if(PT->taskdir_user != p->tgid){
|
|
if(PT->taskdir){
|
|
closedir(PT->taskdir);
|
|
}
|
|
// use "path" as some tmp space
|
|
snprintf(path, PROCPATHLEN, "/proc/%d/task", p->tgid);
|
|
PT->taskdir = opendir(path);
|
|
if(!PT->taskdir) return 0;
|
|
PT->taskdir_user = p->tgid;
|
|
}
|
|
for (;;) {
|
|
ent = readdir(PT->taskdir);
|
|
if(unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return 0;
|
|
if(likely( likely(*ent->d_name > '0') && likely(*ent->d_name <= '9') )) break;
|
|
}
|
|
t->tid = strtoul(ent->d_name, NULL, 10);
|
|
t->tgid = p->tgid;
|
|
t->ppid = p->ppid; // cover for kernel behavior? we want both actually...?
|
|
snprintf(path, PROCPATHLEN, "/proc/%d/task/%s", p->tgid, ent->d_name);
|
|
return 1;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// This "finds" processes in a list that was given to openproc().
|
|
// Return non-zero on success. (tgid was handy)
|
|
static int listed_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) {
|
|
char *restrict const path = PT->path;
|
|
pid_t tgid = *(PT->pids)++;
|
|
if(likely( tgid )){
|
|
snprintf(path, PROCPATHLEN, "/proc/%d", tgid);
|
|
p->tgid = tgid;
|
|
p->tid = tgid; // they match for leaders
|
|
}
|
|
return tgid;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
/* 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 *restrict const PT, proc_t *restrict p) {
|
|
proc_t *ret;
|
|
proc_t *saved_p;
|
|
|
|
if (PT->did_fake) PT->did_fake=0;
|
|
// if (PT->taskdir) {
|
|
// closedir(PT->taskdir);
|
|
// PT->taskdir = NULL;
|
|
// PT->taskdir_user = -1;
|
|
// }
|
|
|
|
saved_p = p;
|
|
if(!p) p = xcalloc(p, sizeof *p); /* passed buf or alloced mem */
|
|
|
|
for(;;){
|
|
// fills in the path, plus p->tid and p->tgid
|
|
if (unlikely(! PT->finder(PT,p) )) goto out;
|
|
|
|
// go read the process data
|
|
ret = PT->reader(PT,p);
|
|
if(ret) return ret;
|
|
}
|
|
|
|
out:
|
|
if(!saved_p) free(p);
|
|
// FIXME: maybe set tid to -1 here, for "-" in display?
|
|
return NULL;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// readtask: return a pointer to a proc_t filled with requested info about the
|
|
// next task available. If no more such tasks are available, return a null
|
|
// pointer (boolean false). Use the passed buffer instead of allocating
|
|
// space if it is non-NULL.
|
|
proc_t* readtask(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict t) {
|
|
static char path[PROCPATHLEN]; // must hold /proc/2000222000/task/2000222000/cmdline
|
|
proc_t *ret;
|
|
proc_t *saved_t;
|
|
|
|
saved_t = t;
|
|
if(!t) t = xcalloc(t, sizeof *t); /* passed buf or alloced mem */
|
|
|
|
// 1. got to fake a thread for old kernels
|
|
// 2. for single-threaded processes, this is faster
|
|
if(task_dir_missing || p->nlwp < 2){
|
|
if(PT->did_fake) goto out;
|
|
PT->did_fake=1;
|
|
memcpy(t,p,sizeof(proc_t));
|
|
return t;
|
|
}
|
|
|
|
for(;;){
|
|
// fills in the path, plus t->tid and t->tgid
|
|
if (unlikely(! PT->taskfinder(PT,p,t,path) )) goto out; // simple_nexttid
|
|
|
|
// go read the task data
|
|
ret = PT->taskreader(PT,p,t,path); // simple_readtask
|
|
if(ret) return ret;
|
|
}
|
|
|
|
out:
|
|
if(!saved_t) free(t);
|
|
return NULL;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// initiate a process table scan
|
|
PROCTAB* openproc(int flags, ...) {
|
|
va_list ap;
|
|
struct stat sbuf;
|
|
static int did_stat;
|
|
PROCTAB* PT = xmalloc(sizeof(PROCTAB));
|
|
|
|
if(!did_stat){
|
|
task_dir_missing = stat("/proc/self/task", &sbuf);
|
|
did_stat = 1;
|
|
}
|
|
PT->taskdir = NULL;
|
|
PT->taskdir_user = -1;
|
|
PT->taskfinder = simple_nexttid;
|
|
PT->taskreader = simple_readtask;
|
|
|
|
PT->reader = simple_readproc;
|
|
if (flags & PROC_PID){
|
|
PT->procfs = NULL;
|
|
PT->finder = listed_nextpid;
|
|
}else{
|
|
PT->procfs = opendir("/proc");
|
|
if(!PT->procfs) return NULL;
|
|
PT->finder = simple_nextpid;
|
|
}
|
|
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);
|
|
if (PT->taskdir) closedir(PT->taskdir);
|
|
memset(PT,'#',sizeof(PROCTAB));
|
|
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);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
void look_up_our_self(proc_t *p) {
|
|
char sbuf[1024];
|
|
|
|
if(file2str("/proc/self", "stat", sbuf, sizeof sbuf) == -1){
|
|
fprintf(stderr, "Error, do this: mount -t proc none /proc\n");
|
|
_exit(47);
|
|
}
|
|
stat2proc(sbuf, p); // parse /proc/self/stat
|
|
}
|
|
|
|
HIDDEN_ALIAS(readproc);
|
|
HIDDEN_ALIAS(readtask);
|
|
|
|
/* 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 exit(). 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_direct(PT, NULL); /* final null to terminate */
|
|
} while (tab[n++]); /* stop when NULL reached */
|
|
closeproc(PT);
|
|
return tab;
|
|
}
|
|
|
|
// Try again, this time with threads and selection.
|
|
proc_data_t *readproctab2(int(*want_proc)(proc_t *buf), int(*want_task)(proc_t *buf), PROCTAB *restrict const PT) {
|
|
proc_t** ptab = NULL;
|
|
unsigned n_proc_alloc = 0;
|
|
unsigned n_proc = 0;
|
|
|
|
proc_t** ttab = NULL;
|
|
unsigned n_task_alloc = 0;
|
|
unsigned n_task = 0;
|
|
|
|
proc_t* data = NULL;
|
|
unsigned n_alloc = 0;
|
|
unsigned long n_used = 0;
|
|
|
|
proc_data_t *pd;
|
|
|
|
for(;;){
|
|
proc_t *tmp;
|
|
if(n_alloc == n_used){
|
|
//proc_t *old = data;
|
|
n_alloc = n_alloc*5/4+30; // grow by over 25%
|
|
data = realloc(data,sizeof(proc_t)*n_alloc);
|
|
//if(!data) return NULL;
|
|
}
|
|
if(n_proc_alloc == n_proc){
|
|
//proc_t **old = ptab;
|
|
n_proc_alloc = n_proc_alloc*5/4+30; // grow by over 25%
|
|
ptab = realloc(ptab,sizeof(proc_t*)*n_proc_alloc);
|
|
//if(!ptab) return NULL;
|
|
}
|
|
tmp = readproc_direct(PT, data+n_used);
|
|
if(!tmp) break;
|
|
if(!want_proc(tmp)) continue;
|
|
ptab[n_proc++] = (proc_t*)(n_used++);
|
|
if(!( PT->flags & PROC_LOOSE_TASKS )) continue;
|
|
for(;;){
|
|
proc_t *t;
|
|
if(n_alloc == n_used){
|
|
//proc_t *old = data;
|
|
n_alloc = n_alloc*5/4+30; // grow by over 25%
|
|
data = realloc(data,sizeof(proc_t)*n_alloc);
|
|
//if(!data) return NULL;
|
|
}
|
|
if(n_task_alloc == n_task){
|
|
//proc_t **old = ttab;
|
|
n_task_alloc = n_task_alloc*5/4+1; // grow by over 25%
|
|
ttab = realloc(ttab,sizeof(proc_t*)*n_task_alloc);
|
|
//if(!ttab) return NULL;
|
|
}
|
|
t = readtask_direct(PT, tmp, data+n_used);
|
|
if(!t) break;
|
|
if(!want_task(t)) continue;
|
|
ttab[n_task++] = (proc_t*)(n_used++);
|
|
}
|
|
}
|
|
|
|
pd = malloc(sizeof(proc_data_t));
|
|
pd->proc = ptab;
|
|
pd->task = ttab;
|
|
pd->nproc = n_proc;
|
|
pd->ntask = n_task;
|
|
if(PT->flags & PROC_LOOSE_TASKS){
|
|
pd->tab = ttab;
|
|
pd->n = n_task;
|
|
}else{
|
|
pd->tab = ptab;
|
|
pd->n = n_proc;
|
|
}
|
|
// change array indexes to pointers
|
|
while(n_proc--) ptab[n_proc] = data+(long)(ptab[n_proc]);
|
|
while(n_task--) ttab[n_task] = data+(long)(ttab[n_task]);
|
|
|
|
return pd;
|
|
}
|
|
|
|
/*
|
|
* get_proc_stats - lookup a single tasks information and fill out a proc_t
|
|
*
|
|
* On failure, returns NULL. On success, returns 'p' and 'p' is a valid
|
|
* and filled out proc_t structure.
|
|
*/
|
|
proc_t * get_proc_stats(pid_t pid, proc_t *p)
|
|
{
|
|
static char path[PATH_MAX], sbuf[1024];
|
|
struct stat statbuf;
|
|
|
|
sprintf(path, "/proc/%d", pid);
|
|
if (stat(path, &statbuf)) {
|
|
perror("stat");
|
|
return NULL;
|
|
}
|
|
|
|
if (file2str(path, "stat", sbuf, sizeof sbuf) >= 0)
|
|
stat2proc(sbuf, p); /* parse /proc/#/stat */
|
|
if (file2str(path, "statm", sbuf, sizeof sbuf) >= 0)
|
|
statm2proc(sbuf, p); /* ignore statm errors here */
|
|
if (file2str(path, "status", sbuf, sizeof sbuf) >= 0)
|
|
status2proc(sbuf, p, 0 /*FIXME*/);
|
|
|
|
return p;
|
|
}
|