blob: cb625b93919c01019139b6e5e16bae3cd7f131e3 [file] [log] [blame]
/*
* dproc.c - NEXTSTEP and OPENSTEP process access functions for lsof
*/
/*
* Copyright 1994 Purdue Research Foundation, West Lafayette, Indiana
* 47907. All rights reserved.
*
* Written by Victor A. Abell
*
* This software is not subject to any license of the American Telephone
* and Telegraph Company or the Regents of the University of California.
*
* Permission is granted to anyone to use this software for any purpose on
* any computer system, and to alter it and redistribute it freely, subject
* to the following restrictions:
*
* 1. Neither the authors nor Purdue University are responsible for any
* consequences of the use of this software.
*
* 2. The origin of this software must not be misrepresented, either by
* explicit claim or by omission. Credit to the authors and Purdue
* University must appear in documentation and sources.
*
* 3. Altered versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 4. This notice may not be removed or altered.
*/
#ifndef lint
static char copyright[] =
"@(#) Copyright 1994 Purdue Research Foundation.\nAll rights reserved.\n";
static char *rcsid = "$Id: dproc.c,v 1.12 2001/08/09 11:44:07 abe Exp $";
#endif
#include "lsof.h"
/*
* Local static values
*/
static int Mxp = 0; /* maximum number of processes */
static int Np; /* number of entries in P[] */
static int Nv = 0; /* allocated Vp[] entries */
static struct proc *P = (struct proc *)NULL;
/* local proc structure table */
static KA_T *Pa = (KA_T *)NULL; /* kernel address for each P[] entry */
static KA_T Kp; /* kernel process table pointer */
static KA_T *Vp = (KA_T *)NULL; /* vnode address cache */
_PROTOTYPE(static void get_kernel_access,(void));
_PROTOTYPE(static void process_map,(caddr_t map));
_PROTOTYPE(static void read_proc,(void));
/*
* ckkv - check kernel version
*/
void
ckkv(d, er, ev, ea)
char *d; /* dialect */
char *er; /* expected release */
char *ev; /* expected version */
char *ea; /* expected architecture */
{
#if defined(HASKERNIDCK)
char m[128], *t;
kernel_version_t kv;
kern_return_t kr;
char *vt = (char *)NULL;
if (Fwarn)
return;
/*
* Read Mach kernel version.
*/
if ((kr = host_kernel_version(host_self(), kv)) != KERN_SUCCESS) {
(void) snpf(m, sizeof(m), "%s: can't get kernel version:", Pn);
mach_error(m, kr);
Exit(1);
}
/*
* Skip blank-separated tokens until reaching "Mach". The kernel version
* string follows. Eliminate anything but decimal digits and periods from
* the kernel version string.
*/
if ((t = strtok(kv, " "))) {
do {
if (strcmp(t, "Mach") == 0)
break;
} while ((t = strtok((char *)NULL, " ")));
if (t)
vt = strtok((char *)NULL, " ");
}
if (vt) {
for (t = vt; *t; t++) {
if (*t == '.' || (*t >= '0' && *t <= '9'))
continue;
*t = '\0';
break;
}
}
/*
* Warn if the actual and expected versions don't match.
*/
if (!ev || !vt || strcmp(ev, vt))
(void) fprintf(stderr,
"%s: WARNING: compiled for %s version %s; this is %s\n",
Pn, d, ev ? ev : "UNKNOWN", vt ? vt : "UNKNOWN");
#endif /* defined(HASKERNIDCK) */
}
/*
* gather_proc_info() -- gather process information
*/
void
gather_proc_info()
{
int i, nf, px;
MALLOC_S nb;
short pss, sf;
struct task { /* (Should come from <kern/task.h>.) */
caddr_t d1[SIMPLE_LOCK_SIZE + 2];
caddr_t map;
caddr_t d2[SIMPLE_LOCK_SIZE + 9];
struct utask *u_address;
struct proc *proc;
} t;
struct utask *u;
static struct file **uf = (struct file **)NULL;
static MALLOC_S ufb = 0;
struct utask ut;
#if defined(HASFSTRUCT)
static char *pof = (char *)NULL;
static MALLOC_S pofb = 0;
#endif /* defined(HASFSTRUCT) */
/*
* Clear previously loaded tables and read the process table.
*/
#if STEPV>=31
(void) clr_svnc();
#endif /* STEPV>=31 */
(void) read_proc();
/*
* Process proc structures pre-loaded in initialize().
*/
for (px = 0, u = &ut; px < Np; px++) {
if (is_proc_excl(P[px].p_pid, (int)P[px].p_pgrp,
(UID_ARG)P[px].p_uid, &pss, &sf))
continue;
/*
* Read the task associated with the process, and the user
* area assocated with the task.
*/
if (kread((KA_T)P[px].task, (char *)&t, sizeof(t)))
continue;
if ((KA_T)t.proc != Pa[px])
continue;
if (kread((KA_T)t.u_address, (char *)&ut, sizeof(ut)))
continue;
if ((KA_T)ut.uu_procp != Pa[px])
continue;
/*
* Allocate a local process structure and start filling it.
*/
if (is_cmd_excl(u->u_comm, &pss, &sf))
continue;
alloc_lproc(P[px].p_pid, (int)P[px].p_pgrp, (int)P[px].p_ppid,
(UID_ARG)P[px].p_uid, u->u_comm, (int)pss, (int)sf);
Plf = (struct lfile *)NULL;
/*
* Save current working directory information.
*/
if (u->u_cdir) {
alloc_lfile(CWD, -1);
FILEPTR = (struct file *)NULL;
process_node((KA_T)u->u_cdir);
if (Lf->sf)
link_lfile();
}
/*
* Save root directory information.
*/
if (u->u_rdir) {
alloc_lfile(RTD, -1);
FILEPTR = (struct file *)NULL;
process_node((KA_T)u->u_rdir);
if (Lf->sf)
link_lfile();
}
/*
* Print information on the text files of the virtual memory
* address map.
*/
if (t.map)
process_map(t.map);
/*
* Save information on file descriptors.
*
* NEXTSTEP file pointers come from a structure whose pointer is
* in the user task area.
*/
nf = ut.uu_ofile_cnt;
nb = (MALLOC_S)(sizeof(struct file *) * nf);
if (nb > ufb) {
if (!uf)
uf = (struct file **)malloc(nb);
else
uf = (struct file **)realloc((MALLOC_P *)uf, nb);
if (!uf) {
(void) fprintf(stderr, "%s: no uu_ofile space\n", Pn);
Exit(1);
}
ufb = nb;
}
if (kread((KA_T)ut.uu_ofile, (char *)uf, nb))
continue;
#if defined(HASFSTRUCT)
if (Fsv & FSV_FG) {
nb = (MALLOC_S)(sizeof(char) * nf);
if (nb > pofb) {
if (!pof)
pof = (char *)malloc(nb);
else
pof = (char *)realloc((MALLOC_P *)pof, nb);
if (!pof) {
(void) fprintf(stderr, "%s: no uu_pofile space\n", Pn);
Exit(1);
}
pofb = nb;
}
if (kread((KA_T)ut.uu_pofile, (char *)pof, nb))
zeromem(pof, nb);
}
#endif /* defined(HASFSTRUCT) */
for (i = 0; i < nf; i++) {
if (uf[i]) {
alloc_lfile((char *)NULL, i);
process_file((KA_T)uf[i]);
if (Lf->sf) {
#if defined(HASFSTRUCT)
if (Fsv & FSV_FG)
Lf->pof = (long)pof[i];
#endif /* defined(HASFSTRUCT) */
link_lfile();
}
}
}
/*
* Examine results.
*/
if (examine_lproc())
return;
}
}
/*
* get_kernel_access() - access the required information in the kernel
*/
static void
get_kernel_access()
{
int i;
KA_T lv;
#if defined(HAS_AFS)
struct nlist *nl = (struct nlist *)NULL;
unsigned long v[3];
#endif /* defined(HAS_AFS) */
/*
* Check kernel version against compiled version.
*/
ckkv("NEXTSTEP", (char *)NULL, LSOF_VSTR, (char *)NULL);
#if defined(WILLDROPGID)
/*
* If kernel memory isn't coming from KMEM, drop setgid permission
* before attempting to open the (Memory) file.
*/
if (Memory)
(void) dropgid();
#else /* !defined(WILLDROPGID) */
/*
* See if the non-KMEM memory file is readable.
*/
if (Memory && !is_readable(Memory, 1))
Exit(1);
#endif /* defined(WILLDROPGID) */
/*
* Access the kernel memory file.
*/
if ((Kd = open(Memory ? Memory : KMEM, O_RDONLY, 0)) < 0) {
(void) fprintf(stderr, "%s: can't open %s: %s\n", Pn,
Memory ? Memory : KMEM, strerror(errno));
Exit(1);
}
#if defined(WILLDROPGID)
/*
* Drop setgid permission, if necessary.
*/
if (!Memory)
(void) dropgid();
#else /* !defined(WILLDROPGID) */
/*
* See if the name list file is readable. Build Nl.
*/
if (Nmlst && !is_readable(Nmlst, 1))
Exit(1);
#endif /* defined(WILLDROPGID) */
(void) build_Nl(Drive_Nl);
#if defined(HAS_AFS)
if (!Nmlst) {
/*
* If AFS is defined and we're getting kernel symbol values from
* from N_UNIX, make a copy of Nl[] for possible use with the AFS
* module name list file.
*/
if (!(nl = (struct nlist *)malloc(Nll))) {
(void) fprintf(stderr,
"%s: no space (%d) for Nl[] copy\n", Pn, Nll);
Exit(1);
}
(void) bcopy((char *)Nl, (char *)nl, Nll);
}
#endif /* defined(HAS_AFS) */
/*
* Access the name list file.
*/
if (nlist(Nmlst ? Nmlst : VMUNIX, Nl) < 0) {
(void) fprintf(stderr, "%s: can't read namelist from %s\n",
Pn, Nmlst ? Nmlst : VMUNIX);
Exit(1);
}
if (get_Nl_value("aproc", Drive_Nl, &lv) < 0 || !lv) {
(void) fprintf(stderr, "%s: can't get proc table address\n",
Pn);
Exit(1);
}
#if defined(HAS_AFS)
if (nl) {
/*
* If AFS is defined and we're getting kernel symbol values from
* N_UNIX, and if any X_AFS_* symbols isn't there, see if it is in the
* the AFS module name list file. Make sure that other symbols that
* appear in both name list files have the same values.
*/
if (get_Nl_value("arFID", Drive_Nl, &v[0]) >= 0
&& get_Nl_value("avol", Drive_Nl, &v[1]) >= 0
&& get_Nl_value("avol", Drive_Nl, &v[2]) >= 0
&& (!vo[0] || !v[1] || !v[2]))
(void) ckAFSsym(nl);
(void) free((MALLOC_P *)nl);
}
#endif /* defined(HAS_AFS) */
/*
* Establish a maximum process count estimate.
*/
if (get_Nl_value("mxproc", Drive_Nl, &lv) < 0
|| kread((KA_T)lv, (char *)&Mxp, sizeof(Mxp))
|| Mxp < 1)
Mxp = PROCDFLT;
}
/*
* initialize() - perform all initialization
*/
void
initialize()
{
get_kernel_access();
}
/*
* kread() - read from kernel memory
*/
int
kread(addr, buf, len)
KA_T addr; /* kernel memory address */
char *buf; /* buffer to receive data */
READLEN_T len; /* length to read */
{
int br;
if (lseek(Kd, (off_t)addr, L_SET) == (off_t)-1L)
return(-1);
br = read(Kd, buf, len);
return((br == len) ? 0 : 1);
}
/*
* process_map() - process vm map for vnode references
*/
static void
process_map(map)
caddr_t map;
{
int i, j, n, ne;
#if STEPV<40
/*
* Structures for NeXTSTEP and OPENSTEP < 4.0.
*/
struct vm_map_entry { /* (Should come from <vm/vm_map.h>). */
struct vm_map_entry *prev;
struct vm_map_entry *next;
unsigned int start;
unsigned int end;
caddr_t object;
unsigned int offset;
unsigned int
is_a_map:1,
is_sub_map:1,
copy_on_write:1,
needs_copy:1;
int protection;
int max_protection;
int inheritance;
int wired_count;
} vme, *vmep;
#define VME_NEXT(entry) entry.next
struct vm_map { /* (Should come from <vm/vm_map.h>.) */
caddr_t d1[SIMPLE_LOCK_SIZE + 2];
struct vm_map_entry header;
int nentries;
caddr_t pmap;
unsigned int size;
boolean_t is_main_map;
} vmm;
struct vm_object { /* (Should come from <vm/vm_object.h>.) */
caddr_t d1[SIMPLE_LOCK_SIZE + 4];
unsigned int size;
short ref_count, resident_page_count;
caddr_t copy;
caddr_t pager;
int pager_request, pager_name;
unsigned int paging_offset;
caddr_t shadow;
} vmo, vmso;
#else /* STEPV>=40 */
/*
* Structures for OPENSTEP >= 4.0.
*/
struct vm_map_links { /* (Should come from <vm/vm_map.h>). */
struct vm_map_entry *prev;
struct vm_map_entry *next;
unsigned int start;
unsigned int end;
};
struct vm_map_entry { /* (Should come from <vm/vm_map.h>). */
struct vm_map_links links;
caddr_t object;
unsigned int offset;
unsigned int
is_shared:1,
is_sub_map:1,
in_transition:1,
needs_wakeup:1,
behavior:2,
needs_copy:1,
protection:3,
max_protection:3,
inheritance:2,
pad1:1,
alias:8;
unsigned short wired_count;
unsigned short user_wired_count;
} vme, *vmep;
#define VME_NEXT(entry) entry.links.next
struct vm_map_header { /* (Should come from <vm/vm_map.h>.) */
struct vm_map_links links;
int nentries;
int entries_pageable;
};
struct vm_map { /* (Should come from <vm/vm_map.h>.) */
caddr_t d1[SIMPLE_LOCK_SIZE + 2];
struct vm_map_header hdr;
caddr_t pmap;
unsigned int size;
boolean_t is_main_map; /* Darwin header has this as ref_count,
* but we'll take some liberties ... */
} vmm;
struct vm_object { /* (Should come from <vm/vm_object.h>.) */
caddr_t d1[SIMPLE_LOCK_SIZE + 4];
unsigned int size;
short ref_count, resident_page_count;
caddr_t copy;
caddr_t shadow;
unsigned int shadow_offset;
caddr_t pager;
unsigned int paging_offset;
int pager_request;
} vmo, vmso;
#endif /* STEPV<40 */
struct vstruct { /* (Should come from <vm/vnode_pager.h>.) */
boolean_t is_device;
caddr_t vs_pf;
caddr_t pfMapEntry;
unsigned int vs_swapfile:1;
short vs_count;
int vs_size;
caddr_t vs_vp;
} vmp;
/*
* Read the vm map.
*/
if (!map
|| kread((KA_T)map, (char *)&vmm, sizeof(vmm)))
return;
if (!vmm.is_main_map)
return;
/*
* Look for non-map and non-sub-map vm map entries that have an object
* with a shadow whose pager pointer addresses a non-swap-file istruct
* that has a vnode pointer. Process the unique vnodes found.
*/
#if STEPV<40
vme = vmm.header;
ne = vmm.nentries;
#else /* STEPV>=40 */
if (!vmm.hdr.links.next
|| kread((KA_T)vmm.hdr.links.next, (char *)&vme, sizeof(vme)))
return;
ne = vmm.hdr.nentries;
#endif /* STEPV<40 */
if (ne > 1000)
ne = 1000;
for (i = n = 0; i < ne; i++) {
if (i) {
if (!VME_NEXT(vme)
|| kread((KA_T)VME_NEXT(vme), (char *)&vme, sizeof(vme)))
continue;
}
#if STEPV<40
if (vme.is_a_map || vme.is_sub_map)
#else /* STEPV>=40 */
if (vme.is_sub_map)
#endif /* STEPV<40 */
continue;
if (!vme.object
|| kread((KA_T)vme.object, (char *)&vmo, sizeof(vmo)))
continue;
if (!vmo.shadow
|| kread((KA_T)vmo.shadow, (char *)&vmso, sizeof(vmso)))
continue;
if (!vmso.pager
|| kread((KA_T)vmso.pager, (char *)&vmp, sizeof(vmp)))
continue;
if (vmp.is_device || vmp.vs_swapfile || !vmp.vs_vp)
continue;
/*
* See if the vnode has been processed before.
*/
for (j = 0; j < n; j++) {
if ((KA_T)vmp.vs_vp == Vp[j])
break;
}
if (j < n)
continue;
/*
* Process a new vnode.
*/
alloc_lfile("txt", -1);
FILEPTR = (struct file *)NULL;
process_node((KA_T)vmp.vs_vp);
if (Lf->sf)
link_lfile();
/*
* Allocate space for remembering the vnode.
*/
if (!Vp) {
if (!(Vp = (KA_T *)malloc((MALLOC_S)
(sizeof(struct vnode *) * 10))))
{
(void) fprintf(stderr, "%s: no txt ptr space, PID %d\n",
Pn, Lp->pid);
Exit(1);
}
Nv = 10;
} else if (n >= Nv) {
Nv += 10;
if (!(Vp = (KA_T *)realloc((MALLOC_P *)Vp,
(MALLOC_S)(Nv * sizeof(struct vnode *)))))
{
(void) fprintf(stderr,
"%s: no more txt ptr space, PID %d\n", Pn, Lp->pid);
Exit(1);
}
}
Vp[n++] = (KA_T)vmp.vs_vp;
}
}
/*
* read_proc() - read proc structures
*/
static void
read_proc()
{
static KA_T apav = (KA_T)0;
static int apax = -1;
int i, try;
static int sz = 0;
KA_T kp;
struct proc *p;
/*
* Try PROCTRYLM times to read a valid proc table.
*/
for (try = 0; try < PROCTRYLM; try++) {
/*
* Read kernel's process list pointer. This needs to be done each
* time lsof rereads the process list.
*/
if (apax < 0) {
if ((apax = get_Nl_value("aproc", Drive_Nl, &apav)) < 0) {
(void) fprintf(stderr,
"%s: can't get process table address pointer\n", Pn);
Exit(1);
}
}
if (kread((KA_T)apav, (char *)&Kp, sizeof(Kp))) {
if (!Fwarn)
(void) fprintf(stderr,
"%s: WARNING: can't read %s from %#x\n",
Pn, Nl[apax].n_un.n_name, apav);
continue;
}
/*
* Pre-allocate proc structure space.
*/
if (sz == 0) {
sz = Mxp;
if (!(P = (struct proc *)malloc((MALLOC_S)
(sz * sizeof(struct proc)))))
{
(void) fprintf(stderr, "%s: no proc table space\n",
Pn);
Exit(1);
}
if (!(Pa = (KA_T *)malloc((MALLOC_S)(sz * sizeof(KA_T)))))
{
(void) fprintf(stderr, "%s: no proc pointer space\n",
Pn);
Exit(1);
}
}
/*
* Accumulate proc structures.
*/
for (kp = Kp, Np = 0; kp; ) {
if (kread(kp, (char *)&P[Np], sizeof(struct proc))) {
Np = 0;
break;
}
Pa[Np] = kp;
kp = (KA_T)P[Np].p_nxt;
if (P[Np].p_stat == 0 || P[Np].p_stat == SZOMB)
continue;
Np++;
if (Np >= sz) {
/*
* Expand the local proc table.
*/
sz += PROCDFLT/2;
if (!(P = (struct proc *)realloc((MALLOC_P *)P,
(MALLOC_S)(sizeof(struct proc) * sz))))
{
(void) fprintf(stderr,
"%s: no more (%d) proc space\n",
Pn, sz);
Exit(1);
}
if (!(Pa = (KA_T *)realloc((MALLOC_P *)Pa,
(MALLOC_S)(sizeof(KA_T) * sz))))
{
(void) fprintf(stderr,
"%s: no more (%d) proc ptr space\n",
Pn, sz);
Exit(1);
}
}
}
/*
* If not enough processes were saved in the local table, try again.
*/
if (Np >= PROCMIN)
break;
}
/*
* Quit if no proc structures were stored in the local table.
*/
if (try >= PROCTRYLM) {
(void) fprintf(stderr, "%s: can't read proc table\n", Pn);
Exit(1);
}
if (Np < sz && !RptTm) {
/*
* Reduce the local proc structure table size to a minimum if
* not in repeat mode.
*/
if (!(P = (struct proc *)realloc((MALLOC_P *)P,
(MALLOC_S)(sizeof(struct proc) * Np))))
{
(void) fprintf(stderr,
"%s: can't reduce proc table to %d\n",
Pn, Np);
Exit(1);
}
if (!(Pa = (KA_T *)realloc((MALLOC_P *)Pa,
(MALLOC_S)(sizeof(KA_T) * Np))))
{
(void) fprintf(stderr,
"%s: can't reduce proc ptrs to %d\n",
Pn, Np);
Exit(1);
}
}
}