cron(1m) facility incl at(1) batch(1) crontab(1) part 3 of 4

Donald Lashomb donlash at uncle.uucp
Mon Jan 14 10:04:11 AEST 1991


---- Cut Here and unpack ----
#!/bin/sh
# This is part 03 of a multipart archive
if touch 2>&1 | fgrep '[-amc]' > /dev/null
 then TOUCH=touch
 else TOUCH=true
fi
# ============= crontab.1 ==============
if test X"$1" != X"-c" -a -f 'crontab.1'; then
	echo "File already exists: skipping 'crontab.1'"
else
echo "x - extracting crontab.1 (Text)"
sed 's/^X//' << 'SHAR_EOF' > crontab.1 &&
X.TH CRONTAB 1 LOCAL
X.SH NAME
Xcrontab \- user crontab file
X.SH SYNOPSIS
X.B crontab
X[file] 
X.br
X.B crontab
X-r
X.br
X.B crontab
X-l
X.SH DESCRIPTION
X.I Crontab
Xcopies the specified file, or standard input if no file is specified,
Xinto a directory that holds all user crontab files.
XA user crontab file
Xspecifies commands to be executed at specific dates and times by the
X.IR cron (1M)
Xdaemon.
X.I Crontab
Xgenerates CR_TAB jobs in the daemon for each command to be executed.
X.P
XUsers are permitted to use
X.I crontab
Xif their names appear in the file
X.B /usr/lib/cron/cron.allow.
XIf that file does not exist,
Xthe file
X.B /usr/lib/cron/cron.deny
Xis checked to determine if the user
Xshould be denied access to
X.I crontab.
XIf neither file exists, only root is allowed to
Xsubmit a job.
XThe allow/deny files consist of one user name
Xper line.
X.P
XA crontab file consists of lines of six fields each.
XThe fields are separated by spaces or tabs.
XThe first five specify the schedule:
X.P
X.RS 8
X.nf
X\fBmins\fR  - minutes    (0...59)
X\fBhrs\fR   - hours      (0...23)
X\fBmdays\fR - month days (1...31)
X\fBmons\fR  - months     (1...12) or (January...December)
X\fBwdays\fR - week days  (0...6)  or (Sunday...Saturday)
X.fi
X.RE
X.P
XEach of these is a pattern that may be an asterisk
X.RB ( * ),
Xmeaning all valid values;
Xor a list of elements separated by commas
X.RB ( , ).
XAn element is a number, name, or two elements separated by a dash
X.RB ( - ),
Xmeaning an inclusive range.
XOnly the first three letters of any name are needed.
XUpper or lower case letters are OK.
XNote that there are two fields which specify days
X(day of the month and day of the week).
XIf both are specified, both are adhered to; in this way:
X.P
X.RS 8
X.nf
X0 0 1 * 0 = 1st of every month and also every Sun.
X0 0 1 * * = only the first of every month.
X0 0 * * 0 = only on Sundays.
X0 0 * * * = every day, of course.
X.fi
X.RE
X.bp
X.P
XThe sixth field of a line in a crontab file
Xis the action that is performed at the specified times.
XPercent characters
X.RB ( % )
Xin this field (unless escaped by \fB\e\fP)
Xare translated to a new-line characters.
X(\e\e is translated to a single \e).
XThe first part of the action field (up to a % or end of line)
Xis a command that is executed by the user's shell
Xas specified in the
X.B /etc/passwd
Xfile.
XThe rest of the action field is made available to the
Xcommand as standard input.
XThe command's
Xstandard output and standard error output are
Xmailed to the user unless they are redirected elsewhere.
XThe shell environment variables, current directory,
Xumask, and ulimit are retained when the commands
Xare executed.
XOpen file descriptors, traps, and priority are lost.
X.P
XBlank lines in the crontab file are ignored.
XLines that start with a sharp
X.RB ( # )
Xcharacter are comments, except for the special
X.I "nice pragma"
Xindicator.
X.P
XThe special forms,
X.BI #-n digits
Xand
X.BI #-N digits
Xare used to set a nice value increment for the commands.
XOnce set, the nice value increment remains in effect for the rest
Xof the file until set again to a different value.
XThe form
X.BI #-N digits
Xspecifies a negative nice value increment
Xand can only used by the superuser.
X.SH OPTIONS
X.TP 8
X.B -r
XRemoves your crontab file from the crontab directory and
Xremoves all your CR_TAB jobs from the
X.IR cron (1M)
Xdaemon.
X.TP 8
X.B -l
XLists the crontab file for the invoking user.
X.SH EXAMPLE
X.RS 8
X.nf
X.sp
X#        ----- uucpadm crontab -----
X.sp
X#-n19
X56 * * * *   /usr/lib/uucp/uudemon.hr >/dev/null
X00 4 * * *   /usr/lib/uucp/uudemon.day >/dev/null
X#-n0
X30 5 * * Sun /usr/lib/uucp/uudemon.wk >/dev/null
X.fi
X.RE
X.SH HINT
XWhile enterring a crontab file "by hand" through stdin,
Xyou can press the interrupt key (DEL) to cancel it,
Xif you do it before you press cntl-D.
X.bp
X.SH NOTES
XThe user must have the
X.B LOGNAME
Xenvironment variable set properly or access will be denied.
XIn the case of the superuser,
X.B LOGNAME=root
Xwill (usually) be put into the environment if need be.
X.P
XThe
X.B TZ
Xenvironment variable, if set in the user's environment,
Xwill (usually) be replaced by the daemon's value when the
Xcommands are executed.
X.SH CAVEATS
XThe syntax of the command line and crontab file are upwardly compatable
Xwith the standard sysV version of
X.IR crontab (1).
XThe environment in which the commands run is different than
Xstandard sysV in the following ways:
X.P
XIn standard sysV the shell is invoked from your 
X.B HOME 
Xdirectory with an 
X.BR arg0 " of " sh.
X.I Cron
Xsupplies a default environment for every shell, defining
X.BR HOME ", " LOGNAME ", " SHELL(=/bin/sh) ,
Xand 
X.BR PATH(=:/bin:/usr/bin:/usr/lbin) .
X.P
XThis version restores the environment as it existed when
X.IR crontab (1)
Xwas called.
XIf you are in your HOME directory when you call
X.IR crontab (1),
Xthen it is almost the same.
XAnother difference is that this version is compatable with
X.IR ksh (1).
XIf that is your login shell, then it will be used to execute the commands.
X.P
XThis program calls
X.IR crtabj (1)
Xto communicate with the daemon - see
X.IR at (1)
Xmanual page.
X.SH FILES
X.nf
X/usr/lib/cron             main cron directory
X/usr/lib/cron/at.allow    list of allowed users \e at
X/usr/lib/cron/at.deny     list of denied users  / batch
X/usr/lib/cron/cron.allow  list of allowed users \e cronjob
X/usr/lib/cron/cron.deny   list of denied users  / crontab
X/usr/spool/cron/atjobs    spool area for jobs
X/usr/spool/cron/crontabs  save area for crontabs
X/usr/local/bin/at         user command
X/usr/local/bin/batch      user command
X/usr/local/bin/cronjob    user command
X/usr/local/bin/crtabj     user command
X/usr/local/bin/crontab    user command
X/etc/daemons/cron         queues and executes jobs
X/usr/lib/cron/log         log of all daemon's actions
X/usr/lib/cron/FNAMES      FIFO from user to daemon
X/usr/lib/cron/JNUMBS      FIFO from daemon to user
X.fi
X.SH SEE ALSO
Xat(1), cron(1M), kill(1), ksh(1), mail(1), nice(1), ps(1), sh(1).
X.SH DIAGNOSTICS
XReports various syntax errors and times that are out of range.
XExit value of 0 is returned if all OK, 1 if an error occurs but
Xsome processing occurred,
Xor 2 if processing can't even get going.
X.SH WARNINGS
XThis facility can only be used by logins that use the standard
XBourne shell,
X.IR sh (1)
Xor the Korn shell,
X.IR ksh (1).
X.SH BUGS
XThe -n or -N nice value increment is not applied to the user's
Xnice value when the job is executed, rather it applies to the
Xdaemon's value.  This is not worth fixing:
Xthe daemon and users usually have the same nice value (20).
X.P
XThe owner of a job is not informed if the job can't be executed
X(eg corrupted) and was removed my the daemon.
X.SH AUTHOR
XDonald Lashomb  4/89, 10/90
SHAR_EOF
$TOUCH -am 1018215990 crontab.1 &&
chmod 0644 crontab.1 ||
echo "restore of crontab.1 failed"
set `wc -c crontab.1`;Wc_c=$1
if test "$Wc_c" != "6593"; then
	echo original size 6593, current size $Wc_c
fi
fi
# ============= crontab.c ==============
if test X"$1" != X"-c" -a -f 'crontab.c'; then
	echo "File already exists: skipping 'crontab.c'"
else
echo "x - extracting crontab.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > crontab.c &&
X/*   crontab(1) user command  D.Lashomb    */
X
X/* SYNOPSIS: crontab [ file ]				<<1>>
X *           crontab -r					<<2>>
X *           crontab -l					<<3>>
X *
X * <<1>> make a new crontab file and jobs:
X *	[file]	stdin otherwise
X *
X *	syntax of a crontab file:
X *
X *	any line that starts with a # is a comment
X *	#-{n|N}nice = sets nice value, no spaces allowed
X *	<schedule> <action>
X *		<schedule> = mins hrs ... same as crtabj
X *		<action> = <command>[<cmd's stdin>]
X *			<cmd's stdin> = uses % to mark newlines
X *
X * <<2>> remove crontab file and jobs
X * <<3>> list crontab file to stdout
X *
X */
X
X/* ------------------------- NOTICE -----------------------------
X
X   at(1) batch(1) cronjob(1) crtabj(1) crontab(1) cron(1M)
X	(c) copyright April 10, 1989 by Donald Lashomb
X	(c) copyright October 16, 1990 by Donald Lashomb
X
X   This program is free.  Use it, modify it, copy it, give a copy
X   to a friend; I simply request the following provisions be observed:
X
X   1. My name as original author and this notice remain intact.
X   2. This program (or any modification of it) is not to be sold
X      for profit.
X   3. If this program is included in commercial products, there be
X      no charge for it.
X   4. This program must be distributed with source code.  Compiled-
X      only or binary-only distribution of this program is not allowed.
X      The administrator of any system that uses this program must have
X      full access to the source code.
X   5. If you enhance this program, discover a bug, have any comments
X      about it (or flames) please let me know.
X   
X   		Donald Lashomb
X   		Main Street
X   		Cranberry Lake, NY 12927
X
X   -------------------------------------------------------------- */
X
X/*************************************
X * security note:
X * Don't ever call system(), popen(), getcwd(), execlp(), or execvp()
X * when euid != real uid.  These functions actually call the shell to
X * do their work and, therefore, are subject to PATH and IFS tricks.
X *************************************/
X
X#include <stdio.h>
X#include <string.h>
X#include <ctype.h>
X#include <pwd.h>
X#include <sys/signal.h>
X#include <errno.h>
X#include "cron.h"
X
Xextern void		exit();
Xextern int		errno;
Xextern void		perror();
Xextern int		getopt();
Xextern int		optind;
Xextern struct passwd	*getpwnam();
Xextern char		*getenv();
X
Xextern int		(*signal())();
X#define BADSIG		((int (*)()) -1)
X
Xextern int		realuid;		/* \                      */
Xextern struct passwd	*userpw;		/*  \ link with login.o   */
Xextern void		deny();			/*  /                     */
Xextern void		login();		/* /                      */
X
X
X/* static variables ------------------------------------------- */
X
Xstatic char		line[LINESIZ];
Xstatic char		*lin;
Xstatic char		fname[PATHSIZ];
Xstatic char		nicopt[LINESIZ] = "-n0";
Xstatic char		command[LINESIZ];
Xstatic int		unlkflg=0;
Xstatic int		pid=0;			/* pid of crtabj */
X
Xstatic char copyright[] = "crontab(1) - (c)1989,1990 D.Lashomb";
X
X/* misc routines ---------------------------------------------- */
X
Xvoid usage()
X	{
X	fputs("\
Xusage: crontab [ file ]\n\
X       crontab -r\n\
X       crontab -l\n\
X",stderr);
X	exit(2);
X	}
X
Xvoid fatal(str)
X	char *str;
X	{
X	int oerrno = errno;
X
X	fputs("crontab: can't ",stderr);
X	errno = oerrno;
X	perror(str);
X	if(unlkflg) {
X		if(realuid == geteuid())
X			system("crontab -r");
X		else
X			unlink(fname);
X		}
X	if(pid > 0) kill(pid,SIGTERM);
X	exit(1);
X	}
X
Xvoid crash(str)
X	char *str;
X	{
X	errno = 0;
X	fatal(str);
X	}
X
Xint intrpt()
X	{
X	if((
X	signal(SIGHUP,SIG_IGN)		== BADSIG	) || (
X	signal(SIGINT,SIG_IGN)		== BADSIG	) || (
X	signal(SIGTERM,SIG_IGN)		== BADSIG	)
X	)
X		fatal("catch signal");
X
X	if(unlkflg) {
X		if(realuid == geteuid())
X			system("crontab -r");
X		else
X			unlink(fname);
X		}
X	if(pid > 0) kill(pid,SIGTERM);
X	exit(2);
X	/*NOTREACHED*/
X	}
X
Xvoid markstring(str)
X	char	**str;
X	{
X	while(isspace(*lin)) ++lin;
X	if(*lin == '\0') crash("- syntax error");
X	*str = lin;
X	while(!isspace(*lin)) ++lin;
X	*lin++ = '\0';
X	}
X
Xvoid getcommand()
X	{
X	register char	*p;
X	register int	c,esc;
X
X	while(isspace(*lin)) ++lin;
X	if(*lin == '\0')
X		crash("- syntax error");
X
X	p = command;
X	esc = 0;
X	while((c = *lin++) != '\0') {
X		if(esc) {
X			switch(c) {
X			case '%':
X			case '\\':
X				*p++ = c; break;
X			case '\n':
X				*p++ = '\\'; *p = '\0'; return;
X			default:
X				*p++ = '\\'; *p++ = c;
X				}
X			esc = 0;
X			}
X		else {
X			switch(c) {
X			case '%':
X				*p = '\0'; return;
X			case '\\':
X				esc = 1; break;
X			case '\n':
X				*p = '\0'; return;
X			default:
X				*p++ = c;
X				}
X			}
X		}
X	*p = '\0';
X	--lin;
X	}
X
Xvoid pputc(c,pfp)
X	int	c;
X	FILE	*pfp;
X	{
X	if(putc(c,pfp) == EOF)
X		fatal("write crtabj");
X	}
X
X/* ============================================================ */
X/*                            MAIN()                            */
X/* ------------------------------------------------------------ */
X
Xmain(argc,argv)
X	int		argc;
X	char		**argv;
X	{
X	register FILE	*fp,*fp0,*pfp;
X	register char	*p;
X	int		pfd[2];
X	int		lflag,rflag;
X	char		*mm,*hh,*DD,*MM,*ww;
X	register int	c;
X	register int	esc;
X	int		retv;
X
X
X/* Setup and check if user allowed to use this program -------- */
X
X	login(CRALLOW,CRDENY);
X
X/* Setup to catch normal user signals ------------------------- */
X
X	if((
X	signal(SIGHUP,intrpt)		== BADSIG	) || (
X	signal(SIGINT,intrpt)		== BADSIG	) || (
X	signal(SIGTERM,intrpt)		== BADSIG	)
X	)
X		fatal("catch signal");
X
X/* Process command line --------------------------------------- */
X
X	if(argc > 2) usage();
X	lflag=rflag=0;
X/*	optind = 0;					ld(1) does this */
X	while((c=getopt(argc,argv,"lr")) != EOF) {
X		switch(c) {
X		case 'l':
X			if(lflag||rflag) usage();
X			lflag = 1; break;
X		case 'r':
X			if(lflag||rflag) usage();
X			rflag = 1; break;
X		default:
X			usage();
X			}
X		}
X
X	sprintf(fname,"%s/%d",CRSPOOL,realuid);
X
X/* delete crontab --------------------------------------------- */
X
X	if(rflag) {
X		if((unlink(fname) != 0) && (errno != ENOENT))
X			fatal("unlink crontab");
X		if(setuid(realuid) != 0)
X			fatal("get real uid");
X		if(system("crtabj -r all") != 0)
X			exit(1);
X		exit(0);
X		}
X
X/* read crontab file ------------------------------------------ */
X
X	if(lflag) {
X		if((fp=fopen(fname,"r")) == NULL)
X			fatal("open crontab");
X		while(fgets(line,LINESIZ,fp) != NULL)
X			if(fputs(line,stdout) == EOF)
X				fatal("write stdout");
X		if(!feof(fp))
X			fatal("read crontab");
X		if(fclose(fp) != 0)
X			fatal("close crontab");
X		exit(0);
X		}
X
X/* make new crontab file and jobs ----------------------------- */
X
X	/* test if already a crontab file */
X
X	if((fp=fopen(fname,"r")) != NULL) {
X		fputs("crontab: you already have a crontab\n",stderr);
X		if(fclose(fp) != 0)
X			fatal("close crontab");
X		exit(2);
X		}
X
X	/* creat crontab file and open input file */
X
X	unlkflg=1;
X	if((fp=fopen(fname,"w")) == NULL)
X		fatal("open crontab");
X	if(chmod(fname,T_PERM) != 0)
X		fatal("chmod crontab");
X	if(setuid(realuid) != 0)
X		fatal("get real uid");
X	if(argc == 1)
X		fp0 = stdin;
X	else
X		if((fp0=fopen(argv[optind],"r")) == NULL)
X			fatal("open input file");
X
X	/* process input file, write crontab file */
X
X	while(fgets(line,LINESIZ,fp0) != NULL) {
X		if(fputs(line,fp) == EOF)
X			fatal("write crontab");
X
X		lin = line;
X		while(isspace(*lin)) ++lin;
X		switch(*lin) {
X
X		case '\0':
X			continue;
X
X		case '#':
X			if((strncmp(lin,"#-n",3) == 0) ||
X			   (strncmp(lin,"#-N",3) == 0)) {
X				++lin;
X				p = nicopt;
X				while(!isspace(*lin)) *p++ = *lin++;
X				*p = '\0';
X				}
X			continue;
X
X		default:
X			/* call crtabj */
X
X			markstring(&mm);
X			markstring(&hh);
X			markstring(&DD);
X			markstring(&MM);
X			markstring(&ww);
X			getcommand();
X
X/*********************
X * can't use popen() because:
X * #1 shell interprets wild card char.s like *
X * #2 doesn't return exit value of crtabj
X *********************/
X
X			if(pipe(pfd) != 0)
X				fatal("open pipe to crtabj");
X			switch(pid=fork()) {
X			case 0:
X				if((
X				close(0)	== 0	) && (
X				dup(pfd[0])	== 0	) && (
X				close(pfd[0])	== 0	) && (
X				close(pfd[1])	== 0	)
X				)
X					execlp("crtabj","crtabj",nicopt,
X						mm,hh,DD,MM,ww,command,NULL);
X				exit(5);
X			case -1:
X				fatal("fork crtabj");
X			default:
X				if(close(pfd[0]) != 0)
X					fatal("close pipe");
X				}
X
X			if((pfp=fdopen(pfd[1],"w")) == NULL)
X				fatal("pipe to crtabj");
X
X			/* pipe rest of line to it */
X
X			esc = 0;
X			while((c = *lin++) != '\0') {
X				if(esc) {
X					switch(c) {
X					case '%':
X						pputc('%',pfp); break;
X					case '\\':
X						pputc('\\',pfp); break;
X					default:
X						pputc('\\',pfp); pputc(c,pfp);
X						}
X					esc = 0;
X					}
X				else {
X					switch(c) {
X					case '%':
X						pputc('\n',pfp); break;
X					case '\\':
X						esc = 1; break;
X					default:
X						pputc(c,pfp);
X						}
X					}
X				}
X			if(fclose(pfp) != 0)
X				fatal("close pipe to crtabj");
X			if(wait(&retv) != pid)
X				fatal("wait");
X			pid = 0;
X			if((retv & 0xffff) != 0)
X				crash("- syntax error");
X			}
X			/* end switch */
X		}
X		/* end while */
X
X	if(!feof(fp0))
X		fatal("read input file");
X	if(fclose(fp) != 0)
X		fatal("close crontab");
X	if(fclose(fp0) != 0)
X		fatal("close input file");
X	exit(0);
X	/*NOTREACHED*/
X	}
SHAR_EOF
$TOUCH -am 1016220990 crontab.c &&
chmod 0644 crontab.c ||
echo "restore of crontab.c failed"
set `wc -c crontab.c`;Wc_c=$1
if test "$Wc_c" != "9025"; then
	echo original size 9025, current size $Wc_c
fi
fi
# ============= daemon.c ==============
if test X"$1" != X"-c" -a -f 'daemon.c'; then
	echo "File already exists: skipping 'daemon.c'"
else
echo "x - extracting daemon.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > daemon.c &&
X/*  cron(1M) - cron facility daemon */
X
X/* ------------------------- NOTICE -----------------------------
X
X   at(1) batch(1) cronjob(1) crtabj(1) crontab(1) cron(1M)
X	(c) copyright April 10, 1989 by Donald Lashomb
X	(c) copyright October 16, 1990 by Donald Lashomb
X
X   This program is free.  Use it, modify it, copy it, give a copy
X   to a friend; I simply request the following provisions be observed:
X
X   1. My name as original author and this notice remain intact.
X   2. This program (or any modification of it) is not to be sold
X      for profit.
X   3. If this program is included in commercial products, there be
X      no charge for it.
X   4. This program must be distributed with source code.  Compiled-
X      only or binary-only distribution of this program is not allowed.
X      The administrator of any system that uses this program must have
X      full access to the source code.
X   5. If you enhance this program, discover a bug, have any comments
X      about it (or flames) please let me know.
X   
X   		Donald Lashomb
X   		Main Street
X   		Cranberry Lake, NY 12927
X
X   -------------------------------------------------------------- */
X
X
X/* hint- catching errors by comparing to 0 rather than -1,
X	 is (probably) more efficent on most processors */
X
X#include <stdio.h>
X#include <string.h>
X#include <errno.h>
X#include <fcntl.h>
X#include <pwd.h>
X#include <time.h>
X#include <sys/signal.h>
X#include <sys/dir.h>
X#include "cron.h"
X#include "job.h"
X
X#define BADSIG		((int (*)()) -1)
X
Xextern int		errno;
Xextern int		sys_nerr;
Xextern char		*sys_errlist[];
Xextern char		**environ;
Xextern char		*getenv();
Xextern void		exit();
Xextern void		perror();
Xextern int		(*signal())();
Xextern unsigned		alarm();
Xextern long		atol();
Xextern long		time();
Xextern long		ulimit();
Xextern char		*sbrk();
Xextern struct passwd	*getpwuid();
Xextern struct passwd	*getpwnam();
Xextern unsigned		sleep();		/* user tries - not daemon */
X
X/* routine in mkfifo.c */
X
Xextern void		mkfifo();
X
X/* routines in fifo.c */
X
Xextern int		openfifo();
Xextern int		rdfifo();
Xextern int		wrfifo();
X
X/* routines in memlist.c */
X
Xextern memJOB		*jtodo;
Xextern void		initlist();
Xextern void		insert();
Xextern void		intodo();
Xextern void		infree();
Xextern memJOB		*rmtodo();
Xextern memJOB		*deltodo();
Xextern int		taken();
X
X/* routines in log.c */
X
Xextern void		openlog();
Xextern void		cleanlog();
Xextern void		logjob();
Xextern void		logtime();
Xextern void		logmsg2();
X
X/* routine in resched.c */
X
Xextern long		resched();
X
X
X/* global variables ------------------------------------------- */
X
XJOB		jjj;				/* default JOB buffer */
Xstruct passwd	*cronpw;			/* cron's login stuff */
Xint		nul;				/* /dev/null fildes   */
X
X
X/* static variables ------------------------------------------- */
X
Xstatic char		fname[DIRSIZ+1];	/* job file name      */
Xstatic int		jnum,fnam;		/* fifo fildes        */
Xstatic memJOB		*mjp;			/* current job ptr    */
Xstatic int		fd0;			/* job fildes         */
X
Xstatic long		now;			/* time of day        */
Xstatic int		kids,maxkids;		/* max proc.s to fork */
X
X#ifdef TZOVRIDE
Xstatic char		*tz;			/* TZ env override    */
X#endif
X
Xstatic char		copyright[] = "cron(1M) - (c)1989,1990 D.Lashomb";
X
X
X/* global routines -------------------------------------------- */
X
Xvoid fatal(str)
X	char	*str;
X	{
X	int	n;
X	char	*errmsg;
X	char	line[LINESIZ];
X
X	if((errno > 0) && (errno < sys_nerr))
X		errmsg = sys_errlist[errno];
X	else
X		errmsg = "(no sys errmsg)";
X	sprintf(line,
X		"FATAL cron daemon: can't %s errno= %d %s\n",
X		str,errno,errmsg);
X	n = strlen(line);
X	write(2,line,(unsigned)n);
X	exit(1);
X	}
X
X/* static routines -------------------------------------------- */
X
Xstatic dummy()
X	{
X	/* no-op function */
X	}
X
X/* inplementation dependent */
Xstatic align()
X	{
X	int	x;
X
X	if((x = (int)sbrk(0)) == -1)
X		return(-1);
X	if((x %= sizeof(char **)) != 0)
X		if((int)sbrk((int)(sizeof(char **) - x)) == -1)
X			return(-1);
X	return(0);
X	}
X
X#define alloc(n)	((char **)sbrk(n))
X
Xstatic void wrfifofail(pfd)
X	int	pfd;
X	{
X	/* just record loss of jobnumber for now */
X	logmsg2("fifo full, jobnumber lost",jjj.jobn);
X	}
X
X
X/* -----------------------------------------------------------
X   Remove a job:
X
X   This part of the code is for the daemon.  The job file is
X   unlinked so its name is free to be used over.  However, it
X   is not actually removed until it is closed when the user's
X   shell exits.  The job slot is free to be used again, so it
X   is put on the free list and sent into the JNUM fifo for any
X   user that wants it.
X   ----------------------------------------------------------- */
X
Xstatic void rmvjob(msg)
X	char	*msg;		/* reason */
X	{
X	unlink(fname);
X	infree(mjp);
X	if(wrfifo(jnum) == 0)
X		wrfifofail(jnum);
X	if(msg != NULL)
X		logjob(msg);
X	}
X
X/* close job file --------------------------------------------- */
X
Xstatic void closejob()
X	{
X	if(close(fd0) != 0)
X		fatal("close job file");
X	}
X
X/* open job file ---------------------------------------------- */
X/*    setup fd0 and jjj						*/
X
Xstatic int openjob()
X	{
X	if((fd0=open(fname,O_RDONLY)) == -1) {
X		rmvjob("can't open job file");
X		return(0);
X		}
X	if((
X	read(fd0,(char *)&jjj,JOBSIZ)	!= JOBSIZ	) || (
X	jjj.link			!= MAGIC	) || (
X	jjj.jobn			!= atoi(fname)	) || (
X	jjj.msg				<  MINMSG	) || (
X	jjj.msg				>  MAXMSG	) || (
X	jjj.time			<  0L		) || (
X	jjj.uid				<  0		) || (
X	jjj.gid				<  0		) || (
X	jjj.nice			<  MINNICE	) || (
X	jjj.nice			>  MAXNICE	) || (
X	jjj.umask			<  0		) || (
X	jjj.umask			>  0777		) || (
X	jjj.ulimit			<  0L		) || (
X	jjj.ulimit			>  MAXULIM	) || (
X	jjj.envc			<  4		) || (
X	jjj.envz			<  3		)
X	) {
X		closejob();
X		rmvjob("rmv corrupt file");
X		return(0);
X		}
X	if((
X	jjj.jobn			!= mjp->jobn	) || (
X	jjj.msg				!= mjp->msg	) || (
X	jjj.uid				!= mjp->uid	)
X	) {
X		closejob();
X		rmvjob("rmv file not= mem");
X		return(0);
X		}
X	return(1);
X	}
X
X/* set up environment ----------------------------------------- */
X/*     return shell, working dir and crontab command		*/
X
Xstatic int readenv(shell,wd,cmd)
X	char		**shell;
X	char		**wd;
X	char		**cmd;
X	{
X	register int	i,n,z;
X	register char	**p,*q;
X
X	i = jjj.envc * sizeof(char *);
X	z = jjj.envz;
X	if((environ = p = alloc(i+z)) == (char **) -1)
X		return(0);
X	q = (char *)p + i;
X	if(read(fd0,q,(unsigned)z) != z)	/* file ptr --> ascii */
X		return(0);
X	n = 0;
X	do {
X		*p++ = q;
X		while(++n, (*q++ != '\0')) ;
X		}
X		while(n < z);
X	*cmd = *(--p);				/* --> crontab cmd */
X	*wd = *(--p);				/* --> working dir */
X	*shell = *(--p);			/* --> user shell  */
X	*p = NULL;				/* mark end of env */
X	return(1);
X	}
X
X/* -----------------------------------------------------------
X   Execute a job:
X
X   This part of the code is for the child process.  Now what we
X   got to do is get the user's shell running with the proper uid,
X   gid, nice, process group and connect stdin to the job file,
X   stdout and stderr piped to mail.  Alarm signal is already set
X   back to the default.  Death_of_child signal reset to default.
X   The environment is read from the job file and set up in memory
X   as well as the current working directory which is chdir'd to.
X   Execl is used rather than execlp so only compiled shells and
X   mail programs are usable; this is done for efficiency and
X   security reasons.  If the forking and exec-ing of the user's
X   processes fail, an indication of what happened is recorded in
X   the log.  The exit(errno) is used in case someday I make use
X   of it.  There is no return from this function!
X   ----------------------------------------------------------- */
X
Xstatic void execjob(shopt)
X	char		*shopt;			/* shell option */
X	{
X	char		*shell,*wd,*cmd;
X	char		*p;
X	char		*login;
X	int		pfd[2];
X	int		tries;
X
X	setpgrp();
X	nice(jjj.nice);
X	umask(jjj.umask);
X	ulimit(2,jjj.ulimit);
X
X	if((
X	signal(SIGCLD,SIG_DFL)		!= BADSIG	) && (
X	setgid(jjj.gid)			== 0		) && (
X	setuid(jjj.uid)			== 0		) && (
X	readenv(&shell,&wd,&cmd)	!= 0		) && (
X	chdir(wd)			== 0		) && (
X	(login = getenv("LOGNAME"))	!= NULL		) && (
X	pipe(pfd)			== 0		) && (
X	close(0)			== 0		) && (
X	close(1)			== 0		) && (
X	close(2)			== 0		)
X	) {
X
X	/* begin if#1 */
X
X#ifdef TZOVRIDE
X	if((p=getenv("TZ")) != NULL)
X		strncpy(p,tz,strlen(p));
X#endif
X
X	tries = MAXTRIES;
X	while(1) {
X
X	switch(fork()) {
X	case 0:
X		if((
X		dup(fd0)	== 0	) && (
X		dup(pfd[1])	== 1	) && (
X		dup(pfd[1])	== 2	) && (
X		close(fd0)	== 0	) && (
X		close(pfd[0])	== 0	) && (
X		close(pfd[1])	== 0	)
X		) {
X			if(*shell == '\0')
X				shell = "/bin/sh";
X			if(*cmd == '\0')
X				cmd = NULL;
X			else
X				logjob(cmd);		/* CR_TAB */
X
X			execl(shell,strrchr(shell,'/')+1,shopt,cmd,NULL);
X			}
X		logjob("can't exec");
X		kill(0,SIGTERM);
X		exit(errno);
X	case -1:
X		if(--tries >= 0) {	/* try MAXTRIES times to fork:   */
X			sleep(10);	/* sleep might screw-up alarm    */
X			continue;	/* signals, but it's easy to use */
X			}
X		logjob("can't fork2");
X		kill(0,SIGTERM);
X		exit(errno);
X	default:
X		if((
X		dup(pfd[0])	== 0	) && (
X		dup(nul)	== 1	) && (
X		dup(nul)	== 2	) && (
X		close(fd0)	== 0	) && (
X		close(pfd[0])	== 0	) && (
X		close(pfd[1])	== 0	)
X		)
X			execl("/bin/mail","mail",login,NULL);
X
X		logjob("can't exec /bin/mail");
X		kill(0,SIGTERM);
X		exit(errno);
X		}
X
X	}
X	/* end while */
X
X	}
X	/* end if#1 */
X	logjob("can't setup");
X	exit(errno);
X	}
X
X
X/* -----------------------------------------------------------
X   run job:
X
X   Open job file, fork off process to execute the job, close it.
X   Return 0 if job removed because can't open it.
X   ----------------------------------------------------------- */
X
Xstatic int runjob(shopt)
X	char *shopt;			/* shell option */
X	{
X	if(++kids > maxkids) {
X		mjp->time = now + GRAINULARITY;
X		intodo(mjp);
X		return(0);
X		}
X	if(!openjob()) return(0);
X
X/* -----------------------------------------------------------
X   At this point, the job file is open.  We are ready to fork
X   off a new process and run the job.  The important variables
X   at this point are:
X		fd0  -  file decsriptor of open job file
X		jjj  -  JOB structure
X   a *copy* of these variables will exist after the fork.
X   ----------------------------------------------------------- */
X
X	logjob("run");
X	switch(fork()) {
X
X	case 0:
X		execjob(shopt);
X	case -1:
X		logjob("can't fork1");
X		if(errno == EAGAIN) {
X			closejob();
X			mjp->time = now + GRAINULARITY;
X			intodo(mjp);
X			return(0);
X			}
X		fatal("kernal fork trouble");
X	default:
X		closejob();
X		}
X	return(1);
X	}
X
X
X/* =========================================================== */
X/*                            MAIN()                           */
X/* =========================================================== */
X
Xmain(argc,argv)
X	int		argc;
X	char		**argv;
X	{
X	int		n;
X	char 		*q;
X	register long	catchup,start,nap;	/* time values            */
X	register int	batch;			/* batch in progress flag */
X
X
X/* get things ready -------------------------------------- */
X
X	start = time((long *)0);
X	catchup = CATCHUP;
X	if((q=getenv("CATCHUP")) != NULL) catchup = atol(q);
X	if(argc >= 2) catchup = atol(argv[1]);
X	maxkids = MAXKIDS;
X	if((q=getenv("MAXKIDS")) != NULL) maxkids = atoi(q);
X	if(argc == 3) maxkids = atoi(argv[2]);
X	if(argc > 3) {
X		fputs("usage: cron [ catchup [ maxkids ]]\n",stderr);
X		exit(1);
X		}
X
X#ifdef TZOVRIDE
X	if((tz=getenv("TZ")) == NULL)
X		fatal("get TZ env.var.");
X#endif
X
X	umask(UMASK);
X	if((cronpw=getpwnam("cron")) == NULL)
X		fatal("get cron passwd");
X	if(chdir(ATSPOOL) != 0)
X		fatal("cd to at spool dir");
X
X/* open the fifo.s --------------------------------------- */
X
X	mkfifo(JNUM);
X	mkfifo(FNAM);
X	jnum = openfifo(JNUM);
X	fnam = openfifo(FNAM);
X
X/* open a file descriptors to /dev/null ------------------ */
X/*    user job's mail stdout and stderr connected there    */
X/*    daemon's stdin and stdout, so can setpgrp() !=window */
X/*    also used by daemon for fildes 2 when clean log      */
X
X	if((nul=open("/dev/null",O_RDWR)) == -1)
X		fatal("open /dev/null");
X	if(fcntl(nul,F_SETFD,1) == -1)
X		fatal("set close-on-exec for /dev/null");
X	if((close(0) != 0) || (close(1) != 0) ||
X	   (dup(nul) != 0) || (dup(nul) != 1))
X		fatal("connect stdin, stdout to /dev/null");
X
X/* open a file descriptor to the log file ---------------- */
X/*    and connect stderr there too                         */
X
X	openlog();
X
X/* build the in-memory job list -------------------------- */
X
X	logtime(" : cron daemon start\n");
X	initlist();
X
X/* put available jobnumbers into JNUM fifo --------------- */
X
X	for(n=0;n<NUMJOBS;++n)
X		if(!taken(n)) {
X			jjj.jobn = n;
X			if(wrfifo(jnum) == 0)
X				wrfifofail(jnum);
X			}
X
X/* no zombies -------------------------------------------- */
X
X	if(signal(SIGCLD,SIG_IGN) == BADSIG)
X		fatal("ignore death_of_child signal");
X
X/* align memory so can set up environments --------------- */
X
X	if(align() != 0)
X		fatal("memory overflow");
X
X/* fork off the real daemon ------------------------------ */
X/*    makes daemon's parent init                           */
X
X	switch(fork()) {
X	case 0:
X		setpgrp();
X		logmsg2("daemon running",getpid());
X		break;
X	case -1:
X		fatal("fork daemon");
X	default:
X		exit(0);
X		}
X
X/* =========================================================== */
X/*                         MAIN LOOP                           */
X/* =========================================================== */
X
Xwhile(1) {
X
X/* go to sleep for awhile -------------------------------- */
X/*    I keep track of when it's really supposed to wakeup  */
X
Xif(signal(SIGALRM,dummy) == BADSIG)
X	fatal("set alarm signal");
Xstart += GRAINULARITY;
Xnap = start - time((long *)0);
Xif((nap < -MAXLATENESS) ||	/* sys clock set ahead or major overload */
X   (nap >  GRAINULARITY))	/* sys clock set back more than a little */
X	{			/* reset daemon's idea of the time       */
X	now = time((long *)0);
X	nap = now / SECS;
X	nap *= SECS;		/* retain "seconds" from original start  */
X	nap += start % SECS;
X	start = nap + GRAINULARITY;
X	nap = start - now;
X	}
Xelse if(nap <= 0L)		/* sys clock set ahead just a little bit */
X	{			/*           or minor overload of daemon */
X	nap = 1;		/* let daemon catch up to the sys clock  */
X	}
Xalarm((unsigned)nap);
Xpause();
Xnow = start;
Xif((now % 3600) < GRAINULARITY) logtime("");
Xbatch = kids = 0;
X
X/* read any messages sent by users ----------------------- */
X
Xwhile((rdfifo(fnam)) != 0) {
X
X	sprintf(fname,"%d",jjj.jobn);
X	switch(jjj.msg) {
X
X	case AT_JOB:
X	case BATCHJ:
X	case CR_JOB:
X	case CR_TAB:
X
X		insert();
X		logjob("add");
X		break;
X
X	case REMOVE:
X
X		if((mjp=deltodo(jjj.jobn)) != NULL) {
X			if((mjp->uid == jjj.uid) || (jjj.uid == 0)) {
X				rmvjob("rmv");
X				}
X			else {
X				intodo(mjp);
X				logjob("unauthorized try rmv");
X				}
X			}
X		else
X			logjob("try rmv non-exist");
X		break;
X
X	case EXECUT:
X
X		if((mjp=deltodo(jjj.jobn)) != NULL) {
X			if((mjp->uid == jjj.uid) || (jjj.uid == 0)) {
X				mjp->time = now;
X				intodo(mjp);
X				}
X			else {
X				intodo(mjp);
X				logjob("unauthorized try exc");
X				}
X			}
X		else
X			logjob("try exc non-exist");
X		break;
X
X		
X	case CL_LOG:
X
X		if((jjj.uid == 0) || (jjj.uid == cronpw->pw_uid))
X			cleanlog();
X		else
X			logmsg2("unauthorized try clean log",jjj.uid);
X		break;
X
X	default:
X
X		logjob("bad msg in fifo");
X
X		} /* end switch */
X
X	} /* end while read fifo */
X
X/* check job list ---------------------------------------- */
X
Xwhile((jtodo != NULL) && (jtodo->time <= now)) {
X
X	mjp=rmtodo();
X	sprintf(fname,"%d",mjp->jobn);
X	switch(mjp->msg) {
X
X	case BATCHJ:
X
X		if(batch) {
X			mjp->time = now + GRAINULARITY;
X			intodo(mjp);
X			continue;
X			}
X		batch = 1;
X		if(runjob("-s"))
X			rmvjob(NULL);
X		break;
X
X	case AT_JOB:
X
X		if(mjp->time < (now-catchup-GRAINULARITY-1)) {
X			rmvjob("rmv outdated");
X			continue;
X			}
X		if(runjob("-s"))
X			rmvjob(NULL);
X		break;
X
X	case CR_JOB:
X	case CR_TAB:
X
X		if(mjp->time < (now-catchup-GRAINULARITY-1)) {
X			if(openjob()) {
X				closejob();
X				if((mjp->time = resched(now)) < 0L)
X					rmvjob("bad cron schedule");
X				else
X					intodo(mjp);
X				}
X			continue;
X			}
X		if(runjob((mjp->msg == CR_TAB)? "-c" : "-s")) {
X			if((mjp->time = resched(now)) < 0L)
X				rmvjob("bad cron schedule");
X			else
X				intodo(mjp);
X			}
X		break;
X
X	default:
X
X		fatal(" - corrupt mem list");
X
X		} /* end switch */
X
X	} /* end while check job list */
X
X} /* end main loop */
X
X} /* end main */
X
SHAR_EOF
$TOUCH -am 1017082990 daemon.c &&
chmod 0644 daemon.c ||
echo "restore of daemon.c failed"
set `wc -c daemon.c`;Wc_c=$1
if test "$Wc_c" != "16055"; then
	echo original size 16055, current size $Wc_c
fi
fi
# ============= dir.c ==============
if test X"$1" != X"-c" -a -f 'dir.c'; then
	echo "File already exists: skipping 'dir.c'"
else
echo "x - extracting dir.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > dir.c &&
X/* open, list and close (current) directory ------------------- */
X
X#include <stdio.h>
X#include <sys/dir.h>
X
Xextern void	fatal();
X
Xstruct dirctry {
X	ino_t	inode;
X	char	name[DIRSIZ+1];
X	};
X
Xstatic FILE		*dir;
Xstatic struct dirctry	dirln;
X
Xvoid opendir()
X	{
X	if((dir=fopen(".","r")) == NULL)
X		fatal("open spool dir");
X	}
X
Xvoid closedir()
X	{
X	if(fclose(dir) != 0)
X		fatal("close spool dir");
X	}
X
X/*
X *	step thru the directory
X *	return pointer to filename in static area
X *	or NULL upon end of directory
X */
Xchar *ls()
X	{
X	do {
X		if(fread(&dirln,sizeof(struct direct),1,dir) != 1) {
X			if(!feof(dir))
X				fatal("read spool dir");
X			return(NULL);
X			}
X		}
X		while((dirln.inode == 0) || (*dirln.name == '.'));
X
X	dirln.name[DIRSIZ] = '\0';
X	return(dirln.name);
X	}
X
SHAR_EOF
$TOUCH -am 1007212190 dir.c &&
chmod 0644 dir.c ||
echo "restore of dir.c failed"
set `wc -c dir.c`;Wc_c=$1
if test "$Wc_c" != "761"; then
	echo original size 761, current size $Wc_c
fi
fi
# ============= fifo.c ==============
if test X"$1" != X"-c" -a -f 'fifo.c'; then
	echo "File already exists: skipping 'fifo.c'"
else
echo "x - extracting fifo.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > fifo.c &&
X/* ------------------------------------------------------------
X   These routines handle the FIFOs
X
X   Read and write part of JOB struct from the FIFOs
X
X   Reading and writing FIFOs (quoting from the manual):
X
X   write(2)- "... pipe (or FIFO), no partial writes ..."
X   read(2)- number of bytes read less than requested "if the file
X	    is associated with a communication line ..., or if the
X	    number of bytes left in the file is less than" req'd.
X
X   So, as long as you read and write the same number of bytes for
X   for each call, it's an all-or-nothing situation.  The only time
X   read will fail is if there is 0 bytes in the FIFO.  The only
X   time write will fail is if the FIFO reaches its "limit".  The 
X   limit is implementation dependent.  It is 10240 bytes in the
X   UNIX-PC.  There is only one undocumented thing that could mess
X   this up: if the FIFO limit changes dynamically at runtime and
X   after the FIFO has already been openned.  If that's the case,
X   then you can't depend on being able to write into a FIFO even
X   though it *isn't* (wasn't) full.  I don't think this can happen.
X   I've tried to make this happen on my UNIX-PC and I always get
X   a FIFO that can handle 10240 bytes.  If it does happen, then
X   my whole scheme is flawed.
X   ------------------------------------------------------------ */
X
X#include <fcntl.h>
X#include "job.h"
X
Xextern void		fatal();
Xextern JOB		jjj;		/* to and from global JOB struct */
X
Xint openfifo(name)
X	char	*name;
X	{
X	int	fd;
X
X	if((fd=open(name,O_RDWR|O_NDELAY)) == -1)
X		fatal("open fifo");
X	if(fcntl(fd,F_SETFD,1) == -1)
X		fatal("set close-on-exec for fifo");
X	return(fd);
X	}
X	
X/*
X *	rdfifo() and wrfifo() use the global JOB struct, jjj
X */
X
Xint rdfifo(fd)
X	int		fd;
X	{
X	register int	bytes;
X	fifoJOB		j;
X
X	if((bytes=read(fd,(char *)&j,fifoJOBSIZ)) != 0) {
X		if(bytes != fifoJOBSIZ)
X			fatal("read job struct");
X		jjj.jobn = j.jobn;
X		jjj.msg  = j.msg;
X		jjj.time = j.time;
X		jjj.uid  = j.uid;
X		}
X	return(bytes);
X	}
X
Xint wrfifo(fd)
X	int		fd;
X	{
X	register int	bytes;
X	fifoJOB		j;
X
X	j.jobn = jjj.jobn;
X	j.msg  = jjj.msg;
X	j.time = jjj.time;
X	j.uid  = jjj.uid;
X	if((bytes=write(fd,(char *)&j,fifoJOBSIZ)) != 0)
X		if(bytes != fifoJOBSIZ)
X			fatal("write job struct");
X	return(bytes);
X	}
SHAR_EOF
$TOUCH -am 1007212590 fifo.c &&
chmod 0644 fifo.c ||
echo "restore of fifo.c failed"
set `wc -c fifo.c`;Wc_c=$1
if test "$Wc_c" != "2253"; then
	echo original size 2253, current size $Wc_c
fi
fi
# ============= getwd.c ==============
if test X"$1" != X"-c" -a -f 'getwd.c'; then
	echo "File already exists: skipping 'getwd.c'"
else
echo "x - extracting getwd.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > getwd.c &&
X/* get current working directory ------------------------------ */
X/*    don't use stdio's getcwd() because of security problem    */
X/* requirements:                                                */
X/*	size = ONE byte extra (room for \0)                     */
X/*	fildes 0 (stdin) and fildes 1 (stdout) must be open     */
X/*	no other child processes or expt'd sig.s (wait)         */
X/*	/bin/pwd returns a 0 upon success                       */
X/* returns:                                                     */
X/*	NULL upon error, buf upon success                       */
X
X/* --------------------
X#ifndef NULL
X#define NULL	0		the following includes just
X#endif				to get NULL and errno
Xextern int	errno;
X----------------------- */
X
X#include <stdio.h>
X#include <errno.h>
X
Xextern void	exit();		/* stupid lint     */
X
X
Xchar *getwd(buf,size)
X	char		*buf;
X	register int	size;
X	{
X	int		pfd[2];
X	int		rv;
X	register char	*p;
X	register int	n;
X
X	if(pipe(pfd) != 0)
X		return(NULL);
X	switch(fork()) {
X	case 0:
X		if((
X		close(1)	== 0	) && (
X		dup(pfd[1])	== 1	) && (
X		close(pfd[0])	== 0	) && (
X		close(pfd[1])	== 0	)
X		)
X		execl("/bin/pwd","pwd",NULL);
X		exit(errno);
X	case -1:
X		close(pfd[0]);
X		close(pfd[1]);
X		return(NULL);
X		}
X
X	if(close(pfd[1]) != 0)
X		goto error;
X	/*
X	 *	keep reading pwd's output until get \n (or eof, or error)
X	 *	because there's no guarantee that pwd will write its output
X	 *	all in one shot, so a single read could be short
X	 */
X	p = buf;
X	while((size > 0) && ((n=read(pfd[0],p,(unsigned)size)) > 0)) {
X		/*
X		 *	advance p and decr size while looking for newline
X		 */
X		while(n != 0) {
X			if(*p == '\n') {
X				/*
X				 *	got it - tidy up and return
X				 */
X				if((
X				close(pfd[0])	!= 0	) || (
X				wait(&rv)	== -1	) || (
X				(rv & 0xffff)	!= 0	)
X				)
X					goto error;
X
X				*p = '\0';		/* strip \n */
X				return(buf);
X				}
X			--n; --size; ++p;
X			}
X		}
X
X	/*
X	 * here if:
X	 *	size too small
X	 *	read fails or get eof before \n
X	 *	can't close pipe fildes
X	 *	get a signal
X	 *	/bin/pwd bombs
X	 */
Xerror:	close(pfd[0]);
X	close(pfd[1]);
X	wait((int *)0);
X	return(NULL);
X	}
X
SHAR_EOF
$TOUCH -am 0925071690 getwd.c &&
chmod 0644 getwd.c ||
echo "restore of getwd.c failed"
set `wc -c getwd.c`;Wc_c=$1
if test "$Wc_c" != "2096"; then
	echo original size 2096, current size $Wc_c
fi
fi
# ============= job.c ==============
if test X"$1" != X"-c" -a -f 'job.c'; then
	echo "File already exists: skipping 'job.c'"
else
echo "x - extracting job.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > job.c &&
Xchar	*jtypes[] = {
X		"AT_JOB",
X		"BATCHJ",
X		"CR_JOB",
X		"CR_TAB",
X		"REMOVE",
X		"EXECUT",
X		"CL_LOG",
X		"unknown"
X		};
SHAR_EOF
$TOUCH -am 1005133190 job.c &&
chmod 0644 job.c ||
echo "restore of job.c failed"
set `wc -c job.c`;Wc_c=$1
if test "$Wc_c" != "120"; then
	echo original size 120, current size $Wc_c
fi
fi
# ============= job.h ==============
if test X"$1" != X"-c" -a -f 'job.h'; then
	echo "File already exists: skipping 'job.h'"
else
echo "x - extracting job.h (Text)"
sed 's/^X//' << 'SHAR_EOF' > job.h &&
X/* --------------------------------------------------------------
X   The following job definition is used to pass information between
X   the daemon and users.  This information, in binary form, is in
X   the job file.  Only a small part of it is actually passed thru
X   the FIFOs.  This is because the number of jobs is limitted by
X   the size of FIFOs, which is implementation dependent.  Also, the
X   daemon only keeps some of this in memory.  The way I've declared
X   the structures here is not really kosher, but it avoids the
X   syntax mess that would happen if I did it *right*.
X	NOTE: in the unix-pc, FIFOs are 10k bytes.  There seems to
X   be no limit on how many FIFOs you can have.  I always run out of
X   processes before I run out of FIFOs.  And if you can make a FIFO
X   you can fill it up to 10240 bytes.  There must be a limit in the
X   kernal somewhere, but I think my scheme is safe.
X   -------------------------------------------------------------- */
X
Xstruct job {
X	struct job	*link;		/* link or magic number    |d |j */
X	short		jobn;		/* jobnumber & filename |f |e |o */
X	short		msg;		/* message type         |i |m |b */
X	long		time;		/* sec.s from Jan1 1970 |f |o |  */
X	int		uid;		/* user id              |o |n |f */
X	int		gid;		/* group id                   |i */
X	int		nice;		/* nice increment             |l */
X	int		umask;		/* user's umask               |e */
X	long		ulimit;		/* user's ulimit              |  */
X	int		envc;		/* num env string ptrs        |  */
X	int		envz;		/* bytes of env strings       |  */
X	char		min[60];	/* cron sched info            |  */
X	char		hour[24];	/*   char is \0 or not        |  */
X	char		mday[32];	/*   for time to do it        |  */
X	char		mon[12];	/*   indexed by corre-        |  */
X	char		wday[7];	/*   sponding value           |  */
X	};
X
Xstruct fifojob {
X	short		jobn;		/* jobnumber & filename */
X	short		msg;		/* message type         */
X	long		time;		/* sec.s from Jan1 1970 */
X	int		uid;		/* user id              */
X	};
X
Xstruct memjob {
X	struct memjob	*link;		/* link or magic number */
X	short		jobn;		/* jobnumber & filename */
X	short		msg;		/* message type         */
X	long		time;		/* sec.s from Jan1 1970 */
X	int		uid;		/* user id              */
X	};
X
Xstruct schedule {
X	char		min[60];	/* cron sched info            |  */
X	char		hour[24];	/*   char is \0 or not        |  */
X	char		mday[32];	/*   for time to do it        |  */
X	char		mon[12];	/*   indexed by corre-        |  */
X	char		wday[7];	/*   sponding value           |  */
X	};
X
Xtypedef struct job JOB;
Xtypedef struct fifojob fifoJOB;
Xtypedef struct memjob memJOB;
Xtypedef struct schedule SCHED;
X
X#define JOBSIZ		(sizeof(JOB))
X#define fifoJOBSIZ	(sizeof(fifoJOB))
X#define memJOBSIZ	(sizeof(memJOB))
X#define SCHEDSIZ	(sizeof(SCHED))
X
X/* implementation dependant - pointers == 4 bytes */
X/* Job files start out with link == NOMAGIC, then */
X/* rewind and mark job done with link == MAGIC.   */
X/*	MAGIC is 'c','r','o','n'                  */
X
X#define NOMAGIC		((JOB *)0)
X#define MAGIC		((JOB *)0x63726f6e)
X
X/* msg values */
X#define AT_JOB		0
X#define BATCHJ		1
X#define CR_JOB		2
X#define CR_TAB		3
X#define REMOVE		4
X#define EXECUT		5
X#define CL_LOG		6
X
X/* implementation dependent limits */
X#define	MINMSG		0
X#define MAXMSG		6
X#define MINNICE		(-39)
X#define MAXNICE		39
X#define MAXULIM		2147483647
X
X
X/* --------------------------------------------------------------
X   Structure of a job file:  jobn = atoi( filename )
X
X
X__________________________________________
Xstruct job	*link=MAGIC;              |
Xshort		jobn;                     V
Xshort		msg;
Xlong		time;           JOB FILE HEADER -
Xint		uid;            This part of the file is
Xint		gid;            implementation dependent
Xint		nice;           non-ascii stream of bytes.
Xint		umask;          Environment strings are
Xlong		ulimit;         marked with null bytes at
Xint		envc;           their ends, not newlines.
Xint		envz;
Xchar		min[60],
X		hour[24],
X		mday[32],
X		mon[12],
X		wday[7];
X
X<<environment>>                           ^
X<<shell>>                                 |
X<<working directory>>                     |
X<<crontab command>> -or- '\0'             |
X__________________________________________|
X
X<<user supplied>>
X
X   -------------------------------------------------------------- */
X
SHAR_EOF
$TOUCH -am 1005124890 job.h &&
chmod 0644 job.h ||
echo "restore of job.h failed"
set `wc -c job.h`;Wc_c=$1
if test "$Wc_c" != "4276"; then
	echo original size 4276, current size $Wc_c
fi
fi
# ============= log.c ==============
if test X"$1" != X"-c" -a -f 'log.c'; then
	echo "File already exists: skipping 'log.c'"
else
echo "x - extracting log.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > log.c &&
X/*  handle daemon's log file */
X
X#include <fcntl.h>
X#include <stdio.h>
X#include <pwd.h>
X#include <time.h>
X#include "cron.h"
X#include "job.h"
X
Xextern int		errno;
Xextern char		*ctime();
Xextern long		time();
X
Xextern void		fatal();
Xextern struct passwd	*cronpw;
Xextern int		nul;
Xextern JOB		jjj;
Xextern char		*jtypes[];
X
Xstatic int		log;			/* fildes for logfile */
Xstatic char		line[LINESIZ];		/* general line buff  */
X
X/* log job ---------------------------------------------------- */
X
Xvoid logjob(str)
X	char	*str;
X	{
X	register int	m;
X
X	m=jjj.msg;
X	if((m < MINMSG) || (m > MAXMSG)) m=MAXMSG+1;
X	sprintf(line,"%s %s job= %d uid= %d\n",
X		str,jtypes[m],jjj.jobn,jjj.uid);
X	write(log,line,(unsigned)strlen(line));
X	}
X
X/* log time --------------------------------------------------- */
X
Xvoid logtime(str)
X	char	*str;
X	{
X	long	t;
X
X	t = time((long *)0);
X	sprintf(line,"\n%s%s",ctime(&t),str);
X	write(log,line,(unsigned)strlen(line));
X	}
X
X/* log message string ----------------------------------------- */
X
Xvoid logmsg1(str)
X	char	*str;
X	{
X	sprintf(line,"%s\n",str);
X	write(log,line,(unsigned)strlen(line));
X	}
X
X/* log message string and number ------------------------------ */
X
Xvoid logmsg2(str,n)
X	char	*str;
X	int	n;
X	{
X	sprintf(line,"%s : %d\n",str,n);
X	write(log,line,(unsigned)strlen(line));
X	}
X
X/* open a file descriptor to the log file --------------------- */
X/*    and connect stderr there too				*/
X
Xvoid openlog()
X	{
X
X	if((log=open(LOG,O_WRONLY|O_CREAT|O_APPEND,LOG_PERM)) == -1)
X		fatal("open log file");
X#ifndef DEBUG
X	if(chown(LOG,cronpw->pw_uid,cronpw->pw_gid) != 0)
X		fatal("chown cron log file");
X#endif
X	if(fcntl(log,F_SETFD,1) == -1)
X		fatal("set close-on-exec for log file");
X	if((close(2) != 0) || (dup(log) != 2)) {
X		logmsg2("FATAL: can't connect stderr to log",errno);
X		fatal("connect stderr to log");
X		}
X	}
X
X/* clean log file --------------------------------------------- */
X
Xvoid cleanlog()
X	{
X	if((
X	close(2)					!=  0	) || (
X	dup(nul)					!=  2	) || (
X	close(log)					!=  0	) || (
X	unlink(LOG)					!=  0	) || (
X	(log=open(LOG,
X	   O_WRONLY|O_CREAT|O_APPEND,LOG_PERM))		== -1	) || (
X	close(2)					!=  0	) || (
X	dup(log)					!=  2	) || (
X#ifndef DEBUG
X	chown(LOG,cronpw->pw_uid,cronpw->pw_gid)	!=  0	) || (
X#endif
X	fcntl(log,F_SETFD,1)				== -1	)
X		)
X		fatal("clean log");
X
X	logtime("");
X	}
SHAR_EOF
$TOUCH -am 1025130590 log.c &&
chmod 0644 log.c ||
echo "restore of log.c failed"
set `wc -c log.c`;Wc_c=$1
if test "$Wc_c" != "2311"; then
	echo original size 2311, current size $Wc_c
fi
fi
# ============= login.c ==============
if test X"$1" != X"-c" -a -f 'login.c'; then
	echo "File already exists: skipping 'login.c'"
else
echo "x - extracting login.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > login.c &&
X/* Setup and check if user allowed to use this program -------- */
X/* login() sets up the following:                               */
X/*	realuid - global int to avoid alot of getuid() calls    */
X/*	userpw  - global ptr to STATIC passwd info              */
X/* checks the allow/deny files to see if the user is allowed    */
X
X#include <stdio.h>
X#include <pwd.h>
X#include <errno.h>
X#include "cron.h"
X
Xextern int		getuid();
Xextern char		*getenv();
Xextern int		putenv();
Xextern char		*getlogin();
Xextern struct passwd	*getpwnam();
X
Xextern void		fatal();
X
X/* global varibles -------------------------------------------- */
X
Xint		realuid;		/* so don't have to getuid() alot */
Xstruct passwd	*userpw;		/* --> static area, be careful    */
X
X
X/* ------------------------------------------------------------ */
X
Xvoid deny()
X	{
X	errno = 0;
X	fatal("- permission denied");
X	}
X
X/* ------------------------------------------------------------ */
X
Xvoid login(allowfile,denyfile)
X	char		*allowfile,*denyfile;
X	{
X	register char	*p,*q;
X	register FILE	*fp;
X	register int	allow;
X	char		line[LINESIZ];
X
X	realuid = getuid();
X
X	if((
X	(p=getenv("LOGNAME"))	== NULL		) || (
X	(userpw=getpwnam(p))	== NULL		) || (
X	userpw->pw_uid		!= realuid	)
X	) {
X
X#ifndef SET_LOGNAME
X		/*
X		 *	LOGNAME must be set in user's environment
X		 *	and it must agree with their real uid
X		 */
X		deny();
X#else
X		if(realuid != 0) deny();
X		/*
X		 *	superuser (realuid == 0) gets special treatment
X		 */
X		if(putenv("LOGNAME=root") != 0)
X			fatal("put LOGNAME in env");
X		if((
X		(userpw=getpwnam("root"))	== NULL	) || (
X		userpw->pw_uid			!= 0	)
X		)
X			fatal("passwd file bad");
X#endif
X		}
X
X	if(realuid == 0)
X		;	/* don't check allow/deny files for superuser */
X
X	else if((fp=fopen(allowfile,"r")) != NULL) {
X		allow = 0;
X		while(fgets(line,LINESIZ,fp) != NULL) {
X			/* check if line equals name */
X			p = line; q = userpw->pw_name;
X			while(*p++ == *q++) ;
X			if((*(--p) == '\n') && (*(--q) == '\0')) {
X				allow = 1;
X				break;
X				}
X			}
X		if(ferror(fp))
X			fatal("read allow file");
X		if(fclose(fp) != 0)
X			fatal("close allow file");
X		if(!allow) deny();
X		}
X	else if((fp=fopen(denyfile,"r")) != NULL) {
X		allow = 1;
X		while(fgets(line,LINESIZ,fp) != NULL) {
X			/* check if line equals name */
X			p = line; q = userpw->pw_name;
X			while(*p++ == *q++) ;
X			if((*(--p) == '\n') && (*(--q) == '\0')) {
X				allow = 0;
X				break;
X				}
X			}
X		if(ferror(fp))
X			fatal("read deny file");
X		if(fclose(fp) != 0)
X			fatal("close deny file");
X		if(!allow) deny();
X		}
X	else if(errno == ENOENT) deny();
X	else fatal("open allow,deny file");
X	}
SHAR_EOF
$TOUCH -am 0924191090 login.c &&
chmod 0644 login.c ||
echo "restore of login.c failed"
set `wc -c login.c`;Wc_c=$1
if test "$Wc_c" != "2583"; then
	echo original size 2583, current size $Wc_c
fi
fi
# ============= makefile ==============
if test X"$1" != X"-c" -a -f 'makefile'; then
	echo "File already exists: skipping 'makefile'"
else
echo "x - extracting makefile (Text)"
sed 's/^X//' << 'SHAR_EOF' > makefile &&
X# makefile for cron facility
X
XCFLAGS = -O
XLDFLAGS = -s
X
XATSRCS = at.c dir.c fifo.c getwd.c job.c login.c parsetime.c parsesched.c \
X	resched.c
XATOBJS = at.o dir.o fifo.o getwd.o job.o login.o parsetime.o parsesched.o \
X	resched.o
X
XCRONSRCS = daemon.c dir.c fifo.c job.c log.c memlist.c mkfifo.c resched.c
XCRONOBJS = daemon.o dir.o fifo.o job.o log.o memlist.o mkfifo.o resched.o
X
XCTABSRCS = crontab.c login.c
XCTABOBJS = crontab.o login.o
X
XCONVSRCS = convertjob.c
XCONVOBJS = convertjob.o
X
Xall: at cron crontab convertjob
X
Xat: $(ATOBJS)
X	ld $(LDFLAGS) /lib/crt0s.o /lib/shlib.ifile -o at $(ATOBJS)
X	-ln at batch
X	-ln at cronjob
X	-ln at crtabj
X
Xcron: $(CRONOBJS)
X	ld $(LDFLAGS) /lib/crt0s.o /lib/shlib.ifile -o cron $(CRONOBJS)
X
Xcrontab: $(CTABOBJS)
X	ld $(LDFLAGS) /lib/crt0s.o /lib/shlib.ifile -o crontab $(CTABOBJS)
X
Xconvertjob: $(CONVOBJS)
X	ld $(LDFLAGS) /lib/crt0s.o /lib/shlib.ifile -o convertjob $(CONVOBJS)
X
X# ======================
X
Xat.o: cron.h job.h at.c
X
Xconvertjob.o: cron.h job.h convertjob.c
X
Xcrontab.o: cron.h crontab.c
X
Xdaemon.o: cron.h job.h daemon.c
X
Xdir.o: dir.c
X
Xfifo.o: job.h fifo.c
X
Xgetwd.o: getwd.c
X
Xjob.o: job.c
X
Xlog.o: cron.h job.h log.c
X
Xlogin.o: cron.h login.c
X
Xmemlist.o: cron.h job.h memlist.c
X
Xmkfifo.o: cron.h mkfifo.c
X
Xparsesched.o: cron.h job.h parsesched.c
X
Xparsetime.o: cron.h parsetime.c
X
Xresched.o: job.h resched.c
X
X# ======================
X
Xlint: atlint cronlint crontablint
X
Xatlint:
X	lint $(ATSRCS)
X
Xcronlint:
X	lint $(CRONSRCS)
X
Xcrontablint:
X	lint $(CTABSRCS)
X
X# ======================
X
Xclean:
X	rm -f *.o
X
Xclobber:
X	rm -f *.o at batch cronjob crtabj cron crontab convertjob
X	rm -rf lib spool
X
Xpublic:
X	make clobber
X	shar * >cron.shar
X	compress cron.shar
X	mv cron.shar.Z /usr/src/public/cron.shar.Z
SHAR_EOF
$TOUCH -am 1017065090 makefile &&
chmod 0644 makefile ||
echo "restore of makefile failed"
set `wc -c makefile`;Wc_c=$1
if test "$Wc_c" != "1734"; then
	echo original size 1734, current size $Wc_c
fi
fi
# ============= memlist.c ==============
if test X"$1" != X"-c" -a -f 'memlist.c'; then
	echo "File already exists: skipping 'memlist.c'"
else
echo "x - extracting memlist.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > memlist.c &&
X/* -----------------------------------------------------------
X   The following code handles the in-memory storage of jobs.
X   Two linked lists are maintained.  The first is a list of
X   free blocks of memory for jobs.  The free list is basically
X   handled like a stack because any free block is useful.  The
X   second is a list of pending jobs.  It is in order of time
X   that the job is scheduled to be run.  Inserting jobs in the
X   todo list requires searching for the right spot, but removing
X   from this list is easy because the earliest job is always at
X   the front of the list.
X   ----------------------------------------------------------- */
X
X#include <stdio.h>
X#include <string.h>
X#include "cron.h"
X#include "job.h"
X
Xextern void	fatal();		/* \ link with daemon.o       */
Xextern JOB	jjj;			/* /                          */
Xextern void	logjob();		/* \ link with log.o          */
Xextern void	logmsg1();		/* /                          */
Xextern void	opendir();		/* \                          */
Xextern void	closedir();		/*  > link with dir.o         */
Xextern char	*ls();			/* /                          */
X
XmemJOB		*jtodo;			/* head of pending jobs list  */
X
Xstatic memJOB	*jfree;			/* head of free jobs list     */
Xstatic memJOB	jobmem[NUMJOBS];	/* in-memory storage for jobs */
X
X/*
X *	intilz:
X *	link everything to free list, nothing on todo list
X */
Xstatic void initfree()
X	{
X	register memJOB	*j,*jend;
X
X	jfree = jobmem;
X	jtodo = NULL;
X
X	/* link everything to free list */
X	j = jfree;
X	jend = &(jobmem[NUMJOBS-1]);
X	while(j < jend) {
X		j->link = j+1;
X		++j;
X		}
X	j->link = NULL;
X	}
X
X/*
X *	remove first one on the free list or return NULL
X */
Xstatic memJOB *rmfree()
X	{
X	register memJOB	*j;
X
X	j = jfree;
X	if(j != NULL) jfree = j->link;
X	return(j);
X	}
X
X/* externally accessed routines ------------------------------- */
X
X/*
X *	insert at head of free list
X */
Xvoid infree(j)
X	register memJOB	*j;
X	{
X	j->link = jfree;
X	jfree = j;
X	}
X
X/*
X *	remove first one on todo list or return NULL
X */
XmemJOB *rmtodo()
X	{
X	register memJOB	*j;
X
X	j = jtodo;
X	if(j != NULL) jtodo = j->link;
X	return(j);
X	}
X
X/*
X *	delete from todo list by jobnumber or return NULL
X */
XmemJOB *deltodo(n)
X	register int	n;
X	{
X	register memJOB	*j,*k;
X
X	j = jtodo;
X	if((j == NULL) || (j->jobn == n)) {
X		jtodo = j->link;
X		return(j);
X		}
X	k = jtodo;
X	j = jtodo->link;
X	while(j != NULL) {
X		if(j->jobn == n) {
X			k->link = j->link;
X			return(j);
X			}
X		k = j;
X		j = k->link;
X		}
X	return(NULL);
X	}
X
X/*
X *	intodo:
X *	insert in todo list by time
X */
Xvoid intodo(j)
X	register memJOB	*j;
X	{
X	register memJOB	*k,*l;
X	register long	t;
X
X	t = j->time;
X	if((jtodo == NULL) || (t < jtodo->time)) {
X		j->link = jtodo;
X		jtodo = j;
X		return;
X		}
X	k = jtodo;
X	l = jtodo->link;
X	while(l != NULL) {
X		if(t < l->time) {
X			j->link = l;
X			k->link = j;
X			return;
X			}
X		k = l;
X		l = k->link;
X		}
X	j->link = NULL;
X	k->link = j;
X	}
X
X/*
X *	see if a jobnumber is already in use
X */
Xint taken(n)
X	register int	n;
X	{
X	register memJOB	*j;
X
X	j = jtodo;
X	while(j != NULL) {
X		if(j->jobn == n) return(1);
X		j = j->link;
X		}
X	return(0);
X	}
X
X/*
X *	insert:
X *	put global JOB jjj on todo list
X */
Xvoid insert()
X	{
X	register memJOB	*j;
X
X	if((j=rmfree()) == NULL)
X		fatal("get empty job slot");
X	j->jobn = jjj.jobn;
X	j->msg  = jjj.msg;
X	j->time = jjj.time;
X	j->uid  = jjj.uid;
X	intodo(j);
X	}
X
X/*
X *	initlist:
X *	build the in-memory job list
X *	note- cur dir must be set to spool dir before calling here
X */
Xvoid initlist()
X	{
X	char	*fname;
X	FILE	*fp;
X
X	logmsg1("build in-mem job list");
X	initfree();
X	opendir();
X	while((fname=ls()) != NULL) {
X		if((fp=fopen(fname,"r")) == NULL)
X			fatal("open a job file");
X
X		if((
X		fread(&jjj,JOBSIZ,1,fp)	== 1		) && (
X		jjj.link		== MAGIC	) && (
X		jjj.jobn		== atoi(fname)	) && (
X		jjj.msg			>= MINMSG	) && (
X		jjj.msg			<= MAXMSG	) && (
X		jjj.time		>= 0L		) && (
X		jjj.uid			>= 0		) && (
X		jjj.gid			>= 0		) && (
X		jjj.nice		>= MINNICE	) && (
X		jjj.nice		<= MAXNICE	) && (
X		jjj.umask		>= 0		) && (
X		jjj.umask		<= 0777		) && (
X		jjj.ulimit		>= 0L		) && (
X		jjj.ulimit		<= MAXULIM	) && (
X		jjj.envc		>= 4		) && (
X		jjj.envz		>= 3		)
X		) {
X			insert();
X			logjob("queued");
X			}
X
X		else {
X			jjj.jobn = atoi(fname);
X			logjob("rmv corrupt file");
X			if(unlink(fname) != 0)
X				fatal("unlink job file");
X			}
X		if(fclose(fp) != 0)
X			fatal("close job file");
X
X		} /* end while */
X
X	closedir();
X	}
SHAR_EOF
$TOUCH -am 1005141690 memlist.c &&
chmod 0644 memlist.c ||
echo "restore of memlist.c failed"
set `wc -c memlist.c`;Wc_c=$1
if test "$Wc_c" != "4373"; then
	echo original size 4373, current size $Wc_c
fi
fi
# ============= mkfifo.c ==============
if test X"$1" != X"-c" -a -f 'mkfifo.c'; then
	echo "File already exists: skipping 'mkfifo.c'"
else
echo "x - extracting mkfifo.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > mkfifo.c &&
X/* make fifos when daemon starts up (if they don't exist)    */
X/*	cron passwd struct must be setup before calling here */
X
X#include <sys/stat.h>
X#include <pwd.h>
X#include "cron.h"
X
Xextern void		fatal();
X
Xextern struct passwd	*cronpw;
X
Xvoid mkfifo(name)
X	char		*name;
X	{
X	if(access(name,0) != 0) {
X		if(mknod(name,S_IFIFO|FIFO_PERM,0) != 0)
X			fatal("make fifo");
X#ifndef DEBUG
X		if(chown(name,cronpw->pw_uid,cronpw->pw_gid) != 0)
X			fatal("chown cron fifo");
X#endif
X		}
X	}
X
SHAR_EOF
$TOUCH -am 0924095690 mkfifo.c &&
chmod 0644 mkfifo.c ||
echo "restore of mkfifo.c failed"
set `wc -c mkfifo.c`;Wc_c=$1
if test "$Wc_c" != "475"; then
	echo original size 475, current size $Wc_c
fi
fi
echo "End of part 3, continue with part 4"
exit 0



More information about the Alt.sources mailing list