#ifndef lint
static char rcsid[] = "$Header: sys-5r4.c,v 1.13 1996/07/20 00:13:44 forys Exp $";
#endif

/*
**  This program may be freely redistributed for noncommercial purposes.
**  This entire comment MUST remain intact.
**
**  System V support by Ric Anderson (ric@cs.arizona.edu) and Jeff Forys.
**
**  Copyright 1994, 1996 by Jeff Forys (jeff@forys.cranbury.nj.us)
*/

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

#include <sys/priocntl.h>
#include <sys/tspriocntl.h>

#if !defined(P_PID)
#include <sys/procset.h>
#endif

#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>

extern int MissedProcCnt;

/*
 * 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.
 */
#ifdef sun
# ifdef	SIG2STR_MAX			/* Solaris: handled in MdepInit() */
#define	SIGMAPSIZE	128
char *SigMap[SIGMAPSIZE+1];
int NSig;
# else
char *SigMap[] = { "0",
	"HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT",		/*  1 -  6 */
	"EMT", "FPE", "KILL", "BUS", "SEGV", "SYS",		/*  7 - 12 */
	"PIPE", "ALRM", "TERM", "USR1", "USR2", "CLD",		/* 13 - 18 */
	"PWR", "WINCH", "URG", "POLL", "STOP", "TSTP",		/* 19 - 24 */
	"CONT", "TTIN", "TTOU", "VTALRM", "PROF", "XCPU",	/* 25 - 30 */
	"XFSZ", "WAITING", "LWP", "FREEZE", "THAW", "RTMIN",	/* 31 - 36 */
	"RTMIN+1", "RTMIN+2", "RTMIN+3", "RTMAX-3", "RTMAX-2",	/* 37 - 41 */
	"RTMAX-1", "RTMAX"					/* 42 - 43 */
};
int NSig = NSIG-1;
# endif
#else
char *SigMap[] = { "0",
	"HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT",		/*  1 -  6 */
	"EMT", "FPE", "KILL", "BUS", "SEGV", "SYS",		/*  7 - 12 */
	"PIPE", "ALRM", "TERM", "USR1", "USR2", "CHLD",		/* 13 - 18 */
	"PWR", "WINCH", "URG", "POLL", "STOP", "TSTP",		/* 19 - 24 */
	"CONT", "TTIN", "TTOU", "VTALRM", "PROF", "XCPU",	/* 25 - 30 */
	"XFSZ", "WAITING", "LWP", "AIO"				/* 31 - 34 */
};
int NSig = NSIG-1;
#endif

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

#define	PRIO_SKEW	20	/* Skew SysVr4 priorities to match "nice" */

static char *TtyDevDir = "/dev";
static int setpriority_bsd();

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 *SysErr();

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

#ifdef sun
	{
		extern int SigsPerLine;
		int sig;

		SigsPerLine = 8;

# ifdef SIG2STR_MAX
		/*
		 * Set up signal info dynamically for binary compatibility
		 * with future Solaris versions that may add more signals.
		 */
		NSig = _sys_nsig-1;

		if (SIGMAPSIZE < _sys_nsig) {
			fprintf(stderr, "%s: SIGMAPSIZE must be at least %d\n",
			        ProgName, _sys_nsig);
			exit(EX_SERR);
		}

		for (sig = 0; sig <= NSig; sig++) {
			if ((SigMap[sig] = malloc(SIG2STR_MAX)) == NULL)
				Exceed("SigMap");
			if (sig2str(sig, SigMap[sig]) == -1)
				sprintf(SigMap[sig], "%d", sig);
		}
# endif
	}
#endif

	PrioMin = -PRIO_SKEW;
	PrioMax = PRIO_SKEW;

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

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

	/*
	 * 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);
	}
	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((pid_t)pid, SigPri);
	else
		return setpriority_bsd((pid_t)pid, SigPri);
}

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

#include <sys/procfs.h>
#include <fcntl.h>

static char *ProcDir =	"/proc";		/* proc directory */
#ifdef	PRMAXOPERAND				/* SysVr4.2MP */
#define	pr_lttydev pr_ttydev
static char *ProcFil =	"/proc/%s/psinfo";	/* proc info file */
#else
static char *ProcFil =	"/proc/%s";		/* proc images */
#endif

/*
 * 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 char *SysErr();
	static char *zombie = "<defunct>";
	static struct ProcInfo procinfo;
	static DIR *dirfp = NULL;
#ifdef	PRMAXOPERAND
	static struct psinfo pinfo;
#else
	static struct prpsinfo pinfo;
#endif
	struct dirent *dp;
	char flnm[FILENAME_MAX];
	int fd;

	/*
	 * If this is our first time here, open the proc directory,...
	 */
	if (dirfp == NULL && (dirfp=opendir(ProcDir)) == NULL) {
		fprintf(stderr, "%s: %s: %s\n", ProgName, ProcDir, SysErr());
		exit(EX_SERR);
	}

	while ((dp = readdir(dirfp)) != NULL) {
		if (strcmp(dp->d_name,".") == 0 || strcmp(dp->d_name,"..") == 0)
			continue;
		(void) sprintf(flnm, ProcFil, dp->d_name);
		if ((fd = open(flnm, O_RDONLY)) < 0) {
			MissedProcCnt++;
			continue;	/* ignore procs we don't own */
		}

		/*
		 * Read process status (either read() or ioctl(PIOCPSINFO)).
		 */
#ifdef	PRMAXOPERAND
		if (read(fd, &pinfo, sizeof(pinfo)) != sizeof(pinfo))
#else
		if (ioctl(fd, PIOCPSINFO, &pinfo) == -1)
#endif
		{
			(void) close(fd);
			if (Wflag)
				printf("Warning: can't read %s\n", flnm);
			continue;	/* ignore these too */
		}
		(void) close(fd);

		/*
		 * Information about a process now resides in 'pinfo'.
		 */
		procinfo.pi_flags = 0;
		procinfo.pi_pid = pinfo.pr_pid;
		procinfo.pi_uid = pinfo.pr_uid;
#ifndef	PRMAXOPERAND	/* cant handle SysVr4.2MP model (per thread zombies) */
		if (pinfo.pr_zomb != 0) {
			procinfo.pi_flags |= PI_ZOMBIE;
			procinfo.pi_cmd = zombie;
		} else
#endif
		{
			if (pinfo.pr_pid < 5)	/* low pids are special */
				procinfo.pi_flags |= PI_ASKUSR;
			if (pinfo.pr_lttydev != PRNODEV) {
				procinfo.pi_flags |= PI_CTLTTY;
				procinfo.pi_tty = pinfo.pr_lttydev;
			}
			procinfo.pi_cmd = pinfo.pr_fname;
		}
		return &procinfo;
	}

	(void) closedir(dirfp);
	dirfp = NULL;
	return (struct ProcInfo *)NULL;
}

/*
 * setpriority_bsd(pid_t pid, int prio)
 *
 * Set priority of a process.
 *
 * pid  - target process id.
 * prio - priority adjustment (range: -PRIO_SKEW - +PRIO_SKEW).
 *
 * Returns 0 on success, -1 on error (setting errno).
 */
static int
setpriority_bsd(pid, prio)
	pid_t pid;
	int prio;
{
	static int firstime = 1;
	static short ts_maxupri;	/* initialized once */
	static id_t ts_id;		/* initialized once */
	pcparms_t parms;
	tsparms_t *tset_p;

	/*
	 * Read default parameters for Time Share (TS) processes.
	 */
	if (firstime) {
		pcinfo_t pcinfo;
		(void)strcpy(pcinfo.pc_clname, "TS");
		if (priocntl(0, 0, PC_GETCID, (caddr_t)&pcinfo) == -1)
			return -1;
		ts_maxupri = ((tsinfo_t *)pcinfo.pc_clinfo)->ts_maxupri;
		ts_id = pcinfo.pc_cid;
		firstime = 0;
	}

	/*
	 * Get scheduling parameters for this process.
	 *
	 * "If process specified does not belong to the specified
	 *  class, priocntl() returns -1 with errno set to ESRCH."
	 */
	parms.pc_cid = ts_id;
	if (priocntl(P_PID, pid, PC_GETPARMS, (caddr_t)&parms) == -1)
		return -1;

	/*
	 * Set new priority.
	 */
	tset_p = (tsparms_t *)parms.pc_clparms;
	tset_p->ts_uprilim = tset_p->ts_upri = -(ts_maxupri * prio) / PRIO_SKEW;
	if (priocntl(P_PID, pid, PC_SETPARMS, (caddr_t)&parms) == -1)
		return -1;

	return 0;
}
