#ifndef lint
static char rcsid[] = "$Header: mach-3.c,v 1.2 1994/10/06 21:15:16 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/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", "ABRT",		/*  1 -  6 */
	"EMT", "FPE", "KILL", "BUS", "SEGV", "SYS",		/*  7 - 12 */
	"PIPE", "ALRM", "TERM", "URG", "STOP", "TSTP",		/* 13 - 18 */
	"CONT", "CHLD", "TTIN", "TTOU", "IO", "XCPU",		/* 19 - 24 */
	"XFSZ", "VTALRM", "PROF", "WINCH", "INFO", "USR1",	/* 25 - 30 */
	"USR2", "32",						/* 31 - 32 */
};
int NSig = NSIG - 1;

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

#undef	PI_ZOMBIE		/* #define'd in "conf.h" *and* <sys/table.h> */
#define	PI_ZOMB	3		/* value from "conf.h" (it *wont* change) */

#include <sys/table.h>

#define	PROCSLOP	32	/* as an aid in catching run-away processes */
#define	NPROCS	(480+PROCSLOP)	/* size of `procs' struct (or use calloc()) */

/*
 * GetProc()
 *
 * Fill in and return a `struct ProcInfo' with information about the
 * next process.  If no processes are left, return NULL.
 */
struct ProcInfo *
GetProc()
{
	extern int errno;
	extern char *SysErr();
	static struct tbl_procinfo procs[NPROCS], *procsp;
	static struct ProcInfo procinfo;
	register struct tbl_procinfo *aproc;
	static int procindx = 0;
	static int thisproc = 0;
	static int maxnproc = 0;
	static int readonce = 1;

	/*
	 * If this is the first time through, determine how many processes
	 * we will grab at one time.  Hopefully, "all"... but, if the
	 * table() fails, or we have more than NPROCS and the calloc()
	 * fails, we'll have to resort to NPROCS at-a-time.
	 */
	if (maxnproc == 0) {
		if ((maxnproc = table(TBL_PROCINFO, 0, (char *)NULL, INT_MAX,
		                      0) + PROCSLOP) > NPROCS) {
			/*
			 * We have more then NPROCS processes running
			 * on this machine.  Ideally, one should bump
			 * NPROCS if they run into this problem.
			 * However, we'll quietly deal with it by
			 * grabbing a large block of memory.
			 *
			 * Note that, if we can not grab the memory,
			 * we will just have to make successive calls
			 * to table() reading NPROCS at-a-time.
			 */
			procsp = (struct tbl_procinfo *)
				calloc(maxnproc, sizeof(struct tbl_procinfo));
			if (procsp == NULL) {
				maxnproc = NPROCS;
				readonce = 0;
			}
		} else
			maxnproc = NPROCS;
	}

	/*
	 * This is a little convoluted due to the table() system call.
	 * We have the total number of processes we can read at-a-time
	 * in `maxnproc'.  We do not care about the actual count of
	 * existing processes because:
	 *   1) The count may change by time we actually read them,
	 *   2) We may be unable to read them all at once, anyway.
	 *
	 * So, if we *know* we have enough memory ("readonce" is set),
	 * just do the read once and hope we get everything; we have
	 * provided for some additional processes (via PROCSLOP).
	 * Alternately, continually read in blocks of processes until
	 * table() returns EINVAL (since we can never be sure of the
	 * actual number of active processes in this situation).
	 *
	 * At any rate, return NULL when all processes have been read.
	 */
	do {
		if (thisproc == 0) {
			if (maxnproc <= NPROCS)
				procsp = procs;

			if (readonce && ++readonce == 3)
				return((struct ProcInfo *)NULL);

			thisproc = table(TBL_PROCINFO, procindx, (char *)procsp,
			                 maxnproc, sizeof(struct tbl_procinfo));

			/*
			 * For now, we must be setuid root up to this point.
			 * Relinquish root here; this will screw us if the
			 * calloc() fails and we are not the superuser, but
			 * we have no choice.
			 */
			(void) seteuid(getuid());

			if (thisproc <= 0) {
				if (thisproc != 0 && errno != EINVAL)
					fprintf(stderr, "%s: read proc: %s\n",
					        ProgName, SysErr());
				return((struct ProcInfo *)NULL);
			}
			procindx += thisproc;
		}

		aproc = procsp++;
		thisproc--;
	} while (aproc->pi_status == PI_EMPTY);

	/*
	 * We now have a process (`aproc').
	 * Fill in the rest of `procinfo'.
	 */
	procinfo.pi_flags = 0;
	procinfo.pi_pid = (pid_T) aproc->pi_pid;
	procinfo.pi_uid = (uid_T) aproc->pi_uid;

	/*
	 * 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.
	 */
	if (aproc->pi_status == PI_ZOMBIE) {
		static char *zombie = "<defunct>";
		procinfo.pi_flags |= PI_ZOMB;
		procinfo.pi_cmd = zombie;
	} else if (aproc->pi_status == PI_EXITING) {
		static char *exiting = "<exiting>";
		procinfo.pi_flags |= PI_SWEXIT;
		procinfo.pi_cmd = exiting;
	} else {
		if (aproc->pi_ttyd != -1) {	/* has a controlling tty */
			procinfo.pi_flags |= PI_CTLTTY;
			procinfo.pi_tty = (tty_T) aproc->pi_ttyd;
		}

		/* set path-stripped command name */
		SETCMD(procinfo.pi_cmd, aproc->pi_comm, PI_COMLEN)
	}

	return(&procinfo);
}
