/*
 * rnch.c -- Sun format name cache functions for lsof library
 */

/*
 * Copyright 1997 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.
 */

#include "common.h"
#include "machine.h"

#if defined(HASNCACHE) && defined(USE_LIB_RNCH)

/*
 * rnch.c - read Sun format (struct ncache) name cache
 *
 * This code is effective only when HASNCACHE is defined.
 */

/*
 * The caller must:
 *
 *	#include the relevant header file -- e.g., <sys/dnlc.h>.
 *
 *	Define X_NCSIZE as the nickname for the kernel cache size variable,
 *	or, if X_NCSIZE is undefined, define FIXED_NCSIZE as the size of the
 *	kernel cache.
 *
 *	Define X_NCACHE as the nickname for the kernel cache address and
 *	define ADDR_NCACHE if the address is the address of the cache,
 *	rather than the address of a pointer to it.
 *
 *	Define NCACHE_NXT if the kernel's name cache is a linked list, starting
 *	at the X_NCACHE address, rather than a table, starting at that address.
 *
 *	Define any of the following casts that differ from their defaults:
 *
 *		NCACHE_SZ_CAST	cast for X_NCACHE (default int)
 *
 * The caller may:
 *
 *	Define NCACHE_DP	as the name of the element in the
 *				ncache structure that contains the
 *				parent vnode pointer.
 *
 *				Default: dp
 *
 *	Define NCACHE_NAME	as the name of the element in the
 *				ncache structure that contains the
 *				name.
 *
 *				Default: name
 *
 *	Define NCACHE_NAMLEN	as the name of the element in the
 *				ncache structure that contains the
 *				name length.
 *
 *				Deafult: namlen
 *
 *	Define NCACHE_NEGVN	as the name of the name list element
 *				whose value is a vnode address to
 *				ignore when loading the kernel name
 *				cache.
 *
 *	Define NCACHE_NODEID	as the name of the element in the
 *				ncache structure that contains the
 *				vnode's capability ID.
 *
 *	Define NCACHE_PARID	as the name of the element in the
 *				ncache structure that contains the
 *				parent vnode's capability ID.
 *
 *	Define NCACHE_VP	as the name of the element in the
 *				ncache structure that contains the
 *				vnode pointer.
 *
 *				Default: vp
 *
 * Note: if NCACHE_NODEID is defined, then NCACHE_PARID must be defined.
 *
 *
 * The caller must:
 *
 *	Define this prototype for ncache_load():
 *
 *		_PROTOTYPE(void ncache_load,(void));
 */

/*
 * Local static values
 */

static int Mch; /* name cache hash mask */

#    if !defined(NCACHE_NC_CAST)
#        define NCACHE_SZ_CAST int
#    endif /* !defined(NCACHE_NC_CAST) */

static NCACHE_SZ_CAST Nc = 0; /* size of name cache */
static int Nch = 0;           /* size of name cache hash pointer
                               * table */
struct l_nch {
    KA_T vp;          /* vnode address */
    KA_T dp;          /* parent vnode address */
    struct l_nch *pa; /* parent Ncache address */

#    if defined(NCACHE_NODEID)
    unsigned long id;  /* node's capability ID */
    unsigned long did; /* parent node's capability ID */
#    endif             /* defined(NCACHE_NODEID) */

    char *nm; /* name */
    int nl;   /* name length */
};

static struct l_nch *Ncache = (struct l_nch *)NULL;
/* the local name cache */
static struct l_nch **Nchash = (struct l_nch **)NULL;
/* Ncache hash pointers */
static int Ncfirst = 1; /* first-call status */

#    if defined(NCACHE_NEGVN)
static KA_T NegVN = (KA_T)NULL; /* negative vnode address */
static int NegVNSt = 0;         /* NegVN status: 0 = not loaded */
#    endif                      /* defined(NCACHE_NEGVN) */

#    if defined(NCACHE_NODEID)
static struct l_nch *ncache_addr(unsigned long i, KA_T v);
#        define ncachehash(i, v)                                               \
            Nchash + (((((int)(v) >> 2) + ((int)(i))) * 31415) & Mch)
#    else /* !defined(NCACHE_NODEID) */
static struct l_nch *ncache_addr(KA_T v);
#        define ncachehash(v) Nchash + ((((int)(v) >> 2) * 31415) & Mch)
#    endif /* defined(NCACHE_NODEID) */

static int ncache_isroot(KA_T va, char *cp);

#    define DEFNCACHESZ 1024 /* local size if X_NCSIZE kernel value < 1 */
#    define LNCHINCRSZ 64    /* local size increment */

#    if !defined(NCACHE_DP)
#        define NCACHE_DP dp
#    endif /* !defined(NCACHE_DP) */

#    if !defined(NCACHE_NAME)
#        define NCACHE_NAME name
#    endif /* !defined(NCACHE_NAME) */

#    if !defined(NCACHE_NAMLEN)
#        define NCACHE_NAMLEN namlen
#    endif /* !defined(NCACHE_NAMLEN) */

#    if !defined(NCACHE_VP)
#        define NCACHE_VP vp
#    endif /* !defined(NCACHE_VP) */

/*
 * ncache_addr() - look up a node's local ncache address
 */

static struct l_nch *ncache_addr(
#    if defined(NCACHE_NODEID)
    unsigned long i, /* capability ID */
#    endif           /* defined(NCACHE_NODEID) */
    KA_T v)          /* vnode's address */
{
    struct l_nch **hp;

#    if defined(NCACHE_NODEID)
    for (hp = ncachehash(i, v); *hp; hp++)
#    else  /* !defined(NCACHE_NODEID) */
    for (hp = ncachehash(v); *hp; hp++)
#    endif /* defined(NCACHE_NODEID) */

    {

#    if defined(NCACHE_NODEID)
        if ((*hp)->vp == v && (*hp)->id == i)
#    else  /* !defined(NCACHE_NODEID) */
        if ((*hp)->vp == v)
#    endif /* defined(NCACHE_NODEID) */

            return (*hp);
    }
    return ((struct l_nch *)NULL);
}

/*
 * ncache_isroot() - is head of name cache path a file system root?
 */

static int ncache_isroot(KA_T va,  /* kernel vnode address */
                         char *cp) /* partial path */
{
    char buf[MAXPATHLEN];
    int i;
    MALLOC_S len;
    struct mounts *mtp;
    struct stat sb;
    struct vnode v;
    static int vca = 0;
    static int vcn = 0;
    static KA_T *vc = (KA_T *)NULL;

    if (!va)
        return (0);
    /*
     * Search the root vnode cache.
     */
    for (i = 0; i < vcn; i++) {
        if (va == vc[i])
            return (1);
    }
    /*
     * Read the vnode and see if it's a VDIR node with the VROOT flag set.  If
     * it is, then the path is complete.
     *
     * If it isn't, and if the file has an inode number, search the mount table
     * and see if the file system's inode number is known.  If it is, form the
     * possible full path, safely stat() it, and see if it's inode number
     * matches the one we have for this file.  If it does, then the path is
     * complete.
     */
    if (kread(ctx, (KA_T)va, (char *)&v, sizeof(v)) || v.v_type != VDIR ||
        !(v.v_flag & VROOT)) {

        /*
         * The vnode tests failed.  Try the inode tests.
         */
        if (Lf->inp_ty != 1 || !Lf->inode || !Lf->fsdir ||
            (len = strlen(Lf->fsdir)) < 1)
            return (0);
        if ((len + 1 + strlen(cp) + 1) > sizeof(buf))
            return (0);
        for (mtp = readmnt(); mtp; mtp = mtp->next) {
            if (!mtp->dir || !mtp->inode)
                continue;
            if (strcmp(Lf->fsdir, mtp->dir) == 0)
                break;
        }
        if (!mtp)
            return (0);
        (void)strcpy(buf, Lf->fsdir);
        if (buf[len - 1] != '/')
            buf[len++] = '/';
        (void)strcpy(&buf[len], cp);
        if (statsafely(buf, &sb) != 0 || (unsigned long)sb.st_ino != Lf->inode)
            return (0);
    }
    /*
     * Add the vnode address to the root vnode cache.
     */
    if (vcn >= vca) {
        vca += 10;
        len = (MALLOC_S)(vca * sizeof(KA_T));
        if (!vc)
            vc = (KA_T *)malloc(len);
        else
            vc = (KA_T *)realloc(vc, len);
        if (!vc) {
            (void)fprintf(stderr, "%s: no space for root vnode table\n", Pn);
            Error(ctx);
        }
    }
    vc[vcn++] = va;
    return (1);
}

/*
 * ncache_load() - load the kernel's name cache
 */

void ncache_load(struct lsof_context *ctx) {
    char *cp, *np;
    struct l_nch **hp, *lc;
    int i, len, n;
    static int iNc = 0;
    struct ncache *kc;
    static KA_T kp = (KA_T)NULL;
    KA_T v;

#    if defined(HASDNLCPTR)
    static int na = 0;
    static char *nb = (char *)NULL;
#    endif /* defined(HASDNLCPTR) */

#    if defined(NCACHE_NXT)
    static KA_T kf;
    struct ncache nc;
#    else  /* !defined(NCACHE_NXT) */
    static struct ncache *kca = (struct ncache *)NULL;
#    endif /* defined(NCACHE_NXT) */

    if (!Fncache)
        return;
    if (Ncfirst) {

        /*
         * Do startup (first-time) functions.
         */
        Ncfirst = 0;
        /*
         * Establish kernel cache size.
         */

#    if defined(X_NCSIZE)
        v = (KA_T)0;
        if (get_Nl_value(X_NCSIZE, (struct drive_Nl *)NULL, &v) < 0 || !v ||
            kread(ctx, (KA_T)v, (char *)&Nc, sizeof(Nc))) {
            if (!Fwarn)
                (void)fprintf(stderr,
                              "%s: WARNING: can't read name cache size: %s\n",
                              Pn, print_kptr(v, (char *)NULL, 0));
            iNc = Nc = 0;
            return;
        }
        iNc = Nc;
#    else  /* !defined(X_NCSIZE) */
        iNc = Nc = FIXED_NCSIZE;
#    endif /* defined(X_NCSIZE) */

        if (Nc < 1) {
            if (!Fwarn) {
                (void)fprintf(stderr,
                              "%s: WARNING: kernel name cache size: %d\n", Pn,
                              Nc);
                (void)fprintf(stderr, "      Cache size assumed to be: %d\n",
                              DEFNCACHESZ);
            }
            iNc = Nc = DEFNCACHESZ;
        }

#    if defined(NCACHE_NEGVN)
        /*
         * Get negative vnode address.
         */
        if (!NegVNSt) {
            if (get_Nl_value(NCACHE_NEGVN, (struct drive_Nl *)NULL, &NegVN) < 0)
                NegVN = (KA_T)NULL;
            NegVNSt = 1;
        }
#    endif /* defined(NCACHE_NEGVN) */

        /*
         * Establish kernel cache address.
         */

#    if defined(ADDR_NCACHE)
        kp = (KA_T)0;
        if (get_Nl_value(X_NCACHE, (struct drive_Nl *)NULL, (KA_T *)&kp) < 0 ||
            !kp) {
            if (!Fwarn)
                (void)fprintf(stderr, "%s: WARNING: no name cache address\n",
                              Pn);
            iNc = Nc = 0;
            return;
        }
#    else  /* !defined(ADDR_NCACHE) */
        v = (KA_T)0;
        if (get_Nl_value(X_NCACHE, (struct drive_Nl *)NULL, &v) < 0 || !v ||
            kread(ctx, (KA_T)v, (char *)&kp, sizeof(kp))) {
            if (!Fwarn)
                (void)fprintf(stderr,
                              "%s: WARNING: can't read name cache ptr: %s\n",
                              Pn, print_kptr(v, (char *)NULL, 0));
            iNc = Nc = 0;
            return;
        }
#    endif /* defined(ADDR_NCACHE) */

        /*
         * Allocate space for a local copy of the kernel's cache.
         */

#    if !defined(NCACHE_NXT)
        len = Nc * sizeof(struct ncache);
        if (!(kca = (struct ncache *)malloc((MALLOC_S)len))) {
            if (!Fwarn)
                (void)fprintf(stderr,
                              "%s: can't allocate name cache space: %d\n", Pn,
                              len);
            Error(ctx);
        }
#    endif /* !defined(NCACHE_NXT) */

        /*
         * Allocate space for the local cache.
         */
        len = Nc * sizeof(struct l_nch);
        if (!(Ncache = (struct l_nch *)calloc(Nc, sizeof(struct l_nch)))) {

        no_local_space:

            if (!Fwarn)
                (void)fprintf(stderr,
                              "%s: no space for %d byte local name cache\n", Pn,
                              len);
            Error(ctx);
        }
    } else {

        /*
         * Do setup for repeat calls.
         */
        if (!iNc)
            return;
        if (Nchash) {
            (void)free((FREE_P *)Nchash);
            Nchash = (struct l_nch **)NULL;
        }
        if (Ncache) {

            /*
             * Free space malloc'd to names in local name cache.
             */
            for (i = 0, lc = Ncache; i < Nc; i++, lc++) {
                if (lc->nm) {
                    (void)free((FREE_P *)lc->nm);
                    lc->nm = (char *)NULL;
                }
            }
        }
        Nc = iNc;

#    if defined(NCACHE_NXT)
        kp = kf;
#    endif /* defined(NCACHE_NXT) */
    }

#    if !defined(NCACHE_NXT)

    /*
     * Read the kernel's name cache.
     */
    if (kread(ctx, kp, (char *)kca, (Nc * sizeof(struct ncache)))) {
        if (!Fwarn)
            (void)fprintf(stderr,
                          "%s: WARNING: can't read kernel's name cache: %s\n",
                          Pn, print_kptr(kp, (char *)NULL, 0));
        Nc = 0;
        return;
    }
#    endif /* !defined(NCACHE_NXT) */

    /*
     * Build a local copy of the kernel name cache.
     */

#    if defined(NCACHE_NXT)
    for (i = iNc * 16, kc = &nc, kf = kp, lc = Ncache, n = 0; kp;)
#    else  /* !defined(NCACHE_NXT) */
    for (i = n = 0, kc = kca, lc = Ncache; i < Nc; i++, kc++)
#    endif /* defined(NCACHE_NXT) */

    {

#    if defined(NCACHE_NXT)
        if (kread(ctx, kp, (char *)kc, sizeof(nc)))
            break;
        if ((kp = (KA_T)kc->NCACHE_NXT) == kf)
            kp = (KA_T)NULL;
#    endif /* defined(NCACHE_NXT) */

        if (!kc->NCACHE_VP || (len = kc->NCACHE_NAMLEN) < 1)
            continue;

#    if defined(NCACHE_NEGVN)
        if (NegVN && ((KA_T)kc->NCACHE_VP == NegVN))
            continue;
#    endif /* defined(NCACHE_NEGVN) */

#    if defined(HASDNLCPTR)
        /*
         * Read name from kernel to a temporary buffer.
         */
        if (len > na) {
            na = len;
            if (!nb)
                nb = (char *)malloc(na);
            else
                nb = (char *)realloc((MALLOC_P *)nb, na);
            if (!nb) {
                (void)fprintf(
                    stderr,
                    "%s: can't allocate %d byte temporary name buffer\n", Pn,
                    na);
                Error(ctx);
            }
        }
        if (!kc->NCACHE_NAME || kread(ctx, (KA_T)kc->NCACHE_NAME, nb, len))
            continue;
        np = nb;
#    else  /* !defined(HASDNLCPTR) */
        /*
         * Use name that is in the kernel cache entry.
         */
        if (len > NC_NAMLEN)
            continue;
        np = kc->NCACHE_NAME;
#    endif /* defined(HASDNLCPTR) */

        if (len < 3 && *np == '.') {
            if (len == 1 || (len == 2 && np[1] == '.'))
                continue;
        }
        /*
         * Allocate space for name in local cache entry.
         */
        if (!(cp = (char *)malloc(len + 1))) {
            (void)fprintf(
                stderr, "%s: can't allocate %d bytes for name cache name: %s\n",
                Pn, len + 1, np);
            Error(ctx);
        }
        (void)strncpy(cp, np, len);
        cp[len] = '\0';

#    if defined(NCACHE_NXT)
        if (n >= Nc) {

            /*
             * Allocate more local space to receive the kernel's linked
             * entries.
             */
            Nc += LNCHINCRSZ;
            if (!(Ncache = (struct l_nch *)realloc(
                      Ncache, (MALLOC_S)(Nc * sizeof(struct l_nch))))) {
                (void)fprintf(
                    stderr, "%s: no more space for %d entry local name cache\n",
                    Pn, Nc);
                Error(ctx);
            }
            lc = &Ncache[n];
            iNc = Nc;
        }
#    endif /* defined(NCACHE_NXT) */

        /*
         * Complete the local cache entry.
         */
        lc->vp = (KA_T)kc->NCACHE_VP;
        lc->dp = (KA_T)kc->NCACHE_DP;
        lc->pa = (struct l_nch *)NULL;
        lc->nm = cp;
        lc->nl = len;

#    if defined(NCACHE_NODEID)
        lc->id = (unsigned long)kc->NCACHE_NODEID;
        lc->did = (unsigned long)kc->NCACHE_PARID;
#    endif /* defined(NCACHE_NODEID) */

        n++;
        lc++;

#    if defined(NCACHE_NXT)
        if (n >= i) {
            if (!Fwarn)
                (void)fprintf(
                    stderr, "%s: WARNING: name cache truncated at %d entries\n",
                    Pn, n);
            break;
        }
#    endif /* defined(NCACHE_NXT) */
    }
    /*
     * Reduce memory usage, as required.
     */

#    if !defined(NCACHE_NXT)
    if (!RptTm)
        (void)free((FREE_P *)kca);
#    endif /* !defined(NCACHE_NXT) */

    if (n < 1) {
        if (!RptTm && Ncache) {

            /*
             * If not in repeat mode, free the space that has been malloc'd
             * to the local name cache.
             */
            for (i = 0, lc = Ncache; i < Nc; i++, lc++) {
                if (lc->nm) {
                    (void)free((FREE_P *)lc->nm);
                    lc->nm = (char *)NULL;
                }
            }
            (void)free((FREE_P *)Ncache);
            Ncache = (struct l_nch *)NULL;
            Nc = 0;
        }
        if (!Fwarn)
            (void)fprintf(stderr, "%s: WARNING: unusable name cache size: %d\n",
                          Pn, n);
        return;
    }
    if (n < Nc) {
        Nc = n;
        if (!RptTm) {
            len = Nc * sizeof(struct l_nch);
            if (!(Ncache = (struct l_nch *)realloc(Ncache, len)))
                goto no_local_space;
        }
    }
    /*
     * Build a hash table to locate Ncache entries.
     */
    for (Nch = 1; Nch < Nc; Nch <<= 1)
        ;
    Nch <<= 1;
    Mch = Nch - 1;
    if (!(Nchash = (struct l_nch **)calloc(Nch + Nc, sizeof(struct l_nch *)))) {
        if (!Fwarn)
            (void)fprintf(stderr,
                          "%s: no space for %d name cache hash pointers\n", Pn,
                          Nch + Nc);
        Error(ctx);
    }
    for (i = 0, lc = Ncache; i < Nc; i++, lc++) {

#    if defined(NCACHE_NODEID)
        for (hp = ncachehash(lc->id, lc->vp), n = 1; *hp; hp++)
#    else  /* !defined(NCACHE_NODEID) */
        for (hp = ncachehash(lc->vp), n = 1; *hp; hp++)
#    endif /* defined(NCACHE_NODEID) */

        {
            if ((*hp)->vp == lc->vp && strcmp((*hp)->nm, lc->nm) == 0 &&
                (*hp)->dp == lc->dp

#    if defined(NCACHE_NODEID)
                && (*hp)->id == lc->id && (*hp)->did == lc->did
#    endif /* defined(NCACHE_NODEID) */

            ) {
                n = 0;
                break;
            }
        }
        if (n)
            *hp = lc;
    }
    /*
     * Make a final pass through the local cache and convert parent vnode
     * addresses to local name cache pointers.
     */
    for (i = 0, lc = Ncache; i < Nc; i++, lc++) {
        if (!lc->dp)
            continue;

#    if defined(NCACHE_NEGVN)
        if (NegVN && (lc->dp == NegVN)) {
            lc->pa = (struct l_nch *)NULL;
            continue;
        }
#    endif /* defined(NCACHE_NEGVN) */

#    if defined(NCACHE_NODEID)
        lc->pa = ncache_addr(lc->did, lc->dp);
#    else  /* !defined(NCACHE_NODEID) */
        lc->pa = ncache_addr(lc->dp);
#    endif /* defined(NCACHE_NODEID) */
    }
}

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

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

    *cp = '\0';
    *fp = 0;

#    if defined(HASFSINO)
    /*
     * 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);
#    endif /* defined(HASFSINO) */

    /*
     * Look up the name cache entry for the node address.
     */
    if (!Nc

#    if defined(NCACHE_NODEID)
        || !(lc = ncache_addr(Lf->id, Lf->na))
#    else  /* !defined(NCACHE_NODEID) */
        || !(lc = ncache_addr(Lf->na))
#    endif /* defined(NCACHE_NODEID) */

    ) {

        /*
         * If the node has no cache entry, see if it's the mount
         * point of a known file system.
         */
        if (!Lf->fsdir || !Lf->dev_def || Lf->inp_ty != 1)
            return ((char *)NULL);
        for (mtp = readmnt(); mtp; mtp = mtp->next) {
            if (!mtp->dir || !mtp->inode)
                continue;
            if (Lf->dev == mtp->dev && mtp->inode == Lf->inode &&
                strcmp(mtp->dir, Lf->fsdir) == 0)
                return (cp);
        }
        return ((char *)NULL);
    }
    /*
     * Begin the path assembly.
     */
    if ((nl = lc->nl) > (blen - 1))
        return ((char *)NULL);
    cp = buf + blen - nl - 1;
    rlen = blen - nl - 1;
    (void)strcpy(cp, lc->nm);
    /*
     * Look up the name cache entries that are parents of the node address.
     * Quit when:
     *
     *	there's no parent;
     *	the name is too large to fit in the receiving buffer.
     */
    for (;;) {
        if (!lc->pa) {
            if (ncache_isroot(lc->dp, cp))
                *fp = 1;
            break;
        }
        lc = lc->pa;
        if (((nl = lc->nl) + 1) > rlen)
            break;
        *(cp - 1) = '/';
        cp--;
        rlen--;
        (void)strncpy((cp - nl), lc->nm, nl);
        cp -= nl;
        rlen -= nl;
    }
    return (cp);
}
#else  /* !defined(HASNCACHE) || !defined(USE_LIB_RNCH) */
char rnch_d1[] = "d";
char *rnch_d2 = rnch_d1;
#endif /* defined(HASNCACHE) && defined(USE_LIB_RNCH) */
