/*
 * dproc.c - EP/IX 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.14 95/10/15 12:45:58 abe Exp $";
#endif

#include "lsof.h"

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


/*
 * Local static values
 */

static KA_T Kp;				/* kernel's process table address */
static int Knp;				/* number of processes according
					 * to kernel */
static int Np;				/* number of processes read from
					 * kernel */

#if	_EPIXV>=20101
static struct proc *P;			/* local proc structure table */
static KA_T *Pa;			/* kernel address for each P[] entry */
#endif


_PROTOTYPE(static void get_kernel_access,(void));
_PROTOTYPE(static int mread,(KA_T addr, char *buf, READLEN_T len));


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

#if	_EPIXV>=20101
_PROTOTYPE(static void read_proc,(void));
#endif


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

void
gather_proc_info()
{
	int err, i, pgrp, pid, px;
	struct proc *p;
	KA_T pa;
	static int p2bsh, p2psh, ps;

#if	_EPIXV<20101
	struct proc pbuf;
#else
	struct cred pcred;
	struct pid pids;
#endif

	short pss, sf;
	static struct user *u = NULL;
	static char *ua;
	char *ub;
	static char *ubl;
	struct file **uf;
	uid_t uid;
/*
 * Allocate user area buffer.
 *
 * Define user area paging parameters.
 */
	if (!u) {
		MALLOC_S ual;

		ual = (MALLOC_S) (sizeof(struct user) 
		    + ((NOFILES_UAREA - 1) * sizeof(struct file *)));
		if ((ua = (char *)malloc(ual)) == NULL) {
		    (void) fprintf(stderr,
			"%s: no space for %d byte user area\n", Pn, ual);
		    exit(1);
		}
		u = (struct user *)ua;
		ubl = ua + ual;
		if ((ps = getpagesize()) == 4096)
		    p2bsh = p2psh = 12;
		else if (ps == 16384) {
		    p2bsh = 14;
		    p2psh = 10;
		} else {
		    (void) fprintf(stderr,
			"%s: unknown page size (%d)\n", Pn, ps);
		    exit(1);
		}
	}
/*
 * Read EP/IX 2.1.1 and above process table.
 * Initialize UID cache.
 * Read kernel name cache.
 */

#if	_EPIXV>=20101
	(void) read_proc();
#endif	/* _EPIXV>=20101 */

	(void) iuidcache(Np);

#if	defined(HASNCACHE)
        ncache_load();
#endif	/* defined(HASNCACHE) */

/*
 * Examine proc structures and their associated information.
 */
#if	_EPIXV<20101
	p = &pbuf;
#endif	/* _EPIXV<20101 */

	for (px = 0; px < Np; px++) {

#if	_EPIXV<20101
	/*
	 * Read the proc structure.  Reject uninteresting ones.
	 *
	 * Set the process group ID, Process ID, and User ID.
	 */
		pa = (KA_T)(Kp + (long)(px * sizeof(struct proc)));
		if (kread(pa, (char *)&pbuf, sizeof(pbuf)))
			continue;
		if (p->p_stat == 0 || p->p_stat == SZOMB)
			continue;
		pid = p->p_pid;
		pgrp = p->p_pgrp;
		uid = (uid_t)p->p_uid;
#else	/* _EPIXV>=20101 */
	/*
	 * Set the proc structure addresses.
	 */
		p = &P[px];
		pa = Pa[px];
	/*
	 * Get the process group ID, Process ID, and User ID.
 	 */
		if (Fpgrp) {
		    if (p->p_pgidp == NULL
		    ||  kread((KA_T)p->p_pgidp, (char *)&pids, sizeof(pids)))
			continue;
		    pgrp = pids.pid_id;
		} else
		    pgrp = 0;
		if (p->p_pidp == NULL
		||  kread((KA_T)p->p_pidp, (char *)&pids, sizeof(pids)))
		    continue;
		pid = pids.pid_id;
		if (p->p_cred == NULL
		||  kread((KA_T)p->p_cred, (char *)&pcred, sizeof(pcred)))
		    continue;
		uid = (uid_t)pcred.cr_ruid;
#endif	/* _EPIXV<20101 */

	/*
	 * See if process is excluded.
	 */
		if (is_proc_excl(pid, pgrp, (UID_ARG)uid, &pss, &sf))
			continue;
	/*
	 * Read the user area.
	 */
		for (err = i = 0, ub = ua; ub < ubl; i++, ub += ps) {
			if (mread((KA_T)((p->p_ubptbl[i].pgi.pg_pde >> p2psh)
			   << p2bsh), ub,
			   (READLEN_T)(((ubl - ub) > ps) ? ps : (ubl - ub))))
			{
				err++;
				break;
			}
		}
		if (err || (KA_T)u->u_procp != pa)
			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 (u->u_exdata.vp != NULL) {
			alloc_lfile(" txt", -1);
			process_node((caddr_t)u->u_exdata.vp);
			if (Lf->sf)
				link_lfile();
		}
	/*
	 * Loop through user's files.
	 */
		uf = (struct file **)&u->u_ofile;
		for (i = 0; i < u->u_nofiles; i++) {
			if (uf[i+1]) {
                                alloc_lfile(NULL, i);
				process_file(uf[i+1]);
				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()
{

/*
 * Open access to /dev/mem.
 */
	if ((Mem = open("/dev/mem", O_RDONLY, 0)) < 0) {
		(void) fprintf(stderr, "%s: can't open /dev/mem: %s\n",
			Pn, 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 accesses.
 */
	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	_EPIXV>=20101
	Vnfops = (struct fileops *) Nl[X_VN_FOPS].n_value;
	if (Nl[X_PROC].n_value == NULL
	||  kread((KA_T)Nl[X_PROC].n_value, (char *)&Kp, sizeof(Kp))
	||  Kp == NULL)
#else	/* _EPIXV<20101 */
	Kp = (KA_T)Nl[X_PROC].n_value;
	if (Kp == NULL)
#endif	/* _EPIXV>=20101 */

	{
		(void) fprintf(stderr, "%s: can't read proc table info\n", Pn);
		exit(1);
	}
	if (Nl[X_NPROC].n_value == NULL
	||  kread((KA_T)Nl[X_NPROC].n_value, (char *)&Knp, sizeof(Knp))
	||  Knp < 1)
	{

#if	_EPIXV>=20101
		Knp = PROCDFLT;
#else	/* _EPIXV<20101 */
		(void) fprintf(stderr, "%s: can't read nproc from %#x\n",
			Pn, Nl[X_NPROC].n_value);
		exit(1);
#endif	/* _EPIXV>=20101 */

	}

#if	_EPIXV<20101
	Np = Knp;
#endif	/* _EPIXV>=20101 */

}


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

void
initialize()
{
	get_kernel_access();
}


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


/*
 * mread() -- read from /dev/mem
 */

static int
mread(addr, buf, len)
	KA_T addr;			/* /dev/mem address */
	char *buf;			/* buffer to receive data */
	READLEN_T len;			/* length to read */
{
	int br;

	if (lseek(Mem, addr, L_SET) == (off_t)-1L)
		return(1);
	br = read(Mem, buf, len);
	return((br == len) ? 0 : 1);
}


#if	_EPIXV>=20101
/*
 * read_proc() - read proc structures
 */

static void
read_proc()
{
	MALLOC_S len1, len2;
	static int sz = 0;
	int try;
	KA_T kp;
	struct proc *p;

/*
 * Try PROCTRYLM times to read a valid proc table.
 */
	for (try = 0; try < PROCTRYLM; try++) {

	/*
	 * Re-read the kernel's proc structure head pointer.
	 */
	    if (kread((KA_T)Nl[X_PROC].n_value, (char *)&Kp, sizeof(Kp))
	    ||  Kp == NULL) {
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: can't read proc table pointer, %s: %#x\n",
			Pn, Nl[X_PROC].n_name, Nl[X_PROC].n_value);
		continue;
	    }
	/*
	 * Pre-allocate space for sz proc structures.
	 */
	    if (sz == 0) {
		sz = Knp + PROCDFLT/4;
		len1 = (MALLOC_S)(sz * sizeof(struct proc));
		len2 = (MALLOC_S)(sz * sizeof(KA_T));
		if ((P = (struct proc *)malloc(len1)) == (struct proc *)NULL) {
		    (void) fprintf(stderr, "%s: no proc table space\n", Pn);
		    exit(1);
		}
		if ((Pa = (KA_T *)malloc(len2)) == (KA_T *)NULL) {
		    (void) fprintf(stderr, "%s: no proc pointer space\n", Pn);
		    exit(1);
		}
	    }
	/*
	 * Accumulate proc structures.
	 */
	    for (kp = Kp, Np = 0; kp; ) {
		if (kread(kp, (char *)&P[Np], sizeof(struct proc))) {
		    Np = 0;
		    break;
		}
		Pa[Np] = kp;
		kp = (KA_T)P[Np].p_next;
		if (P[Np].p_stat == 0 || P[Np].p_stat == SZOMB)
		    continue;
		Np++;
		if (Np >= sz) {

		/*
		 * Expand the local proc table.
		 */
		    sz += PROCDFLT/2;
		    len1 = (MALLOC_S)(sz * sizeof(struct proc));
		    len2 = (MALLOC_S)(sz * sizeof(KA_T));
		    if ((P = (struct proc *)realloc((MALLOC_P *)P, len1))
		    == NULL) {
			(void) fprintf(stderr,
			    "%s: no more (%d) proc space\n", Pn, sz);
			exit(1);
		    }
		    if ((Pa = (KA_T *)realloc((MALLOC_P *)Pa, len2)) == NULL) {
			(void) fprintf(stderr,
			    "%s: no more (%d) proc ptr space\n", Pn, sz);
			exit(1);
		    }
		}
	    }
	/*
	 * If not enough processes were saved in the local table, try again.
	 */
	    if (Np >= PROCMIN)
		break;
	}
/*
 * Quit if no proc structures were stored in the local table.
 */
	if (try >= PROCTRYLM) {
	    (void) fprintf(stderr, "%s: can't read proc table\n", Pn);
	    exit(1);
	}
	if (Np < sz && !RptTm) {

	/*
	 * Reduce the local proc structure table size to a minimum.
	 */
	    len1 = (MALLOC_S)(Np * sizeof(struct proc));
	    len2 = (MALLOC_S)(Np * sizeof(KA_T));
	    if ((P = (struct proc *)realloc(P, len1)) == NULL) {
		(void) fprintf(stderr, "%s: can't reduce proc table to %d\n",
		    Pn, Np);
		exit(1);
	     }
	     if ((Pa = (KA_T *)realloc((MALLOC_P *)Pa, len2)) == NULL) {
		(void) fprintf(stderr, "%s: can't reduce proc ptrs to %d\n",
		     Pn, Np);
		exit(1);
	     }
	}
}
#endif	/* _EPIXV >= 20101 */


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

#define	v_flag	va_flag			/* for ncache_isroot() */
