#ifndef lint
static char rcsid[] = "$Header: irix-3.c,v 1.8 1994/06/26 04:16:15 forys Exp $";
#endif

/*
**  This program may be freely redistributed for noncommercial purposes.
**  This entire comment MUST remain intact.
**
**  Copyright 1994 by Jeff Forys (jeff@forys.cranbury.nj.us)
*/

#define	NO_MEXTERN
#include "conf.h"
#undef	NO_MEXTERN

#include <sys/dir.h>
#include <sys/user.h>
#include <sys/proc.h>

#include <stdio.h>

/*
 * Define SigNames, NSig, and TtyDevDir here; they are used by other
 * routines and must be global.  Everyone seems to have their own
 * idea as to what NSIG should be.  Here, `NSig' is the number of
 * signals available, not counting zero.
 */
char *SigMap[] = { "0",
	"HUP", "INT", "QUIT", "ILL", "TRAP", "IOT",		/*  1 -  6 */
	"EMT", "FPE", "KILL", "BUS", "SEGV", "SYS",		/*  7 - 12 */
	"PIPE", "ALRM", "TERM", "USR1", "USR2", "CHLD",		/* 13 - 18 */
	"PWR", "STOP", "TSTP", "POLL", "IO", "URG",		/* 19 - 24 */
	"WINCH", "VTALRM", "PROF", "CONT", "TTIN", "TTOU",	/* 25 - 30 */
	"XCPU", "XFSZ",						/* 31 - 32 */
};
int NSig = NUMSIGS;

#define	SETCMD(dst,src,maxlen) {			\
	extern char *rindex();				\
	if (maxlen > 0) src[maxlen] = '\0';		\
	dst = (dst = rindex(src, '/')) ? ++dst: src;	\
}

static char *TtyDevDir = "/dev";

int	Skill;			/* set 1 if running `skill', 0 if `snice' */
int	PrioMin, PrioMax;	/* min and max process priorities */
int	SigPri;			/* signal to send or priority to set */
pid_T	MyPid;			/* pid of this process */
uid_T	MyUid;			/* uid of this process */
char	*ProgName;		/* program name */

/*
 * This is the machine-dependent initialization routine.
 *
 *   - The following global variables must be initialized:
 *     MyPid, MyUid, ProgName, Skill, PrioMin, PrioMax, SigPri
 *   - The working directory will be changed to that which contains the
 *     tty devices (`TtyDevDir'); this makes argument parsing go faster.
 *   - If possible, this routine should raise the priority of this process.
 */
void
MdepInit(pname)
	char *pname;
{
	extern char *rindex(), *SysErr();

	MyPid = (pid_T) getpid();
	MyUid = (uid_T) getuid();
	SETCMD(ProgName, pname, 0)

	/*
	 * If we are running as root, raise our priority to better
	 * catch runaway processes.
	 */
	if (MyUid == ROOTUID)
		(void) setpriority(PRIO_PROCESS, MyPid, PRIO_MIN);

	/*
	 * Determine what we are doing to processes we find.  We will
	 * either send them a signal (skill), or renice them (snice).
	 */
	Skill = (strcmp(ProgName, "snice") != 0);

	/*
	 * chdir to `TtyDevDir' to speed up tty argument parsing.
	 */
	if (chdir(TtyDevDir) < 0) {
		fprintf(stderr, "%s: chdir(%s): %s\n", ProgName, TtyDevDir,
		        SysErr());
		exit(EX_SERR);
	}

	/*
	 * Set up minimum and maximum process priorities.
	 * Initialize SigPri to either default signal (`skill') or
	 * default priority (`snice').
	 */
	PrioMin = PRIO_MIN;
	PrioMax = PRIO_MAX;
	SigPri = Skill? SIGTERM: 4;
}

/*
 * Carry out an action on a particular process.  If this is `skill',
 * then send the process a signal, otherwise this is `snice' so change
 * it's priority.
 *
 * If 0 is returned, the operation was successful, otherwise -1 is
 * returned and `errno' set.
 */
int
MdepAction(pid)
	pid_T pid;
{
	if (Skill)
		return(kill((int)pid, SigPri));
	else
		return(setpriority(PRIO_PROCESS, (int)pid, SigPri));
}

/*
 * Now, set up everything we need to write a GetProc() routine.
 */

#include <sys/immu.h>
#include <sys/swap.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>

#include <nlist.h>

static char *kmemf =	"/dev/kmem";	/* window into kernel virtual memory */
static char *memf =	"/dev/mem";	/* window into physical memory */
static char *kernf =	"/unix";	/* kernel image */
static char *devdskf =	"/dev/dsk";	/* where swap devices can be found */
static int kmem = 0, mem = 0;

static struct nlist nl[] = {
	{ "v" },
#define	X_VAR		0
	{ "proc" },
#define	X_PROC		1
	{ "swaptab" },
#define	X_SWAPTAB	2
	{ "" },
#define	X_LAST		3
};

static	int	nproc = -1;
static	struct	proc *procp;
static	swpt_t	swtab[MSFILES];		/* available swap devices */
static	dev_t	swdev[MSFILES];		/* opened swap devices */

#define	NPROCS	32			/* number of procs to read at once */

static	char	*pidmap[] = { "sched", "init", "vhand", "bdflush" };
static	int	pidmapsiz = sizeof(pidmap) / sizeof(pidmap[0]);

extern	off_t lseek();

/*
 * GetProc()
 *
 * Fill in and return a `struct ProcInfo' with information about the
 * next process.  If no processes are left, return NULL.
 *
 * Fflag support:
 *      If Fflag is set we will try to avoid reading in the user struct
 *	and locating/opening the swap device(s).
 *      We can do this only if Iflag, TtyIndx, and CmdIndx are zero.
 */
struct ProcInfo *
GetProc()
{
	extern char *SysErr();
	static struct user *GetUser();
	static struct proc procs[NPROCS], *procsp;
	static struct ProcInfo procinfo;
	static struct var var;
	register struct user *auser;
	register struct proc *aproc;
	static int thisproc = 0;
	static int needuser = 1;		/* Fflag support */

	/*
	 * If this is our first time here, open various files,
	 * and set up the nlist.
	 */
	if (nproc == -1) {
		char *errstr = "%s: %s: %s\n";
		int nfound;

		if ((kmem=open(kmemf, 0)) < 0) {	/* open kmem */
			fprintf(stderr, errstr, ProgName, kmemf, SysErr());
			exit(EX_SERR);
		}

		if ((mem=open(memf, 0)) < 0) {		/* open mem */
			fprintf(stderr, errstr, ProgName, memf, SysErr());
			exit(EX_SERR);
		}

		if ((nfound=nlist(kernf, nl)) < 0) {	/* kernel name list */
			fprintf(stderr, errstr, ProgName, kernf,"no name list");
			exit(EX_SERR);
		}

		if (nfound != 0) {
			register int i;

			fprintf(stderr, "%s: nlist: unresolved symbols:",
			        ProgName);
			for (i = 0; i < X_LAST; i++)
				if (nl[i].n_type == 0)
					fprintf(stderr, " %s", nl[i].n_name);
			(void) putc('\n', stderr);
			exit(EX_SERR);
		}

		GetStruct((off_t)svirtophys(nl[X_VAR].n_value), "var",
		          (off_t)&var, sizeof(var));
		GetStruct((off_t)svirtophys(nl[X_SWAPTAB].n_value), "swaptab",
		          (off_t)swtab, sizeof(swtab));
		procp = (struct proc *)svirtophys(nl[X_PROC].n_value);
		nproc = var.v_proc;

		/*
		 * We run faster without finding/opening the swap devices
		 * and reading in the user struct; the price is incomplete
		 * information for errors (no cmd).
		 */
		if (Fflag && Iflag == 0 && TtyIndx == 0 && CmdIndx == 0)
			needuser = 0;
		else
			OpenSwapDevs();
	}

	/*
	 * Read in NPROCS proc structures at-a-time.  Decrement `nproc'
	 * by the number of proc structures we have read; when it reaches
	 * zero, we are finished (return NULL).
	 */
	do {
		while (thisproc == 0) {
			int nread;
			int psize;

			if (nproc == 0)
				return((struct ProcInfo *)NULL);

			thisproc = MIN(NPROCS, nproc);
			psize = thisproc * sizeof(struct proc);
			nproc -= thisproc;
			if (lseek(kmem, (off_t)procp, L_SET) == -1 ||
			    (nread = read(kmem, (char *)procs, psize)) < 0) {
				fprintf(stderr, "%s: read proc: %s\n",
				        ProgName, SysErr());
				return((struct ProcInfo *)NULL);
			} else if (nread != psize) {
				thisproc = nread / sizeof(struct proc);
				nproc = 0;
				fprintf(stderr, "%s: read proc: short read\n",
				        ProgName);
			}
			procsp = procs;
			procp += thisproc;
		}

		aproc = procsp++;
		thisproc--;

		if (aproc->p_stat != 0) {
			/*
			 * Before we go through the trouble of reading
			 * in the user struct, let's make sure this isn't
			 * a "zombie" or "exiting" process.  If it is,
			 * we have all the information we need; fill in
			 * procinfo and return.
			 */
			procinfo.pi_flags = 0;
			procinfo.pi_pid = (pid_T) aproc->p_pid;
			procinfo.pi_uid = (uid_T) aproc->p_uid;

			if (aproc->p_stat == SZOMB) {		/* zombie */
				static char *zombie = "<defunct>";
				procinfo.pi_flags |= PI_ZOMBIE;
				procinfo.pi_cmd = zombie;
			} else if (aproc->p_flag & SEXIT) {	/* exiting */
				static char *exiting = "<exiting>";
				procinfo.pi_flags |= PI_SWEXIT;
				procinfo.pi_cmd = exiting;
			} else if (!needuser) {			/* Fflag */
				static char *fflagcmd = "<-f>";
				procinfo.pi_cmd = fflagcmd;
			}

			if (procinfo.pi_flags || !needuser)
				return(&procinfo);
			else
				auser = GetUser(aproc);
		}

	} while (aproc->p_stat == 0 || auser == NULL);

	/*
	 * We now have a process (`aproc') and a user (`auser').
	 * Fill in the rest of `procinfo'.
	 */
	if (auser->u_ttyp != 0) {	/* has a controlling tty */
		procinfo.pi_flags |= PI_CTLTTY;
		procinfo.pi_tty = (tty_T) auser->u_ttyd;
	}

	if (aproc->p_pid < pidmapsiz) {	/* special */
		procinfo.pi_cmd = pidmap[aproc->p_pid];
		procinfo.pi_flags |= PI_ASKUSR;
	} else				 /* set path-stripped command name */
		SETCMD(procinfo.pi_cmd, auser->u_comm, PSCOMSIZ)

	return(&procinfo);
}

#define	SKRD(file, src, dst, size)			\
	(lseek(file, (off_t)(src), L_SET) == -1) ||	\
	(read(file, (char *)(dst), (size)) != (size))

GetStruct(loc, name, dest, size)
	off_t loc;
	char *name;
	off_t dest;
	int size;
{
	if (SKRD(kmem, loc, dest, size)) {
		fprintf(stderr, "%s: can't read %s struct at %lx in %s\n",
		        ProgName, name, (u_long)loc, kmemf);
		exit(EX_SERR);
	}
}

/*
 * OpenSwapDevs()
 *
 * Locate and open all the swap devices currently configured.
 * The opened file descriptors are saved in `swdev[i]'.
 *
 * Side Effect: this routine does a chdir(devdskf).
 */
OpenSwapDevs()
{
	extern char *SysErr();
	struct stat statb;
	register DIR *devdsk;
	register struct direct *dp;
	register char *flnm;
	register int i, keeplooking = 1;

	/*
	 * Initialize all file descriptors to "unopened".
	 */
	for (i = 0; i < MSFILES; i++)
		swdev[i] = -1;

	/*
	 * Relocate our current working directory to `devdskf' and open
	 * the directory for reading it's contents.
	 */
	if (chdir(devdskf) < 0) {
		fprintf(stderr, "%s: can't change dir to %s: %s\n",
		        ProgName, devdskf, SysErr());
		return;
	}
	if ((devdsk = opendir(".")) == NULL) {
		fprintf(stderr, "%s: can't open %s: %s\n",
			        ProgName, devdskf, SysErr());
		return;
	}

	/*
	 * For each directory entry, if it is one of our swap devices,
	 * try to open it.
	 */
	while ((dp = readdir(devdsk)) != NULL && keeplooking) {
		flnm = dp->d_name;
		if (stat(flnm, &statb) < 0)	/* who knows, who cares... */
			continue;
		keeplooking = 0;
		for (i = 0; i < MSFILES; i++) {
			if (swdev[i] < 0 && S_ISBLK(statb.st_mode) &&
			    swtab[i].st_dev == statb.st_rdev)
				if ((swdev[i] = open(flnm, 0)) < 0)
					fprintf(stderr,
					        "%s: can't open %s/%s: %s\n",
					        ProgName,devdskf,flnm,SysErr());

			if (swtab[i].st_dev && swdev[i] < 0)
				keeplooking = 1;
		}
	}

	/*
	 * Lastly, see if there were any swap devices we could not locate.
	 */
	if (keeplooking) {
		for (i = 0; i < MSFILES; i++)
			if (swtab[i].st_dev && swdev[i] < 0)
				fprintf(stderr,
				"%s: can't open swap device %d,%d\n", ProgName,
				major(swtab[i].st_dev), minor(swtab[i].st_dev));
	}
	closedir(devdsk);
}

/*
 * GetUser(aproc)
 *
 * Read in the user struct for `aproc' and return a pointer to it.
 * If an error occurs, return NULL.
 */
static struct user *
GetUser(aproc)
	struct proc *aproc;
{
	static char *WarnMsg = "Warning: can't read ";
	static union {
		struct user user;
		char upgs[USIZE][NBPP];
	} u;
	register int i, pgtype, file, swapi;
	register u_int addr;
	register char *flnm;

	/*
	 * aproc->p_upgs has the pdes for the u-area.  Also, encoded into
	 * the pde is the key to the location of these pages.  If the disk
	 * block descriptor is of type DBD_NONE, then the pages are in
	 * core.  If they are of type DBD_SWAP they are out on swap device
	 * number `dbd_swpi' at logical block number `dbd_pgno'.  This is
	 * actually pretty cool, except that each swap partition must be
	 * treated separately (so we had to open all of them).
	 *
	 * If at any time, an lseek() or read() fails, print a warning
	 * message (if `Wflag' is set) and return NULL.
	 */
	for (i = 0; i < USIZE; i++) {
		pgtype = pdetodbd(&aproc->p_upgs[i])->dbd_type;
		switch (pgtype) {
		    case DBD_NONE:	/* in core */
			addr = aproc->p_upgs[i].pgm.pg_pfn*NBPP;
			file = mem;
			flnm = memf;
			break;
		    case DBD_SWAP:	/* swapped out */
			/*
			 * I assume that it's necessary to add in `st_swplo'
			 * in case our logical swap partition starts at a
			 * block offset other than zero.  The include files
			 * werent clear on this, so I guessed.  ..jef
			 */
			swapi = aproc->p_upgs[i].pgm.dbd.dbd_swpi;
			addr = BBTOB(aproc->p_upgs[i].pgm.dbd.dbd_pgno +
			             swtab[swapi].st_swplo);
			file = swdev[swapi];
			flnm = "swap";
			break;
		    default:		/* other (impossible?) things */
			if (Wflag)
				printf("%suser page of type %d for pid %d\n",
				       WarnMsg, pgtype, aproc->p_pid);
			return((struct user *)NULL);
		}
		if (SKRD(file, addr, u.upgs[i], NBPP)) {
			if (Wflag)
				printf("%suser page %u for pid %d from %s\n",
				       WarnMsg, addr/NBPP, aproc->p_pid, flnm);
			return((struct user *)NULL);
		}
	}
	return (&u.user);
}
