/*
 * dproc.c - Linux process access functions for lsof
 */


/*
 * Copyright 1994 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 1994 Purdue Research Foundation.\nAll rights reserved.\n";
static char *rcsid = "$Id: dproc.c,v 1.17 96/02/27 08:02:48 abe Exp $";
#endif

#include "lsof.h"


_PROTOTYPE(static void get_kernel_access,(void));
_PROTOTYPE(static int hash_ksym,(char *nm));
_PROTOTYPE(static char *hasparmhash,(char *nm));

#if	defined(HASNCACHE)
_PROTOTYPE(static void ncache_load,(void));
#endif	/* defined(HASNCACHE) */

_PROTOTYPE(static int nlist,(char *fnm, struct nlist *nl));
_PROTOTYPE(static void process_mapmem,(struct inode *x, struct vm_area_struct *m));


/*
 * Local static values
 */

static int Coff = 0;			/* kernel loder format is COFF */
static caddr_t *Nc = NULL;		/* node cache */
static MALLOC_S Nn = 0;			/* number of Nc[] entries allocated */


/*
 * Local definitions
 */

#define	KSHASHSZ	256		/* kernel symbol hash table size */


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

void
gather_proc_info()
{
	struct file *f;
	struct file fs;
	int i, nt;
	struct inode *ip;
	struct task_struct *t, ts;
	static struct task_struct **tt = NULL;
	MALLOC_S len;
	short pss, sf;
	uid_t uid;

#if	LINUXV>1321
	struct files_struct fis;
	struct fs_struct fss;
	struct mm_struct mms;
#endif	/* LINUXV>1321 */

/*
 * Allocate space for task structure pointers and read them.
 */
	len = (MALLOC_S)(NR_TASKS * sizeof(struct task_struct *));
	if (tt == (struct task_struct **)NULL) {
		if ((tt = (struct task_struct **)malloc(len)) == NULL) {
			(void) fprintf(stderr,
				"%s: no space for %d task structure pointers\n",
				Pn, NR_TASKS);
			exit(1);
		}
	}
	if (kread((KA_T)Nl[X_TASK].n_value, (char *)tt, (int)len)) {
		(void) fprintf(stderr,
			"%s: can't read task structure pointers from %#lx\n",
			Pn, Nl[X_TASK].n_value);
		exit(1);
	}

#if	defined(HASNCACHE)
/*
 * Load the kernel name cache.
 */
	(void) ncache_load();
#endif	/* defined(HASNCACHE) */

/*
 * Examine task structures and their associated information.
 */
	for (nt = 0, t = &ts; nt < NR_TASKS; nt++) {
		if (tt[nt] == (struct task_struct *)NULL
		||   kread((KA_T)tt[nt], (char *)t, sizeof(ts)))
			continue;
		if (t->state == TASK_ZOMBIE)
			continue;
		if (is_proc_excl((pid_t)t->pid, (pid_t)t->pgrp,
		    (UID_ARG)t->uid, &pss, &sf))
			continue;
	/*
	 * Allocate a local process structure.
	 */
		if (is_cmd_excl(t->comm, &pss, &sf))
			continue;
		alloc_lproc(t->pid, t->pgrp, (UID_ARG)t->uid, t->comm,
			(int)pss, (int)sf);
		Plf = NULL;

#if	LINUXV>1321
	/*
	 * Read file system structure for Linux version 1.3.22 and above.
	 */
		if (t->fs == (struct fs_struct *)NULL
		||  kread((KA_T)t->fs, (char *)&fss, sizeof(fss)))
			continue;
		
#endif	/* LINUXV>1321 */

	/*
	 * Save current working directory information.
	 */

#if	LINUXV<=1321
		if ((ip = t->PWD) != (struct inode *)NULL)
#else	/* LINUXV>1321 */
		if ((ip = fss.pwd) != (struct inode *)NULL)
#endif	/* LINUXV<=1321 */

		{
			alloc_lfile(CWD, -1);
			process_inode(ip, (struct file_operations *)NULL);
			if (Lf->sf)
				link_lfile();
		}
	/*
	 * Save root directory information.
	 */

#if	LINUXV<=1321
		if ((ip = t->ROOT) != (struct inode *)NULL)
#else	/* LINUXV>1321 */
		if ((ip = fss.root) != (struct inode *)NULL)
#endif	/* LINUXV<=1321 */

		{
			alloc_lfile(RTD, -1);
			process_inode(ip, (struct file_operations *)NULL);
			if (Lf->sf)
				link_lfile();
		}
	/*
	 * Save information on the executable file and mapped memory.
	 */

#if	LINUXV<1147
		if (t->executable || t->mmap)
			process_mapmem(t->executable, t->mmap);
#else	/* LINUXV>=1147 */
# if	LINUXV<=1321
		if (t->mm->mmap)
			process_mapmem(NULL, t->mm->mmap);
# else	/* LINUXV>1321 */
		if (t->mm
		&&  kread((KA_T)t->mm, (char *)&mms, sizeof(mms)) == 0
		&&  mms.mmap)
			process_mapmem(NULL, mms.mmap);
# endif	/* LINUXV<=1321 */
#endif	/* LINUXV<1147 */

	/*
	 * Save information on file descriptors.
	 */

#if	LINUXV>1321
		if (t->files == (struct files_struct *)NULL
		||  kread((KA_T)t->files, (char *)&fis, sizeof(fis)))
			continue;
#endif	/* LINUXV>1321 */

		for (f = &fs, i = 0; i < NR_OPEN; i++) {

#if	LINUXV<1147
			if (t->filp[i] == (struct file *)NULL
			||  kread((KA_T)t->filp[i], (char *)f, sizeof(fs)))
#else	/* LINUXV>=1147 */
# if	LINUXV<=1321
			if (t->files->fd[i] == (struct file *)NULL
			||  kread((KA_T)t->files->fd[i], (char *)f, sizeof(fs)))
# else	/* LINUXV>1321 */
			if (fis.fd[i] == (struct file *)NULL
			||  kread((KA_T)fis.fd[i], (char *)f, sizeof(fs)))
# endif	/* LINUXV<=1321 */
#endif	/* LINUXV<1147 */

				continue;
			alloc_lfile(NULL, i);
			process_file(f);
			if (Lf->sf)
				link_lfile();
		}
	/*
	 * Examine results.
	 */
		if (examine_lproc())
			return;
	}
}


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

static void
get_kernel_access()
{

#if	defined(WILLDROPGID)
/*
 * If kernel memory isn't coming from KMEM, drop setgid permission
 * before attempting to open the (Memory) file.
 */
	if (Memory)
		(void) dropgid();
#else	/* !defined(WILLDROPGID) */
/*
 * See if the non-KMEM memory file is readable.
 */
	if (Memory && !is_readable(Memory, 1))
		exit(1);
#endif	/* defined(WILLDROPGID) */

/*
 * Open kernel memory access.
 */
	if ((Kmem = open(Memory ? Memory : KMEM, O_RDONLY, 0)) < 0) {
		(void) fprintf(stderr, "%s: can't open %s: %s\n", Pn,
			Memory ? Memory : KMEM, strerror(errno));
		exit(1);
	}

#if	defined(WILLDROPGID)
/*
 * Drop setgid permission, if necessary.
 */
	if (!Memory)
		(void) dropgid();
#else	/* !defined(WILLDROPGID) */
/*
 * See if the name list file is readable.
 */
	if (Nmlst && !is_readable(Nmlst, 1))
		exit(1);
#endif	/* defined(WILLDROPGID) */

/*
 * Access kernel symbols.
 */
	if (nlist(Nmlst ? Nmlst : N_UNIX, Nl) < 0) {
		(void) fprintf(stderr,
			"%s: can't read kernel name list from %s\n",
			Pn, Nmlst ? Nmlst : N_UNIX);
		exit(1);
	}
/*
 * Make sure important kernel symbols are defined.
 */
	if ( ! Nl[X_TASK].n_type) {
		(void) fprintf(stderr,
			"%s: no %stask kernel definition\n",
			Pn, Coff ? "_" : "");
		exit(1);
	}
}


/*
 * hash_ksym() - hash kernel symbol by name
 */

static int
hash_ksym(nm)
	char *nm;
{
	int i, j;

	for (i = j = 0; *nm; nm++) {
		i ^= (int)*nm << j;
		j ^= 1;
	}
	return(i % KSHASHSZ);
}


/*
 * hasparmhash() - does symbol name have parameter hash
 */

static char *
hasparmhash(nm)
	char *nm;			/* pointer to symbol name */
{
	char *cp, *rv;
	int n;

	if ((cp = strrchr(nm, '_')) == NULL || *(cp + 1) != 'R')
		return(NULL);
	rv = cp;
	for (cp += 2, n = 0; n < 8; cp++, n++) {
		if ((*cp >= '0' && *cp <= '9')
		||  (*cp >= 'a' && *cp <= 'f')
		||  (*cp >= 'A' && *cp <= 'F'))
			continue;
		return(NULL);
	}
	return((*cp == '\0') ? rv : NULL);
}


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

void
initialize()
{
	size_t sz;
	unsigned long hi, st;

	get_kernel_access();
	iuidcache(NR_TASKS);
}


/*
 * kread() - read from kernel memory
 *
 * return: 0 = success
 *	   1 = read failed (perhaps some data was read, but not all)
 *	  -1 = seek to kernel memory address failed
 */

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

	if (lseek(Kmem, (off_t)addr, SEEK_SET) == (off_t)-1)
		return(-1);
	br = read(Kmem, buf, len);
	return((br == (int)len) ? 0 : 1);
}


/*
 * nlist() - get name list
 */

static int
nlist(fnm, nl)
	char *fnm;			/* name of file to search */
	struct nlist *nl;		/* names to locate */
{
	unsigned long addr;
	char buf[128];
	char *cp, *eol, *nm;
	int elf = 0;
	int i, j, mm, nf, nk, nn;
	struct kernel_sym *ksa = NULL;
	struct kernel_sym *ks;
	FILE *nfs;
	struct ksym {
		char *name;
		unsigned long addr;
		struct ksym *next;
	} **kh, *kp, *kpn;
	struct nlist *np;

	if ((nfs = fopen(fnm, "r")) == NULL)
		return(-1);
/*
 * Read the kernel symbols via get_kernel_syms().
 */
	if ((nk = get_kernel_syms(NULL)) < 0) {
	    if (!Fwarn) {
		if (errno == ENOSYS) {
		    (void) fprintf(stderr,
			"%s: WARNING: get_kernel_syms() unimplemented\n", Pn);
		    (void) fprintf(stderr,
		        "      CONFIG_MODULES not defined in autoconf.h?\n");
		} else {
		    (void) fprintf(stderr,
			"%s: get_kernel_syms() error: %s\n",
			Pn, strerror(errno));
		}
		(void) fprintf(stderr,
		    "%s: WARNING: unable to verify symbols in %s\n", Pn, fnm);
	    }
	} else {
	    i = nk * sizeof(struct kernel_sym);
	    if ((ksa = (struct kernel_sym *)malloc((MALLOC_S)i)) == NULL) {
		(void) fprintf(stderr, "%s: no space for kernel symbols\n", Pn);
		exit(1);
	    }
	}
	if (nk > 0) {
	    if (get_kernel_syms(ksa) < 0) {
		(void) fprintf(stderr, "%s: get_kernel_syms: %s\n",
		    Pn, strerror(errno));
		exit(1);
	    }
	/*
	 * Allocate hash table space.
	 */
	    if ((kh = (struct ksym **)calloc((MALLOC_S)sizeof(struct ksym),
		      KSHASHSZ))
	    == NULL) {
		(void) fprintf(stderr,
		    "%s: no space for %d byte kernel symbol hash table\n",
		    Pn, (KSHASHSZ * sizeof(struct ksym)));
		exit(1);
	    }
	/*
	 * Scan the kernel symbol table and store their addresses, hashed
	 * by their names for later comparison to the same values in
	 * /[z]System.map.
	 *
	 * Look for the symbol "_system_utsname" or "system_utsname" to
	 * determine the loader format of the kernel: it's * COFF if the
	 * symbol has a leading `_'; ELF if it does not.
	 */
	    for (i = 0, ks = ksa; i < nk; i++, ks++) {
		if (ks->name[0] == '#')
		    continue;
		if ((kp = (struct ksym *)malloc((MALLOC_S)sizeof(struct ksym)))
		== NULL) {
		    (void) fprintf(stderr,
			"%s: no space for kernel symbol structure: %s\n",
			Pn, nm);
		    exit(1);
		}
		if ((nm = hasparmhash(ks->name)) != NULL)
		    *nm = '\0';
		if ((kp->name = (char *)malloc((MALLOC_S)(strlen(ks->name)+1)))
		== NULL) {
		    (void) fprintf(stderr,
			"%s: no space for kernel symbol name: %s\n", Pn, nm);
		    exit(1);
		}
		j = hash_ksym(ks->name);
		(void) strcpy(kp->name, ks->name);
		if (strcmp(ks->name, "_system_utsname") == 0)
		    Coff = 1;
		else if (strcmp(ks->name, "system_utsname") == 0)
		    elf = 1;
		kp->addr = ks->value;
		kp->next = kh[j];
		kh[j] = kp;
	    }
	    (void) free((FREE_P *)ksa);
	} else
	    kh = (struct ksym **)NULL;
/*
 * Complain if we don't know the kernel binary's format, COFF or ELF, or if
 * we have conflicting evidence.  In either case, set the default format.
 */
	if ((!Coff && !elf) || (Coff && elf)) {

#if	defined(KERN_LD_ELF)
		Coff = 0;
		elf = 1;
#else	/* !defined(KERN_LD_ELF) */
		Coff = 1;
		elf = 0;
#endif	/* defined(KERN_LD_ELF) */

	    if (!Fwarn) {
		(void) fprintf(stderr, "%s: WARNING: uncertain kernel", Pn);
		(void) fprintf(stderr, " loader format; assuming %s.\n",

#if	defined(KERN_LD_ELF)
			"ELF"
#else	/* !defined(KERN_LD_ELF) */
			"COFF"
#endif	/* defined(KERN_LD_ELF) */

			);

	    }
	}
/*
 * Read the lines of the name list file.  Look for the symbols defined
 * in Nl[].  Look for any symbols also defined from the get_kernel_syms()
 * call and complain if their addresses don't match.  Count the symbols
 * in Nl[] and quit when addresses have been located for all of them.
 */
	for (nn = 0, np = nl; np->n_name; nn++, np++)
		;
	mm = nf = 0;
	while (nf < nn && fgets(buf, sizeof(buf), nfs) != NULL) {
	    if ((eol = strchr(buf, '\n')) == NULL)
		continue;
	    if ((cp = strchr(buf, ' ')) == NULL)
		continue;
	    nm = cp + 3;
	    *eol = '\0';
	    addr = strtol(buf, &cp, 16);
	    if (kh) {

	    /*
	     * Look for name in the kernel symbol hash table; if it's there,
	     * check the address; complain volubly about the first mismatch;
	     * count mismatches.
	     */
		for (kp = kh[hash_ksym(nm)]; kp; kp = kp->next) {
		    if (strcmp(kp->name, nm) == 0)
			break;
		}
		if (kp && addr != kp->addr) {
		    if (++mm == 1 && !Fwarn) {
			(void) fprintf(stderr,
			    "%s: kernel symbol address mismatch: %s\n",
			    Pn, nm);
			(void) fprintf(stderr,
			    "      get_kernel_syms() value is %#x;", kp->addr);
			(void) fprintf(stderr, " %s value is %#x.\n",
			    fnm, addr);
		    }
		}
	    }
	/*
	 * Search for symbol in Nl[].
	 */
	    for (np = nl; np->n_name; np++) {
		cp = Coff ? np->n_name : (np->n_name + 1);
		if (strcmp(nm, cp) != 0)
		    continue;
		if (np->n_type) {

		/*
		 * Warn about a duplicate.
		 */
		    if (!Fwarn)
			(void) fprintf(stderr,
			    "%s: WARNING: %s: ambiguous symbol: %s\n",
			    Pn, fnm, cp);
		    continue;
		}
	    /*
	     * Save address; set type to 1, signifying an address has been
	     * loccated.  Count the number of addresses located.
	     */
		np->n_value = addr;
		np->n_type = 1;
		nf++;
		break;
	    }
	}
	(void) fclose(nfs);
/*
 * Complete the kernel symbol mismatch message if there were additional
 * mismtaches.  Quit if there were any mismatches.
 */
	if (mm) {
	    if (!Fwarn) {
		if (mm > 1) {
		    (void) fprintf(stderr,
			"      There %s %d additional mismatch%s.\n",
			(mm == 2) ? "was" : "were",
			mm - 1,
			(mm == 2) ? "" : "es");
		 }
		 (void) fprintf(stderr,
			"      %s and the booted kernel may not be a",
			fnm);
		 (void) fprintf(stderr, " matched set.\n");
	    }
	    exit(1);
	}
/*
 * Free the hashed kernel name space.
 */
	if (kh) {
	    for (i = 0; i < KSHASHSZ; i++) {
		if ((kp = kh[i]) == NULL)
		    continue;
		while (kp) {
		    kpn = kp->next;
		    if (kp->name)
			(void) free((FREE_P *)kp->name);
		    (void) free((MALLOC_S *)kp);
		    kp = kpn;
		}
	    }
	    (void) free((FREE_P *)kh);
	}
	return(1);
}


/*
 * process_mapmem() - process mapped memory
 */

static void
process_mapmem(x, m)
	struct inode *x;
	struct vm_area_struct *m;
{
	int f;
	int i, j, lm;
	MALLOC_S ma;
	struct inode *ia;
	char *ty;
	struct vm_area_struct v;

	for (f = 1, i = lm = 0; m; lm++) {

	/*
	 * Avoid infinite loop.
	 */
		if (lm > 1000) {
			if (!Fwarn)
				(void) fprintf(stderr,
					"%s: too many memory maps, PID %d\n",
					Pn, Lp->pid);
				return;
		}
		if (f) {
		
		/*
		 * The first time through the loop, use the executable inode
		 * address.
		 */
			ty = "txt";
			f = 0;
			if ((ia = x) == (struct inode *)NULL)
				continue;
		} else {

		/*
		 * After the first time through the loop, use the inode
		 * addresses in the memory map.
		 */
			if (kread((KA_T)m, (char *)&v, sizeof(v)))
				break;
			m = v.vm_next;
			if ((ia = v.vm_inode) == (struct inode *)NULL)
				continue;
			ty = "mem";
		}
	/*
	 * Skip duplicate inode addresses.
	 */
		for (j = 0; j < i; j++) {
			if (Nc[j] == (caddr_t)ia)
				break;
		}
		if (j < i)
			continue;
	/*
	 * Cache the inode address for duplicate checking.
	 */
		if (i >= Nn) {
			Nn += 10;
			ma = (MALLOC_S)(Nn * sizeof(caddr_t));
			if (Nc)
				Nc = (caddr_t *)realloc(Nc, ma);
			else
				Nc = (caddr_t *)malloc(ma);
			if (!Nc) {
			    (void) fprintf(stderr,
				"%s: no space for memmap pointers, PID %d\n",
				Pn, Lp->pid);
			    exit(1);
			}
		}
		Nc[i++] = (caddr_t)ia;
	/* * Save the inode information.
	 */
		alloc_lfile(ty, -1);
		process_inode(ia, (struct file_operations *)NULL);
		if (Lf->sf)
			link_lfile();
	}
}


#if	defined(HASNCACHE)


#include "dialects/linux/include/kncache.h"


/*
 * Define the maximum name length for the local cache.
 */

# if	defined(HASDEVKNC) && defined(HASNFSKNC)
#  if	DCACHE_NAME_LEN >= NFS_MAXNAMLEN
#define	L_NCH_NL	DCACHE_NAME_LEN
#  else	/* DCACHE_NAME_LEN < NFS_MAXNAMLEN */
#define L_NCH_NL	NFS_MAXNAMLEN
#  endif	/* DCACHE_NAME_LEN >= NFS_MAXNAMLEN */
# else	/* !defined(HASDEVKNC) || !defined(HASNFSKNC) */
#  if	defined(HASDEVKNC)
#define	L_NCH_NL	DCACHE_NAME_LEN
#  else	/* !defined(HASDEVKNC) */
#   if	defined(HASNFSKNC)
#define L_NCH_NL	NFS_MAXNAMLEN
#   endif	/* defined(HASNFSKNC) */
#  endif	/* defined(HASDEVKNC) */
# endif	/* defined(HASDEVKNC) && defined(HASNFSKNC) */


# if	defined(HASDEVKNC) || defined(HASNFSKNC)
/*
 * Device and inode cache values.
 */

struct l_nch {				/* local cache entry structure */

	dev_t dev;			/* device */
	unsigned long dir;		/* parent directory inode number */
	unsigned long ino;		/* inode number */
	struct l_nch *pa;		/* parent Nc address */
	char nm[L_NCH_NL+1];		/* name */
	unsigned char nl;		/* name length */
	unsigned char dup;		/* duplicate entry status */
};

static int Asz = 0;			/* cache allocated size */
static int Csz = 0;			/* cache current size */
static int Hm = 0;			/* cache hash mask */
static struct l_nch **Hp = (struct l_nch **)NULL;
					/* cache hash pointers */
static struct l_nch *Nmc = (struct l_nch *)NULL;
static int Nh = 0;			/* cache hash pointer count */
					/* cache */

_PROTOTYPE(static struct l_nch *ncache_addr,(dev_t *d, unsigned long i));

#if	LINUXV<=1321
#define	DEV	dev
#else	/* LINUXV>1321 */
#define	DEV	dc_dev
#endif	/* LINUXV<=1321 */

#define hash(d, i)	Hp+((((int)(d + i)>>2)*31415)&Hm)


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

static struct l_nch *
ncache_addr(d, i)
	dev_t *d;			/* device number */
	unsigned long i;		/* inode number */
{
	struct l_nch **hp;

	for (hp = hash(*d, i); *hp; hp++) {
	    if ((*hp)->dev == *d && (*hp)->ino == i)
		return(*hp);
	}
	return(NULL);
}
# endif	/* defined(HASDEVKNC) || defined(HASNFSKNC) */


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

static void
ncache_load()
{

# if	defined(HASDEVKNC) || defined(HASNFSKNC)
	static char *c = (char *)NULL;
	MALLOC_S cl;
	int f, i, len, nx;
	struct l_nch **hp, *lc;

#  if	defined(HASDEVKNC)
	static int dl = DCACHE_SIZE * sizeof(struct dir_cache_entry);
	struct dir_cache_entry *ld;
#  endif	/* defined(HASDEVKNC) */

#  if	defined(HASNFSKNC)
	struct nfs_lookup_cache_entry *nd;
	static nl = NFS_LOOKUP_CACHE_SIZE*sizeof(struct nfs_lookup_cache_entry);
#  endif	/* defined(HASNFSKNC) */

	if (!Fncache)
	    return;
	if (Asz == 0) {

	/*
	 * Do startup (first-time) functions.
	 */
	    cl = 0;

#  if	defined(HASDEVKNC)
	/*
	 * Device device cache parameters.
	 */
	    if (Nl[X_LEVEL1].n_value)
		Asz = DCACHE_SIZE;
	    if (Nl[X_LEVEL2].n_value)
		Asz += DCACHE_SIZE;
	    if (Asz)
		cl = DCACHE_SIZE * sizeof(struct dir_cache_entry);
	    else
		dl = 0;
#  endif	/* defined(HASDEVKNC) */

#  if	defined(HASNFSKNC)
	/*
	 * Define NFS cache parameters.
	 */
	    if (Nl[X_NFSKNC].n_value) {
		i = NFS_LOOKUP_CACHE_SIZE;
		Asz += i;
		if ((i * sizeof(struct nfs_lookup_cache_entry)) > cl)
		    cl = i * sizeof(struct nfs_lookup_cache_entry);
	    } else
		nl = 0;
#  endif	/* defined(HASNFSKNC) */

	    if (Asz == 0)
		return;

	/*
	 * Allocate space for the local cache.
	 */
	    len = Asz * sizeof(struct l_nch);
	    if ((Nmc = (struct l_nch *)malloc((MALLOC_S)len))
	    == (struct l_nch *)NULL) {
		(void) fprintf(stderr,
		    "%s: no space for %d byte local name cache\n", Pn, len);
		exit(1);
	    }
	/*
	 * Allocate a buffer to read the kernel caches.
	 */
	    if ((c = (char *)malloc(cl)) == (char *)NULL) {
		(void) fprintf(stderr,
		    "%s: no space for %d byte kernel name cache copy\n",
		    Pn, cl);
		exit(1);
	    }
	} else {

	/*
	 * Do setup for repeat calls.
	 */
	    if (Hp) {
		(void) free((FREE_P *)Hp);
		Hp = NULL;
	    }
	}
	Csz = 0;
	lc = Nmc;

#  if	defined(HASDEVKNC)
/*
 * Read each device name cache and add its entries to the local name cache.
 */
	if (dl) {
	    for (nx = X_LEVEL1; nx != X_LEVEL2; nx = X_LEVEL2) {
		if (!Nl[nx].n_value)
		    continue;
		if (kread((KA_T)Nl[nx].n_value, c, dl))
		    continue;
		for (i = 0, ld = (struct dir_cache_entry *)c;
		     i < DCACHE_SIZE;
		     i++, ld++)
		{
		    if (!ld->DEV || !ld->dir || !ld->ino)
			continue;
		    if ((len = (int)ld->name_len) < 1 || len > DCACHE_NAME_LEN)
			continue;
		    if (len < 3 && ld->name[0] == '.') {
			if (len == 1 || (len == 2 && ld->name[1] == '.'))
			    continue;
		    }
		    lc->dev = ld->DEV;
		    lc->dir = ld->dir;
		    lc->ino = ld->ino;
		    (void) strncpy(lc->nm, ld->name, len);
		    lc->nm[len] = '\0';
		    lc->nl = (unsigned char)strlen(lc->nm);
		    lc->pa = (struct l_nch *)NULL;
		    lc->dup = 0;
		    lc++;
		    Csz++;
	        }
	    }
	}
#  endif	/* defined(HASDEVKNC) */

#  if	defined(HASNFSKNC)
/*
 * Read NFS name cache and add its entries to the local cache.
 */
	if (nl && kread((KA_T)Nl[X_NFSKNC].n_value, c, nl) == 0) {
	    for (i = 0, nd = (struct nfs_lookup_cache_entry *)c;
	      i < NFS_LOOKUP_CACHE_SIZE;
	      i++, nd++)
	    {
		if (!nd->dev || !nd->inode)
		    continue;
		nd->filename[NFS_MAXNAMLEN] = '\0';
		if ((len = strlen(nd->filename)) < 1)
		    continue;
		if (len < 3 && nd->filename[0] == '.') {
		    if (len == 1 || nd->filename[1] == '.')
			continue;
		}
		lc->dev = (dev_t)nd->dev;
		lc->dir = (unsigned long)nd->inode;
		lc->ino = (unsigned long)nd->fattr.fileid;
		(void) strcpy(lc->nm, nd->filename);
		lc->nl = len;
		lc->pa = (struct l_nch *)NULL;
		lc->dup = 0;
		lc++;
		Csz++;
	    }
	}
#  endif	/* defined(HASNFSKNC) */

/*
 * Reduce memory usage, as required.
 */
	if (Csz < 1) {
	    Csz = 0;
	    if (!RptTm) {
		(void) free((FREE_P *)Nmc);
		Nmc = NULL;
	    }
	    return;
	}
	if (Csz < Asz && !RptTm) {
	    len = Csz * sizeof(struct l_nch);
	    if ((Nmc = (struct l_nch *)realloc(Nmc, len))
	    == (struct l_nch *)NULL)
	    {
		(void) fprintf(stderr, "%s: can't realloc local name cache\n",
		    Pn);
		exit(1);
	    }
	    Asz = Csz;
	}
/*
 * Build a hash table to locate cache entries.
 */
	for (Nh = 1; Nh < Csz; Nh <<= 1)
	    ;
	Nh <<= 1;
	Hm = Nh - 1;
	if ((Hp = (struct l_nch **)calloc(Nh + Csz, sizeof(struct l_nch *)))
	== (struct l_nch **)NULL) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: no space for %d name cache hash pointers\n",
		    Pn, Nh + Csz);
	    exit(1);
	}
/*
 * Enter cache pointers in the hash table.  Look for entries with the
 * same device and inode numbers that have different names.
 */
	for (i = 0, lc = Nmc; i < Csz; i++, lc++) {
	    for (hp = hash(lc->dev, lc->ino), f = 0; *hp; hp++) {
		if ((*hp)->dev == lc->dev && (*hp)->ino == lc->ino) {
		    if (strcmp((*hp)->nm, lc->nm) == 0)
			f = 1;
		    else {
			f = 2;	/* same numbers, different names */
			break;
		    }
		}
	    }
	    if (!f)
		*hp = lc;
	    else if (f == 2) {

	    /*
	     * Since entries with the same device and inode numbers were
	     * located, mark entries with those values as duplicates.
	     */
		for (hp = hash(lc->dev, lc->ino); *hp; hp++) {
		    if ((*hp)->dev == lc->dev && (*hp)->ino == lc->ino)
			(*hp)->dup = 1;
		}
	    }
	}
/*
 * Make a final pass through the local cache and convert parent directory
 * inode numbers to local name cache pointers.
 */
	for (i = 0, lc = Nmc; i < Csz; i++, lc++) {
	    if (!lc->dir)
		continue;
	    lc->pa = ncache_addr(&lc->dev, lc->dir);
	}
# endif	/* defined(HASDEVKNC) || defined(HASNFSKNC) */

}


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

char *
ncache_lookup(buf, blen, fp)
	char *buf;			/* receiving name buffer */
	int blen;			/* receiving buffer length */
	int *fp;			/* full path reply */
{

# if	defined(HASDEVKNC) || defined(HASNFSKNC)
	char *cp = buf;
	struct l_nch *lc;
	int nl, rlen;

	*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);
/*
 * Look up the name cache entry for the device and inode number.
 */
	if (!Lf->dev_def || Lf->inp_ty != 1)
		return(NULL);
	if (Csz
	&&  (lc = ncache_addr(&Lf->dev, Lf->inode))
	&&  !lc->dup)
	{
	    if ((nl = (int)lc->nl) > (blen - 1))
		return(*cp ? cp : 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
	 * 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->dir && Lf->fs_ino
		    &&  lc->dir == Lf->fs_ino)
			*fp = 1;
		    break;
		}
		lc = lc->pa;
		if (lc->dup)
		    break;
		if (lc->ino && Lf->fs_ino
		&&  lc->ino == 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 : NULL);
	}
# endif	/* defined(HASDEVKNC) || defined(HASNFSKNC) */

	return(NULL);
}
#endif	/* defined(HASNCACHE) */
