/*
 * dproc.c - SCO Unix 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";
static char *rcsid = "$Id: dproc.c,v 1.16 95/11/22 12:44:19 abe Exp $";
#endif

#include "lsof.h"


/*
 * Local static values
 */

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

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


static struct var Var;			/* kernel variables */



_PROTOTYPE(static void get_cdevsw,(void));
_PROTOTYPE(static void get_kernel_access,(void));

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

_PROTOTYPE(static void readfsinfo,(void));
_PROTOTYPE(static void process_text,(struct pregion *prp));


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

void
gather_proc_info()
{
	int i, j, nf, pbc, px;
	struct proc *p;
	static char *pb = NULL;
	int pid, pgrp;
	short pss, sf;
	static struct user *u;
	static char *ua = NULL;
	unsigned int uid;
/*
 * Allocate user structure buffer.
 */
	if (!ua) {
	    if ((ua = (char *)malloc(MAXUSIZE * NBPC)) == (char *)NULL) {
		(void) fprintf(stderr,
		    "%s: no space for %d byte user structure buffer\n",
		    Pn, MAXUSIZE * NBPC);
		exit(1);
	    }
	    u = (struct user *)ua;
	}
/*
 * Allocate proc structure buffer.
 */
	if (!pb) {
	    if ((pb = (char *)malloc(sizeof(struct proc) * PROCBFRD)) == NULL) {
		(void) fprintf(stderr, "%s: no space for %d proc structures\n",
		    Pn, PROCBFRD);
		exit(1);
	    }
	}

#if     defined(HASNCACHE)
/*
 * Read kernel name caches.
 */
        ncache_load();
#endif  /* defined(HASNCACHE) */

/*
 * 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((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, (UID_ARG)uid, u->u_comm,
			(int)pss, (int)sf);
		Plf = NULL;
	/*
	 * Save current working directory information.
	 */
		if (u->u_cdir) {
			alloc_lfile(CWD, -1);
			process_node((caddr_t)u->u_cdir);
			if (Lf->sf)
				link_lfile();
		}
	/*
	 * Save root directory information.
	 */
		if (u->u_rdir) {
			alloc_lfile(RTD, -1);
			process_node((caddr_t)u->u_rdir);
			if (Lf->sf)
				link_lfile();
		}
	/*
	 * Print information on the text file.
	 */
		if (p->p_region)
			process_text(p->p_region);
	/*
	 * Save information on file descriptors.
	 */

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

		for (i = 0; i < nf; i++) {
			if (u->u_ofile[i]) {
				alloc_lfile(NULL, i);
				process_file(u->u_ofile[i]);
				if (Lf->sf)
					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;
/*
 * Check cdevsw[] kernel addresses.
 * Read cdevsw[] count from kernel's cdevcnt.
 */
	if (Nl[X_CDEVSW].n_value == (long)NULL
	||  Nl[X_CDEVCNT].n_value == (long)NULL
	||  kread((KA_T)Nl[X_CDEVCNT].n_value, (char *)&Cdevcnt,
		sizeof(Cdevcnt))
	||  Cdevcnt < 1)
		return;
/*
 * Allocate cache space.
 */
	if ((Cdevsw = (char **)malloc(Cdevcnt * sizeof(char *))) == NULL) {
		(void) fprintf(stderr, "%s: no space for %d cdevsw[] names\n",
			Pn, Cdevcnt);
		exit(1);
	}
/*
 * Allocate temporary space for a copy of cdevsw[] and read it.
 */
	i = Cdevcnt * sizeof(struct cdevsw);
	if ((tmp = (struct cdevsw *)malloc(i)) == NULL) {
		(void) fprintf(stderr, "%s: no space for %d cdevsw[] entries\n",
			Pn, Cdevcnt);
		exit(1);
	}
	if (kread((KA_T)Nl[X_CDEVSW].n_value, (char *)tmp, i)) {
		(void) free((FREE_P *)Cdevsw);
		Cdevsw = 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] = NULL;
		if (c->d_name == (char *)NULL)
		    continue;
		if (kread((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;
		if ((Cdevsw[i] = (char *)malloc(strlen(buf) + 1)) == NULL) {
		    (void) fprintf(stderr,
			"%s: no space for cdevsw[%d] name: %s\n",
			Pn, i, buf);
		    exit(1);
		}
		(void) strcpy(Cdevsw[i], buf);
		if ( ! HaveCloneMajor && strcmp(buf, "clone") == 0) {
		    CloneMajor = i;
		    HaveCloneMajor = 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;

/*
 * See if the non-KMEM memory file is readable.
 */
	if (Memory && !is_readable(Memory, 1))
		exit(1);
/*
 * 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);
	}
/*
 * See if the name list file is readable.
 */
	if (Nmlst && !is_readable(Nmlst, 1))
		exit(1);
/*
 * 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);
	}

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

/*
 * Check proc table pointer.
 */
	if ((Kp = Nl[X_PROC].n_value) == (long)NULL) {
	    (void) fprintf(stderr, "%s: no proc table pointer\n", Pn);
	    exit(1);
	}

#if	_SCOV<500
/*
 * Read pregion information.
 */
	if (Nl[X_PREGPP].n_value == (long)NULL
	||  kread((KA_T)Nl[X_PREGPP].n_value, (char *)&Npp, sizeof(Npp))
	||  Npp < 1) {
	    (void) fprintf(stderr,
		"%s: can't read pregion count (%d) from 0x%#x\n",
		Pn, Npp, Nl[X_PREGPP].n_value); 
	    exit(1);
	}
	Prsz = (MALLOC_S)(Npp * sizeof(struct pregion));
	if ((Pr = (struct pregion *)malloc(Prsz)) == NULL) {
	    (void) fprintf(stderr,
		"%s: can't allocate space for %d pregions\n",
		Pn, Npp);
	    exit(1);
	}
#endif	/* _SCOV< 500 */

/*
 * Read system configuration information.
 */
	if (Nl[X_VAR].n_value == (long)NULL
	||  kread((KA_T)Nl[X_VAR].n_value, (char *)&Var, sizeof(Var)))
	{
	    (void) fprintf(stderr,
		"%s: can't read system configuration info\n", Pn);
	    exit(1);
	}
/*
 * Read system clock values -- Hz and lightning bolt timer.
 */
	if (Nl[X_HZ].n_value == (long)NULL
	||  kread((KA_T)Nl[X_HZ].n_value, (char *)&Hz, sizeof(Hz)))
	{
	    if (!Fwarn)
		(void) fprintf(stderr, "%s: WARNING: can't read Hz from %#lx\n",
		    Pn, Nl[X_HZ].n_value);
	    Hz = -1;
	}
	if (!Fwarn
	&&  (Nl[X_LBOLT].n_value == (long)NULL
	||   kread((KA_T)Nl[X_LBOLT].n_value, (char *)&lbolt, sizeof(lbolt))))
	{
	    (void) fprintf(stderr,
		"%s: WARNING: can't read lightning bolt timer from %#lx\n",
		Pn, Nl[X_LBOLT].n_value);
	}
/*
 * Get socket device number and socket table address.
 */
	if (Nl[X_SOCKDEV].n_value == (long)NULL
	||  kread((KA_T)Nl[X_SOCKDEV].n_value, (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 ((Socktab = (KA_T)Nl[X_SOCKTAB].n_value) == (long)NULL) {
	    (void) fprintf(stderr,
		"%s: WARNING: socket table address is NULL.\n", Pn);
	    (void) fprintf(stderr,
		"      Socket output may be incomplete.\n");
	    return;
	}

#if	_SCOV>=40
/*
 * Get extended device table parameters.  These are needed by the
 * kernel versions of the major() and minor() device numbers; they
 * identify socket devices and assist in the conversion of socket
 * device numbers to socket table addresses.
 */
	if (Nl[X_NXD].n_value == (long)NULL
	||  kread((KA_T)Nl[X_NXD].n_value,(char *)&nxdevmaps,sizeof(nxdevmaps))
	||  nxdevmaps < 0)
	{
	    (void) fprintf(stderr,
		"%s: WARNING: bad extended device table size (%d) at 0x%#x.\n",
		Pn, nxdevmaps, Nl[X_NXD].n_value);
	    (void) fprintf(stderr,
		"      Socket output may be incomplete.\n");
	    return;
	}
	len = (MALLOC_S)((nxdevmaps + 1) * sizeof(struct XDEVMAP));
	if ((Xdevmap = (struct XDEVMAP *)malloc(len)) == (struct XDEVMAP *)NULL)
	{
	    (void) fprintf(stderr, "%s: no space for %d byte xdevmap table\n",
		Pn, len);
	    exit(1);
	}
	if (Nl[X_XDEV].n_value == (long)NULL
	||  kread((KA_T)Nl[X_XDEV].n_value, (char *)Xdevmap, len))
	{
	    (void) fprintf(stderr,
		"%s: WARNING: can't read %d byte xdevmap table at 0x%#x.\n",
		Pn, len, Nl[X_XDEV].n_value);
	    (void) fprintf(stderr,
		"      Socket output may be incomplete.\n");
	    return;
	}
#endif	/* _SCOV>=40 */

	HaveSockdev = 1;
}


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

void
initialize()
{
	get_kernel_access();
	get_cdevsw();
	iuidcache(Var.v_proc);
	readfsinfo();
}


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

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)-1L)
		return(1);
	if ((br = read(Kmem, buf, len)) < 0)
		return(1);
	return(((READLEN_T)br == len) ? 0 : 1);
}


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

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

#if	_SCOV>=500
	struct pregion *pc, ps;
#endif	/* _SCOV>=500 */

/*
 * Read and process the pregions.
 */

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

	{

#if	_SCOV>=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((KA_T)pc, (char *)p, sizeof(ps)))
			return;
#endif	/* _SCOV>=500 */

		if (p->p_reg == NULL)
			continue;
	/*
	 * Read the region.
	 * Skip entries with no node pointers and duplicate node addresses.
	 */
		if (kread((KA_T)p->p_reg, (char *)&r, sizeof(r)))
			continue;
		if ((na = (caddr_t)r.r_iptr) == NULL)
			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 == NULL) {
			if ((Nc = (caddr_t *)malloc((MALLOC_S)
				  (sizeof(KA_T) * 10)))
			== NULL) {
				(void) fprintf(stderr,
					"%s: no txt ptr space, PID %d\n",
					Pn, Lp->pid);
				exit(1);
			}
			Nn = 10;
		} else if (j >= Nn) {
			Nn += 10;
			if ((Nc = (caddr_t *)realloc((MALLOC_P *)Nc,
				   (MALLOC_S)(Nn * sizeof(KA_T))))
			== NULL) {
				(void) fprintf(stderr,
					"%s: no more txt ptr space, PID %d\n",
					Pn, Lp->pid);
				exit(1);
			}
		}
		Nc[j++] = na;
	/*
	 * Save text node and mapped region information.
	 */
		switch (p->p_type) {
 			case PT_DATA:		/* data and text of */
 			case PT_TEXT:		/* executing binaries */
 				ty = " txt";
 				break;
  			case PT_LIBDAT:		/* shared library data and */
  			case PT_LIBTXT:		/* COFF format text */
  				ty = " ltx";
  				break;
 			case PT_SHFIL:		/* memory mapped file */
 				ty = " mem";
 				break;
 			case PT_V86:		/* virtual 8086 mode */
 				ty = " v86";
				break;
 			case PT_VM86:		/* MERGE386 vm86 region */
 				ty = " m86";
 				break;
  			default:		/* all others as a hex number */
				(void) sprintf(tyb, " M%02x", p->p_type & 0xff);
 				ty = tyb;
		}
		if (ty) {
			alloc_lfile(ty, -1);
			process_node(na);
			if (Lf->sf)
				link_lfile();
		}
	}
}


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

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

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


#if	defined(HASNCACHE)

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

#include <sys/fs/s5inode.h>

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

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


/*
 * Device and inode (DEV) cache values.
 */

struct l_DEV_nch {			/* DEV local cache entry structure */

	dev_t dev;			/* device */
	unsigned long inum;		/* inode number */
	unsigned long pa_inum;		/* parent inode number */
	struct l_DEV_nch *pa;		/* parent DEV_nc address */
	char nm[DIRSIZ+1];		/* name */
	unsigned char nl;		/* name length */
	unsigned char dup;		/* duplicate entry status */
};

static int DEV_asz = 0;			/* DEV cache allocated size */
static int DEV_csz = 0;			/* DEV cache current size */
static KA_T DEV_head_DTFS = (KA_T)NULL;	/* DT file system hash head */
static KA_T DEV_head_HTFS = (KA_T)NULL;	/* HT file system hash head */
static KA_T DEV_head_SYSV = (KA_T)NULL;	/* SYSV file system hash head */
static int DEV_hm = 0;			/* DEV cache hash mask */
static struct l_DEV_nch **DEV_hp = (struct l_DEV_nch **)NULL;
					/* DEV cache hash pointers */
static int DEV_nh = 0;			/* DEV cache hash pointer count */
static struct l_DEV_nch *DEV_nc = (struct l_DEV_nch *)NULL;
					/* DEV cache */

# if	defined(HAS_NFS)
/*
 * NFS cache values.
 */

struct l_NFS_nch {			/* NFS local cache entry structure */

	struct rnode *rp;		/* rnode address */
	struct rnode *dp;		/* parent rnode address */
	struct l_NFS_nch *pa;		/* parent NFS_nc address */
	char nm[NC_NAMLEN+1];		/* name */
	unsigned char nl;		/* name length */
	unsigned char dup;		/* duplicate entry status */
};

static int NFS_asz = 0;			/* NFS cache allocated size */
static int NFS_csz = 0;			/* NFS cache current size */
static int NFS_hm;			/* NFS cache hash mask */
static struct l_NFS_nch **NFS_hp = (struct l_NFS_nch **)NULL;
					/* NFS_nc hash pointers */
static int NFS_ksz = 0;			/* NFS cache kernel size */
static int NFS_nh = 0;			/* NFS cache hash pointer count */
static struct l_NFS_nch *NFS_nc = (struct l_NFS_nch *)NULL;
					/* NFS cache */
# endif	/* defined(HAS_NFS) */


_PROTOTYPE(static int DEV_enter,(struct l_DEV_nch *le, char *nm, int nl, char *fs));
_PROTOTYPE(static struct l_DEV_nch *DEV_ncache_addr,(dev_t *d, unsigned long i));
_PROTOTYPE(static void DEV_nosp,(int len));

# if	_SCOV>=500
_PROTOTYPE(static void DTFS_load,());
_PROTOTYPE(static void HTFS_load,());
# endif /* _SCOV>=500 */


# if	defined(HAS_NFS)
_PROTOTYPE(static struct l_NFS_nch *NFS_addr,(struct rnode *r));
_PROTOTYPE(static void NFS_load,(void));
_PROTOTYPE(static void NFS_nosp,(int len));
_PROTOTYPE(static int NFS_root,(struct rnode *r));
# endif	/* defined(HAS_NFS) */

_PROTOTYPE(static void SYSV_load,());


# if	defined(HAS_NFS)
#define NFS_hash(v)	NFS_hp+((((int)(v)>>2)*31415)&NFS_hm)
# endif	/* defined(HAS_NFS) */

#define DEV_hash(d, i)	DEV_hp+((((int)(d + i)>>2)*31415)&DEV_hm)

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


/*
 * DEV_enter() - make a l_DEV_nch entry
 */

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

	if (DEV_csz >= DEV_asz) {
	    DEV_asz += LNCHINCRSZ;
	    len = (MALLOC_S)(DEV_asz * sizeof(struct l_DEV_nch));
	    if ((DEV_nc = (struct l_DEV_nch *)realloc(DEV_nc, len))
	    == (struct l_DEV_nch *)NULL) {
		(void) fprintf(stderr,
		    "%s: no more space for %d byte local name cache: %s\n",
		    Pn, len, fs);
		exit(1);
	    }
	}
	lc = &DEV_nc[DEV_csz];
	lc->dev = le->dev;
	lc->inum = le->inum;
	lc->pa_inum = le->pa_inum;
	(void) strncpy(lc->nm, nm, nl);
	lc->nm[nl] = '\0';
	lc->nl = (unsigned char)nl;
	lc->dup = 0;
	if (++DEV_csz >= (10 * DEFNCACHESZ)) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING: %s name cache truncated at %d entries\n",
		    Pn, fs, DEV_csz);
	    return(1);
	}
	return(0);
}


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

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

	for (hp = DEV_hash(*d, i); *hp; hp++) {
	    if ((*hp)->dev == *d && (*hp)->inum == i)
		return(*hp);
	}
	return(NULL);
}


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

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


# if	_SCOV>=500
/*
 * DTFS_load() - load DTFS cache
 */

static void
DTFS_load()
{
	struct dthashq hq;
	int len;
	KA_T kp;
	struct dtcachent nc;
	struct l_DEV_nch lc;
	char nm[DTNCMAX+1];
/*
 * Read hash queue head.
 */
	if (!Nl[X_DT_NCACHE].n_value
	||  kread((KA_T)Nl[X_DT_NCACHE].n_value, (char *)&hq, sizeof(hq)))
	    return;
/*
 * Build a local copy of the kernel name cache.
 */
	nm[DTNCMAX] = '\0';
	for (kp = (KA_T)hq.dh_forw; kp; ) {
	    if (kread(kp, (char *)&nc, sizeof(nc)))
		break;
	    if ((kp = (KA_T)nc.dn_av_forw) == (KA_T)hq.dh_forw)
		kp = (KA_T)NULL;
	    if (!nc.dn_dev && !nc.dn_newinum)
		continue;
	    (void) strncpy(nm, nc.dn_name, DTNCMAX);
	    if ((len = strlen(nm)) < 1)
		continue;
	    if (len < 3 && nc.dn_name[0] == '.') {
		if (len == 1 || (len == 2 && nc.dn_name[1] == '.'))
		    continue;
	    }
	    lc.dev = nc.dn_dev;
	    lc.inum = (unsigned long)nc.dn_newinum;
	    lc.pa_inum = (unsigned long)nc.dn_inum;
	    if (DEV_enter(&lc, nm, len, "DTFS"))
		break;
	}
}
# endif	/* _SCOV>=500 */


# if	_SCOV>=500
/*
 * HTFS_load() - load HTFS cache
 */

static void
HTFS_load()
{
	dev_t d;
	struct hthashq hq;
	int len;
	KA_T kp;
	struct l_DEV_nch lc;
	struct htcachent nc;
	char nm[DIRSIZ+1];
/*
 * Read hash queue head.
 */
	if (!Nl[X_HT_NCACHE].n_value
	||  kread((KA_T)Nl[X_HT_NCACHE].n_value, (char *)&hq, sizeof(hq)))
	    return;
/*
 * Build a local copy of the kernel name cache.
 */
	nm[DIRSIZ] = '\0';
	for (kp = (KA_T)hq.forw; kp; ) {
	    if (kread(kp, (char *)&nc, sizeof(nc)))
		break;
	    if ((kp = (KA_T)nc.av_forw) == (KA_T)hq.forw)
		kp = (KA_T)NULL;
	    if (!nc.dev && !nc.newinum)
		continue;
	    (void) strncpy(nm, nc.name, DIRSIZ);
	    if ((len = strlen(nm)) < 1)
		continue;
	    if (len < 3 && nc.name[0] == '.') {
		if (len == 1 || (len == 2 && nc.name[1] == '.'))
		    continue;
	    }
	    lc.dev = (dev_t)nc.dev;
	    lc.inum = (unsigned long)nc.newinum;
	    lc.pa_inum = (unsigned long)nc.inum;
	    if (DEV_enter(&lc, nm, len, "HTFS"))
		break;
	}
}
# endif	/* _SCOV>=500 */


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

static void
ncache_load()
{
	struct l_DEV_nch **hp, *lc;
	int f, i, len;

	if (!Fncache)
	    return;

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

/*
 * Load DEV cache.
 */
	if (DEV_asz == 0) {

	/*
	 * Initialize DEV cache.
	 */
	    DEV_asz = DEFNCACHESZ;
	/*
	 * Allocate space for the local cache.
	 */
	    len = DEFNCACHESZ * sizeof(struct l_DEV_nch);
	    if ((DEV_nc = (struct l_DEV_nch *)malloc((MALLOC_S)len)) == NULL)
		(void) DEV_nosp(len);
	} else {

	/*
	 * Re-use DEV cache, but don't re-use hash pointers.
	 */
	    if (DEV_hp) {
		(void) free((FREE_P *)DEV_hp);
		DEV_hp = (struct l_DEV_nch **)NULL;
	    }
	}
	DEV_csz = 0;
/*
 * Load the SYSV file system cache into the DEV cache.
 */
	(void) SYSV_load();

# if	_SCOV>=500
/*
 * Load the DT and HT file system caches into the DEV cache.
 */
	(void) DTFS_load();
	(void) HTFS_load();
# endif	/* _SCOV>=500 */

/*
 * Reduce DEV cache memory usage, as required.
 */
	if (DEV_csz < 1) {
	    DEV_csz = 0;
	    if (!RptTm) {
		(void) free((FREE_P *)DEV_nc);
		DEV_nc = NULL;
	    }
	    return;
	}
	if (DEV_csz < DEV_asz && !RptTm) {
	    len = DEV_csz * sizeof(struct l_DEV_nch);
	    if ((DEV_nc = (struct l_DEV_nch *)realloc(DEV_nc, len)) == NULL)
		(void)DEV_nosp(len);
	    DEV_asz = DEV_csz;
	}
/*
 * Build a hash table to locate DEV_cache entries.
 */
	for (DEV_nh = 1; DEV_nh < DEV_csz; DEV_nh <<= 1)
	    ;
	DEV_nh <<= 1;
	DEV_hm = DEV_nh - 1;
	if ((DEV_hp = (struct l_DEV_nch **)calloc((MALLOC_S)(DEV_nh + DEV_csz),
						  sizeof(struct l_DEV_nch *)))
	== NULL) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: no space for %d DEV name cache hash pointers\n",
		    Pn, DEV_nh + DEV_csz);
	    exit(1);
	}
/*
 * Enter DEV_cache pointers in the hash table.  Look for entries with the
 * same device and inode numbers that have different names.
 */
	for (i = 0, lc = DEV_nc; i < DEV_csz; i++, lc++) {
	    for (hp = DEV_hash(lc->dev, lc->inum), f = 0; *hp; hp++) {
		if ((*hp)->dev == lc->dev && (*hp)->inum == lc->inum) {
		    if (strcmp((*hp)->nm, lc->nm) == 0)
			f = 1;
		    else {
			f = 2;	/* same device and inode, different names */
			break;
		    }
		}
	    }
	    if (!f)
		*hp = lc;
	    else if (f == 2) {

	    /*
	     * Since entries with the same device and inode numbers but
	     * different names were located, mark entries with the device
	     * and inode numbers as duplicates.
	     */
		for (hp = DEV_hash(lc->dev, lc->inum); *hp; hp++) {
		    if ((*hp)->dev == lc->dev && (*hp)->inum == lc->inum)
			(*hp)->dup = 1;
		}
	    }
	}
/*
 * Make a final pass through the DEV and convert parent device
 * and inode numbers to local name cache pointers.
 */
	for (i = 0, lc = DEV_nc; i < DEV_csz; i++, lc++) {
	    if (!lc->pa_inum)
		continue;
	    lc->pa = DEV_ncache_addr(&lc->dev, lc->pa_inum);
	}
}


/*
 * 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 */
{
	char *cp = buf;
	int nl, rlen;
	struct l_DEV_nch *DEV_lc;

# if	defined(HAS_NFS)
	struct l_NFS_nch *NFS_lc;
# endif	/* defined(HAS_NFS) */


	*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	defined(HAS_NFS)
/*
 * Look up the name cache entry for the NFS rnode address.
 */
	if (Lf->is_nfs) {
	    if (NFS_nc
	    &&  (NFS_lc = NFS_addr((struct rnode *)Lf->na))
	    &&  !NFS_lc->dup)
	    {
		if ((nl = (int)NFS_lc->nl) > (blen - 1))
		    return(*cp ? cp : NULL);
		cp = buf + blen - nl - 1;
		rlen = blen - nl - 1;
		(void) strcpy(cp, NFS_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 (!NFS_lc->pa) {
			if (NFS_root(NFS_lc->dp))
			    *fp = 1;
			break;
		    }
		    NFS_lc = NFS_lc->pa;
		    if (NFS_lc->dup)
			break;
		    if (((nl = (int)NFS_lc->nl) + 1) > rlen)
			break;
		    *(cp - 1) = '/';
		    cp--;
		    rlen--;
		    (void) strncpy((cp - nl), NFS_lc->nm, nl);
		    cp -= nl;
		    rlen -= nl;
	    	}
		return(*cp ? cp : NULL);
	    }
	} else
# endif	/* defined(HAS_NFS) */

	{

	/*
	 * Look up the name cache entry for the DEV device and inode number.
	 */
	    if (!Lf->dev_def || Lf->inp_ty != 1)
		return(NULL);
	    if (DEV_csz
	    &&  (DEV_lc = DEV_ncache_addr(&Lf->dev, Lf->inode))
	    &&  !DEV_lc->dup)
	    {
		if ((nl = (int)DEV_lc->nl) > (blen - 1))
		    return(*cp ? cp : NULL);
		cp = buf + blen - nl - 1;
		rlen = blen - nl - 1;
		(void) strcpy(cp, DEV_lc->nm);
	    /*
	     * Look up the DEV 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 (!DEV_lc->pa) {
			if (DEV_lc->pa_inum && Lf->fs_ino
			&&  DEV_lc->pa_inum == Lf->fs_ino)
			    *fp = 1;
			break;
		    }
		    DEV_lc = DEV_lc->pa;
		    if (DEV_lc->dup)
			break;
		    if (DEV_lc->inum && Lf->fs_ino
		    &&  DEV_lc->inum == Lf->fs_ino) {
			*fp = 1;
			break;
		    }
		    if (((nl = (int)DEV_lc->nl) + 1) > rlen)
		        break;
		    *(cp - 1) = '/';
		    cp--;
		    rlen--;
		    (void) strncpy((cp - nl), DEV_lc->nm, nl);
		    cp -= nl;
		    rlen -= nl;
		}
		return(*cp ? cp : NULL);
	    }
	}
	return(NULL);
}


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

static struct l_NFS_nch *
NFS_addr(r)
	struct rnode *r;			/* rnode's address */
{
	struct l_NFS_nch **hp;

	for (hp = NFS_hash(r); *hp; hp++) {
	    if ((*hp)->rp == r)
		return(*hp);
	}
	return(NULL);
}


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

static void
NFS_load()
{
	int f, i, j, len;
	struct l_NFS_nch **hp, *lc;
	struct nc_hash hq;
	static KA_T kh = (KA_T)NULL;
	static KA_T kp, kw;
	static int i_NFS_nc = 0;
	static int nh = 0;
	struct ncache nc;

	if (NFS_ksz == 0) {

	/*
	 * Do startup (first-time) functions.
	 */
	    if (!Nl[X_NFS_NCSIZE].n_value
	    ||  kread((KA_T)Nl[X_NFS_NCSIZE].n_value, (char *)&NFS_ksz,
		      sizeof(NFS_asz)))
	    {
		if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING: can't read NFS name cache size (%s): %#x\n",
		    Pn, Nl[X_NFS_NCSIZE].n_name, Nl[X_NFS_NCSIZE].n_value);
		NFS_csz = NFS_ksz = 0;
		return;
	    }
	    if (NFS_ksz < 1) {
		if (!Fwarn) {
		    (void) fprintf(stderr,
			"%s: WARNING: kernel NFS name cache size: %d\n",
			Pn, Nc);
		}
		NFS_csz = NFS_ksz = 0;
		return;
	    }
	    NFS_asz = NFS_ksz;
	/*
	 * Establish kernel NFS cache hash list address.
	 */
	    if (!(kh = Nl[X_NFS_HASH].n_value)) {
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: WARNING: NFS hash list address: %x\n",
			Pn, Nl[X_NFS_HASH].n_value);
		NFS_asz = NFS_ksz = 0;
		return;
	    }
	/*
	 * Establish kernel NFS cache hash list length.
	 */
	    if (!Nl[X_NFS_HASHSZ].n_value
	    ||  kread((KA_T)Nl[X_NFS_HASHSZ].n_value, (char *)&nh, sizeof(nh)))
	    {
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: WARNING: NFS hash list size: %x\n",
			Pn, Nl[X_NFS_HASHSZ].n_value);
		NFS_asz = NFS_ksz = 0;
		return;
	    }
	/*
	 * Allocate space for the local cache.
	 */
	    len = NFS_asz * sizeof(struct l_NFS_nch);
	    if ((NFS_nc = (struct l_NFS_nch *)malloc((MALLOC_S)len))
	    == (struct l_NFS_nch *)NULL)
		(void) NFS_nosp(len);
	} else {

	/*
	 * Do setup for repeat calls.
	 */
	    if (NFS_hp) {
		(void) free((FREE_P *)NFS_hp);
		NFS_hp = NULL;
	    }
	}
	NFS_csz = 0;
/*
 * Read each NFS name cache hash pointer, follow its chain, and add
 * its entries to the local name cache.
 */
	for (kw = kh, j = 0, lc = NFS_nc;
	     j < nh && NFS_csz < (10 * DEFNCACHESZ);
	     kw += sizeof(struct nc_hash), j++)
	{
	    if (kread(kw, (char *)&hq, sizeof(hq)))
		continue;
	    if ((KA_T)hq.hash_next == kw)
		continue;
	    for (kp = (KA_T)hq.hash_next; kp; ) {
		if (kread(kp, (char *)&nc, sizeof(nc)))
		    break;
		if ((kp = (KA_T)nc.hash_next) == (KA_T)hq.hash_next)
		    kp = (KA_T)NULL;
		if (!nc.rp)
		    continue;
		if ((len = (int)nc.namlen) < 1 || len > NC_NAMLEN)
		    continue;
		if (len < 3 && nc.name[0] == '.') {
		    if (len == 1 || (len == 2 && nc.name[1] == '.'))
			continue;
		}
		if (NFS_csz >= NFS_asz) {
		    NFS_asz += LNCHINCRSZ;
		    len = NFS_asz * sizeof(struct l_NFS_nch);
		    if ((NFS_nc = (struct l_NFS_nch *)realloc(NFS_nc, len))
		    == NULL)
			(void) NFS_nosp(len);
		    lc = &NFS_nc[NFS_csz];
	        }
		lc->rp = nc.rp;
		lc->dp = nc.dp;
		lc->pa = (struct l_NFS_nch *)NULL;
		(void) strncpy(lc->nm, nc.name, len);
		lc->nm[len] = '\0';
		lc->nl = (unsigned char)strlen(lc->nm);
		lc->dup = 0;
		lc++;
		if (++NFS_csz >= (10 * DEFNCACHESZ)) {
		    if (!Fwarn)
			(void) fprintf(stderr,
			    "%s: WARNING: NFS cache truncated at %d entries\n",
			    Pn, NFS_csz);
		    break;
		}
	    }
	}
/*
 * Reduce memory usage, as required.
 */
	if (NFS_csz < 1) {
	    NFS_csz = 0;
	    if (!RptTm) {
		(void) free((FREE_P *)NFS_nc);
		NFS_nc= NULL;
	    }
	    return;
	}
	if (NFS_csz < NFS_asz && !RptTm) {
	    len = NFS_csz * sizeof(struct l_NFS_nch);
	    if ((NFS_nc = (struct l_NFS_nch *)realloc(NFS_nc, len))
	    == (struct l_NFS_nch *)NULL)
		(void) NFS_nosp(len);
	    NFS_asz = NFS_csz;
	}
/*
 * Build a hash table to locate NFS_cache entries.
 */
	for (NFS_nh = 1; NFS_nh < NFS_csz; NFS_nh <<= 1)
	    ;
	NFS_nh <<= 1;
	NFS_hm = NFS_nh - 1;
	if ((NFS_hp = (struct l_NFS_nch **)calloc(NFS_nh + NFS_csz,
						  sizeof(struct l_NFS_nch *)))
	== (struct l_NFS_nch **)NULL) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: no space for %d NFS name cache hash pointers\n",
		    Pn, NFS_nh + NFS_csz);
	    exit(1);
	}
/*
 * Enter NFS_cache pointers in the hash table.  Look for entries with the
 * same rnode addresses that have different names.
 */
	for (i = 0, lc = NFS_nc; i < NFS_csz; i++, lc++) {
	    for (hp = NFS_hash(lc->rp), f = 0; *hp; hp++) {
		if ((*hp)->rp == lc->rp) {
		    if (strcmp((*hp)->nm, lc->nm) == 0)
			f = 1;
		    else {
			f = 2;	/* same rnode address, different names */
			break;
		    }
		}
	    }
	    if (!f)
		*hp = lc;
	    else if (f == 2) {

	    /*
	     * Since entries with the same rnode address and different names
	     * were located, mark entries with the rnode address as duplicates.
	     */
		for (hp = NFS_hash(lc->rp); *hp; hp++) {
		    if ((*hp)->rp == lc->rp)
			(*hp)->dup = 1;
		}
	    }
	}
/*
 * Make a final pass through the local cache and convert parent rnode
 * addresses to local name cache pointers.
 */
	for (i = 0, lc = NFS_nc; i < NFS_csz; i++, lc++) {
	    if (!lc->dp)
		continue;
	    lc->pa = NFS_addr(lc->dp);
	}
}


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

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


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

# if	_SCOV>=500
	unsigned short *n1;
# endif	/* _SCOV>=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((KA_T)r, (char *)&rn, sizeof(rn)))
	    return(0);

# if	_SCOV<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	/* _SCOV>=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	/* _SCOV<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(struct rnode *));
		if ((rc = (struct rnode **)malloc(len))
		!=  (struct rnode **)NULL)
		    rnca = 10;
	    } else {
		len = (MALLOC_S)((rnca + 10) * sizeof(struct rnode *));
		if ((rc = (struct rnode **)realloc(rc, len))
		!=  (struct rnode **)NULL)
		    rnca += 10;
	    }
	    if (rc == (struct rnode **)NULL) {
		(void) fprintf(stderr, "%s: no space for root rnode table\n",
		    Pn);
		exit(1);
	    }
	}
	rc[rnc++] = r;
	return(1);
}
# endif	/* defined(HAS_NFS) */



/*
 * SYSV_load() - load SYSV cache
 */

static void
SYSV_load()
{
	struct s5hashq hq;
	int len;
	KA_T kp;
	struct l_DEV_nch lc;
	struct s5cachent nc;
	char nm[DIRSIZ+1];
/*
 * Read hash queue head.
 */
	if (!Nl[X_S5_NCACHE].n_value
	||  kread((KA_T)Nl[X_S5_NCACHE].n_value, (char *)&hq, sizeof(hq)))
	    return;
/*
 * Build a local copy of the kernel name cache.
 */
	nm[DIRSIZ] = '\0';
	for (kp = (KA_T)hq.forw; kp; ) {
	    if (kread(kp, (char *)&nc, sizeof(nc)))
		break;
	    if ((kp = (KA_T)nc.av_forw) == (KA_T)hq.forw)
		kp = (KA_T)NULL;
	    if (!nc.dev && !nc.newinum)
		continue;
	    (void) strncpy(nm, nc.name, DIRSIZ);
	    if ((len = strlen(nm)) < 1)
		continue;
	    if (len < 3 && nc.name[0] == '.') {
		if (len == 1 || (len == 2 && nc.name[1] == '.'))
		    continue;
	    }
	    lc.dev = (dev_t)nc.dev;
	    lc.inum = (unsigned long)nc.newinum;
	    lc.pa_inum = (unsigned long)nc.inum;
	    if (DEV_enter(&lc, nm, len, "SYSV"))
		break;
	}
}
#endif	/* defined(HASNCACHE) */
