#ifndef lint
static char rcsid[] = "$Header: aix-3.c,v 1.4 1994/06/26 04:17:59 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", "26", "MSG", "WINCH", "PWR", "USR1",		/* 25 - 30 */
	"USR2", "PROF", "DANGER", "VTALRM", "MIGRATE", "PRE",	/* 31 - 36 */
	"37", "38", "39", "40", "41", "42",			/* 37 - 42 */
	"43", "44", "45", "46", "47", "48",			/* 43 - 48 */
	"49", "50", "51", "52", "53", "54",			/* 49 - 54 */
	"55", "56", "57", "58", "59", "GRANT",			/* 55 - 60 */
	"RETRACT", "SOUND", "SAK", "64",			/* 61 - 64 */
};
int NSig = NSIG;

#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.
 *
 * IMPORTANT NOTE TO PEOPLE SNARFING THIS CODE:
 *
 *	The AIX getproc() and getuser() routines are unsupported (not to
 *	mention, undocumented).  They are liable to be changed, or replaced
 *	in future versions of AIX.  Please include this comment in any
 *	code that uses these routines.  Thanks.
 *
 *	With that said, here's a brief (incomplete) description of each:
 *
 *		#include <procinfo.h>
 *
 *		struct procinfo pinfo[N];
 *		getproc(pinfo, N, sizeof(struct procinfo))
 *		RETURN: number of procs read (must be all), or -1 on error.
 *		[If N is too small, -1 is returned with errno == ENOSPC]
 *
 *		struct userinfo uinfo;
 *		struct procinfo *aproc;
 *		getuser(aproc, sizeof(struct procinfo), &uinfo, sizeof(uinfo))
 *		RETURN: 0 on success, -1 on error.
 */

#include <sys/var.h>
#include <sys/sysconfig.h>
#include <sys/errno.h>

#include <procinfo.h>

extern	off_t lseek();

#define	NPROCS	256	/* size of `procs' struct (if too small, use calloc) */

/*
 * 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.
 *	We can do this only if Iflag, TtyIndx, and CmdIndx are zero.
 */
struct ProcInfo *
GetProc()
{
	extern int errno;
	extern char *SysErr();
	static struct procinfo pinfo[NPROCS], *procsp;
	static struct ProcInfo procinfo;
	register struct procinfo *aproc;
	register struct userinfo *auser;
	static int thisproc = 0;
	static int initialized = 0;
	static int needuser = 1;		/* Fflag support */

	/*
	 * Read in all the processes at once, into a large block of memory;
	 * if `pinfo' is big enough, we'll use that, o/w we will calloc()
	 * what we need.  Either way, `procsp' will point to the next proc.
	 *
	 * The first time thisproc == 0, we do the `procsp' initialization.
	 * The second time thisproc == 0, we are finished and return NULL.
	 * The following `while' is for sanity; it could be an `if'.
	 */
	while (thisproc == 0) {
		char *errstr = "%s: %s: %s\n";
		register u_int nprocs, maxnprocs, pisize;

		if (initialized)
			return((struct ProcInfo *)NULL);

		pisize = sizeof(struct procinfo);
		if ((thisproc = getproc(pinfo, (u_int)NPROCS, pisize)) < 0) {
			/*
			 * We apparently have more then NPROCS processes
			 * running on this machine.  Ideally, one should
			 * raise NPROCS if they run into this problem.
			 * However, we'll quietly deal with it by grabbing
			 * a sufficiently large block of memory.
			 *
			 * N.B. `nprocs' is temporarily used to hold `errno'
			 * across the call to sysconfig(), after which, it
			 * will assume it's real duty.
			 */
			struct var v;

			nprocs = errno;
			if (sysconfig(SYS_GETPARMS, &v, sizeof(struct var)) < 0)
				maxnprocs = 1024 * 1024;	/* very big */
			else
				maxnprocs = v.v_proc;
			errno = nprocs;

			nprocs = NPROCS;
			while (errno == ENOSPC && nprocs <= maxnprocs) {
				nprocs <<= 1;
				if ((procsp = (struct procinfo *)
				     calloc(nprocs, pisize)) == NULL) {
					fprintf(stderr, errstr, ProgName,
					        "getproc", "out of memory");
					exit(EX_SERR);
				}
				errno = 0;
				thisproc = getproc(procsp, nprocs, pisize);
				if (thisproc < 0 && errno != ENOSPC) {
					fprintf(stderr, errstr, ProgName,
						"getproc", SysErr());
					exit(EX_SERR);
				} else if (thisproc == nprocs)
					break;
				free((void *)procsp);
			}
			if (nprocs > maxnprocs) {
				fprintf(stderr, errstr, ProgName,
				        "GetProc", "maxnproc exceeded");
				exit(EX_SERR);
			}
		} else
			procsp = pinfo;

		/*
		 * We run a little faster without reading in the user struct;
		 * the price is incomplete information for errors (no cmd).
		 */
		if (Fflag && Iflag == 0 && TtyIndx == 0 && CmdIndx == 0)
			needuser = 0;

		initialized = 1;
	}

	/*
	 * Trudge thru `procsp'.  Decrement `thisproc' as we go.
	 */
	do {
		aproc = procsp++;
		thisproc--;

		if (aproc->pi_stat != SNONE) {
			/*
			 * 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->pi_pid;
			procinfo.pi_uid = (uid_T) aproc->pi_uid;

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

			if (procinfo.pi_flags || !needuser)
				return(&procinfo);
			else {
				static struct userinfo uinfo;
				if (getuser(aproc, sizeof(struct procinfo),
				            &uinfo,sizeof(struct userinfo)) < 0)
					auser = NULL;
				else
					auser = &uinfo;
			}
		}

	} while (aproc->pi_stat == SNONE || auser == NULL);

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

	/* set path-stripped command name */
	SETCMD(procinfo.pi_cmd, auser->ui_comm, MAXCOMLEN)

	return(&procinfo);
}
