/*
 * 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";
#endif

#include "common.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 */

#if defined(PROC_PIDLISTFILEPORTS)
#    define FILEPORTS_INCR                                                     \
        (sizeof(struct proc_fileportinfo) * 32) /* Fileports space increment   \
                                                 */
#endif                                          /* PROC_PIDLISTFILEPORTS */

/*
 * 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 */

#if defined(PROC_PIDLISTFILEPORTS)
static struct proc_fileportinfo *Fps =
    (struct proc_fileportinfo *)NULL; /* fileport buffer */
static int NbFps = 0;                 /* bytes allocated to fileports */
#endif                                /* PROC_PIDLISTFILEPORTS */

/*
 * 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
 */
static void enter_vn_text(struct lsof_context *ctx, struct vnode_info_path *vip,
                          int *n);
static void process_fds(struct lsof_context *ctx, int pid, uint32_t n,
                        int ckscko);
static void process_text(struct lsof_context *ctx, int pid);

#if DARWINV >= 900
static void process_threads(struct lsof_context *ctx, int pid, uint32_t n);
#endif /* DARWINV>=900 */

#if defined(PROC_PIDLISTFILEPORTS)
static void process_fileports(struct lsof_context *ctx, int pid, int ckscko);
#endif /* PROC_PIDLISTFILEPORTS */

/*
 * enter_vn_text() -- enter vnode information text reference
 */
static void enter_vn_text(struct lsof_context *ctx,    /* context */
                          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(ctx, LSOF_FD_PROGRAM_TEXT, -1);
    Cfp = (struct file *)NULL;
    (void)enter_vnode_info(ctx, vip);
    if (Lf->sf)
        link_lfile(ctx);
    /*
     * 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);
            Error(ctx);
        }
    }
    /*
     * 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(struct lsof_context *ctx) {
    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));
        Error(ctx);
    }
    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 *)));
            Error(ctx);
        }
    }
    /*
     * 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));
            Error(ctx);
        }

        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 *)));
                Error(ctx);
            }
        }
    }
    /*
     * 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);
            Error(ctx);
        }
        /*
         * Check for process or command exclusion.
         */
        if (is_proc_excl(ctx, (int)pid, (int)tai.pbsd.pbi_pgid,
                         (UID_ARG)tai.pbsd.pbi_uid, &pss, &sf)) {
            continue;
        }
        tai.pbsd.pbi_comm[sizeof(tai.pbsd.pbi_comm) - 1] = '\0';
        if (is_cmd_excl(ctx, 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(ctx, 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);
                Error(ctx);
            } else
                cres = 0;
        }
        /*
         * Allocate local process space.
         *
         * Caveat: pbi_name can be changed by setprogname(3), while pbi_comm
         * cannot. Should we allow empty pbi_name here?
         */
        alloc_lproc(ctx, (int)pid, (int)tai.pbsd.pbi_pgid,
                    (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(ctx, LSOF_FD_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(ctx, Namech);
                        if (Lf->sf)
                            link_lfile(ctx);
                    }
                } else {
                    (void)enter_vnode_info(ctx, &vpi.pvi_cdir);
                    if (Lf->sf)
                        link_lfile(ctx);
                }
            }
        }
        /*
         * Save root directory information.
         */
        if (!ckscko) {
            if (!cres && vpi.pvi_rdir.vip_path[0]) {
                alloc_lfile(ctx, LSOF_FD_ROOT_DIR, -1);
                Cfp = (struct file *)NULL;
                (void)enter_vnode_info(ctx, &vpi.pvi_rdir);
                if (Lf->sf)
                    link_lfile(ctx);
            }
        }

#if DARWINV >= 900
        /*
         * Check for per-thread current working directories
         */
        if (!ckscko) {
            if (tai.pbsd.pbi_flags & PROC_FLAG_THCWD) {
                (void)process_threads(ctx, pid, tai.ptinfo.pti_threadnum);
            }
        }
#endif /* DARWINV>=900 */

        /*
         * Print text file information.
         */
        if (!ckscko)
            (void)process_text(ctx, pid);

#if defined(PROC_PIDLISTFILEPORTS)
        /*
         * Loop through the fileports
         */
        (void)process_fileports(ctx, pid, ckscko);
#endif /* PROC_PIDLISTFILEPORTS */

        /*
         * Loop through the file descriptors.
         */
        (void)process_fds(ctx, pid, tai.pbsd.pbi_nfiles, ckscko);
        /*
         * Examine results.
         */
        if (examine_lproc(ctx))
            return;
    }
}

/*
 * initialize() -- perform all initialization
 */

void initialize(struct lsof_context *ctx) {}

/*
 * process_fds() -- process file descriptors
 */
static void process_fds(struct lsof_context *ctx, /* context */
                        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)));
        Error(ctx);
    }
    /*
     * 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(ctx, LSOF_FD_ERROR, -1);
        (void)snpf(Namech, Namechl, "FD info error: %s", strerror(errno));
        Namech[Namechl - 1] = '\0';
        enter_nm(ctx, Namech);
        if (Lf->sf)
            link_lfile(ctx);
        return;
    }
    nf = (int)(nb / sizeof(struct proc_fdinfo));
    /*
     * Loop through the file descriptors.
     */
    for (i = 0; i < nf; i++) {
        fdp = &Fds[i];
        alloc_lfile(ctx, LSOF_FD_NUMERIC, (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(ctx, pid, fdp->proc_fd);
            break;
        case PROX_FDTYPE_FSEVENTS:
            if (!ckscko)
                (void)process_fsevents(ctx, pid, fdp->proc_fd);
            break;
        case PROX_FDTYPE_KQUEUE:
            if (!ckscko)
                (void)process_kqueue(ctx, pid, fdp->proc_fd);
            break;
        case PROX_FDTYPE_PIPE:
            if (!ckscko)
                (void)process_pipe(ctx, pid, fdp->proc_fd);
            break;
        case PROX_FDTYPE_PSEM:
            if (!ckscko)
                (void)process_psem(ctx, pid, fdp->proc_fd);
            break;
        case PROX_FDTYPE_SOCKET:
            (void)process_socket(ctx, pid, fdp->proc_fd);
            isock = 1;
            break;
        case PROX_FDTYPE_PSHM:
            (void)process_pshm(ctx, pid, fdp->proc_fd);
            break;
        case PROX_FDTYPE_VNODE:
            (void)process_vnode(ctx, 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(ctx, Namech);
            break;
        }
        if (Lf->sf) {
            if (!ckscko || isock)
                link_lfile(ctx);
        }
    }
}

#if defined(PROC_PIDLISTFILEPORTS)
/*
 * process_fileports() -- process fileports
 */
static void process_fileports(struct lsof_context *ctx, /* context */
                              int pid,                  /* PID of interest */
                              int ckscko) /* check socket files only */
{
    int ef, i, isock, nb = 0, nf;
    struct proc_fileportinfo *fpi;

    /*
     * Get fileport information for the process.
     */
    for (ef = 0; !ef;) {
        nb = proc_pidinfo(pid, PROC_PIDLISTFILEPORTS, 0, Fps, NbFps);
        if (nb == 0) {

            /*
             * Quit if no fileport information
             */
            return;
        } else if (nb < 0) {
            if (errno == ESRCH) {

                /*
                 * Quit if no fileport information is available for the process.
                 */
                return;
            }
            /*
             * Make a dummy file entry with an error message in its NAME column.
             */
            alloc_lfile(ctx, LSOF_FD_ERROR, -1);
            (void)snpf(Namech, Namechl, "FILEPORT info error: %s",
                       strerror(errno));
            Namech[Namechl - 1] = '\0';
            enter_nm(ctx, Namech);
            if (Lf->sf)
                link_lfile(ctx);
        }

        if ((nb + sizeof(struct proc_fileportinfo)) < NbFps) {

            /*
             * There is room in the buffer for at least one more fileport.
             */
            ef = 1;
        } else {
            if (Fps && ((nb = proc_pidinfo(pid, PROC_PIDLISTFILEPORTS, 0, NULL,
                                           0)) <= 0)) {
                (void)fprintf(stderr, "%s: can't get fileport byte count: %s\n",
                              Pn, strerror(errno));
                Error(ctx);
            }

            /*
             * The fileport buffer must be enlarged.
             */
            while (nb > NbFps) {
                NbFps += FILEPORTS_INCR;
            }
            if (!Fps)
                Fps = (struct proc_fileportinfo *)malloc((MALLOC_S)NbFps);
            else
                Fps = (struct proc_fileportinfo *)realloc((MALLOC_P *)Fps,
                                                          (MALLOC_S)NbFps);
        }
    }

    /*
     * Loop through the fileports.
     */
    nf = (int)(nb / sizeof(struct proc_fileportinfo));
    for (i = 0; i < nf; i++) {
        fpi = &Fps[i];
        /*
         * fileport reported as "fp." with "(fileport=0xXXXX)" in the Name
         * column
         */
        alloc_lfile(ctx, LSOF_FD_FILEPORT, -1);
        Lf->fileport = fpi->proc_fileport;
        /*
         * Process the file by its type.
         */
        isock = 0;
        switch (fpi->proc_fdtype) {
        case PROX_FDTYPE_PIPE:
            if (!ckscko)
                (void)process_fileport_pipe(ctx, pid, fpi->proc_fileport);
            break;
        case PROX_FDTYPE_SOCKET:
            (void)process_fileport_socket(ctx, pid, fpi->proc_fileport);
            isock = 1;
            break;
        case PROX_FDTYPE_PSHM:
            (void)process_fileport_pshm(ctx, pid, fpi->proc_fileport);
            break;
        case PROX_FDTYPE_VNODE:
            (void)process_fileport_vnode(ctx, pid, fpi->proc_fileport);
            break;
        default:
            (void)snpf(Namech, Namechl - 1, "unknown file type: %d",
                       fpi->proc_fileport);
            Namech[Namechl - 1] = '\0';
            (void)enter_nm(ctx, Namech);
            break;
        }
        if (Lf->sf) {
            if (!ckscko || isock)
                link_lfile(ctx);
        }
    }
}
#endif /* PROC_PIDLISTFILEPORTS */

/*
 * process_text() -- process text information
 */
static void process_text(struct lsof_context *ctx, /* context */
                         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(ctx, LSOF_FD_PROGRAM_TEXT, -1);
            Cfp = (struct file *)NULL;
            (void)snpf(Namech, Namechl, "region info error: %s",
                       strerror(errno));
            Namech[Namechl - 1] = '\0';
            enter_nm(ctx, Namech);
            if (Lf->sf)
                link_lfile(ctx);
            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);
            Error(ctx);
        }
        if (rwpi.prp_vip.vip_path[0])
            enter_vn_text(ctx, &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(struct lsof_context *ctx, /* context */
                            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 *)));
            Error(ctx);
        }
    }
    /*
     * 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(ctx, LSOF_FD_TASK_CWD, -1);
            Cfp = (struct file *)NULL;
            (void)snpf(Namech, Namechl, "thread info error: %s",
                       strerror(errno));
            Namech[Namechl - 1] = '\0';
            enter_nm(ctx, Namech);
            if (Lf->sf)
                link_lfile(ctx);
            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);
            Error(ctx);
        }
        if (tpi.pvip.vip_path[0]) {
            alloc_lfile(ctx, LSOF_FD_TASK_CWD, -1);
            Cfp = (struct file *)NULL;
            (void)enter_vnode_info(ctx, &tpi.pvip);
            if (Lf->sf)
                link_lfile(ctx);
        }
    }
}
#endif /* DARWINV>=900 */
