2002-02-02 04:17:29 +05:30
|
|
|
/*
|
|
|
|
* New Interface to Process Table -- PROCTAB Stream (a la Directory streams)
|
|
|
|
* Copyright (C) 1996 Charles L. Blake.
|
|
|
|
* Copyright (C) 1998 Michael K. Johnson
|
2002-12-15 06:00:17 +05:30
|
|
|
* Copyright 1998-2002 Albert Cahalan
|
2002-02-02 04:17:29 +05:30
|
|
|
* May be distributed under the conditions of the
|
|
|
|
* GNU Library General Public License; a copy is in COPYING
|
|
|
|
*/
|
2002-09-27 19:18:00 +05:30
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
2002-06-24 09:59:04 +05:30
|
|
|
#include "version.h"
|
|
|
|
#include "readproc.h"
|
2002-12-09 12:30:07 +05:30
|
|
|
#include "alloc.h"
|
|
|
|
#include "pwcache.h"
|
2002-06-24 09:59:04 +05:30
|
|
|
#include "devname.h"
|
|
|
|
#include "procps.h"
|
2002-02-02 04:17:29 +05:30
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
2002-12-03 14:37:59 +05:30
|
|
|
#include <errno.h>
|
2002-02-02 04:17:29 +05:30
|
|
|
#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>
|
|
|
|
|
2002-09-27 19:18:00 +05:30
|
|
|
#ifdef FLASK_LINUX
|
|
|
|
#include <fs_secure.h>
|
|
|
|
#endif
|
|
|
|
|
2003-02-17 06:27:15 +05:30
|
|
|
#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
|
|
|
|
|
2002-02-02 04:17:29 +05:30
|
|
|
/* initiate a process table scan
|
|
|
|
*/
|
|
|
|
PROCTAB* openproc(int flags, ...) {
|
|
|
|
va_list ap;
|
|
|
|
PROCTAB* PT = xmalloc(sizeof(PROCTAB));
|
|
|
|
|
2002-10-22 11:42:12 +05:30
|
|
|
if (flags & PROC_PID)
|
2002-02-02 04:17:29 +05:30
|
|
|
PT->procfs = NULL;
|
|
|
|
else if (!(PT->procfs = opendir("/proc")))
|
|
|
|
return NULL;
|
|
|
|
PT->flags = flags;
|
|
|
|
va_start(ap, flags); /* Init args list */
|
2002-10-22 11:42:12 +05:30
|
|
|
if (flags & PROC_PID)
|
2002-02-02 04:17:29 +05:30
|
|
|
PT->pids = va_arg(ap, pid_t*);
|
2002-10-22 11:42:12 +05:30
|
|
|
else if (flags & PROC_UID) {
|
2002-02-02 04:17:29 +05:30
|
|
|
PT->uids = va_arg(ap, uid_t*);
|
|
|
|
PT->nuid = va_arg(ap, int);
|
2002-09-30 12:41:30 +05:30
|
|
|
}
|
2002-02-02 04:17:29 +05:30
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-02-17 06:27:15 +05:30
|
|
|
///////////////////////////////////////////////////////////////////////////
|
2002-02-02 04:17:29 +05:30
|
|
|
|
2003-02-17 06:27:15 +05:30
|
|
|
typedef struct status_table_struct {
|
|
|
|
unsigned char name[6]; // /proc/*/status field name
|
|
|
|
short len; // name length
|
|
|
|
#ifdef LABEL_OFFSET
|
|
|
|
long offset; // jump address offset
|
|
|
|
#else
|
|
|
|
void *addr;
|
|
|
|
#endif
|
|
|
|
} status_table_struct;
|
|
|
|
|
|
|
|
#ifdef LABEL_OFFSET
|
2003-05-30 08:43:32 +05:30
|
|
|
#define F(x) {#x, sizeof(#x)-1, (long)(&&case_##x-&&base)},
|
2003-02-17 06:27:15 +05:30
|
|
|
#else
|
|
|
|
#define F(x) {#x, sizeof(#x)-1, &&case_##x},
|
|
|
|
#endif
|
|
|
|
#define NUL {"", 0, 0},
|
2002-12-03 14:37:59 +05:30
|
|
|
|
2003-02-17 06:27:15 +05:30
|
|
|
// Derived from:
|
|
|
|
// gperf -7 --language=ANSI-C --key-positions=1,3,4 -C -n -c sml.gperf
|
2002-02-02 04:17:29 +05:30
|
|
|
|
2003-02-17 06:27:15 +05:30
|
|
|
static void status2proc(char *S, proc_t *restrict P){
|
|
|
|
static const unsigned char asso[] = {
|
|
|
|
56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
|
|
|
|
56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
|
|
|
|
56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
|
|
|
|
56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 15, 56, 56, 56, 56, 56,
|
|
|
|
56, 56, 25, 30, 15, 3, 56, 5, 56, 3, 56, 56, 3, 56, 10, 56,
|
|
|
|
18, 56, 13, 0, 30, 25, 0, 56, 56, 56, 56, 56, 56, 56, 56, 56,
|
|
|
|
56, 30, 56, 8, 0, 0, 56, 25, 56, 5, 56, 56, 56, 0, 56, 56,
|
|
|
|
56, 56, 56, 56, 0, 56, 56, 56, 0, 56, 56, 56, 56, 56, 56, 56
|
|
|
|
};
|
|
|
|
static const status_table_struct table[] = {
|
|
|
|
F(VmStk)
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
F(VmExe)
|
|
|
|
NUL
|
|
|
|
F(VmSize)
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
F(VmLib)
|
|
|
|
NUL
|
|
|
|
F(Name)
|
|
|
|
F(VmLck)
|
|
|
|
NUL
|
|
|
|
F(VmRSS)
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
F(ShdPnd)
|
|
|
|
NUL
|
|
|
|
F(Gid)
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
F(PPid)
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
F(SigIgn)
|
|
|
|
NUL
|
|
|
|
F(State)
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
F(Pid)
|
|
|
|
NUL
|
|
|
|
F(Tgid)
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
F(Uid)
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
F(SigPnd)
|
|
|
|
NUL
|
|
|
|
F(VmData)
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
F(SigBlk)
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
F(SigCgt)
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
NUL
|
|
|
|
};
|
2002-02-02 04:17:29 +05:30
|
|
|
|
2003-02-17 06:27:15 +05:30
|
|
|
#undef F
|
|
|
|
#undef NUL
|
2002-02-02 04:17:29 +05:30
|
|
|
|
2003-02-17 06:27:15 +05:30
|
|
|
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;
|
2002-02-02 04:17:29 +05:30
|
|
|
|
2003-02-17 06:27:15 +05:30
|
|
|
goto base;
|
2003-02-12 13:45:53 +05:30
|
|
|
|
2003-02-17 06:27:15 +05:30
|
|
|
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);
|
2002-02-02 04:17:29 +05:30
|
|
|
#else
|
2003-02-17 06:27:15 +05:30
|
|
|
goto *entry.addr;
|
2002-02-02 04:17:29 +05:30
|
|
|
#endif
|
|
|
|
|
2003-02-17 06:27:15 +05:30
|
|
|
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_Name:{
|
2003-02-18 09:21:03 +05:30
|
|
|
unsigned u = 0;
|
|
|
|
while(u < sizeof P->cmd - 1u){
|
2003-02-17 06:27:15 +05:30
|
|
|
int c = *S++;
|
|
|
|
if(unlikely(c=='\n')) break;
|
2003-02-18 09:21:03 +05:30
|
|
|
if(unlikely(c=='\0')) break; // should never happen
|
2003-02-17 06:27:15 +05:30
|
|
|
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 '\\'
|
|
|
|
}
|
2003-02-18 09:21:03 +05:30
|
|
|
P->cmd[u++] = c;
|
2003-02-17 06:27:15 +05:30
|
|
|
}
|
2003-02-18 09:21:03 +05:30
|
|
|
P->cmd[u] = '\0';
|
|
|
|
S--; // put back the '\n' or '\0'
|
2003-02-17 06:27:15 +05:30
|
|
|
continue;
|
|
|
|
}
|
|
|
|
case_PPid:
|
|
|
|
P->ppid = strtol(S,&S,10);
|
|
|
|
continue;
|
|
|
|
case_Pid:
|
|
|
|
P->pid = strtol(S,&S,10);
|
|
|
|
continue;
|
|
|
|
|
|
|
|
case_ShdPnd:
|
|
|
|
memcpy(P->signal, S, 16);
|
|
|
|
P->signal[16] = '\0';
|
|
|
|
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;
|
2002-02-02 04:17:29 +05:30
|
|
|
|
2003-02-17 06:27:15 +05:30
|
|
|
case_State:
|
|
|
|
P->state = *S;
|
|
|
|
continue;
|
|
|
|
case_Tgid:
|
|
|
|
P->tgid = 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_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;
|
|
|
|
}
|
|
|
|
LEAVE(0x220);
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
2002-02-02 04:17:29 +05:30
|
|
|
|
2002-12-21 16:04:50 +05:30
|
|
|
// Reads /proc/*/stat files, being careful not to trip over processes with
|
|
|
|
// names like ":-) 1 2 3 4 5 6".
|
2002-11-25 15:46:33 +05:30
|
|
|
static void stat2proc(const char* S, proc_t *restrict P) {
|
2002-12-03 14:48:27 +05:30
|
|
|
unsigned num;
|
2002-12-03 14:37:59 +05:30
|
|
|
char* tmp;
|
|
|
|
|
2003-02-17 06:27:15 +05:30
|
|
|
ENTER(0x160);
|
|
|
|
|
2002-02-02 04:17:29 +05:30
|
|
|
/* fill in default values for older kernels */
|
|
|
|
P->exit_signal = SIGCHLD;
|
|
|
|
P->processor = 0;
|
2002-10-03 15:11:57 +05:30
|
|
|
P->rtprio = -1;
|
|
|
|
P->sched = -1;
|
2002-12-03 14:37:59 +05:30
|
|
|
|
2002-12-21 11:52:00 +05:30
|
|
|
S = strchr(S, '(') + 1;
|
|
|
|
tmp = strrchr(S, ')');
|
2002-12-03 14:37:59 +05:30
|
|
|
num = tmp - S;
|
2002-12-07 14:04:03 +05:30
|
|
|
if(unlikely(num >= sizeof P->cmd)) num = sizeof P->cmd - 1;
|
2002-12-03 14:37:59 +05:30
|
|
|
memcpy(P->cmd, S, num);
|
|
|
|
P->cmd[num] = '\0';
|
|
|
|
S = tmp + 2; // skip ") "
|
|
|
|
|
|
|
|
num = sscanf(S,
|
2002-02-02 04:17:29 +05:30
|
|
|
"%c "
|
|
|
|
"%d %d %d %d %d "
|
2002-05-28 09:48:55 +05:30
|
|
|
"%lu %lu %lu %lu %lu "
|
|
|
|
"%Lu %Lu %Lu %Lu " /* utime stime cutime cstime */
|
|
|
|
"%ld %ld %ld %ld "
|
|
|
|
"%Lu " /* start_time */
|
|
|
|
"%lu "
|
2002-02-02 04:17:29 +05:30
|
|
|
"%ld "
|
2003-01-16 13:33:40 +05:30
|
|
|
"%lu %"KLF"u %"KLF"u %"KLF"u %"KLF"u %"KLF"u "
|
2002-02-02 04:17:29 +05:30
|
|
|
"%*s %*s %*s %*s " /* discard, no RT signals & Linux 2.1 used hex */
|
2003-01-16 13:33:40 +05:30
|
|
|
"%"KLF"u %lu %lu "
|
2002-10-03 15:11:57 +05:30
|
|
|
"%d %d "
|
|
|
|
"%lu %lu",
|
2002-02-02 04:17:29 +05:30
|
|
|
&P->state,
|
|
|
|
&P->ppid, &P->pgrp, &P->session, &P->tty, &P->tpgid,
|
2002-05-28 09:48:55 +05:30
|
|
|
&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,
|
2002-02-02 04:17:29 +05:30
|
|
|
&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 -- */
|
2002-10-03 15:11:57 +05:30
|
|
|
&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 */
|
2002-02-02 04:17:29 +05:30
|
|
|
);
|
2003-02-17 06:27:15 +05:30
|
|
|
LEAVE(0x160);
|
2002-02-02 04:17:29 +05:30
|
|
|
}
|
|
|
|
|
2003-02-17 06:27:15 +05:30
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2002-11-25 15:46:33 +05:30
|
|
|
static void statm2proc(const char* s, proc_t *restrict P) {
|
2002-02-02 04:17:29 +05:30
|
|
|
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); */
|
|
|
|
}
|
|
|
|
|
2002-10-12 09:55:57 +05:30
|
|
|
static int file2str(const char *directory, const char *what, char *ret, int cap) {
|
2002-02-02 04:17:29 +05:30
|
|
|
static char filename[80];
|
|
|
|
int fd, num_read;
|
|
|
|
|
|
|
|
sprintf(filename, "%s/%s", directory, what);
|
2002-12-03 14:37:59 +05:30
|
|
|
fd = open(filename, O_RDONLY, 0);
|
|
|
|
if(unlikely(fd==-1)) return -1;
|
|
|
|
num_read = read(fd, ret, cap - 1);
|
2002-02-02 04:17:29 +05:30
|
|
|
close(fd);
|
2003-02-17 06:27:15 +05:30
|
|
|
if(unlikely(num_read<=0)) return -1;
|
|
|
|
ret[num_read] = '\0';
|
2002-02-02 04:17:29 +05:30
|
|
|
return num_read;
|
|
|
|
}
|
|
|
|
|
2002-10-12 09:55:57 +05:30
|
|
|
static char** file2strvec(const char* directory, const char* what) {
|
2002-02-02 04:17:29 +05:30
|
|
|
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);
|
2002-12-03 14:37:59 +05:30
|
|
|
fd = open(buf, O_RDONLY, 0);
|
|
|
|
if(fd==-1) return NULL;
|
2002-02-02 04:17:29 +05:30
|
|
|
|
|
|
|
/* read whole file into a memory buffer, allocating as we go */
|
|
|
|
while ((n = read(fd, buf, sizeof buf - 1)) > 0) {
|
2002-10-14 02:02:09 +05:30
|
|
|
if (n < (int)(sizeof buf - 1))
|
2002-02-02 04:17:29 +05:30
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2002-12-03 14:37:59 +05:30
|
|
|
// warning: interface may change
|
|
|
|
int read_cmdline(char *restrict const dst, unsigned sz, unsigned pid){
|
|
|
|
char name[32];
|
|
|
|
int fd;
|
2002-12-03 14:48:27 +05:30
|
|
|
unsigned n = 0;
|
|
|
|
dst[0] = '\0';
|
2002-12-03 14:37:59 +05:30
|
|
|
snprintf(name, sizeof name, "/proc/%u/cmdline", pid);
|
|
|
|
fd = open(name, O_RDONLY);
|
2002-12-03 14:48:27 +05:30
|
|
|
if(fd==-1) return 0;
|
2002-12-03 14:37:59 +05:30
|
|
|
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
|
|
|
|
}
|
2002-12-28 15:07:42 +05:30
|
|
|
close(fd);
|
2002-12-03 14:37:59 +05:30
|
|
|
if(n){
|
|
|
|
int i;
|
|
|
|
if(n==sz) n--;
|
|
|
|
dst[n] = '\0';
|
|
|
|
i=n;
|
|
|
|
while(i--){
|
2003-02-17 06:27:15 +05:30
|
|
|
int c = dst[i];
|
|
|
|
if(c<' ' || c>'~') dst[i]=' ';
|
2002-12-03 14:37:59 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
return n;
|
|
|
|
}
|
2002-02-02 04:17:29 +05:30
|
|
|
|
|
|
|
/* 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.
|
|
|
|
*/
|
2002-05-28 07:46:24 +05:30
|
|
|
proc_t* readproc(PROCTAB* PT, proc_t* p) {
|
2002-02-02 04:17:29 +05:30
|
|
|
static struct direct *ent; /* dirent handle */
|
|
|
|
static struct stat sb; /* stat buffer */
|
2002-02-02 05:10:38 +05:30
|
|
|
static char path[32], sbuf[1024]; /* bufs for stat,statm */
|
2002-09-27 19:18:00 +05:30
|
|
|
#ifdef FLASK_LINUX
|
2002-09-30 12:41:30 +05:30
|
|
|
security_id_t secsid;
|
2002-09-27 19:18:00 +05:30
|
|
|
#endif
|
2002-12-21 16:57:47 +05:30
|
|
|
pid_t pid; // saved until we have a proc_t allocated for sure
|
2002-02-02 04:17:29 +05:30
|
|
|
|
|
|
|
/* 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)
|
|
|
|
|
2002-10-22 11:42:12 +05:30
|
|
|
if (flags & PROC_PID) {
|
2002-12-21 16:57:47 +05:30
|
|
|
pid = *(PT->pids)++;
|
2002-12-21 11:52:00 +05:30
|
|
|
if (unlikely(!pid)) return NULL;
|
|
|
|
snprintf(path, sizeof path, "/proc/%d", pid);
|
2002-02-02 04:17:29 +05:30
|
|
|
} else { /* get next numeric /proc ent */
|
2002-12-09 05:23:05 +05:30
|
|
|
for (;;) {
|
|
|
|
ent = readdir(PT->procfs);
|
|
|
|
if(unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return NULL;
|
2002-12-21 11:52:00 +05:30
|
|
|
if(likely( likely(*ent->d_name > '0') && likely(*ent->d_name <= '9') )) break;
|
2002-12-09 05:23:05 +05:30
|
|
|
}
|
2002-12-21 16:57:47 +05:30
|
|
|
pid = strtoul(ent->d_name, NULL, 10);
|
2002-12-21 11:52:00 +05:30
|
|
|
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);
|
2002-02-02 04:17:29 +05:30
|
|
|
}
|
2002-09-27 19:18:00 +05:30
|
|
|
#ifdef FLASK_LINUX
|
2002-09-30 12:41:30 +05:30
|
|
|
if ( stat_secure(path, &sb, &secsid) == -1 ) /* no such dirent (anymore) */
|
2002-09-27 19:18:00 +05:30
|
|
|
#else
|
2002-12-09 05:23:05 +05:30
|
|
|
if (unlikely(stat(path, &sb) == -1)) /* no such dirent (anymore) */
|
2002-09-27 19:18:00 +05:30
|
|
|
#endif
|
2002-02-02 04:17:29 +05:30
|
|
|
goto next_proc;
|
2002-09-27 19:18:00 +05:30
|
|
|
|
2002-10-22 11:42:12 +05:30
|
|
|
if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid))
|
2002-02-02 04:17:29 +05:30
|
|
|
goto next_proc; /* not one of the requested uids */
|
|
|
|
|
2002-05-28 07:46:24 +05:30
|
|
|
if (!p)
|
|
|
|
p = xcalloc(p, sizeof *p); /* passed buf or alloced mem */
|
2002-02-02 04:17:29 +05:30
|
|
|
|
2002-12-21 16:57:47 +05:30
|
|
|
p->euid = sb.st_uid; /* need a way to get real uid */
|
2003-06-30 09:00:35 +05:30
|
|
|
p->egid = sb.st_gid; /* need a way to get real gid */
|
2002-09-27 19:18:00 +05:30
|
|
|
#ifdef FLASK_LINUX
|
2002-09-30 12:41:30 +05:30
|
|
|
p->secsid = secsid;
|
2002-09-27 19:18:00 +05:30
|
|
|
#endif
|
2002-12-21 16:57:47 +05:30
|
|
|
p->pid = pid;
|
2002-09-27 19:18:00 +05:30
|
|
|
|
2002-12-09 00:21:56 +05:30
|
|
|
if (flags & PROC_FILLSTAT) { /* read, parse /proc/#/stat */
|
2002-12-09 05:23:05 +05:30
|
|
|
if (unlikely( file2str(path, "stat", sbuf, sizeof sbuf) == -1 ))
|
2002-12-09 00:21:56 +05:30
|
|
|
goto next_proc; /* error reading /proc/#/stat */
|
|
|
|
stat2proc(sbuf, p); /* parse /proc/#/stat */
|
|
|
|
}
|
2002-02-02 04:17:29 +05:30
|
|
|
|
2002-12-09 05:23:05 +05:30
|
|
|
if (unlikely(flags & PROC_FILLMEM)) { /* read, parse /proc/#/statm */
|
|
|
|
if (likely( file2str(path, "statm", sbuf, sizeof sbuf) != -1 ))
|
2002-02-02 04:17:29 +05:30
|
|
|
statm2proc(sbuf, p); /* ignore statm errors here */
|
|
|
|
} /* statm fields just zero */
|
|
|
|
|
2002-10-22 11:42:12 +05:30
|
|
|
if (flags & PROC_FILLSTATUS) { /* read, parse /proc/#/status */
|
2002-12-09 05:23:05 +05:30
|
|
|
if (likely( file2str(path, "status", sbuf, sizeof sbuf) != -1 )){
|
2002-12-03 14:37:59 +05:30
|
|
|
status2proc(sbuf, p);
|
2002-02-02 04:17:29 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* some number->text resolving which is time consuming */
|
2002-10-22 11:42:12 +05:30
|
|
|
if (flags & PROC_FILLUSR){
|
2002-02-02 04:17:29 +05:30
|
|
|
strncpy(p->euser, user_from_uid(p->euid), sizeof p->euser);
|
2002-10-22 11:42:12 +05:30
|
|
|
if(flags & PROC_FILLSTATUS) {
|
2002-02-02 04:17:29 +05:30
|
|
|
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);
|
2002-10-02 17:40:39 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* some number->text resolving which is time consuming */
|
2002-10-22 11:42:12 +05:30
|
|
|
if (flags & PROC_FILLGRP){
|
2002-10-02 17:40:39 +05:30
|
|
|
strncpy(p->egroup, group_from_gid(p->egid), sizeof p->egroup);
|
2002-10-22 11:42:12 +05:30
|
|
|
if(flags & PROC_FILLSTATUS) {
|
2002-10-02 17:40:39 +05:30
|
|
|
strncpy(p->rgroup, group_from_gid(p->rgid), sizeof p->rgroup);
|
|
|
|
strncpy(p->sgroup, group_from_gid(p->sgid), sizeof p->sgroup);
|
2002-02-02 04:17:29 +05:30
|
|
|
strncpy(p->fgroup, group_from_gid(p->fgid), sizeof p->fgroup);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-10-22 11:42:12 +05:30
|
|
|
if ((flags & PROC_FILLCOM) || (flags & PROC_FILLARG)) /* read+parse /proc/#/cmdline */
|
2002-02-02 04:17:29 +05:30
|
|
|
p->cmdline = file2strvec(path, "cmdline");
|
2002-10-02 17:40:39 +05:30
|
|
|
else
|
|
|
|
p->cmdline = NULL;
|
|
|
|
|
2002-12-09 05:23:05 +05:30
|
|
|
if (unlikely(flags & PROC_FILLENV)) /* read+parse /proc/#/environ */
|
2002-02-02 04:17:29 +05:30
|
|
|
p->environ = file2strvec(path, "environ");
|
2002-10-02 17:40:39 +05:30
|
|
|
else
|
|
|
|
p->environ = NULL;
|
2002-02-02 04:17:29 +05:30
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2002-05-28 07:46:24 +05:30
|
|
|
proc_t* ps_readproc(PROCTAB* PT, proc_t* p) {
|
2002-02-02 04:17:29 +05:30
|
|
|
static struct direct *ent; /* dirent handle */
|
|
|
|
static struct stat sb; /* stat buffer */
|
2002-02-02 05:10:38 +05:30
|
|
|
static char path[32], sbuf[1024]; /* bufs for stat,statm */
|
2002-09-27 19:18:00 +05:30
|
|
|
#ifdef FLASK_LINUX
|
2002-09-30 12:41:30 +05:30
|
|
|
security_id_t secsid;
|
2002-09-27 19:18:00 +05:30
|
|
|
#endif
|
2002-12-21 16:57:47 +05:30
|
|
|
pid_t pid; // saved until we have a proc_t allocated for sure
|
2002-02-02 04:17:29 +05:30
|
|
|
|
|
|
|
/* 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)
|
|
|
|
|
2002-12-21 11:52:00 +05:30
|
|
|
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;
|
|
|
|
}
|
2002-12-21 16:57:47 +05:30
|
|
|
pid = strtoul(ent->d_name, NULL, 10);
|
2002-12-21 11:52:00 +05:30
|
|
|
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);
|
2002-02-02 04:17:29 +05:30
|
|
|
|
2002-09-27 19:18:00 +05:30
|
|
|
#ifdef FLASK_LINUX
|
2002-09-30 12:41:30 +05:30
|
|
|
if (stat_secure(path, &sb, &secsid) == -1) /* no such dirent (anymore) */
|
2002-09-27 19:18:00 +05:30
|
|
|
#else
|
2002-02-02 04:17:29 +05:30
|
|
|
if (stat(path, &sb) == -1) /* no such dirent (anymore) */
|
2002-09-27 19:18:00 +05:30
|
|
|
#endif
|
2002-02-02 04:17:29 +05:30
|
|
|
goto next_proc;
|
|
|
|
|
2002-05-28 07:46:24 +05:30
|
|
|
if (!p)
|
|
|
|
p = xcalloc(p, sizeof *p); /* passed buf or alloced mem */
|
2002-12-21 16:57:47 +05:30
|
|
|
|
2002-02-02 04:17:29 +05:30
|
|
|
p->euid = sb.st_uid; /* need a way to get real uid */
|
2003-06-30 09:00:35 +05:30
|
|
|
p->egid = sb.st_gid; /* need a way to get real gid */
|
2002-09-27 19:18:00 +05:30
|
|
|
#ifdef FLASK_LINUX
|
2002-09-30 12:41:30 +05:30
|
|
|
p->secsid = secsid;
|
2002-09-27 19:18:00 +05:30
|
|
|
#endif
|
2002-12-21 16:57:47 +05:30
|
|
|
p->pid = pid;
|
2002-02-02 04:17:29 +05:30
|
|
|
|
|
|
|
if ((file2str(path, "stat", sbuf, sizeof sbuf)) == -1)
|
|
|
|
goto next_proc; /* error reading /proc/#/stat */
|
|
|
|
stat2proc(sbuf, p); /* parse /proc/#/stat */
|
|
|
|
|
2002-10-22 11:42:12 +05:30
|
|
|
if (flags & PROC_FILLMEM) { /* read, parse /proc/#/statm */
|
2002-02-02 04:17:29 +05:30
|
|
|
if ((file2str(path, "statm", sbuf, sizeof sbuf)) != -1 )
|
|
|
|
statm2proc(sbuf, p); /* ignore statm errors here */
|
2002-10-02 17:40:39 +05:30
|
|
|
} /* statm fields just zero */
|
2002-02-02 04:17:29 +05:30
|
|
|
|
2002-10-22 11:42:12 +05:30
|
|
|
/* if (flags & PROC_FILLSTATUS) { */ /* read, parse /proc/#/status */
|
2002-02-02 04:17:29 +05:30
|
|
|
if ((file2str(path, "status", sbuf, sizeof sbuf)) != -1 ){
|
2002-12-03 14:37:59 +05:30
|
|
|
status2proc(sbuf, p);
|
2002-02-02 04:17:29 +05:30
|
|
|
}
|
|
|
|
/* }*/
|
|
|
|
|
|
|
|
/* some number->text resolving which is time consuming */
|
2002-10-22 11:42:12 +05:30
|
|
|
if (flags & PROC_FILLUSR){
|
2002-02-02 04:17:29 +05:30
|
|
|
strncpy(p->euser, user_from_uid(p->euid), sizeof p->euser);
|
2002-10-22 11:42:12 +05:30
|
|
|
/* if(flags & PROC_FILLSTATUS) { */
|
2002-02-02 04:17:29 +05:30
|
|
|
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);
|
2002-10-02 17:40:39 +05:30
|
|
|
/* }*/
|
|
|
|
}
|
|
|
|
|
|
|
|
/* some number->text resolving which is time consuming */
|
2002-10-22 11:42:12 +05:30
|
|
|
if (flags & PROC_FILLGRP){
|
2002-10-02 17:40:39 +05:30
|
|
|
strncpy(p->egroup, group_from_gid(p->egid), sizeof p->egroup);
|
2002-10-22 11:42:12 +05:30
|
|
|
/* if(flags & PROC_FILLSTATUS) { */
|
2002-10-02 17:40:39 +05:30
|
|
|
strncpy(p->rgroup, group_from_gid(p->rgid), sizeof p->rgroup);
|
|
|
|
strncpy(p->sgroup, group_from_gid(p->sgid), sizeof p->sgroup);
|
2002-02-02 04:17:29 +05:30
|
|
|
strncpy(p->fgroup, group_from_gid(p->fgid), sizeof p->fgroup);
|
|
|
|
/* }*/
|
2002-10-02 17:40:39 +05:30
|
|
|
}
|
2002-02-02 04:17:29 +05:30
|
|
|
|
2002-10-22 11:42:12 +05:30
|
|
|
if ((flags & PROC_FILLCOM) || (flags & PROC_FILLARG)) /* read+parse /proc/#/cmdline */
|
2002-02-02 04:17:29 +05:30
|
|
|
p->cmdline = file2strvec(path, "cmdline");
|
2002-10-02 17:40:39 +05:30
|
|
|
else
|
|
|
|
p->cmdline = NULL;
|
|
|
|
|
2002-10-22 11:42:12 +05:30
|
|
|
if (flags & PROC_FILLENV) /* read+parse /proc/#/environ */
|
2002-02-02 04:17:29 +05:30
|
|
|
p->environ = file2strvec(path, "environ");
|
2002-10-02 17:40:39 +05:30
|
|
|
else
|
|
|
|
p->environ = NULL;
|
2002-02-02 04:17:29 +05:30
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
#undef flags
|
|
|
|
|
|
|
|
|
|
|
|
void look_up_our_self(proc_t *p) {
|
2003-05-31 20:48:13 +05:30
|
|
|
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
|
2002-02-02 04:17:29 +05:30
|
|
|
}
|
|
|
|
|
2003-01-15 16:22:39 +05:30
|
|
|
HIDDEN_ALIAS(readproc);
|
2002-02-02 04:17:29 +05:30
|
|
|
|
|
|
|
/* Convenient wrapper around openproc and readproc to slurp in the whole process
|
|
|
|
* table subset satisfying the constraints of flags and the optional PID list.
|
2003-07-03 10:50:19 +05:30
|
|
|
* Free allocated memory with exit(). Access via tab[N]->member. The pointer
|
|
|
|
* list is NULL terminated.
|
2002-02-02 04:17:29 +05:30
|
|
|
*/
|
|
|
|
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 */
|
2002-10-22 11:42:12 +05:30
|
|
|
if (flags & PROC_UID) {
|
2002-02-02 04:17:29 +05:30
|
|
|
/* 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);
|
|
|
|
}
|
2002-10-22 11:42:12 +05:30
|
|
|
else if (flags & PROC_PID)
|
2002-02-02 04:17:29 +05:30
|
|
|
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 */
|
2003-01-15 16:22:39 +05:30
|
|
|
tab[n] = readproc_direct(PT, NULL); /* final null to terminate */
|
2002-02-02 04:17:29 +05:30
|
|
|
} while (tab[n++]); /* stop when NULL reached */
|
|
|
|
closeproc(PT);
|
|
|
|
return tab;
|
|
|
|
}
|