/* * dproc.c - Darwin process access functions for /dev/kmem-based 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.8 2005/11/01 20:24:51 abe Exp $"; #endif #include "lsof.h" #include #include #include #include /* * Local definitions */ #define NPHASH 1024 /* Phash bucket count -- * MUST BE A POWER OF 2!!! */ #define PHASH(a) (((int)((a * 31415) >> 3)) & (NPHASH - 1)) #define PINCRSZ 256 /* Proc[] size inrement */ /* * Local structures */ struct phash { KA_T ka; /* kernel proc struct address */ struct proc *la; /* local proc struct address */ struct phash *next; /* next phash entry */ }; /* * Local function prototypes */ _PROTOTYPE(static pid_t get_parent_pid,(KA_T kpa)); _PROTOTYPE(static int read_procs,()); _PROTOTYPE(static void process_map,(pid_t pid)); _PROTOTYPE(static void enter_vn_text,(KA_T va, int *n)); #if DARWINV>=700 _PROTOTYPE(static char *getcmdnm,(pid_t pid)); #endif /* DARWINV>=700 */ _PROTOTYPE(static void get_kernel_access,(void)); /* * Local static values */ static KA_T Akp = (KA_T)NULL; /* kernel allproc chain address */ static int Np = 0; /* PA[] and Proc[] entry count */ static int Npa = 0; /* Proc[] structure allocation count */ static MALLOC_S Nv = 0; /* allocated Vp[] entries */ static KA_T *Pa = (KA_T *)NULL; /* Proc[] addresses */ struct phash **Phash = (struct phash **)NULL; /* kernel proc address hash pointers */ static struct proc *Proc = (struct proc *)NULL; /* local copy of prc struct chain */ static KA_T *Vp = NULL; /* vnode address cache */ /* * enter_vn_text() - enter a vnode text reference */ static void enter_vn_text(va, n) KA_T va; /* vnode address */ int *n; /* Vp[] entries in use */ { int i; /* * Ignore the request if the vnode has already been entered. */ for (i = 0; i < *n; i++) { if (va == Vp[i]) return; } /* * Save the text file information. */ alloc_lfile(" txt", -1); Cfp = (struct file *)NULL; process_node(va); if (Lf->sf) link_lfile(); if (i >= Nv) { /* * Allocate space for remembering the vnode. */ Nv += 10; if (!Vp) Vp=(KA_T *)malloc((MALLOC_S)(sizeof(struct vnode *)*10)); else Vp=(KA_T *)realloc((MALLOC_P *)Vp,(MALLOC_S)(Nv*sizeof(KA_T))); if (!Vp) { (void) fprintf(stderr, "%s: no txt ptr space, PID %d\n", Pn, Lp->pid); Exit(1); } } /* * Remember the vnode. */ Vp[*n] = va; (*n)++; } /* * gather_proc_info() -- gather process information */ void gather_proc_info() { char *cmd; struct filedesc fd; int i, nf; MALLOC_S nb; static struct file **ofb = NULL; static int ofbb = 0; struct proc *p; int pgid; int ppid = 0; static char *pof = (char *)NULL; static int pofb = 0; short pss, sf; int px; uid_t uid; #if DARWINV<800 struct pcred pc; #else /* DARWINV>=800 */ struct ucred uc; #endif /* DARWINV<800 */ /* * Read the process table. */ if (read_procs()) { (void) fprintf(stderr, "%s: can't read process table\n", Pn); Exit(1); } /* * Examine proc structures and their associated information. */ for (p = Proc, px = 0; px < Np; p++, px++) { #if DARWINV<800 if (!p->p_cred || kread((KA_T)p->p_cred, (char *)&pc, sizeof(pc))) continue; pgid = pc.p_rgid; uid = pc.p_ruid; #else /* DARWINV>=800 */ if (!p->p_ucred || kread((KA_T)p->p_ucred, (char *)&uc, sizeof(uc))) continue; pgid = uc.cr_rgid; uid = uc.cr_uid; #endif /* DARWINV<800 */ #if defined(HASPPID) ppid = get_parent_pid((KA_T)p->p_pptr); #endif /* defined(HASPPID) */ /* * Get the command name. */ #if DARWINV<700 cmd = p->P_COMM; #else /* DARWINV>=700 */ if (!strcmp(p->p_comm, "LaunchCFMApp")) { if (!(cmd = getcmdnm(p->p_pid))) cmd = p->p_comm; } else cmd = p->p_comm; #endif /* DARWINV<700 */ /* * See if process is excluded. * * Read file structure pointers. */ if (is_proc_excl(p->p_pid, pgid, (UID_ARG)uid, &pss, &sf)) continue; if (!p->p_fd || kread((KA_T)p->p_fd, (char *)&fd, sizeof(fd))) continue; if (!fd.fd_refcnt || fd.fd_lastfile > fd.fd_nfiles) continue; /* * Allocate a local process structure. * * Set kernel's proc structure address. */ if (is_cmd_excl(cmd, &pss, &sf)) continue; alloc_lproc(p->p_pid, pgid, ppid, (UID_ARG)uid, cmd, (int)pss, (int)sf); Plf = (struct lfile *)NULL; Kpa = Pa[px]; /* * Save current working directory information. */ if (fd.fd_cdir) { alloc_lfile(CWD, -1); Cfp = (struct file *)NULL; process_node((KA_T)fd.fd_cdir); if (Lf->sf) link_lfile(); } /* * Save root directory information. */ if (fd.fd_rdir) { alloc_lfile(RTD, -1); Cfp = (struct file *)NULL; process_node((KA_T)fd.fd_rdir); if (Lf->sf) link_lfile(); } /* * Process the VM map. */ process_map(p->p_pid); /* * Read open file structure pointers. */ if (!fd.fd_ofiles || (nf = fd.fd_nfiles) <= 0) continue; nb = (MALLOC_S)(sizeof(struct file *) * nf); if (nb > ofbb) { if (!ofb) ofb = (struct file **)malloc(nb); else ofb = (struct file **)realloc((MALLOC_P *)ofb, nb); if (!ofb) { (void) fprintf(stderr, "%s: PID %d, no file * space\n", Pn, p->p_pid); Exit(1); } ofbb = nb; } if (kread((KA_T)fd.fd_ofiles, (char *)ofb, nb)) continue; 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: PID %d, no file flag space\n", Pn, p->p_pid); Exit(1); } pofb = nb; } if (!fd.fd_ofileflags || kread((KA_T)fd.fd_ofileflags, pof, nb)) zeromem(pof, nb); /* * Save information on file descriptors. */ for (i = 0; i < nf; i++) { if (ofb[i] && !(pof[i] & UF_RESERVED)) { alloc_lfile(NULL, i); process_file((KA_T)(Cfp = ofb[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; } } #if DARWINV>=700 static char * getcmdnm(pid) pid_t pid; /* process ID */ { static int am; static char *ap = (char *)NULL; char *cp, *ep, *sp; int mib[3]; size_t sz; if (!ap) { /* * Allocate space for the maximum argument size. */ mib[0] = CTL_KERN; mib[1] = KERN_ARGMAX; sz = sizeof(am); if (sysctl(mib, 2, &am, &sz, NULL, 0) == -1) { (void) fprintf(stderr, "%s: can't get arg max, PID %d\n", Pn, pid); Exit(1); } if (!(ap = (char *)malloc((MALLOC_S)am))) { (void) fprintf(stderr, "%s: no arg ptr (%d) space, PID %d\n", Pn, am, pid); Exit(1); } } /* * Get the arguments for the process. */ mib[0] = CTL_KERN; mib[1] = KERN_PROCARGS; mib[2] = pid; sz = (size_t)am; if (sysctl(mib, 3, ap, &sz, NULL, 0) == -1) return((char *)NULL); /* * Skip to the first NUL character, which should end the saved exec path. */ for (cp = ap; *cp && (cp < (ap + sz)); cp++) { ; } if (cp >= (ap + sz)) return((char *)NULL); /* * Skip trailing NULs, which should find the beginning of the command. */ while (!*cp && (cp < (ap + sz))) { cp++; } if (cp >= (ap + sz)) return((char *)NULL); /* * Make sure that the command is NUL-terminated. */ for (sp = cp; *cp && (cp < (ap + sz)); cp++) { ; } if (cp >= (ap + sz)) return((char *)NULL); ep = cp; /* * Locate the start of the command's base name and return it. */ for (ep = cp, cp--; cp >= sp; cp--) { if (*cp == '/') { return(cp + 1); } } return(sp); } #endif /* DARWINV>=700 */ /* * get_kernel_access() - get access to kernel memory */ static void get_kernel_access() { /* * Check kernel version. */ (void) ckkv("Darwin", LSOF_VSTR, (char *)NULL, (char *)NULL); /* * Set name list file path. */ if (!Nmlst) Nmlst = N_UNIX; #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 and the name list files are readable. */ if ((Memory && !is_readable(Memory, 1)) || (Nmlst && !is_readable(Nmlst, 1))) Exit(1); #endif /* defined(WILLDROPGID) */ /* * Open kernel memory access. */ if ((Kd = open(Memory ? Memory : KMEM, O_RDONLY, 0)) < 0) { (void) fprintf(stderr, "%s: open(%s): %s\n", Pn, Memory ? Memory : KMEM, strerror(errno)); Exit(1); } (void) build_Nl(Drive_Nl); if (nlist(Nmlst, Nl) < 0) { (void) fprintf(stderr, "%s: can't read namelist from %s\n", Pn, Nmlst); Exit(1); } #if defined(WILLDROPGID) /* * Drop setgid permission, if necessary. */ if (!Memory) (void) dropgid(); #endif /* defined(WILLDROPGID) */ } /* * get_parent_pid() - get parent process PID */ static pid_t get_parent_pid(kpa) KA_T kpa; /* kernel parent process address */ { struct phash *ph; if (kpa) { for (ph = Phash[PHASH(kpa)]; ph; ph = ph->next) { if (ph->ka == kpa) return((pid_t)ph->la->p_pid); } } return((pid_t)0); } /* * 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 ((off_t)addr & (off_t)0x3) { /* * No read is possible if the address is not aligned on a word * boundary. */ return(1); } if (lseek(Kd, (off_t)addr, SEEK_SET) == (off_t)-1) return(1); br = read(Kd, buf, len); return((br == len) ? 0 : 1); } /* * prcess_map() - process VM map */ static void process_map(pid) pid_t pid; /* process id */ { vm_address_t address = 0; mach_msg_type_number_t count; vm_region_extended_info_data_t e_info; int n = 0; mach_port_t object_name; vm_size_t size = 0; vm_map_t task; vm_region_top_info_data_t t_info; struct vm_object { /* should come from */ #if DARWINV<800 KA_T Dummy1[15]; #else /* DARWINV>=800 */ KA_T Dummy1[14]; #endif /* DARWINV>=800 */ memory_object_t pager; } vmo; struct vnode_pager { /* from */ KA_T Dummy1[4]; struct vnode *vnode; } vp; /* * Get the task port associated with the process */ if (task_for_pid((mach_port_name_t)mach_task_self(), pid, (mach_port_name_t *)&task) != KERN_SUCCESS) { return; } /* * Go through the task's address space, looking for blocks of memory * backed by an external pager (i.e, a "vnode") */ for (address = 0;; address += size) { count = VM_REGION_EXTENDED_INFO_COUNT; if (vm_region(task, &address, &size, VM_REGION_EXTENDED_INFO, (vm_region_info_t)&e_info, &count, &object_name) != KERN_SUCCESS) { break; } if (!e_info.external_pager) continue; count = VM_REGION_TOP_INFO_COUNT; if (vm_region(task, &address, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&t_info, &count, &object_name) != KERN_SUCCESS) { break; } /* * The returned "obj_id" is the "vm_object_t" address. */ if (!t_info.obj_id) continue; if (kread(t_info.obj_id, (char *)&vmo, sizeof(vmo))) break; /* * If the "pager" is backed by a vnode then the "vm_object_t" * "memory_object_t" address is actually a "struct vnode_pager *". */ if (!vmo.pager) continue; if (kread((KA_T)vmo.pager, (char *)&vp, sizeof(vp))) break; (void) enter_vn_text((KA_T)vp.vnode, &n); } return; } /* * read_procs() - read proc structures */ static int read_procs() { int h, i, np, pe; KA_T kp, kpn; MALLOC_S msz; struct proc *p; struct phash *ph, *phn; if (!Akp) { /* * Get kernel allproc structure pointer once. */ if (get_Nl_value("aproc", Drive_Nl, &Akp) < 0 || !Akp) { (void) fprintf(stderr, "%s: can't get proc table address\n", Pn); Exit(1); } } /* * Get the current number of processes and calculate PA and Proc[] allocation * sizes large enough to handle it. */ if (get_Nl_value("nproc", Drive_Nl, &kp) < 0 || !kp) { (void) fprintf(stderr, "%s: can't get nproc address\n", Pn); Exit(1); } if (kread(kp, (char *)&np, sizeof(np))) { (void) fprintf(stderr, "%s: can't read process count from %s\n", Pn, print_kptr(kp, (char *)NULL, 0)); Exit(1); } for (np += np, pe = PINCRSZ; pe < np; pe += PINCRSZ) ; /* * Allocate or reallocate the Pa[] and Proc[] tables. */ msz = (MALLOC_S)(pe * sizeof(struct proc)); if (!Proc) Proc = (struct proc *)malloc(msz); else if (pe > Npa) Proc = (struct proc *)realloc((MALLOC_P *)Proc, msz); if (!Proc) { (void) fprintf(stderr, "%s: no space for proc table\n", Pn); Exit(1); } msz = (MALLOC_S)(pe * sizeof(KA_T)); if (!Pa) Pa = (KA_T *)malloc(msz); else if (pe > Npa) Pa = (KA_T *)realloc((MALLOC_P *)Pa, msz); if (!Pa) { (void) fprintf(stderr, "%s: no space for proc addr table\n", Pn); Exit(1); } Npa = pe; /* * Allocate or reset the Phash[] table. */ if (!Phash) { Phash = (struct phash **)calloc(NPHASH, sizeof(struct phash *)); } else { for (h = 0; h < NPHASH; h++) { for (ph = Phash[h]; ph; ph = phn) { phn = ph->next; (void) free((MALLOC_P *)ph); } Phash[h] = (struct phash *)NULL; } } if (!Phash) { (void) fprintf(stderr, "%s: no space for proc address hash\n", Pn); Exit(1); } /* * Read the proc structures on the kernel's chain. */ for (i = Np = 0, kp = Akp, p = Proc, pe += pe; kp && i < pe; i++, kp = kpn) { if (kread(kp, (char *)p, sizeof(struct proc))) break; kpn = (KA_T)(((KA_T)p->p_list.le_next == Akp) ? NULL : p->p_list.le_next); if (p->p_stat == 0 || p->p_stat == SZOMB) continue; /* * Cache the proc structure's addresses. */ h = PHASH(kp); if (!(ph = (struct phash *)malloc((MALLOC_S)sizeof(struct phash)))) { (void) fprintf(stderr, "%s: no space for phash struct\n", Pn); Exit(1); } ph->ka = kp; ph->la = p; ph->next = Phash[h]; Phash[h] = ph; p++; Pa[Np++] = kp; if (Np >= Npa) { /* * Enlarge Pa[] and Proc[]. */ msz = (int)((Npa + PINCRSZ) * sizeof(struct proc)); if (!(Proc = (struct proc *)realloc((MALLOC_P *)Proc, msz))) { (void) fprintf(stderr, "%s: no additional proc space\n", Pn); Exit(1); } msz = (int)((Npa + PINCRSZ) * sizeof(KA_T)); if (!(Pa = (KA_T *)realloc((MALLOC_P *)Pa, msz))) { (void) fprintf(stderr, "%s: no additional proc addr space\n", Pn); Exit(1); } Npa += PINCRSZ; } } /* * If too many processes were read, the chain following probably failed; * report that and exit. */ if (i >= pe) { (void) fprintf(stderr, "%s: can't follow kernel proc chain\n", Pn); Exit(1); } /* * If not in repeat mode, reduce Pa[] and Proc[] to their minimums. */ if (Np < Npa && !RptTm) { msz = (MALLOC_S)(Np * sizeof(struct proc)); if (!(Proc = (struct proc *)realloc((MALLOC_P *)Proc, msz))) { (void) fprintf(stderr, "%s: can't reduce proc table\n", Pn); Exit(1); } msz = (MALLOC_S)(Np * sizeof(KA_T)); if (!(Pa = (KA_T *)realloc((MALLOC_P *)Pa, msz))) { (void) fprintf(stderr, "%s: can't reduce proc addr table\n", Pn); Exit(1); } Npa = Np; } /* * Return 0 if any processes were loaded; 1 if none were. */ return((Np > 0) ? 0 : 1); }