/*
 * dfile.c -- pstat-based HP-UX file functions for lsof
 */


/*
 * Copyright 1999 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 1999 Purdue Research Foundation.\nAll rights reserved.\n";
#endif


#include "lsof.h"


#if	defined(HASNCACHE)
/*
 * Local definitions
 */

#define DNLCINCR	2048		/* DNLC read increment */

#define	NFSIDH		256		/* file system ID hash count
					 * -- MUST BE A POWER OF TWO!!! */
#define	NFSID		sizeof(struct psfsid)
					/* size of psfsid structure */
#define	NL_NC		sizeof(struct l_nc)
					/* size of l_nc structure */
#define	NPSTM		sizeof(struct pst_mpathnode)
					/* size of pst_mpathnode */


/*
 * Local structure definitions
 */

struct l_nc {				/* local name cache */
	struct psfileid id;		/* node's PSTAT ID */
	struct psfileid par;		/* parent's PSTAT ID */
	struct l_nc *pl;		/* local parent name cache entry --
					 * NULL if not found or not yet
					 * accessed (see pls) */
	int pls;			/* status of pl: 0 = not accessed
					 *		 1 = accessed */
	int nl;				/* name length */
	char *nm;			/* name */
	struct l_nc *next;		/* next hash bucket link */
};

struct l_fic {				/* file system ID cache */
	struct psfsid fsid;		/* file system ID */
	int nc;				/* files cached for file system */
	struct l_fic *next;		/* next hash bucket link */
};


/*
 * Local static variables
 */

static int Nceh;			/* number of Nchash entries allocated */
static struct l_nc **Nchash = (struct l_nc **)NULL;
					/* the name cache hash buckets */
static int Ncmask;			/* power of two mask for the name
					 * cache -- sized from Nc */
static int Ndnlc;			/* number of DNLC entries via
					 * pst_dynamic.psd_dnlc_size */
static struct l_fic **Ncfsid = (struct l_fic **)NULL;
					/* the file system hash buckets */
static struct pst_fid Nzpf;		/* zeroed pst_fid (for memcmp()) */
static struct psfileid Nzps;		/* zeroed psfilid (for memcmp()) */
static int Nzpfs = 0;			/* Nzpf status: 1 = zeroed */
static int Nzpss = 0;			/* Nzps status: 1 = zeroed */


/*
 * Local macros
 */

#define	HASHFSID(i)	(Ncfsid + \
	(((int)(((((struct psfsid *)i)->psfs_id * 31415) << 3)&0xfffffff) \
	+ (int)((((((struct psfsid *)i)->psfs_type * 31415) << 5)&0xfffffff))) \
	& (NFSIDH - 1)))
#define	HASHPSFILEID(p)	(Nchash + \
	(((int)(((int)((((struct psfileid *)p)->psf_fsid.psfs_id * 31415) << 3)\
		& 0xfffffff) \
	+ (int)(((((struct psfileid *)p)->psf_fsid.psfs_type * 31415) << 5) \
		& 0xfffffff) \
	+ (int)(((((struct psfileid *)p)->psf_fileid * 31415) << 7) \
		& 0xfffffff))) \
	& Ncmask))


/*
 * Local function prototypes
 */

_PROTOTYPE(static struct l_nc *ncache_addr,(struct psfileid *ps));
_PROTOTYPE(static void ncache_free,(void));
_PROTOTYPE(static int ncache_isroot,(struct psfileid *ps));
_PROTOTYPE(static void ncache_size,(void));
#endif	/* defined(HASNCACHE) */


#if	defined(HASIPv6)
/*
 * gethostbyname2() -- an RFC2133-compatible get-host-by-name-two function
 *                     to get AF_INET and AF_INET6 addresses from host names,
 *                     using the gethostbyname() and RFC2553-compatible
 *		       getipnodebyname() functions
 */

extern struct hostent *
gethostbyname2(nm, prot)
	char *nm; 			/* host name */
	int prot;			/* protocol -- AF_INET or AF_INET6 */
{
	int err;

	if (prot == AF_INET) {

	/*
	 * This shouldn't be necessary if /etc/nsswitch.conf is correct, but
	 * it's a good fail-safe in case /etc/nsswitch.conf is missing or
	 * incorrect.
	 */
	    return(gethostbyname(nm));
	}
	return(getipnodebyname(nm, prot, 0, &err));
}
#endif	/* defined(HASIPv6) */


/*
 * get_max_fd() -- get maximum file descriptor plus one
 */

int
get_max_fd()
{
	struct rlimit r;

	if (getrlimit(RLIMIT_NOFILE, &r))
	    return(-1);
	return(r.rlim_cur);
}


#if	defined(HASNCACHE)


/*
 * ncache_addr() -- get ncache entry address
 */

static struct l_nc *
ncache_addr(ps)
	struct psfileid *ps;		/* parent's psfileid */
{
	struct l_nc **hp, *lc;

	for (hp = HASHPSFILEID(ps), lc = *hp; lc; lc = lc->next) {
	    if (!memcmp((void *)ps, (void *)&lc->id, sizeof(struct psfileid)))
		return(lc);
	}
	return((struct l_nc *)NULL);
}


/*
 * ncache_alloc() -- allocate name cache space
 */

static void
ncache_alloc()
{
	if (Nchash || Ncfsid)
	    ncache_free();
	(void) ncache_size();
	if (!(Nchash = (struct l_nc **)calloc(Nceh, sizeof(struct l_nc *))))
	{
	    (void) fprintf(stderr,
		"%s: can't allocate %d local name cache entries\n", Pn, Nceh);
	    Error();
	}
	if (Ncfsid)
	    return;
	if (!(Ncfsid = (struct l_fic **)calloc(NFSIDH, sizeof(struct l_fic *))))
	{
	    (void) fprintf(stderr,
		"%s: can't allocate %d local file system cache entries\n",
		Pn, NFSIDH);
	    Error();
	}
}


/*
 * ncache_free() -- free previous ncache allocations
 */

static void
ncache_free()
{
	int i;
	struct l_fic **fh, *fp, *fx;
	struct l_nc **nh, *np, *nx;

	if (Ncfsid) {

	/*
	 * Free file system ID hash bucket contents.
	 */
	    for (fh = Ncfsid, i = 0; i < NFSIDH; fh++, i++) {
		for (fp = *fh; fp; fp = fx) {
		    fx = fp->next;
		    (void) free((MALLOC_P *)fp);
		}
		Ncfsid[i] = (struct l_fic *)NULL;
	    }
	}
	if (Nchash) {

	/*
	 * Free name cache.
	 */
	    for (i = 0, nh = Nchash; i < Nceh; i++, nh++) {
		for (np = *nh; np; np = nx) {
		    nx = np->next;
		    if (np->nm)
			(void) free((MALLOC_P *)np->nm);
		    (void) free((MALLOC_P *)np);
		}
	    }
	    (void) free((MALLOC_P *)Nchash);
	    Nchash = (struct l_nc **)NULL;
	}
}


/*
 * ncache_isroot() -- does psfileid represent the root of a file system?
 */

static int
ncache_isroot(ps)
	struct psfileid *ps;		/* psfileid */
{
	if (!ps->psf_fsid.psfs_id && !ps->psf_fsid.psfs_type
	&&  ps->psf_fileid == -1)
	    return(1);

# if	defined(HASFSINO)
	if (!Lf->fs_ino || (Lf->inp_ty != 1) || !Lf->dev_def)
	    return(0);
	if ((Lf->dev == (dev_t)ps->psf_fsid.psfs_id)
	&&  (Lf->fs_ino == (unsigned long)ps->psf_fileid))
	    return(1);
# endif	/* defined(HASFSINO) */

	return(0);
}


/*
 * ncache_load() -- load name cache
 */

void
ncache_load()
{
	if (!Fncache)
	    return;
	(void) ncache_alloc();
	if (!Nzpfs) {
	    (void)memset((void *)&Nzpf, 0, sizeof(Nzpf));
	    Nzpfs = 1;
	}
	if (!Nzpss) {
	    (void)memset((void *)&Nzps, 0, sizeof(Nzps));
	    Nzpss = 1;
	}
}


/*
 * ncache_loadfs() -- load the name cache for a file system
 */

struct l_fic *
ncache_loadfs(fsid, fh)
	struct psfsid *fsid;		/* ID of file system to add */
	struct l_fic **fh;		/* Ncfsid hash bucket */
{
	char *cp;
	struct l_fic *f;
	int i, nl, nr;
	struct pst_mpathnode mp[DNLCINCR];
	struct l_nc **nh, *nn, *nt, *ntp;
	int x = 0;
/*
 * Allocate a new file system pointer structure and link it to its bucket.
 */
	if (!(f = (struct l_fic *)malloc(sizeof(struct l_fic)))) {
	    (void) fprintf(stderr, "%s: no fsid structure space\n", Pn);
	    Error();
	}
	f->fsid = *fsid;
	f->nc = 0;
	f->next = *fh;
	*fh = f;
	while ((nr = pstat_getmpathname(&mp[0], NPSTM, DNLCINCR, x, fsid)) > 0)
	{
	    x = mp[nr - 1].psr_idx + 1;
	    for (i = 0; i < nr; i++) {

	    /*
	     * Ignore NUL names, ".", and "..".
	     */
		if (!(nl = (int)strlen(mp[i].psr_name)))
		    continue;
		if ((nl < 3) && (mp[i].psr_name[0] == '.')) {
		    if ((nl == 1) || (mp[i].psr_name[1] == '.'))
			continue;
		}
	    /*
	     * Allocate name and name cache structure space.
	     */
		if (!(cp = (char *)malloc((MALLOC_S)(nl + 1)))) {
		    (void) fprintf(stderr,
			"%s: no name entry space (%d) for:%s\n",
			Pn, nl + 1, mp[i].psr_name);
		    Error();
		}
		if (!(nn = (struct l_nc *)malloc(sizeof(struct l_nc)))) {
		    (void) fprintf(stderr,
			"%s: no name cache entry space (%d) for: %s\n",
			Pn, (int)sizeof(struct l_nc), mp[i].psr_name);
		    Error();
		}
	    /*
	     * Fill in name cache entry, complete with name and name length.
	     */
		(void) snpf(cp, nl + 1, "%s", mp[i].psr_name);
		nn->id = mp[i].psr_file;
		nn->par = mp[i].psr_parent;
		nn->nm = cp;
		nn->nl = nl;
		nn->pl = nn->next = (struct l_nc *)NULL;
		nn->pls = 0;
		nh = HASHPSFILEID(&mp[i].psr_file);
	    /*
	     * Skip to the end of the hash bucket chain, looking for
	     * duplicates along the way.
	     */
		for (nt = *nh, ntp = (struct l_nc *)NULL;
		     nt;
		     ntp = nt, nt = nt->next)
		{
		    if (memcmp((void *)&nt->id, (void *)&nn->id, NL_NC) == 0)
			break;
		}
		if (nt) {

		/*
		 * Remove a duplicate.
		 */
		    if (ntp)
			ntp = nt->next;
		    else
			*nh = nt->next;
		    (void) free((MALLOC_P *)nt->nm);
		    (void) free((MALLOC_P *)nt);
		    (void) free((MALLOC_P *)nn->nm);
		    (void) free((MALLOC_P *)nn);
		} else {

		/*
		 * Link a new entry.
		 */
		    if (ntp)
			ntp->next = nn;
		    else
			*nh = nn;
		    f->nc++;
		}
	    }
	    if (nr < DNLCINCR)
		break;
	}
	return(f);
}


/*
 * 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;
	int ef;
	struct l_fic **fh, *fs;
	struct l_nc *lc;
	int nl, rlen;
	char *pc;

	*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 that the already-printed system mount point name is sufficient.
 */
	if (Lf->inp_ty == 1 && Lf->fs_ino && Lf->inode == Lf->fs_ino)
	    return(cp);
# endif	/* defined(HASFSINO) */

/*
 * See if cache has been loaded for this pfsid.  Don't try to load if cache
 * loading has been inhibited with -C, or unless the real or effective UID of
 * this process is root.
 */
	if ((!Myuid || Setuidroot) && Fncache) {
	    for (fh = HASHFSID(&Lf->psfid.psf_fsid), fs = *fh;
		 fs;
		 fs = fs->next)
	    {
		if (memcmp((void *)&fs->fsid, (void *)&Lf->psfid.psf_fsid,
			    NFSID)
		== 0)
		    break;
	    }
	    if (!fs)
		fs = ncache_loadfs(&Lf->psfid.psf_fsid, fh);
	} else
	    fs = (struct l_fic *)NULL;
/*
 * Search the cache for an entry whose psfileid matches.
 */
	if (!fs || !fs->nc || !(lc = ncache_addr(&Lf->psfid))) {

	/*
	 * If the node has no cache entry, see if it's the root of the file
	 * system.
	 */

# if	defined(HASFSINO)
	    if (Lf->fs_ino && (Lf->inp_ty == 1) && (Lf->fs_ino == Lf->inode))
		return(cp);
# endif	/* defined(HASFSINO) */

	/*
	 * If the file system's cache couldn't be loaded -- e.g., this lsof
	 * process lacks permission to load it or cache lookup is inhibited
	 * with -C -- but the UID of the file's process matches the UID of the
	 * lsof process, see if it's possible to read the single path name for
	 * this particular file.  (The file must have a non-zero opaque ID.)
	 */
	    if (!fs) {
		if (Fncache
		&&  (Myuid == Lp->uid)
		&&  memcmp((void *)&Lf->opfid, (void *)&Nzpf, sizeof(Nzpf))
		&&  (nl = pstat_getpathname(buf, (blen - 1), &Lf->opfid)) > 0)
		{
		    buf[nl] = '\0';
		    if (*buf == '/')
			*fp = 1;
		    return(buf);
		}
	    }
	    return((char *)NULL);
	}
	if (ncache_isroot(&lc->id)) {

	/*
	 * If the node is the root of the file system, return a response
	 * that will cause the root directory to be displayed.
	 */
	    return(cp);
	}
/*
 * Start the path assembly.
 */
	if ((nl = lc->nl) > (blen - 1))
	    return((char *)NULL);
	cp = buf + blen - nl - 1;
	rlen = blen - nl - 1;
	(void) snpf(cp, nl + 1, "%s", lc->nm);
/*
 * Look up the name cache entries that are parents of the node address.
 * Quit when:
 *
 *	there's no parent;
 *	the file system root is reached;
 *	the name length is too large to fit in the receiving buffer.
 */
	for (ef = 0; !ef;) {
	    if (!lc->pl) {
		if (!lc->pls) {

		/*
		 * If there is a parent, look up its Ncache address;
		 * otherwise quit on an incomplete path assembly.
		 */
		    if (memcmp((void *)&lc->par, (void *)&Nzps, sizeof(Nzps))) {
			lc->pl = ncache_addr(&lc->par);
			lc->pls = 1;
		    } else
			break;
		}
	    }
	    if (ncache_isroot(&lc->par)) {

	    /*
	     * If the parent entry is the file system root, enter the file
	     * system root directory, and indicate that the assembly should
	     * stop after this entry.
	     */
		if (!(pc = Lf->fsdir))
		    break;
		nl = (int)strlen(pc);
		ef = 1;
	    } else {

	    /*
	     * Use the parent link if it exists; otherwise exit on an
	     * incomplete path assembly.
	     */
	        if (!(lc = lc->pl))
		    break;
		pc = lc->nm;
		nl = lc->nl;
	    }
	/*
	 * Prefix the next path component.  Intersperse a '/' if the
	 * component doesn't end in one.
	 */
	    if (!nl)
		break;
	    if (pc[nl - 1] != '/') {
		if (1 > rlen)
		    break;
		*(cp - 1) = '/';
		cp--;
		rlen--;
	    }
	    if (nl > rlen)
		break;
	    (void) strncpy((cp - nl), pc, nl);
	    cp -= nl;
	    rlen -= nl;
	    if (ef) {

	    /*
	     * If the file system root directory was just prefixed, return
	     * a full-path indication.
	     */
		*fp = 1;
		break;
	    }
	}
	return(cp);
}


/*
 * ncache_size() -- get DNLC size
 */

static void
ncache_size()
{
	struct pst_dynamic pd;

	if (pstat_getdynamic(&pd, sizeof(pd), 1, 0) != 1) {
	    (void) fprintf(stderr, "%s: can't get dynamic status\n", Pn);
	    Error();
	}
	Ndnlc = (int)pd.psd_dnlc_size;
	for (Nceh = 1; Nceh < (Ndnlc + Ndnlc); Nceh <<= 1)
		;
	Ncmask = Nceh - 1;
}
#endif	/* defined(HASNCACHE) */


/*
 * print_dev() -- print device
 */

char *
print_dev(lf, dev)
	struct lfile *lf;		/* file whose device is to be printed */
	dev_t *dev;			/* device to be printed */
{
	static char buf[128];

	(void) snpf(buf, sizeof(buf), "%d,%#x", GET_MAJ_DEV(*dev),
		    GET_MIN_DEV(*dev));
	return(buf);
}


/*
 * process_finfo() -- process file information
 */

void
process_finfo(pd, opfid, psfid, na)
	struct pst_filedetails *pd;	/* file details */
	struct pst_fid *opfid;		/* opaque file ID for this file */
	struct psfileid *psfid;		/* PSTAT file ID for this file */
	KA_T na;			/* node address */
{
	char *cp, buf[32];
	dev_t dev;
	int devs = 0;
	int32_t lk;
	struct mounts *mp;
/*
 * Save file IDs for later use in name lookup.
 */
	Lf->opfid = *opfid;
	Lf->psfid = *psfid;

#if	defined(HASFSTRUCT)
/*
 * Save node ID.
 */
	if (na && (Fsv & FSV_NI)) {
	    Lf->fna = na;
	    Lf->fsv |= FSV_NI;
	}
#endif	/* defined(HASFSTRUCT) */

/*
 * Construct lock code.
 */
	if ((lk = pd->psfd_lckflag) & PS_FPARTRDLCK)
	    Lf->lock = 'r';
	else if (lk & PS_FPARTWRLCK)
	    Lf->lock = 'w';
	else if (lk & PS_FFULLRDLCK)
	    Lf->lock = 'R';
	else if (lk & PS_FFULLWRLCK)
	    Lf->lock = 'W';
	else
	    Lf->lock = ' ';
/*
 * Derive type from modes.
 */
	switch ((int)(pd->psfd_mode & PS_IFMT)) {
	case PS_IFREG:
	    cp = "REG";
	    Ntype = N_REGLR;
	    break;
	case PS_IFBLK:
	    cp = "BLK";
	    Ntype = N_BLK;
	    break;
	case PS_IFDIR:
	    cp = "DIR";
	    Ntype = N_REGLR;
	    break;
	case PS_IFCHR:
	    cp = "CHR";
	    Ntype = N_CHR;
	    break;
	case PS_IFIFO:
	    cp = "FIFO";
	    Ntype = N_FIFO;
	    break;
	default:
	    (void) snpf(buf, sizeof(buf), "%04o",
		(unsigned int)(((pd->psfd_mode & PS_IFMT) >> 12) & 0xfff));
	    cp = buf;
	    Ntype = N_REGLR;
	}
	if (!Lf->type[0])
	    (void) snpf(Lf->type, sizeof(Lf->type), "%s", cp);
	Lf->ntype = Ntype;
/*
 * Save device number.
 */
	switch (Ntype) {
	case N_FIFO:
	    (void) enter_dev_ch(print_kptr(na, (char *)NULL, 0));
	    break;
	default:
	    dev = Lf->dev = (dev_t)pd->psfd_dev;
	    devs = Lf->dev_def = 1;
	    if ((Ntype == N_CHR) || (Ntype == N_BLK)) {
		Lf->rdev = (dev_t)pd->psfd_rdev;
		Lf->rdev_def = 1;
	    }
	}
/*
 * Save node number.
 */
	Lf->inode = (INODETYPE)pd->psfd_ino;
	Lf->inp_ty = 1;
/*
 * Save link count.
 */
	if (Fnlink) {

	/*
	 * Ignore a zero link count only if the file is a FIFO.
	 */
	    if ((Lf->nlink = (long)pd->psfd_nlink) || (Ntype != N_FIFO))
		Lf->nlink_def = 1;
	    if (Lf->nlink_def && Nlink && (Lf->nlink < Nlink))
		Lf->sf |= SELNLINK;
	}
/*
 * Save file system identity.
 */
	if (devs) {
	    for (mp = readmnt(); mp; mp = mp->next) {
		if (dev == mp->dev) {
		    Lf->fsdir = mp->dir;
		    Lf->fsdev = mp->fsname;

#if	defined(HASFSINO)
		    Lf->fs_ino = (unsigned long)mp->inode;
#endif	/* defined(HASFSINO) */

		    break;
		}
	    }
	} else
	    mp = (struct mounts *)NULL;
/*
 * If no offset has been activated and no size saved, activate the offset or
 * save the size.
 */
	if (!Lf->off_def && !Lf->sz_def) {
	    if (Foffset)
		Lf->off_def = 1;
	    else {
		switch (Ntype) {
		case N_CHR:
		case N_FIFO:
		    Lf->off_def = 1;
		    break;
		default:
		    Lf->sz = (SZOFFTYPE)pd->psfd_size;
		    Lf->sz_def = 1;
		}
	    }
	}
/*
 * See if this is an NFS file.
 */
	if (Fnfs) {
	    if (HasNFS < 0)
		(void) scanmnttab();
	    if (HasNFS && mp && mp->is_nfs)
		Lf->sf |= SELNFS;
	}
/*
 * Test for specified file.
 */
	if (Sfile && is_file_named(NULL,
				   ((Ntype == N_CHR) || (Ntype == N_BLK) ? 1
									 : 0)))
	    Lf->sf |= SELNM;
/*
 * Enter name characters.
 */
	if (!Lf->nm && Namech[0])
	    enter_nm(Namech);
}
