/*
** Copyright (c) 1986, 1994, 1996, 2000, 2002
**	Jeff Forys (jeffware@marjum.com).  All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that: (1) Redistributions of
** source code must retain the above copyright notice, this list of
** conditions and the following disclaimer, (2) Redistributions in
** binary form must reproduce the above copyright notice, this list
** of conditions and the following disclaimer in the documentation
** and/or other materials provided with the distribution, (3) All
** advertising materials mentioning features or use of this software
** must display the following acknowledgment: ``This product includes
** software developed by Jeff Forys (jeffware@marjum.com).'', (4)
** The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
** WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** NetBSD/KERN_PROC2 support by Simon Burge (simonb@NetBSD.ORG)
*/

#ifndef lint
static char rcsid[] = "$Id: nbsd-44.c,v 1.10 2005/04/06 23:49:35 forys Exp $";
#endif

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

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

#include <sys/resource.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[NSIG];		/* dynamically filled in by MdepInit() */

int NSig = NSIG - 1;

#define	SETCMD(dst,src,maxlen) {			\
	if (maxlen > 0) src[maxlen] = '\0';		\
	dst = (dst = strrchr(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 */
int	CmdLen;			/* commands are unique to this many bytes */
pid_T	MyPid;			/* pid of this process */
uid_T	MyUid;			/* uid of this process */
char	*ProgName;		/* program name */

static const char *nomemmsg =	"%s: %s: out of memory (wanted %u bytes)\n";

/*
 * 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();
	const char * const *signam, *scp;
	size_t siz;
	char *cp;
	int i;

	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 = (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);
	}

	/*
	 * 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;
	CmdLen = MAXCOMLEN;

	/*
	 * Determine total space for signal names (or numbers) and NULs,
	 * allocate memory, then copy signals with pointers in SigMap[].
	 */
	for (i = siz = 0, signam = sys_signame; i < NSIG; i++, signam++)
		siz += (signam != NULL && strchr(*signam, ' ') == NULL)?
			strlen(*signam): 10;	/* names or numbers */
	siz += NSIG;				/* NUL terminators */

	if ((cp = malloc(siz)) == NULL) {
		fprintf(stderr, nomemmsg, ProgName, "MdepInit", siz);
                exit(EX_SERR);
	}

	for (i = 0, signam = sys_signame; i < NSIG; i++, signam++) {
		SigMap[i] = cp;
		if (signam != NULL && strchr(*signam, ' ') == NULL) {
			for (scp = *signam; *scp != '\0'; scp++)
				*cp++ = islower(*scp)? toupper(*scp): *scp;
			*cp++ = '\0';
		} else
			cp += sprintf(cp, "%u", i) + 1;
	}
}

/*
 * 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));
}

/* OS supports POSIX-style regular expressions */
#include <regex.h>
REAL_REGEX_FUNCS

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

#include <sys/user.h>
#include <sys/sysctl.h>

static	char	*pidmap[] = {
	"swapper", "init"

#ifdef NeXTBSD
	, "mach_init"
#else
	, "pagedaemon"

#ifdef __FreeBSD__
#if (__FreeBSD__ > 3)
	, "vmdaemon", "bufdaemon", "syncer", "vnlru"
#else
	, "vmdaemon", "update"
#endif
#endif

#ifdef __NetBSD_Version__
#if (__NetBSD_Version__ >= 105000000)
	, "reaper", "ioflush"
#endif
#endif
#endif	/* !NeXTBSD */

};

static	int	pidmapsiz = sizeof(pidmap) / sizeof(pidmap[0]);

#ifdef __FreeBSD__
#if (__FreeBSD__ > 4)
#define	_PROC_COMM(p)	(p)->ki_comm
#define	_PROC_FLAG(p)	(p)->ki_flag
#define	_PROC_PID(p)	(p)->ki_pid
#define	_PROC_STAT(p)	(p)->ki_stat
#define	_PROC_TDEV(p)	(p)->ki_tdev
#define	_PROC_UID(p)	(p)->ki_uid
#endif
#endif	/* __FreeBSD__ */

#if defined(__NetBSD_Version__) && defined(KERN_PROC2)
#define	_SYSCTL_ARG	KERN_PROC2
#define	_SYSCTL_NMIB	6
#define	_SYSCTL_PROC	kinfo_proc2
#define	_SYSCTL_MIBINIT(m)			\
	m[0] = CTL_KERN;			\
	m[1] = KERN_PROC2;			\
	m[2] = KERN_PROC_ALL;			\
	m[3] = 0;				\
	m[4] = sizeof(struct _SYSCTL_PROC);	\
	m[5] = 0
#define	_SYSCTL_MIBPROC(m,sz)			\
	m[5] = (sz) / sizeof(struct _SYSCTL_PROC)

#define	_PROC_COMM(p)	(p)->p_comm
#define	_PROC_FLAG(p)	(p)->p_flag
#define	_PROC_PID(p)	(p)->p_pid
#define	_PROC_STAT(p)	(p)->p_stat
#define	_PROC_TDEV(p)	(p)->p_tdev
#define	_PROC_UID(p)	(p)->p_uid
#endif	/* __NetBSD_Version__ && KERN_PROC2 */

#ifndef	_PROC_TDEV	/* PROC defaults */
#define	_PROC_COMM(p)	(p)->kp_proc.p_comm
#define	_PROC_FLAG(p)	(p)->kp_proc.p_flag 
#define	_PROC_PID(p)	(p)->kp_proc.p_pid
#define	_PROC_STAT(p)	(p)->kp_proc.p_stat
#define	_PROC_TDEV(p)	(p)->kp_eproc.e_tdev
#define	_PROC_UID(p)	(p)->kp_eproc.e_ucred.cr_uid
#endif

#ifndef	_SYSCTL_MIBINIT	/* SYSCTL defaults */
#define	_SYSCTL_ARG	KERN_PROC
#define	_SYSCTL_NMIB	3
#define	_SYSCTL_PROC	kinfo_proc
#define	_SYSCTL_MIBINIT(m)			\
	m[0] = CTL_KERN;			\
	m[1] = KERN_PROC;			\
	m[2] = KERN_PROC_ALL
#define _SYSCTL_MIBPROC(m,sz)
#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()
{
	static struct ProcInfo procinfo;
	static int nproc = -1;
	static struct _SYSCTL_PROC *aproc;

	/*
	 * If this is our first time here, prepare to read procs from kernel.
	 */
	if (nproc == -1) {
		int mib[_SYSCTL_NMIB];
		size_t size = 0;

		_SYSCTL_MIBINIT(mib);
		if (sysctl(mib, _SYSCTL_NMIB, NULL, &size, NULL, 0) < 0) {
			fprintf(stderr, "%s: sysctl proctab size: %s\n",
				ProgName, strerror(errno));
			exit(EX_SERR);
		}

		size += (size / 8);
		if ((aproc = malloc(size)) == NULL) {
			fprintf(stderr, nomemmsg, ProgName, "GetProc", size);
			exit(EX_SERR);
		}

		_SYSCTL_MIBPROC(mib,size);
		if (sysctl(mib, _SYSCTL_NMIB, aproc, &size, NULL, 0) < 0) {
			fprintf(stderr, "%s: sysctl proctab: %s\n",
				ProgName, strerror(errno));
			exit(EX_SERR);
		}

		if (size % sizeof *aproc != 0) {
			fprintf(stderr,
				"%s: proc size mismatch, recompile libkvm\n",
				ProgName);
			exit(EX_SERR);
		}

		nproc = size / sizeof(struct _SYSCTL_PROC);
	}

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

	do {
		if (_PROC_STAT(aproc) != 0) {
			/*
			 * Make sure this isn't a "zombie" or "exiting"
			 * process.  If it is, fill in procinfo and return.
			 */
			procinfo.pi_flags = 0;
			procinfo.pi_pid = (pid_T) _PROC_PID(aproc);
			procinfo.pi_uid = (uid_T) _PROC_UID(aproc);

			if (_PROC_STAT(aproc) == SZOMB) {	/* zombie */
				static char *zombie = "<defunct>";
				procinfo.pi_flags |= PI_ZOMBIE;
				procinfo.pi_cmd = zombie;
			} else if (_PROC_FLAG(aproc) & P_WEXIT) { /* exiting */
				static char *exiting = "<exiting>";
				procinfo.pi_flags |= PI_SWEXIT;
				procinfo.pi_cmd = exiting;
			}

			if (procinfo.pi_flags) {
				nproc--;
				aproc++;
				return(&procinfo);
			}
		}
	} while (_PROC_STAT(aproc) == 0);

	/*
	 * We now have a process (`aproc').
	 * Fill in the rest of `procinfo'.
	 */
	if (_PROC_TDEV(aproc) != NODEV) {	/* controlling tty */
		procinfo.pi_flags |= PI_CTLTTY;
		procinfo.pi_tty = (tty_T) _PROC_TDEV(aproc);
	}

#ifdef __FreeBSD__
# if (__FreeBSD__ > 4)
	if (_PROC_PID(aproc) < 100)		/* special */
		procinfo.pi_flags |= PI_ASKUSR;
# else
	if (_PROC_PID(aproc) < pidmapsiz) {	/* special */
		procinfo.pi_cmd = pidmap[_PROC_PID(aproc)];
		procinfo.pi_flags |= PI_ASKUSR;
	} else				 /* set path-stripped command name */
# endif
#else
	if (_PROC_PID(aproc) < pidmapsiz) {	/* special */
		procinfo.pi_cmd = pidmap[_PROC_PID(aproc)];
		procinfo.pi_flags |= PI_ASKUSR;
	} else				 /* set path-stripped command name */
#endif
		SETCMD(procinfo.pi_cmd, _PROC_COMM(aproc), MAXCOMLEN)

	nproc--;
	aproc++;
	return(&procinfo);
}


syntax highlighted by Code2HTML, v. 0.9.1