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