Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Posting-Version: version B 2.10.2 9/18/84 +MULTI+2.11; site stc.UUCP Path: utzoo!linus!philabs!cmcl2!seismo!mcvax!ukc!stc!shimell From: shimell@stc.UUCP (Dave Shimell) Newsgroups: net.sources Subject: A batch system for 4.2 Message-ID: <226@stc-a.stc.UUCP> Date: Sat, 27-Apr-85 00:27:40 EDT Article-I.D.: stc-a.226 Posted: Sat Apr 27 00:27:40 1985 Date-Received: Sun, 28-Apr-85 07:47:30 EDT Reply-To: ollie@stc.UUCP Organization: STC Telecomms. London. Lines: 609 Xpath: stc stc-a 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@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. {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 #include #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 .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 .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 #include #include #include #include #include #include #include #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. {root44, ukc, idec, stl, creed}!stc!shimell