[alt.sources] Keith's at package, part 2 of 2

keith@sequoia.execu.com (Keith Pyle) (02/14/91)

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 2 (of 2)."
# Contents:  doc/at.1 src/at.c src/atq.c src/atrun.c src/date.y
# Wrapped by keith@lime on Wed Feb 13 22:33:50 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'doc/at.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'doc/at.1'\"
else
echo shar: Extracting \"'doc/at.1'\" \(7841 characters\)
sed "s/^X//" >'doc/at.1' <<'END_OF_FILE'
X.TH "AT" 1L "7 November 1990" "Execucom" "LOCAL USER COMMANDS"
X.SH NAME
Xat \- execute commands at a specified time
X.SH SYNOPSIS
X.LP
X.B at
X[
X.B \-m
X] [
X.B \-cks
X]
X.I time
X[
X.I date
X] [
X.B \+increment
X] [
X.I script
X]
X.SH DESCRIPTION
X.B at
Xis a utility which accepts user commands to be executed at a specified later
Xtime.  The execution time can be given as a time only, as a time and date,
Xor as either of these with an increment added.  If a
X.I script
Xis specified, this file will be read to obtain the commands that will be
Xexecuted.  Otherwise,
X.B at
Xwill read standard input for the commands to be queued.  If standard input
Xis a terminal device,
X.B at
Xwill prompt for input with the string
X.LP
X.RS
Xat>
X.RE
X.LP
XThe shell used to process the commands will be determined as follows: (1) if
Xa command line argument is used to specify a shell, that shell will be used,
X(2) if the commands come from a
X.I script
Xand that script specifies a shell on its first line, e.g., #!/bin/sh, the
Xindicated shell will be used, or (3) the shell specified in the current
XSHELL environment variable will be used.
X.LP
XIf the queued job generates output on either standard output or standard
Xerror, the output will be mailed to the user who submitted the job.  If no
Xoutput is generated or if it is redirected in the commands, there will be
Xno mail to the user unless either (1) the commands terminated with an
Xerror status, or (2) the user requests confirmation of the run through the
X.B \-m
Xoption.
X.LP
XThe user's environment variables (except TERM), current directory, and
Xumask are copied to the queued job when it is created.  If
X.I script
Xis specified, its contents are copied to the queued job.  Thus, subsequent
Xchanges to
X.I script
Xor its deletion will not affect the
X.B at
Xjob.
X.LP
XThe format of
X.I time
Xis quite flexible.  It may consist of one or two digits to indicate hours only.
XIf three or four digits are given, they are taken to indicate hours and minutes.
XAn optional colon between the hours and minutes is allowed.  The hours
Xspecification is assumed to be a 24 hour clock reference unless an am or pm
Xsuffix is present.  The time zone may be included; if not, the current time
Xzone is assumed.  The special times
X.B now, noon,
Xand
X.B midnight
Xare also recognized.  Case is not significant for any of the character fields.
X.LP
XThe
X.I date
Xis optional and may be any of the following forms: (1) month name and day,
X(2) month name, day, comma, and year, (3) day and month name, (4) day, month
Xname, and year, (5) month number, slash, and day, (6) month number,
Xslash, day, slash, and year, or (7) a day of the week reference.
XThe month name may be abbreviated to the first
Xthree letters in the forms using it.
XIn those forms where the year is not specified,
Xthe current year is used.  If a two digit year is specified, 1900 is added to
Xit.  The special dates today and tomorrow can be used in place of the
X.I date
Xfield.  If no
X.I date
Xis specified, the current date is used.
X.LP
XThe optional
X.I +increment
Xor
X.I -increment
Xconsists of a simple number suffixed by one of the following: minute, hour,
Xday, week, month, or year.  The plural form of any of these keywords will also
Xwork.  The following abbreviations are also valid: min, m, hr, h, d, wk, w,
Xmon, yr, and y.
X.LP
XIf the
X.I date
Xspecification is omitted, the time will be interpreted as the next occurrence
Xof that time.  For instance, if it is 11 AM and the command
X.LP
X.RS
Xat 10 am
X.RE
X.LP
Xis given, the at job will be scheduled for 10 AM on the following day.
X.LP
XThe following are example of allowable time/date specifications for the
Xcurrent time assuming it is 1:00 AM on August 24, 1990:
X.LP
X.RS
X1
X.br
X100
X.br
X1:00
X.br
X0100
X.br
X1:00 am
X.br
X1:00 aug 24
X.br
X1:00 24 aug
X.br
X0100 8/24
X.br
X01 8/24/90
X.RE
X.LP
XThe use of the day of week reference consists of a time and the name of a
Xday of the week.  The day name may be preceded by an ordinal reference (e.g.,
Xthird). For example,
X.LP
X.RS
X1 pm friday
X.br
X1300 second friday
X.RE
X.LP
XThe special ordinal value
X.B this
Xis provided, however, it has no influence on the computation since
X"this Friday" is synonymous with simply "Friday".
X.B next
Xis synonymous
Xwith the ordinal
X.B first
Xand both imply the next occurence of the specified day.
XThus, both "first Friday"
Xand "next Friday" will be
Xthe first Friday after today.  If today were Friday, either of these would
Xrefer to the day one week from now.
X.LP
XWhen the job is entered into the
X.B at
Xqueue, a unique job number and the scheduled time will be displayed on
Xstandard output.
X.LP
XThere may be restrictions on
X.B at
Xusage depending upon the policies of the installation.  These restrictions
Xare accomplished through one of two files:
X.B at\.allow
Xor
X.B at\.deny.
XIf
X.B at\.allow
Xexists, only those users listed in it may use the
X.B at
Xprograms.  If it does not exist, but
X.B at\.deny
Xdoes exist, all users except those listed may submit jobs.  If
X.B at\.deny
Xis empty, all users are allowed to submit jobs.  If neither file exists,
Xonly the super-user may use
X.B at.
XEntries in either file consist of one user name per line.
X.LP
XThe execution time specified is the earliest that the job will be run.
XAll jobs are run via the
X.BR atrun (8)
Xcommand, which is typically invoked periodically by
X.BR cron (8).
XThe exact time of execution will depend upon the frequency of execution
Xof
X.BR atrun (8).
X.SH OPTIONS
X.TP
X.B \-c
XUse the C shell to execute the specified commands.
X.TP
X.B \-k
XUse the Korn shell to execute the specified commands.
X.TP
X.B \-m
XSend mail to the user who submitted the job on completion, even if no
Xoutput is generated and no error occurs.
X.TP
X.B \-s
XUse the Bourne shell to execute the specified commands.
X.SH EXAMPLES
X(1) To delete a .forward file on June 29 at 10:38 in the morning (e.g., to
Xterminate vacation processing), the sequence could be
X.sp
X.RS
X.B "at 10:38 am June 29"
X.br
Xat>
X.B "rm .forward"
X.br
Xat>
X.B "CTRL-D"
X.RE
X.sp
XThere is no limit on the number of commands that can be entered.
XNote that CTRL-D is the control-D character (hold down control and press D).
X.LP
X(2) To execute the script
X.B cleanup
Xat 3:00 AM on this coming Friday, the
X.B at
Xcommand could be
X.sp
X.RS
X.B "at 3:00 AM Friday cleanup"
X.RE
X.LP
X(3) To perform the same script at the same time, but with notification of
Xits completion, the following command could be used.
X.sp
X.RS
X.B "at -m 0300 fri cleanup"
X.RE
X.LP
X(4) If the
X.B cleanup
Xscript were written for the C shell and the
X.B at
Xjob is being submitted while running a different shell (e.g., the Bourne
Xshell), it would be necessary to force
X.B at
Xto run the commands in the script using the C shell.
X.sp
X.RS
X.B "at -c -m 3 Fri cleanup"
X.RE
X.sp
XNote that the time specifications shown in the preceding three examples
Xwill all result in the same execution time.
X.SH AUTHOR
XKeith Pyle
X.SH FILES
X.ta 2.5i
X\fB/usr/spool/at\fR	spool directory
X.br
X\fB/usr/spool/at/at.allow\fR	allowed users
X.br
X\fB/usr/spool/at/at.deny\fR	prohibited users
X.br
X\fB/usr/spool/at/.seq\fR	job sequence number
X.SH "SEE ALSO"
X.BR atcat (1L),
X.BR atq (1L),
X.BR atrm (1L),
X.BR atrun (8)
X.SH DIAGNOSTICS
XThe exit status is 0 when a job is correctly queued.  For file access failures
Xand other execution problems, the exit status is set to 1.
XAn exit status of 2 is set for syntax errors.
X.SH BUGS
X.LP
XIf a job does not complete due to a system failure, it will not be requeued.
XHowever, it would be possible to affect this function by modification of the
Xappropriate boot file (e.g., rc.local on a BSD system).  Assuming that the
X.B at
Xqueue directory is /usr/spool/at and the working directory is
X/usr/spool/at/past, the following shell
Xcommand could be used:
X.sp
X.RS
X.ft B
Xfind /usr/spool/at/past -name '[1-9]*'
X.if n \{ \\
X.br
X.ti +0.5i
X\}
X-exec mv {} /usr/spool/at \\;
X.ft R
X.RE
X.LP
XThe next execution of
X.BR atrun (8)
Xwill restart these jobs.
END_OF_FILE
if test 7841 -ne `wc -c <'doc/at.1'`; then
    echo shar: \"'doc/at.1'\" unpacked with wrong size!
fi
# end of 'doc/at.1'
fi
if test -f 'src/at.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/at.c'\"
else
echo shar: Extracting \"'src/at.c'\" \(8859 characters\)
sed "s/^X//" >'src/at.c' <<'END_OF_FILE'
X/* at --- a member of the kat family (Keith's at package) */
X
X/* at(1) queues jobs for later execution by atrun.  It is similar in */
X/* function to the at(1) supplied with most BSD systems, but with additional */
X/* capabilities in date parsing, shell selection, and confirmation of the */
X/* run. */
X
X/* Copyright (c) 1990, W. Keith Pyle, Austin, Texas */
X
X#include "at.h"
X
Xchar at_file[MAXPATHLEN];
Xchar at_tmp[MAXPATHLEN];
Xchar buffer[MAXLINE];
Xchar *verstring = PATCHLEVEL;
X
Xint jobno;
X
Xchar *atname();
Xchar *ctime();
Xchar *rindex();
Xtime_t parse_date();
Xvoid quit();
X
Xextern char *optarg;
X
Xextern int opterr;
Xextern int optind;
X
X/* -------------------------------------------------------------------------- */
X
Xmain(argc, argv, envp)
X
Xint argc;
Xchar *argv[];
Xchar *envp[];
X
X{
X	char *lastarg;
X	char owner[32];
X	char script[128];
X	char shell[33];
X
X	int flag;
X	int mail;
X	int mask;
X
X	time_t at_time;
X	time_t now_time;
X
X	FILE *fp_at_file;
X	FILE *fp_script;
X
X	if (argc == 1)
X		usage();
X
X	/* Some initialization */
X
X	mail = FALSE;
X	shell[0] = '\0';
X
X	/* Get the user name */
X
X	whoami(owner);
X
X	/* Check for allowed at usage (just use two handy arrays for the names) */
X
X	(void)sprintf(at_tmp, "%s/%s", ATDIR, ALLOW_FILE);
X	(void)sprintf(at_file, "%s/%s", ATDIR, DENY_FILE);
X	
X	if (!permit(owner, at_tmp, at_file)) {
X
X		(void)fprintf(stderr, "you are not authorized to use at.  Sorry.\n");
X		exit(1);
X	}
X
X	/* Parse any option flags */
X
X	while ((flag = getopt(argc, argv, "ckms")) != EOF) {
X
X		switch (flag) {
X
X			case 'c':
X
X				(void)strcpy(shell,  C_SHELL);
X				break;
X			
X			case 'k':
X
X				(void)strcpy(shell, KORN_SHELL);
X				break;
X			
X			case 'm':
X
X				mail = TRUE;
X				break;
X			
X			case 's':
X
X				(void)strcpy(shell, BOURNE_SHELL);
X				break;
X			
X			default:
X
X				usage();
X		}
X	}
X
X	/* Try to parse all of the remaining arguments as a time specification. */
X	/* If this fails and there at least three arguments, see if the last */
X	/* argument is a file name. */
X
X	lastarg = argv[argc - 1];
X	(void)time(&now_time);
X	now_time -= now_time % 60;
X
X	if ((at_time = parse_args(argc - optind, argv + optind)) != -1) {
X
X		fp_script = stdin;
X		(void)strcpy(script, "stdin");
X	}
X
X	else if ((argc - optind) > 1 &&
X			(at_time = parse_args(argc - optind - 1, argv + optind)) != -1) {
X
X		char *ptr;
X
X		if (access(lastarg, F_OK) != 0) {
X
X			(void)fprintf(stderr, "at: %s does not exist\n", lastarg);
X			exit(1);
X		}
X
X		if ((ptr = rindex(lastarg, '/')) == NULL)
X			ptr = lastarg;
X		else
X			ptr++;
X			
X		(void)strncpy(script, ptr, 127);
X
X		if ((fp_script = fopen(lastarg, "r")) == NULL) {
X
X			perror(lastarg);
X			exit(1);
X		}
X	}
X
X	else {
X
X		(void)fprintf(stderr, "bad date/time specification\n");
X		exit(2);
X	}
X
X	/* Check the 'at time' against the current time */
X
X	if (at_time < now_time) {
X
X		(void)fprintf(stderr, "too late\n");
X		exit(1);
X	}
X
X	/* Build file names for the temporary file and the 'at file' */
X
X	(void)sprintf(at_tmp, "%s/_at%d", ATDIR, getpid());
X	jobno = atseq();
X	(void)sprintf(at_file, "%s/%s", ATDIR, atname(at_time, jobno));
X
X	/* Set up signal handlers */
X
X	signal_init();
X
X	/* Open the temporary file */
X
X	if ((fp_at_file = fopen(at_tmp, "w")) == NULL) {
X
X		perror("can't create a job for you");
X		exit(1);
X	}
X
X	/* If the shell hasn't been specified, determine what should be used */
X
X	if (shell[0] == '\0') {
X
X		/* If the commands are from stdin, use the default shell; */
X		/* otherwise, look at the script to see if a shell is specified */
X
X		if (fp_script == stdin || which_shell(fp_script, shell))
X			(void)strcpy(shell, "$SHELL");
X	}
X
X	/* Put the identification comments in the job file, copy the current */
X	/* environment, and the shell command */
X
X	comments(owner, script, mail, fp_at_file);
X	dumpenv(envp, fp_at_file);
X	shell_start(shell, fp_at_file);
X
X	/* Copy commands from the appropriate source to the job file */
X
X	if (copy_commands(fp_script, fp_at_file) == 0) {
X
X		(void)fprintf(stderr, "nothing specified\n");
X		quit();
X		/* NOTREACHED */
X	}
X
X	/* Change the ownership of the job file to the real user */
X
X	if (chown(at_tmp, getuid(), getgid()) < 0) {
X	
X		perror("can't change the owner/group of your job to you");
X		exit(1);
X	}
X
X	/* Set the mode of the file according to the owner's preference */
X	/* (but allow owner read/write access in any case) */
X	
X	mask = umask(0);
X	(void)umask(mask);
X
X	if (chmod(at_tmp, (mask ^ 0777) | 0600) < 0) {
X
X		perror("can't change the mode of your job");
X		exit(1);
X	}
X
X	/* Close the temporary job file and rename it to the 'at name' */
X
X	(void)fclose(fp_at_file);
X
X	if (rename(at_tmp, at_file) < 0) {
X		
X		perror(at_tmp);
X		(void)unlink(at_tmp);
X		exit(1);
X	}
X
X	/* Say, "Nice user, good user..." */
X
X	(void)printf("job %d at %s", jobno, ctime(&at_time));
X
X	exit(0);
X	/* NOTREACHED */
X}
X
X/* -------------------------------------------------------------------------- */
X
Xvoid
Xquit()
X
X{
X	(void)unlink(at_tmp);
X	(void)unlink(at_file);
X	exit(1);
X}
X
X/* -------------------------------------------------------------------------- */
X
Xcomments(owner, script, mail, fp_at_file)
X
Xchar *owner;
Xchar *script;
Xint mail;
XFILE *fp_at_file;
X
X{
X	(void)fprintf(fp_at_file, "# at job\n");
X	(void)fprintf(fp_at_file, "# owner: %.127s\n", owner);
X	(void)fprintf(fp_at_file, "# jobname: %.127s\n", script);
X	(void)fprintf(fp_at_file, "# shell: sh\n");
X	(void)fprintf(fp_at_file, "# notify by mail: %s\n", mail ? "yes" : "no");
X	(void)fprintf(fp_at_file, "\n");
X}
X
X/* -------------------------------------------------------------------------- */
X
Xcopy_commands(fp_script, fp_at_file)
X
XFILE *fp_script;
XFILE *fp_at_file;
X
X{
X	int ncmd;
X
X	/* If a script has been specified or if stdin has been redirected, */
X	/* read and copy the script */
X
X	if (fp_script != stdin || isatty(0) == 0) {
X
X		for (ncmd = 0 ; fgets(buffer, sizeof(buffer), fp_script) ; ncmd++)
X			fputs(buffer, fp_at_file);
X	}
X
X	/* Otherwise, prompt for each line and copy it */
X
X	else {
X
X		for (ncmd = 0 ; (void)printf("at> "), fgets(buffer, sizeof(buffer),
X				stdin) ; ncmd++)
X			fputs(buffer, fp_at_file);
X
X		(void)printf("<EOT>\n");
X	}
X
X	return(ncmd);
X}
X
X/* -------------------------------------------------------------------------- */
X
X#if defined(GENERIC_SYSV)
X
X/* A dummy routine to keep certain System V machines happy since parse_date */
X/* wants to use it and we want to use the stock version of parse_date */
X
Xftime(dummy)
X
Xstruct timeb *dummy;
X
X{
X}
X
X#endif
X
X/* -------------------------------------------------------------------------- */
X
Xparse_args(nargs, argv)
X
Xint nargs;
Xchar *argv[];
X
X{
X	int i;
X
X	struct timeb *now;
X
X	time_t at_time;
X
X	buffer[0] = '\0';
X
X	/* Build up a string of the specified arguments */
X
X	for (i = 0 ; i < nargs ; i++) {
X
X		(void)strcat(buffer, argv[i]);
X		(void)strcat(buffer, " ");
X	}
X
X	/* Now try to parse it as a time specification */
X
X	now = NULL;
X	at_time = parse_date(buffer, now);
X
X	/* Drop any seconds in the specification */
X
X	if (at_time > 0)
X		at_time -= at_time % 60;
X
X	return(at_time);
X}
X
X/* -------------------------------------------------------------------------- */
X
Xshell_start(shell, fp_at_file)
X
Xchar *shell;
XFILE *fp_at_file;
X
X{
X	char path[MAXPATHLEN];
X	int mask;
X
X	(void)fprintf(fp_at_file,
X		"%s << '...use the rest of this file for input'\n", shell);
X
X#ifdef HAS_GETCWD
X	if (getcwd(path, MAXPATHLEN) == (char *)NULL) {
X#else
X	if (getwd(path) == (char *)NULL) {
X#endif
X
X		(void)fprintf(stderr, "%s\n", path);
X		exit(1);
X	}
X
X	(void)fprintf(fp_at_file, "cd %s\n", path);
X	mask = umask(0);
X	umask(mask);
X	(void)fprintf(fp_at_file, "umask %o\n", mask);
X}
X
X/* -------------------------------------------------------------------------- */
X
Xsignal_init()
X
X{
X	(void)signal(SIGHUP, quit);
X	(void)signal(SIGQUIT, quit);
X	(void)signal(SIGINT, quit);
X	(void)signal(SIGTERM, quit);
X}
X
X/* -------------------------------------------------------------------------- */
X
Xusage()
X
X{
X	(void)printf("usage: at [-c|k|s] [-m] time [date] [script]\n");
X	exit(2);
X}
X
X/* -------------------------------------------------------------------------- */
X
Xwhich_shell(fp_script, shell)
X
XFILE *fp_script;
Xchar *shell;
X
X{
X	char *ptr;
X
X	int length;
X
X	/* Determine if a shell is specified in the script */
X
X	/* If we get EOF, just use the current shell */
X
X	if (fgets(buffer, sizeof(buffer), fp_script) == NULL)
X		return(1);
X	
X	/* Reposition to the beginning of the file */
X
X	if (fseek(fp_script, 0L, 0) < 0) {
X
X		perror("seek on input script failed");
X		exit(1);
X	}
X	
X	/* If we don't get a cookie, forget it */
X
X	if (buffer[0] != '#' || buffer[1] != '!')
X		return(1);
X	
X	/* Skip leading spaces and drop the newline */
X
X	for (ptr = buffer + 2 ; *ptr == ' ' ; ptr++)
X		;
X	
X	length = strlen(ptr);
X	*(ptr + --length) = '\0';
X
X	/* Is this too long? */
X
X	if (length > 32) {
X
X		(void)fprintf(stderr,
X			"shell specifier (%s) in script is too long - using default\n",
X			ptr);
X		return(1);
X	}
X
X	(void)strcpy(shell, ptr);
X	return(0);
X}
END_OF_FILE
if test 8859 -ne `wc -c <'src/at.c'`; then
    echo shar: \"'src/at.c'\" unpacked with wrong size!
fi
# end of 'src/at.c'
fi
if test -f 'src/atq.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/atq.c'\"
else
echo shar: Extracting \"'src/atq.c'\" \(6069 characters\)
sed "s/^X//" >'src/atq.c' <<'END_OF_FILE'
X/* atq --- a member of the kat family (Keith's at package) */
X
X/* atq(1) lists all or part of the queue of jobs submitted via at(1). */
X
X/* Copyright (c) 1990, W. Keith Pyle, Austin, Texas */
X
X#include "at.h"
X
Xchar *verstring = PATCHLEVEL;
X
Xchar *disp_date();
Xchar *nth();
Xint comp_ctime();
Xint comp_start();
X
Xextern char *optarg;
Xextern char *sys_errlist[];
X
Xextern int optind;
Xextern int opterr;
X
X/* -------------------------------------------------------------------------- */
X
Xmain(argc, argv)
X
Xint argc;
Xchar *argv[];
X
X{
X	char jobname[32];					/* Jobname from queued jobfile */
X	char owner[16];						/* Owner of queued job */
X
X	int count_only;						/* TRUE for display count only */
X	int flag;							/* The getopt found flag */
X	int i;								/* An index */
X	int n_jobs;							/* The number of jobs to be listed */
X	int qsize;							/* Size of queue data structure */
X
X	int (*sort_func)();					/* Pointer to function for sort use */
X
X	struct dirent *entry;				/* Pointer to directory entry */
X	struct queue *atq;					/* The queue list data structure ptr */
X	register struct queue *qptr;		/* A working queue list pointer */
X
X	DIR *dp;							/* Descriptor for queue directory */
X
X	/* Check for allowed at usage */
X	 
X	whoami(owner);
X	
X	/* Change to the at directory and check permissions */
X
X	if (chdir(ATDIR) < 0) {
X
X		perror("can't change directory to the at directory");
X		exit(1);
X	}
X
X	if (!permit(owner, ALLOW_FILE, DENY_FILE)) {
X 
X		fprintf(stderr, "you are not authorized to use at.  Sorry.\n");
X		exit(1);
X	}
X
X	/* Some initialization */
X
X	count_only = FALSE;
X	qsize = 0;
X	sort_func = comp_start;
X
X	/* Parse any arguments */
X
X	while ((flag = getopt(argc, argv, "cn")) != EOF) {
X
X		switch (flag) {
X
X			case 'c':
X
X				sort_func = comp_ctime;
X				break;
X			
X			case 'n':
X
X				count_only = TRUE;
X				break;
X
X			default:
X
X				fprintf(stderr, "usage: atq [-c|n] [username]\n");
X				exit(2);
X		}
X	}
X
X	/* Try to open the at job directory */
X
X	if ((dp = opendir(".")) == NULL) {
X
X		perror("can't read the at directory");
X		exit(1);
X	}
X
X	/* Process all of the directory entries which have names of the proper */
X	/* format for a queued at job */
X
X	while ((entry = readdir(dp)) != NULL) {
X
X		int jobno;
X
X		time_t tod;
X
X		struct stat statbuf;
X
X		/* Is the name of the proper format? */
X
X		if (sscanf(entry->d_name, "%d.%d", &tod, &jobno) != 2)
X			continue;
X		
X		if (stat(entry->d_name, &statbuf) < 0) {
X
X			perror(entry->d_name);
X			continue;
X		}
X
X		/* If there are names in the argument list, do any of them */
X		/* match the owner of this jobfile? */
X
X		if (optind < argc) {
X
X			int found;
X
X			struct passwd *pw;
X
X			if ((pw = getpwuid(statbuf.st_uid)) == NULL) {
X
X				perror(entry->d_name);
X				continue;
X			}
X
X			found = FALSE;
X
X			for (i = optind ; i < argc ; i++) {
X
X				if (strcmp(argv[i], pw->pw_name) == 0) {
X					
X					found = TRUE;
X					break;
X				}
X			}
X
X			if (!found)
X				continue;
X		}
X
X		/* This entry should be listed.  Make sure there is space */
X		/* in the queue data structure to store it */
X
X		if ((n_jobs + 1) > qsize) {
X
X			if ((qsize = qalloc(&atq, qsize)) < 0) {
X
X				fprintf(stderr, "can't allocate space for queue data\n");
X				exit(1);
X			}
X		}
X			
X		/* Save the info */
X
X		atq[n_jobs].qu_start = tod;
X		strcpy(atq[n_jobs].qu_name, entry->d_name);
X		atq[n_jobs].qu_jobno = jobno;
X		atq[n_jobs].qu_ctime = statbuf.st_ctime;
X		n_jobs++;
X	}
X
X	closedir(dp);
X
X	/* Do we do a count only? */
X
X	if (count_only) {
X
X		printf("%d\n", n_jobs);
X		exit(0);
X	}
X
X	/* Were there any jobs? */
X
X	if (n_jobs == 0) {
X
X		printf("no files in queue\n");
X		exit(0);
X	}
X
X	/* If more than one job, sort them */
X
X	else if (n_jobs > 1)
X		qsort(atq, n_jobs, sizeof(struct queue), sort_func);
X	
X	/* Print the info on the selected queued entries */
X
X	printf(" Rank     Execution Date     Owner     Job #   Job Name\n");
X
X	for (i = 1, qptr = atq ; i <= n_jobs ; i++, qptr++) {
X
X		if (get_ident(qptr->qu_name, owner, jobname) < 0)
X			continue;
X
X		printf("%3d%s   %s   %-10s%5d   %s\n", i, nth(i),
X			disp_date(qptr->qu_start), owner, qptr->qu_jobno, jobname);
X	}
X
X	exit(0);
X}
X
X/* -------------------------------------------------------------------------- */
X
Xcomp_ctime(str1, str2)
X
Xstruct queue *str1;
Xstruct queue *str2;
X
X{
X	/* Compare the creation times of the two entries */
X
X	return(str1->qu_ctime - str2->qu_ctime);
X}
X
X/* -------------------------------------------------------------------------- */
X
Xcomp_start(str1, str2)
X
Xstruct queue *str1;
Xstruct queue *str2;
X
X{        
X	/* Compare the start times of the two entries */
X
X	return(str1->qu_start - str2->qu_start);
X}
X
X/* -------------------------------------------------------------------------- */
X
Xchar *
Xdisp_date(tod)
X
Xtime_t tod;
X
X{
X	static char datebuf[20];
X	static char *mon[] = {
X		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
X		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
X		};
X
X	struct tm *tm;
X
X	tm = localtime(&tod);
X	sprintf(datebuf, "%s %2d, %4d %02d:%02d", mon[tm->tm_mon], tm->tm_mday,
X		tm->tm_year + 1900, tm->tm_hour, tm->tm_min);
X	
X	return(datebuf);
X}
X
X/* -------------------------------------------------------------------------- */
X
Xget_ident(jobfile, owner, jobname)
X
Xchar *jobfile;
Xchar *owner;
Xchar *jobname;
X
X{
X	char line[MAXLINE];
X
X	int found_job;
X	int found_owner;
X
X	FILE *fp;
X
X	/* Open the jobfile */
X
X	if ((fp = fopen(jobfile, "r")) == NULL) {
X
X		printf("atq: Can't open job file %s: %s\n",
X			jobfile, sys_errlist[errno]);
X		return(-1);
X	}
X
X	/* Look for the owner and jobname lines of the jobfile */
X	
X	found_job = found_owner = FALSE;
X	*owner = *jobname = '\0';
X
X	while (fgets(line, sizeof(line), fp)) {
X
X		if (sscanf(line, "# owner: %10s", owner) == 1)
X			found_owner = TRUE;
X		
X		else if (sscanf(line, "# jobname: %27s%*[^\n]", jobname) == 1)
X			found_job = TRUE;
X		
X		if (found_owner && found_job)
X			break;
X	}
X
X	fclose(fp);
X	return(0);
X}
X
X/* -------------------------------------------------------------------------- */
X
Xchar *
Xnth(n)
X
Xint n;
X
X{
X	switch (n) {
X
X		case 1:
X
X			return("st");
X		
X		case 2:
X
X			return("nd");
X		
X		case 3:
X
X			return("rd");
X		
X		default:
X
X			return("th");
X	}
X}
END_OF_FILE
if test 6069 -ne `wc -c <'src/atq.c'`; then
    echo shar: \"'src/atq.c'\" unpacked with wrong size!
fi
# end of 'src/atq.c'
fi
if test -f 'src/atrun.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/atrun.c'\"
else
echo shar: Extracting \"'src/atrun.c'\" \(17097 characters\)
sed "s/^X//" >'src/atrun.c' <<'END_OF_FILE'
X/* atrun --- a member of the kat family (Keith's at package) */
X
X/* atrun(8) controls execution of jobs in the at(1) queue.  Provision is */
X/* made to mail output generated during the execution to the submitting */
X/* user and to control the number of concurrent at processes.  atrun(8) */
X/* is typically run by cron periodically (e.g., every 15 minutes). */
X
X/* Copyright (c) 1990, W. Keith Pyle, Austin, Texas */
X
X#include "at.h"
X
Xchar *verstring = PATCHLEVEL;
X
X#include <syslog.h>
X#ifdef HAS_TERMIO
X#include <termio.h>
X#else
X#include <sgtty.h> 
X#endif
X
X#define LOCKFILE	"atrun.lock"
X
Xchar *progname;
X
X#ifdef DEBUG
X
X/* These options define logging behavior for a debugging version only */
X
Xint debug_level;
Xextern char *optarg;
X
X#ifdef NOSYSLOG
X#define syslog	fprintf
X#define LOGDEST	logfile
X#define LOGFILE	"./atrun.debug"
XFILE *logfile;
X#endif
X
X#else
X
X/* These options apply to logging in the production version */
X
X#ifdef HAS_SYSLOGFACILITY
X#define LOGDEST LOG_ERR | LOG_DAEMON
X#else
X#define LOGDEST LOG_ERR
X#endif
X
X#endif	/* DEBUG */
X
Xchar *rindex();
X
Xint comp_start();
X
Xextern char *sys_errlist[];
X
X/* -------------------------------------------------------------------------- */
X
Xmain(argc, argv)
X
Xint argc;
Xchar *argv[];
X
X{
X	char lockfile[MAXPATHLEN];
X	char logname[32];
X	char *ptr;
X
X	int flag;
X	int n_jobs;
X	int tod;
X
X	struct queue *atq;
X
X	FILE *fp;
X
X	/* Make sure only root runs this */
X
X	if (geteuid() != 0) {
X
X		fprintf(stderr, "permission denied\n");
X		exit(1);
X	}
X
X	/* Make sure we are allowed to run: check for lock file */
X
X	sprintf(lockfile, "%s/%s", ATDIR, LOCKFILE);
X
X	if (access(lockfile, F_OK) == 0) {
X
X		fprintf(stderr, "atrun.lock exists: cannot execute\n");
X		exit(1);
X	}
X
X	/* Convert to a daemon */
X
X	daemonize();
X
X	/* Set up syslog options */
X
X#ifndef NOSYSLOG
X	if ((ptr = rindex(argv[0], '/')) != NULL)
X		ptr++;
X	else
X		ptr = argv[0];
X	
X	strcpy(logname, ptr);
X	
X#ifdef HAS_SYSLOGFACILITY
X	openlog(logname, LOG_CONS, LOG_DAEMON);
X#else
X	openlog(logname, 0);
X#endif
X#else
X	if ((logfile = fopen(LOGFILE, "a")) == NULL) {
X
X		perror(LOGFILE);
X		exit(1);
X	}
X
X	setbuf(logfile, NULL);
X	time(&tod);
X	fprintf(logfile, "atrun started at %s", ctime(&tod));
X#endif
X
X	/* Make our name stand out for ps(1) */
X
X	progname = argv[0];
X
X	for (ptr = argv[0] ; *ptr ; ptr++)
X		*ptr = islower(*ptr) ? toupper(*ptr) : *ptr;
X
X#ifdef DEBUG
X	/* In the DEBUG version, check for a debug level argument */
X
X	while ((flag = getopt(argc, argv, "x")) != EOF) {
X
X		switch (flag) {
X
X			case 'x':
X
X				debug_level = TRUE;
X				break;
X			
X			default:
X
X				syslog(LOGDEST, "invalid argument: %c\n", flag);
X				exit(1);
X		}
X	}
X#endif
X
X	/* Change to the at job directory */
X
X	if (chdir(ATDIR) < 0) {
X
X		syslog(LOGDEST, "can't chdir to %s: %s\n", ATDIR, sys_errlist[errno]);
X		exit(1);
X	}
X
X	/* Make sure we can update the last done file */
X
X	if ((fp = fopen("lasttimedone", "w")) == NULL) {
X
X		syslog(LOGDEST, "can't open 'lasttimedone': %s\n", sys_errlist[errno]);
X		exit(1);
X	}
X
X	/* Select the job files to be run */
X
X	n_jobs = select_jobs(&atq);
X
X	/* Update the last done file */
X
X	time(&tod);
X	fprintf(fp, "%d (%.19s)\n", tod, ctime(&tod));
X	fclose(fp);
X
X	/* Now run the selected jobs */
X
X	if (n_jobs != 0)
X		run_all(n_jobs, atq);
X
X	exit(0);
X}
X
X/* -------------------------------------------------------------------------- */
X
X/* Compare function for use with sort: compare job start times */
X
Xcomp_start(ptr1, ptr2)
X
Xstruct queue *ptr1;
Xstruct queue *ptr2;
X
X{
X	return(ptr1->qu_start - ptr2->qu_start);
X}
X
X/* -------------------------------------------------------------------------- */
X
X/* Determine if the user requested mail confirming the completion */
X/* of the job */
X
Xconfirmation(mail_uid, jobfile, jobno)
X
Xint mail_uid;
Xchar *jobfile;
Xint jobno;
X
X{
X	char buffer[MAXLINE];
X
X	int confirm;
X
X	FILE *jobfile_fp;
X
X	confirm = FALSE;
X
X	/* Open the job file and look for the notify line */
X
X	if ((jobfile_fp = fopen(jobfile, "r")) != NULL) {
X
X		while (fgets(buffer, sizeof(buffer), jobfile_fp)) {
X
X			if (strcmp(buffer, "# notify by mail: yes\n") == 0) {
X
X				confirm = TRUE;
X				break;
X			}
X		}
X
X		fclose(jobfile_fp);
X	}
X
X	/* Did we find a comfirm line? */
X
X	if (confirm) {
X
X		/* Oh, yes, we did! */
X
X		open_mail(mail_uid);
X		sprintf(buffer, "Your \"at\" job \"%d\" completed.\n", jobno);
X		write(1, buffer, strlen(buffer));
X	}
X
X	return(confirm);
X}
X
X/* -------------------------------------------------------------------------- */
X
X/* Send generated output to user via mail */
X
Xcopy_output(jobfile, jobno, outfile)
X
Xchar *jobfile;
Xint jobno;
Xchar *outfile;
X
X{
X	char buffer [8192];
X
X	int bytes;
X	int outfile_fd;
X
X	sprintf(buffer,
X		"Your \"at\" job \"%d\" generated the following output:\n\n",
X		jobno);
X	write(1, buffer, strlen(buffer));
X
X	/* Open the generated output file and write it to sendmail */
X
X	if ((outfile_fd = open(outfile, O_RDONLY)) >= 0) {
X
X		while ((bytes = read(outfile_fd, buffer, sizeof(buffer))) > 0)
X			write(1, buffer, bytes);
X	
X		close(outfile_fd);
X	}
X
X	else
X		syslog(LOGDEST,
X			"couldn't open output %s from jobfile %s for reading: %s\n",
X			outfile, jobfile, sys_errlist[errno]);
X}
X
X/* -------------------------------------------------------------------------- */
X
X/* Convert the master process to a daemon */
X
Xdaemonize()
X
X{
X	int fd;
X	int pid;
X
X	/* Ignore the terminal stop signals */
X
X	(void) signal(SIGTTOU, SIG_IGN);
X	(void) signal(SIGTTIN, SIG_IGN);
X	(void) signal(SIGTSTP, SIG_IGN);
X
X	/* Fork to a child process, parent terminates */
X
X	if ((pid = fork()) < 0) {
X
X		syslog(LOGDEST, "could not fork to create daemon\n");
X		exit(1);
X	}
X
X	else if (pid > 0)
X		exit(0);
X	
X	/* Disassociate ourselves from the old process group */
X
X	if (setpgrp(0, getpid()) == -1) {
X
X		syslog(LOGDEST, "couldn't set process group in master atrun\n");
X		exit(1);
X	}
X
X#ifdef HAS_TIOCNOTTY
X	/* Lose the controlling terminal */
X
X	if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
X
X		(void) ioctl(fd, TIOCNOTTY, (char *) NULL);
X		(void) close(fd);
X	}
X#endif
X
X	/* Make stdin, stdout, and stderr worthless but keep them as placeholders */
X	/* since we may need to do forks and use them in the child */
X
X	close(0);
X	close(1);
X
X	if (open("/dev/null", O_RDONLY) != 0) {
X
X		syslog(LOGDEST, "couldn't reopen stdin as /dev/null");
X		exit(1);
X	}
X
X	if (open("/dev/null", O_WRONLY) != 1) {
X
X		syslog(LOGDEST, "couldn't reopen stdout as /dev/null");
X		exit(1);
X	}
X
X	close(2);
X	dup(1);
X}
X
X/* -------------------------------------------------------------------------- */
X
X/* Create a sendmail process to mail output to the at job owner */
X
Xopen_mail(uid)
X
Xuid_t uid;
X
X{
X	int mail_pipe[2];
X	int pid;
X
X	struct passwd *pw;
X
X	/* Determine the user name for this job */
X
X	if ((pw = getpwuid(uid)) == NULL) {
X
X		syslog(LOGDEST, "can't find passwd entry for uid %d: %s\n",
X			uid, sys_errlist[errno]);
X		return;
X	}
X
X#ifdef DEBUG
X	if (debug_level)
X		syslog(LOGDEST, "opening mail for %s in slave atrun (pid=%d)\n",
X			pw->pw_name, getpid());
X#endif
X
X	if (pipe(mail_pipe) < 0) {
X
X		syslog(LOGDEST, "can't create pipe for mail: %s\n", sys_errlist[errno]);
X		return;
X	}
X
X	if ((pid = fork()) < 0) {
X
X		syslog(LOGDEST, "can't fork for mail: %s\n", sys_errlist[errno]);
X		return;
X	}
X
X	else if (pid > 0) {
X
X		char buffer[256];
X
X		/* Parent (slave atrun): make stdout go to sendmail and write header */
X
X		close(1);
X		dup(mail_pipe[1]);
X		close(mail_pipe[0]);
X		close(mail_pipe[1]);
X
X#ifdef USE_SENDMAIL
X		sprintf(buffer, "To: %s\nSubject: Output from \"at\" job\n\n",
X			pw->pw_name);
X		write(1, buffer, strlen(buffer));
X#endif
X
X		return;
X	}
X
X	else {
X
X		/* Child: convert pipe to stdin and exec to sendmail */
X
X		close(0);
X		dup(mail_pipe[0]);
X		close(mail_pipe[0]);
X		close(mail_pipe[1]);
X
X#ifdef USE_SENDMAIL
X		execl("/usr/lib/sendmail", "sendmail", "-t", (char *)NULL);
X#endif
X
X#ifdef USE_UCBMAIL
X		execl("/usr/ucb/Mail", "Mail", "-s", "Output from \"at\" job",
X			pw->pw_name, (char *)NULL);
X#endif
X
X#ifdef USE_BINMAIL
X		execl("/bin/mail", "mail", pw->pw_name, (char *)NULL);
X#endif
X
X		syslog(LOGDEST, "exec to mailer failed: %s\n", sys_errlist[errno]);
X		exit(1);
X	}
X}
X
X/* -------------------------------------------------------------------------- */
X
X/* If a slave atrun fails, this will try to move the jobfile back to the */
X/* normal queue directory */
X
Xrecover(jobfile)
X
Xchar *jobfile;
X
X{
X	char newname[MAXPATHLEN];
X
X#ifdef DEBUG
X	if (debug_level)
X		syslog(LOGDEST,
X			"attempting recovery of jobfile %s in slave atrun (pid=%d)\n",
X			jobfile, getpid());
X#endif
X
X	sprintf(newname, "%s/%s", ATDIR, jobfile);
X
X	if (rename(jobfile, newname) < 0)
X		syslog(LOGDEST,
X			"couldn't recover jobfile %s from the past directory: %s\n",
X			jobfile, sys_errlist[errno]);
X}
X
X/* -------------------------------------------------------------------------- */
X
X/* Create slave atrun processes for each job in queue. Manage total */
X/* number running so as not to exceed configured limit. */
X
Xrun_all(n_jobs, atq)
X
Xint n_jobs;
Xstruct queue *atq;
X
X{
X	int i;
X	int n_slave;
X	int status;
X
X	register struct queue *qptr;
X
X	/* cd to the working directory */
X
X	if (chdir(PASTDIR) < 0) {
X
X		syslog(LOGDEST, "can't chdir to past: %s\n", sys_errlist[errno]);
X		exit(1);
X	}
X
X	/* Sort the entries in order of starting time */
X
X	if (n_jobs > 1)
X		qsort(atq, n_jobs, sizeof(struct queue), comp_start);
X
X	/* Run them */
X
X	n_slave = 0;
X
X	for (i = 0 , qptr = atq ; i < n_jobs ; i++, qptr++) {
X
X#if MAXATPROC
X		/* Check number of slaves running */
X
X		if (n_slave >= MAXATPROC) {
X
X			/* At configured limit; wait for one to finish */
X
X#ifdef DEBUG
X			if (debug_level)
X				syslog(LOGDEST,
X					"MAXATPROC hit; waiting for slave to finish...\n");
X#endif
X
X			if (wait(&status) == -1)
X				syslog(LOGDEST,
X					"shouldn't happen: wait reports no slaves running\n");
X
X			n_slave--;
X		}
X#endif	/* MAXATPROC */
X
X		/* Run the next queue entry */
X
X		run_one(qptr);
X		n_slave++;
X	}
X
X	/* Wait for any remaining slaves to finish */
X
X	while (wait(&status) != -1)
X		;
X}
X
X/* -------------------------------------------------------------------------- */
X
X/* Manage creation and execution of one queue entry */
X
Xrun_one(qptr)
X
Xstruct queue *qptr;
X
X{
X	char *ptr;
X	char *jobfile;
X	char outfile[32];
X
X	int jobno;
X	int pid;
X	int mail_pipe[2];
X	int outfile_fd;
X
X	struct stat statbuf;
X
X	jobfile = qptr->qu_name;
X	jobno = qptr->qu_jobno;
X
X	/* Create a slave atrun; return if master process */
X
X	if ((pid = fork()) < 0) {
X
X		syslog(LOGDEST, "can't create fork slave atrun for jobfile %s: %s\n",
X			jobfile, sys_errlist[errno]);
X		recover(jobfile);
X		return;
X	}
X
X	else if (pid > 0)
X		return;
X
X#ifdef DEBUG
X	if (debug_level)
X		syslog(LOGDEST, "slave atrun (pid=%d) created for jobfile %s\n",
X			getpid(), jobfile);
X#endif
X	
X	/* We're now a slave atrun responsible for only one jobfile. */
X	/* Fix our name so ps(1) shows it a little differently than the master. */
X
X	if ((ptr = rindex(progname, '/')) != NULL)
X		ptr += 2;
X	else
X		ptr = progname + 1;
X
X	for ( ; *ptr ; ptr++)
X		*ptr = isupper(*ptr) ? tolower(*ptr) : *ptr;
X
X	/* Create a file to receive the stdout and stderr from the at job. */
X
X	strcpy(outfile, "atoutXXXXXX");
X
X	if (mktemp(outfile) == NULL) {
X
X		syslog(LOGDEST,
X			"can't create tmp file for output from jobfile %s: %s\n",
X			jobfile, sys_errlist[errno]);
X		recover(jobfile);
X		exit(1);
X	}
X
X	if ((outfile_fd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC,
X			0600)) < 0) {
X		
X		syslog(LOGDEST,
X			"can't open tmp file for output from jobfile %s: %s\n",
X			jobfile, sys_errlist[errno]);
X		recover(jobfile);
X		exit(1);
X	}
X
X#ifdef DEBUG
X	syslog(LOGDEST,
X		"tmp file %s created for jobfile %s in slave atrun (pid=%d)\n",
X		outfile, jobfile, getpid());
X#endif
X
X	/* Find out who the owner of the job should be */
X
X	if (stat(jobfile, &statbuf) < 0) {
X
X		syslog(LOGDEST, "couldn't stat jobfile %s: %s\n", jobfile,
X			sys_errlist[errno]);
X		recover(jobfile);
X		exit(1);
X	}
X
X	/* Fork to create child for running at job */
X
X	if ((pid = fork()) < 0) {
X
X		syslog(LOGDEST, "can't fork from slave atrun for jobfile %s: %s\n",
X			jobfile, sys_errlist[errno]);
X		recover(jobfile);
X		exit(1);
X	}
X
X	else if (pid == 0) {
X
X		/* Child: close stdout and stderr, dup the write pipe onto both */
X
X		close(1);
X		dup(outfile_fd);
X		close(2);
X		dup(outfile_fd);
X		close(outfile_fd);
X
X		/* Set the group and owner for the at job */
X
X		if(setgid(statbuf.st_gid) < 0) {
X
X			syslog(LOGDEST, "couldn't set gid for jobfile %s: %s\n",
X				jobfile, sys_errlist[errno]);
X			recover(jobfile);
X			exit(1);
X		}
X
X		if(setuid(statbuf.st_uid) < 0) {
X
X			syslog(LOGDEST, "couldn't set uid for jobfile %s: %s\n",
X				jobfile, sys_errlist[errno]);
X			recover(jobfile);
X			exit(1);
X		}
X
X		/* Now run the at job via sh passing a NULL environment */
X
X		execle("/bin/sh", "sh", jobfile, (char *)NULL, (char *)NULL);
X
X		/* If we're here, the exec failed! */
X
X		syslog(LOGDEST,
X			"could exec /bin/sh from slave atrun for jobfile %s: %s\n",
X			jobfile, sys_errlist[errno]);
X		recover(jobfile);
X		exit(1);
X	}
X
X	else {
X
X		int mail_open;
X		int mail_uid;
X		int status;
X
X#ifdef DEBUG
X		if (debug_level)
X			syslog(LOGDEST,
X				"slave atrun (pid=%d) created child (pid=%d) to 'sh %s'\n",
X				getpid(), pid, jobfile);
X#endif
X
X		/* Parent (slave atrun): close tmp output file */
X
X		close(outfile_fd);
X		mail_open = FALSE;
X		mail_uid = statbuf.st_uid;
X
X		/* Wait for an exit status from the at job */
X
X		wait(&status);
X#ifdef DEBUG
X		if (debug_level)
X			syslog(LOGDEST, "jobfile %s (pid=%d) terminated with status=%d\n",
X				jobfile, pid, status);
X#endif
X
X		/* Determine if the job generated any output: stat the file ... */
X
X		if (stat(outfile, &statbuf) < 0) {
X
X			syslog(LOGDEST, "couldn't stat output file for jobfile %s: %s\n",
X				jobfile, sys_errlist[errno]);
X		}
X
X		/* ... and check its size */
X
X		else if (statbuf.st_size > 0) {
X
X			mail_open = TRUE;
X			open_mail(mail_uid);
X			copy_output(jobfile, jobno, outfile);
X		}
X
X#ifdef DEBUG
X		if (debug_level)
X			syslog(LOGDEST, "output file for jobfile %s was %d bytes\n",
X				jobfile, statbuf.st_size);
X#endif
X
X		/* Dispose of the temporary file */
X
X		if (unlink(outfile) < 0)
X			syslog(LOGDEST, "couldn't unlink output %s from jobfile %s: %s\n",
X				outfile, jobfile, sys_errlist[errno]);
X
X		/* If an error status is received, put it in the mail message */
X
X		if (status != 0) {
X
X			char buffer[128];
X
X			if (!mail_open)
X				open_mail(mail_uid);
X			else
X				write(1, "\n", 1);
X
X			sprintf(buffer, "Your \"at\" job \"%d\" exited with status=%d\n",
X				jobno, status);
X			write(1, buffer, strlen(buffer));
X		}
X
X		/* If there hasn't been any mail generated, see if the user */
X		/* requested confirmation of the run by mail */
X
X		else if (!mail_open)
X			mail_open = confirmation(mail_uid, jobfile, jobno);
X
X		/* Close the mail pipe, if any; check status */
X
X		if (mail_open) {
X
X			close(1);
X
X			if (wait(&status) != -1 && status != 0)
X				syslog(LOGDEST, "mailer exited with status=%d\n", status);
X		}
X		
X		/* Dispose of the at jobfile */
X
X		if (unlink(jobfile) < 0)
X			syslog(LOGDEST, "can't unlink completed jobfile %s: %s\n",
X				jobfile, sys_errlist[errno]);
X
X		/* Exit the slave atrun */
X
X		exit(0);
X	}
X}
X
X/* -------------------------------------------------------------------------- */
X
X/* Select at jobfiles from the queue directory that should be run.  Move */
X/* them to the working directory and store their file names. */
X
Xselect_jobs(atq)
X
Xstruct queue **atq;
X
X{
X	char buffer[MAXPATHLEN];
X
X	int n_jobs;
X	int qsize;
X
X	struct dirent *entry;
X	register struct queue *qptr;
X
X	time_t tod;
X
X	DIR *dp;
X
X	n_jobs = 0;
X	qsize = 0;
X	time(&tod);
X
X	/* Open the queue directory */
X
X	if ((dp = opendir(".")) == NULL) {
X
X		syslog(LOGDEST, "can't open at directory\n");
X		exit(1);
X	}
X
X	/* Get the name of each directory entry */
X
X	while ((entry = readdir(dp)) != NULL) {
X
X		int runtime;
X		int jobno;
X
X		struct stat statbuf;
X
X		/* See if the file name matches the jobfile name format */
X
X		if (sscanf(entry->d_name, "%d.%d", &runtime, &jobno) != 2) {
X
X#ifdef DEBUG
X			if (debug_level)
X				syslog(LOGDEST, "file %s rejected - not a jobfile name\n",
X					entry->d_name);
X#endif
X			continue;
X		}
X
X		/* Should this one be run yet? */
X		
X		if (runtime > tod) {
X
X#ifdef DEBUG
X			if (debug_level)
X				syslog(LOGDEST, "jobfile %s rejected - not time yet\n",
X					entry->d_name);
X#endif
X			continue;
X		}
X
X		/* Is the queue data structure large enough to store this entry? */
X
X		if ((n_jobs + 1) > qsize) {
X
X			if ((qsize = qalloc(atq, qsize)) < 0) {
X
X				fprintf(stderr, "can't allocate space for queue data\n");
X				exit(1);
X			}
X
X			qptr = *atq;
X		}
X
X		/* Save the info on this entry */
X
X		qptr[n_jobs].qu_start = runtime;
X		qptr[n_jobs].qu_jobno = jobno;
X		strcpy(qptr[n_jobs].qu_name, entry->d_name);
X		
X		/* Move the entry to the working directory */
X
X		sprintf(buffer, "%s/%s", PASTDIR, entry->d_name);
X
X		if (rename(entry->d_name, buffer) < 0) {
X
X			syslog(LOGDEST, "can't rename job file %s: %s\n",
X				entry->d_name, sys_errlist[errno]);
X			continue;
X		}
X
X#ifdef DEBUG
X		if (debug_level)
X			syslog(LOGDEST, "jobfile %s selected\n", entry->d_name);
X#endif
X
X		n_jobs++;
X	}
X
X	closedir(dp);
X
X#ifdef DEBUG
X	if (debug_level)
X		syslog(LOGDEST, "%d jobs selected\n", n_jobs);
X#endif
X
X	return(n_jobs);
X}
END_OF_FILE
if test 17097 -ne `wc -c <'src/atrun.c'`; then
    echo shar: \"'src/atrun.c'\" unpacked with wrong size!
fi
# end of 'src/atrun.c'
fi
if test -f 'src/date.y' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/date.y'\"
else
echo shar: Extracting \"'src/date.y'\" \(13039 characters\)
sed "s/^X//" >'src/date.y' <<'END_OF_FILE'
X%token MONTH DAY RELDAY MERIDIAN NUMBER ORDINAL UNIT MONUNIT
X%token SECOND STDZONE DSTZONE ABSHOUR STRING
X
X%{
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <ctype.h>
X#include <sys/timeb.h>
X#include <sys/time.h>
X
X/* Define some useful time constants */
X
X#define SEC_PER_MIN		(60)
X#define SEC_PER_HOUR	(3600)
X#define MIN_PER_HOUR	(60)
X#define HOUR_PER_DAY	(24)
X#define SEC_PER_DAY		(SEC_PER_HOUR * HOUR_PER_DAY)
X#define DAY_PER_WEEK	(7)
X
X#define EPOCH			1970
X
X/* Define some symbolic values */
X
X#define FALSE		0
X#define TRUE		1
X
X#define AM			0
X#define PM			1
X#define MILITARY	2
X
X#define	NOT_GIVEN	-1
X#define STANDARD	0
X#define DST			1
X
X/* Declare external values used by the following routines */
X
Xstatic char *lptr;			/* Pointer to input string for parsing */
Xstatic char *lstring;		/* Input string pointer (allocated space) */
X
Xchar yyerrmsg[1024];		/* Description of any detected error */
X
Xstatic int day;				/* Day-of-month value */
Xstatic int hour;			/* Hour value */
Xstatic int inc_mon;			/* Number of incremental months */
Xstatic int meridian;		/* AM/PM/Military flag */
Xstatic int minute;			/* Minute value */
Xstatic int month;			/* Month value */
Xstatic int ordinal;			/* Ordinal value (first=1, etc.) */
Xstatic int second;			/* Second value */
Xstatic int timezone;		/* Time zone value */
Xstatic int weekday;			/* Day-of-week value */
Xstatic int year;			/* Year value */
Xstatic int zone_type;		/* Indicates if DST is possible */
X
Xstatic int month_len[12] =
X	{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};	/* Length of months */
X
Xstatic time_t inc_sec;		/* Number of incremental seconds */
Xstatic time_t rel_sec;		/* Number of relative day seconds */
X
X/* The following externals contain field counts used in syntax checking */
X
Xstatic int date_fields;		/* Number of date fields */
Xstatic int day_fields;		/* Number of day of week fields */
Xstatic int inc_fields;		/* Number of increment/decrement fields */
Xstatic int rday_fields;		/* Number of relative day fields */
Xstatic int time_fields;		/* Number of time-of-day fields */
Xstatic int zone_fields;		/* Number of time zone fields */
X
X/* Declare function types */
X
Xchar *malloc();
Xtime_t parse_date();
Xvoid num_to_time();
X
X/* Declare external variables */
X
Xextern char *sys_errlist[];
X
Xextern int errno;
X
X%}
X
X%%
X
Xfullspec: timestr increment
X	;
X
Xtimestr: /* empty */
X	| timestr field
X	;
X
Xfield:  date
X		{ date_fields++; }
X	| day
X		{ day_fields++; }
X	| number
X	| relday
X		{ rday_fields++; }
X	| time
X		{ time_fields++; }
X	| zone
X		{ zone_fields++; }
X	;
X
Xdate:	NUMBER '/' NUMBER
X		{ month = $1 - 1; day = $3; }
X	| NUMBER '/' NUMBER '/' NUMBER
X		{ month = $1 - 1; day = $3; year = $5; }
X	| MONTH NUMBER
X		{ month = $1; day = $2; }
X	| MONTH NUMBER ',' NUMBER
X		{ month = $1; day = $2; year = $4; }
X	| NUMBER MONTH NUMBER
X		{ month = $2; day = $1; year = $3; }
X	| NUMBER MONTH
X		{ month = $2; day = $1; }
X	;
X
Xday:	DAY
X		{ weekday = $1; ordinal = 1; }
X	| DAY ','
X		{ weekday = $1; ordinal = 1; }
X	| ORDINAL DAY
X		{ weekday = $2; ordinal = $1; }
X	| SECOND DAY
X		{ weekday = $2; ordinal = 2; }
X	;
X
Xrelday:	RELDAY
X		{ rel_sec += $1; }
X	| ORDINAL RELDAY
X		{ rel_sec += ($1 * $2); }
X	| SECOND RELDAY
X		{ rel_sec += (2 * $2); }
X	;
X
Xtime:	NUMBER MERIDIAN
X		{ hour = $1; minute = 0;  second = 0; meridian = $2; }
X	| NUMBER ':' NUMBER
X		{ hour = $1; minute = $3; second = 0; meridian = MILITARY; }
X	| NUMBER ':' NUMBER MERIDIAN
X		{ hour = $1; minute = $3; second = 0; meridian = $4; }
X	| NUMBER ':' NUMBER ':' NUMBER
X		{ hour = $1; minute = $3; second = $5; meridian = MILITARY; }
X	| NUMBER ':' NUMBER ':' NUMBER MERIDIAN
X		{ hour = $1; minute = $3; second = $5; meridian = $6; }
X	| ABSHOUR
X		{ hour = $1; minute = 0; second = 0; meridian = MILITARY; }
X	;
X
Xzone:	STDZONE
X		{ timezone = $1; zone_type = STANDARD; }
X	| DSTZONE
X		{ timezone = $1; zone_type = DST; }
X	;
X
Xincrement:	/* empty */
X	| increment incfield
X		{ inc_fields++; }
X	;
X
Xincfield:	sign unit
X		{ inc_sec += ($1 * $2); }
X	| sign NUMBER unit
X		{ inc_sec += ($1 * $2 * $3); }
X	| sign MONUNIT
X		{ inc_mon += ($1 * $2); }
X	| sign NUMBER MONUNIT
X		{ inc_mon += ($1 * $2 * $3); }
X	;
X
Xsign:	'+'
X		{ $$ = 1; }
X	| '-'
X		{ $$ = -1; }
X	;
X
Xunit:	UNIT
X	| SECOND
X	;
X
Xnumber:	NUMBER
X		{
X			if (time_fields > 0 && date_fields > 0)
X				year = $1;
X			else {
X
X				time_fields++;
X				num_to_time($1);
X				meridian = MILITARY;
X			}
X		}
X	;
X
X%%
X
X/* ------------------------------------------------------------------------- */
X
X#include "lex.yy.c"
X
X/* ------------------------------------------------------------------------- */
X
Xtime_t
Xparse_date(string, base)
X
Xchar *string;
Xstruct timeb *base;
X
X{
X	register int i;
X	int length;
X	int rc;
X
X	struct timeb *ourtime;
X	struct timeb current;
X	struct tm *local;
X
X	time_t tod;
X
X	/* Initialize the various global variables */
X
X	date_fields = 0;
X	day_fields = 0;
X	inc_fields = 0;
X	rday_fields = 0;
X	time_fields = 0;
X	zone_fields = 0;
X
X	inc_mon = 0;
X	inc_sec = 0;
X	rel_sec = 0;
X	ordinal = 0;
X	meridian = MILITARY;
X	yyerrmsg[0] = '\0';
X	zone_type = NOT_GIVEN;
X
X	/* Determine the base time (for "now" and relative references) */
X
X	if (base == NULL) {
X
X		ourtime = &current;
X
X		if (ftime(ourtime) < 0) {
X
X			sprintf(yyerrmsg, "can't get current time via ftime: %s",
X				sys_errlist[errno]);
X			return(-1);
X		}
X	}
X
X	else
X		ourtime = base;
X	
X	tod = ourtime->time;
X	
X	if ((local = localtime(&tod)) == NULL) {
X
X		sprintf(yyerrmsg, "couldn't convert time via localtime: %s",
X			sys_errlist[errno]);
X		return(-1);
X	}
X
X	/* Initialize values for current time */
X
X	month = local->tm_mon;
X	day = local->tm_mday;
X	weekday = local->tm_wday;
X	year = local->tm_year;
X	hour = local->tm_hour;
X	minute = local->tm_min;
X	second = local->tm_sec;
X	timezone = ourtime->timezone * SEC_PER_MIN;
X
X	/* Make a local, lower case copy of the date/time string */
X
X	length = strlen(string);
X
X	if ((lstring = malloc(length + 1)) == NULL) {
X
X		sprintf(yyerrmsg, "couldn't allocate memory for date/time string: %s",
X			sys_errlist[errno]);
X		return(-1);
X	}
X
X	for (i = 0 ; i <= length ; i++) {
X
X		register char ch;
X
X		ch = string[i];
X
X		if (isalpha(ch) && isupper(ch))
X			lstring[i] = tolower(ch);
X		else
X			lstring[i] = ch;
X	}
X
X	lptr = lstring;
X
X	/* Attempt to parse the input */
X
X	if ((rc = yyparse()) != 0)
X		return(-1);
X
X	/* Check for an allowable number of each field */
X
X	if ((day_fields + rday_fields) > 1) {
X
X		strcpy(yyerrmsg, "more than one day specified");
X		return(-1);
X	}
X
X	if (date_fields > 1) {
X
X		strcpy(yyerrmsg, "more than one date specified");
X		return(-1);
X	}
X
X	if (time_fields > 1) {
X
X		strcpy(yyerrmsg, "more than one time specified");
X		return(-1);
X	}
X
X	if (zone_fields > 1) {
X
X		strcpy(yyerrmsg, "more than one timezone specified");
X		return(-1);
X	}
X
X	/* Convert the parsed fields into a time from epoch */
X
X	if (date_fields > 0 || time_fields > 0 || day_fields > 0) {
X
X		if ((tod = comp_tval()) <  0)
X			return(tod);
X	}
X
X	/* Adjust for any relative day (e.g., tomorrow) specified */
X
X	tod += rel_sec;
X	
X	/* Rationalize the time value, if no date is given (i.e., make */
X	/* certain it is not a past time) */
X
X	if (date_fields == 0  && rday_fields == 0 && tod < ourtime->time
X			&& ordinal >= 0)
X		tod += SEC_PER_DAY;
X
X	/* Adjust for any increment(s) specified */
X
X	if (inc_mon > 0)
X		tod += month_adjust(tod);
X	
X	if (day_fields > 0 && date_fields == 0)
X		tod += day_adjust(tod);
X
X	tod += inc_sec;
X
X	/* Free the storage for the local copy of the input string */
X
X	(void)free(lstring);
X
X	/* Return the computed time */
X
X	return(tod);
X}
X
X/* ------------------------------------------------------------------------- */
X
Xtime_t
Xcomp_tval()
X
X{
X	register int i;
X	int n_days;
X	int offset;
X	int rc;
X
X	time_t tval;
X
X	/* Adjust year, if necessary */
X
X	if (year < EPOCH)
X		year += 1900;
X
X	/* Adjust length of February for leap year, if necessary */
X	/* (True, reality is more complicated, but not needed for this program) */
X
X	month_len[1] = 28 + (year % 4 == 0);
X
X	/* Check for valid month and day */
X
X	if (month < 0 || month > 11) {
X
X		strcpy(yyerrmsg, "invalid month");
X		return(-1);
X	}
X
X	if (day < 1 || day > month_len[month]) {
X
X		strcpy(yyerrmsg, "invalid day of month");
X		return(-1);
X	}
X
X	/* Compute number of days from epoch to this day */
X
X	n_days = day - 1;
X
X	for (i = 0 ; i < month ; i++)
X		n_days += month_len[i];
X
X	for (i = EPOCH ; i < year ; i++)
X		n_days += 365 + (i % 4 == 0);
X
X	/* Check for valid minutes and seconds */
X
X	if (minute < 0 || minute > 59) {
X
X		strcpy(yyerrmsg, "invalid minutes value");
X		return(-1);
X	}
X
X
X	if (second < 0 || second > 59) {
X
X		strcpy(yyerrmsg, "invalid seconds value");
X		return(-1);
X	}
X
X	/* Compute the number of seconds since beginning of this day */
X
X	rc = 0;
X	offset = 0;
X
X	switch (meridian) {
X
X		case MILITARY:
X
X			if (hour < 0 || hour > 23)
X				rc = -1;
X
X			else
X				tval = ((hour * MIN_PER_HOUR) + minute) * SEC_PER_MIN + second;
X
X			break;
X
X		case PM:
X
X			offset = 12;
X			/* fall through to next case */
X
X		case AM:
X
X			if (hour < 1 || hour > 12)
X				rc = -1;
X
X			else
X				tval = (((hour + offset) * MIN_PER_HOUR) + minute)
X					* SEC_PER_MIN + second;
X
X			break;
X
X		default:
X
X			strcpy(yyerrmsg,
X				"can't happen: unknown meridian value in comp_tval");
X			return(-1);
X	}
X
X	if (rc < 0) {
X
X		strcpy(yyerrmsg, "invalid hours value");
X		return(-1);
X	}
X
X	/* Add seconds since beginning of epoch to seconds this day */
X
X	tval += n_days * SEC_PER_DAY;
X
X	/* Add in adjustment for timezone (to get to GMT) */
X
X	tval += timezone;
X
X	/* Adjust for daylight saving time, if necessary */
X
X	if (zone_type == DST ||
X			(zone_type == NOT_GIVEN && localtime(&tval)->tm_isdst))
X		tval -= SEC_PER_HOUR;
X	
X	return(tval);
X}
X
X/* ------------------------------------------------------------------------- */
X
Xday_adjust(tod)
X
Xtime_t tod;
X
X{
X	int day_diff;
X	int inc;
X	int ord_value;
X
X	struct tm *now;
X
X	now = localtime(&tod);
X
X	/* Compute the number of days until the specified day */
X	/* and the corresponding number of seconds */
X
X	day_diff = (weekday - now->tm_wday + 7) % 7;
X	inc = SEC_PER_DAY * day_diff;
X
X	/* If a postitive ordinal value was specified (e.g., third), */
X	/* adjust the value used in the subsequent computations */
X
X	if (ordinal > 0) {
X
X		/* If the ordinal is "first" or "next" and the day is the same */
X		/* as today, make sure we move ahead one week */
X
X		if (ordinal == 1 && day_diff == 0)
X			ord_value = 1;
X		else
X			ord_value = ordinal - 1;
X	}
X
X	else
X		ord_value = ordinal;
X
X	/* Adjust for the number of weeks until the specified day */
X
X	inc += 7 * SEC_PER_DAY * ord_value;
X
X	return(inc);
X}
X
X/* ------------------------------------------------------------------------- */
X
X#include "table.h"
X
Xstatic int hash[256];				/* One value for each ASCII character */
Xstatic int lookup_init = TRUE;		/* TRUE if init needs to be done */
Xstatic int table_size;				/* Number of string table entries */
X
Xstatic void linit();
X
X/* -------------------------------------------------------------------------- */
X
Xlookup_string(string)
X
Xchar *string;
X
X{
X	register int i;
X
X	/* Do we need to initialize the string and hash tables? */
X
X	if (lookup_init)
X		linit();
X	
X	/* Scan all table entries with a matching first letter */
X
X	for (i = hash[*string] ;
X			i < table_size && *string_table[i].ta_name == *string ; i++) {
X
X		if (strcmp(string_table[i].ta_name, string) == 0) {
X
X			yylval = string_table[i].ta_value;
X			return(string_table[i].ta_type);
X		}
X	}
X
X	return(-1);
X}
X
X/* -------------------------------------------------------------------------- */
X
Xstatic
Xentry_comp(ptr1, ptr2)
X
Xregister struct table_def *ptr1;
Xregister struct table_def *ptr2;
X
X{
X	return(strcmp(ptr1->ta_name, ptr2->ta_name));
X}
X
X/* -------------------------------------------------------------------------- */
X
Xstatic void
Xlinit()
X
X{
X	register int ch;
X	register int i;
X
X	/* Indicate that an initialization is no longer needed */
X
X	lookup_init = FALSE;
X	table_size = sizeof(string_table) / sizeof(struct table_def);
X
X	/* Sort the table by ASCII code */
X
X	qsort(string_table, table_size, sizeof(struct table_def), entry_comp);
X
X	/* Build a simple hash code table (based on first character of string) */
X	/* If there are no entries for a hash value, the hash table has the */
X	/* index of the next existing hash value. */
X
X	for (i = 0, ch = 0 ; i < table_size ; i++) {
X
X		while (*string_table[i].ta_name >= ch)
X			hash[ch++] = i;
X	}
X
X	return;
X}
X
X/* ------------------------------------------------------------------------- */
X
Xmonth_adjust(tod)
X
Xtime_t tod;
X
X{
X	int n_months;
X
X	struct tm *now;
X
X	now = localtime(&tod);
X	n_months = now->tm_mon + inc_mon;
X	year += n_months / 12;
X	month = n_months % 12;
X
X	return(comp_tval() - tod);
X}
X
X/* ------------------------------------------------------------------------- */
X
Xvoid
Xnum_to_time(value)
X
Xint value;
X
X{
X	minute = 0;
X	second = 0;
X
X	if (value >= 10000) {
X		second = value % 100;
X		value /= 100;
X	}
X	if (value >= 100) {
X		minute = value % 100;
X		value /= 100;
X	}
X
X	hour = value;
X	return;
X}
X
X/* ------------------------------------------------------------------------- */
X
Xyyerror(string)
X
Xchar *string;
X
X{
X	sprintf(yyerrmsg, "%s: at or near token \"%s\"", string, yytext);
X}
END_OF_FILE
if test 13039 -ne `wc -c <'src/date.y'`; then
    echo shar: \"'src/date.y'\" unpacked with wrong size!
fi
# end of 'src/date.y'
fi
echo shar: End of archive 2 \(of 2\).
cp /dev/null ark2isdone
MISSING=""
for I in 1 2 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked both archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
-- 
-----------------------------------------------------------------------------
Keith Pyle                                UUCP: ...!cs.utexas.edu!execu!keith
Execucom Systems Corp., Austin, Texas     Internet: keith@execu.com
"It's 10 o'clock.  Do you know where      Disclaimer: Everything I say is
   your child processes are?"               true unless I use the word 'the'.
-----------------------------------------------------------------------------