/*
 * dproc.c - PTX 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.4 95/09/19 10:20:33 abe Exp $";
#endif

#include "lsof.h"

#if     defined(HASNCACHE)
#include <sys/dnlc.h>
#endif  /* defined(HASNCACHE) */


/*
 * Local static values
 */

static long Kp;				/* kernel's process table address */
static int Nf = 0;			/* nominal open files per process */
static struct var Var;			/* kernel's var structure */

#if	_PTXV>=400
int Nv = 0;				/* number of Vc[] entries allocated */
struct vnode **Vc = NULL;		/* vnode ponter cache */
int Vu;					/* number of Vc[] entries in use */
#endif	/* _PTXV>=400 */


_PROTOTYPE(static void get_kernel_access,(void));

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

#if	_PTXV>=400
_PROTOTYPE(static void process_text,(struct vnode *xv, struct as *as));
_PROTOTYPE(static void store_va,(struct vnode *va));
#endif	/* _PTXV>=400 */


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

void
gather_proc_info()
{
	int i;
	struct ofile *ofp;
	static struct ofile_tab *oft = NULL;
	static MALLOC_S oftsz;
	MALLOC_S ofxmax, ofxsz;
	struct ofile *ofx = NULL;
	struct proc *p;
	pid_t pid;
	KA_T pa;
	struct proc ps;
	short pss, sf;
	int px;
	off_t sp;
	uid_t uid;
	caddr_t va;

#if	_PTXV<400
	struct swiocread sw;
	static struct user *u;
	static char *ub;
	MALLOC_S ui;
#else	/* _PTXV>=400 */
	struct ucred pc;
	struct proc_data pd;
#endif	/* _PTXV<400 */

#if	_PTXV<400
/*
 * Allocate space for the user structure.
 */
	if (ub == (char *)NULL) {
	    if ((ub = (char *)malloc((MALLOC_S)ctob(UPAGES) + 511)) == NULL) {
		(void) fprintf(stderr,
		    "%s: can't allocate space for user structure\n", Pn);
		exit(1);
	    }
	    ub = (char *)(((int)ub + 511) & ~511);
	    u = UBTOUSER(ub);
	}
#endif	/* _PTXV<400 */

/*
 * Allocate space for the nominal number of open file table structures.
 */
	if (oft == (struct ofile_tab *)NULL) {
	    oftsz = (MALLOC_S)(sizeof(struct ofile_tab)
	          +		   ((Nf - 1) * sizeof(struct ofile)));
	    if ((oft = (struct ofile_tab *)malloc(oftsz)) == NULL) {
		(void) fprintf(stderr,
		    "%s: can't allocate space for ofile structures\n", Pn);
		exit(1);
	    }
	}

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

/*
 * Examine proc structures and their associated information.
 */
	for (p = &ps, px = 0; px < Var.v_proc; px++) {

	/*
	 * Read the proc structure.
	 */
		pa = (KA_T)(Kp + (long)(px * sizeof(struct proc)));
		if (kread(pa, (char *)&ps, sizeof(ps)))
			continue;
		if (p->p_stat == 0 || p->p_stat == SZOMB)
			continue;

#if	_PTXV<400
	/*
	 * Set UID for PTX < 4.
	 */
		uid = p->p_uid;

#else	/* _PTXV>=400 */

	/*
	 * Read credentials for PTX 4 and above to get UID.
	 *
	 * Read proc_data structure to get command name.
	 */
		if (p->p_cred == NULL
		||  kread((KA_T)p->p_cred, (char *)&pc, sizeof(pc))
		||  p->p_data == NULL
		||  kread((KA_T)p->p_data, (char *)&pd, sizeof(pd)))
			continue;
		uid = pc.cr_ruid;
#endif	/* _PTXV>=400 */

		if (is_proc_excl(p->p_pid, (int)p->p_pgrp, (UID_ARG)uid,
		    &pss, &sf))
			continue;
#if	_PTXV<400
	/*
	 * Read the user area.
	 */
		if ((p->p_flag & SLOAD) == 0) {
			if (Swap < 0)
				continue;
			sw.swr_blkno = (swblk_t)p->p_swaddr;
			sw.swr_len = ctob(UPAGES);
			sw.swr_data = (caddr_t)ub;
			if (ioctl(Swap, SWIOCREAD, &sw) != 0)
				continue;
		} else {
			if (kread((KA_T)p->p_uarea, (char *)u, U_SIZE))
				continue;
		}
		if ((KA_T)u->u_procp != pa)
			continue;
#endif	/* _PTXV<400 */

	/*
	 * Allocate a local process structure.
	 */
		if (is_cmd_excl(

#if	_PTXV<400
			u->u_comm,
#else	/* _PTXV>=400 */
			pd.pd_comm,
#endif	/* _PTXV<400 */

			&pss, &sf)
		)
			continue;
		alloc_lproc(p->p_pid, (int)p->p_pgrp, (UID_ARG)uid,

#if	_PTXV<400
			u->u_comm,
#else	/* _PTXV>=400 */
			pd.pd_comm,
#endif	/* _PTXV<400 */
			(int)pss, (int)sf);

		Plf = NULL;
	/*
	 * Save current working directory information.
	 */

#if	_PTXV<400
		if ((va = (caddr_t)u->u_cdir) != NULL)
#else	/* _PTXV>=400 */
		if ((va = (caddr_t)p->p_cdir) != NULL)
#endif	/* _PTXV<400 */
		{

			alloc_lfile(CWD, -1);
			process_node(va);
			if (Lf->sf)
				link_lfile();
		}
	/*
	 * Save root directory information.
	 */

#if	_PTXV<400
		if ((va = (caddr_t)u->u_rdir) != NULL)
#else	/* _PTXV>=400 */
		if ((va = (caddr_t)p->p_rdir) != NULL)
#endif	/* _PTXV<400 */

		{
			alloc_lfile(RTD, -1);
			process_node(va);
			if (Lf->sf)
				link_lfile();
		}

#if	_PTXV>=400
	/*
	 * Process PTX 4 and above executable and library text accesses.
	 */
		if (p->p_execvp || p->p_as)
			process_text(p->p_execvp, p->p_as);
#endif	/* _PTXV>=400 */

	/*
	 * Read the open file structures.
	 */

#if	_PTXV<400
		if (kread((KA_T)u->u_ofile_tab, (char *)oft, oftsz))
			continue;
		if (((long)u->u_ofile_tab + (long)oft->oft_lofile - (long)oft)
		!=  (long)oft->oft_ofile)
#else	/* _PTXV>=400 */
		if (kread((KA_T)p->p_ofile_tab, (char *)oft, oftsz))
			continue;
		if (((long)p->p_ofile_tab + (long)oft->oft_lofile - (long)oft)
		!=  (long)oft->oft_ofile)
#endif	/* _PTXV<400 */

		{

		/*
		 * Read the open file pointers from an extended area.
		 */
		    ofxsz = (MALLOC_S)(oft->oft_nofile * sizeof(struct ofile));
		    if (ofx == NULL) {
			if ((ofx = (struct ofile *)malloc(ofxsz)) == NULL) {

no_ofx_space:
			    (void) fprintf(stderr,
				"%s: no space for %d open files: PID %d\n",
				Pn, ofxsz, Lp->pid);
			    continue;
			}
			ofxmax = ofxsz;
		    } else if (ofxsz > ofxmax) {
			if ((ofx = (struct ofile *)realloc(ofx, ofxsz))
			== NULL)
			    goto no_ofx_space;
			ofxmax = ofxsz;
		    }
		    if (kread((KA_T)oft->oft_ofile, (char *)ofx, ofxsz))
			continue;
		    ofp = ofx;
		 } else
		    ofp = oft->oft_lofile;

	/*
	 * Save information on file descriptors.
	 */
		for (i = 0; i < oft->oft_nofile; i++, ofp++) {
			if (ofp->of_file) {
				alloc_lfile(NULL, i);
				process_file(ofp->of_file);
				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()
{

#if	_PTXV>=400
	int warn;
#endif	/* _PTXV>=400 */

/*
 * Open swap access.
 */
	if (Memory == NULL || strcmp(Memory, KMEM) == 0) {
	    if ((Swap = open(SWAP, O_RDONLY, 0)) < 0) {
		(void) fprintf(stderr, "%s: %s: %s\n", Pn, SWAP,
		    strerror(errno));
		exit(1);
	    }
	}

#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 namelist from %s\n", Pn,
		Nmlst ? Nmlst : N_UNIX);
	    exit(1);
	}
	if (Nl[X_PROC].n_value == NULL
	||  kread((KA_T)Nl[X_PROC].n_value, (char *)&Kp, sizeof(Kp))
	||  Kp == NULL) {
	    (void) fprintf(stderr, "%s: can't read proc table address\n", Pn);
	    exit(1);
	}
/*
 * Get the kernel's var structure.
 */
	if (Nl[X_VAR].n_value == NULL
	||  kread((KA_T)Nl[X_VAR].n_value, (char *)&Var, sizeof(Var))) {
	    (void) fprintf(stderr, "%s: can't read var structure from %#x\n",
		Pn, Nl[X_VAR].n_value);
	    exit(1);
	}
	if (Var.v_proc == 0) {
	    (void) fprintf(stderr, "%s: proc table length is zero\n", Pn);
	    exit(1);
	}
/*
 * Get the per-process file count.
 */
	if (Nl[X_NOFILE].n_value == NULL
	||  kread((KA_T)Nl[X_NOFILE].n_value, (char *)&Nf, sizeof(Nf))
	||  Nf < 3) {
	    (void) fprintf(stderr, "%s: bad open file count (%d) from %#x\n",
		Pn, Nf, Nl[X_NOFILE].n_value);
	    exit(1);
	}
/*
 * Read clone major device number, if possible.
 */
	if (Nl[X_CLONEMAJ].n_value
	&&  kread((KA_T)Nl[X_CLONEMAJ].n_value, (char *)&CloneMajor,
		sizeof(CloneMajor)) == 0)
	{
	    HaveCloneMajor = 1;
	    findclones();
	} else
	    HaveCloneMajor = 0;

#if	_PTXV>=400
/*
 * Check on asops_* addresses and complain about missing ones.
 */
	warn = 0;
	if (Nl[X_ASOPSLARGE].n_value == NULL) {
	    (void) fprintf(stderr,
		"%s: WARNING: %s kernel address is undefined.\n",
		Pn, Nl[X_ASOPSLARGE].n_name);
	    warn = 1;
	}
	if (Nl[X_ASOPSSMALL].n_value == NULL) {
	    (void) fprintf(stderr,
		"%s: WARNING: %s kernel address is undefined.\n",
		Pn, Nl[X_ASOPSSMALL].n_name);
	}
	if (warn) {
	    (void) fprintf(stderr,
		"%s: WARNING: may not be able to identify \"txt\" files.\n",
		    Pn);
	}
#endif	/* _PTXV>=400 */

}


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

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


/*
 * 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, (long)addr, SEEK_SET) == (off_t)-1L)
		return(-1);
	br = read(Kmem, buf, len);
	return((br == len) ? 0 : 1);
}


#if	_PTXV>=400
/*
 * process_text() - process text references for PTX 4 and above
 */

static void
process_text(xv, as)
	struct vnode *xv;		/* pointer to executable's vnode */
	struct as *as;			/* address space structure pointer */
{
	struct as a;
	static struct vnode **da = NULL;
	int i, j;
	MALLOC_S len;
	static int nd = 0;
/*
 * Save the address of the executable vnode.
 */
	Vu = 0;
	store_va(xv);
/*
 * If there is an address space map, store the addresses of the vnodes
 * in its mapped vnode lists.  Discard duplicates.
 */
	if (as != (struct as *)NULL
	&&  kread((KA_T)as, (char *)&a, sizeof(a)) == 0) {

	/*
	 * Save the vnodes of the short, static list.
	 */
	    if (Nl[X_ASOPSSMALL].n_value
	    &&  a.as_ops == (struct asops *)Nl[X_ASOPSSMALL].n_value) {
		for (i = j = 0; i < AS_SVSM_MMAP_MAX; i++) {
		    if (a.as_mvp.mvp_small[i] != (struct vnode *)NULL) {
			store_va(a.as_mvp.mvp_small[i]);
			j++;
		    }
		} 
	    }
	/*
	 * If there's a dynamic list, allocate space for it, read it, and save
	 * the addresses of the vnodes it contains.
	 */
	    if (Nl[X_ASOPSLARGE].n_value
	    &&  a.as_ops == (struct asops *)Nl[X_ASOPSLARGE].n_value) {
		if ((i = a.as_mvp.mvp_dynamic_size) > 0 && a.as_mvp.mvp_large) {
		    len = (MALLOC_S)(i * sizeof(struct vnode *));
		    if (da == NULL) {
			if ((da = (struct vnode **)malloc(len)) == NULL) {

no_vm_vp_space:

				(void) fprintf(stderr,
				    "%s: no space for dynamic vnodes: PID %d\n",
				    Pn, Lp->pid);
				    exit(1);
			}
			nd = i;
		    } else if (i >= nd) {
			if ((da = (struct vnode **)realloc(da, len)) == NULL)
			    goto no_vm_vp_space;
			nd = i;
		    }
		    if (kread((KA_T)a.as_mvp.mvp_large, (char *)da,
			(READLEN_T)len)
		    == 0) {
			for (j = 0; j < i; j++) {
			    if (da[j] != (struct vnode *)NULL)
				store_va(da[j]);
			}
		    }
		}
	   }
	}
/*
 * Process the unique vnode addresses accumulated from the executable,
 * short static vnode list, and dynamic vnode list.
 */
	for (i = 0; i < Vu; i++) {
	    alloc_lfile("txt", -1);
	    process_node((caddr_t)Vc[i]);
	    if (Lf->sf)
		link_lfile();
	}
}


/*
 * store_va() - store unique vnode address
 */

void
store_va(va)
	struct vnode *va;		/* vnode address */
{
	int i;
	MALLOC_S len;

	if (va == (struct vnode *)NULL)
		return;
	for (i = 0; i < Vu; i++) {
		if (va == Vc[i])
			return;
	}
	if (Vc == NULL) {
		Nv = AS_SVSM_MMAP_MAX;
		len = (MALLOC_S)(Nv * sizeof(struct vnode *));
		if ((Vc = (struct vnode **)malloc(len)) == NULL) {

no_Vc_space:

			(void) fprintf(stderr,
				"%s: no vnode cache space: PID %d\n",
				Pn, Lp->pid);
			exit(1);
		}
	} else if (Vu >= Nv) {
		Nv += MVP_DYN_MIN;
		len = (MALLOC_S)(Nv * sizeof(struct vnode *));
		if ((Vc = (struct vnode **)realloc(Vc, len)) == NULL)
			goto no_Vc_space;
	}
	Vc[Vu++] = va;
}
#endif	/* _PTXV>=400 */


/*
 * The ncache_addr(), ncache_load(), and ncache_lookup() functions are
 * obtained from ../common/rnch.frag.
 */
