/*
* Copyright (c) 2007, 2008 Apple Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#include <stdlib.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <libproc.h>
typedef struct {
// process IDs
int *pids;
int pids_count;
size_t pids_size;
// threads
uint64_t *threads;
int thr_count;
size_t thr_size;
// open file descriptors
struct proc_fdinfo *fds;
int fds_count;
size_t fds_size;
// file/volume of interest
struct stat match_stat;
// flags
uint32_t flags;
} fdOpenInfo, *fdOpenInfoRef;
/*
* check_init
*/
static fdOpenInfoRef
check_init(const char *path, uint32_t flags)
{
fdOpenInfoRef info;
int status;
info = malloc(sizeof(*info));
if (!info) {
return NULL;
}
info->pids = NULL;
info->pids_count = 0;
info->pids_size = 0;
info->threads = NULL;
info->thr_count = 0;
info->thr_size = 0;
info->fds = NULL;
info->fds_count = 0;
info->fds_size = 0;
status = stat(path, &info->match_stat);
if (status == -1) {
goto fail;
}
info->flags = flags;
return info;
fail:
free(info);
return NULL;
}
/*
* check_free
*/
static void
check_free(fdOpenInfoRef info)
{
if (info->pids != NULL) {
free(info->pids);
}
if (info->threads != NULL) {
free(info->threads);
}
if (info->fds != NULL) {
free(info->fds);
}
free(info);
return;
}
/*
* check_file
* check if a process vnode is of interest
*
* in : vnode stat(2)
* out : -1 if error
* 0 if no match
* 1 if match
*/
static int
check_file(fdOpenInfoRef info, struct vinfo_stat *sb)
{
if (sb->vst_dev == 0) {
// if no info
return 0;
}
if (sb->vst_dev != info->match_stat.st_dev) {
// if not the requested filesystem
return 0;
}
if (!(info->flags & PROC_LISTPIDSPATH_PATH_IS_VOLUME) &&
(sb->vst_ino != info->match_stat.st_ino)) {
// if not the requested file
return 0;
}
return 1;
}
/*
* check_process_vnodes
* check [process] current working directory
* check [process] root directory
*
* in : pid
* out : -1 if error
* 0 if no match
* 1 if match
*/
static int
check_process_vnodes(fdOpenInfoRef info, int pid)
{
int buf_used;
int status;
struct proc_vnodepathinfo vpi;
buf_used = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
if (buf_used <= 0) {
if (errno == ESRCH) {
// if the process is gone
return 0;
}
return -1;
} else if (buf_used < sizeof(vpi)) {
// if we didn't get enough information
return -1;
}
// processing current working directory
status = check_file(info, &vpi.pvi_cdir.vip_vi.vi_stat);
if (status != 0) {
// if error or match
return status;
}
// processing root directory
status = check_file(info, &vpi.pvi_rdir.vip_vi.vi_stat);
if (status != 0) {
// if error or match
return status;
}
return 0;
}
/*
* check_process_text
* check [process] text (memory)
*
* in : pid
* out : -1 if error
* 0 if no match
* 1 if match
*/
static int
check_process_text(fdOpenInfoRef info, int pid)
{
int status;
int buf_used;
struct proc_regionwithpathinfo rwpi;
if (info->flags & PROC_LISTPIDSPATH_PATH_IS_VOLUME) {
// ask for first memory region that matches mountpoint
buf_used = proc_pidinfo(pid, PROC_PIDREGIONPATHINFO3, info->match_stat.st_dev, &rwpi, sizeof(rwpi));
if (buf_used <= 0) {
if ((errno == ESRCH) || (errno == EINVAL)) {
// if no more text information is available for this process.
return 0;
}
return -1;
} else if (buf_used < sizeof(rwpi)) {
// if we didn't get enough information
return -1;
}
status = check_file(info, &rwpi.prp_vip.vip_vi.vi_stat);
if (status != 0) {
// if error or match
return status;
}
} else {
uint64_t a = 0;
while (1) { // for all memory regions
// processing next address
buf_used = proc_pidinfo(pid, PROC_PIDREGIONPATHINFO2, a, &rwpi, sizeof(rwpi));
if (buf_used <= 0) {
if ((errno == ESRCH) || (errno == EINVAL)) {
// if no more text information is available for this process.
break;
}
return -1;
} else if (buf_used < sizeof(rwpi)) {
// if we didn't get enough information
return -1;
}
status = check_file(info, &rwpi.prp_vip.vip_vi.vi_stat);
if (status != 0) {
// if error or match
return status;
}
a = rwpi.prp_prinfo.pri_address + rwpi.prp_prinfo.pri_size;
}
}
return 0;
}
/*
* check_process_fds
* check [process] open file descriptors
*
* in : pid
* out : -1 if error
* 0 if no match
* 1 if match
*/
static int
check_process_fds(fdOpenInfoRef info, int pid)
{
int buf_used;
int i;
int status;
// get list of open file descriptors
buf_used = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0);
if (buf_used <= 0) {
return -1;
}
while (1) {
if (buf_used > info->fds_size) {
// if we need to allocate [more] space
while (buf_used > info->fds_size) {
info->fds_size += (sizeof(struct proc_fdinfo) * 32);
}
if (info->fds == NULL) {
info->fds = malloc(info->fds_size);
} else {
info->fds = reallocf(info->fds, info->fds_size);
}
if (info->fds == NULL) {
return -1;
}
}
buf_used = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, info->fds, (int)info->fds_size);
if (buf_used <= 0) {
return -1;
}
if ((buf_used + sizeof(struct proc_fdinfo)) >= info->fds_size) {
// if not enough room in the buffer for an extra fd
buf_used = (int)(info->fds_size + sizeof(struct proc_fdinfo));
continue;
}
info->fds_count = (int)(buf_used / sizeof(struct proc_fdinfo));
break;
}
// iterate through each file descriptor
for (i = 0; i < info->fds_count; i++) {
struct proc_fdinfo *fdp;
fdp = &info->fds[i];
switch (fdp->proc_fdtype) {
case PROX_FDTYPE_VNODE: {
int buf_used;
struct vnode_fdinfo vi;
buf_used = proc_pidfdinfo(pid, fdp->proc_fd, PROC_PIDFDVNODEINFO, &vi, sizeof(vi));
if (buf_used <= 0) {
if (errno == ENOENT) {
/*
* The file descriptor's vnode may have been revoked. This is a
* bit of a hack, since an ENOENT error might not always mean the
* descriptor's vnode has been revoked. As the libproc API
* matures, this code may need to be revisited.
*/
continue;
}
return -1;
} else if (buf_used < sizeof(vi)) {
// if we didn't get enough information
return -1;
}
if ((info->flags & PROC_LISTPIDSPATH_EXCLUDE_EVTONLY) &&
(vi.pfi.fi_openflags & O_EVTONLY)) {
// if this file should be excluded
continue;
}
status = check_file(info, &vi.pvi.vi_stat);
if (status != 0) {
// if error or match
return status;
}
break;
}
default:
break;
}
}
return 0;
}
/*
* check_process_threads
* check [process] thread working directories
*
* in : pid
* out : -1 if error
* 0 if no match
* 1 if match
*/
static int
check_process_threads(fdOpenInfoRef info, int pid)
{
int buf_used;
int status;
struct proc_taskallinfo tai;
buf_used = proc_pidinfo(pid, PROC_PIDTASKALLINFO, 0, &tai, sizeof(tai));
if (buf_used <= 0) {
if (errno == ESRCH) {
// if the process is gone
return 0;
}
return -1;
} else if (buf_used < sizeof(tai)) {
// if we didn't get enough information
return -1;
}
// check thread info
if (tai.pbsd.pbi_flags & PROC_FLAG_THCWD) {
int i;
// get list of threads
buf_used = tai.ptinfo.pti_threadnum * sizeof(uint64_t);
while (1) {
if (buf_used > info->thr_size) {
// if we need to allocate [more] space
while (buf_used > info->thr_size) {
info->thr_size += (sizeof(uint64_t) * 32);
}
if (info->threads == NULL) {
info->threads = malloc(info->thr_size);
} else {
info->threads = reallocf(info->threads, info->thr_size);
}
if (info->threads == NULL) {
return -1;
}
}
buf_used = proc_pidinfo(pid, PROC_PIDLISTTHREADS, 0, info->threads, (int)info->thr_size);
if (buf_used <= 0) {
return -1;
}
if ((buf_used + sizeof(uint64_t)) >= info->thr_size) {
// if not enough room in the buffer for an extra thread
buf_used = (int)(info->thr_size + sizeof(uint64_t));
continue;
}
info->thr_count = buf_used / sizeof(uint64_t);
break;
}
// iterate through each thread
for (i = 0; i < info->thr_count; i++) {
uint64_t thr = info->threads[i];
struct proc_threadwithpathinfo tpi;
buf_used = proc_pidinfo(pid, PROC_PIDTHREADPATHINFO, thr, &tpi, sizeof(tpi));
if (buf_used <= 0) {
if ((errno == ESRCH) || (errno == EINVAL)) {
// if the process or thread is gone
continue;
}
} else if (buf_used < sizeof(tai)) {
// if we didn't get enough information
return -1;
}
status = check_file(info, &tpi.pvip.vip_vi.vi_stat);
if (status != 0) {
// if error or match
return status;
}
}
}
return 0;
}
/*
* check_process_phase1
* check [process] process-wide current working and root directories
* check [process] open file descriptors
* check [process] per-thread current working and root directories
*
* in : pid
* out : -1 if error
* 0 if no match
* 1 if match
*/
static int
check_process_phase1(fdOpenInfoRef info, int pid)
{
int status;
// check root and current working directory
status = check_process_vnodes(info, pid);
if (status != 0) {
// if error or match
return status;
}
// check open file descriptors
status = check_process_fds(info, pid);
if (status != 0) {
// if error or match
return status;
}
// check per-thread working directories
status = check_process_threads(info, pid);
if (status != 0) {
// if error or match
return status;
}
return 0;
}
/*
* check_process_phase2
* check [process] text (memory)
*
* in : pid
* out : -1 if error
* 0 if no match
* 1 if match
*/
static int
check_process_phase2(fdOpenInfoRef info, int pid)
{
int status;
// check process text (memory)
status = check_process_text(info, pid);
if (status != 0) {
// if error or match
return status;
}
return 0;
}
/*
* proc_listpidspath
*
* in : type
* : typeinfo
* : path
* : pathflags
* : buffer
* : buffersize
* out : buffer filled with process IDs that have open file
* references that match the specified path or volume;
* return value is the bytes of the returned buffer
* that contains valid information.
*/
int
proc_listpidspath(uint32_t type,
uint32_t typeinfo,
const char *path,
uint32_t pathflags,
void *buffer,
int buffersize)
{
int buf_used;
int *buf_next = (int *)buffer;
int i;
fdOpenInfoRef info;
int status = -1;
if (buffer == NULL) {
// if this is a sizing request
return proc_listpids(type, typeinfo, NULL, 0);
}
buffersize -= (buffersize % sizeof(int)); // make whole number of ints
if (buffersize < sizeof(int)) {
// if we can't even return a single PID
errno = ENOMEM;
return -1;
}
// init
info = check_init(path, pathflags);
if (info == NULL) {
return -1;
}
// get list of processes
buf_used = proc_listpids(type, typeinfo, NULL, 0);
if (buf_used <= 0) {
goto done;
}
while (1) {
if (buf_used > info->pids_size) {
// if we need to allocate [more] space
while (buf_used > info->pids_size) {
info->pids_size += (sizeof(int) * 32);
}
if (info->pids == NULL) {
info->pids = malloc(info->pids_size);
} else {
info->pids = reallocf(info->pids, info->pids_size);
}
if (info->pids == NULL) {
goto done;
}
}
buf_used = proc_listpids(type, typeinfo, info->pids, (int)info->pids_size);
if (buf_used <= 0) {
goto done;
}
if ((buf_used + sizeof(int)) >= info->pids_size) {
// if not enough room in the buffer for an extra pid
buf_used = (int)(info->pids_size + sizeof(int));
continue;
}
info->pids_count = buf_used / sizeof(int);
break;
}
// iterate through each process
buf_used = 0;
for (i = info->pids_count - 1; i >= 0; i--) {
int pid;
int pstatus;
pid = info->pids[i];
if (pid == 0) {
continue;
}
pstatus = check_process_phase1(info, pid);
if (pstatus != 1) {
// if not a match
continue;
}
*buf_next++ = pid;
buf_used += sizeof(int);
if (buf_used >= buffersize) {
// if we have filled the buffer
break;
}
}
if (buf_used >= buffersize) {
// if we have filled the buffer
status = buf_used;
goto done;
}
// do a more expensive search if we still have buffer space
for (i = info->pids_count - 1; i >= 0; i--) {
int pid;
int pstatus;
pid = info->pids[i];
if (pid == 0) {
continue;
}
pstatus = check_process_phase2(info, pid);
if (pstatus != 1) {
// if not a match
continue;
}
*buf_next++ = pid;
buf_used += sizeof(int);
if (buf_used >= buffersize) {
// if we have filled the buffer
break;
}
}
status = buf_used;
done:
// cleanup
check_free(info);
return status;
}