]> git.saurik.com Git - apple/xnu.git/blobdiff - libsyscall/wrappers/libproc/proc_listpidspath.c
xnu-2422.1.72.tar.gz
[apple/xnu.git] / libsyscall / wrappers / libproc / proc_listpidspath.c
diff --git a/libsyscall/wrappers/libproc/proc_listpidspath.c b/libsyscall/wrappers/libproc/proc_listpidspath.c
new file mode 100644 (file)
index 0000000..57494de
--- /dev/null
@@ -0,0 +1,611 @@
+/*
+ * 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)
+{
+       uint64_t        a       = 0;
+       int             status;
+
+       while (1) {     // for all memory regions
+               int                             buf_used;
+               struct proc_regionwithpathinfo  rwpi;
+
+               // processing next address
+               buf_used = proc_pidinfo(pid, PROC_PIDREGIONPATHINFO, 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, 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 = info->fds_size + sizeof(struct proc_fdinfo);
+                       continue;
+               }
+
+               info->fds_count = 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, 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 = 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
+ *   check [process] current working and root directories
+ *   check [process] text (memory)
+ *   check [process] open file descriptors
+ *
+ *   in  : pid
+ *   out : -1 if error
+ *          0 if no match
+ *          1 if match
+ */
+static int
+check_process(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 process text (memory)
+       status = check_process_text(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;
+}
+
+
+/*
+ * 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, 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 = 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     status;
+
+               pid = info->pids[i];
+               if (pid == 0) {
+                       continue;
+               }
+
+               status = check_process(info, pid);
+               if (status != 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;
+}