/* * 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 .) */ 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 ). */ 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 .) */ 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 .) */ 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 ). */ struct vm_map_entry *prev; struct vm_map_entry *next; unsigned int start; unsigned int end; }; struct vm_map_entry { /* (Should come from ). */ 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 .) */ struct vm_map_links links; int nentries; int entries_pageable; }; struct vm_map { /* (Should come from .) */ 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 .) */ 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 .) */ 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); } } }