/*
 * dproc.c - SGI IRIX 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.21 96/03/09 14:51:37 abe Exp $";
#endif

#include "lsof.h"

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


_PROTOTYPE(static void get_kernel_access,(void));

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

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

#if	_IRIXV==50300 || !defined(IRIX32BITS) || _IRIXV>=60200
/*
 * Handle lseek complications:
 *
 * o  For Irix 5.3 and its 64 bit descendants with _KERNEL defined, declare
 *    an alternate lseek prototype that avoids 32 vs. 64 bit length confusion
 *    about off_t.
 *
 * o  For IRIX 6. and above, use lseek64().
 */

# if	_IRIXV>=60200
#define	lseek	lseek64
# else	/* _IRIXV<60200 */
_PROTOTYPE(int _lseek,(int, long, int));
#define lseek _lseek
# endif	/* _IRIXV>=60200 */
#endif	/* _IRIXV==50300 && !defined(IRIX32BITS) */



/*
 * Local static values
 */

static KA_T Kp;				/* kernel process table address */
static caddr_t *Ndc = NULL;		/* node cache */
static MALLOC_S Nn = 0;			/* number of Ndc[] entries allocated */
static struct var Var;			/* kernel variables */


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

void
gather_proc_info()
{
	int i, j;
	struct proc *p;
	KA_T pa;
	struct proc ps;
	short pss, sf;
	int px;
	static struct user *u;
	uid_t uid;

#if	_IRIXV<50101
	static struct file **fp;
	int nf, ul;
	static char *uaf = NULL;
	static MALLOC_S uafl;
#else	/* _IRIXV>=50101 */
# if	_IRIXV<60200
	int dl;
	struct sat_wd sw;
	KA_T ta;
# endif	/* _IRIXV<60200 */
	struct file *f;
	struct cred pcred;
	struct user us;
# if	_IRIXV>=50300
	struct ufchunk uc;
# endif	/* _IRIXV>=50300 */
#endif	/* _IRIXV<50101 */

#if	_IRIXV<50101
/*
 * Allocate space for user area and file pointers for IRIX version
 * below 5.1.1.
 */
	if (uaf == (char *)NULL) {
		uafl = (MALLOC_S) (sizeof(struct user)
		     +  (Var.v_nofiles * sizeof(struct file *)));
		if ((uaf = (char *)malloc(uafl)) == NULL) {
			(void) fprintf(stderr,
				"%s: no space for uarea and file pointers\n",
				Pn);
			exit(1);
		}
		fp = (struct file **)(uaf + sizeof(struct user));
		u = (struct user *)uaf;
	}
#else	/* _IRIXV>=50101 */

/*
 * Set user structure pointer for IRIX 5.1.1 and above.
 */
	u = &us;

# if	 _IRIXV<60200
/*
 * Allocate space for current working and root directory buffers for
 * IRIX versions 5.1.1 through 6.1.
 */
	if (Cwd == (char *)NULL) {
		if ((Cwd = (char *)malloc(MAXPATHLEN)) == NULL) {
			(void) fprintf(stderr,
				"%s: no space for cwd buffer\n", Pn);
			exit(1);
		}
	}
	if (Rtd == (char *)NULL) {
		if ((Rtd = (char *)malloc(MAXPATHLEN)) == NULL) {
			(void) fprintf(stderr,
				"%s: no space for root directory buffer\n",
				Pn);
			exit(1);
		}
	}
	*Cwd = *Rtd = '\0';
# endif	/* _IRIXV<60200 */
#endif	/* _IRIXV<50101 */

#if     defined(HASNCACHE)
/*
 * Read the 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++) {
		pa = Kp + (KA_T)(px * sizeof(struct proc));
		if (kread(pa, (char *)&ps, sizeof(ps)))
			continue;
		if (p->p_stat == 0 || p->p_stat == SZOMB)
			continue;
	/*
	 * Get User ID.
	 */

#if	_IRIXV>=50101
		if (p->p_cred == NULL
		||  kread((KA_T)p->p_cred, (char *)&pcred, sizeof(pcred)))
			continue;
		uid = (uid_t)pcred.cr_ruid;
#else	/* _IRIXV<50101 */
		uid = (uid_t)p->p_uid;
#endif	/* _IRIXV>=50101 */

		if (is_proc_excl(p->p_pid, p->p_pgrp, (UID_ARG)uid, &pss, &sf))
			continue;
	/*
	 * Get the user area associated with the process.
	 */

#if	_IRIXV>=50101
# if	_IRIXV>=60200
		if (!p->p_user
		||  kread((KA_T)p->p_user, (char *)&us, sizeof(us)))
# else	/* _IRIXV<60200 */
		if (syssgi(SGI_RDUBLK, p->p_pid, (char *)&us, sizeof(us))
		!= sizeof(us))
# endif	/* _IRIXV>=60200 */

			continue;
#else	/* _IRIXV<50101 */
		if ((ul = syssgi(SGI_RDUBLK, p->p_pid, uaf, uafl))
		< (int)sizeof(struct user))
			continue;
		if (ul <= (int)sizeof(struct user)
		||  ((long)u->u_ofile - UADDR) != sizeof(struct user))
			nf = 0;
		else
			nf = (ul - sizeof(struct user)) / sizeof(struct file *);
#endif	/* _IRIXV>=50101 */

#if	_IRIXV>=50101 && _IRIXV<60200
	/*
	 * Read the IRIX 5.1.1 through 6.1 current working directory and
	 * root directory paths.
	 */
		*Cwd = *Rtd = '\0';
		if ((ta = (KA_T)u->u_satwd)
		&& kread((KA_T)ta, (char *)&sw, sizeof(sw)) == 0) {
			ta += offsetof(struct sat_wd, data);
			if ((dl = sw.cwd_len) > 0) {
				if (dl >= MAXPATHLEN)
					dl = MAXPATHLEN - 1;
				if (dl < 2 || kread(ta, Cwd, dl))
					*Cwd = '\0';
				else
					Cwd[dl] = '\0';
			}
			if ((dl = sw.root_len) > 0) {
				if (dl >= MAXPATHLEN)
					dl = MAXPATHLEN - 1;
				ta += sw.cwd_len;
				if (dl < 2 || kread(ta, Rtd, dl))
					*Rtd = '\0';
				else
					Rtd[dl] = '\0';
			}
		}
#endif	/* _IRIXV>=50101 && _IRIXV<60200 */

	/*
	 * Allocate a local process structure.
	 */
		if (is_cmd_excl(u->u_comm, &pss, &sf))
			continue;
		alloc_lproc(p->p_pid, p->p_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	_IRIXV<50300
		if (p->p_region)
			process_text(p->p_region);
#else	/* _IRIXV>=50300 */
		if (PREG_FIRST(p->p_region))
			process_text(PREG_FIRST(p->p_region));
#endif	/* _IRIXV<50300 */

	/*
	 * Save information on file descriptors.
	 */

#if	_IRIXV<50101
	/*
	 * The file pointers for IRIX versions below 5.1.1 are stored in a
	 * dynamically allocated array that follows the user area segment.
	 */
		for (i = 0; i < nf; i++) {
		     if (fp[i]) {
			alloc_lfile(NULL, i);
			process_file(fp[i]);
			if (Lf->sf)
			    link_lfile();
		     }
		}
#endif	/* _IRIXV<50101 */

#if	_IRIXV>=50101 && _IRIXV<50300
	/*
	 * Under IRIX 5.1.1 and below IRIX 5.3, the file pointers are stored
	 * in dynamically-linked ufchunk structures, each containing NFPCHUNK
	 * file pointers.  The first ufchunk structure is in the user area.
	 */
		for (i = 0, j = 0; i < u->u_nofiles; i++) {
		    if (++j > NFPCHUNK) {
			if ( ! u->u_flist.uf_next)
			    break;
			if (kread((KA_T)u->u_flist.uf_next, (char *)&u->u_flist,
				  sizeof(struct ufchunk)))
			    break;
			j = 1;
		    }
		    if ((f = u->u_flist.uf_ofile[j-1])) {
			alloc_lfile(NULL, i);
			process_file(f);
			if (Lf->sf)
			    link_lfile();
		    }
		}
#endif	/* _IRIXV>=50101 && _IRIXV<50300 */

#if	_IRIXV>=50300
	/*
	 * Under IRIX 5.3 and above, the file pointers are stored in
	 * dynamically-linked ufchunk structures, each containing NFPCHUNK
	 * file pointers.  The first urchunk structure is addressed via the
	 * u_flist pointer in the user structure.
	 */
		for (uc.uf_next = u->u_flist, i = 0, j = NFPCHUNK;
		     i < u->u_nofiles;
		     i++)
		{
		    if (++j > NFPCHUNK) {
			if( ! uc.uf_next)
			    break;
			if (kread((KA_T)uc.uf_next, (char *)&uc, sizeof(uc)))
			    break;
			j = 1;
		    }
		    f = uc.uf_ofile[j-1];
		    if (f && f != FDALLOCED) {
			alloc_lfile(NULL, i);
			process_file(f);
			if (Lf->sf)
			    link_lfile();
		    }
		}
#endif	/* _IRIXV>=50300 */

	/*
	 * Examine results.
	 */
		if (examine_lproc())
			return;
	}
}


/*
 * get_kernel_access() - get access to kernel memory
 */

static void
get_kernel_access()
{
	KA_T kvar;
	short no_proc = 0;
	short no_var = 0;
	int psz;

#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) */

/*
 * Read kernel addresses from the name list file.
 */
	if (NLIST_TYPE(Nmlst ? Nmlst : N_UNIX, Nl) == -1) {
	    (void) fprintf(stderr,
		"%s: can't read kernel name list from %s\n",
		Pn, Nmlst ? Nmlst : N_UNIX);
	    exit(1);
	}
/*
 * Set process table and var structure addresses.
 */
	if (Nmlst) {

	/*
	 * If the kernel name list file is specified, get the addresses
	 * from it.
	 */
	    if (Nl[X_PROC].n_value == NULL

#if     _IRIXV>=50101
	    ||  kread((KA_T)Nl[X_PROC].n_value, (char *)&Kp, sizeof(Kp))
#endif	/* _IRIXV>=50101 */

	    )
		no_proc++;

#if     _IRIXV<50101
	    Kp = (KA_T)(Nl[X_PROC].n_value & 0x7fffffff);
#endif	/* _IRIXV<50101 */

	    if ((kvar = (KA_T)Nl[X_VAR].n_value) == (KA_T)NULL)
		no_var++;

	} else {

	/*
	 * If no kernel name list file is specified, get the addresses
	 * via sysmp() calls.
	 *
	 * Verify that the local notion of the size of a proc structure
	 * agrees with the kernel's.
	 */
	    if ((Kp = (KA_T)sysmp(MP_KERNADDR, MPKA_PROC)) == (KA_T)-1)
		no_proc++;

#if	_IRIXV<50101
	    Kp &= 0x7fffffff;
#endif	/* _IRIXV<50101 */

	    if ((kvar = (KA_T)sysmp(MP_KERNADDR, MPKA_VAR)) == (KA_T)-1)
		no_var++;
	    if ((psz = sysmp(MP_KERNADDR, MPKA_PROCSIZE)) != PROCSIZE) {
		(void) fprintf(stderr,
		    "%s: WARNING: kernel proc struct size differs;", Pn);
		(void) fprintf(stderr,
		    " output may not be correct.\n");
		(void) fprintf(stderr,
		    "      kernel size = %d, sizeof(struct proc) = %d\n",
		    psz, PROCSIZE);
	    }
	}
/*
 * Notify about proc and var address errors, then exit.
 */
	if (no_proc)
	    (void) fprintf(stderr, "%s: can't get proc table address\n", Pn);
	if (no_var)
	    (void) fprintf(stderr, "%s: can't get var struct address\n", Pn);
	if (no_proc || no_var)
	    exit(1);
/*
 * Read var structure.
 */
	if (kread(kvar, (char *)&Var, sizeof(Var))) {
	    (void) fprintf(stderr, "%s: can't read var struct at %#x\n",
		Pn, kvar);
	    exit(1);
	}
}


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

void
initialize()
{
	get_kernel_access();
	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	defined(IRIX32BITS) && _IRIXV<60200
	if (lseek(Kmem, (KA_T)(addr & 0x7fffffff), L_SET) == (KA_T)-1L)
#else	/* !defined(IRIX32BITS) || _IRIXV>=60200 */
	if (lseek(Kmem, (KA_T)addr, L_SET) == (KA_T)-1L)
#endif	/* defined(IRIX32BITS) && _IRIXV<60200 */

		return(-1);
	br = read(Kmem, buf, len);
	return((br == len) ? 0 : 1);
}



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

static void
process_text(prp)
	preg_t *prp;		/* process region pointer */
{
	int i, j, lm;
	struct pregion p;
	struct region r;
	caddr_t na;

/*
 * Follow the virtual address space pregion structure chain.
 */
#if	_IRIXV<50300
	for (i = lm = 0; prp; prp = p.p_forw, lm++)
#else	/* _IRIXV>=50300 */
	for (i = lm = 0;
	     prp; 
	     prp = (preg_t *)p.p_avlnode.avl_nextino, lm++)
#endif	/* _IRIXV<50300 */

	{

	/*
	 * Avoid infinite loop.
	 */
		if (lm > 1000) {
			if (!Fwarn)
				(void) fprintf(stderr,
					"%s: too many regions, PID %d\n",
					Pn, Lp->pid);
			return;
		}
	/*
	 * Read the pregion; then read the region.
	 */
		if (kread((KA_T)prp, (char *)&p, sizeof(p))
		||  kread((KA_T)p.p_reg, (char *)&r, sizeof(r)))
			return;
	/*
	 * Skip file entries with no file pointers and duplicate entries.
	 */

#if	_IRIXV<50101
		if ((na = (caddr_t)r.r_iptr) == NULL)
#else	/* _IRIXV>=50101 */
		if ((na = (caddr_t)r.r_vnode) == NULL)
#endif	/* _IRIXV<50101 */

			continue;
		for (j = 0; j < i; j++) {
			if (Ndc[j] == na)
				break;
			}
		if (j < i)
			continue;
	/*
	 * Cache the vnode address for duplicate checking.
	 */
		if (Ndc == NULL) {
			if ((Ndc = (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 (i >= Nn) {
			Nn += 10;
			if ((Ndc = (caddr_t *)realloc((MALLOC_P *)Ndc,
				   (MALLOC_S)(Nn * sizeof(KA_T))))
			== NULL) {
				(void) fprintf(stderr,
					"%s: no more txt ptr space, PID %d\n",
					Pn, Lp->pid);
				exit(1);
			}
		}
		Ndc[i++] = na;
	/*
	 * Save text node information.
	 */

#if	_IRIXV<50200
		switch (p.p_type) {
			case PT_MAPFILE:
				alloc_lfile(" mem", -1);
				break;
			case PT_LIBTXT:
			case PT_LIBDAT:
				alloc_lfile(" ltx", -1);
				break;
			case PT_DATA:
			case PT_TEXT:
			default:
				alloc_lfile(" txt", -1);
		}

#else	/* _IRIXV>=50200 */
		if (r.r_flags & RG_TEXT)
			alloc_lfile(" txt", -1);
		else
			alloc_lfile(" mem", -1);
#endif	/* _IRIXV<50200 */

		process_node(na);
		if (Lf->sf)
			link_lfile();
	}
}


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

static void
readfsinfo()
{

#if	_IRIXV>=50101
	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);
	}

#else	/* _IRIXV<50101 */

	char *cp;
	int i;
	MALLOC_S len;
/*
 * Access the file system information table in the kernel.
 */
	if (Nl[X_NFSTYP].n_value == NULL
	||  kread((KA_T)Nl[X_NFSTYP].n_value, (char *)&Nfstyp, sizeof(Nfstyp))
	||  Nfstyp < 1) {
		(void) fprintf(stderr,
			"%s: nfstyp value (%d) error, kernel = %#x\n",
			Pn, Nfstyp, Nl[X_NFSTYP].n_value);
		exit(1);
	}
	len = (MALLOC_S)(Nfstyp * sizeof(struct fsinfo));
	if (Nl[X_FSINFO].n_value == NULL
	||  (Fsinfo = (struct fsinfo *)malloc(len)) == NULL
	||  kread((KA_T)Nl[X_FSINFO].n_value, (char *)Fsinfo, len)) {
		(void) fprintf(stderr,
			"%s: fstyp read error, length = %d, kernel = %#x\n",
			Pn, len, Nl[X_FSINFO].n_value);
		exit(1);
	}
/*
 * Get the file system names.
 */
	for (i = 0; i < Nfstyp; i++) {
	    if (Fsinfo[i].fs_name) {
		if ((cp = malloc(FSNAMEL)) == NULL) {
		    (void) fprintf(stderr,
			"%s: no space for file system name[%d]\n", Pn, i);
		    exit(1);
		}
		if (kread((KA_T)Fsinfo[i].fs_name, cp, FSNAMEL)) {
		    (void) fprintf(stderr,
			"%s: can't read file system name[i] from %#x\n",
			Pn, i, Fsinfo[i].fs_name);
		    exit(1);
		}
		cp[FSNAMEL - 1] = '\0';
		Fsinfo[i].fs_name = cp;
	    }
	}
#endif	/* _IRIXV>=50101 */

}


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

#if     defined(HASNCACHE)
#define NCACHE_NODEID   vp_cap		/* node ID in NCACHE */
#define	NCACHE_NXT	lru_next	/* link to next cache entry */
#define NCACHE_PARID    dp_cap		/* parent node ID in NCACHE */
#endif  /* defined(HASNCACHE) */
