/*
 * dproc.c - SCO OpenServer process access functions for lsof
 */

/*
 * Copyright 1995 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 1995 Purdue Research Foundation.\nAll rights reserved.\n";
#endif

#include "common.h"

/*
 * Local static values
 */

static KA_T Kp;                 /* kernel process table address */
static KA_T *Nc = (KA_T *)NULL; /* node cache */
static int Nn = 0;              /* number of Nc[] entries allocated */

#if OSRV < 500
static int Npp = 0; /* number of pregions per process */
static struct pregion *Pr = (struct pregion *)NULL;
/* pregion buffer */
static int Prsz = 0; /* size of Pr */
#endif               /* OSRV<500 */

static struct var Var; /* kernel variables */

static void get_cdevsw(void);
static void get_kernel_access(void);

#if !defined(N_UNIX)
static int is_boot(char *p);
#endif /* !defined(N_UNIX) */

static int open_kmem(int nx);
static void process_text(KA_T prp);
static void readfsinfo(void);

/*
 * Ckkv - check kernel version
 */

static void Ckkv(d, er, ev, ea) char *d; /* dialect */
char *er;                                /* expected release */
char *ev;                                /* expected version */
char *ea;                                /* expected architecture */
{

#if defined(HASKERNIDCK)
    struct scoutsname s;

    if (Fwarn)
        return;
    /*
     * Read OSR kernel information.
     */
    if (__scoinfo(&s, sizeof(s)) < 0) {
        (void)fprintf(stderr, "%s: can't get __scoinfo: %s\n", Pn,
                      strerror(errno));
        Error(ctx);
    }
    /*
     * Warn if the actual and expected releases don't match.
     */
    if (!er || strcmp(er, s.release))
        (void)fprintf(stderr,
                      "%s: WARNING: compiled for %s release %s; this is %s.\n",
                      Pn, d, er ? er : "UNKNOWN", s.release);
#endif /* defined(HASKERNIDCK) */
}

/*
 * gather_proc_info() -- gather process information
 */

void gather_proc_info() {
    int i, j, nf, pbc, px;
    struct proc *p;
    static char *pb = (char *)NULL;
    int pid, pgrp;
    short pss, sf;
    static struct user *u;
    static char *ua = (char *)NULL;
    static MALLOC_S ual = 0;
    unsigned int uid;

#if defined(HASFSTRUCT)
    static MALLOC_S npofb = 0;
    char *pof;
    static char *pofb = (char *)NULL;
#endif /* defined(HASFSTRUCT) */

    /*
     * Allocate user structure buffer.
     */
    if (!ua) {
        ual = (MALLOC_S)(MAXUSIZE * NBPC);
        if (!(ua = (char *)malloc(ual))) {
            (void)fprintf(stderr,
                          "%s: no space for %d byte user structure buffer\n",
                          Pn, ual);
            Error(ctx);
        }
        u = (struct user *)ua;
    }
    /*
     * Allocate proc structure buffer.
     */
    if (!pb) {
        if (!(pb = (char *)malloc(sizeof(struct proc) * PROCBFRD))) {
            (void)fprintf(stderr, "%s: no space for %d proc structures\n", Pn,
                          PROCBFRD);
            Error(ctx);
        }
    }
    /*
     * Examine proc structures and their associated information.
     */
    for (pbc = px = 0; px < Var.v_proc; px++) {
        if (px >= pbc) {

            /*
             * Refill proc buffer.
             */
            i = Var.v_proc - px;
            if (i > PROCBFRD)
                i = PROCBFRD;
            j = kread(ctx, (KA_T)(Kp + (px * sizeof(struct proc))), pb,
                      sizeof(struct proc) * i);
            pbc = px + i;
            p = (struct proc *)pb;
            if (j) {
                px += i;
                continue;
            }
        } else
            p++;
        if (p->p_stat == 0 || p->p_stat == SZOMB)
            continue;
        /*
         * Get Process ID, Process group ID, and User ID.
         */
        pid = (int)p->p_pid;
        pgrp = (int)p->p_pgrp;
        uid = (unsigned int)p->p_uid;
        if (is_proc_excl(pid, pgrp, (UID_ARG)uid, &pss, &sf))
            continue;
        /*
         * Get the user area associated with the process.
         */
        if (sysi86(RDUBLK, pid, ua, MAXUSIZE * NBPC) == -1)
            continue;
        /*
         * Allocate a local process structure.
         */
        if (is_cmd_excl(u->u_comm, &pss, &sf))
            continue;
        alloc_lproc(pid, pgrp, (int)p->p_ppid, (UID_ARG)uid, u->u_comm,
                    (int)pss, (int)sf);
        Plf = (struct lfile *)NULL;
        /*
         * Save current working directory information.
         */
        if (u->u_cdir) {
            alloc_lfile(ctx, LSOF_FD_CWD, -1);
            process_node((KA_T)u->u_cdir);
            if (Lf->sf)
                link_lfile();
        }
        /*
         * Save root directory information.
         */
        if (u->u_rdir) {
            alloc_lfile(ctx, LSOF_FD_ROOT_DIR, -1);
            process_node((KA_T)u->u_rdir);
            if (Lf->sf)
                link_lfile();
        }
        /*
         * Print information on the text file.
         */
        if (p->p_region)
            process_text((KA_T)p->p_region);
            /*
             * Save information on file descriptors.
             */

#if OSRV < 42
        nf = Var.v_nofiles;
#else  /* OSRV>=42 */
        nf = u->u_nofiles ? u->u_nofiles : Var.v_nofiles;
#endif /* OSRV<42 */

#if defined(HASFSTRUCT)

        /*
         * If u_pofile is in the u block, set its address.
         */
        if (nf && u->u_pofile && ((unsigned)u->u_pofile >= UVUBLK) &&
            ((MALLOC_S)((unsigned)u->u_pofile - UVUBLK + nf) <= ual)) {
            pof = ua + (unsigned)u->u_pofile - UVUBLK;
        } else if (nf && u->u_pofile) {

            /*
             * Allocate space for u_pofile and read it from kernel memory.
             */
            if (nf > npofb) {
                if (!pofb)
                    pofb = (char *)malloc((MALLOC_S)nf);
                else
                    pofb = (char *)realloc((MALLOC_P *)pofb, (MALLOC_S)nf);
                if (!pofb) {
                    (void)fprintf(stderr, "%s: no pofile space\n", Pn);
                    Error();
                }
                npofb = nf;
            }
            if (kread(ctx, (KA_T)u->u_pofile, pofb, nf))
                pof = (char *)NULL;
            else
                pof = pofb;
        } else
            pof = (char *)NULL;
#endif /* defined(HASFSTRUCT) */

        for (i = 0; i < nf; i++) {
            if (u->u_ofile[i]) {
                alloc_lfile(ctx, LSOF_FD_NUMERIC, i);
                process_file((KA_T)u->u_ofile[i]);
                if (Lf->sf) {

#if defined(HASFSTRUCT)
                    if (pof)
                        Lf->pof = (long)pof[i];
#endif /* defined(HASFSTRUCT) */

                    link_lfile();
                }
            }
        }
        /*
         * Examine results.
         */
        if (examine_lproc())
            return;
    }
}

/*
 * get_cdevsw() - get cdevsw[] names and record clone major device number
 */

void get_cdevsw() {
    char buf[16];
    struct cdevsw *c, *tmp;
    int i, j, len;
    struct stat sb;
    KA_T v[2];
    /*
     * Check cdevsw[] kernel addresses.
     * Read cdevsw[] count from kernel's cdevcnt.
     */
    if (get_Nl_value("cdev", Drive_Nl, &v[0]) < 0 ||
        get_Nl_value("ncdev", Drive_Nl, &v[1]) < 0 || !v[0] || !v[1] ||
        kread(ctx, v[1], (char *)&Cdevcnt, sizeof(Cdevcnt)) || Cdevcnt < 1)
        return;
    /*
     * Allocate cache space.
     */
    if (!(Cdevsw = (char **)malloc(Cdevcnt * sizeof(char *)))) {
        (void)fprintf(stderr, "%s: no space for %d cdevsw[] names\n", Pn,
                      Cdevcnt);
        Error(ctx);
    }
    /*
     * Allocate temporary space for a copy of cdevsw[] and read it.
     */
    i = Cdevcnt * sizeof(struct cdevsw);
    if (!(tmp = (struct cdevsw *)malloc(i))) {
        (void)fprintf(stderr, "%s: no space for %d cdevsw[] entries\n", Pn,
                      Cdevcnt);
        Error(ctx);
    }
    if (kread(ctx, (KA_T)v[0], (char *)tmp, i)) {
        (void)free((FREE_P *)Cdevsw);
        Cdevsw = (char **)NULL;
        Cdevcnt = 0;
        (void)free((FREE_P *)tmp);
        return;
    }
    /*
     * Cache the names from cdevsw[].d_name.
     * Record the number of the "clone" device.
     */
    j = sizeof(buf) - 1;
    buf[j] = '\0';
    for (c = tmp, i = 0; i < Cdevcnt; c++, i++) {
        Cdevsw[i] = (char *)NULL;
        if (!c->d_name)
            continue;
        if (kread(ctx, (KA_T)c->d_name, buf, j)) {
            (void)fprintf(stderr,
                          "%s: WARNING: can't read name for cdevsw[%d]: %#x\n",
                          Pn, i, c->d_name);
            continue;
        }
        if (!buf[0])
            continue;
        len = strlen(buf) + 1;
        if (!(Cdevsw[i] = (char *)malloc(len))) {
            (void)fprintf(stderr, "%s: no space for cdevsw[%d] name: %s\n", Pn,
                          i, buf);
            Error(ctx);
        }
        (void)snpf(Cdevsw[i], len, "%s", buf);
        if (!HaveCloneMajor && strcmp(buf, "clone") == 0) {
            CloneMajor = i;
            HaveCloneMajor = 1;
            continue;
        }
        if (!HaveEventMajor && strcmp(buf, "ev") == 0) {
            if (stat("/dev/event", &sb) == 0 && GET_MAJ_DEV(sb.st_rdev) == i) {
                EventMajor = i;
                HaveEventMajor = 1;
            }
        }
    }
    (void)free((FREE_P *)tmp);
}

/*
 * get_kernel_access() - get access to kernel memory
 */

static void get_kernel_access() {
    time_t lbolt;
    MALLOC_S len;
    KA_T v;
    /*
     * Check kernel version.
     */
    (void)Ckkv("OSR", LSOF_VSTR, (char *)NULL, (char *)NULL);
    /*
     * See if the name list file is readable.
     */
    if (Nmlst && !is_readable(Nmlst, 1))
        Error(ctx);
        /*
         * Access kernel symbols.
         */

#if defined(N_UNIX)
    (void)build_Nl(Drive_Nl);
    if (nlist(Nmlst ? Nmlst : N_UNIX, Nl) < 0)
#else  /* !defined(N_UNIX) */
    if (!get_nlist_path(0))
#endif /* defined(N_UNIX) */

    {
        (void)fprintf(stderr, "%s: can't read kernel name list.\n", Pn);
        Error(ctx);
    }
    /*
     * Open access to kernel memory.
     */
    (void)open_kmem(0);

#if defined(WILLDROPGID)
    /*
     * Drop setgid permission.
     */
    (void)dropgid();
#endif /* defined(WILLDROPGID) */

    /*
     * Check proc table pointer.
     */
    if (get_Nl_value("proc", Drive_Nl, &Kp) < 0 || !Kp) {
        (void)fprintf(stderr, "%s: no proc table pointer\n", Pn);
        Error(ctx);
    }

#if OSRV < 500
    /*
     * Read pregion information.
     */
    v = (KA_T)0;
    if (get_Nl_value("pregpp", Drive_Nl, &v) < 0 || !v ||
        kread(ctx, v, (char *)&Npp, sizeof(Npp)) || Npp < 1) {
        (void)fprintf(stderr, "%s: can't read pregion count (%d) from %s\n", Pn,
                      Npp, print_kptr(v, (char *)NULL, 0));
        Error(ctx);
    }
    Prsz = (MALLOC_S)(Npp * sizeof(struct pregion));
    if (!(Pr = (struct pregion *)malloc(Prsz))) {
        (void)fprintf(stderr, "%s: can't allocate space for %d pregions\n", Pn,
                      Npp);
        Error(ctx);
    }
#endif /* OSRV< 500 */

    /*
     * Read system configuration information.
     */
    if (get_Nl_value("var", Drive_Nl, &v) < 0 || !v ||
        kread(ctx, (KA_T)v, (char *)&Var, sizeof(Var))) {
        (void)fprintf(stderr, "%s: can't read system configuration info\n", Pn);
        Error(ctx);
    }
    /*
     * Read system clock values -- Hz and lightning bolt timer.
     */
    v = (KA_T)0;
    if (get_Nl_value("hz", Drive_Nl, &v) < 0 || !v ||
        kread(ctx, v, (char *)&Hz, sizeof(Hz))) {
        if (!Fwarn)
            (void)fprintf(stderr, "%s: WARNING: can't read Hz from %s\n", Pn,
                          print_kptr(v, (char *)NULL, 0));
        Hz = -1;
    }
    if (get_Nl_value("lbolt", Drive_Nl, &Lbolt) < 0 || !v ||
        kread(ctx, (KA_T)v, (char *)&lbolt, sizeof(lbolt))) {
        if (!Fwarn)
            (void)fprintf(
                stderr,
                "%s: WARNING: can't read lightning bolt timer from %s\n", Pn,
                print_kptr(v, (char *)NULL, 0));
        Lbolt = (KA_T)0;
    }
    /*
     * Get socket device number and socket table address.
     */
    if (get_Nl_value("sockd", Drive_Nl, &v) < 0 || !v ||
        kread(ctx, v, (char *)&Sockdev, sizeof(Sockdev))) {
        (void)fprintf(stderr, "%s: WARNING: can't identify socket device.\n",
                      Pn);
        (void)fprintf(stderr, "      Socket output may be incomplete.\n");
        return;
    }
    if (get_Nl_value("sockt", Drive_Nl, &Socktab) < 0 || !Socktab) {
        (void)fprintf(stderr, "%s: WARNING: socket table address is NULL.\n",
                      Pn);
        (void)fprintf(stderr, "      Socket output may be incomplete.\n");
        return;
    }

#if OSRV >= 40
    /*
     * Get extended device table parameters.  These are needed by the kernel
     * versions of the major() and minor() device number macros; they also
     * identify socket devices and assist in the conversion of socket device
     * numbers to socket table addresses.
     */
    v = (KA_T)0;
    if (get_Nl_value("nxdm", Drive_Nl, &v) < 0 || !v ||
        kread(ctx, v, (char *)&nxdevmaps, sizeof(nxdevmaps)) || nxdevmaps < 0) {
        (void)fprintf(stderr,
                      "%s: bad extended device table size (%d) at %s.\n", Pn,
                      nxdevmaps, print_kptr(v, (char *)NULL, 0));
        Error(ctx);
    }
    len = (MALLOC_S)((nxdevmaps + 1) * sizeof(struct XDEVMAP));
    if (!(Xdevmap = (struct XDEVMAP *)malloc(len))) {
        (void)fprintf(stderr, "%s: no space for %d byte xdevmap table\n", Pn,
                      len);
        Error(ctx);
    }
    v = (KA_T)0;
    if (get_Nl_value("xdm", Drive_Nl, &v) < 0 || !v ||
        kread(ctx, (KA_T)v, (char *)Xdevmap, len)) {
        (void)fprintf(stderr, "%s: can't read %d byte xdevmap table at #x\n",
                      Pn, len, v);
        Error(ctx);
    }
#endif /* OSRV>=40 */

    HaveSockdev = 1;
}

#if !defined(N_UNIX)
/*
 * get_nlist_path() - get kernel nlist() path
 *
 * As a side effect on a successful return (non-NULL character pointer), the
 * boot path's name list will have been loaded into Nl[].
 */

char *get_nlist_path(int ap) /* on success, return an allocated path
                              * string pointer if 1; return a
                              * constant character pointer if 0;
                              * return NULL if failure */
{
    FILE *bf;
    char *bfp, b1[MAXPATHLEN + 1], b2[MAXPATHLEN + 1], *pp, *tp;
    struct dirent *de;
    char *dir[] = {"/", "/stand/", NULL};
    DIR *dp;
    int i;
    MALLOC_S len;
    /*
     * If a kernel name list file was specified, use it.
     */
    if (Nmlst) {
        if (is_boot(Nmlst))
            return (Nmlst);
        return ((char *)NULL);
    }
    /*
     * If it's possible to open /etc/ps/booted system, search it for a preferred
     * boot path, defined by the value of a line that begins with "KERNEL=".
     */
    bfp = pp = (char *)NULL;
    if ((bf = fopen("/etc/ps/booted.system", "r"))) {
        len = strlen("KERNEL=");
        while (fgets(b1, sizeof(b1), bf)) {
            if (strncmp(b1, "KERNEL=", len) != 0)
                continue;
            if ((tp = strrchr(&b1[len], '\n'))) {
                *tp = '\0';
                if (b1[len]) {
                    bfp = &b1[len];
                    if (is_boot(bfp)) {
                        pp = bfp;
                        (void)fclose(bf);
                        goto get_nlist_return_path;
                    }
                    break;
                }
            }
        }
        (void)fclose(bf);
    }
    /*
     * Look for possible unix* boot paths.
     */
    for (i = 0; dir[i]; i++) {
        if (!(dp = opendir(dir[i])))
            continue;
        while ((de = readdir(dp))) {

            /*
             * Use the next entry that begins with "unix".
             */
            if (strncmp("unix", de->d_name, 4) != 0)
                continue;
            /*
             * Construct a temporary copy of the path name,
             * If it matches the preferred boot name, skip it.
             */
            len = strlen(dir[i]) + strlen(de->d_name) + 1;
            if (len >= sizeof(b2))
                continue;
            (void)snpf(b2, sizeof(b2), "%s%s", dir[i], de->d_name);
            if (bfp && strcmp(b2, bfp) == 0)
                continue;
            /*
             * See if it's the booted kernel.
             */
            if (is_boot(b2)) {
                (void)closedir(dp);
                pp = b2;

            get_nlist_return_path:

                /*
                 * A boot path has been located.  As requested return a
                 * malloc'd pointer to it.
                 */
                if (!ap)
                    return ("");
                len = (MALLOC_S)(strlen(pp) + 1);
                if (!(tp = (char *)malloc(len))) {
                    (void)fprintf(stderr,
                                  "%s: can't allocate %d bytes for: %s\n", Pn,
                                  len, pp);
                    Error(ctx);
                }
                (void)snpf(tp, len, "%s", pp);
                return (tp);
            }
        }
        if (dp)
            (void)closedir(dp);
    }
    return ((char *)NULL);
}
#endif /* !defined(N_UNIX) */

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

void initialize() {
    get_kernel_access();
    get_cdevsw();
    readfsinfo();
    if (Fsv & FSV_NI)
        NiTtl = "INODE-ADDR";
}

#if !defined(N_UNIX)
/*
 * is_boot() - does the specified path lead to a booted kernel
 */

is_boot(p) char *p; /* specified path */
{
    int i;
    KA_T ka;
    union {
        struct scoutsname s;
        unsigned char sc[sizeof(struct scoutsname)];
    } s1, s2;
    /*
     * Get the scoutsname structure via __scoinfo() to use as a reference
     * against the one obtained via kread(ctx, )'ing from the
     * nlist(<possible_kernel>) address. If __scoinfo() fails, return the
     * default boot path.
     */
    if (__scoinfo(&s1.s, sizeof(s1.s)) < 0)
        return 0;
    /*
     * Get the name list for this boot path.  Using the scoutsname address, read
     * the scoutsname structure and compare it to the _s_scoinfo() one.  If the
     * two match, this is the boot path.
     */
    if (Nl) {
        (void)free((FREE_P *)Nl);
        Nl = (struct NLIST_TYPE *)NULL;
    }
    (void)build_Nl(Drive_Nl);
    if (nlist(p, Nl) < 0)
        return (0);
    if (get_Nl_value("scouts", Drive_Nl, &ka) < 0 || !ka)
        return (0);
    if (Kd < 0) {
        if (open_kmem(1))
            return (0);
    }
    if (kread(ctx, ka, (char *)&s2.s, sizeof(s2.s)))
        return (0);
    for (i = 0; i < sizeof(struct scoutsname); i++) {
        if (s1.sc[i] != s2.sc[i])
            return (0);
    }
    return (1);
}
#endif /* !defined(N_UNIX) */

/*
 * kread() - read from kernel memory
 */

int kread(struct lsof_context *ctx, /* context */
          KA_T addr,                /* kernel memory address */
          char *buf,                /* buffer to receive data */
          READLEN_T len)            /* length to read */
{
    int br;

    if (lseek(Kd, (off_t)addr, SEEK_SET) == (off_t)-1L)
        return (1);
    if ((br = read(Kd, buf, len)) < 0)
        return (1);
    return (((READLEN_T)br == len) ? 0 : 1);
}

/*
 * open_kmem() - open kernel memory access
 */

static int open_kmem(int nx) /* no Error(ctx) if 1 */
{
    if (Kd >= 0)
        return (0);
    /*
     * See if the non-KMEM memory file is readable.
     */
    if (Memory && !is_readable(Memory, 1)) {
        if (nx)
            return (1);
        Error(ctx);
    }
    /*
     * Open kernel memory access.
     */
    if ((Kd = open(Memory ? Memory : KMEM, O_RDONLY, 0)) < 0) {
        if (nx)
            return (1);
        (void)fprintf(stderr, "%s: can't open %s: %s\n", Pn,
                      Memory ? Memory : KMEM, strerror(errno));
        Error(ctx);
    }
    return (0);
}

/*
 * process_text() - process text access information
 */

static void process_text(prp) KA_T prp; /* process region pointer */
{
    int i, j, k;
    struct pregion *p;
    struct region r;
    KA_T na;
    enum lsof_fd_type ty;
    int fd_num;
    char tyb[8];

#if OSRV >= 500
    KA_T pc;
    struct pregion ps;
#endif /* OSRV>=500 */

    /*
     * Read and process the pregions.
     */

#if OSRV < 500
    if (kread(ctx, prp, (char *)Pr, Prsz))
        return;
    for (i = j = 0, p = Pr; i < Npp; i++, p++)
#else  /* OSRV>=500 */
    for (i = j = 0, p = &ps, pc = prp; pc; pc = (KA_T)p->p_next, i++)
#endif /* OSRV<500 */

    {

#if OSRV >= 500
        /*
         * Avoid infinite loop.
         */
        if (i > 1000) {
            if (!Fwarn)
                (void)fprintf(
                    stderr, "%s: too many virtual address regions for PID %d\n",
                    Pn, Lp->pid);
            return;
        }
        if ((i && pc == prp) || kread(ctx, (KA_T)pc, (char *)p, sizeof(ps)))
            return;
#endif /* OSRV>=500 */

        if (!p->p_reg)
            continue;
        /*
         * Read the region.
         * Skip entries with no node pointers and duplicate node addresses.
         */
        if (kread(ctx, (KA_T)p->p_reg, (char *)&r, sizeof(r)))
            continue;
        if (!(na = (KA_T)r.r_iptr))
            continue;
        for (k = 0; k < j; k++) {
            if (Nc[k] == na)
                break;
        }
        if (k < j)
            continue;
        /*
         * Cache the node address for duplicate checking.
         */
        if (!Nc) {
            if (!(Nc = (KA_T *)malloc((MALLOC_S)(sizeof(KA_T) * 10)))) {
                (void)fprintf(stderr, "%s: no txt ptr space, PID %d\n", Pn,
                              Lp->pid);
                Error(ctx);
            }
            Nn = 10;
        } else if (j >= Nn) {
            Nn += 10;
            if (!(Nc = (KA_T *)realloc((MALLOC_P *)Nc,
                                       (MALLOC_S)(Nn * sizeof(KA_T))))) {
                (void)fprintf(stderr, "%s: no more txt ptr space, PID %d\n", Pn,
                              Lp->pid);
                Error(ctx);
            }
        }
        Nc[j++] = na;
        /*
         * Save text node and mapped region information.
         */
        fd_num = -1;
        switch (p->p_type) {
        case PT_DATA: /* data and text of */
        case PT_TEXT: /* executing binaries */
            ty = LSOF_FD_PROGRAM_TEXT;
            break;
        case PT_LIBDAT: /* shared library data and */
        case PT_LIBTXT: /* COFF format text */
            ty = LSOF_FD_LIBRARY_TEXT;
            break;
        case PT_SHFIL: /* memory mapped file */
            ty = LSOF_FD_MEMORY;
            break;
        case PT_V86: /* virtual 8086 mode */
            ty = LSOF_FD_VIRTUAL_8086;
            break;
        case PT_VM86: /* MERGE386 vm86 region */
            ty = LSOF_FD_MERGE_386;
            break;
        default:
            ty = LSOF_FD_MMAP_UNKNOWN;
            fd_num = p->p_type & 0xff;
            break;
        }
        alloc_lfile(ctx, ty, fd_num);
        process_node(na);
        if (Lf->sf)
            link_lfile();
    }
}

/*
 * readfsinfo() - read file system information
 */

static void readfsinfo() {
    char buf[FSTYPSZ + 1];
    int i, len;

    if ((Fsinfomax = sysfs(GETNFSTYP)) == -1) {
        (void)fprintf(stderr, "%s: sysfs(GETNFSTYP) error: %s\n", Pn,
                      strerror(errno));
        Error(ctx);
    }
    if (Fsinfomax == 0)
        return;
    if (!(Fsinfo = (char **)malloc((MALLOC_S)(Fsinfomax * sizeof(char *))))) {
        (void)fprintf(stderr, "%s: no space for sysfs info\n", Pn);
        Error(ctx);
    }
    for (i = 1; i <= Fsinfomax; i++) {
        if (sysfs(GETFSTYP, i, buf) == -1) {
            (void)fprintf(stderr, "%s: sysfs(GETFSTYP) error: %s\n", Pn,
                          strerror(errno));
            Error(ctx);
        }
        buf[FSTYPSZ] = '\0';
        len = strlen(buf) + 1;
        if (!(Fsinfo[i - 1] = (char *)malloc((MALLOC_S)len))) {
            (void)fprintf(stderr, "%s: no space for file system entry %s\n", Pn,
                          buf);
            Error(ctx);
        }
        (void)snpf(Fsinfo[i - 1], len, "%s", buf);
    }
}

#if defined(HASNCACHE)

/*
 * Prepare for and #include the appropriate header files.
 */

#    if OSRV >= 500
#        undef IFIR
#        undef IFIW
#        undef IRCOLL
#        undef IWCOLL
#    endif /* OSRV>=500 */

#    include <sys/fs/s5inode.h>

#    if defined(HAS_NFS)
#        include <sys/fs/nfs/dnlc.h>
#    endif /* defined(HAS_NFS) */

#    if OSRV >= 500
#        if OSRV < 504
#            include <sys/fs/dtdnlc.h>
#            undef IFIR
#            undef IFIW
#            undef IRCOLL
#            undef IWCOLL
#            define _INKERNEL
#            include <sys/fs/htinode.h>
#            undef _INKERNEL
#        else /* OSRV>=504 */
#            include <sys/fs/dnlc.h>
#        endif /* OSRV<504 */
#    endif     /* OSRV>=500 */

/*
 * Determine the maximum size of the cache name character array.
 */

#    if OSRV < 504
#        define MAXNSZ DIRSIZ
#        if OSRV >= 500 && DTNCMAX > MAXNSZ
#            undef MAXNSZ
#            define MAXNSZ DTNCMAX
#        endif /* OSRV>=500 && DTNCMAX>MAXNSZ */
#    else      /* OSRV>=504 */
#        define MAXNSZ DNLC_NAMELEN
#    endif /* OSRV<504 */

#    if defined(HAS_NFS) && NC_NAMLEN > MAXNSZ
#        undef MAXNSZ
#        define MAXNSZ NC_NAMLEN
#    endif /* defined(HAS_NFS) && NC_NAMLEN>MAXNSZ */

/*
 * Define the local name cache structures.
 */

struct lnch { /* local name cache structure */
    union {
        struct ldev {              /* device-inode info */
            dev_t dev;             /* device */
            unsigned long inum;    /* inode number */
            unsigned long pa_inum; /* parent inode number */
        } ld;
        struct lnfs { /* NFS info */
            KA_T rp;  /* rnode address */
            KA_T dp;  /* parent rnode address */
        } ln;
    } u;
    char nm[MAXNSZ + 1]; /* name */
    unsigned char nl;    /* name length */
    unsigned char dup;   /* duplicate if 1 */
    unsigned char type;  /* type: 0 = device-inode; 1 = NFS */
    struct lnch *pa;     /* parent address */
    struct lnch *next;   /* link to next same-type structure */
};

struct lnch_hh {        /* device-inode and NFS hash head */
    struct lnch *hp[2]; /* [0] = device-inode; [1] = NFS*/
};

/*
 * Local name cache (LNC) definitions, macros, and static values
 */

#    define LCHUNKSZ                                                           \
        256             /* local "chunk" size for reading the                  \
                         * kernel DNLC -- used for OSRV>=504 */
static int LNC_asz = 0; /* LNC cache allocated size */
static int LNC_csz = 0; /* LNC cache current size */
#    define LNCHHLEN                                                           \
        64               /* hash head length (must be a                        \
                          * power of 2) */
#    define LNCINCR 256  /* LNC size increment */
#    define LNCINIT 1024 /* LNC initial size */

#    define DIN_hash(d, i)                                                     \
        &LNC_hh[((((int)(d + i) >> 2) * 31415) & (LNCHHLEN - 1))]

#    if defined(HAS_NFS)
#        define NFS_hash(r)                                                    \
            &LNC_hh[((((int)(r) >> 2) * 31415) & (LNCHHLEN - 1))]
#    endif /* defined(HAS_NFS) */

static struct lnch_hh *LNC_hh = (struct lnch_hh *)NULL;
/* LNC hash head pointers */
static struct lnch *LNC_nc = (struct lnch *)NULL;
/* the linear LNC */

/*
 * Local function prototypes
 */

static struct lnch *DIN_addr(dev_t *d, unsigned long i);

#    if OSRV >= 500
#        if OSRV >= 504
static void DNLC_load();
#        else  /* OSRV<504 */
static void DTFS_load();
static void HTFS_load();
#        endif /* OSRV>=504 */
#    endif     /* OSRV>=500 */

static int LNC_enter(struct lnch *le, char *nm, int nl, char *fs);
static void LNC_nosp(int len);

#    if defined(HAS_NFS)
static struct lnch *NFS_addr(KA_T r);
static void NFS_load(void);
static int NFS_root(KA_T r);
#    endif /* defined(HAS_NFS) */

#    if OSRV < 504
static void SYSV_load();
#    endif /* OSRV<504 */

/*
 * DIN_addr() - look up a node's local device-inode address
 */

static struct lnch *DIN_addr(dev_t *d,        /* device number */
                             unsigned long i) /* inode number */
{
    struct lnch_hh *hh = DIN_hash(*d, i);
    struct lnch *lc = hh->hp[0];

    while (lc) {
        if (lc->u.ld.dev == *d && lc->u.ld.inum == i)
            return (lc);
        lc = lc->next;
    }
    return ((struct lnch *)NULL);
}

#    if OSRV >= 504
/*
 * DNLC_load() - load DNLC cache
 */

static void DNLC_load() {
    int ccl;            /* current "chunk" length */
    int ccs;            /* current "chunk" size */
    int ccx;            /* current "chunk" index */
    static int cha = 0; /* "chunk" allocation size */
    static int chl = 0; /* "chunk" allocation length */
    struct dnlc__cachent *cp;
    static struct dnlc__cachent *dnlc = (struct dnlc__cachent *)NULL;
    static int dnlce = 0;
    int i, len;
    static KA_T ka = (KA_T)0;
    struct lnch lc;
    char nm[DNLC_NAMELEN + 1];
    KA_T v;
    char *wa; /* "working" kernel DNLC
               * address */
              /*
               * Do first-time setup, as required.
               */
    if (dnlce == 0) {

        /*
         * Quit if the DNLC name cache address is unknown.
         */
        if (get_Nl_value("dnlc", Drive_Nl, &ka) < 0 || !ka)
            return;
        /*
         * Determine of the DNLC name cache address is that of an array or a
         * pointer to the array.
         */
        v = (KA_T)NULL;
        if (get_Nl_value("pdnlc", Drive_Nl, &v) >= 0 && v) {

            /*
             * If the DNLC name cache address is that of a pointer to an array,
             * get the array's address.  If that fails, return without comment
             * and without further action.
             */
            if (kread(ctx, ka, (char *)&ka, sizeof(ka)))
                return;
        }
        /*
         * Get the kernel's DNLC name cache size.
         */
        if (get_Nl_value("ndnlc", Drive_Nl, &v) < 0 || !v ||
            kread(ctx, v, (char *)&dnlce, sizeof(dnlce)) || dnlce < 1)
            return;
        /*
         * Allocate space for a copy of a portion ("chunk") of the kernel's
         * DNLC name cache.
         */
        cha = (dnlce <= LCHUNKSZ) ? dnlce : LCHUNKSZ;
        chl = sizeof(struct dnlc__cachent) * cha;
        if (!(dnlc = (struct dnlc__cachent *)malloc(chl))) {
            (void)fprintf(stderr,
                          "%s: can't allocate %d bytes for DNLC chunk\n", Pn,
                          chl);
            cha = 0;
            Error(ctx);
        }
    }
    /*
     * Prepare to read the kernel's DNLC name cache.
     */
    if (!cha || !chl || !dnlc || !ka)
        return;
    /*
     * Build a local copy of the kernel's DNLC name cache, reading the kernel
     * data a "chunk" at a time.
     */
    nm[DNLC_NAMELEN] = '\0';
    lc.type = 0;
    for (ccl = ccs = i = 0, wa = (char *)ka; i < dnlce; i += ccs, wa += ccl) {

        /*
         * Read the next "chunk".
         */
        ccs = ((dnlce - i) < cha) ? (dnlce - i) : cha;
        ccl = sizeof(struct dnlc__cachent) * ccs;
        if (kread(ctx, (KA_T)wa, (char *)dnlc, ccl))
            break;
        /*
         * Process the "chunk".
         */
        for (ccx = 0, cp = dnlc; ccx < ccs; cp++, ccx++) {
            if (!cp->dev && !cp->newinum)
                continue;
            (void)strncpy(nm, cp->name, DNLC_NAMELEN);
            if ((len = strlen(nm)) < 1)
                continue;
            if (len < 3 && nm[0] == '.') {
                if (len == 1 || (len == 2 && nm[1] == '.'))
                    continue;
            }
            lc.u.ld.dev = cp->dev;
            lc.u.ld.inum = (unsigned long)cp->newinum;
            lc.u.ld.pa_inum = (unsigned long)cp->inum;
            if (LNC_enter(&lc, nm, len, "DNLC"))
                break;
        }
    }
    /*
     * If not repeating, free kernel DNLC name cache buffer space.
     */
    if (dnlc && !RptTm) {
        (void)free((MALLOC_P *)dnlc);
        dnlc = (struct dnlc__cachent *)NULL;
        dnlce = cha = chl = 0;
    }
}
#    endif /* OSRV>=504 */

#    if OSRV >= 500 && OSRV < 504
/*
 * DTFS_load() - load DTFS cache
 */

static void DTFS_load() {
    struct dtcachent *cp;
    static struct dtcachent *dtnc = (struct dtcachent *)NULL;
    static int dtnce = 0;
    int i, len;
    static KA_T ka = (KA_T)NULL;
    static int kcl = 0;
    struct lnch lc;
    char nm[DTNCMAX + 1];
    KA_T v;
    /*
     * Do first-time setup, as required.
     */
    if (dtnce == 0) {

        /*
         * Quit if the DTFS name cache address is unknown.
         */
        if (get_Nl_value("dtnc", Drive_Nl, &ka) < 0 || !ka)
            return;
        /*
         * Get the kernel's DTFS name cache size.
         */
        if (get_Nl_value("ndtnc", Drive_Nl, &v) < 0 || !v ||
            kread(ctx, v, (char *)&dtnce, sizeof(dtnce)) || dtnce < 1)
            return;
        /*
         * Allocate space for a copy of the kernel's DTFS name cache.
         */
        kcl = sizeof(struct dtcachent) * dtnce;
        if (!(dtnc = (struct dtcachent *)malloc(kcl))) {
            (void)fprintf(stderr,
                          "%s: can't allocate %d bytes for DTFS name cache\n",
                          Pn, kcl);
            Error(ctx);
        }
    }
    /*
     * Read the kernel's DTFS name cache.
     */
    if (!dtnc || !kcl || !ka || kread(ctx, ka, (char *)dtnc, kcl))
        return;
    /*
     * Build a local copy of the kernel's DTFS name cache.
     */
    lc.type = 0;
    nm[DTNCMAX] = '\0';
    for (cp = dtnc, i = 0; i < dtnce; cp++, i++) {
        if (!cp->dn_dev && !cp->dn_newinum)
            continue;
        (void)strncpy(nm, cp->dn_name, DTNCMAX);
        if ((len = strlen(nm)) < 1)
            continue;
        if (len < 3 && cp->dn_name[0] == '.') {
            if (len == 1 || (len == 2 && cp->dn_name[1] == '.'))
                continue;
        }
        lc.u.ld.dev = cp->dn_dev;
        lc.u.ld.inum = (unsigned long)cp->dn_newinum;
        lc.u.ld.pa_inum = (unsigned long)cp->dn_inum;
        if (LNC_enter(&lc, nm, len, "DTFS"))
            break;
    }
    /*
     * If not repeating, free kernel DTFS name cache buffer space.
     */
    if (dtnc && !RptTm) {
        (void)free((MALLOC_P *)dtnc);
        dtnc = (struct dtcachent *)NULL;
        dtnce = kcl = 0;
    }
}

/*
 * HTFS_load() - load HTFS cache
 */

static void HTFS_load() {
    struct htcachent *cp;
    static struct htcachent *htnc = (struct htcachent *)NULL;
    static int htnce = 0;
    int i, len;
    static KA_T ka = (KA_T)NULL;
    static int kcl = 0;
    struct lnch lc;
    char nm[DIRSIZ + 1];
    KA_T v;
    /*
     * Do first-time setup, as required.
     */
    if (htnce == 0) {

        /*
         * Quit if the HTFS name cache address is unknown.
         */
        if (get_Nl_value("htnc", Drive_Nl, &ka) < 0 || !ka)
            return;
        /*
         * Get the kernel's HTFS name cache size.
         */
        if (get_Nl_value("nhtnc", Drive_Nl, &v) < 0 || !v ||
            kread(ctx, v, (char *)&htnce, sizeof(htnce)) || htnce < 1)
            return;
        /*
         * Allocate space for a copy of the kernel's HTFS name cache.
         */
        kcl = sizeof(struct htcachent) * htnce;
        if (!(htnc = (struct htcachent *)malloc(kcl))) {
            (void)fprintf(stderr,
                          "%s: can't allocate %d bytes for HTFS name cache\n",
                          Pn, kcl);
            Error(ctx);
        }
    }
    /*
     * Read the kernel's HTFS name cache.
     */
    if (!htnc || !kcl || !ka || kread(ctx, ka, (char *)htnc, kcl))
        return;
    /*
     * Build a local copy of the kernel's HTFS name cache.
     */
    lc.type = 0;
    nm[DIRSIZ] = '\0';
    for (cp = htnc, i = 0; i < htnce; cp++, i++) {
        if (!cp->dev && !cp->newinum)
            continue;
        (void)strncpy(nm, cp->name, DIRSIZ);
        if ((len = strlen(nm)) < 1)
            continue;
        if (len < 3 && cp->name[0] == '.') {
            if (len == 1 || (len == 2 && cp->name[1] == '.'))
                continue;
        }
        lc.u.ld.dev = (dev_t)cp->dev;
        lc.u.ld.inum = (unsigned long)cp->newinum;
        lc.u.ld.pa_inum = (unsigned long)cp->inum;
        if (LNC_enter(&lc, nm, len, "HTFS"))
            break;
    }
    /*
     * If not repeating, free kernel HTFS name cache buffer space.
     */
    if (htnc && !RptTm) {
        (void)free((MALLOC_P *)htnc);
        htnc = (struct htcachent *)NULL;
        htnce = kcl = 0;
    }
}
#    endif /* OSRV>=500 && OSRV<504 */

/*
 * LNC_enter() - make a local name cache entry
 */

static int LNC_enter(struct lnch *le, /* skeleton local entry */
                     char *nm,        /* name */
                     int nl,          /* name length */
                     char *fs)        /* file system name */
{
    struct lnch *lc;
    MALLOC_S len;

    if (LNC_csz >= LNC_asz) {
        LNC_asz += LNCINCR;
        len = (MALLOC_S)(LNC_asz * sizeof(struct lnch));
        if (!(LNC_nc = (struct lnch *)realloc(LNC_nc, len))) {
            (void)fprintf(
                stderr, "%s: no more space for %d byte local name cache: %s\n",
                Pn, len, fs);
            Error(ctx);
        }
    }
    lc = &LNC_nc[LNC_csz];
    if ((lc->type = le->type) == 1) {

        /*
         * Make an NFS entry.
         */
        lc->u.ln.rp = le->u.ln.rp;
        lc->u.ln.dp = le->u.ln.dp;
    } else {

        /*
         * Make a device-inode entry.
         */
        lc->u.ld.dev = le->u.ld.dev;
        lc->u.ld.inum = le->u.ld.inum;
        lc->u.ld.pa_inum = le->u.ld.pa_inum;
    }
    /*
     * Enter the name and its size, clear the duplicate flag,
     * and advance the linear cache entry count.
     */
    if (nl > MAXNSZ) {
        if (!Fwarn)
            (void)fprintf(stderr,
                          "%s: WARNING: length for \"%s\" too large: %d\n", Pn,
                          nm, nl);
        nl = MAXNSZ;
    }
    (void)strncpy(lc->nm, nm, nl);
    lc->nm[nl] = '\0';
    lc->nl = (unsigned char)nl;
    lc->dup = 0;
    lc->next = lc->pa = (struct lnch *)NULL;
    LNC_csz++;
    return (0);
}

/*
 * LNC_nosp() - notify that we're out of space for the local name cache
 */

static void LNC_nosp(len) int len; /* attempted length */
{
    if (!Fwarn)
        (void)fprintf(stderr, "%s: no space for %d byte local name cache\n", Pn,
                      len);
    Error(ctx);
}

/*
 * ncache_load() - load the kernel's NFS and DEV name caches
 */

void ncache_load() {
    struct lnch_hh *hh;
    struct lnch *hl, *hlp, *lc;
    int f, i, len;

    if (!Fncache)
        return;
    /*
     * Initialize local name cache, as required.
     */
    if (LNC_asz == 0) {
        LNC_asz = LNCINIT;
        len = LNCINIT * sizeof(struct lnch);
        if (!(LNC_nc = (struct lnch *)malloc((MALLOC_S)len)))
            (void)LNC_nosp(len);
    }
    LNC_csz = 0;

#    if defined(HAS_NFS)
    /*
     * Load NFS cache.
     */
    (void)NFS_load();
#    endif /* defined(HAS_NFS) */

#    if OSRV < 504
    /*
     * Load the device-inode SYSV file system cache.
     */
    (void)SYSV_load();

#        if OSRV >= 500
    /*
     * Load the device-inode DT and HT file system caches.
     */
    (void)DTFS_load();
    (void)HTFS_load();
#        endif /* OSRV>=500 */
#    else      /* OSRV>=504 */
    /*
     *  Load the device-inode combined file system cache.
     */
    (void)DNLC_load();
#    endif     /* OSRV<504 */

    /*
     * Reduce local name cache memory usage, as required.
     */
    if (LNC_csz < 1) {
        LNC_csz = 0;
        if (!RptTm) {
            (void)free((FREE_P *)LNC_nc);
            LNC_nc = (struct lnch *)NULL;
        }
        return;
    }
    if (LNC_csz < LNC_asz && !RptTm) {
        len = LNC_csz * sizeof(struct lnch);
        if (!(LNC_nc = (struct lnch *)realloc(LNC_nc, len)))
            (void)LNC_nosp(len);
        LNC_asz = LNC_csz;
    }
    /*
     * Initialize hash head pointers.
     */
    if (!LNC_hh) {
        LNC_hh = (struct lnch_hh *)calloc(LNCHHLEN, sizeof(struct lnch_hh));
        if (!LNC_hh) {
            (void)fprintf(
                stderr,
                "%s: can't allocate %d bytes for name cache hash table\n", Pn,
                LNCHHLEN * sizeof(struct lnch_hh));
            Error(ctx);
        }
    } else
        (void)zeromem((void *)LNC_hh, (LNCHHLEN * sizeof(struct lnch_hh)));
    /*
     * Enter local name cache pointers in the hash table.  Look for entries with
     * the same identifications that have different names.
     */
    for (i = 0, lc = LNC_nc; i < LNC_csz; i++, lc++) {

#    if defined(HAS_NFS)
        if (lc->type)
            hh = NFS_hash(lc->u.ln.rp);
        else
#    endif /* defined(HAS_NFS) */

            hh = DIN_hash(lc->u.ld.dev, lc->u.ld.inum);
        if (!(hl = hh->hp[lc->type])) {
            hh->hp[lc->type] = lc;
            continue;
        }
        for (f = 0, hlp = hl; hl; hlp = hl, hl = hl->next) {

#    if defined(HAS_NFS)
            if (lc->type == 1) {
                if (lc->u.ln.rp != hl->u.ln.rp)
                    continue;
            } else
#    endif /* defined(HAS_NFS) */

            {
                if (lc->u.ld.dev != hl->u.ld.dev ||
                    lc->u.ld.inum != hl->u.ld.inum)
                    continue;
            }
            if (strcmp(lc->nm, hl->nm) == 0)
                f = 1;
            else {
                f = 2; /* same identifiers, different names */
                break;
            }
        }
        if (!f)
            hlp->next = lc;
        else if (f == 2) {

            /*
             * Since entries with the same identification but different names
             * were located, mark entries with the same identification as
             * duplicates.
             */
            for (hl = hh->hp[lc->type]; hl; hl = hl->next) {

#    if defined(HAS_NFS)
                if (lc->type == 1) {
                    if (lc->u.ln.rp == hl->u.ln.rp)
                        hl->dup = 1;
                    continue;
                }
#    endif /* defined(HAS_NFS) */

                if (hl->u.ld.dev == lc->u.ld.dev &&
                    hl->u.ld.inum == lc->u.ld.inum)
                    hl->dup = 1;
            }
        }
    }
    /*
     * Make a final pass through the local name cache and convert parent
     * identifications to local name cache pointers. Ignore duplicates.
     */
    for (i = 0, lc = LNC_nc; i < LNC_csz; i++, lc++) {
        if (lc->dup)
            continue;

#    if defined(HAS_NFS)
        if (lc->type == 1) {
            if (lc->u.ln.dp)
                lc->pa = NFS_addr(lc->u.ln.dp);
            continue;
        }
#    endif /* defined(HAS_NFS) */

        if (lc->u.ld.dev && lc->u.ld.pa_inum)
            lc->pa = DIN_addr(&lc->u.ld.dev, lc->u.ld.pa_inum);
    }
}

/*
 * ncache_lookup() - look up a node's name in the kernel's name caches
 */

char *ncache_lookup(char *buf, /* receiving name buffer */
                    int blen,  /* receiving buffer length */
                    int *fp)   /* full path reply */
{
    char *cp = buf;
    int nl, rlen;
    struct lnch *lc;

    *cp = '\0';
    *fp = 0;
    /*
     * If the entry has an inode number that matches the inode number of the
     * file system mount point, return an empty path reply.  That tells the
     * caller to print the file system mount point name only.
     */
    if (Lf->inp_ty == 1 && Lf->fs_ino && Lf->inode == Lf->fs_ino)
        return (cp);
    if (!LNC_nc)
        return ((char *)NULL);

#    if defined(HAS_NFS)
    /*
     * Look up the NFS name cache entry.
     */
    if (Lf->is_nfs) {
        if ((lc = NFS_addr(Lf->na)) && !lc->dup) {
            if ((nl = (int)lc->nl) > (blen - 1))
                return (*cp ? cp : (char *)NULL);
            cp = buf + blen - nl - 1;
            rlen = blen - nl - 1;
            (void)snpf(cp, nl + 1, "%s", lc->nm);
            /*
             * Look up the NFS name cache entries that are parents of the
             * rnode address.  Quit when:
             *
             *	there's no parent;
             *  the parent is a duplicate;
             *	the name is too large to fit in the receiving buffer.
             */
            for (;;) {
                if (!lc->pa) {
                    if (NFS_root(lc->u.ln.dp))
                        *fp = 1;
                    break;
                }
                lc = lc->pa;
                if (lc->dup)
                    break;
                if (((nl = (int)lc->nl) + 1) > rlen)
                    break;
                *(cp - 1) = '/';
                cp--;
                rlen--;
                (void)strncpy((cp - nl), lc->nm, nl);
                cp -= nl;
                rlen -= nl;
            }
            return (*cp ? cp : (char *)NULL);
        }
        return ((char *)NULL);
    }
#    endif /* defined(HAS_NFS) */

    /*
     * Look up the device-inode name cache entry.
     */
    if (Lf->dev_def && Lf->inp_ty == 1 &&
        (lc = DIN_addr(&Lf->dev, Lf->inode)) && !lc->dup) {
        if ((nl = (int)lc->nl) > (blen - 1))
            return (*cp ? cp : (char *)NULL);
        cp = buf + blen - nl - 1;
        rlen = blen - nl - 1;
        (void)snpf(cp, nl + 1, "%s", lc->nm);
        /*
         * Look up the LNC name cache entries that are parents of the
         * device and inode number.  Quit when:
         *
         *	there's no parent;
         *	the parent is a duplicate cache entry;
         *	the name is too large to fit in the receiving buffer.
         */
        for (;;) {
            if (!lc->pa) {
                if (lc->u.ld.pa_inum && Lf->fs_ino &&
                    lc->u.ld.pa_inum == Lf->fs_ino)
                    *fp = 1;
                break;
            }
            lc = lc->pa;
            if (lc->dup)
                break;
            if (lc->u.ld.inum && Lf->fs_ino && lc->u.ld.inum == Lf->fs_ino) {
                *fp = 1;
                break;
            }
            if (((nl = (int)lc->nl) + 1) > rlen)
                break;
            *(cp - 1) = '/';
            cp--;
            rlen--;
            (void)strncpy((cp - nl), lc->nm, nl);
            cp -= nl;
            rlen -= nl;
        }
        return (*cp ? cp : (char *)NULL);
    }
    return ((char *)NULL);
}

#    if defined(HAS_NFS)
/*
 * NFS_addr() - look up a node's local NFS_nc address
 */

static struct lnch *NFS_addr(r)
KA_T r; /* rnode's address */
{
    struct lnch_hh *hh = NFS_hash(r);
    struct lnch *lc = hh->hp[1];

    while (lc) {
        if (lc->u.ln.rp == r)
            return (lc);
        lc = lc->next;
    }
    return ((struct lnch *)NULL);
}

/*
 * NFS_load() -- load kernel's NFS name cache
 */

static void NFS_load() {
    struct ncache *cp;
    int i, len;
    struct lnch lc;
    static KA_T ka = (KA_T)NULL;
    static int kcl = 0;
    static struct ncache *nfnc = (struct ncache *)NULL;
    static int nfnce = 0;
    char nm[NC_NAMLEN + 1];
    KA_T v;
    /*
     * Do first-time setup, as required.
     */
    if (nfnce == 0) {

        /*
         * Quit if the NFS name cache address is unknown.
         */
        if (get_Nl_value("nfnc", Drive_Nl, &ka) < 0 || !ka)
            return;
        /*
         * Get the kernel's NFS name cache size.
         */
        if (get_Nl_value("nnfnc", Drive_Nl, &v) < 0 || !v ||
            kread(ctx, v, (char *)&nfnce, sizeof(nfnce)) || nfnce < 1)
            return;
        /*
         * Allocate space for a copy of the kernel's NFS name cache.
         */
        kcl = nfnce * sizeof(struct ncache);
        if (!(nfnc = (struct ncache *)malloc(kcl))) {
            (void)fprintf(stderr,
                          "%s: can't allocate %d bytes for NFS name cache\n",
                          Pn, kcl);
            Error(ctx);
        }
    }
    /*
     * Read the kernel's NFS name cache.
     */
    if (!nfnc || !kcl || !ka || kread(ctx, ka, (char *)nfnc, kcl))
        return;
    /*
     * Build a local copy of the kernel's NFS name cache.
     */
    lc.type = 1;
    for (cp = nfnc, i = 0; i < nfnce; cp++, i++) {
        if (!cp->rp)
            continue;
        if ((len = cp->namlen) < 0 || len > NC_NAMLEN)
            continue;
        (void)strncpy(nm, cp->name, len);
        nm[len] = '\0';
        if ((len = strlen(nm)) < 1)
            continue;
        if (len < 3 && nm[0] == '.') {
            if (len == 1 || (len == 2 && nm[1] == '.'))
                continue;
        }
        lc.u.ln.rp = (KA_T)cp->rp;
        lc.u.ln.dp = (KA_T)cp->dp;
        if (LNC_enter(&lc, nm, len, "NFS"))
            break;
    }
    /*
     * If not repeating, free kernel NFS name cache buffer space.
     */
    if (nfnc && !RptTm) {
        (void)free((MALLOC_P *)nfnc);
        nfnc = (struct ncache *)NULL;
        kcl = nfnce = 0;
    }
}

static int NFS_root(r)
KA_T r; /* node's rnode address */
{
    int i;
    MALLOC_S len;
    static int rnc = 0;
    static int rnca = 0;
    static KA_T *rc = (KA_T *)NULL;
    struct rnode rn;
    unsigned short *n;
    unsigned long nnum;

#        if OSRV >= 500
    unsigned short *n1;
#        endif /* OSRV>=500 */

    if (!Lf->fs_ino || !r)
        return (0);
    /*
     * Search NFS root rnode cache.
     */
    for (i = 0; i < rnc; i++) {
        if (rc[i] == r)
            return (1);
    }
    /*
     * Read rnode and get the node number.
     */
    if (kread(ctx, r, (char *)&rn, sizeof(rn)))
        return (0);

#        if OSRV < 500
    n = (unsigned short *)&rn.r_fh.fh_pad[14];
    if (!(nnum = (unsigned long)ntohs(*n)))
        nnum = (unsigned long)rn.r_fh.fh_u.fh_fgen_u;
#        else  /* OSRV>=500 */
    n = (unsigned short *)&rn.r_fh.fh_u.fh_fid_u[4];
    n1 = (unsigned short *)&rn.r_fh.fh_u.fh_fid_u[2];
    if (!(nnum = (unsigned long)*n))
        nnum = (unsigned long)*n1;
#        endif /* OSRV<500 */

    if (!nnum || nnum != Lf->fs_ino)
        return (0);
    /*
     * Add the rnode address to the NFS root rnode cache.
     */
    if (rnc >= rnca) {
        if (rnca == 0) {
            len = (MALLOC_S)(10 * sizeof(KA_T));
            if ((rc = (KA_T *)malloc(len)))
                rnca = 10;
        } else {
            len = (MALLOC_S)((rnca + 10) * sizeof(KA_T));
            if ((rc = (KA_T *)realloc(rc, len)))
                rnca += 10;
        }
        if (!rc) {
            (void)fprintf(stderr, "%s: no space for root rnode table\n", Pn);
            Error(ctx);
        }
    }
    rc[rnc++] = r;
    return (1);
}
#    endif /* defined(HAS_NFS) */

#    if OSRV < 504
/*
 * SYSV_load() - load SYSV cache
 */

static void SYSV_load() {
    struct s5cachent *cp;
    int i, len;
    static KA_T ka = (KA_T)NULL;
    static int kcl = 0;
    struct lnch lc;
    char nm[DIRSIZ + 1];
    static struct s5cachent *s5nc = (struct s5cachent *)NULL;
    static int s5nce = 0;
    KA_T v;
    /*
     * Do first-time setup, as required.
     */
    if (s5nce == 0) {

        /*
         * Quit if the SYSV name cache address is unknown.
         */
        if (get_Nl_value("s5nc", Drive_Nl, &ka) < 0 || !ka)
            return;
            /*
             * Get the kernel's SYSV name cache size.
             */

#        if OSRV < 500
        s5nce = Var.v_s5cacheents;
#        else  /* OSRV>=500 */
        if (get_Nl_value("nsfnc", Drive_Nl, &v) < 0 || !v ||
            kread(ctx, v, (char *)&s5nce, sizeof(s5nce)))
            return;
#        endif /* OSRV<500 */

        if (s5nce < 1)
            return;
        /*
         * Allocate space for a copy of the kernel's SYSV name cache.
         */
        kcl = sizeof(struct s5cachent) * s5nce;
        if (!(s5nc = (struct s5cachent *)malloc(kcl))) {
            (void)fprintf(stderr,
                          "%s: can't allocate %d bytes for SYSV name cache\n",
                          Pn, kcl);
            Error(ctx);
        }
    }
    /*
     * Read the kernel's SYSV name cache.
     */
    if (!s5nc || !kcl || !ka || kread(ctx, ka, (char *)s5nc, kcl))
        return;
    /*
     * Build a local copy of the kernel's SYSV name cache.
     */
    nm[DIRSIZ] = '\0';
    lc.type = 0;
    for (cp = s5nc, i = 0; i < s5nce; cp++, i++) {
        if (!cp->dev && !cp->newinum)
            continue;
        (void)strncpy(nm, cp->name, DIRSIZ);
        if ((len = strlen(nm)) < 1)
            continue;
        if (len < 3 && cp->name[0] == '.') {
            if (len == 1 || (len == 2 && cp->name[1] == '.'))
                continue;
        }
        lc.u.ld.dev = (dev_t)cp->dev;
        lc.u.ld.inum = (unsigned long)cp->newinum;
        lc.u.ld.pa_inum = (unsigned long)cp->inum;
        if (LNC_enter(&lc, nm, len, "SYSV"))
            break;
    }
    /*
     * If not repeating, free kernel SYSV name cache buffer space.
     */
    if (s5nc && !RptTm) {
        (void)free((MALLOC_P *)s5nc);
        s5nc = (struct s5cachent *)NULL;
        kcl = s5nce = 0;
    }
}
#    endif /* OSRV<504 */
#endif     /* defined(HASNCACHE) */
