

/*
 * $Id: rnch.frag,v 1.11 96/01/09 08:27:54 abe Exp $
 */


#if	defined(HASNCACHE)
/*
 * rnch.frag - read Sun format (struct ncache) name cache
 *
 * This fragment 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 Nl[] index to 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 Nl[] index to the kernel cache address.
 *	and define ADDR_NCACHE if Nl[X_NCACHE].n_value 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 Nl[X_NCACHE].n_value, rather than a table
 *	starting at that address.
 *
 * The caller may:
 *
 *	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.
 *
 * Note: if NCACHE_NODEID is defined, then NCACHE_PARID must be defined.
 *
 *
 * The caller may need to:
 *
 *	Define this prototype for ncache_load() if it is called
 *	before rnch.frag is included.
 *
 *		_PROTOTYPE(static void ncache_load,(void));
 */


/*
 * Local static values
 */

static int Mch;				/* name cache hash mask */
static int Nc = 0;			/* size of name cache */
static int Nch = 0;			/* size of name cache hash pointer
					 * table */
struct l_nch {
	struct vnode *vp;		/* vnode address */
	struct vnode *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[NC_NAMLEN+1];		/* name */
	int nl;				/* name length */
};

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

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

_PROTOTYPE(static int ncache_isroot,(struct vnode *va, char *cp));

#define DEFNCACHESZ	512	/* local size if X_NCACHE kernel value < 1 */
#define	LNCHINCRSZ	64	/* local size increment */


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

static struct l_nch *

# if	defined(NCACHE_NODEID)
ncache_addr(i, v)
# else	/* !defined(NCACHE_NODEID) */
ncache_addr(v)
# endif	/* defined(NCACHE_NODEID) */

# if	defined(NCACHE_NODEID)
	unsigned long i;			/* capability ID */
# endif	/* defined(NCACHE_NODEID) */

	struct vnode *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(NULL);
}


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

static int
ncache_isroot(va, cp)
	struct vnode *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 struct vnode **vc = (struct vnode **)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((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 = Mtab; 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) {
	    if (vca == 0) {
		len = (MALLOC_S)(10 * sizeof(struct vnode *));
		if ((vc = (struct vnode **)malloc(len))
		!=  (struct vnode **)NULL)
		    vca = 10;
	    } else {
		len = (MALLOC_S)((vca + 10) * sizeof(struct rnode *));
		if ((vc = (struct vnode **)realloc(vc, len))
		!=  (struct vnode **)NULL)
		    vca += 10;
	    }
	    if (vc == (struct vnode **)NULL) {
		(void) fprintf(stderr, "%s: no space for root vnode table\n",
		    Pn);
		exit(1);
	    }
	}
	vc[vcn++] = va;
	return(1);
}


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

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

# 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)
	    if (!Nl[X_NCSIZE].n_value
	    ||  kread((KA_T)Nl[X_NCSIZE].n_value, (char *)&Nc, sizeof(Nc)))
	    {
		if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING: can't read name cache size (%s): %#x\n",
		    Pn, Nl[X_NCSIZE].n_name, Nl[X_NCSIZE].n_value);
		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;
	    }
	/*
	 * Establish kernel cache address.
	 */

# if	defined(ADDR_NCACHE)
	    if (!(kp = Nl[X_NCACHE].n_value)) {
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: WARNING: no name cache address\n", Pn);
		iNc = Nc = 0;
		return;
	    }
# else	/* !defined(ADDR_NCACHE) */
	    if (!Nl[X_NCACHE].n_value
	    ||  kread((KA_T)Nl[X_NCACHE].n_value, (char *)&kp, sizeof(kp))) {
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: WARNING: can't read name cache ptr, %s: %#x\n",
			Pn, Nl[X_NCACHE].n_name, Nl[X_NCACHE].n_value);
		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)) == NULL) {
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: can't allocate name cache space: %d\n", Pn, len);
		exit(1);
	    }
# endif	/* !defined(NCACHE_NXT) */

	/*
	 * Allocate space for the local cache.
	 */
	    len = Nc * sizeof(struct l_nch);
	    if ((Ncache = (struct l_nch *)malloc((MALLOC_S)len)) == NULL) {

no_local_space:

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

	/*
	 * Do setup for repeat calls.
	 */
	    if ((Nc = iNc) == 0)
		return;
	    if (Nchash) {
		(void) free((FREE_P *)Nchash);
		Nchash = NULL;
	    }

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

	}

# if	!defined(NCACHE_NXT)

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

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

# if	defined(NCACHE_NXT)
	for (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(kp, (char *)kc, sizeof(nc)))
		break;
	    if ((kp = (KA_T)kc->NCACHE_NXT) == kf)
		kp = NULL;
# endif	/* defined(NCACHE_NXT) */

	    if (!kc->vp)
		continue;
	    if ((len = kc->namlen) < 1 || len > NC_NAMLEN)
		continue;
	    if (len < 3 && kc->name[0] == '.') {
		if (len == 1 || (len == 2 && kc->name[1] == '.'))
		    continue;
	    }

# if	defined(NCACHE_NXT)
	    if (n >= Nc) {
		Nc += LNCHINCRSZ;
		if ((Ncache = (struct l_nch *)realloc(Ncache,
		    (MALLOC_S)(Nc * sizeof(struct l_nch))))
		== NULL) {
		    (void) fprintf(stderr,
			"%s: no more space for %d entry local name cache\n",
			Pn, Nc);
		    exit(1);
		}
		lc = &Ncache[n];
		iNc = Nc;
	    }
# endif	/* defined(NCACHE_NXT) */

	    lc->vp = kc->vp;
	    lc->dp = kc->dp;
	    lc->pa = (struct l_nch *)NULL;
	    (void) strncpy(lc->nm, kc->name, len);
	    lc->nm[len] = '\0';
	    lc->nl = strlen(lc->nm);

# 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 >= (10 * DEFNCACHESZ)) {
		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) {
	    Nc = 0;
	    if (!RptTm) {
		(void) free((FREE_P *)Ncache);
		Ncache = NULL;
	    }
	    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)) == NULL)
		    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 *)))
	== NULL) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: no space for %d name cache hash pointers\n",
		    Pn, Nch + Nc);
	    exit(1);
	}
	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_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(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 (Nc == 0

# if	defined(NCACHE_NODEID)
	||  (lc = ncache_addr(Lf->id, (struct vnode *)Lf->na)) == NULL)
# else	/* !defined(NCACHE_NODEID) */
	||  (lc = ncache_addr((struct vnode *)Lf->na)) == NULL)
# 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(NULL);
	    for (mtp = Mtab; mtp; mtp = mtp->next) {
		if (!mtp->dir || !mtp->inode)
		    continue;
		if (Lf->dev == mtp->dev
		&&  (unsigned long)mtp->inode == Lf->inode
		&&  strcmp(mtp->dir, Lf->fsdir) == 0)
		    return(cp);
	    }
	    return(NULL);
	}
/*
 * Begin the path assembly.
 */
	if ((nl = lc->nl) > (blen - 1))
	    return(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);
}
#endif	/* defined(HASNCACHE) */
