A batch system for 4.2

Dave Shimell shimell at stc.UUCP
Sat Apr 27 14:27:40 AEST 1985


<eat me>
	Here is a batch system for BSD4.2 written by Bruce Ollie Munro.
	(I am posting it as he is currently away on a course - please
	address flames etc to ollie at stc.UUCP)

	The batch system uses the line printer queueing system to do
	the hard work.  This has the advantages of using standard
	software to control queues.  Thus batch queues may be
	manipulated by lpc.

	We have run this batch system for a number of months with no
	problems.  Please note that the Makefile is an augmented Make
	so this may not work on your system.  To install for the first
	time type:

		make new	(as root)

	May I suggest that if your system is greatly loaded then you
	should think about running the load contol software distributed over
	the net recently from San Diego.  We run it in addition to batch
	and are greatly impressed!
	
Regards,
Dave Shimell.  <shimell at stc.UUCP>
{root44, ukc, idec, stl, creed}!stc!shimell

--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# To unbundle, csh this file
echo Makefile
cat >Makefile <<'End of Makefile'
# Makefile for batch
# %W%
#

USER	=	root

SPOOL	=	/usr/spool/lpd/batch
SCCS	=	.
BIN	=	/usr/local
LIBDIR	=	/usr/local/lib
MAN	=	/usr/man/manl
PROGS	=	batch bback

CFLAGS	=	-O
LIBS	=	-llocal

#-------------------------------------------------------------------------------

all:		$(PROGS)
		@echo programs are up to date

$(PROGS):	$$@.o
		$(CC) $(LDFLAGS) $@.o $(LIBS)
		mv a.out $@

install cp:	$(BIN)/batch $(LIBDIR)/bback $(MAN)/batch.l
		@echo installed programs are up to date

new:		install printcap
		mkdir $(SPOOL)
		cat /dev/null > $(SPOOL)/LOGFILE
		cat printcap >> /etc/printcap

$(BIN)/batch:	batch
		-rm -f $@
		cp $? $@

$(LIBDIR)/bback:bback
		-rm -f $@
		cp $? $@
		chown $(USER) $@
		chmod 4755 $@

$(MAN)/batch.l:	batch.n
		-rm -f $@
		cp $? $@

.DEFAULT:
		$(GET) $(GFLAGS) $(SCCS)/s.$<

rm clean tidy:
		-rm -f $(PROGS) batch.o bback.o batch.n
'End of Makefile'
echo batch.c
cat >batch.c <<'End of batch.c'
#include <stdio.h>
#include <assert.h>

#ifndef lint
#ifndef NSCCS
	static char sccsid[] = "%W%";
#endif
#endif

#define MAXLINE 512

#ifndef TRUE
#	define TRUE  1
#	define FALSE 0
#endif

typedef int bool;

extern char **environ;
extern char *optarg;
extern int  optind;

main(argc, argv)
char **argv;
{
	register char **envp;	/* Environment pointer */
	register char *s1;	/* Pointers used in inserting qoute escape */
	register char *s2;	/* in environment variable names */
	int c;			/* Option character */
	int n;			/* Number of sscanf arguments */
	char name[MAXLINE];	/* Environment variable name  */
	char value[MAXLINE];	/* Environment variable value */
	char value2[MAXLINE];	/* Environment value after escape of quotes */
	char pathname[MAXLINE];	/* Pathname of current working directory */
	char *shell;		/* Name of shell */
	char *ofile = "batch.log";	/* Job output file, set to default */
	char *efile = NULL;	/* Job error file */
	bool notsh;		/* TRUE if shell is other than sh */
	bool  mail = FALSE;	/* Mail required switch */
	FILE *pfp;
	extern FILE *popen();
	extern char *getenv();	/* Routine to access environment variables */
	extern char *getwd();	/* Routine to get current working directory */
	extern int getopt();	/* Routine to return command line options */

	while ((c = getopt(argc, argv, "me:o:")) != EOF) {
		switch (c) {
		case 'o' :
			ofile = optarg;
			break;
		case 'e' :
			efile = optarg;
			break;
		
		case 'm' :
			mail = TRUE;
			break;

		case '?' :
			quit("Usage: batch [-m] [-o file] [-e file] [command [args .... ]]");
		}
	}
	if (efile == NULL)
		efile = ofile;
	if ((pfp = popen("/usr/ucb/lpr -P batch", "w")) == NULL)
		quit("batch: can't pipe to lpr");
	fprintf(pfp, "%s\n", ofile);
	fprintf(pfp, "%s\n", efile);
	fprintf(pfp, "%d\n", mail);
	fprintf(pfp, "%s\n", getwd(pathname));
	fprintf(pfp, "umask %o\n", umask(0));

	/*
	 * Get environment variables for command file,
	 * scanf used to seperate values from names in
	 * order to place quotes around the values so
	 * that those containing spaces will be handled
	 * correctly by 'sh'
	 */

	for (envp = environ; *envp != NULL; envp++) {
		n = sscanf(*envp, "%[^=]=%[^\n]", name, value);
		assert(n == 2);

		/*
		 * Check for occurence of qoute (') in environment values
		 * and replace them with '\'' in order that sh does not
		 * get confused when executing job.
		 */
		 
		s1 = value;
		s2 = value2;
		while (*s2++ = *s1) {	/* Assignment */
			if (*s1++ == '\'') {
				*s2++ = '\\';
				*s2++ = '\'';
				*s2++ = '\'';
			}
		}
		fprintf(pfp, "%s='%s'\n", name, value2);
		fprintf(pfp, "export %s\n", name);
	}

	/*
	 * Checks if shell is not 'sh' and if not places
	 * an invocation line for the correct shell in
	 * the command file
	 */

	shell = getenv("SHELL");
	if (notsh = strcmp(shell, "/bin/sh"))
		fprintf(pfp, "%s << 'xxAARRGGHHxx'\n", shell);
	/* Place user's commands in command file */
	if (argv[optind] == NULL)
		while ((c = getchar()) != EOF)
			putc(c, pfp);
	else {
		for (; argv[optind]; ++optind)
			fprintf(pfp, "%s ", argv[optind]);
		putc('\n', pfp);
	}

	/*
	 * Shell termination line if not 'sh'
	 */

	if (notsh)
		fprintf(pfp, "xxAARRGGHHxx\n");
	pclose(pfp);
	exit(0);
}
'End of batch.c'
echo batch.n
cat >batch.n <<'End of batch.n'
.TH BATCH LOCAL
.SH NAME
batch \- submit a batch command 
.SH SYNTAX
.B batch 
[\-o stdout] [\-e stderr] [\-m] [command [arg ...]]
.SH DESCRIPTION
.I Batch
submits a job to the batch job queue, where it will be run
at low priority. This is useful for large jobs in order
that they do not load the system heavily at peak times.
.B Default
if no command is specified is to read command from the
.I "standard input."
A log of the commands' output is maintained by
.I batch.
.I Batch
interprets only the options, which may be in any order, that
are placed
.I before
the command. The options are:
.TP 10
.B "\-o file"
Appends the standard output of
.I command
to
.I file.
Default file is
.I batch.log.
.TP 10
.B "\-e file"
Appends the standard error of
.I command
to
.I file.
Default is to merge standard error with standard output.
.TP 10
.B \-m
Sends mail to user to inform them of completion of batch job.
.PP
The state of the batch queue may be examined using lpq(1)
invoked as:
.IP
.I "lpq -P batch."
.PP
Batch jobs may be dequeued using lprm(1) invoked as:
.IP
.I "lprm -P batch."
(See RESTRICTIONS below)
.SH EXAMPLES
.sp 3
.IP
.br
batch nroff xyz.n
.PP
will nroff xyz.n placing the standard output in
.I batch.log.
.PP
.IP
batch
.br
nroff xyz.n >xyz.doc
.br
<EOF>
.PP
will nroff xyz.n and redirect the standard output to
.I xyz.doc.
.PP
The same effect could be achieved by:\-
.PP
.IP
batch \-o xyz.doc nroff xyz.n
.PP
The above examples will place any error reports from
the command on the standard output.
.PP
However 
.PP
.IP
batch \-e error.log \-o xyz.doc nroff xyz.n
.PP
nroff's xyz.n appending standard output to xyz.doc
and standard error to error.log.
.s3
.ne 6
.IP
batch \-m
.br
cd ~/mydocs
.br
foreach i (*.n)
.br
	tbl \-TX $i | nroff \-mm | col | lpr
.br
end
.br
<EOF>
.PP
will format documents from the mydocs directory and print them.
When the job is complete mail will be sent to the user.
.SH FILES
.PD 0
.TP 32 
 batch.log
default standard output file
.TP 32
 /usr/spool/lpd/batch/LOGFILE
record of all batch jobs and their time statistics
.PD
.SH "SEE ALSO"
lpq(1), lprm(1), lpc(8), lpd(8)
.SH RESTRICTIONS
If the command to be batched is specified on the invocation line
all shell metacharacters should be escaped in order that they are
not interpreted by the current login shell.
.PP
Using lprm(1) on the active batch job will cause some error messages.
If the job removed is the only one in the queue then no harm is done,
however if there are jobs waiting removing the active job will cause
the daemon to stop and the queue must be restarted by the system administrator.
'End of batch.n'
echo bback.c
cat >bback.c <<'End of bback.c'
#include <stdio.h>
#include <pwd.h>
#include <assert.h>
#include <sys/param.h>
#include <sys/quota.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>

#ifndef NSCCS
#ifndef lint
	static char sccsid[] = "%W%";
#endif
#endif

typedef int bool;

#define MAXLINE 512
#define DAEMON_UID 1
#define LOW_PRIORITY 10

#define NORM_EXIT 0
#define BAD_EXIT  2

/*
 * convert whole seconds to number of clock
 * cycles by multiplying by CYCLE_SECS
 */

#define  CYCLE_SECS  100

/*
 * convert micro-seconds to number of clock
 * cycles by dividing by USEC_CYCLES
 */

#define  USEC_CYCLES 10000

/*
 * reduce number in micro-secs to tenths
 * secs by dividing by RED_TO_TENTHS
 */

#define  RED_TO_TENTHS  100000
#define  SECS_IN_MIN  60

extern char *optarg;
extern char *program;
extern int  fail_status;
char *user;			/* User's login name */

extern char *gets();

main(argc, argv)
char **argv;
{

	char ofile[MAXLINE];		/* STDOUT filename */
	char efile[MAXLINE];		/* STDERR filename */
	char dir_path[MAXLINE];
	int c;
	union wait status;		/* Exit status of child  */
	union wait execute();

	int n;
	bool mail;			/* Mail required switch */
	struct passwd *pw;		/* structure for user's passwd file entry */

	program = "batch backend";
	fail_status = BAD_EXIT;
	while ((c = getopt(argc, argv, "w:l:i:n:h:")) != EOF) {
		switch (c) {

		case 'n':
			user = optarg;
			break;

		case '?':
			quit("Bad option -%c from lpd", c);

		}
	}

	/*
	 * Check for attempts to use backend as root
	 * or daemon  by bypassing  queueing process
	 */

	if (getuid() != DAEMON_UID) {
		pw = getpwuid(geteuid());
		quit("attempt by user %s to use status of %s in batch", pw->pw_name, user);
	}

	/*
	 * Get user's uid and gid from passwd file.
	 */

	if ((pw = getpwnam(user)) == NULL)
		quit("Non-existent user name- %s from lpd", user);
	endpwent();

	/*
	 * Set unbuffered input so that parent doesn't
	 * read any command lines intended for child's use
	 */

	setbuf(stdin, NULL);
	gets(ofile);
	gets(efile);
	n = scanf("%d\n", &mail);
	assert(n == 1);

	/*
	 * Change to passed directory name
	 */

	if (chdir(gets(dir_path)) == -1)
		quit("Couldn't access directory (%s)", dir_path);
	status = execute(ofile, efile, pw->pw_uid, pw->pw_gid);
	if (status.w_T.w_Termsig || status.w_T.w_Retcode)
		badstat(status);
	if (mail)
		sendmail(user, status);
	exit(NORM_EXIT);
}

/*
 * forks,  execs sh  with output  directed
 * to ofile and efile and does  setuid and
 * setgid to specified values
 */

union wait
execute(ofile, efile, uid, gid)
char *ofile;
char *efile;
int  uid;
int  gid;
{
	int pid;		/* Return value from fork system call */
	union wait status;	/* Return value from time_command function */
	union wait time_job();

	if ((pid = fork()) == -1)
		quit("couldn't fork");
	if (pid == 0) {
		if (quota(Q_SETUID, uid, NULL, NULL) == -1)
			quit("Cannot set quotas for %s", user);
		setgid(gid);
		setuid(uid);
		if (freopen(ofile, "a", stdout) != stdout)
			quit("can't create %s", ofile);
		if (strcmp(ofile, efile) == 0) {
			if (dup2(1, 2) == -1)
				quit("can't duplicate standard error to standard output");
		} else if (freopen(efile, "a", stderr) != stderr)
			quit("can't create %s", efile);
		setbuf(stdout, NULL);
		setpriority(PRIO_PROCESS, getpid(), LOW_PRIORITY);
		execl("/bin/sh", "sh", "-s", NULL);
		quit("execl failed");
	}
	status = time_job();
	return(status);
}

/*
 * Waits for job to finish, collects resource stats on job
 * from wait3 system call and outputs on stderr a summary
 * of these stats in similar format to the csh time command
 */

union wait
time_job()
{
	union wait status;	/* Return value from wait3 system call */
	struct rusage rusage;	/* Resources used by process */
	struct timeval inittime;/* Time returned by gettimeofday system call */
	struct timeval endtime;	/* ditto */
	struct timezone dummy;	/* time zone correction (not used) */
	u_long elap_time;	/* Elapsed time during execution of job */
	long cpu_cycles;	/* No. of cycles of cpu time */
	long elap_cycles;	/* Elapsed time in terms of cycles */

	gettimeofday(&inittime, &dummy);
	wait3(&status, NULL, &rusage);
	gettimeofday(&endtime, &dummy);
	elap_time = endtime.tv_sec - inittime.tv_sec;
	elap_cycles = ((endtime.tv_sec * CYCLE_SECS) + (endtime.tv_usec/USEC_CYCLES)) -
		((inittime.tv_sec * CYCLE_SECS) + (inittime.tv_usec/USEC_CYCLES));
	cpu_cycles = (((rusage.ru_utime.tv_sec + rusage.ru_stime.tv_sec) * CYCLE_SECS) +
		((rusage.ru_utime.tv_usec + rusage.ru_stime.tv_usec)/USEC_CYCLES));

	fprintf(stderr, "Job: %-9s", user);
	fprintf(stderr, "%ld.%ldu %ld.%lds %ld:%02ld %02ld%% %ld+%ldk %ld+%ldio %ldpf+%ldw\n",
		rusage.ru_utime.tv_sec,
		rusage.ru_utime.tv_usec/RED_TO_TENTHS,
		rusage.ru_stime.tv_sec,
		rusage.ru_stime.tv_usec/RED_TO_TENTHS,
		elap_time/SECS_IN_MIN, elap_time % SECS_IN_MIN,
		cpu_cycles * CYCLE_SECS/elap_cycles,
		rusage.ru_ixrss/cpu_cycles,
		(rusage.ru_idrss + rusage.ru_isrss)/cpu_cycles,
		rusage.ru_inblock,
		rusage.ru_oublock,
		rusage.ru_majflt,
		rusage.ru_nswap
	);
	return(status);
}

/*
 * Sends mail to the user on completion of the job,
 * indicating the exit status of the job
 */
 
sendmail(user, status)
char *user;
union wait status;
{
	FILE *pp;
	char mail[MAXLINE];
	extern FILE *popen();

	sprintf(mail, "/usr/ucb/mail -s 'batch job' %s", user);
	if ((pp = popen(mail, "w")) == NULL)
		quit("can't pipe to /usr/ucb/mail");
	fprintf(pp, "Your batch job ");
	if (!status.w_T.w_Termsig && !status.w_T.w_Retcode)
		fprintf(pp, "completed successfully.\n");
	else {
		fprintf(pp, "terminated abnormally ");
		if (status.w_T.w_Termsig == 0)
			fprintf(pp, "with exit status %d.\n", status.w_T.w_Retcode);
		else {
			fprintf(pp, "due to signal %d.\n", status.w_T.w_Termsig);
			if (status.w_T.w_Coredump != 0)
				fprintf(pp, "Signal caused a core dump.\n");
		}
	}
	pclose(pp);
}

badstat(status)
union wait status;
{
	if (status.w_T.w_Termsig == 0)
		error("job exited with status %u", status.w_T.w_Retcode);
	else {
		error("job terminated due to signal %u", status.w_T.w_Termsig);
		if (status.w_T.w_Coredump)
			error("Core dumped");
	}
}
'End of bback.c'
echo printcap
cat >printcap <<'End of printcap'
batch|batch job queue:\
	:lp=/dev/null:sd=/usr/spool/lpd/batch:\
	:if=/usr/local/lib/bback:\
	:lf=/usr/spool/lpd/batch/LOGFILE:
'End of printcap'
-- 

Regards,
Dave Shimell.  <shimell at stc.UUCP>
{root44, ukc, idec, stl, creed}!stc!shimell



More information about the Comp.sources.unix mailing list