/*
 * dproc.c - OpenUNIX process access functions for lsof
 */


/*
 * Copyright 2001 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 2001 Purdue Research Foundation.\nAll rights reserved.\n";
static char *rcsid = "$Id: dproc.c,v 1.1 2001/10/04 12:40:44 abe Exp $";
#endif

#include "lsof.h"


/*
 * Local static values
 */

static int Np;				/* occupied P[] count */
static int Npa = 0;			/* allocated P[] count */
static struct proc *P = (struct proc *)NULL;
					/* proc table */
static KA_T Pract;			/* kernel's practive address */
static KA_T Sgdnops;			/* kernel's segdev_ops address */
static KA_T Sgvnops;			/* kernel's segvn_ops address */
static struct var Var;			/* kernel variables */


/*
 * Local definitions
 */

#define	PROCINCR	32		/* increment for increasing P[] */


/*
 * Local function prototypes.
 */

_PROTOTYPE(static int get_clonemaj,(void));
_PROTOTYPE(static void read_proc,(void));
_PROTOTYPE(static void get_kernel_access,(void));
_PROTOTYPE(static void readfsinfo,(void));
_PROTOTYPE(static void process_text,(KA_T pa));


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

void
gather_proc_info()
{
	struct cred cr;
	struct execinfo ex;
	KA_T fa;
	static struct fd_entry *fe;
	struct fd_entry *f;
	int i, nf;
	MALLOC_S len;
	static int nfea = 0;
	struct proc *p;
	int pgid, pid, px;
	struct pid pids;
	short pss, sf;
	struct pollx plx;
	uid_t uid;
/*
 * Examine proc structures and their associated information.
 */
	(void) read_proc();
	for (p = P, px = 0; px < Np; p++, px++) {
	    if ((p->p_flag & P_DESTROY) || (p->p_flag & P_GONE)
	    ||  !p->p_pidp || !p->p_cred || !p->p_execinfo)
		continue;
	/*
	 * Get Process ID, Process group ID, and User ID.
	 */
	    if (!p->p_pidp
	    ||  kread((KA_T)p->p_pidp, (char *)&pids, sizeof(pids)))
		continue;
	    pid = (int)pids.pid_id;
	    pgid = (int)p->p_pgid;
	    if (!p->p_cred
	    ||  kread((KA_T)p->p_cred, (char *)&cr, sizeof(cr)))
		continue;
	    uid = cr.cr_uid;
	    if (is_proc_excl(pid, pgid, (UID_ARG)uid, &pss, &sf))
		continue;
	/*
	 * Get the execution information -- for the command name.
	 */
	    if (!p->p_execinfo
	    ||  kread((KA_T)p->p_execinfo, (char *)&ex, sizeof(ex)))
		continue;
	/*
	 * Allocate a local process structure.
	 */
	    if (is_cmd_excl(ex.ei_comm, &pss, &sf))
		continue;
	    alloc_lproc(pid, pgid, (int)p->p_ppid, (UID_ARG)uid, ex.ei_comm,
		(int)pss, (int)sf);
	    Plf = NULL;
	/*
	 * Save current working directory information.
	 */
	    if (p->p_cdir) {
		alloc_lfile(CWD, -1);
		process_node((KA_T)p->p_cdir);
		if (Lf->sf)
		    link_lfile();
	    }
	/*
	 * Save root directory information.
	 */
	    if (p->p_rdir) {
		alloc_lfile(RTD, -1);
		process_node((KA_T)p->p_rdir);
		if (Lf->sf)
		    link_lfile();
	    }
	/*
	 * Print information on the text file.
	 */
	    if (Sgvnops && p->p_as)
		process_text((KA_T)p->p_as);
	/*
	 * Save information on file descriptors.
	 */
	    if (!p->p_fdtab.fdt_entrytab || (nf = p->p_fdtab.fdt_sizeused) < 1)
		continue;
	    len = (MALLOC_S)(nf * sizeof(struct fd_entry));
	    if (nf > nfea) {
		if (fe)
		    fe = (struct fd_entry *)realloc((MALLOC_P *)fe, len);
		else
		    fe = (struct fd_entry *)malloc(len);
		if (!fe) {
		    (void) fprintf(stderr,
			"%s: PID %d; no space for %d file descriptors\n",
			Pn, pid, nf);
		    Exit(1);
		}
		nfea = nf;
	    }
	    if (kread((KA_T)p->p_fdtab.fdt_entrytab, (char *)fe, len))
		continue;
	    for (f = fe, i = 0; i < nf; f++, i++) {
		if ((fa = (KA_T)f->fd_file) && (f->fd_status & FD_INUSE)) {
		    if (f->fd_flag & FPOLLED) {
			if (kread(fa, (char *)&plx, sizeof(plx))
			||  !(fa = (KA_T)plx.px_fp))
			    continue;
		    }
		    alloc_lfile(NULL, i);
		    process_file(fa);
		    if (Lf->sf) {

#if	defined(HASFSTRUCT)
			if (Fsv & FSV_FG)
			    Lf->pof = (long)f->fd_flag;
#endif	/* defined(HASFSTRUCT) */

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


/*
 * get_clonemaj() - get clone major device number
 */

static int
get_clonemaj()
{
	KA_T v;

	if (get_Nl_value("cmaj", Drive_Nl, &v) < 0 || !v
	||  kread((KA_T)v, (char *)&CloneMaj, sizeof(CloneMaj)))
	    return(0);
	return((HaveCloneMaj = 1));
}


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

static void
get_kernel_access()
{
	KA_T v;
/*
 * Check kernel version.
 */
	(void) ckkv("OpenUNIX", (char *)NULL, LSOF_VSTR, (char *)NULL);

#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 ((Kd = 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 and values.
 */
	(void) build_Nl(Drive_Nl);
        if (nlist(Nmlst ? Nmlst : N_UNIX, Nl) < 0) {
	    (void) fprintf(stderr, "%s: can't read kernel name list from %s\n",
		Pn, Nmlst ? Nmlst : N_UNIX);
	    Exit(1);
	}
	if (get_Nl_value("var", Drive_Nl, &v) < 0 || !v
	||  kread((KA_T)v, (char *)&Var, sizeof(Var))) {
	    (void) fprintf(stderr,
		"%s: can't read system configuration info\n", Pn);
	    Exit(1);
	}
	if (get_Nl_value("proc", Drive_Nl, &Pract) < 0 || !Pract) {
	    (void) fprintf(stderr,
		"%s: can't find active process chain pointer\n", Pn);
	    Exit(1);
	}
	if (get_Nl_value("sgdnops", Drive_Nl, &Sgdnops) < 0 || !Sgdnops)
	    Sgdnops = (unsigned long)0;
	if (get_Nl_value("sgvnops", Drive_Nl, &Sgvnops) < 0 || !Sgvnops)
	    Sgvnops = (unsigned long)0;
/*
 * Identify the clone major device number.
 */
	if (!get_clonemaj()) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING; can't identify major clone device number\n",
		    Pn);
	}
}


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

void
initialize()
{
	get_kernel_access();
	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 */
{
	READLEN_T br;

	br = (READLEN_T) pread(Kd, buf, len, (off_t)addr);
	return((br == len) ? 0 : 1);
}


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

static void
process_text(pa)
	KA_T pa;			/* kernel address space description
					 * pointer */
{

	struct as as;
	struct segdev_data dv;
	char *fd;
	int i, j, k, l;
	struct seg s;
	KA_T v[MAXSEGS];
	struct segvn_data vn;
	KA_T vp;
/*
 * Get address space description.
 */
	if (kread(pa, (char *)&as, sizeof(as)))
	    return;
/*
 * Loop through the segments.  The loop should stop when the segment
 * pointer returns to its starting point, but just in case, it's stopped
 * when MAXSEGS have been recorded or 2*MAXSEGS have been examined.
 */
	s.s_next = as.a_segs;
	for (i = j = k = 0; i < MAXSEGS && j < 2*MAXSEGS; j++) {
	    if (!s.s_next || kread((KA_T)s.s_next, (char *)&s, sizeof(s)))
		break;
	    fd = (char *)NULL;
	    vp = (KA_T)NULL;
	    if (Sgvnops == (KA_T)s.s_ops && s.s_data) {

	    /*
	     * Process a virtual node segment.
	     */
		if (kread((KA_T)s.s_data, (char *)&vn, sizeof(vn)))
		    break;
		if ((vp = (KA_T)vn.svd_vp)) {
		    if ((vn.svd_flags & SEGVN_PGPROT)
		    ||  (vn.svd_prot & PROT_EXEC))
			fd = " txt";
		    else
			fd = " mem";
		}
	    } else if (Sgdnops == (KA_T)s.s_ops && s.s_data) {

	    /*
	     * Process a special device segment.
	     */
		if (kread((KA_T)s.s_data, (char *)&dv, sizeof(dv)))
		    break;
		if ((vp = (KA_T)dv.vp))
		    fd = "mmap";
	    }
	    if (fd && vp) {

	    /*
	     * Process the vnode pointer.  First make sure it's unique.
	     */
		for (l = 0; l < k; l++) {
		    if (v[l] == vp)
			break;
		}
		if (l >= k) {
		    alloc_lfile(fd, -1);
		    process_node(vp);
		    if (Lf->sf) {
			link_lfile();
			i++;
		    }
		}
		v[k++] = vp;
	    }
	/*
	 * Follow the segment link to the starting point in the address
	 * space description.  (The i and j counters place an absolute
	 * limit on the loop.)
	 */
	    if (s.s_next == as.a_segs)
		break;
	}


}


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

static void
readfsinfo()
{
	char buf[FSTYPSZ+1];
	int i, len;

	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 *)))))
	{
	    (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';
	    len = strlen(buf) + 1;
	    if (!(Fsinfo[i-1] = (char *)malloc((MALLOC_S)len))) {
		(void) fprintf(stderr,
		    "%s: no space for file system entry %s\n", Pn, buf);
		Exit(1);
	    }
	    (void) snpf(Fsinfo[i-1], len, "%s", buf);
	}
}


/*
 * read_proc() - read the process table
 */

static void
read_proc()
{
	MALLOC_S len;
	struct proc *p;
	KA_T pa;
	char tbuf[32];
	int try;

	if (!P) {

	/*
	 * Allocate initial space for local proc table.
	 */
	    if ((Npa = Var.v_proc) < 1) {
		(void) fprintf(stderr, "%s: bad proc table size: %d\n",
		    Pn, Var.v_proc);
		Exit(1);
	    }
	    Npa += PROCINCR;
	    len = (MALLOC_S)(Npa * sizeof(struct proc));
	    if (!(P = (struct proc *)malloc(len))) {
		(void) fprintf(stderr, "%s: no space for %d proc structures\n",
		    Pn, Npa);
		Exit(1);
	    }
	}
/*
 * Scan the active process chain.
 */
	for (try = 0; try < PROCTRYLM; try++) {

	/*
	 * Read the active process chain head.
	 */
	    pa = (KA_T)NULL;
	    if (!Pract || kread((KA_T)Pract, (char *)&pa, sizeof(pa)) || !pa) {
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: active proc chain ptr err; addr=%s, val=%s\n",
			Pn, print_kptr(Pract, tbuf, sizeof(tbuf)),
			print_kptr(pa, (char *)NULL, 0));
		continue;
	    }
	/*
	 * Follow the active process chain, accumulating proc structures.
	 */
	    for (Np = 0, p = P; pa;) {
		if (Np >= Npa) {

		/*
		 * Allocate more proc table space.
		 */
		    Npa += PROCINCR;
		    len = (MALLOC_S)(Npa * sizeof(struct proc));
		    if (!(P = (struct proc *)realloc((MALLOC_P *)P, len))) {
			(void) fprintf(stderr,
			    "%s: can't realloc %d proc table entries (%d)\n",
			    Pn, Npa, len);
			Exit(1);
		    }
		    p = &P[Np];
		}
		if (kread(pa, (char *)p, sizeof(struct proc)))
		    break;
		pa = (KA_T)p->p_next;
		if ((p->p_flag & P_DESTROY) || (p->p_flag & P_GONE)
		||  !p->p_pidp || !p->p_cred || !p->p_execinfo)
		    continue;
		Np++;
		p++;
	    }
	/*
	 * See if enough processes were accumulated.
	 */
	    if (Np >= PROCMIN)
		break;
	}
/*
 * Quit if not enough proc structures could be collected.
 */
	if (try >= PROCTRYLM) {
	    (void) fprintf(stderr, "%s: can't read proc table\n", Pn);
	    Exit(1);
	}
	if (Np < Npa && !RptTm) {

	/*
	 * If not repeating, reduce the local proc table size to a minimum.
	 */
	    len = (MALLOC_S)(Np * sizeof(struct proc));
	    if (!(P = (struct proc *)realloc((MALLOC_P *)P, len))) {
		(void) fprintf(stderr,
		    "%s: can't reduce proc table to %d entries\n", Pn, Np);
		Exit(1);
	    }
	    Npa = Np;
	}
}
