blob: 8e66bad0b4c50406f17b06626a9961298f490f1e [file] [log] [blame]
/*
* dproc.c -- Darwin process access functions for libproc-based lsof
*/
/*
* Portions Copyright 2005-2007 Apple Inc. All rights reserved.
*
* Copyright 2005 Purdue Research Foundation, West Lafayette, Indiana
* 47907. All rights reserved.
*
* Written by Allan Nathanson, Apple Inc., and Victor A. Abell, Purdue
* University.
*
* 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 Apple Inc. 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, Apple
* Inc. and Purdue University must appear in documentation and sources.
* 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 2005-2007 Apple Inc. and Purdue Research Foundation.\nAll rights reserved.\n";
static char *rcsid = "$Id: dproc.c,v 1.7 2011/08/07 22:52:30 abe Exp $";
#endif
#include "lsof.h"
/*
* Local definitions
*/
#define PIDS_INCR (sizeof(int) * 32) /* PID space increment */
#define VIPS_INCR 16 /* Vips space increment */
#if DARWINV>=900
#define THREADS_INCR (sizeof(uint64_t) * 32) /* Threads space increment */
#endif /* DARWINV>=900 */
/*
* Local static variables
*/
static struct proc_fdinfo *Fds = (struct proc_fdinfo *)NULL;
/* FD buffer */
static int NbPids = 0; /* bytes allocated to Pids */
static int NbFds = 0; /* bytes allocated to FDs */
static int *Pids = (int *)NULL; /* PID buffer */
#if DARWINV>=900
static int NbThreads = 0; /* Threads bytes allocated */
static uint64_t *Threads = (uint64_t *)NULL; /* Thread buffer */
#endif /* DARWINV>=900 */
/*
* Local structure definitions
*/
static struct vips_info {
dev_t dev;
ino_t ino;
} *Vips = (struct vips_info *)NULL; /* recorded vnodes */
static int NbVips = 0; /* bytes allocated to Vips */
static int NVips = 0; /* entries allocated to Vips */
/*
* Local function prototypes
*/
_PROTOTYPE(static void enter_vn_text,(struct vnode_info_path *vip, int *n));
_PROTOTYPE(static void process_fds,(int pid, uint32_t n, int ckscko));
_PROTOTYPE(static void process_text,(int pid));
#if DARWINV>=900
_PROTOTYPE(static void process_threads,(int pid, uint32_t n));
#endif /* DARWINV>=900 */
/*
* enter_vn_text() -- enter vnode information text reference
*/
static void
enter_vn_text(vip, n)
struct vnode_info_path *vip; /* vnode info */
int *n; /* number of vips[] entries in use */
{
int i;
/*
* Ignore the request if the vnode information has already been entered.
*/
for (i = 0; i < *n; i++) {
if ((vip->vip_vi.vi_stat.vst_dev == Vips[i].dev)
&& (vip->vip_vi.vi_stat.vst_ino == Vips[i].ino))
{
return;
}
}
/*
* Save the text file information.
*/
alloc_lfile(" txt", -1);
Cfp = (struct file *)NULL;
(void) enter_vnode_info(vip);
if (Lf->sf)
link_lfile();
/*
* Record the entry of the vnode information.
*/
if (i >= NVips) {
/*
* Allocate space for recording the vnode information.
*/
NVips += VIPS_INCR;
NbVips += (int)(VIPS_INCR * sizeof(struct vips_info));
if (!Vips)
Vips = (struct vips_info *)malloc((MALLOC_S)NbVips);
else
Vips = (struct vips_info *)realloc((MALLOC_P *)Vips,
(MALLOC_S)NbVips);
if (!Vips) {
(void) fprintf(stderr, "%s: PID %d: no text recording space\n",
Pn, Lp->pid);
Exit(1);
}
}
/*
* Record the vnode information.
*/
Vips[*n].dev = vip->vip_vi.vi_stat.vst_dev;
Vips[*n].ino = vip->vip_vi.vi_stat.vst_ino;
(*n)++;
}
/*
* gather_proc_info() -- gather process information
*/
void
gather_proc_info()
{
short cckreg; /* conditional status of regular file
* checking:
* 0 = unconditionally check
* 1 = conditionally check */
short ckscko; /* socket file only checking status:
* 0 = none
* 1 = check only socket files,
* including TCP and UDP
* streams with eXPORT data,
* where supported */
int cre, cres, ef, i, nb, np, pid;
short pss, sf;
struct proc_taskallinfo tai;
struct proc_vnodepathinfo vpi;
/*
* Define socket and regular file conditional processing flags.
*
* If only socket files have been selected, or socket files have been
* selected, ANDed with other selection options, enable the skipping of
* regular files.
*
* If socket files and some process options have been selected, enable
* conditional skipping of regular file; i.e., regular files will be skipped
* unless they belong to a process selected by one of the specified options.
*/
if (Selflags & SELNW) {
/*
* Some network files selection options have been specified.
*/
if (Fand || !(Selflags & ~SELNW)) {
/*
* Selection ANDing or only network file options have been
* specified, so set unconditional skipping of regular files
* and socket file only checking.
*/
cckreg = 0;
ckscko = 1;
} else {
/*
* If ORed file selection options have been specified, or no
* ORed process selection options have been specified, enable
* unconditional file checking and clear socket file only
* checking.
*
* If only ORed process selection options have been specified,
* enable conditional file skipping and socket file only checking.
*/
if ((Selflags & SELFILE) || !(Selflags & SELPROC))
cckreg = ckscko = 0;
else
cckreg = ckscko = 1;
}
} else {
/*
* No network file selection options were specified. Enable
* unconditional file checking and clear socket file only checking.
*/
cckreg = ckscko = 0;
}
/*
* Determine how many bytes are needed to contain the PIDs on the system;
* make sure sufficient buffer space is allocated to hold them (and a few
* extra); then read the list of PIDs.
*/
if ((nb = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0)) <= 0) {
(void) fprintf(stderr, "%s: can't get PID byte count: %s\n",
Pn, strerror(errno));
Exit(1);
}
if (nb > NbPids) {
while (nb > NbPids) {
NbPids += PIDS_INCR;
}
if (!Pids)
Pids = (int *)malloc((MALLOC_S)NbPids);
else
Pids = (int *)realloc((MALLOC_P *)Pids, (MALLOC_S)NbPids);
if (!Pids) {
(void) fprintf(stderr,
"%s: can't allocate space for %d PIDs\n", Pn,
(int)(NbPids / sizeof(int *)));
Exit(1);
}
}
/*
* Get the list of PIDs.
*/
for (ef = 0; !ef;) {
if ((nb = proc_listpids(PROC_ALL_PIDS, 0, Pids, NbPids)) <= 0) {
(void) fprintf(stderr, "%s: can't get list of PIDs: %s\n",
Pn, strerror(errno));
Exit(1);
}
if ((nb + sizeof(int)) < NbPids) {
/*
* There is room in the buffer for at least one more PID.
*/
np = nb / sizeof(int);
ef = 1;
} else {
/*
* The PID buffer must be enlarged.
*/
NbPids += PIDS_INCR;
Pids = (int *)realloc((MALLOC_P *)Pids, (MALLOC_S)NbPids);
if (!Pids) {
(void) fprintf(stderr,
"%s: can't allocate space for %d PIDs\n", Pn,
(int)(NbPids / sizeof(int *)));
Exit(1);
}
}
}
/*
* Loop through the identified processes.
*/
for (i = 0; i < np; i++) {
if (!(pid = Pids[i]))
continue;
nb = proc_pidinfo(pid, PROC_PIDTASKALLINFO, 0, &tai, sizeof(tai));
if (nb <= 0) {
if ((errno == EPERM) || (errno == ESRCH))
continue;
if (!Fwarn) {
(void) fprintf(stderr, "%s: PID %d information error: %s\n",
Pn, pid, strerror(errno));
}
continue;
} else if (nb < sizeof(tai)) {
(void) fprintf(stderr,
"%s: PID %d: proc_pidinfo(PROC_PIDTASKALLINFO);\n",
Pn, pid);
(void) fprintf(stderr,
" too few bytes; expected %ld, got %d\n",
sizeof(tai), nb);
Exit(1);
}
/*
* Check for process or command exclusion.
*/
if (is_proc_excl((int)pid, (int)tai.pbsd.pbi_rgid,
(UID_ARG)tai.pbsd.pbi_uid, &pss, &sf))
{
continue;
}
tai.pbsd.pbi_comm[sizeof(tai.pbsd.pbi_comm) - 1] = '\0';
if (is_cmd_excl(tai.pbsd.pbi_comm, &pss, &sf))
continue;
if (tai.pbsd.pbi_name[0]) {
tai.pbsd.pbi_name[sizeof(tai.pbsd.pbi_name) - 1] = '\0';
if (is_cmd_excl(tai.pbsd.pbi_name, &pss, &sf))
continue;
}
if (cckreg) {
/*
* If conditional checking of regular files is enabled, enable
* socket file only checking, based on the process' selection
* status.
*/
ckscko = (sf & SELPROC) ? 0 : 1;
}
/*
* Get root and current directory information.
*/
if (!ckscko) {
nb = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi,
sizeof(vpi));
if (nb <= 0) {
cre = errno;
cres = 1;
} else if (nb < sizeof(vpi)) {
(void) fprintf(stderr,
"%s: PID %d: proc_pidinfo(PROC_PIDVNODEPATHINFO);\n",
Pn, pid);
(void) fprintf(stderr,
" too few bytes; expected %ld, got %d\n",
sizeof(vpi), nb);
Exit(1);
} else
cres = 0;
}
/*
* Allocate local process space.
*/
alloc_lproc((int)pid, (int)tai.pbsd.pbi_rgid,
(int)tai.pbsd.pbi_ppid, (UID_ARG)tai.pbsd.pbi_uid,
(tai.pbsd.pbi_name[0] != '\0') ? tai.pbsd.pbi_name
: tai.pbsd.pbi_comm,
(int)pss, (int)sf);
Plf = (struct lfile *)NULL;
/*
* Save current working directory information.
*/
if (!ckscko) {
if (cres || vpi.pvi_cdir.vip_path[0]) {
alloc_lfile(CWD, -1);
Cfp = (struct file *)NULL;
if (cres) {
/*
* If the CWD|RTD information access error is ESRCH,
* ignore it; otherwise report the error's message in the
* CWD's NAME column.
*/
if (cre != ESRCH) {
(void) snpf(Namech, Namechl, "%s|%s info error: %s",
CWD + 1, RTD + 1, strerror(cre));
Namech[Namechl - 1] = '\0';
enter_nm(Namech);
if (Lf->sf)
link_lfile();
}
} else {
(void) enter_vnode_info(&vpi.pvi_cdir);
if (Lf->sf)
link_lfile();
}
}
}
/*
* Save root directory information.
*/
if (!ckscko) {
if (!cres && vpi.pvi_rdir.vip_path[0]) {
alloc_lfile(RTD, -1);
Cfp = (struct file *)NULL;
(void) enter_vnode_info(&vpi.pvi_rdir);
if (Lf->sf)
link_lfile();
}
}
#if DARWINV>=900
/*
* Check for per-thread current working directories
*/
if (!ckscko) {
if (tai.pbsd.pbi_flags & PROC_FLAG_THCWD) {
(void) process_threads(pid, tai.ptinfo.pti_threadnum);
}
}
#endif /* DARWINV>=900 */
/*
* Print text file information.
*/
if (!ckscko)
(void) process_text(pid);
/*
* Loop through the file descriptors.
*/
(void) process_fds(pid, tai.pbsd.pbi_nfiles, ckscko);
/*
* Examine results.
*/
if (examine_lproc())
return;
}
}
/*
* initialize() -- perform all initialization
*/
void
initialize()
{
}
/*
* process_fds() -- process file descriptors
*/
static void
process_fds(pid, n, ckscko)
int pid; /* PID of interest */
uint32_t n; /* max FDs */
int ckscko; /* check socket files only */
{
int i, isock, nb, nf;
struct proc_fdinfo *fdp;
/*
* Make sure an FD buffer has been allocated.
*/
if (!Fds) {
NbFds = sizeof(struct proc_fdinfo) * n;
Fds = (struct proc_fdinfo *)malloc((MALLOC_S)NbFds);
} else if (NbFds < sizeof(struct proc_fdinfo) * n) {
/*
* More proc_fdinfo space is required. Allocate it.
*/
NbFds = sizeof(struct proc_fdinfo) * n;
Fds = (struct proc_fdinfo *)realloc((MALLOC_P *)Fds,
(MALLOC_S)NbFds);
}
if (!Fds) {
(void) fprintf(stderr,
"%s: PID %d: can't allocate space for %d FDs\n",
Pn, pid, (int)(NbFds / sizeof(struct proc_fdinfo)));
Exit(1);
}
/*
* Get FD information for the process.
*/
nb = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, Fds, NbFds);
if (nb <= 0) {
if (errno == ESRCH) {
/*
* Quit if no FD information is available for the process.
*/
return;
}
/*
* Make a dummy file entry with an error message in its NAME column.
*/
alloc_lfile(" err", -1);
(void) snpf(Namech, Namechl, "FD info error: %s", strerror(errno));
Namech[Namechl - 1] = '\0';
enter_nm(Namech);
if (Lf->sf)
link_lfile();
return;
}
nf = (int)(nb / sizeof(struct proc_fdinfo));
/*
* Loop through the file descriptors.
*/
for (i = 0; i < nf; i++) {
fdp = &Fds[i];
alloc_lfile(NULL, (int)fdp->proc_fd);
/*
* Process the file by its type.
*/
isock = 0;
switch (fdp->proc_fdtype) {
case PROX_FDTYPE_ATALK:
if (!ckscko)
(void) process_atalk(pid, fdp->proc_fd);
break;
case PROX_FDTYPE_FSEVENTS:
if (!ckscko)
(void) process_fsevents(pid, fdp->proc_fd);
break;
case PROX_FDTYPE_KQUEUE:
if (!ckscko)
(void) process_kqueue(pid, fdp->proc_fd);
break;
case PROX_FDTYPE_PIPE:
if (!ckscko)
(void) process_pipe(pid, fdp->proc_fd);
break;
case PROX_FDTYPE_PSEM:
if (!ckscko)
(void) process_psem(pid, fdp->proc_fd);
break;
case PROX_FDTYPE_SOCKET:
(void) process_socket(pid, fdp->proc_fd);
isock = 1;
break;
case PROX_FDTYPE_PSHM:
(void) process_pshm(pid, fdp->proc_fd);
break;
case PROX_FDTYPE_VNODE:
(void) process_vnode(pid, fdp->proc_fd);
break;
default:
(void) snpf(Namech, Namechl - 1, "unknown file type: %d",
fdp->proc_fdtype);
Namech[Namechl - 1] = '\0';
(void) enter_nm(Namech);
break;
}
if (Lf->sf) {
if (!ckscko || isock)
link_lfile();
}
}
}
/*
* process_text() -- process text information
*/
static void
process_text(pid)
int pid; /* PID */
{
uint64_t a;
int i, n, nb;
struct proc_regionwithpathinfo rwpi;
for (a = (uint64_t)0, i = n = 0; i < 10000; i++) {
nb = proc_pidinfo(pid, PROC_PIDREGIONPATHINFO, a, &rwpi,
sizeof(rwpi));
if (nb <= 0) {
if ((errno == ESRCH) || (errno == EINVAL)) {
/*
* Quit if no more text information is available for the
* process.
*/
return;
}
/*
* Warn about all other errors via a NAME column message.
*/
alloc_lfile(" txt", -1);
Cfp = (struct file *)NULL;
(void) snpf(Namech, Namechl,
"region info error: %s", strerror(errno));
Namech[Namechl - 1] = '\0';
enter_nm(Namech);
if (Lf->sf)
link_lfile();
return;
} else if (nb < sizeof(rwpi)) {
(void) fprintf(stderr,
"%s: PID %d: proc_pidinfo(PROC_PIDREGIONPATHINFO);\n",
Pn, pid);
(void) fprintf(stderr,
" too few bytes; expected %ld, got %d\n",
sizeof(rwpi), nb);
Exit(1);
}
if (rwpi.prp_vip.vip_path[0])
enter_vn_text(&rwpi.prp_vip, &n);
a = rwpi.prp_prinfo.pri_address + rwpi.prp_prinfo.pri_size;
}
}
#if DARWINV>=900
/*
* process_threads() -- process thread information
*/
#define TWD " twd" /* per-thread current working directory
* fd name */
static void
process_threads(pid, n)
int pid; /* PID */
uint32_t n; /* number of threads */
{
int i, nb, nt;
/*
* Make sure a thread buffer has been allocated.
*/
n += 10;
if (n > NbThreads) {
while (n > NbThreads) {
NbThreads += THREADS_INCR;
}
if (!Threads)
Threads = (uint64_t *)malloc((MALLOC_S)NbThreads);
else
Threads = (uint64_t *)realloc((MALLOC_P *)Threads,
(MALLOC_S)NbThreads);
if (!Threads) {
(void) fprintf(stderr,
"%s: can't allocate space for %d Threads\n", Pn,
(int)(NbThreads / sizeof(int *)));
Exit(1);
}
}
/*
* Get thread information for the process.
*/
nb = proc_pidinfo(pid, PROC_PIDLISTTHREADS, 0, Threads, NbThreads);
if (nb <= 0) {
if (errno == ESRCH) {
/*
* Quit if no thread information is available for the
* process.
*/
return;
}
}
nt = (int)(nb / sizeof(uint64_t));
/*
* Loop through the threads.
*/
for (i = 0; i < nt; i++) {
uint64_t t;
struct proc_threadwithpathinfo tpi;
t = Threads[i];
nb = proc_pidinfo(pid, PROC_PIDTHREADPATHINFO, t, &tpi,
sizeof(tpi));
if (nb <= 0) {
if ((errno == ESRCH) || (errno == EINVAL)) {
/*
* Quit if no more thread information is available for the
* process.
*/
return;
}
/*
* Warn about all other errors via a NAME column message.
*/
alloc_lfile(TWD, -1);
Cfp = (struct file *)NULL;
(void) snpf(Namech, Namechl,
"thread info error: %s", strerror(errno));
Namech[Namechl - 1] = '\0';
enter_nm(Namech);
if (Lf->sf)
link_lfile();
return;
} else if (nb < sizeof(tpi)) {
(void) fprintf(stderr,
"%s: PID %d: proc_pidinfo(PROC_PIDTHREADPATHINFO);\n",
Pn, pid);
(void) fprintf(stderr,
" too few bytes; expected %ld, got %d\n",
sizeof(tpi), nb);
Exit(1);
}
if (tpi.pvip.vip_path[0]) {
alloc_lfile(TWD, -1);
Cfp = (struct file *)NULL;
(void) enter_vnode_info(&tpi.pvip);
if (Lf->sf)
link_lfile();
}
}
}
#endif /* DARWINV>=900 */