Subject: GR - A Game Regulator
Reply-To: mcooper@usc-oberon.arpa
Newsgroups: mod.sources
Approved: jpn@panda.UUCP

Mod.sources:  Volume 4, Issue 113
Submitted by: talcott!usc-oberon.ARPA:mcooper

#!/bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #!/bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	README
#	Makefile
#	gr.control
#	gr.c
#	gr.h
#	logfile.c
# This archive created: Mon May  5 13:14:10 1986
# By:	Michael A. Cooper (USC Computing Services, Los Angeles)
export PATH; PATH=/bin:$PATH
echo shar: extracting "'README'" '(1915 characters)'
if test -f 'README'
then
	echo shar: over-writing existing file "'README'"
fi
cat << \SHAR_EOF > 'README'

		G R  -  A   G A M E   R E G U L A T O R

			     Revision 1.16


	GR is used to regulate game playing.  It checks various system
facts such as the number of logged in users, and the system load average
against a central control file to determine whether it is okay to play
a requested game.  It also lets you select a priority (see setpriority(2))
at which the game will run.  See the (enclosed) manual for more details.
	
	GR was formerly called "gsh" from which the base code was written
at Reed College by a person who wishes to remain anonymous.  I "adopted"
it and have extensively modified the code.  

	We are currently running GR on 4 of our VAX 750's running 4.2BSD.
I suspect that the code should run without problems under 4.3BSD.  As for
System V, I'm sure some minimal modifications will have to be made, but
as yet, I have not tried porting it.

	To install GR, edit gr.h and Makefile to tailor to your system.
The comments supplied therein, should suffice.  Then run "make gr" to
compile GR.  You should have no errors.  Now edit the "gr.control" file
to set your local system parameters.  Next run "make create" to 
create the appropriate directory and support files.  Run "make dolink"
if you wish to have most of the standard 4.2 games put under the regulator;
otherwise, you can link them by hand.

	I'd appreciate any bugs/comments you wish to pass along.

					Mike Cooper


+-----------------------------------------------------------------------------+
| Michael Cooper		    UUCP: ...!{uscvax, sdcrdcf, scgvaxd,      |
| University Computing Services           engvax, smeagol}!usc-oberon!mcooper |
| University of Southern Cal.     BITNET: mcooper@uscvaxq, mcooper@jaxom      |
| Los Angeles, Ca.   90089-0251     ARPA: mcooper@usc-oberon.arpa,	      |
| (213) 743-3462		  	  mcooper@usc-eclc.arpa  	      |
+-----------------------------------------------------------------------------+
SHAR_EOF
echo shar: extracting "'Makefile'" '(1506 characters)'
if test -f 'Makefile'
then
	echo shar: over-writing existing file "'Makefile'"
fi
cat << \SHAR_EOF > 'Makefile'
#
# Makefile for Game Regulator.
#
# $Header: Makefile,v 1.1 86/05/05 13:13:34 mcooper Exp $
#

#
# GAMEDIR - This is where the symbolic links are to be placed.
#	    Should be /usr/games.
#
GAMEDIR = /usr/games

#
# HIDEDIR - Name of directory to hide the actual executables
#
HIDEDIR = $(GAMEDIR)/.hide

# 
# GROUP - This group has read/execute permissions on HIDEDIR and owns
#	  the Game Regulator which is setgid to GROUP.
#
GROUP = play

#
# NAME - Name of Game Regulator in GAMEDIR.  I have it set to ".gr"
# 	 so it doesn't show up with ls(1) by default.
#
NAME = .gr

#
# GAMES - The games in the original GAMEDIR to be put under regulation.
#
GAMES = adventure backgammon btlgammon canfield chess cribbage fish\
	hangman mille monop rain rogue sail snake teachgammon trek \
	worm worms wump zork

OBJS = gr.o logfile.o

gr: $(OBJS)
	cc -s $(OBJS) -o gr

dbx: $(OBJS)
	cc -g $(OBJS) -o gr -llocal

.c.o:
	cc -c $*.c

$(OBJS): gr.h

install: gr
	install -m 2751 -g $(GROUP) gr $(GAMEDIR)/$(NAME)

create:
	-mkdir $(HIDEDIR)
	chgrp $(GROUP) $(HIDEDIR)
	chmod 770 $(HIDEDIR)
	cp gr.control $(GAMEDIR)/lib
	touch $(GAMEDIR)/lib/gr.log
	chgrp $(GROUP) $(GAMEDIR)/lib/gr.control $(GAMEDIR)/lib/gr.log
	chmod 664 $(GAMEDIR)/lib/gr.control $(GAMEDIR)/lib/gr.log

dolink: dolink.sh
	sed s#HIDEDIR#$(HIDEDIR)# dolink.sh |\
		sed s#GAMEDIR#$(GAMEDIR)# |\
		sed s#NAME#$(NAME)# > dolink
	chmod +x dolink
	dolink $(GAMES)

clean:
	rm -f *.o log dolink

shar:
	shar README Makefile gr.control *.[ch] > shar.out
SHAR_EOF
echo shar: extracting "'gr.control'" '(582 characters)'
if test -f 'gr.control'
then
	echo shar: over-writing existing file "'gr.control'"
fi
cat << \SHAR_EOF > 'gr.control'
#
# Game Regulator Control File
#
# Note - The last line contains the default parameters for any file NOT listed.
#
# 	Name 		- Name of game in /usr/games. 		(string)
#	Load 		- Maximum load average.  		(floating point)
#	Users 		- Maximum number of users.  		(integer)
#	Priority	- Priority game will run at.
#			  See setpriority(2).			(integer)
#
#		 Maximum
# Name	       Load    Users   Priority
#--------------------------------------
rogue		4.0	8	0
arogue		4.0	8	0
srogue		4.0	8	0
urogue		4.0	8	0
ourogue		4.0	8	0
hack		4.0	8	5
sail		4.0	8	0
empire		4.0	8	0
default		5.0	8	0
SHAR_EOF
echo shar: extracting "'gr.c'" '(5975 characters)'
if test -f 'gr.c'
then
	echo shar: over-writing existing file "'gr.c'"
fi
cat << \SHAR_EOF > 'gr.c'
#ifndef lint
static char *RCSid = "$Header: gr.c,v 1.16 86/04/26 17:30:53 mcooper Exp $";
#endif

/*
 *------------------------------------------------------------------
 *
 * $Source: /usr/src/local/gr/RCS/gr.c,v $
 * $Revision: 1.16 $
 * $Date: 86/04/26 17:30:53 $
 * $State: Exp $
 * $Author: mcooper $
 * $Locker: mcooper $
 *
 *------------------------------------------------------------------
 *
 * Michael Cooper (mcooper@usc-oberon.arpa)
 * University Computing Services,
 * University of Southern California,
 * Los Angeles, California,   90089-0251
 * (213) 743-3469
 *
 *------------------------------------------------------------------
 * $Log:	gr.c,v $
 * Revision 1.16  86/04/26  17:30:53  mcooper
 * This version adds a new field to the gr.control
 * file that specifies the priority that the game will
 * run at.
 * 
 * Revision 1.15  86/03/25  18:50:08  mcooper
 * Added #ifdef LOGFILE.
 * 
 * Revision 1.14  86/03/25  15:48:49  mcooper
 * New headers.
 * 
 *------------------------------------------------------------------
 */

#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <utmp.h>
#include <nlist.h>
#include "gr.h"


static int ufd = 0;
static int max_users;
static int priority = 0;
static lfd = 0;
static double avenrun[3];
static double max_load;
static struct utmp buf;
static struct nlist nl[] = {
	{ "_avenrun" },
	{ "" },
};

int was_tod;
int was_load;
int was_users;
char *rindex();
long time();

main(argc, argv)
int argc;
char **argv;
{
	char *game = rindex(argv[0], '/');

	if (game == 0)
		game = argv[0];
	else
		game++;	/* Skip the '/' */
	setup(game);
	if(checkfor(NOGAMING))
		exit(2);
	if (tod() || users() || load()) {
		fprintf(stderr, "Sorry, no games now.\n");
		if (was_load)
			fprintf(stderr, 
				"The system load is greater than %.2lf.\n",
				max_load);
		if (was_users)
			fprintf(stderr, 
				"There are more than %d users logged in.\n",
				max_users);
		if (was_tod)
			fprintf(stderr, 
"Game playing is not permitted between %d00 and %d00 hours on weekdays.\n", 
				MORNING, EVENING);
		exit(1);
	}
#ifdef LOGFILE
	logfile(game);
#endif
	play(game, argv);
}

play(game, args)
char *game;
char **args;
{
	int pid;
	char tmp[128];

	switch (pid = fork()) {
	case -1:
		fprintf(stderr, "Cannot fork.\n");
		exit(1);
	case 0:
		strcpy(tmp, HIDEDIR);
		strcat(tmp, "/");
		strcat(tmp, game);
		signal(SIGINT, SIG_DFL);
		signal(SIGQUIT, SIG_DFL);
		signal(SIGTSTP, SIG_DFL);
		setuid(getuid());
		if(setpriority(PRIO_PROCESS, 0, priority) < 0) {
			perror("setpriority");
			exit(1);
		}
		execv(tmp, args);
		perror(tmp);
		exit(1);
	}
	for (;;) {
		sleep(60);
		if (load() || users() || tod()) {
			warn(0);
			sleep(60);
			if (reprieve())
				continue;
			sleep(60);
			if (reprieve())
				continue;
			warn(1);
			sleep(60);
			if (reprieve())
				continue;
			warn(2);
			sleep(60);
			if (reprieve())
				continue;
			blast(pid);
		}
	}
}

reprieve()
{
	static char mess[] = "\rYou have a reprieve.  Continue playing.\r\n";

	if (load() || users() || tod())
		return (0);
	write(2, mess, sizeof(mess));
	return (1);
}

warn(which)
int which;
{
	static char buff[512];
	static char *mesg = "to save your game.  If you do not leave\r\n\
the game within this period, your game will be terminated.\r\n";
	static char *t[3] = {
		"4 minutes ",
		"2 minutes ",
		"1 minute "
	};

	if (was_load)
		sprintf(buff, "\rThe system load is greater than %.2lf.\r\n",
			max_load);
	if (was_users)
		sprintf(buff, "\rThere are more than %d users logged in.\r\n",
			max_users);
	if (was_tod)
		sprintf(buff, "\rGame time is over.\r\n");
	strcat(buff, "You have ");
	strcat(buff, t[which]);
	strcat(buff, mesg);
	write(2, buff, strlen(buff));
}

blast(pid)
int pid;
{
	static char mess[] = "\rYour game is forfeit.\r\n";

	write(2, mess, sizeof(mess));
	kill(pid, SIGHUP);
	sleep(60);
	kill(pid, SIGKILL);
}

death(n)
int n;
{
	union wait status;

	if (wait3(&status, WNOHANG | WUNTRACED, (char *)0) == 0) {
		return;
	}
	if (status.w_stopval == WSTOPPED) {
		signal(SIGTSTP, SIG_DFL);
		kill(getpid(), SIGTSTP);
		signal(SIGTSTP, SIG_IGN);
		return;
	} else
		exit(0);
}


setup(game)
char *game;
{
	char tmp[16];
	char lbuf[BUFSIZ];
	int n;
	FILE *list;

	if ((ufd = open("/etc/utmp", 0)) < 0) {
		perror("/etc/utmp");
		exit(1);
	}
	if ((lfd = open("/dev/kmem", 0)) < 0) {
		perror("/dev/kmem");
		exit(1);
	}
	nlist("/vmunix", nl);
	if (nl[0].n_type == 0) {
		perror("/vmunix");
		exit(1);
	}
	if ((list = fopen(CONTROL, "r")) == NULL) {
		perror(CONTROL);
		exit(1);
	}
	while (fgets(lbuf, sizeof(lbuf), list)) {
		if(lbuf[0] == COMMENT)
			continue;
		sscanf(lbuf, "%s%lf%d%d", 
			tmp, &max_load, &max_users, &priority);
		if (strcmp(tmp, game) == 0)
			break;
	}
	fclose(list);
	signal(SIGCHLD, death);
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGTSTP, SIG_IGN);
}

load()
{
	lseek(lfd, (long)nl[0].n_value, 0);
	read(lfd, avenrun, sizeof(avenrun));
	return (was_load = (avenrun[1] >= max_load));
}

users()
{
	char tmp[32];
	int count = 0;
	long l = time((long *)0);
	struct stat sbuf;

	lseek(ufd, (long)0, 0);
	while (read(ufd, &buf, sizeof(buf)) > 0) {
		if (buf.ut_name[0] != '\0') {
			count++;
		}
	}
	return (was_users = (count > max_users));
}

tod()
{
#ifdef TOD
	long now;
	struct tm *localtime();
	struct tm *ntime;

	time(&now);
	ntime = localtime(&now);
	if(ntime->tm_wday == 0 || ntime->tm_wday == 6)
		return(was_tod = FALSE);/* OK on Sat & Sun */
	if(ntime->tm_hour < MORNING || ntime->tm_hour >= EVENING)
		return(was_tod = FALSE);/* OK during non working hours */
	
	return(was_tod = TRUE);
#endif
}

checkfor(file)
char *file;
{
	char buf[200];
	FILE *fd, *fopen();

	if((fd = fopen(file, "r")) != NULL) {
		fprintf(stderr, "Sorry, no games now...\n");
		while(fgets(buf, sizeof(buf), fd))
			fprintf(stderr, buf);
		fclose(fd);
		return(TRUE);
	}
	return(FALSE);
}
SHAR_EOF
echo shar: extracting "'gr.h'" '(1771 characters)'
if test -f 'gr.h'
then
	echo shar: over-writing existing file "'gr.h'"
fi
cat << \SHAR_EOF > 'gr.h'
/*
 * $Header: gr.h,v 1.3 86/03/25 15:50:31 mcooper Exp $
 *------------------------------------------------------------------
 *
 * $Source: /usr/src/local/gr/RCS/gr.h,v $
 * $Revision: 1.3 $
 * $Date: 86/03/25 15:50:31 $
 * $State: Exp $
 * $Author: mcooper $
 * $Locker: mcooper $
 *
 *------------------------------------------------------------------
 *
 * Michael Cooper (mcooper@usc-oberon.arpa)
 * University Computing Services,
 * University of Southern California,
 * Los Angeles, California,   90089-0251
 * (213) 743-3469
 *
 *------------------------------------------------------------------
 * $Log:	gr.h,v $
 * Revision 1.3  86/03/25  15:50:31  mcooper
 * Moved CONTROL back to normal place in /usr/games/lib.
 * 
 * Revision 1.2  86/03/25  15:47:57  mcooper
 * Lines beginning with '#' are comment lines.
 * 
 * Revision 1.1  86/02/12  17:49:54  mcooper
 * Initial revision
 * 
 *------------------------------------------------------------------
 */

/*
 * TOD - define if you wish to restrict game playing during a certain
 *	 time of day on weekdays.  Use MORNING and EVENING as the time
 *	 period.
 */
/*
#define TOD
*/
#define MORNING		8			/* stop playing games */
#define EVENING		17			/* game time starts */

/*
 * HIDEDIR - directory where the actual games are kept.
 */
#define HIDEDIR		"/usr/games/.hide"

/* 
 * NOGAMING - If this file exists, it's contents are printed out and
 *	      gaming is not permitted.
 */
#define NOGAMING	"/usr/games/nogames"	

/*
 * LOGFILE - Define to file name to log entries in.
 */
#define LOGFILE		"/usr/games/lib/gr.log"

/*
 * CONTROL - Master control file.
 */
#define CONTROL		"/usr/games/lib/gr.control"

/*
 * Misc. defines - Leave this alone!
 */
#define TRUE		1
#define FALSE		0
#define COMMENT		'#'
SHAR_EOF
echo shar: extracting "'logfile.c'" '(1757 characters)'
if test -f 'logfile.c'
then
	echo shar: over-writing existing file "'logfile.c'"
fi
cat << \SHAR_EOF > 'logfile.c'
#ifndef lint
static char *RCSid = "$Header: logfile.c,v 1.3 86/03/25 15:44:35 mcooper Exp $";
#endif

/*
 *------------------------------------------------------------------
 *
 * $Source: /usr/src/local/gr/RCS/logfile.c,v $
 * $Revision: 1.3 $
 * $Date: 86/03/25 15:44:35 $
 * $State: Exp $
 * $Author: mcooper $
 * $Locker: mcooper $
 *
 *------------------------------------------------------------------
 *
 * Michael Cooper (mcooper@usc-oberon.arpa)
 * University Computing Services,
 * University of Southern California,
 * Los Angeles, California,   90089-0251
 * (213) 743-3469
 *
 *------------------------------------------------------------------
 * $Log:	logfile.c,v $
 * Revision 1.3  86/03/25  15:44:35  mcooper
 * more of same.
 * 
 * Revision 1.2  86/03/25  15:35:43  mcooper
 * New headers...
 * 
 *------------------------------------------------------------------
 */

/*
 * logfile() -- log who is playing.  If LOGFILE is defined (in gr.c)
 *		then this function keeps track of users who play.
 */


#include <stdio.h>
#include <pwd.h>
#include "gr.h"

#ifdef LOGFILE
struct passwd *pw;
struct passwd *getpwuid();

logfile(msg)
char *msg;
{
	char *fprintf();
	char *strcmp();
	char *getlogin();
	char *time(), *ctime();
	char buf[80];
	char *str;
	char *user;
	double now;
	int diff = 0;
	FILE *fd, *fopen();

	if ((pw = getpwuid(getuid())) != NULL)
		user = pw->pw_name;
	else
		user = "(unknown)";

	if((str = getlogin()) != NULL)
		if(strcmp(str, user) != 0) {
			diff = 1;
			sprintf(buf, "(%s)", str);
		}

	time(&now);

	if((fd = fopen(LOGFILE, "a")) != NULL){
		fprintf(fd, "%-10s%-12s%-7s%10.24s  [ %s ]\n", 
				user, 
				diff ? buf : "", 
				rindex(ttyname(0), '/') + 1,
				ctime(&now), 
				msg);
		fclose(fd);
	}
}
#endif
SHAR_EOF
#	End of shell archive
exit 0


