/*
** 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.
**
** Linux support by Chuck L. Blake (chuckb@Caltech.EDU) and Jeff Forys.
*/

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

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

#include <sys/time.h>
#include <sys/resource.h>	/* for [get|set]priority() */
#ifndef	__CYGWIN__
#include <linux/fs.h>		/* for MINOR() macro */
#endif

#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.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.
 */

char *SigMap[] = {
	"0",
	"HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT",		/*  1 -  6 */
	"BUS", "FPE", "KILL", "USR1", "SEGV", "USR2",		/*  7 - 12 */
	"PIPE", "ALRM", "TERM", "STKFLT", "CHLD", "CONT",	/* 13 - 18 */
	"STOP", "TSTP", "TTIN", "TTOU", "URG", "XCPU",		/* 19 - 24 */
	"XFSZ", "VTALRM", "PROF", "WINCH", "POLL", "PWR",	/* 25 - 30 */
	"UNUSED" 
};

int NSig = 31;

#define	SETCMD(dst,src,maxlen) {			\
	extern char *strrchr();				\
	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 */
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 skill_getpri(int *low, int *high);

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.
	 */
#ifndef	__CYGWIN__
	if (MyUid == ROOTUID)
		(void) setpriority(PRIO_PROCESS, MyPid, PRIO_MIN);
#endif

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

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

#ifndef	__CYGWIN__
	/*
	 * 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);
	}
#endif
}

/*
 * 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
#ifndef	__CYGWIN__
		return(setpriority(PRIO_PROCESS, (int)pid, SigPri));
#else
		{
			extern int errno;
			errno = ENOSYS;
			return -1;
		}
#endif
}

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

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

#include <fcntl.h>
char *SysErr();

static char *ProcDir =	"/proc";		/* proc directory */
static char *ProcFil =	"/proc/%s/stat";	/* name of only needed file */

#define MXBUF 1024
	static char buf[MXBUF];	/* primary storage for file io */
/*
 * 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 DIR *dirfp = NULL;
	struct dirent *dp;
	char flnm[FILENAME_MAX];
	int fd;

	int n;		/* first for length of buf[], then as a loop counter */
	char* tmp;	/* pointer into buf[] */
	struct stat sb;	/* stat() buffer, used to get UID */

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

	/*
	 * Read process-id's from /proc, open each processes' corresponding
	 * "stat" file and parse it to fill in our 'procinfo' structure.
	 */
	while ((dp = readdir(dirfp)) != NULL) {
		if (dp->d_name[0] < '0' || dp->d_name[0] > '9')
			continue;

		/*
		 * Try to open the corresponding "stat" file, if successful
		 * then stat it (the file owner is the owner of the process).
		 * If either fails, chalk it up as missed and try next one.
		 */
		(void) sprintf(flnm, ProcFil, dp->d_name);
		if ((fd = open(flnm, O_RDONLY)) < 0 || fstat(fd, &sb) < 0) {
			MissedProcCnt++;
			continue;
		}
		procinfo.pi_uid = sb.st_uid;

		/* read in the stat file and ensure it is NUL-terminated */
		n = read(fd, buf, MXBUF-1);
		(void) close(fd);
		if (n == -1) {
			MissedProcCnt++;
			continue;
		}
		buf[n] = '\0';

		/*
		 * Parse the contents of the stat file.  There is much here
		 * we do not care about, and the contents seem to change
		 * with each release.  We want <PID> <comm> and <tty> from:
		 *
		 * <PID> (<comm>) <state> <ppid> <pgrp> <session> <tty> ...
		 *
		 * Since <comm> could potentially contain paren or whitespace,
		 * start looking for the trailing paren from end of string.
		 * If we find it, make it NUL to terminate the command.
		 */
		if ((tmp = strrchr(buf, ')')) == NULL) {
badstatfmt:
			if (Wflag)
				printf("Warning: can't parse file: %s\n", flnm);
			continue;
		}
		*tmp = '\0';

		/*
		 * Grab the process id (atoi() will stop at first non-digit),
		 * the command name, and whether this one is zombified.
		 */
		procinfo.pi_pid = atoi(buf);
		procinfo.pi_cmd = strchr(buf, '(') + 1;
		procinfo.pi_flags = 0;
		if (*(tmp + 2) == 'Z')
			procinfo.pi_flags |= PI_ZOMBIE;

		/*
		 * All we need now is the tty's rdev.  This is the fifth
		 * field after <comm>.
		 */
		for (n = 0; n < 5; n++)
			while (*++tmp != ' ')	/* land on a space */
				if (*tmp == '\0')
					goto badstatfmt;

		if ((n = atoi(tmp)) == 0) {		/* no controlling tty */
			procinfo.pi_tty = (tty_T)-2;
		} else {
			procinfo.pi_tty = (tty_T)n;
			procinfo.pi_flags |= PI_CTLTTY;
		}

		return(&procinfo);
	}

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


syntax highlighted by Code2HTML, v. 0.9.1