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