[net.sources] batch,batchd - low priority batched execution

kwlalonde@watmath.UUCP (Ken Lalonde) (06/07/85)

Batch allows you to run resource-intensive jobs with mimimum impact on
other users, without going out of your way.  Batch submits a shell
script to a queue for execution by a daemon.  Each queue has a
"profile" file describing its characteristics.  Here is the profile for
our "troff" queue, with comments in ():

	nice 2		(nice jobs +2)
	maxexec 1	(run at most one at a time)
	mail ec		(mail submitter when job is done, or if crash)
	supervisor /usr/spool/batch/troff/This_week	(see "man batchd")
	mailsupervisor sec	(see "man batchd")
	loadsched 10	(don't schedule jobs if load average > 10)
	loadstop 14	(stop running jobs if l.a. > 14)
	restart		(restart after crash)
	rlimitcpu 1800	(various 4.2BSD resource limits)
	rlimitfsize 10000000
	rlimitcore 0
	rlimitrss 50000
We use batch to run most troff's, and some special
purpose things like image processing and simulations.

The original author is Alex White (watmath!mks!alex).
The code is written for 4.2BSD.  Comments welcome.

	Ken Lalonde	ihnp4!watmath!kwlalonde

: This is a shar archive.	Extract with sh, not csh.
: The rest of this file will extract:
: Makefile README batch.1 batch.c batchd.8 batchd.c junk lex.h lex.l queues
echo Extracting Makefile
sed 's/^X//' > Makefile << 'e-o-f'
XCFLAGS = -O
XBATCHDIR = /usr/spool/batch
X
Xall: batch batchd
X
Xbatchd: batchd.o lex.o
X	cc $(CFLAGS) batchd.o lex.o -o batchd
X
Xbatch: batch.o
X	cc $(CFLAGS) batch.o -o batch
X
Xlex.c: lex.l
X	lex lex.l
X	mv lex.yy.c lex.c
X
X# Batch must be setuid root to signal batchd
Xinstall: all
X	install -m 740 -o root batchd $(DESTDIR)/usr/lib
X	install -m 4755 -o root $(DESTDIR)/usr/bin
X
Xclean:
X	rm -f *.o lex.c batch batchd
e-o-f
echo Extracting README
sed 's/^X//' > README << 'e-o-f'
XTo install:
X	su
X	make install
X	cp batch.1 /usr/man/man1
X	cp batchd.8 /usr/man/man8
X	mkdir /usr/spool/batch
X	cp -r ./queues/* ./queues/.cleanup /usr/spool/batch
X	cat >> /usr/lib/crontab << "."
X00 5 * * 6 sh /usr/spool/batch/.cleanup
X.
X	cat >> /etc/rc.local << "."
Xif [ -d /usr/spool/batch ]; then
X	rm -f /usr/spool/batch/*/tf*
X	/usr/lib/batchd & echo -n ' batch'			>/dev/console
Xfi
X.
X	/usr/lib/batchd &
X	sleep 5; echo echo testing | batch now
e-o-f
echo Extracting batch.1
sed 's/^X//' > batch.1 << 'e-o-f'
X.TH BATCH 1 UofW
X.SH NAME
Xbatch \- Submit a shell script to a daemon for low-priority batched execution
X.SH SYNOPSIS
X.B batch
X[
X\fB\-v\fR
X]
X[
X\fB\-P\fIa\fR
X]
Xqueue_name
X[
Xshell_file ...
X]
X.SH DESCRIPTION
X.B Batch
Xis used to submit a list of commands to be executed by the shell possibly
Xat a later time when system resources are available.
XThis allows the system to restrict the number of jobs of a particular
Xclass that are executed simultaneously, and to adjust the number
Xbased on the current system load.
X.PP
XThe contents of the specified files are concatenated together, saved
Xaway in a system spool directory, and passed to the current shell
X($SHELL) at execution time.
XIf no files are specified, the standard input is read.
XMost, but not all, environment variables are also copied to the
Xbeginning of the spooled command file.
X.PP
XThe
X.B queue_name
Xargument is the name of the queue to submit to.
XQueues are named by what kind of thing usually goes into them;
Xthey can actually contain any shell script at all.
X.PP
XThe \-P option may specify a priority from
X.I a
Xto
X.I z;
Xthis priority determines the sequence in which jobs are run.
XThe highest priority is
X.I a
Xand the default is
X.I q.
XPriorities in the range from
X.I a
Xto
X.I g
Xare reserved to the super-user.
XThe \-v option for verbose gives you the name of the spool file
Xin which the shell commands have been put while wating execution.
X.PP
XMail will be returned to you at the end of the job if the job exits with
Xa non-zero status return or produces any output.
XMail may also be sent to you depending on the individual queue upon
Xjob start, system crash, or successful execution with no output.
X.SH "SEE ALSO"
Xbatchd(8), nice(1), renice(8), batchqueue(p), batchcancel(p), troffqueue(p), troffcancel(p)
X.SH BUGS
X.PP
XIf you don't have permission to determine the current working directory
Xthen the shell file may execute under the spool directory when it runs.
X.PP
XQueues are named by the type of thing that usually gets put into them;
Xthey should be named by the type of service they provide.
X.PP
XThere is no official "batch remove" command to cancel a batch job.
XYou can try to find the spool directory and over-write the spooled
Xlist of shell commands, but if the job has already started to run you
Xmust use "ps -x" to find the running process and kill it by hand.
XYour best bet is to use batchcancel(p).
X.PP
XUsing letters to indicate priority is strange.
XA numbering system would be more natural.
XOne based on
X.IR nice (1)
Xwouldn't be too bad.
e-o-f
echo Extracting batch.c
sed 's/^X//' > batch.c << 'e-o-f'
X/*
X *  batch [-v] [-Pn] queue [file ...]
X */
X#include <stdio.h>
X#include <sys/param.h>
X#include <sys/stat.h>
X#include <signal.h>
X#include <errno.h>
X
Xchar grade = 'q';	/* Priority of this job */
Xint vflag;
Xextern errno;
Xchar *getenv();
Xchar *index();
Xenum { BOURNE, CSH } theshell;
Xchar *deleteerr;
Xchar *tffmt = "%s/%.14s/tf%c%D";
Xchar *cffmt = "%s/%.14s/cf%c%D";
X#define	SPOOLDIR	"/usr/spool/batch"
Xchar Pid[] = "/usr/spool/pid/batchd";
X
Xmain(argc, argv, arge)
Xchar **argv;
Xchar **arge;
X{
X	int i, j;
X	register char **ep;
X	char *queue = NULL;
X	char tfname[256];
X	char cfname[256];
X	long unique();
X	long spoolnumber;
X	char *shell;
X	FILE *tf;
X	char dir[MAXPATHLEN];
X	struct stat sb;
X	int cleanup();
X
X#ifndef DEBUG
X	if(geteuid() != 0)
X		error("System error-Effective uid is %d, not root\n", getuid());
X#endif
X	/*
X	 *  Find and validate the grade.  a-z, only SU can set it to values
X	 *  lower than 'h'.
X	 */
X	for(i = 1 ; i < argc ; i++) {
X		if(argv[i][0] == '-')
X			switch(argv[i][1]) {
X			case 'P':
X				grade = argv[i][2];
X				break;
X			case 'v':
X				vflag = 1;
X				break;
X			default:
X				Usage();
X			}
X	}
X	if(grade < 'a'  ||  grade > 'z'  ||  (grade < 'h')  &&  getuid())
X		error("Grade `%c' is invalid.\n", grade);
X	/*
X	 *  First non - arg is the queue.
X	 */
X	for(i = 1 ; i < argc ; i++)
X		if(argv[i][0] != '-') {
X			queue = argv[i];
X			break;
X		}
X	if(queue == NULL)
X		Usage();
X	if(signal(SIGINT, SIG_IGN) != SIG_IGN)
X		signal(SIGINT, cleanup);
X	if(signal(SIGHUP, SIG_IGN) != SIG_IGN)
X		signal(SIGHUP, cleanup);
X	if(signal(SIGQUIT, SIG_IGN) != SIG_IGN)
X		signal(SIGQUIT, cleanup);
X	sprintf(tfname, "%s/%.14s", SPOOLDIR, queue);
X	if(stat(tfname, &sb) < 0  ||
X	   (errno=ENOENT, (sb.st_mode & S_IFMT) != S_IFDIR)) {
X		if(errno == ENOENT)
X			error("%s: No such queue\n", queue);
X		else {
X			perror(tfname);
X			error("%s: System error\n", tfname);
X		}
X	}
X	/*
X	 *  Spool file name starts with tf, followed by grade, followed by
X	 *  a unique sequential number.  The files are sorted by this name
X	 *  for priority of execution.
X	 */
X	spoolnumber = unique();
X	sprintf(tfname, tffmt, SPOOLDIR, queue, grade, spoolnumber);
X	deleteerr = tfname;
X	umask(0077);
X	if((tf = fopen(tfname, "w")) == NULL) {
X		perror(tfname);
X		error("%s: System error\n", tfname);
X	}
X	/*
X	 *  Since it will be directly executable by the system put out a
X	 *  system magic word followed by the shell.
X	 *  THESHELL tells which type of environment setting to use.
X	 */
X	if((shell = getenv("SHELL")) == NULL)
X		shell = "/bin/sh";
X	if(strcmp(shell, "/bin/sh") == 0)
X		theshell = BOURNE;
X	else if(strncmp(shell+strlen(shell)-3, "csh", 3) == 0)
X		theshell = CSH;
X	else
X		error("%s: Unrecognized shell in $SHELL variable\n", shell);
X	fprintf(tf, "#!%s\n", shell);
X	/*
X	 *  Set everything up so that it will execute in the same env. as we
X	 *  have right now.  BUG::::  Since we're root, we can get the current
X	 *  directory, but since the chdir back here is done by the user prog
X	 *  later that could fail if he didn't have full perms to get to the
X	 *  current location.   We should at very least mention this to the user
X	 */
X	if(getwd(dir) == NULL)
X		error("getwd: System error: %s\n", dir);
X	if( theshell == CSH )
X		fprintf(tf,"set histchars\n");
X	fprintf(tf, "cd ");
X	putstring(tf, dir);
X	fprintf(tf, "\n");
X	/*
X	 *  Dump the current environment variables.  Ignore them if they
X	 *  don't have = signs, throw away TERMCAP and TERM.
X	 */
X	for(ep = arge ; *ep ; ep++) {
X		register char *p;
X
X		if((p = index(*ep, '=')) == NULL)
X			continue;
X		*p++ = 0;
X		if(strcmp(*ep, "TERMCAP") == 0)
X			continue;
X		if(strcmp(*ep, "TERM") == 0)
X			continue;
X		putenv(tf, *ep, p);
X	}
X	/*
X	 *  If using csh, set $home so the ~ character expands correctly.
X	 */
X	if(theshell == CSH)
X		fprintf(tf, "set home=$HOME\n");
X	/*
X	 *  Dump the files specified; or stdin otherwise.
X	 */
X	j = 0;
X	for(i++ ; i < argc ; i++) {
X		FILE *input;
X
X		if(argv[i][0] == '-')
X			continue;
X		if((input = fopen(argv[i], "r")) == NULL) {
X			perror(argv[i]);
X			cleanup();
X		}
X		concat(tf, input);
X		++j;
X	}
X	if(j == 0)
X		concat(tf, stdin);
X	/*
X	 *  Set everything up for direct execution; and then link it to
X	 *  its real filename.
X	 */
X	fchown(fileno(tf), getuid(), getgid());
X	fchmod(fileno(tf), 0700);
X	fclose(tf);
X	sprintf(cfname, cffmt, SPOOLDIR, queue, grade, spoolnumber);
X	if (rename(tfname, cfname) < 0) {
X		perror(tfname);
X		error("%s: System error\n", tfname);
X	}
X	/*
X	 * Send SIGALRM to the batchd to wake it up.
X	 */
X	if ((tf = fopen(Pid, "r")) != NULL) {
X		if (fscanf(tf, "%d", &i) == 1 && i > 0)
X			(void) kill(i, SIGALRM);
X		fclose(tf);
X	}
X	if(vflag)
X		printf("%s\n", cfname);
X	exit(0);
X}
X
X/*VARARGS1*/
Xerror(s)
Xchar *s;
X{
X	fprintf(stderr, "batch: %r", &s);
X	cleanup();
X}
X
Xcleanup()
X{
X	if(deleteerr != NULL)
X		unlink(deleteerr);
X	exit(1);
X}
X
Xconcat(to, from)
X	register FILE *to, *from;
X{
X	register int c;
X
X	while ((c = getc(from)) != EOF)
X		putc(c, to);
X}
X
X/*
X *  Dump out an environment variable in the format for this shell.
X */
Xputenv(tf, ep, p)
XFILE *tf;
Xchar *ep, *p;
X{
X	switch(theshell) {
X	case BOURNE:
X		fprintf(tf, "%s=", ep);
X		putstring(tf, p );
X		fprintf(tf, "; export %s\n", ep);
X		break;
X	case CSH:
X		fprintf(tf, "setenv %s ", ep);
X		putstring(tf, p );
X		fprintf(tf, "\n");
X		break;
X	}
X}
X
X/*
X *  God help us; put out a string with whatever escapes are needed to make
X *  damn sure that the shell doesn't interpret it.  BOURNE shell doesn't
X *  need escapes on newlines; CSH does.
X */
Xputstring(tf, s)
Xregister FILE *tf;
Xregister char *s;
X{
X	fputc('\'', tf);
X	while(*s) {
X		if(*s == '\n' && theshell == CSH )
X			fputc('\\', tf);
X		if(*s == '\'')
X			s++, fprintf(tf,"'\"'\"'"); /* 'You can'"'"'t come' */
X		else
X			fputc(*s++, tf);
X	}
X	fputc('\'', tf);
X}
X
XUsage()
X{
X	error("Usage: batch [-Pn] queue [file ...]\n");
X}
e-o-f
echo Extracting batchd.8
sed 's/^X//' > batchd.8 << 'e-o-f'
X.TH BATCHD 8 UofW
X.SH NAME
Xbatchd \- Run the batch daemon
X.SH SYNOPSIS
X.B /usr/lib/batchd
X.SH DESCRIPTION
XThe batch daemon can run several independent queues of batch jobs.
XEach subdirectory under its spool directory defines one queue,
Xwith the name of the subdirectory being the name of the queue.
XEach such directory must contain a file named
X\fIprofile\fP which contains information pertaining to the execution
Xof that queue.
X.PP
XEach directory is periodically searched for files beginning with the
Xletters ``cf''.
XCharacters following the cf are used for priority, jobs which compare
Xlower in the ascii collating sequence are executed first.
XThis file must be a directly executable file.
XThe owner and group of this file are set from the submitter of the job;
Xuid and gid are both set from this prior to execution of the file.
XThe standard input of the shell file is from /dev/null; the standard
Xoutput and error output default to being mailed back to the owner.
XFiles with the letters ``of'' are output files corresponding to ``cf''
Xfiles.  When the daemon starts any ``of'' files were assumed to have
Xbeen running at the time of a crash; action is taken per the restart
Xparameter.
X.PP
XThe \fIprofile\fP contains lines of options, each line has keyword value,
Xseparated by whitespace.
XThe following keywords are recognized:
X.IP exec
XIf the option \fIoff\fP is given this queue is not examined.
XIf the \fIoff\fP option is set while job(s)s are running the current job(s)
Xis/are aborted immediately.
XIf \fIdrain\fP is specified the current job(s) is permitted to terminate
Xnormally before stopping the queue.
X.IP maxexec
XA number indicating the maximum number of simultaneous jobs to run follows.
X.IP supervisor
XThe name of a person to be send mail about this queue.
XIf this starts with a slash, it is assumed to be a filename to append to.
X.IP mail
XA combination of the letters \fIsec\fP follow; mail is sent to the
Xowner of a job at the start, end, or restart after crash of a job.
X.IP mailsupervisor
XAs for the \fImail\fP keyword, only mail is sent to the controller of this
Xqueue as specified in the \fIsupervisor\fP keyword.
XThis may be used for a console log if the supervisor is a filename.
XNote that both mail and mailsupervisor default to \fIec\fP.
XIf user mail is turned off the output of the job is still always mailed back to the owner
Xif there is any, or the status of the job is non-zero.
X.IP loadsched
XIf the system 15 minute load average goes over the integer value specified
Xthen no new jobs will be scheduled until it goes under again.
X.IP loadstop
XIf the system 5 minute load average goes over the integer value specified
Xthen all jobs in the queue will be sent stop signals until it again
Xgoes below this value whereupon it will be restarted.
X.IP nice
XA number indicating the nice to run jobs at.
X.IP restart
XIf this keyword is given then if the system crashes while running a job
Xit will be restarted.  If not only an error will be sent to the user.
X.IP time
XFollowed by hh:[mm] hh[:mm] will cause the queue to be active only between
Xthose hours.  There may be multiple entries of this keyword.
X(Not yet implemented.)
X.IP day
XFollowed by a day of the week will cause the queue to be active only on
Xthat day.  There may be multiple entries of this keyword.
X(Not yet implemented.)
X.SH "RESOURCE LIMITS"
X.PP
XThe following words take both one and two numbers.
XIf one number is given, only the current resource limit is set.
XIf two numbers are given, the current and maximum limits are set.
XAs explained in
X.IR setrlimit (2),
Xa process is free to raise its current limit to the maximum limit.
X.IP rlimitcpu
XCurrent and maximum cpu, in seconds.
X.I Cpulimit
Xis also a synonym for this keyword.
X.IP rlimitfsize
XCurrent and maximum file size, in bytes.
X.IP rlimitdata
XCurrent and maximum size of the data area, in bytes.
X.IP rlimitstack
XCurrent and maximum size of the stack, in bytes.
X.IP rlimitcore
XCurrent and maximum size of a core dump file, in bytes.
X.IP rlimitrss
XCurrent and maximum size of the virtual memory working set, in bytes.
X.SH "QUEUE MANAGEMENT"
X.PP
XWhen a batch job is submitted via
X.IR batch (1),
Xa SIGALRM signal is sent to the daemon.
XThe daemon will check
Xqueues which are eligible to run more jobs.
XIt also checks for changed queue profile files.
XIn the absence of any batch activity, this checking is done
Xevery 15 minutes.
XTo wake up the daemon early, send it a SIGALRM signal.
X.PP
XIf a directory disappears all jobs are aborted.
XIf something strange happens to a particular queue that queue will be
Xdrained. (For example, the profile file is changed and has a syntax error.)
X.PP
XThis programme is assumed to have been started by the /etc/rc and have no
Xcontrolling tty; hence all errors are mailed to userid ``batch''.
X.SH "SEE ALSO"
Xbatch(1).
X.SH FILES
X.nf
X/usr/spool/batch	spool directory
X/usr/lib/batchd	batch daemon executable
X/usr/bin/batch	batch job submitter executable
X/usr/spool/batch/.cleanup	Cleanup script run by cron
X/usr/spool/pid/batchd	Process ID of batchd
X.fi
e-o-f
echo Extracting batchd.c
sed 's/^X//' > batchd.c << 'e-o-f'
X/*
X *  Batch daemon
X */
X#include <sys/param.h>
X#include <sys/stat.h>
X#include <sys/time.h>
X#include <sys/resource.h>
X#include <sys/ioctl.h>
X#include <sys/wait.h>
X#include <sys/file.h>
X#include <sys/dir.h>
X#include <stdio.h>
X#include <nlist.h>
X#include <errno.h>
X#include <ctype.h>
X#include <signal.h>
X#include <pwd.h>
X#include "lex.h"
X/*
X * For 4.2bsd signals.
X */
X#define	mask(s)		(1 << ((s)-1))
X#define	sighold(s)	sigblock(mask(s))
X#define	sigrelse(s)	sigsetmask(sigblock(0)&~mask(s))
X
X#undef DIRSIZ
X#define	DIRSIZ	32	/* Used to be 14; could be MAXNAMLEN... -IAN! */
X
X#define SPOOLDIR	"/usr/spool/batch"
X#define	PROFILE		"profile"
Xchar Pid[] = "/usr/spool/pid/batchd";
X#define	NQUEUES		16	/* Max # of queues */
X#define	MAXENTRIES	50	/* Max # per queue */
X#define MAXTOTAL	50	/* Max # of total jobs */
X#ifdef DEBUG
X#define DEBUGFILE	"/usr/spool/batch/troff/debug"
X#define	NSEC	10
X#define STATIC
X#else
X#define	NSEC	(15*60)
X#define STATIC	static
X#endif
X
XSTATIC int nkids;
X
XSTATIC struct stat dot, odot;	/* Start of spooldir */
XSTATIC int alarmed;
X
Xtypedef char MAIL;
X#define	MAIL_START	1
X#define	MAIL_END	2
X#define	MAIL_CRASH	4
X
Xtypedef struct {
X	char	f_name[DIRSIZ+1];
X	struct stat f_stat;
X	int	f_pid;
X	char	f_seen;
X} CF_File;
X
X#define NRLIM 6		/* Number of resources, e.g. RLIMIT_CPU */
X
Xtypedef struct {
X	char	q_name[DIRSIZ+1];
X	CF_File q_files[MAXENTRIES];/* Sorted list of files */
X	int	q_running;	/* If not set, queue is not active */
X	int	q_idle;		/* If set, queue doesn't have anything in it */
X	int	q_drain;	/* Set if shutdown started */
X	int	q_abort;	/* Set if waiting for aborting */
X	int	q_stopped;	/* Set if queue stopped for lower load */
X	struct	stat q_stat, q_ostat;	/* Stat of queue directory */
X	struct	stat q_prostat, q_oprostat; /* Stat of queue profile file */
X	char	*q_supervisor;	/* Queue supervisor */
X	MAIL	q_supmail;	/* Type of mail for supervisor */
X	MAIL	q_usermail;	/* Type of notification for user */
X	int	q_maxexec;	/* Max # of executing jobs */
X	int	q_nexec;	/* Current # of executing jobs */
X	int	q_loadsched;	/* Stop scheduling new jobs if over this load */
X	int	q_loadstop;	/* Stop jobs if over this load */
X	int	q_nice;		/* Job priority */
X	int	q_restart;	/* Restart on system crash? */
X	int	q_pgroup;	/* Process group for this queue */
X	struct rlimit q_rlimit[NRLIM];	/* Resource hard/soft limits */
X#ifdef NOT_IMPLEMENTED
X	/* Remainder not yet implemented */
X	struct {
X		time_t	q_starttime;	/* Start queue at */
X		time_t	q_endtime;	/* End queue at */
X	} q_times[5];
X	char	q_days[7];	/* Day to activate on, sun = 0 */
X#endif NOT_IMPLEMENTED
X} Queue;
XSTATIC Queue queues[NQUEUES];
X
Xtypedef struct {
X	int pid;
X	Queue *rqp;
X	CF_File *rfp;
X} Running;
XSTATIC Running running[MAXTOTAL];
X
XSTATIC int ignore[] = { SIGINT, SIGHUP, SIGQUIT, SIGSYS, SIGPIPE, SIGTERM };
X
Xextern errno;
Xextern char *calloc(), *strcpy(), *strncpy();
X
Xmain()
X{
X	register Queue *qp;
X	int alarming(), reapchild();
X	char fname[DIRSIZ*2];
X	int i, dotu;
X	FILE *f;
X	extern char **environ;
X	static char *env[] = { "PATH=/bin", 0 };
X
X#ifndef DEBUG
X	if(getuid() != 0)
X		error("Real uid is %d, not root\n", getuid());
X	if(geteuid() != 0)
X		error("Effective uid is %d, not root\n", geteuid());
X#endif
X	if(chdir(SPOOLDIR) < 0)
X		pxerror(SPOOLDIR);
X	dotu = open(".", O_RDONLY, 0);
X	if (dotu < 0)
X		pxerror("open .");
X	if(fstat(dotu, &dot) < 0)
X		pxerror("stat of dot");
X#ifndef DEBUG
X	if(dot.st_uid != 0)
X		error("%s: Not owned by root\n", SPOOLDIR);
X#endif
X	if(dot.st_mode & (S_IWRITE>>6))
X		error("%s: Mode %03o allows general write\n", SPOOLDIR, dot.st_mode);
X#ifndef DEBUG
X	
X	/* Log my pid */
X	if (access(Pid, R_OK) == 0)
X		error("%s exists.  Batchd probably already running. Please check.\n", Pid ) ;
X	if (f = fopen(Pid, "w")) {
X		fprintf(f, "%d\n", getpid());
X		fclose(f);
X	}
X	for (i = 0; i < sizeof ignore/sizeof ignore[0]; i++)
X		signal(ignore[i], SIG_IGN);
X	nice(-40);
X	nice(20);
X	nice(0);
X	/*
X	 *  Get rid of controlling tty
X	 */
X	if((i = open("/dev/tty", 2)) != -1) {
X		ioctl(i, TIOCNOTTY, 0);
X		close(i);
X	}
X#endif
X
X	/*
X	 *  Get rid of most current environment variables.
X	 */
X	environ = env;
X
X	signal(SIGALRM, alarming);
X	signal(SIGCHLD, reapchild);
X	for(;;) {
X		/*
X		 *  Check if the main spool directory changed.  If so, make
X		 *  a new list of potential queue entries.
X		 */
X		if(fstat(dotu, &dot) < 0)
X			pxerror("statting dot");
X		if(dot.st_mtime > odot.st_mtime) {
X			readqueues();
X			odot = dot;
X		}
X		/*
X		 *  Now check each potential queue entry.
X		 */
X		for(qp = queues ; qp < &queues[NQUEUES] ; qp++) {
X			if(qp->q_name[0] == 0)
X				continue;
X			/*
X			 *  Check if its profile file changed; if so re-read it.
X			 *  Be generous, if somebody deleted it don't affect
X			 *  the queue or whats runing.
X			 */
X			sprintf(fname, "%s/%s", qp->q_name, PROFILE);
X			if(stat(fname, &qp->q_prostat) < 0) {
X				pmerror(fname);
X				continue;
X			}
X			if(qp->q_prostat.st_mtime > qp->q_oprostat.st_mtime) {
X				readpro(qp);
X				qp->q_oprostat = qp->q_prostat;
X			}
X			/*
X			 *  If profile file said exec off, ignore.
X			 *  If still draining then we have to call runqueue
X			 *  still for purposes of starting and stopping
X			 */
X			if(qp->q_drain) {
X				runqueue(qp);
X				continue;
X			}
X			if(qp->q_running == 0)
X				continue;
X			/*
X			 *  Now check the directory itself.  If it changed,
X			 *  then something new might have been queued.
X			 */
X			if(stat(qp->q_name, &qp->q_stat) < 0) {
X				pmerror(fname);
X				continue;
X			}
X			if(qp->q_stat.st_mtime > qp->q_ostat.st_mtime) {
X				requeue(qp);
X				qp->q_ostat = qp->q_stat;
X			}
X			/*
X			 *  Now, start something running in queue if it can.
X			 */
X			runqueue(qp);
X		}
X		/*
X		 *  Now wait for children until either there are none; or
X		 *  NSEC seconds have gone by.
X		 */
X		alarm(NSEC);
X		alarmed = 0;
X		while (!alarmed)
X			pause();
X		alarm(0);
X#ifdef DEBUG
X		muser(DEBUGFILE, "looking around...\n");
X#endif DEBUG
X	}
X}
X
XSTATIC
Xreapchild()
X{
X	union wait status;
X	int pid;
X
X	while ((pid = wait3(&status, WNOHANG, (struct rusage *)0)) > 0) {
X#ifdef DEBUG
X		muser(DEBUGFILE, "wait3 exit pid=%d, stat=%o\n",
X			pid, status.w_status);
X#endif DEBUG
X		terminated(pid, status.w_status);
X	}
X}
X
XSTATIC
Xalarming()
X{
X	++alarmed;
X#ifdef DEBUG
X	muser(DEBUGFILE, "How alarming\n");
X#endif DEBUG
X}
X
X/*VARARGS1*/
XSTATIC
Xerror(s)
Xchar *s;
X{
X	FILE *mail, *sendmail();
X
X	mail = sendmail("batch");
X	fprintf(mail, "Fatal error; batch terminating\n%r", &s);
X	mailclose(mail);
X#ifdef DEBUG
X	abort();
X#else
X	exit(1);
X#endif
X}
X
X#if 0
X/*VARARGS1*/
XSTATIC
Xpanic(s)
Xchar *s;
X{
X	error("%r", &s);
X}
X#endif 0
X
Xextern int	sys_nerr;
Xextern char	*sys_errlist[];
XSTATIC
Xpxerror(s)
Xchar *s;
X{
X	register char *c;
X
X	c = "Unknown error";
X	if(errno < sys_nerr)
X		c = sys_errlist[errno];
X	error("%s: %s\n", s, c);
X}
X
XSTATIC
Xpmerror(s)
Xchar *s;
X{
X	register char *c;
X	FILE *mail, *sendmail();
X	int err;
X
X	err = errno;
X	mail = sendmail("batch");
X	c = "Unknown error";
X	if(err < sys_nerr)
X		c = sys_errlist[err];
X	fprintf(mail, "%s: %s\n", s, c);
X	mailclose(mail);
X}
X
X/*VARARGS1*/
XSTATIC
Xmerror(s)
Xchar *s;
X{
X	FILE *mail, *sendmail();
X
X	mail = sendmail("batch");
X	fprintf(mail, "%r", &s);
X	mailclose(mail);
X}
X
X/*VARARGS2*/
XSTATIC
Xmuser(s, m)
Xchar *s, *m;
X{
X	FILE *mail;
X	FILE *sendmail();
X	time_t now, time();
X	char *ctime();
X
X	if(s == NULL)
X		return;
X	if(*s == '/') {
X		if((mail = fopen(s, "a")) == NULL) {
X			pmerror(s);
X			mail = stderr;
X		}
X		now = time(0);
X		fprintf(mail, "%-15.15s ", ctime(&now) + 4);
X	} else
X		mail = sendmail(s);
X	fprintf(mail, "%r", &m);
X	mailclose(mail);
X}
X
X#if 0
XSTATIC FILE *
Xsendmail(s)
X{
X	fprintf(stderr, "%s: ", s);
X	return stderr;
X}
XSTATIC
Xmailclose(file)
XFILE *file;
X{
X	if(file != stderr)
X		fclose(file);
X}
X#else
XSTATIC FILE *
Xsendmail(s)
Xchar *s;
X{
X	FILE *mailer;
X	int pid;
X	struct {int read; int write;} mailp;
X	register Running *rp;
X
X	if(pipe(&mailp) < 0) {
X	err:
X		fprintf(stderr, "%s: ", s);
X		return stderr;
X	}
X	switch(pid = vfork()) {
X	case 0:
X		dup2(mailp.read, fileno(stdin));
X		close(mailp.write);
X		nice(4);	/* lower priority */
X		execl("/usr/lib/sendmail", "sendmail", s, (char *)0);
X		perror("BATCHD execl sendmail!");
X		exit(1);
X	case -1:
X		goto err;
X	default:
X		close(mailp.read);
X	}
X	nkids++;
X	for(rp = running ; rp < &running[MAXTOTAL] ; rp++)
X		if(rp->pid == 0) {
X			rp->pid = pid;
X			rp->rqp = NULL;
X			rp->rfp = NULL;
X			break;
X		}
X	if(rp >= &running[MAXTOTAL])
X		fprintf(stderr, "BATCHD sendmail: too many jobs\n");
X	mailer = fdopen(mailp.write, "w");
X	if(mailer == NULL) {
X		close(mailp.write);
X		perror("BATCHD fdopen");
X		goto err;
X	}
X	fprintf(mailer, "To: %s\nFrom: batchd (Batch Daemon)\n\n", s);
X	return mailer;
X}
X
XSTATIC
Xmailclose(f)
XFILE *f;
X{
X	fclose(f);
X}
X#endif
X
X/*
X *  Read the home directory, looking for all current queue entries.
X */
XSTATIC
Xreadqueues()
X{
X	register Queue *qp, *nextq;
X	register i;
X	static DIR *dotp;
X	struct direct *d;
X	char seen[NQUEUES];
X	struct stat sb;
X	register CF_File *fp;
X
X	if(dotp == NULL) {
X		if((dotp = opendir(".")) == NULL)
X			pxerror("opendir dot");
X	} else
X		rewinddir(dotp);
X	bzero(seen, sizeof seen);
X	while((d = readdir(dotp)) != NULL) {
X		/*
X		 *  Ignore everything starting with a dot.
X		 */
X		if(*d->d_name == '.')
X			continue;
X		if(stat(d->d_name, &sb) == -1) {
X			pmerror(d->d_name);
X			continue;
X		}
X		if((sb.st_mode & S_IFMT) != S_IFDIR) {
X			errno = ENOTDIR;
X			pmerror(d->d_name);
X			continue;
X		}
X		/*
X		 *  Found a directory.
X		 */
X		nextq = NULL;
X		for(qp = queues ; qp < &queues[NQUEUES] ; qp++) {
X			if(strcmp(d->d_name, qp->q_name) == 0)
X				break;
X			if(nextq == NULL  &&  qp->q_name[0] == 0)
X				nextq = qp;
X		}
X		/*
X		 *  Was it a new queue?
X		 */
X		if(qp >= &queues[NQUEUES]) {
X			if(nextq == NULL) {
X				merror("More than %d queues at %s\n",
X					NQUEUES, d->d_name);
X				continue;
X			}
X			qp = nextq;
X			strncpy(qp->q_name, d->d_name, DIRSIZ);
X			qp->q_nexec = 0;
X			qp->q_stopped = 0;
X			qp->q_ostat.st_mtime = qp->q_oprostat.st_mtime = 0;
X			for(fp = qp->q_files ; fp < &qp->q_files[MAXENTRIES] ; fp++)
X				fp->f_name[0] = 0;
X		}
X		seen[qp-queues] = 1;
X	}
X	/*
X	 *  Any directories which have been deleted imply the queues should
X	 *  end violently since we can't get the output files..
X	 */
X	for(i = 0 ; i < NQUEUES ; i++)
X		if(seen[i] == 0  &&  queues[i].q_name[0]) {
X			if(qp->q_nexec) {
X				abortall(&queues[i]);
X				qp->q_abort = 1;
X			} else {
X				qp->q_name[0] = 0;
X				qp->q_pgroup = 0;
X			}
X		}
X}
X
X/*
X *  Read a profile file under the given queue entry.
X */
XSTATIC
Xreadpro(qp)
Xregister Queue *qp;
X{
X	static struct rlimit minus_one = { -1, -1 };
X	register int i;
X	int run;
X	FILE *pro;
X	char pname[DIRSIZ*2];
X	enum keyword yylex();
X	enum keyword k;
X	extern char yytext[];
X	MAIL *mailp;
X
X	/*
X	 *  First set up queue to all defaults so if we re-read a file
X	 *  we don't get the old values.
X	 */
X	qp->q_supervisor = NULL;
X	qp->q_usermail = qp->q_supmail = MAIL_END|MAIL_CRASH;
X	qp->q_nice = 0;
X	qp->q_restart = 0;
X	for( i=0; i<NRLIM; i++ )
X		qp->q_rlimit[i] = minus_one;
X#ifdef NOT_IMPLEMENTED
X	qp->q_times[0].q_starttime = 0;
X	qp->q_days[0] = qp->q_days[1] = qp->q_days[2] = qp->q_days[3] =
X		qp->q_days[4] = qp->q_days[5] = qp->q_days[6] = 0;
X#endif NOT_IMPLEMENTED
X	qp->q_drain = qp->q_abort = 0;
X	qp->q_maxexec = 1;
X	qp->q_idle = 0;
X	qp->q_loadsched = 25;	/* Incredibly high! if not specified */
X	qp->q_loadstop = 50;
X	if(qp->q_pgroup == 0) {
X		register Queue *qp1;
X		static int nextpg = 31100;	/* Random number higher than
X						   system allocates for procs*/
X		again:
X		for(qp1 = queues ; qp1 < &queues[NQUEUES] ; qp1++)
X			if(qp1->q_pgroup == nextpg) {
X				nextpg++;
X				goto again;
X			}
X		qp->q_pgroup = nextpg++;
X		if(nextpg > 31200)
X			nextpg = 31100;
X	}
X	run = 1;	/* Default is start running */
X
X	/*
X	 *  Try to read the profile file.  If not there, assume shutdown this
X	 *  queue.
X	 */
X	sprintf(pname, "%s/%s", qp->q_name, PROFILE);
X	if((pro = fopen(pname, "r")) == NULL) {
X		pxerror(pname);
X		rundown(qp);
X		return;
X	}
X	lexfile(pro);
X#define	syntax	goto syntaxerr
X#define	lex	(k = yylex())
X	while((int)lex) {
X		register int i;
X		register int rl;
X		register int limit;
X		switch(k) {
X		case K_LINE:
X			break;
X		case K_EXEC:
X			switch(lex) {
X			case K_OFF:
X				run = 0;
X				break;
X			case K_LINE:
X			case K_ON:
X				run = 1;
X				break;
X			case K_DRAIN:
X				run = 0;
X				qp->q_drain = 1;
X				break;
X			default:
X				syntax;
X			}
X			break;
X		case K_MAXEXEC:
X			if(lex != K_NUMBER)
X				syntax;
X			qp->q_maxexec = atoi(yytext);
X			break;
X		case K_SUPERVISOR:
X			if(lex != K_VARIABLE)
X				syntax;
X			qp->q_supervisor = calloc(1, strlen(yytext)+1);
X			strcpy(qp->q_supervisor, yytext);
X			break;
X		case K_MAIL:
X			mailp = &qp->q_usermail;
X		mailstuff:
X			*mailp = 0;
X			if(lex == K_LINE)
X				break;
X			if(k != K_VARIABLE)
X				syntax;
X			if(index(yytext, 's'))
X				*mailp |= MAIL_START;
X			if(index(yytext, 'e'))
X				*mailp |= MAIL_END;
X			if(index(yytext, 'c'))
X				*mailp |= MAIL_CRASH;
X			break;
X		case K_MAILSUPERVISOR:
X			mailp = &qp->q_supmail;
X			goto mailstuff;
X		case K_LOADSCHED:
X			if(lex != K_NUMBER)
X				syntax;
X			qp->q_loadsched = atoi(yytext);
X			break;
X		case K_LOADSTOP:
X			if(lex != K_NUMBER)
X				syntax;
X			qp->q_loadstop = atoi(yytext);
X			break;
X		case K_NICE:
X			if(lex != K_NUMBER)
X				syntax;
X			qp->q_nice = atoi(yytext);
X			break;
X		case K_RESTART:
X			qp->q_restart = 1;
X			break;
X		case K_CPULIMIT:
X		case K_RLIMITCPU:
X		case K_RLIMITFSIZE:
X		case K_RLIMITDATA:
X		case K_RLIMITSTACK:
X		case K_RLIMITCORE:
X		case K_RLIMITRSS:
X			/* If only one number is given, we set only the
X			 * current limit.  If two numbers, we set current and
X			 * maximum limits.   -IAN!
X			 */
X			i = rltoi(rl = Ktorl(k));/* index in q_rlimit array */
X			if(lex != K_NUMBER)
X				syntax;
X			limit = atoi(yytext);
X			if(lex == K_LINE){
X				if(getrlimit(rl,&(qp->q_rlimit[i])) != 0){
X					pmerror("getrlimit(%d,%d)\n",
X					    rl,&(qp->q_rlimit[i]));
X					break;
X				}
X			}
X			qp->q_rlimit[i].rlim_cur = limit;
X			if(k == K_LINE)
X				break;
X			if(k != K_NUMBER)
X				syntax;
X			qp->q_rlimit[i].rlim_max = atoi(yytext);
X			break;
X		default:
X			syntax;
X		}
X		if(k != K_LINE)
X			if(yylex() != K_LINE)
X				syntax;
X	}
X	fclose(pro);
X	if(run == 0)
X		if(qp->q_drain)
X			rundown(qp);
X		else
X			abortall(qp);
X	qp->q_running = run;
X	return;
X
Xsyntaxerr:
X	merror("%s: Syntax error near %s\n", qp->q_name, yytext);
X	rundown(qp);
X}
X
X/*
X *  Read the directory for a queue and find all jobs ready to run.
X */
XSTATIC
Xrequeue(qp)
Xregister Queue *qp;
X{
X	DIR *qdir;
X	struct direct *d;
X	char procname[50];
X	register CF_File *fp, *nextf;
X	int cmp();
X	static int startup = 1;
X
X	for(fp = qp->q_files ; fp < &qp->q_files[MAXENTRIES] ; fp++)
X		fp->f_seen = 0;
X	if((qdir = opendir(qp->q_name)) == NULL) {
X		pmerror(qp->q_name);
X		rundown(qp);
X		return;
X	}
X	while((d = readdir(qdir)) != NULL) {
X		if(strncmp(d->d_name, "cf", 2) != 0)
X			continue;
X		nextf = NULL;
X		for(fp = qp->q_files ; fp < &qp->q_files[MAXENTRIES] ; fp++) {
X			if(fp->f_name[0] == 0) {
X				/* Found the first empty slot */
X				if(nextf == NULL)
X					nextf = fp;
X				continue;
X			}
X			if(strcmp(d->d_name, fp->f_name) == 0)
X				break;
X		}
X		/*
X		 * If we ran off the end of the table, we didn't
X		 * find it and it must be a new entry.
X		 */
X		if(fp >= &qp->q_files[MAXENTRIES]) {
X			if(nextf == NULL) {
X				muser(qp->q_supervisor,
X				   "%s: More than %d entries; %s ignored\n",
X				   qp->q_name, MAXENTRIES, d->d_name);
X				continue;
X			}
X			sprintf(procname, "%s/%s", qp->q_name, d->d_name);
X			fp = nextf;
X			if(stat(procname, &fp->f_stat) == -1) {
X				pmerror(procname);
X				continue;
X			}
X			strncpy(fp->f_name, d->d_name, DIRSIZ);
X			fp->f_pid = 0;
X		}
X		fp->f_seen = 1;
X	}
X	/*
X	 * Look at our internal table and check that every job we
X	 * know about is still in the directory.  If one has been
X	 * deleted and is currently running, abort it; otherwise
X	 * just remove the job from the table.
X	 */
X	for(fp = qp->q_files ; fp < &qp->q_files[MAXENTRIES] ; fp++){
X		if(fp->f_seen == 0  &&  fp->f_name[0])
X			if(fp->f_pid)
X				abortjob(fp);
X			else
X				fp->f_name[0] = 0;
X	}
X	/*
X	 * Check for restarted jobs.  This is done exactly once, when
X	 * BATCHD gets started after a reboot.
X	 */
X	if(startup) {
X		startup = 0;
X		rewinddir(qdir);
X		while((d = readdir(qdir)) != NULL) {
X			if(strncmp(d->d_name, "of", 2) != 0)
X				continue;
X			for(fp = qp->q_files ; fp < &qp->q_files[MAXENTRIES] ; fp++)
X				if(strcmp(d->d_name+1, fp->f_name+1) == 0) {
X					char cfname[DIRSIZ*2];
X					mailback(0, qp, fp, MAIL_CRASH,
X					qp->q_restart?"restarted":"not restarted");
X					if(qp->q_restart)
X						break;
X					/*
X					 *  If restart disallowed, delete file
X					 */
X					sprintf(cfname, "%s/%s", qp->q_name, fp->f_name);
X					if(unlink(cfname) == -1) {
X						int e = errno;
X
X						pmerror(cfname);
X						muser(qp->q_supervisor,
X						    "%s: Can't unlink dead job%s",
X						    qp->q_name,
X							e==ENOENT? "":
X							"; stopping queue");
X						if (e != ENOENT)
X							rundown(qp);
X					}
X					fp->f_name[0] = 0;
X					break;
X				}
X			/*
X			 *  Left over output files with no control files??
X			 */
X			if(fp >= &qp->q_files[MAXENTRIES]) {
X				char ofname[DIRSIZ*2];
X				sprintf(ofname, "%s/%s", qp->q_name, d->d_name);
X				unlink(ofname);
X				muser(qp->q_supervisor, "%s: Old output file deleted\n", ofname);
X			}
X		}
X	}
X	closedir(qdir);
X	qp->q_idle = 0;
X}
X
X/*
X *  A queue that might have changed.
X *  Wander along it and start jobs if possible.
X *  If we couldn't fork we just return; in 30 seconds we'll try again.
X */
XSTATIC
Xrunqueue(qp)
Xregister Queue *qp;
X{
X	register CF_File *fp;
X	register CF_File *bestfp;
X	int load5;
X
X	load5 = load(1);
X	if(qp->q_stopped  &&  load5 < qp->q_loadstop) {
X		startall(qp);
X		qp->q_stopped = 0;
X	}
X	if(qp->q_stopped == 0  &&  load5 >= qp->q_loadstop) {
X		stopall(qp);
X		qp->q_stopped = 1;
X	}
X	if(qp->q_stopped)
X		return;
X	if(qp->q_drain  ||  qp->q_running == 0  ||  qp->q_idle  ||
X	   load(2) >= qp->q_loadsched)
X		return;
X	qp->q_idle = 1;
X	while(qp->q_nexec < qp->q_maxexec) {
X		bestfp = NULL;
X		for(fp = qp->q_files ; fp < &qp->q_files[MAXENTRIES] ; fp++)
X			if(fp->f_name[0]  &&  fp->f_pid == 0) {
X				if(bestfp == NULL ||
X				   strcmp(bestfp->f_name+2, fp->f_name+2) > 0)
X					bestfp = fp;
X			}
X		if(bestfp != NULL) {
X			if(startjob(qp, bestfp) == 0) {
X				qp->q_idle = 0;
X				return;
X			}
X		} else
X			break;
X	}
X	qp->q_idle = 1;
X	return;
X}
X
X/*
X *  Start an actual job executing.
X */
XSTATIC
Xstartjob(qp, fp)
Xregister CF_File *fp;
Xregister Queue *qp;
X{
X	int pid;
X	int i;
X	char outname[50];
X	char procname[50];
X	int fd;
X	register Running *rp;
X	struct passwd *pw, *getpwuid();
X
X	sprintf(procname, "%s/%s", qp->q_name, fp->f_name);
X	if((pw = getpwuid(fp->f_stat.st_uid)) == NULL)
X		return 1;	/* ? */
X	sighold(SIGCHLD);		/* until in parent table */
X	if((pid = fork()) == -1) {
X		sigrelse(SIGCHLD);
X		pmerror("forking");
X		return 0;
X	}
X	if(pid) {
X#ifdef DEBUG
X		muser(DEBUGFILE, "Job %s, pid %d\n", procname, pid);
X#endif DEBUG
X		nkids++;
X		qp->q_nexec++;
X		fp->f_pid = pid;
X		for(rp = running ; rp < &running[MAXTOTAL] ; rp++)
X			if(rp->pid == 0) {
X				rp->pid = pid;
X				rp->rqp = qp;
X				rp->rfp = fp;
X				break;
X			}
X		sigrelse(SIGCHLD);	/* job is safely in table now */
X		if(rp >= &running[MAXTOTAL])
X			merror("More than %d jobs; too many\n", MAXTOTAL );
X		if(qp->q_usermail & MAIL_START)
X		muser(pw->pw_name, "%s: Job is starting now.\n", procname);
X		if(qp->q_supmail & MAIL_START)
X		muser(qp->q_supervisor, "%s: Job starting for %s\n", procname, pw->pw_name);
X		return 1;
X	}
X	sigrelse(SIGCHLD);	/* for symmetry and completeness */
X	/*
X	 *  Child. Setuid to the owner and setup
X	 *  i/o redirection stuff to mail it back.
X	 */
X	setpgrp(0, qp->q_pgroup);
X	fp->f_name[0] = 'o';	/* Output to `of' file name */
X	sprintf(outname, "%s/%s", qp->q_name, fp->f_name);
X	if((fd = creat(outname, 0600)) < 0)
X		pxerror(outname);
X	dup2(fd, 1);
X	dup2(fd, 2);
X	for(i=3 ; i < NOFILE ; i++)
X		close(i);
X	close(0);
X	open("/dev/null", 0);
X	for( i=0; i<NRLIM; i++ ){
X		register struct rlimit *rlp = &(qp->q_rlimit[i]);
X		if( rlp->rlim_cur >= 0 && rlp->rlim_max >= 0 )
X			setrlimit( itorl(i), rlp );
X	}
X	nice(qp->q_nice);
X	initgroups(pw->pw_name, fp->f_stat.st_gid);
X	setgid(fp->f_stat.st_gid);
X	setuid(fp->f_stat.st_uid);
X	for(i=1 ; i <= NSIG ; i++)
X		signal(i, SIG_DFL);
X	execl(procname, procname, (char *)0);
X	pxerror(procname);
X	/*NOTREACHED*/
X}
X
X/* Turn RLIMIT manifest number into Integer.
X */
XSTATIC int
Xrltoi( rl )
Xint rl;
X{
X	switch( rl ){
X	case RLIMIT_CPU: return 0;
X	case RLIMIT_FSIZE: return 1;
X	case RLIMIT_DATA: return 2;
X	case RLIMIT_STACK: return 3;
X	case RLIMIT_CORE: return 4;
X	case RLIMIT_RSS: return 5;
X	}
X	error("rltoi(%d) invalid\n", rl );
X	/*NOTREACHED*/
X}
X
X/* Turn K token from LEX into RLIMIT number.
X */
XSTATIC int
XKtorl( k )
Xenum keyword k;
X{
X	switch( k ){
X	case K_CPULIMIT: return(RLIMIT_CPU);
X	case K_RLIMITCPU: return(RLIMIT_CPU);
X	case K_RLIMITFSIZE: return(RLIMIT_FSIZE);
X	case K_RLIMITDATA: return(RLIMIT_DATA);
X	case K_RLIMITSTACK: return(RLIMIT_STACK);
X	case K_RLIMITCORE: return(RLIMIT_CORE);
X	case K_RLIMITRSS: return(RLIMIT_RSS);
X	}
X	error("Ktorl(%d) invalid\n", k );
X	/*NOTREACHED*/
X}
X
X/* Turn Integer into RLIMIT number.
X */
XSTATIC int
Xitorl( i )
Xint i;
X{
X	switch( i ){
X	case 0: return(RLIMIT_CPU);
X	case 1: return(RLIMIT_FSIZE);
X	case 2: return(RLIMIT_DATA);
X	case 3: return(RLIMIT_STACK);
X	case 4: return(RLIMIT_CORE);
X	case 5: return(RLIMIT_RSS);
X	}
X	error("itorl(%d) invalid\n", i );
X	/*NOTREACHED*/
X}
X
X/*
X *  A process terminated.
X */
XSTATIC
Xterminated(pid, status)
X{
X	register Running *rp;
X	register Queue *qp;
X	register CF_File *fp;
X	char cfname[DIRSIZ*2];
X
X	for(rp = running ; rp < &running[MAXTOTAL] ; rp++)
X		if(rp->pid == pid)
X			break;
X	if(rp >= &running[MAXTOTAL]) {
X		/* This should never happen, provided SIGCHLD is
X		 * properly held from before the fork() until after
X		 * the process pid is put in the run table.   -IAN!
X		 */
X		merror("Signal %d from process %d but process not in internal table\n",
X			status, pid);
X		return;
X	}
X	nkids--;
X	qp = rp->rqp;
X	/*  Assume this is return of a mail process */
X	if(qp == NULL) {
X		rp->pid = 0;
X		return;
X	}
X	fp = rp->rfp;
X	mailback(status, qp, fp, MAIL_END, (char *)0);
X	if(qp->q_supmail & MAIL_END)
X	muser(qp->q_supervisor, "%s/%s: Job ended, status %o\n", qp->q_name, fp->f_name, status);
X	sprintf(cfname, "%s/%s", qp->q_name, fp->f_name);
X	if(unlink(cfname) == -1) {
X		int e = errno;
X		
X		pmerror(cfname);
X		muser(qp->q_supervisor,
X		    "%s: Can't unlink jobs%s\n", qp->q_name,
X			e==ENOENT? "": "; stopping queue");
X		if (e != ENOENT)
X			rundown(qp);
X	}
X	fp->f_name[0] = 0;
X	fp->f_pid = 0;
X	rp->pid = 0;
X	qp->q_nexec--;
X	if(qp->q_nexec < 0)
X		error("nexec < 0!\n");
X	/*
X	 *  Finally all gone??
X	 */
X	if(qp->q_abort)
X		if(qp->q_nexec == 0) {
X			qp->q_name[0] = 0;
X			qp->q_pgroup = 0;
X		}
X	/*
X	 *  Drained?
X	 */
X	if(qp->q_drain  &&  qp->q_nexec == 0) {
X		qp->q_drain = 0;
X		muser(qp->q_supervisor, "%s: Drained finally\n", qp->q_name);
X	}
X	qp->q_idle = 0;
X	runqueue(qp);
X}
X
XSTATIC
Xmailback(status, qp, fp, mailstat, expl)
XQueue *qp;
XCF_File *fp;
XMAIL mailstat;
Xchar *expl;
X{
X	FILE *mail, *sendmail();
X	char buf[BUFSIZ];
X	char outname[DIRSIZ*2];
X	int n;
X	int output;
X	int outstat;
X	struct stat sb;
X	struct passwd *pw, *getpwuid();
X	static char *faults[] = {
X		"hangup",
X		"interrupt",
X		"quit",
X		"illegal instruction",
X		"trace trap",
X		"IOT instruction",
X		"EMT instruction",
X		"floating point exception",
X		"killed utterly",
X		"bus error",
X		"segmentation violation",
X		"bad arg to system call",
X		"broken pipe",
X		"alarm",
X		"terminate",
X		"signal 16",
X		"stop",
X		"keyboard stop",
X		"continue",
X		"child status",
X		"background read",
X		"background write",
X		"input available",
X		"cpu time limit exceeded",
X		"file size limit exceeded",
X		"signal 26",
X		"signal 27",
X		"signal 28",
X		"signal 29",
X		"signal 30",
X		"signal 31",
X		"signal 32",
X	};
X
X	fp->f_name[0] = 'o';
X	sprintf(outname, "%s/%s", qp->q_name, fp->f_name);
X	fp->f_name[0] = 'c';
X	outstat = stat(outname, &sb);
X	/*
X	 *  Send mail even if we aren't supposed to if there is output
X	 *  or status is non-null.
X	 */
X	if((qp->q_usermail & mailstat) == 0  &&
X	   (outstat==-1 || sb.st_size==0)  &&
X	   status == 0) {
X		if(unlink(outname) < 0)
X			merror(outname);
X		return;
X	}
X	if((pw = getpwuid(fp->f_stat.st_uid)) == NULL) {
X		merror("Can't mail %s to %d\n", outname, fp->f_stat.st_uid);
X		return;
X	}
X	/*
X	 * If submitter is root, mail to $USER.
X	 * This ain't pretty...
X	 */
X	if (pw->pw_uid == 0) {
X		FILE *f;
X		char *rindex();
X
X		rindex(outname, '/')[1] = 'c';
X		if ((f = fopen(outname, "r")) != NULL) {
X			char *p, *up = NULL;
X			struct passwd *xpw, savepw;
X			
X			while (fgets(buf, sizeof buf, f) != NULL) {
X				if (strncmp(buf, "USER=", 5) == 0) {
X					up = buf+5;
X					break;
X				}
X				if (strncmp(buf, "setenv USER ", 12) == 0) {
X					up = buf+12;
X					break;
X				}
X			}
X			fclose(f);
X			if (up == NULL)
X				goto nope;
X			/* crack 'user' */
X			while (*up && *up != '\'')
X				up++;
X			if (*up == 0)
X				goto nope;
X			p = ++up;
X			while (*p && *p != '\'')
X				p++;
X			if (*p == 0)
X				goto nope;
X			*p = 0;
X			savepw = *pw;
X			xpw = getpwnam(up);
X			if (xpw != NULL)
X				pw = xpw;
X			else
X				*pw = savepw;
X		}
X	   nope:
X		rindex(outname, '/')[1] = 'o';
X	}
X	mail = sendmail(pw->pw_name);
X	fprintf(mail, "Job %s in %s queue has ", fp->f_name, qp->q_name);
X	if(mailstat & MAIL_CRASH)
X		fprintf(mail, "been aborted due to crash; it was %s", expl);
X	else {
X	fprintf(mail, "completed");
X	if(status == 0)
X		fprintf(mail, " successfully");
X	else {
X		if(status & 0177) {
X			if((status & 0177) > NSIG)
X				fprintf(mail, " with unknown status %o", status);
X			else
X				fprintf(mail, " with termination %s",
X					faults[(status & 0177)-1]);
X			if(status & 0200)
X				fprintf(mail, " and a core dump");
X		} else
X			fprintf(mail, " with exit code %d", status >> 8);
X	}
X	}
X	fprintf(mail, ".\n\n");
X	if(outstat == -1)
X		fprintf(mail, "Can't stat output file %s\n", outname);
X	else if(sb.st_size > 0) {
X		fprintf(mail, "Output %s follows:\n\n", mailstat & MAIL_CRASH?
X			"to crash point": "");
X		if((output = open(outname, 0)) == -1) {
X			pmerror(outname);
X			fprintf(mail, "**Daemon can't read the output data!\n");
X		} else {
X			while((n = read(output, buf, sizeof(buf))) > 0)
X				fwrite(buf, n, 1, mail);
X			close(output);
X		}
X	}
X	if(unlink(outname) < 0)
X		pmerror(outname);
X	mailclose(mail);
X}
X
X/*
X *  Stop all jobs in a particular queue.
X */
XSTATIC
Xstopall(qp)
Xregister Queue *qp;
X{
X	if(qp->q_nexec == 0)
X		return;
X	muser(qp->q_supervisor, "%s: Stopping; load too high\n", qp->q_name);
X	killpg(qp->q_pgroup, SIGSTOP);
X}
X
X/*
X *  Restart all jobs in a particular queue.
X */
XSTATIC
Xstartall(qp)
Xregister Queue *qp;
X{
X	if(qp->q_nexec == 0)
X		return;
X	muser(qp->q_supervisor, "%s: Restarting; load is ok now\n", qp->q_name);
X	killpg(qp->q_pgroup, SIGCONT);
X}
X	
X/*
X *  Abort all jobs in a particular queue.
X */
XSTATIC
Xabortall(qp)
Xregister Queue *qp;
X{
X	register CF_File *fp;
X
X	if(qp->q_nexec == 0)
X		return;
X	muser(qp->q_supervisor, "%s:  Terminating all jobs with extreme prejudice", qp->q_name);
X	for(fp = qp->q_files ; fp < &qp->q_files[MAXENTRIES] ; fp++)
X		if(fp->f_pid)
X			abortjob(fp);
X}
X
X/*
X *  Abort a particular job.  Use extreme prejudice.
X *  Note that we count on wait getting the process back later.
X */
XSTATIC
Xabortjob(fp)
Xregister CF_File *fp;
X{
X	kill(fp->f_pid, SIGKILL);
X}
X
X/*
X *  Queue is to be terminated. Stop new jobs running in that queue.
X */
XSTATIC
Xrundown(qp)
Xregister Queue *qp;
X{
X	if(qp->q_running)
X		muser(qp->q_supervisor, "%s: Draining\n", qp->q_name);
X	qp->q_running = 0;
X	if(qp->q_nexec == 0) {
X		muser(qp->q_supervisor, "%s: Drained\n", qp->q_name);
X		qp->q_drain = 0;
X	}
X}
X
Xstruct	nlist nl[] = {
X	{ "_avenrun" },
X#define	X_AVENRUN	0
X	{ 0 },
X};
X
XSTATIC
Xload(i)
X{
X	static int kmem = -1;
X	double	avenrun[3];
X
X	if(kmem == -1) {
X		nlist("/vmunix", nl);
X		if (nl[0].n_type==0) {
X			merror("%s: No namelist\n", "/vmunix");
X			return 0;
X		}
X		if((kmem = open("/dev/kmem", 0)) == -1) {
X			pmerror("/dev/kmem");
X			return 0;
X		}
X	}
X	lseek(kmem, (long)nl[X_AVENRUN].n_value, 0);
X	read(kmem, avenrun, sizeof(avenrun));
X	return (int) (avenrun[i] + .5);
X}
e-o-f
echo Extracting junk
mkdir - junk
cd junk
echo Extracting Makefile
sed 's/^X//' > Makefile << 'e-o-f'
Xinstall:
X	install -c -m 755 batchqueue /usr/public
X	install -c -m 755 batchcancel /usr/public
X	cp *.p /usr/man/manp
e-o-f
echo Extracting batchcancel
sed 's/^X//' > batchcancel << 'e-o-f'
X#!/bin/sh
X# Batch Queue Cancel command.
X#    batchcancel [-q reg_exp] jobid [ jobid ... ]
X# This will mess up if you pass an arg that is not a valid
X# GLOB pattern, e.g. batchcancel "["   -IAN!
X
XPATH=/bin:/usr/bin:/usr/ucb
Xdate=`date`
X
Xdirs=*
Xcase "$1" in
X-q)
X	shift
X	case $# in
X	0)
X		echo batchcancel: queue list missing, default used
X		;;
X	*)
X		dirs=$1
X		shift
X		;;
X	esac
X	;;
Xesac
X
Xcase "$#" in
X0)
X	echo Usage: ${0} "[-q reg_exp]" jobid "[ jobid ... ]"
X	exit 1
X	;;
Xesac
X
Xwhoami=`whoami`
Xcase "$whoami" in
Xroot)
X	who=$whoami"($USER)"
X	user=""
X	;;
X*)
X	who=$whoami
X	user="-user $whoami"
X	;;
Xesac
X
Xcd /usr/spool/batch
Xfor dir in $dirs ; do
X	if [ ! -d "$dir" ]; then
X		continue
X	fi
X	cd $dir
X	echo ">>-- $dir queue --<<"
X	for jobid  do
X		for file in `find . $user -name cf'*'"$jobid" -print` ; do
X			ls -l $file | awk ' { printf("%-12s %s %s %s   %s\n", $3, $5, $6, $7, substr($8,3) ) }'
X			grep "JOB CANCELLED" $file || (
X				sed	-e "1,/set home=/d" -e "/^$/d" \
X					-e "s/'//g;s/  */ /g;s/^ */  /;" \
X					-e "/typeset/s;/tmp/typesetSTDIN_;#;" \
X					-e "/spice/s;/tmp/spiceSTDIN_;#;" \
X					2>/dev/null $file ; \
X				( echo "#!/bin/cat" ; \
X				  echo "JOB CANCELLED by $who on $date." \
X					) >$file && chmod ugo+r $file \
X					&& echo "  JOB CANCELLED." )
X		done
X	done
X	cd ..
Xdone
Xexit 0
e-o-f
echo Extracting batchcancel.p
sed 's/^X//' > batchcancel.p << 'e-o-f'
X.TH BATCHCANCEL PUBLIC "UofW"
X.SH NAME
Xbatchcancel \- cancel jobs waiting in specified batch queues
X.SH SYNOPSIS
X.B /usr/public/batchcancel
X[-q queue_name]
Xjobid [ jobid ... ]
X.SH EXAMPLES
X.nf
X	/usr/public/batchcancel 128
X	/usr/public/batchcancel '12?'
X	/usr/public/batchcancel '*'
X	/usr/public/batchcancel -q 'vlsi*' '*'
X.fi
X.SH DESCRIPTION
X.I Batchcancel
Xcancels the named files waiting in the specified batch queues.
XThe queue_name can be any regular expression needed to
Xspecify the desired batch queue(s).
XThe arguments should match unique numeric suffixes of the
Xbatch spool file names you want cancelled.
X(Do not prefix the name with "cf"; use only the trailing numbers.)
X.PP
XThe spool file names can be listed by the
X.IR batchqueue (public)
Xcommand.
XNormal users can only cancel their own jobs, and the suffix
Xneed only be unique among their own jobs.
XThe Super-User can cancel any job, and the suffix must
Xuniquely identify the job from among all the others in the
Xspecified batch queues.
X.PP
XThe jobid is really the right half of a GLOB pattern to be
Xused in a 
X.IR find (1)
Xcommand, so any GLOB expression will do.
XIf the jobid '*' or '' is used, all queued files submitted
Xby a user in the specified batch queues will be cancelled.
X.PP
XThe queue entry is not removed, but it is overwritten with a
Xfile that says "JOB CANCELLED".
XThe file is made readable, so others can see that the job is
Xcancelled.
X.SH FILES
X.nf
X.DT
X/usr/spool/batch/*/cf[a-z]*     	\- batch spool files
X.fi
X.SH AUTHORS
XIan! Allen, University of Waterloo
X.br
XJim Barby, University of Waterloo
X.SH "SEE ALSO"
Xbatchqueue(public), batch(1), batchd(8), typeset(1),
Xcifplot(CAD1), spice(CAD1)
X.SH BUGS
XIt won't cancel files that have already begun to execute.
XYou must use
X.IR ps (1)
Xwith the \-x option to find already-running batch files, and then use
X.IR kill (1)
Xto kill them.
X.PP
XIt is too slow, because it's a shell script.
X.PP
XFile names that are not valid GLOB patterns will cause
X.IR find (1)
Xto yell at you a lot.
e-o-f
echo Extracting batchqueue
sed 's/^X//' > batchqueue << 'e-o-f'
X#!/bin/sh
X# Batch Queue command.
X#    batchqueue [-q reg_exp] [ -all ] [ userid ... ]
X# The command defaults to listing all users' jobs waiting in all queues.
X# "-all" means NOT all, i.e. only me.  Little arg checking is done.
X# This will mess up if you pass an arg that is not a valid
X# awk regular expression, e.g. batchqueue "*" or ""
X# I don't use FIND because this ls into awk is faster (!)
X# and because I can sort on queue order this way.  (The queue order
X# is based on the file name.  The third letter is priority.)
X# -IAN!
X
XPATH=/bin:/usr/bin:/usr/ucb
X
Xdirs=*
Xcase "$1" in
X-q)
X	shift
X	case $# in
X	0)
X		echo batchqueue: queue list missing, default used
X		;;
X	*)
X		dirs=$1
X		shift
X		;;
X	esac
X	;;
Xesac
X
Xcase "$1" in
X-a*)
X	who=${USER-`whoami`}
X	shift
X	;;
X+a*)
X	shift
X	;;
Xesac
X
Xcase $# in
X0)
X	case "$who" in
X	"")
X		who=".*"
X		;;
X	esac
X	;;
X*)
X	case "$who" in
X	"")
X		who="$1"
X		shift
X		;;
X	esac
X	for x do
X		who="$who|$1"
X		shift
X	done
X	;;
Xesac
X
Xcd /usr/spool/batch
Xfor dir in $dirs ; do
X	if [ ! -d "$dir" ]; then
X		continue
X	fi
X	cd $dir
X	echo ">>-- $dir queue --<<"
X	for file in `ls -l cf* 2>/dev/null | awk "/ ($who) /"' { print $8 }'`; do
X		ls -l $file | awk \
X			'{ printf("%-12s %s %s %s   %s\n", $3, $5, $6, $7, $8 ) }'
X		grep 2>/dev/null "JOB CANCELLED" $file || \
X			sed	-e "1,/set home=/d" -e "/^$/d" \
X				-e "s/'//g;s/  */ /g;s/^ */  /;" \
X				-e "/typeset/s;/tmp/typesetSTDIN_;#;" \
X				-e "/spice/s;/tmp/spiceSTDIN_;#;" \
X				2>/dev/null $file
X	done
X	cd ..
Xdone
Xexit 0
e-o-f
echo Extracting batchqueue.p
sed 's/^X//' > batchqueue.p << 'e-o-f'
X.TH BATCHQUEUE PUBLIC "UofW"
X.SH NAME
Xbatchqueue \- show jobs waiting in specified batch queues
X.SH SYNOPSIS
X.B /usr/public/batchqueue
X[-q queue_name]
X[ -all | userid ... ]
X.SH EXAMPLES
X.nf
X	/usr/public/batchqueue
X	/usr/public/batchqueue -all
X	/usr/public/batchqueue idallen
X	/usr/public/batchqueue -all idallen fbaggins
X	/usr/public/batchqueue -q 'vlsi*'
X	/usr/public/batchqueue -q 'vlsi*' -all
X.fi
X.SH DESCRIPTION
X.I Batchqueue
Xlists the files waiting in each of the specified batch
Xqueues, with the head of the queue at the top and the
Xmost recent entries at the bottom.
XThe queue_name can be any regular expression needed to
Xspecify the desired batch queue(s).
XIf you have permission, the command that created the spool file
Xis displayed after each entry.
XIf you want to cancel one of your entries, see
X.IR batchcancel (public).
X.PP
XAn input file name of #nnn in the any of the troff or spice
Xqueues indicates that you supplied standard input to the
X.IR typeset (1)
Xor
X.IR spice (CAD1)
Xcommand.
X.I Typeset/spice
Xcopied that input to a temporary file, awaiting the time that the
Xbatch processor gets around to running your job.
XThe real file name of the temporary input file is /tmp/tyesetSTDIN_nnn
Xor /tmp/spiceSTDIN_nnn;
Xonly the
X.I nnn
Xpart is shown in the listing from
X.IR batchqueue ,
Xfor brevity.
X.PP
XIf no arguments are given, all waiting files are displayed.
XThe \-all option restricts the listing to your own files, plus
Xfiles belonging to any other userid names on the command line.
X.PP
XSee
X.IR batch (1)
Xfor a description of queue priorities.
X.SH FILES
X.nf
X.DT
X/usr/spool/batch/*/cf[a-z]*	\- spool files
X.fi
X.SH AUTHORS
XIan! Allen, University of Waterloo
X.br
XJim Barby, University of Waterloo
X.SH "SEE ALSO"
Xbatchcancel(public), batch(1), batchd(8), typeset(1),
Xcifplot(CAD1), spice(CAD1)
X.SH BUGS
XIt is too slow, because it's a shell script.
X.PP
XUser names that are not valid awk(1) regular expressions will cause
X.IR awk (1)
Xto yell at you a lot.
e-o-f
cd ..
echo Extracting lex.h
sed 's/^X//' > lex.h << 'e-o-f'
Xenum keyword {
X	K_EXEC=1, K_MAXEXEC, K_MAXQUEUE, K_SUPERVISOR, K_MAIL, K_MAILSUPERVISOR,
X	K_CONSOLE, K_NICE, K_RESTART, K_TIMECMD, K_DAY,
X	K_OFF, K_DRAIN, K_ON, K_NUMBER, K_TIME, K_VARIABLE, K_LINE,
X	K_SYNTAX, K_LOADSTOP, K_LOADSCHED,
X	K_CPULIMIT, 
X	K_RLIMITCPU,
X	K_RLIMITFSIZE,
X	K_RLIMITDATA,
X	K_RLIMITSTACK,
X	K_RLIMITCORE,
X	K_RLIMITRSS,
X};
e-o-f
echo Extracting lex.l
sed 's/^X//' > lex.l << 'e-o-f'
X%{
X#include "lex.h"
X
Xstruct klist {
X	char *keyword;
X	enum keyword keyvalue;
X} klist[] = {
X	"exec",		K_EXEC,
X	"maxexec",	K_MAXEXEC,
X	"maxqueue",	K_MAXQUEUE,
X	"supervisor",	K_SUPERVISOR,
X	"mail",		K_MAIL,
X	"mailsupervisor", K_MAILSUPERVISOR,
X	"console",	K_CONSOLE,
X	"nice",		K_NICE,
X	"restart",	K_RESTART,
X	"time",		K_TIMECMD,
X	"day",		K_DAY,
X	"off",		K_OFF,
X	"drain",	K_DRAIN,
X	"on",		K_ON,
X	"loadstop",	K_LOADSTOP,
X	"loadsched",	K_LOADSCHED,
X	"cpulimit",	K_CPULIMIT,
X	"rlimitcpu",	K_RLIMITCPU,
X	"rlimitfsize",	K_RLIMITFSIZE,
X	"rlimitdata",	K_RLIMITDATA,
X	"rlimitstack",	K_RLIMITSTACK,
X	"rlimitcore",	K_RLIMITCORE,
X	"rlimitrss",	K_RLIMITRSS,
X	0
X};
X
X%}
X
X%%
X
X[0-9]+				return K_NUMBER;
X
X[/0-9a-zA-Z_.-]+	{
X				register struct klist *kp;
X				for(kp = klist ; kp->keyword ; kp++)
X					if(strcmp(kp->keyword, yytext) == 0)
X						return kp->keyvalue;
X				return K_VARIABLE;
X			}
X
X[0-9][0-9]:[0-9][0-9] 		return K_TIME;
X
X\n				return K_LINE;
X
X[ \t]				;
X
X.				return K_SYNTAX;
X
X%%
X
Xlexfile(f)
XFILE *f;
X{
X	char *p;
X	struct yysvf ***q;
X	extern struct yysvf *yylstate [YYLMAX], **yylsp, **yyolsp;
X	extern int *yyfnd;
X
X	yylineno = 1;
X	bzero(yytext, YYLMAX*sizeof yytext[0]);
X	bzero(yylstate, sizeof yylstate);
X	yylsp = yyolsp = 0;
X	bzero(yysbuf, YYLMAX*sizeof yysbuf[0]);
X	yysptr = yysbuf;
X	yyfnd = 0;
X	yyprevious = YYNEWLINE;
X	yyleng = 0;
X	yymorfg = 0;
X	yytchar = 0;
X	yyin = f;
X	yyestate = 0;
X}
X
Xyywrap()
X{
X	return 1;
X}
e-o-f
echo Extracting queues
mkdir - queues
cd queues
echo Extracting .cleanup
sed 's/^X//' > .cleanup << 'e-o-f'
X: Weekly script run by cron to clean up batch queues.
Xcd /usr/spool/batch
Xfor dir in ???* ; do
X	if [ ! -d "/usr/spool/batch/$dir" ]; then
X		continue
X	fi
X	cd /usr/spool/batch/$dir
X	if [ -f This_week ]; then
X		cp -m This_week Last_week
X	fi
X	cp /dev/null This_week
Xdone
e-o-f
echo Extracting later
mkdir - later
cd later
echo Extracting profile
sed 's/^X//' > profile << 'e-o-f'
Xnice 15
Xmaxexec 1
Xmail ec
Xsupervisor /usr/spool/batch/later/This_week
Xmailsupervisor sec
Xloadsched 4
Xloadstop 6
Xrestart
Xrlimitfsize 10000000
Xrlimitrss 0
e-o-f
cd ..
echo Extracting never
mkdir - never
cd never
echo Extracting profile
sed 's/^X//' > profile << 'e-o-f'
Xnice 20
Xmaxexec 1
Xmail ec
Xsupervisor /usr/spool/batch/never/This_week
Xmailsupervisor sec
Xloadsched 2
Xloadstop 4
Xrestart
Xrlimitfsize 10000000
Xrlimitrss 0
e-o-f
cd ..
echo Extracting now
mkdir - now
cd now
echo Extracting profile
sed 's/^X//' > profile << 'e-o-f'
Xnice 0
Xmaxexec 1
Xmail ec
Xsupervisor /usr/spool/batch/now/This_week
Xmailsupervisor sec
Xloadsched 100
Xloadstop 100
Xrestart
Xrlimitrss 0
e-o-f
cd ..
echo Extracting troff
mkdir - troff
cd troff
echo Extracting profile
sed 's/^X//' > profile << 'e-o-f'
Xnice 2
Xmaxexec 1
Xmail ec
Xsupervisor /usr/spool/batch/troff/This_week
Xmailsupervisor sec
Xloadsched 10
Xloadstop 14
Xrestart
Xrlimitcpu 1800
Xrlimitfsize 10000000
Xrlimitcore 0
Xrlimitrss 50000
e-o-f
cd ..
cd ..
exit 0