/*
 * dproc.c -- pstat-based HP-UX process access 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";
static char *rcsid = "$Id";
#endif


#include "lsof.h"


/*
 * Local definitions
 */

#define	FINFOINCR	128		/* pst_fileinfo2 table allocation
					 * increment */
#define INCLMEM(s, m)	((size_t)(offsetof(struct s, m) \
			+	  sizeof(((struct s *)0)->m)))
					/* size of struct s, including
					 * member m */
#define	PSTATINCR	512		/* pst_status table allocation
					 * increment */
#define	TXTVMINCR	64		/* text and vm info table table
					 * allocation increment */
#define	VMREGINCR	64		/* VM region table table allocation
					 * increment */


/*
 * Local structures
 */

struct pstatck {
	size_t moff;			/* offset of size member in pst_static
					 * -- from offsetof(...member) */
	size_t msz;			/* structure's pst_static member
					 * inclusion size -- from INCLMEM(s, m)
					 * macro */
	size_t ssz;			/* structure size -- from
					 * sizeof(struct) */
	char *sn;			/* structure name */
} PstatCk[] = {
	{ (size_t)offsetof(struct pst_static, pst_status_size),
	  (size_t)INCLMEM(pst_static, pst_status_size),
	  sizeof(struct pst_status),
	  "pst_status" },
	{ (size_t)offsetof(struct pst_static, pst_vminfo_size),
	  (size_t)INCLMEM(pst_static, pst_vminfo_size),
	  sizeof(struct pst_vminfo),
	  "pst_vminfo" },
	{ (size_t)offsetof(struct pst_static, pst_filedetails_size),
	  (size_t)INCLMEM(pst_static, pst_filedetails_size),
	  sizeof(struct pst_filedetails),
	  "pst_filedetails" },
	{ (size_t)offsetof(struct pst_static, pst_socket_size),
	  (size_t)INCLMEM(pst_static, pst_socket_size),
	  sizeof(struct pst_socket),
	  "pst_socket" },
	{ (size_t)offsetof(struct pst_static, pst_stream_size),
	  (size_t)INCLMEM(pst_static, pst_stream_size),
	  sizeof(struct pst_stream),
	  "pst_stream" },
	{ (size_t)offsetof(struct pst_static, pst_mpathnode_size),
	  (size_t)INCLMEM(pst_static, pst_mpathnode_size),
	  sizeof(struct pst_mpathnode),
	  "pst_mpathnode" },
	{ (size_t)offsetof(struct pst_static, pst_fileinfo2_size),
	  (size_t)INCLMEM(pst_static, pst_fileinfo2_size),
	  sizeof(struct pst_fileinfo2),
	  "pst_fileinfo2" },
};
#define	NPSTATCK	(sizeof(PstatCk) /sizeof(struct pstatck))


/*
 * Local static variables
 */

static int HvRtPsfid = 0;		/* 1 if the psfileid of "/" is known */
static struct psfileid RtPsfid;		/* "/" psfileid */


/*
 * Local function prototypes
 */

_PROTOTYPE(static void get_kernel_access,(void));
_PROTOTYPE(static void process_text,(struct pst_status *p));
_PROTOTYPE(static struct pst_fileinfo2 *read_files,(struct pst_status *p,
						    int *n));
_PROTOTYPE(static struct pst_status *read_proc,(int *n));
_PROTOTYPE(static struct pst_vm_status *read_vmreg,(struct pst_status *p, int *n));


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

void
gather_proc_info()
{
	char *cp;
	struct pst_fileinfo2 *f, fi;
	long flag;
	int i, idx, j, lk, nf, np;
	KA_T ka, na;
	struct pst_status *p;
	struct pst_filedetails pd;
	struct pst_socket *s;
	short pss, sf;
/*
 * Examine proc structures and their associated information.
 */
	for (i = 0, p = read_proc(&np); i < np; i++, p++) {
	    if (!p->pst_stat || p->pst_stat == PS_ZOMBIE)
		continue;
	    if (is_proc_excl(p->pst_pid, (int)p->pst_pgrp, (UID_ARG)p->pst_uid,
			     &pss, &sf))
		continue;
	/*
	 * Make sure the command name is NUL-terminated.
	 */
	    p->pst_ucomm[PST_UCOMMLEN - 1] = '\0';
	    if (is_cmd_excl(p->pst_ucomm, &pss, &sf))
		continue;
	    alloc_lproc(p->pst_pid, (int)p->pst_pgrp, (int)p->pst_ppid,
			(UID_ARG)p->pst_uid, p->pst_ucomm, (int)pss, (int)sf);
	    Plf = (struct lfile *)NULL;
	/*
	 * Save current working directory information.
	 */
	    if (IS_PSFILEID(&p->pst_cdir) && (p->pst_cdir.psf_fileid > 0)) {
		alloc_lfile(CWD, -1);
		if ((na = read_det(&p->pst_fid_cdir, p->pst_hi_fileid_cdir,
				   p->pst_lo_fileid_cdir, p->pst_hi_nodeid_cdir,
				   p->pst_lo_nodeid_cdir, &pd)))
		    (void) process_finfo(&pd, &p->pst_fid_cdir, &p->pst_cdir,
					 na);
		else {
		    (void) snpf(Namech, Namechl,
			"can't read%s pst_filedetails%s%s", CWD,
			errno ? ": " : "", errno ? strerror(errno) : "");
		    enter_nm(Namech);
		}
		if (Lf->sf)
		    link_lfile();
	    }
	/*
	 * Save root directory information.
	 */
	    if (IS_PSFILEID(&p->pst_rdir) && (p->pst_rdir.psf_fileid > 0)) {
	 	if (!HvRtPsfid
		||  memcmp((void *)&RtPsfid, (void *)&p->pst_rdir,
			   sizeof(RtPsfid)))
		{
		    alloc_lfile(RTD, -1);
		    if ((na = read_det(&p->pst_fid_rdir, p->pst_hi_fileid_rdir,
				       p->pst_lo_fileid_rdir,
				       p->pst_hi_nodeid_rdir,
				       p->pst_lo_nodeid_rdir, &pd)))
			(void) process_finfo(&pd, &p->pst_fid_rdir,
					     &p->pst_rdir, na);
		    else {
			(void) snpf(Namech, Namechl,
			    "can't read%s pst_filedetails%s%s", RTD,
			    errno ? ": " : "", errno ? strerror(errno) : "");
			enter_nm(Namech);
		    }
		    if (Lf->sf)
			link_lfile();
		}
	    }
	/*
	 * Print information on the text files.
	 */
	    (void) process_text(p);
	/*
	 * Loop through user's files.
	 */
	    for (j = 0, f = read_files(p, &nf); j < nf; j++, f++) {
		alloc_lfile(NULL, f->psf_fd);
	    /*
	     * Construct access code.
	     */
		if ((flag = (long)(f->psf_flag & ~PS_FEXCLOS))
		== (long)PS_FRDONLY)
		    Lf->access = 'r';
		else if (flag == (long)PS_FWRONLY)
		    Lf->access = 'w';
		else
		    Lf->access = 'u';

#if	defined(HASFSTRUCT)
	    /*
	     * Save file structure values.
	     */
		if (Fsv & FSV_CT) {
		    Lf->fct = (long)f->psf_count;
		    Lf->fsv |= FSV_CT;
		}
		if (Fsv & FSV_FA) {
		    ka = (((KA_T)(f->psf_hi_fileid & 0xffffffff) << 32)
		       |  (KA_T)(f->psf_lo_fileid & 0xffffffff));
		    if (Lf->fsa = ka)
			Lf->fsv |= FSV_FA;
		}
		if (Fsv & FSV_FG) {
		    Lf->ffg = flag;
		    Lf->fsv |= FSV_FG;
		}
		Lf->pof = (long)(f->psf_flag & PS_FEXCLOS);
#endif	/* defined(HASFSTRUCT) */

	    /*
	     * Save file offset.  _STAT64 should alwaus be defined, but just
	     * to be safe, check for it.
	     */

#if	defined(_PSTAT64)
		Lf->off = (SZOFFTYPE)f->_PSF_OFFSET64;
#else	/* !defined(_PSTAT64) */
		Lf->off = (SZOFFTYPE)f->psf_offset;
#endif	/* defined(_PSTAT64) */

	    /*
	     * Process the file by its type.
	     */
		switch (f->psf_ftype) {
		case PS_TYPE_VNODE:
		    if ((na = read_det(&f->psf_fid, f->psf_hi_fileid,
				       f->psf_lo_fileid, f->psf_hi_nodeid,
				       f->psf_lo_nodeid, &pd)))
			(void) process_finfo(&pd, &f->psf_fid, &f->psf_id, na);
		    else {
			(void) snpf(Namech, Namechl,
			    "can't read pst_filedetails%s%s",
			    errno ? ": " : "",
			    errno ? strerror(errno) : "");
			enter_nm(Namech);
		    }
		    break;
		case PS_TYPE_SOCKET:
		    switch (f->psf_subtype) {
		    case PS_SUBTYPE_SOCK:
			(void) process_socket(f, (struct pst_socket *)NULL);
			break;
		    case PS_SUBTYPE_SOCKSTR:
			if ((s = read_sock(f)))
			    (void) process_socket(f, s);
			else
			    (void) process_stream(f);
			break;
		    default:
			(void) snpf(Namech, Namechl,
			    "unknown socket sub-type: %d", (int)f->psf_subtype);
			enter_nm(Namech);
		    }
		    break;
		case PS_TYPE_STREAMS:
		    (void) process_stream(f);
		    break;
		case PS_TYPE_UNKNOWN:
		    (void) snpf(Lf->type, sizeof(Lf->type), "UNKN");
		    (void) enter_nm("no more information");
		    break;
		case PS_TYPE_UNSP:
		    (void) snpf(Lf->type, sizeof(Lf->type), "UNSP");
		    (void) enter_nm("no more information");
		    break;
		case PS_TYPE_LLA:
		    (void) snpf(Lf->type, sizeof(Lf->type), "LLA");
		    (void) enter_nm("no more information");
		    break;
		}
		if (Lf->sf)
		    link_lfile();
	    }
	/*
	 * Examine results.
	 */
	    if (examine_lproc())
		return;
	}
}


/*
 * get_kernel_access() -- access the required information in the kernel
 */

static void
get_kernel_access()
{
	int err = 0;
	int i;
	struct pst_static pst;
	_T_LONG_T *szp;
/*
 * Check the kernel version.
 */
	(void) ckkv("HP-UX", LSOF_VSTR, (char *)NULL, (char *)NULL);
/*
 * Check PSTAT support.  First make sure we can read pst_static up through
 * its pst_static_size member.  If not, quit.  If we can, read the full
 * pst_static structure.
 */
	if (pstat_getstatic(&pst, (size_t)INCLMEM(pst_static, pst_static_size),
			    1, 0) != 1)
	{
	    (void) fprintf(stderr,
		"%s: FATAL: can't determine PSTAT static size: %s\n",
		Pn, strerror(errno));
	    Exit(1);
	}
	if (pstat_getstatic(&pst, (size_t)pst.pst_static_size, 1, 0) != 1) {
	    (void) fprintf(stderr,
		"%s: FATAL: can't read %ld bytes of pst_static\n",
		Pn, (long)pst.pst_static_size);
	    Exit(1);
	}
/*
 * Check all the pst_static members defined in PstatCk[].
 */
	for (i = 0; i < NPSTATCK; i++) {
	    if (pst.pst_static_size < PstatCk[i].msz) {
		(void) fprintf(stderr,
		    "%s: FATAL: pst_static doesn't contain %s_size\n",
		    Pn, PstatCk[i].sn);
		err = 1;
		continue;
	    }
	    szp = (_T_LONG_T *)(((char *)&pst) + PstatCk[i].moff);
	    if (*szp < PstatCk[i].ssz) {
		(void) fprintf(stderr,
		    "%s: FATAL: %s_size should be: %llu; is %llu\n",
		    Pn, PstatCk[i].sn, (unsigned long long)PstatCk[i].ssz,
		    (unsigned long long)*szp);
		err = 1;
	    }
	}
/*
 * Save the clone major device number, if pst_static is big enough to hold it.
 */
	if (pst.pst_static_size >= (size_t)INCLMEM(pst_static, clonemajor)) {
	    CloneMaj = pst.clonemajor;
	    HaveCloneMaj = 1;
	}
	if (!err)
	    return;
	Exit(1);
}


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

void
initialize()
{
	struct mounts *mp;

	get_kernel_access();
/*
 * Identify NFS file systems.
 *
 * Compute the psfileid for "/".
 */
	(void) memset((void *)&RtPsfid, 0, sizeof(RtPsfid));
	for (HasNFS = 0, mp = readmnt(); mp; mp = mp->next) {
	    if (mp->MOUNTS_FSTYPE
	    &&  (strcmp(mp->MOUNTS_FSTYPE, MNTTYPE_NFS) == 0
	    ||   strcmp(mp->MOUNTS_FSTYPE, MNTTYPE_NFS3) == 0)) {
		HasNFS = 1;
		mp->is_nfs = 1;
	    } else
		mp->is_nfs = 0;
	    if (!HvRtPsfid && !strcmp(mp->dir, "/")) {
		HvRtPsfid = 1;
		RtPsfid.psf_fsid.psfs_id = mp->dev;
		RtPsfid.psf_fsid.psfs_type = mp->MOUNTS_STAT_FSTYPE;
		RtPsfid.psf_fileid = mp->inode;
	    }
	}
}


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

static void
process_text(p)
	struct pst_status *p;		/* pst_status for process */
{
	int i, j, nr, ntvu;
	int meme = 0;
	KA_T na;
	MALLOC_S nb;
	static int ntva;
	struct pst_vm_status *rp;
	struct txtvm {
	    char *fd;
	    struct pst_fid opfid;
	    struct psfileid psfid;
	    KA_T na;
	    struct pst_filedetails pd;
	};
	static struct txtvm *tv = (struct txtvm *)NULL;
/*
 * Pre-allocate sufficient tv[] space for text file.
 */
	if (!tv) {
	    ntva = TXTVMINCR;
	    nb = (MALLOC_S)(ntva * sizeof(struct txtvm));
	    if (!(tv = (struct txtvm *)malloc(nb))) {

no_txtvm_space:

		(void) fprintf(stderr,
		    "%s: no memory for text and VM info array; PID: %ld\n",
		    Pn, (long)p->pst_pid);
		Exit(1);
	    }
	}
/*
 * Enter text file in tv[], if possible.
 */
	if (IS_PSFILEID(&p->pst_text) && (p->pst_text.psf_fileid > 0)) {
	    if ((na = read_det(&p->pst_fid_text, p->pst_hi_fileid_text,
			       p->pst_lo_fileid_text, p->pst_hi_nodeid_text,
			       p->pst_lo_nodeid_text, &tv[0].pd)))
	    {
		tv[0].fd = "txt";
		tv[0].na = na;
		tv[0].opfid = p->pst_fid_text;
		tv[0].psfid = p->pst_text;
		ntvu = 1;
	    } else {
		alloc_lfile("txt", -1);
		(void) snpf(Namech, Namechl,
		    "can't read txt pst_filedetails%s%s",
		    errno ? ": " : "", errno ? strerror(errno) : "");
		enter_nm(Namech);
		if (Lf->sf)
		    link_lfile();
		ntvu = 0;
	    }
	} else
	    ntvu = 0;
/*
 * Get unique VM regions.
 */
	for (i = 0, rp = read_vmreg(p, &nr); i < nr; i++, rp++) {

	/*
	 * Skip duplicate regions.
	 */
	    for (j = 0; j < ntvu; j++) {
		if (memcmp((void *)&rp->pst_id, (void *)&tv[j].psfid,
			    sizeof(struct psfileid))
		== 0)
		    break;
	    }
	    if (j < ntvu)
		continue;
	/*
	 * Make sure there's tv[] space for this region.
	 */
	    if (ntvu >= ntva) {
		ntva += TXTVMINCR;
		nb = (MALLOC_S)(ntva * sizeof(struct txtvm));
		if (!(tv = (struct txtvm *)realloc((MALLOC_P *)tv, nb)))
		    goto no_txtvm_space;
	    }
	/*
	 * See if we can read the file details for this region.
	 */
	    if ((na = read_det(&rp->pst_fid, rp->pst_hi_fileid,
			       rp->pst_lo_fileid, rp->pst_hi_nodeid,
			       rp->pst_lo_nodeid, &tv[ntvu].pd)))
	    {
		tv[ntvu].fd = "mem";
		tv[ntvu].na = na;
		tv[ntvu].opfid = rp->pst_fid;
		tv[ntvu].psfid = rp->pst_id;
		ntvu++;
	    } else if (!meme) {
		alloc_lfile("mem", -1);
		(void) snpf(Namech, Namechl,
		    "can't read mem pst_filedetails%s%s",
		    errno ? ": " : "", errno ? strerror(errno) : "");
		enter_nm(Namech);
		if (Lf->sf)
		    link_lfile();
		meme = 1;
	    }
	}
/*
 * Process information for unique regions.
 */
	for (i = 0; i < ntvu; i++) {
	    alloc_lfile(tv[i].fd, -1);
	    (void) process_finfo(&tv[i].pd, &tv[i].opfid, &tv[i].psfid,
				 tv[i].na);
	    if (Lf->sf)
		link_lfile();
	}
}
 

/*
 * read_det() -- read the pst_filedetails structure
 */

KA_T
read_det(ki, hf, lf, hn, ln, pd)
	struct pst_fid *ki;		/* kernel file ID */
	uint32_t hf;			/* high file ID bits */
	uint32_t lf;			/* low file ID bits */
	uint32_t hn;			/* high node ID bits */
	uint32_t ln;			/* low node ID bits */
	struct pst_filedetails *pd;	/* details receiver */
{
	KA_T na;

	errno = 0;
	na = (KA_T)(((KA_T)(hn & 0xffffffff) << 32) | (KA_T)(ln & 0xffffffff));
	if (pstat_getfiledetails(pd, sizeof(struct pst_filedetails), ki) <= 0
	||  hf != pd->psfd_hi_fileid || lf != pd->psfd_lo_fileid
	||  hn != pd->psfd_hi_nodeid || ln != pd->psfd_lo_nodeid)
	    return((KA_T)0);
	return(na);
}


/*
 * read_files() -- read the file descriptor information for a process
 */

static struct pst_fileinfo2 *
read_files(p, n)
	struct pst_status *p;		/* pst_status for the process */
	int *n;				/* returned fi[] entry count */
{
	size_t ec;
	static struct pst_fileinfo2 *fi = (struct pst_fileinfo2 *)NULL;
	MALLOC_S nb;
	int nf = 0;
	static int nfa = 0;
	int rc;
	static size_t sz = sizeof(struct pst_fileinfo2);
/*
 * Read the pst_fileinfo2 information for all files of the process
 * into fi[].
 */
	do {
	    if (nf >= nfa) {

	    /*
	     * Increase the size of fi[].
	     */
		nfa += FINFOINCR;
		nb = (MALLOC_S)(nfa * sizeof(struct pst_fileinfo2));
		if (!fi)
		    fi = (struct pst_fileinfo2 *)malloc(nb);
		else
		    fi = (struct pst_fileinfo2 *)realloc((MALLOC_P *)fi, nb);
		if (!fi) {
		    (void) fprintf(stderr,
			"%s: can't allocate %d bytes for pst_filinfo\n",
			Pn, nb);
		    Exit(1);
		}
	    }
	/*
	 * Read the next block of pst_fileinfo2 structures.
	 */
	    ec = (size_t)(nfa - nf);
	    if ((rc = pstat_getfile2(fi + nf, sz, ec, nf, p->pst_pid)) > 0) {
		nf += rc;
		if (rc < (int)ec)
		    rc = 0;
	    }
	} while (rc > 0);
	*n = nf;
	return(fi);
}


/*
 * read_proc() -- read process table status information
 */

static struct pst_status *
read_proc(n)
	int *n;				/* returned ps[] entry count */
{
	size_t el;
	int i = 0;
	MALLOC_S nb;
	int np = 0;
	static int npa = 0;
	static struct pst_status *ps = (struct pst_status *)NULL;
	int rc;
	size_t sz = sizeof(struct pst_status);
/*
 * Read the pst_status information for all processes into ps[].
 */
	do {
	    if (np >= npa) {

	    /*
	     * Increase the size of ps[].
	     */
		npa += PSTATINCR;
		nb = (MALLOC_S)(npa * sizeof(struct pst_status));
		if (!ps)
		    ps = (struct pst_status *)malloc(nb);
		else
		    ps = (struct pst_status *)realloc((MALLOC_P *)ps, nb);
		if (!ps) {

ps_alloc_error:
		    (void) fprintf(stderr,
			"%s: can't allocate %d bytes for pst_status table\n",
			Pn, nb);
		    Exit(1);
		}
	    }
	/*
	 * Read the next block of pst_status structures.
	 */
	    el = (size_t)(npa - np);
	    if ((rc = pstat_getproc(ps + np, sz, el, i)) > 0) {
		np += rc;
		i = (ps + np - 1)->pst_idx + 1;
		if (rc < el)
		    rc = 0;
	    }
	} while (rc > 0);
/*
 * Reduce ps[] to a minimum, unless repeat mode is in effect.
 */
	if (!RptTm && ps && np && (np < npa)) {
	    nb = (MALLOC_S)(np * sizeof(struct pst_status));
	    if (!(ps = (struct pst_status *)realloc((MALLOC_P *)ps, nb)))
		goto ps_alloc_error;
	}
	*n = np;
	return(ps);
}


/*
 * read_vmreg() -- read info about the VM regions of a process
 */

static struct pst_vm_status *
read_vmreg(p, n)
	struct pst_status *p;		/* pst_status for process */
	int *n;				/* returned region count */
{
	size_t ec = (size_t)p->pst_pid;
	MALLOC_S nb;
	int nr, rx;
	static int nra = 0;
	struct pst_vm_status *rp;
	static struct pst_vm_status *reg = (struct pst_vm_status *)NULL;
	size_t sz = sizeof(struct pst_vm_status);
/*
 * Read all VM region information for the process.
 */
	for (nr = rx = 0;; rx++) {
	    if (nr >= nra) {

	    /*
	     * Increase the region table size.
	     */
		nra += VMREGINCR;
		nb = (MALLOC_S)(nra * sizeof(struct pst_vm_status));
		if (!reg)
		    reg = (struct pst_vm_status *)malloc(nb);
		else
		    reg = (struct pst_vm_status *)realloc((MALLOC_P *)reg, nb);
		if (!reg) {
		    (void) fprintf(stderr,
			"%s: can't allocate %d bytes for pst_vm_status\n",
			Pn, nb);
		    Exit(1);
		}
	    }
	/*
	 * Read the pst_vm_status structure for the next region.
	 */
	    rp = reg + nr;
	    if (pstat_getprocvm(rp, sz, ec, rx) != 1)
		break;
	    if (IS_PSFILEID(&rp->pst_id) && (rp->pst_id.psf_fileid > 0))
		nr++;
	}
	*n = nr;
	return(reg);
}
