/*
 * rnmh.c -- functions to read BSD format name cache information from a
 *	     kernel hash table
 */


/*
 * 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 "../machine.h"

#if	defined(HASNCACHE) && defined(USE_LIB_RNMH)

# if	!defined(lint)
static char copyright[] =
"@(#) Copyright 1997 Purdue Research Foundation.\nAll rights reserved.\n";
# endif	/* !defined(lint) */

#include "../lsof.h"


/*
 * rnmh.c - read BSD format hashed kernel name cache
 */

/*
 * The caller must:
 *
 *	#include the relevant header file -- e.g., <sys/namei.h>.
 *
 *	Define X_NCACHE as the nickname for the kernel cache hash tables
 *	address.
 *
 *	Define X_NCSIZE as the nickname for the size of the kernel cache has
 *	table length.
 *
 *	Define NCACHE_NO_ROOT if the calling dialect doesn't support
 *	the locating of the root node of a file system.
 *
 *	Define the name of the name cache structure -- e.g.,
 *
 *		#define NCACHE	<structure name>
 *
 *
 *	Define the following casts, if they differ from the defaults:
 *
 *		NCACHE_SZ_CAST	case for X_NCSIZE (default unsigned long)
 *
 *	Define the names of these elements of struct NCACHE:
 *
 *		#define NCACHE_NM	<name>
 *		#define NCACHE_NXT	<link to next entry>
 *		#define NCACHE_NODEADDR	<node address>
 *		#define NCACHE_PARADDR	<parent node address>
 *
 *	Optionally define:
 *
 *		#define NCACHE_NMLEN	<name length>
 *
 *	Optionally define *both*:
 *
 *		#define	NCACHE_NODEID	<node capability ID>
 *		#define	NCACHE_PARID	<parent node capability ID>
 *
 * The caller may need to:
 *
 *	Define this prototype for ncache_load():
 *
 *		_PROTOTYPE(static void ncache_load,(void));
 *
 *	Define NCACHE_VROOT to be the value of the flag that signifies that
 *	the vnode is the root of its file system.
 *
 *		E.g., for BSDI >= 5:
 *
 *			#define NCACHE_VROOT	VV_ROOT
 *
 *	If not defined, NCACHE_VROOT is defined as "VROOT".
 *
 *	Define VNODE_VFLAG if the vnode's flag member's name isn't v_flag.
 *
 * Note: if NCHNAMLEN is defined, the name is assumed to be in
 * NCACHE_NM[NCHNAMLEN]; if it isn't defined, the name is assumed to be in an
 * extension that begins at NCACHE_NM[0].
 *
 * Note: if NCACHE_NMLEN is not defined, then NCACHE_NM must be a pointer to
 * a kernel allocated, NUL-terminated, string buffer.
 */


/*
 * Casts
 */

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


/*
 * Flags
 */

# if	!defined(NCACHE_NMLEN)
#undef	NCHNAMLEN
# endif	/* !defined(NCACHE_NMLEN) */

# if	!defined(NCACHE_VROOT)
#define	NCACHE_VROOT	VROOT		/* vnode is root of its file system */
# endif	/* !defined(NCACHE_VROOT) */

# if	!defined(VNODE_VFLAG)
#define	VNODE_VFLAG	v_flag
# endif	/* !defined(VNODE_VFLAG) */


/*
 * Local static values
 */

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

struct l_nch {
	KA_T na;			/* node address */
	KA_T pa;			/* parent node address */
	struct l_nch *pla;		/* parent local node address */
	int nl;				/* name length */
	struct l_nch *next;		/* next entry */

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

# if	defined(NCHNAMLEN)
	char nm[NCHNAMLEN + 1];		/* name */
# else	/* !defined(NCHNAMLEN) */
	char nm[1];			/* variable length name */
# endif	/* defined(NCHNAMLEN) */

};

static struct l_nch *Ncache = (struct l_nch *)NULL;
					/* the head of the local name cache */
static struct l_nch **Nchash = (struct l_nch **)NULL;
					/* Ncache hash pointers */

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

# if	!defined(NCACHE_NO_ROOT)
_PROTOTYPE(static int ncache_isroot,(KA_T na, char *cp));
# endif	/* !defined(NCACHE_NO_ROOT) */


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

static struct l_nch *

# if	defined(NCACHE_NODEID)
ncache_addr(i, na)
	unsigned long i;		/* node's capability ID */
# else	/* !defined(NCACHE_NODEID) */
ncache_addr(na)
# endif	/* defined(NCACHE_NODEID) */

	KA_T na;			/* node's address */
{
	struct l_nch **hp;

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

	{

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

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


# if	!defined(NCACHE_NO_ROOT)
/*
 * ncache_isroot() - is head of name cache path a file system root?
 */

static int
ncache_isroot(na, cp)
	KA_T na;			/* kernel node address */
	char *cp;			/* partial path */
{
	char buf[MAXPATHLEN];
	int i;
	MALLOC_S len;
	struct mounts *mtp;
	static int nca = 0;
	static int ncn = 0;
	static KA_T *nc = (KA_T *)NULL;
	struct stat sb;
	struct vnode v;

	if (!na)
	    return(0);
/*
 * Search the root vnode cache.
 */
	for (i = 0; i < ncn; i++) {
	    if (na == nc[i])
		return(1);
	}
/*
 * Read the vnode and see if it's a VDIR node with the NCACHE_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((KA_T)na, (char *)&v, sizeof(v))
	||  v.v_type != VDIR || !(v.VNODE_VFLAG & NCACHE_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 node address to the root node cache.
 */
	if (ncn >= nca) {
	    if (!nca) {
		len = (MALLOC_S)(10 * sizeof(KA_T));
		nc = (KA_T *)malloc(len);
	    } else {
		len = (MALLOC_S)((nca + 10) * sizeof(KA_T));
		nc = (KA_T *)realloc(nc, len);
	    }
	    if (!nc) {
		(void) fprintf(stderr, "%s: no space for root node table\n",
		    Pn);
		Exit(1);
	    }
	    nca += 10;
	}
	nc[ncn++] = na;
	return(1);
}
# endif	/* !defined(NCACHE_NO_ROOT) */


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

void
ncache_load()
{
	struct NCACHE c;
	struct l_nch **hp, *ln;
	KA_T ka, knx;
	static struct NCACHE **khp = (struct namecache **)NULL;
	static int khpl = 0;
	NCACHE_SZ_CAST khsz;
	unsigned long kx;
	static struct l_nch *lc = (struct l_nch *)NULL;
	static int lcl = 0;
	int len, lim, n, nch, nchl, nlcl;
	char tbuf[32];
	KA_T v;

# if	!defined(NCHNAMLEN)
	int cin = sizeof(c.NCACHE_NM);
	KA_T nmo = (KA_T)offsetof(struct NCACHE, NCACHE_NM);
# endif	/* !defined(NCHNAMLEN) */

# if	!defined(NCACHE_NMLEN)
	char nbf[MAXPATHLEN + 1];
	int nbfl = (int)(sizeof(nbf) - 1);
	KA_T nk;
	char *np;
	int rl;

	nbf[nbfl] = '\0';
# endif	/* !defined(NCACHE_NMLEN) */

	if (!Fncache)
	    return;
/*
 * Free previously allocated space.
 */
	for (lc = Ncache; lc; lc = ln) {
	    ln = lc->next;
	    (void) free((FREE_P *)lc);
	}
	Ncache = (struct l_nch *)NULL;
	if (Nchash)
	    (void) free((FREE_P *)Nchash);
	Nchash = (struct l_nch **)NULL;
/*
 * Get kernel cache hash table size
 */
	v = (KA_T)0;
	if (get_Nl_value(X_NCSIZE, (struct drive_Nl *)NULL, &v) < 0
	||  !v
	||  kread((KA_T)v, (char *)&khsz, sizeof(khsz)))
	{
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING: can't read name cache hash size: %s\n",
		    Pn, print_kptr(v, (char *)NULL, 0));
	    return;
	}
	if (khsz < 1) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING: name cache hash size length error: %#lx\n",
		    Pn, khsz);
	    return;
	}
/*
 * Get kernel cache hash table address.
 */
	ka = (KA_T)0;
	v = (KA_T)0;
	if (get_Nl_value(X_NCACHE, (struct drive_Nl *)NULL, &v) < 0
	||  !v
	||  kread((KA_T)v, (char *)&ka, sizeof(ka))
	||  !ka)
	{
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING: unusable name cache hash pointer: (%s)=%s\n",
	            Pn, print_kptr(v, tbuf, sizeof(tbuf)),
		    print_kptr(ka, (char *)NULL, 0));
	    return;
	}
/*
 * Allocate space for the hash table pointers and read them.
 */
	len = (MALLOC_S)(khsz * sizeof(struct NCACHE *));
	if (len > khpl) {
	    if (khp)
		khp = (struct NCACHE **)realloc((MALLOC_P *)khp, len);
	    else
		khp = (struct NCACHE **)malloc(len);
	    if (!khp) {
		(void) fprintf(stderr,
		    "%s: can't allocate %d bytes for name cache hash table\n",
		    Pn, len);
		Exit(1);
	    }
	    khpl = len;
	}
	if (kread((KA_T)ka, (char *)khp, len)) {
	    (void) fprintf(stderr,
		"%s: can't read name cache hash pointers from: %s\n",
		Pn, print_kptr(ka, (char *)NULL, 0));
	    return;
	}
/*
 * Process the kernel's name cache hash table buckets.
 */
	lim = khsz * 10;
	for (kx = nch = 0; kx < khsz; kx++) {

	/*
	 * Loop through the entries for a hash bucket.
	 */
	    for (ka = (KA_T)khp[kx], n = 0; ka; ka = knx, n++) {
		if (n > lim) {
		    if (!Fwarn)
			(void) fprintf(stderr,
			    "%s: WARNING: name cache hash chain too long\n",
			    Pn);
		    break;
		}
		if (kread(ka, (char *)&c, sizeof(c)))
		    break;
		knx = (KA_T)c.NCACHE_NXT;
		if (!c.NCACHE_NODEADDR)
		    continue;

# if	defined(NCACHE_NMLEN)
		if ((len = c.NCACHE_NMLEN) < 1)
		    continue;
# else	/* !defined(NCACHE_NMLEN) */
	    /*
	     * If it's possible to read the first four characters of the name,
	     * do so and check for "." and "..".
	     */
		if (!c.NCACHE_NM
		||  kread((KA_T)c.NCACHE_NM, nbf, 4))
		    continue;
		if (nbf[0] == '.') {
		    if (!nbf[1]
		    ||  ((nbf[1] == '.') && !nbf[2]))
			continue;
		}
	   /*
	    * Read the rest of the name, 32 characters at a time, until a NUL
	    * character has been read or nbfl characters have been read.
	    */
		nbf[4] = '\0';
		if ((len = (int)strlen(nbf)) < 4) {
		    if (!len)
			continue;
		} else {
		    for (np = &nbf[4]; len < nbfl; np += rl) {
			if ((rl = nbfl - len) > 32) {
			    rl = 32;
			    nbf[len + rl] = '\0';
			}
			nk = (KA_T)((char *)c.NCACHE_NM + len);
			if (kread(nk, np, rl)) {
			    rl = -1;
			    break;
			}
			rl = (int)strlen(np);
			len += rl;
			if (rl < 32)
			    break;
		    }
		    if (rl < 0)
			continue;
		}
# endif	/* defined(NCACHE_NMLEN) */

	    /*
	     * Allocate a cache entry long enough to contain the name and
	     * move the name to it.
	     */

# if	defined(NCHNAMLEN)
		if (len > NCHNAMLEN)
		    continue;
		if (len < 3 && c.NCACHE_NM[0] == '.') {
		    if (len == 1 || (len == 2 && c.NCACHE_NM[1] == '.'))
			continue;
		}
		if ((nlcl = sizeof(struct l_nch)) > lcl)
# else	/* !defined(NCHNAMLEN) */
		if ((nlcl = sizeof(struct l_nch) + len) > lcl)
# endif	/* defined(NCHNAMLEN) */

		{
		    if (lc)
			lc = (struct l_nch *)realloc(lc, nlcl);
		    else
			lc = (struct l_nch *)malloc(nlcl);
		    if (!lc) {
			(void) fprintf(stderr,
			    "%s: can't allocate %d local name cache bytes\n",
			    Pn, nlcl);
			Exit(1);
		    }
		    lcl = nlcl;
		}

# if	defined(NCHNAMLEN)
		(void) strncpy(lc->nm, c.NCACHE_NM, len);
# else	/* !defined(NCHNAMLEN) */
#  if	defined(NCACHE_NMLEN)
		if ((len < 3) && (cin > 1)) {

		/*
		 * If this is a one or two character name, and if NCACHE_NM[]
		 * in c has room for at least two characters, check for "."
		 * and ".." first, ignoring this entry if the name is either.
		 */
		    if (len < 3 && c.NCACHE_NM[0] == '.') {
			if (len == 1 || (len == 2 && c.NCACHE_NM[1] == '.'))
			    continue;
		    }
		}
		if (len > cin) {

		/*
		 * If not all (possibly not any, depending on the value in
		 * cin) of the name has yet been read to lc->nm[], read it
		 * or the rest of it.  If it wasn't possible before to check
		 * for "." or "..", do that. too.
		 */
		    if (cin > 0)
			(void) strncpy(lc->nm, c.NCACHE_NM, cin);
		    if (kread(ka + (KA_T)(nmo + cin), &lc->nm[cin], len - cin))
			continue;
		    if ((cin < 2) && (len < 3) && (lc->nm[0] == '.')) {
			if (len == 1 || (len == 2 && lc->nm[1] == '.'))
			    continue;
		    }
		} else
		    (void) strncpy(lc->nm, c.NCACHE_NM, len);
#  else	/* !defined(NCACHE_NMLEN) */
		(void) strncpy(lc->nm, nbf, len);
#  endif	/* defined(NCACHE_NMLEN) */

# endif	/* defined(NCHNAMLEN) */
		lc->nm[len] = '\0';
	    /*
	     * Complete the new local cache entry and link it to the previous
	     * local cache chain.
	     */
		lc->next = Ncache;
		Ncache = lc;
		lc->na = (KA_T)c.NCACHE_NODEADDR;
		lc->nl = len;
		lc->pa = (KA_T)c.NCACHE_PARADDR;
		lc->pla = (struct l_nch *)NULL;

# if	defined(NCACHE_NODEID)
		lc->id = c.NCACHE_NODEID;
		lc->did = c.NCACHE_PARID;
# endif	/* defined(NCACHE_NODEID) */

		lcl = 0;
		lc = (struct l_nch *)NULL;
		nch++;
	    }
	}
/*
 * Reduce memory usage, as required.
 */
	if (!RptTm) {
	    (void) free((FREE_P *)khp);
	    khp = (struct NCACHE **)NULL;
	    khpl = 0;
	}
	if (nch < 1) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING: unusable name cache size: %d\n", Pn, nch);
	    return;
	}
/*
 * Build a hash table to locate Ncache entries.
 */
	for (nchl = 1; nchl < nch; nchl <<= 1)
	    ;
	nchl <<= 1;
	Mch = nchl - 1;
	len = nchl + nch;
	if (!(Nchash = (struct l_nch **)calloc(len, sizeof(struct l_nch *)))) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: no space for %d local name cache hash pointers\n",
		    Pn, len);
	    Exit(1);
	}
	for (lc = Ncache; lc; lc = lc->next) {

# if	defined(NCACHE_NODEID)
	    for (hp = ncachehash(lc->id, lc->na),
# else	/* !defined(NCACHE_NODEID) */
	    for (hp = ncachehash(lc->na),
# endif	/* defined(NCACHE_NODEID) */

		    n = 1; *hp; hp++)
		{
		if ((*hp)->na == lc->na && strcmp((*hp)->nm, lc->nm) == 0) {
		    n = 0;
		    break;
		}
	    }
	    if (n)
		*hp = lc;
	    else
		lc->pa = (KA_T)0;
	}
/*
 * Make a final pass through the local cache and convert parent node
 * addresses to local name cache pointers.
 */
	for (lc = Ncache; lc; lc = lc->next) {
	    if (!lc->pa)
		continue;

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

	}
}


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

char *
ncache_lookup(buf, blen, fp)
	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	defined(NCACHE_NODEID)
	if (!Nchash || !(lc = ncache_addr(Lf->id, Lf->na)))
# else	/* !defined(NCACHE_NODEID) */
	if (!Nchash || !(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);
	}
/*
 * Start 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 length is too large to fit in the receiving buffer.
 */
	for (;;) {
	    if (!lc->pla) {

#  if	!defined(NCACHE_NO_ROOT)
		if (ncache_isroot(lc->pa, cp))
		    *fp = 1;
#  endif	/* !defined(NCACHE_NO_ROOT) */

		break;
	    }
	    lc = lc->pla;
	    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_RNMH) */
char rnmh_d1[] = "d"; char *rnmh_d2 = rnmh_d1;
#endif	/* defined(HASNCACHE) && defined(USE_LIB_RNMH) */
