[alt.sources] cron

donlash@uncle.uucp (Donald Lashomb) (01/14/91)

Here is the newest version of my cron(1M) facility.  This version fixes
the getwd() bug in my previous version, adds a new option and generally
is a cleaner implementation than the old version.

Cron(1M) is a facility that allows you to schedule jobs that will be
executed at a later time.  It includes the at(1), batch(1) and [user]
crontab(1) commands which are available on many unix systems, but not
the AT&T UNIX-pc; so I wrote my own.

Some highlights:

	*  the ability to specify a nice value, even neg for root

	*  great variety of listing options

	*  accepts names like Sun Mon etc in times, dates, schedules

	*  security strictly enforced by real uid

	*  new cronjob command runs like at(1), but repetitively like
	   crontab.  I use this instead of crontab, it's more efficient.

	*  crtabj command is what really does crontab

	*  everything (almost) compatable with standard sysV versions

This cron facility was written for the AT&T Unix-PC model 7300.  I believe
it should port well to other sysV machines.  How well it will port to BSD
machines, I don't know.

---
Donald Lashomb			donlash@uncle.UUCP
Cranberry Lake, NY		...!uncle!crlake!{install root donny}

---- Cut Here and unpack ----
#!/bin/sh
# This is a shell archive (shar 3.21)
# made 10/25/1990 21:37 UTC by install@crlake
# Source directory /u/install/Filecabinet/src/cron
#
# existing files will NOT be overwritten
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#  19046 -rw-r--r-- Design_Notes
#   4358 -rw-r--r-- Examples
#    244 -rw-r--r-- Files
#   7286 -rwxr--r-- Install
#     40 -rw-r--r-- Name
#  17482 -rw-r--r-- README
#    882 -rwxr--r-- Remove
#      4 -rw-r--r-- Size
#  15142 -rw-r--r-- at.1
#  22541 -rw-r--r-- at.c
#   2275 -rw-r--r-- convertjob.c
#   7418 -rw-r--r-- cron.1
#   4499 -rw-r--r-- cron.h
#   6593 -rw-r--r-- crontab.1
#   9025 -rw-r--r-- crontab.c
#  16055 -rw-r--r-- daemon.c
#    761 -rw-r--r-- dir.c
#   2253 -rw-r--r-- fifo.c
#   2096 -rw-r--r-- getwd.c
#    120 -rw-r--r-- job.c
#   4276 -rw-r--r-- job.h
#   2311 -rw-r--r-- log.c
#   2583 -rw-r--r-- login.c
#   1734 -rw-r--r-- makefile
#   4373 -rw-r--r-- memlist.c
#    475 -rw-r--r-- mkfifo.c
#   4523 -rw-r--r-- parsesched.c
#  10619 -rw-r--r-- parsetime.c
#   3496 -rw-r--r-- resched.c
#   4537 -rw-r--r-- version
#
if touch 2>&1 | fgrep '[-amc]' > /dev/null
 then TOUCH=touch
 else TOUCH=true
fi
# ============= Design_Notes ==============
if test X"$1" != X"-c" -a -f 'Design_Notes'; then
	echo "File already exists: skipping 'Design_Notes'"
else
echo "x - extracting Design_Notes (Text)"
sed 's/^X//' << 'SHAR_EOF' > Design_Notes &&
XCron Facility by Donald Lashomb
X
X
X===                                                                ===
X| The following notes were some of things that I was thinking during |
X| the design of the at(1) facility.  DON'T pay too much attention to |
X| them, many of the details were changed in the final design.        |
X===                                                                ===
X
XThis project is to build an at(1) facility for the unix-pc.
X
X[ wrote all my own code ]
X
XMy design for at(1) consists of basically two programs: a daemon that
Xruns all the time in the backaground, and a user-callable command that
Xaccepts data from the user and passes it to the daemon thru a FIFO.
X
X[ really two fifo.s used ]
X
XIn standard sysV unix, the at(1) facility is co-implemented as part of
Xcron(1M) and both facilities are available to trusted users.  Files are
Xstored in subdirectories using cron's name, like: /usr/lib/cron/FIFO.
XIn the unix-pc, however, smgr(1M) takes the place of cron, and is
Ximplemented more like sysIII unix's cron.  Therefore, it would not make
Xsense to use cron's name on directories that are used only by at(1).
XSo the filenames that I'll use will sort of parallel sysV, but use at's
Xname, like: /usr/lib/at/FIFO.
X
X[ no- used std sysV names ]
X
XSome of the design objectives are:
X
X	performance - don't use too much processor time or tie-up too
X		many of the system's resources.  In a small system like
X		the unix-pc, this is a major consideration.
X
X	security - always a consideration.  The ability to abuse at(1)
X		is well known.  There are basically three ways to break
X		security using at(1).  I won't detail them here, but I
X		will try to guard against all three in my design.
X
X	ease of use - at(1) should be easy for users to use, and easy
X		for the system admin to take care of.  I would very much
X		like to make the user's command line the same as sysV's
X		at(1), but I don't have a manual page from any version
X		of unix to follow.  Therefore, my choice of options and
X		command line arguments may not be *standard*.  I will
X		try to make reasonable choices for them, though.
X
X		[ options and args upwardly compatable with sysV ]
X		[ thanks to John Milton for his suggestions      ]
X
X	multi-user - I'll try to keep in mind that more than one user may
X		be using at(1) at the same time.
X
X	signals - handle them gracefully.  If the user presses the SIGINT
X		key, for example, there shouldn't be half-baked/temporary
X		files laying around.
X
XSo here's the basic design:
X
X	The daemon is started up at boot time or whatever, runs in the
X		background and has super-user uid.  It basically reads the
X		FIFO to see if anyone has sent it a message.  If no message
X		is received, it goes to sleep for a little while.  When it
X		wakes up, it checks the time and looks to see if any jobs
X		are scheduled to be done at that time.  If there is nothing
X		to do it just checks the FIFO and goes back to sleep.
X
X	The user calls the at(1) command and gives it the name of a file
X		containing the command he wants done or types the commands
X		in using stdin.  The user also supplies the time at which
X		he wants this stuff done.  At(1) checks to see if this user
X		is allowed to use the facility.  Opens up a temporary file.
X		Puts some environment setup stuff in the temp file (like
X		cd `pwd`) and copies the user's stuff into it.  It then sends
X		a message to the daemon thru the FIFO telling the name of the
X		temp file amoung other things.
X		
X		[ no- only stdin for cmds ]
X		[ no temp file used, writes file in spool directory ]
X
X	When the daemon gets the message from the FIFO, it moves the temp
X		file into the spool directory an gives it a unique name.  The
X		filename, time, uid and other info about the job is put on an
X		in-memory list of jobs; sort of like a symbol table.  This
X		list will probably be orderred on the time that the jobs are
X		supposed to be done so that the daemon can determine which
X		job to run when as quickly as possible.  Otherwise the daemon
X		would have to scan the whole list everytime it wakes up.
X
X		[ job file is already in the spool directory ]
X
X	At some point the daemon will wake up, check its list and find that
X		it is time to run the job.  It will fork off a process, set
X		the uid and gid to the job's owner and call the shell to run
X		it.  I may incorporate the ability to use either sh(1) or
X		ksh(1) and the ability to set a nice value for the job.  The
X		job will be taken off the daemon's list and the file removed.
X
X		[ uses whatever shell defined in /etc/passwd for the user ]
X		[ user can specify nice value ]
X
X	Some details that must be answered: How to connect stdin, stdout and
X		stderr for a job?  If ksh(1) is used, what to do with history?
X		How is the job file removed; by the daemon or by the shell?
X		Can the user delete a job before it runs; if so how does he
X		find it?
X
X		[ stdin is job file, stdout and stderr mailled to user ]
X		[ ksh histfile same as user's. used when read -s or print -s ]
X		[ job rmv'd by unlinking file and/or send rmv msg to daemon ]
X		[ at -l lists jobs, at -r removes them ]
X
X	There is also the consideration of incorporating a batch(1) facility
X		into at(1).  It would be relitively easy to do a simple batch
X		that doesn't monitor system load, just set the maximum nice
X		value and let-her-go.  You could get real fancy, by building
X		a histogram of system load factors and intelegently picking
X		a historically low-load time to run batch jobs; but I think
X		that is going too far.  One simple possiblity is to just count
X		the number of active processes, and if the number is close to
X		the minimum number known to batch, and has stayed close to the
X		minimum for awhile; then run a batch job.  How do you count
X		the number of processes?  Well you could do ps(1) and count
X		the number of lines, but this would create alot of overhead.
X		The other way is to count the kernal's process table directly.
X		I know how to do this.  It's not a easy as it sounds, though.
X		Hmmmm, we'll see, let's concentrate on at(1) first.
X
X		[ batch just runs the job at maximum nice ]
X
X/* --------------------------------------------------------------
X   FIFOs:  daemon ----JNUM----> user
X	   daemon <---FNAM----- user
X
X   Daemon generates unique jobnumbers and sticks them in JNUM fifo.
X   User gets jobnumber from JNUM fifo, uses it as a filename, and
X   puts its information in that file.  When user finishes building
X   the file, it writes the filename(=jobnumber) into FNAM fifo.
X   Daemon reads filename from FNAM fifo, opens the file, reads some
X   info from it and builds its in-memory list.  When the time comes,
X   the daemon runs the job and writes the jobnumber back on the JNUM
X   fifo.  The reasons to do it this way are:  (1) daemon's in-memory
X   list can't overflow because it limits the number of jobnumbers
X   available to the user.  (2) daemon can always build its in-memory
X   list from files in the spool directory at startup time.  (3) the
X   user can build its files directly in the spool directory.  (4) if
X   the daemon finds a file in the spool directory which is not on
X   its list, it means the user has crashed; it can remove the file
X   and make that jobnumber available again.  (5) if the user cancels
X   the job before writing its filename into the FNAM fifo, he writes
X   it back on the JNUM fifo (sneeky!).  (6) the last line in every
X   job file is specially marked.  If everything crashes this line
X   will be missing.  The daemon removes files that don't have this
X   special line at startup.  (7) the daemon opens the job file and
X   unlinks it when the job is run.  The jobnumber is immediately
X   available and the file is deleted automatically when the shell is
X   done, even if something or everything crashes.
X   -------------------------------------------------------------- */
X
X   [ #4 not implemented ]
X   [ caveate: the fifos must be big enough to hold all the job no.s ]
X   [[ #6 jobs are marked with a magic number not special last line ]]
X
Xcron facility  4/89 D.Lashomb
X
XThis project is to add the cron capabilities to the at(1) facility
Xalready implemented.  Some of the key ideas are:
X(1) users are screened thru cron.allow and cron.deny
X(2) only one crontab file for each user
X(3) format of crontab files:
X	# any line beginning with a # is a comment
X	mm hh DD MM ww <action>
X		schedule fields are integers incl ran-ges com,mas
X		action upto \n or first % remainder: %=\n stdin to command
X(4) the daemon scans all crontabs at startup and when new ones are added
X(5) when daemon scans a crontab it generates a job (or jobs) for the next
X    scheduled action.
X(6) when the daemon runs a cronjob it rescans the asscociated crontab, again
X    generating jobs for the next scheduled actions for that user.
X
X[ crontab(1) not implemented yet.  I did cronjob(1) instead. ]
X[ most of the hooks for crontab are in the daemon.  I'll add ]
X[ crontab sometime soon [[ crontab implemented ]]            ]
X[[ #4,5,6 scanning the crontab is a function of the crontab(1) ]]
X[[ command, the daemon has nothing to do with crontab files    ]]
X
X==================
X
XUser's crontabs are stored in /usr/spool/cron/crontabs with the filename = uid.
XThis is different from sysV, where filenames=lognames.  It also means that you
Xcan't have multiple crontabs for multiple login names with the same uid.  There
Xare some problems in this area with at(1) too.  It could be fixed with great
Xeffort.
X
X[ This is mostly right! My programs go by uid not logname ]
X
XAnother thing is that the crontab(1) command must have read access to the
Xuser's files and yet has to be suid cron to write into the spool dir.  There
Xare a couple of solutions to this.  Could have crontab(1) only a little
Xscript that opens the user's file and pipes it into stdin of the *real* cmd.
XAnother solution would be to have a group called "cron" instead of a login.
X
XHere's how to do it: at(1), batch(1), and crontab(1) are all the same program
Xwhich runs as effective uid = cron, real uid = the user.  Crontab(1) can
Xwrite into the spool directories, but can't read the user's file because its
Xeffective uid is cron.  So it spawns a child that does setuid back to the
Xreal user's uid and pipes the user's file in.
XUser calls crontab(1) and gives the name of his file (or stdin).
XCrontab(1) spawns a "cat" process to pipe the user's file in.  For every line
Xin every user's crontab files, a ct_job is created.  The only reason to keep
Xa copy of the user's crontab file in ...spool/crontabs is so the user can get
Xa copy back with the -l option.  The daemon doesn't use it at all.
X
X[ hooks in the daemon for CR_TAB ]
X[ crontab opens user's file after setuid() back to real uid ]
X[[ at(1), batch(1), cronjob(1) and crtabj(1) are all the same program. ]]
X[[ crontab(1) parses the crontab file and generates multiple calls to  ]]
X[[ crtabj(1) to create the jobs - one for each line in the crontab file]]
X[[ note: bug in setuid() won't let you setuid(getuid()) for real uid=0 ]]
X[[       so crontab command must run as SUID root, intead of SUID cron ]]
X
X=================
X
XAnother problem concerns the environment for cron_jobs.  Unfortunately the
XsysV manual page specifically states that a default environment is setup for
Xcron_jobs; incl $HOME (ok), $LOGNAME (ok), $SHELL (so-so) and $PATH (ng).
XWhereas at(1) could just grab the current environment for its jobs, cron will
Xhave to figure-out what to give its jobs.  The sysV manual also specifically
Xstates the .profile will not be executed.  The biggest problem is $PATH.
X$HOME, $LOGNAME and $SHELL can be setup from the passwd file.  If the user's
Xlogin shell is ksh, then $ENV could be executed, if setup.  Now there are
Xreal problems in this area!  In my opinion, a cron_job should be equivelent
Xto at(1), except that it is cyclic instead of one-shot.  Barring that; cron
Xshould be equivelent to logging in and executing the commands by hand, so
Xthat your environment is recreated.  It is MUCH cheaper, however, to do it
Xthe sysV way - for one thing you don't have to save the user's environment
Xanywhere.  But there are a great many programs that depend on various environ
Xvariables being setup.  The best way I think is this:  the crontab(1) command
Xdoes a cd to $HOME first and then saves the environment like at(1). [maybe]
X
X[ uses full user environment as exists when the job is created ]
X[[ doesn't cd to $HOME either, uses current working directory when created ]]
X
X==================
X
XMarch 15, 1990  -- a few things that should be fixed
X
XI released version 3.0.1 to usenet on Dec 21, 89.  Since then I have received
Xmostly positive feedback about this facility.  Only one person reported any
Xtrouble and that was traced to the use of the stty(1) command in his ksh $ENV
Xfile.  I myself, however, have discovered a few little problems.  I have cron
Xrunning on three machines.  Machine #1, crlake, runs 24 hours a day and has
Xhad no trouble.  Machine #2, crlak2, is a machine which is shutdown every
Xnight (my wife doesn't like the noise).  And, machine #3, peanut, has a bad
Xbattery or RTC chip or something - it doesn't keep proper time when shutdown.
XFrom my experience with these machines, and other considerations, here are a
Xfew things that should be changed:
X
X	The CATCHUP scheme is only effective on at(1) jobs.  It does not
X	work for crontab(1) or cronjob(1) jobs.  This is because the date
X	stored in the job file for crontab(1) and cronjob(1) jobs doesn't
X	control execution, rather the resched() function takes care of
X	scheduling execution times.  What happens on a machine that is
X	shutdown every night is that crontab(1) and cronjob(1) jobs which
X	should be executed at night never get done.  The hard part of
X	making CATCHUP work for these jobs is determining when the machine
X	went down and deciding whether to run the job or not.  I could
X	update the date in the job file each time the job is executed, but
X	that wouldn't be very efficient.  Cron's log file, however, is
X	used to record every action.  So what I can do is check the mod
X	time of the log file to determine (about) when the machine went
X	down.  Ok, let's say the machine went down 8 hrs ago and you are
X	now bringing it back up.  Cron missed some jobs, and is now
X	catching them up.  What about jobs that were, say, supposed to
X	execute hourly - does cron execute them 8 times or just once?  I
X	say do them just once.  Most likely these jobs are cleanup-type
X	jobs or uucp polling jobs anyway.
X
X[ added -x option to at(1)... to execute jobs at will.  When bringing ]
X[ the system up after a shutdown, the system admin can "-x" any jobs  ]
X[ he/she thinks should have been run but weren't.                     ]
X
X	The sleep time in the daemon's sleep-wake cycle gets screwed-up
X	if someone changes the system time (ie with date command).  What
X	happens is that the daemon goes into either a 1-sec cycle or it
X	sleeps for a long time until it resync-s with the system time
X	(depending on whether the system time is set backwards or ahead).
X	The fall back to 1-sec sleep was intended to take care of the
X	situation in which the daemon's processing time while awake might
X	be longer than the GRAINULARITY time.  If this was caused by a
X	temporary heavy load, then the 1-sec sleep would allow the daemon
X	to keep chugging along until the load cooled off.  In all honesty,
X	I never anticipated that someone would change the system clock.
X	BTW- If you've got a machine with a dead battery, you should edit
X	the /etc/rc script to let the operator set the clock before doing
X	anything else.  OK, so what if someone changes the system time?
X	Well, first I've got to detect that the clock has been changed.
X	This isn't too hard, if the calculated sleep time is more than
X	GRAINULARITY or a lot less than zero, then the clock was changed.
X	Next, I've got to figure out what to do about it.  Probably
X	something like catchup should happen.  If the system clock is set
X	back, the jobs should be rescheduled and so some jobs might be
X	run twice.  If the system clock is set ahead, the jobs should be
X	caught up so that the jobs get run at least once.
X
X[ The 1-sec sleep stuff was retained for minor overloads.  If system ]
X[ clock is set significantly ahead or back, the daemon's idea of the ]
X[ time is reset to the system clock.  Jobs might still be run twice  ]
X[ or skipped over.  Ain't perfect, but it's better than it was.      ]
X
X	I'm not realy comfortable with the way that most jobs are marked
X	with "# job done".  The idea for this was to protect against
X	half-baked jobs in case the user crashed while creating a job.
X	But crontab(1) jobs are not marked, and so don't get the same
X	protection.  A better idea would be to have a checksum as part of
X	the job header.  This will involve seeking back to the beginning
X	of the job file so that the checksum can be written, which causes
X	some loss of performance.  Also, listing the "user" portion of
X	a job with the "-lu" option, causes the newline preceding the
X	"# job done" mark to be output.  This newline was not enterred
X	by the user, and so it should not be output.  Changing to a
X	checksum scheme will eliminate this minor bug too.
X
X[ "# job done" eliminated.  Jobs are now marked with a magic number ]
X
X	Currently there is no use of register variables in the code.  By
X	sprinkling the code with register declarations, I could probably
X	increase the efficiency some.  Although, IMHO, the code is quite
X	good as is.
X
X[ sprinkled code with register variables ]
X
X	The code could be better organized on a source level.  The reading
X	and writing of the environment portion of a job file is currently
X	spread out in whatever source code that needs it.  It should be
X	consolodated into one source file.
X
X[ I tried to clean up the source ]
X
X	Fix execlp in crontab.c (no 0 ending arg list).
X
X[  fixed ]
X
X	Rdfifo and wrfifo might as well use jjj directly instead of
X	passing &jjj to these routines.
X
X[ yup ]
X
X	Add "all" to -l option for symmetry with -r option
X
X[ yup ]
X
X	A better alternative to CATCHUP for cron{tab|job} jobs might be to
X	just have a -x option which causes said job(s) to run immediately.
X	The sysadmin could simply '-x all' the jobs that were scheduled to
X	run on a night when the system was down, could even be put in /etc/rc.
X
X[ "-x" option added, CATCHUP still used too ]
X
XAug 8, 1990  The above work on the cron programs has been delayed for most
Xof the summer.  The reason is that there is a nasty little bug in the at.c
Xprogram that causes the captured current working directory to be wrong.  I
Xjust couldn't get a handle on it.  The c.w.d. is correct 99% of the time,
Xit's that 1% that had me baffled.  Finally, someone on Usenet had a simalar
Xproblem.  The problem is that the /bin/pwd command does not necessarily
Xoutput the c.w.d. in one shot.  The reader on the end of the pipe must keep
Xreading until it sees a '\n' to be sure that it got it all.  I am grateful
Xto all "net-people" that helped whoever it was that had the problem, and
Xunknowingly help me with my problem too.
X
X[ fixed my getwd() function to keep reading until it got the whole line ]
SHAR_EOF
$TOUCH -am 1016164590 Design_Notes &&
chmod 0644 Design_Notes ||
echo "restore of Design_Notes failed"
set `wc -c Design_Notes`;Wc_c=$1
if test "$Wc_c" != "19046"; then
	echo original size 19046, current size $Wc_c
fi
fi
# ============= Examples ==============
if test X"$1" != X"-c" -a -f 'Examples'; then
	echo "File already exists: skipping 'Examples'"
else
echo "x - extracting Examples (Text)"
sed 's/^X//' << 'SHAR_EOF' > Examples &&
XThe following are examples of cronjobs and crontabs that may assist
Xyou in setting up my cron facility on your machine.  The time
Xschedules and the names of the various scripts shown are typical of
Xa Unix-PC machine.  Of course, the exact time schedules and the names
Xof the various scripts shown here would be tailored to your specific
Xsystem.
X
X***************
X
XThe UUCP system has a number administrative tasks that are usually
Xexecuted by cron at various times.  These tasks are usually performed
Xby running the /usr/lib/uucp/uudemon.{hr|day|wk} scripts at the
Xappropriate times.
X
XIn order to setup these tasks in the cron facility, it is necessary
Xto login as the UUCP administrator.  This is accomplished on the
XUnix-PC by logging in as "root" and doing an "su - uucpadm".
X
XThe hourly uudemon script is a good canidate for execution as a
Xcronjob rather than crontab.  CR_JOBs run a little more efficiently
Xthan CR_TAB jobs.  This makes it a little less bothersome to your
Xusers when it kicks in every hour and sucks away cpu cycles.
X
XEdit the uudemon.hr script to attach " >/dev/null 2>&1" to the
Xcommands in the script which might produce an output that you don't
Xcare about.  If you don't, any output will be mailed to "uucpadm",
Xproducing a lot of junk mail.  Then type in the following command:
X
X	cronjob -n19 '40 * * * *' < /usr/lib/uucp/uudemon.hr
X
XThis will make the CR_JOB a copy of the hourly script and set it up
Xto run at 40 minutes after every hour and will run it with a heavy
Xnice value.  Note that the CR_JOB is only a copy of the script.  If
Xyou edit the uudemon.hr script at a later time, you will have to
Xremove the original CR_JOB (cronjob -r _jobnumber_) and make a new
XCR_JOB by typing in the above command again.
X
XThe daily and weekly uudemon scripts are setup via crontab.  These
Xscripts usually run late at night, so the efficiency considerations
Xaren't as important.  Type in the following command:
X
X	crontab <<'EOF'
X	# ---------- crontab for uucp ----------
X
X	#-n19
X	# uudemon.hr executed by cronjob for better efficiency
X	# 40  *  *  *  *	/usr/lib/uucp/uudemon.hr >/dev/null
X	00  4  *  *  *		/usr/lib/uucp/uudemon.day >/dev/null
X	30  5  *  *  Sun	/usr/lib/uucp/uudemon.wk >/dev/null
X	EOF
X
XThe cron facility will save this crontab for you and create two CR_TAB
Xjobs to run the uudemon.day and uudemon.wk scripts.  Note that it is
Xthe uudemon.{day|wk} scripts themselves that are run at the scheduled
Xtimes.  If you edit these scripts there is no need to change the crontab.
X
X***************
X
XGeneral system maintainence is usually performed on a regular basis
Xby the superuser.  Login as "root" and enter the following command:
X
X	crontab <<'EOF'
X	# ---------- crontab for root ----------
X
X	#-N20
X	15  3  *  *  Sun	/etc/clockupd.wk >/dev/null
X	#-n0
X	15  5  *  *  Mon	/etc/cleanup.wk >/dev/null
X	EOF
X
XThe cron facility will save this crontab for you and create two CR_TAB
Xjobs to run the clockupd.wk and cleanup.wk scripts.  Note the use of
Xthe nice switches to run the clockupd.wk script at high priority and
Xthe cleanup.wk script at normal priority.
X
X***************
X
XThe cron facility itself keeps a log of all its activities.  This log
Xshould be cleaned up periodicly so it doesn't grow too big.  Login as
X"root" and do a "su - cron", then type in the following:
X
X	cronjob '0 0 * * Sun' <<'EOF'
X	cp /usr/lib/cron/log /usr/lib/cron/log-old
X	rm -f /usr/lib/cron/log-old.z
X	pack /usr/lib/cron/log-old >/dev/null 2>&1
X	cronjob -c
X	EOF
X
XThis will create a CR_JOB which, at midnight every Sunday, saves and
Xsqueezes last week's log and starts a fresh log for the coming week.
X
X***************
X
XThe Unix-PC has a program called "smgr" which uses the file
X/usr/lib/crontab to provide (limitted) cron facilities (no user
Xcrontabs, no at(1) program, ...).  This older form of cron, which
Xuses the file /usr/lib/crontab, is, I believe, like the system-III
Xcron.  Other machines may also have a similar vendor-supplied cron.
XThere is no reason why you can't run both the old cron and my cron
Xtoo.  On the Unix-PC, if you would like to continue to run the smgr
Xprogram in order to partake of its other functions (eg. date display
Xat the top of the screen), but would like to let my program handle
Xall the cron duties; just comment (#) all the lines in the
X/usr/lib/crontab file and create equivelent crontabs (cronjobs) with
Xmy facility.
SHAR_EOF
$TOUCH -am 1022100690 Examples &&
chmod 0644 Examples ||
echo "restore of Examples failed"
set `wc -c Examples`;Wc_c=$1
if test "$Wc_c" != "4358"; then
	echo original size 4358, current size $Wc_c
fi
fi
# ============= Files ==============
if test X"$1" != X"-c" -a -f 'Files'; then
	echo "File already exists: skipping 'Files'"
else
echo "x - extracting Files (Text)"
sed 's/^X//' << 'SHAR_EOF' > Files &&
XSize
XDesign_Notes
XExamples
XFiles
XInstall
XName
XREADME
XRemove
Xat.1
Xat.c
Xconvertjob.c
Xcron.1
Xcron.h
Xcrontab.1
Xcrontab.c
Xdaemon.c
Xdir.c
Xfifo.c
Xgetwd.c
Xjob.c
Xjob.h
Xlog.c
Xlogin.c
Xmakefile
Xmemlist.c
Xmkfifo.c
Xparsesched.c
Xparsetime.c
Xresched.c
Xversion
SHAR_EOF
$TOUCH -am 1022100790 Files &&
chmod 0644 Files ||
echo "restore of Files failed"
set `wc -c Files`;Wc_c=$1
if test "$Wc_c" != "244"; then
	echo original size 244, current size $Wc_c
fi
fi
# ============= Install ==============
if test X"$1" != X"-c" -a -f 'Install'; then
	echo "File already exists: skipping 'Install'"
else
echo "x - extracting Install (Text)"
sed 's/^X//' << 'SHAR_EOF' > Install &&
X#   Install for the cron(1M) facility  4/89,10/90 D.Lashomb
X
XLOCAL=/usr/local
XBIN=/usr/local/bin
XLIB=/usr/lib/cron
XSPOOL=/usr/spool/cron
XDAEMONS=/etc/daemons
XSRC=$HOME/Filecabinet/SRC
XmySRC=$SRC/Cron
XDOCS=$HOME/Filecabinet/DOCS
XmyDOCS=$DOCS/Cron
X
Xif grep '^cron:' /etc/passwd
Xthen
X
X########################################
X# upgrading old version to new version
X########################################
X
X	cat <<END
X
XInstall for cron(1M) facility:
X
XYou already have a login named "cron" in
Xyour passwd file.  I assume that this is
Xa re-install of my cron facility for the
Xpurpose of upgrading to the new version.
X
XEND
X	echo "shall I continue y/[n]? \c"
X	case "`line`" in
X	[Yy]*)	:
X		;;
X	*)	exit 2
X		;;
X	esac
X
X	echo "killing the cron daemon"
X	I=`ps -e | grep 'cron' | cut -c1-6`
X	for i in $I
X	do
X		kill -9 $i
X	done
X
X	echo "compiling the programs"
X	make all
X	make clean
X
X	echo "copying new programs over the old ones"
X	cp cron $DAEMONS/cron
X	cp at $BIN/at
X	cp crontab $BIN/crontab
X	rm cron at batch cronjob crtabj crontab
X
X	# convert old jobs to new style
X	ls $SPOOL/atjobs/* >/dev/null 2>&1 && {
X		cat <<END
X
XYou have old jobs in the spool directory.
XThe new cron daemon is not compatable with
Xold style jobs.
X
XEND
X		echo "convert old jobs to new style [y]/n? \c"
X		case "`line`" in
X		[Nn]*)	:
X			;;
X		*)	for i in `ls $SPOOL/atjobs`
X			do
X				./convertjob $SPOOL/atjobs/$i >temp
X				cp temp $SPOOL/atjobs/$i
X			done
X			rm temp convertjob
X			;;
X		esac
X		}
X
X	cat <<END
X
XYou may (or may not) have the source code and
Xdocumentation for the old version saved in
X$mySRC and $myDOCS
XI'll remove the old stuff and put the new
Xsource code and docs there if you want.
X
XEND
X	echo "remove old and save new [y]/n? \c"
X	case "`line`" in
X	[Nn]*)	:
X		;;
X	*)	rm -f $mySRC/*
X		rm -f $myDOCS/*
X		for i in $SRC $mySRC $DOCS $myDOCS
X		do
X			[ -d $i ] || {
X				mkdir $i
X				chown $LOGNAME $i
X				chgrp users $i
X				chmod 0755 $i
X				}
X		done
X
X		echo "putting source code in $mySRC"
X		ln *.h *.c makefile version $mySRC
X		echo "putting manual pages in $myDOCS"
X		ln *.1 README Design_Notes Examples $myDOCS
X		chown $LOGNAME *.h *.c makefile version *.1 README Design_Notes Examples
X		chgrp users *.h *.c makefile version *.1 README Design_Notes Examples
X		chmod 0644 *.h *.c makefile version *.1 README Design_Notes Examples
X		;;
X	esac
X
Xelse
X
X########################################
X# installing cron for the first time
X########################################
X
X	cat <<END
XInstall for cron(1M) facility:
X
XI will add a login named "cron" to your
Xpasswd file!  If you don't want me messing
Xwith your password file, stop me now.
X
XEND
X	echo "shall I continue y/[n]? \c"
X	case "`line`" in
X	[Yy]*)	:
X		;;
X	*)	exit 2
X		;;
X	esac
X
X	# find an unused uid
X	for uid in 50 51 52 53 54 55 56 57 58 59 60 bad
X	do
X		cut -d: -f3 /etc/passwd | grep $uid >/dev/null || break
X	done
X	[ "$uid" = "bad" ] && {
X		echo "can't find unused uid"
X		exit 1
X		}
X
X	# find gid for "other"
X	gid=`grep "other" /etc/group | cut -d: -f3`
X	[ "$gid" = "" ] && {
X		echo "can't find group 'other'"
X		exit 1
X		}
X
X	echo "cron:NONE:$uid:$gid:cron facility:/usr/lib/cron:" >>/etc/passwd
X
X	echo "compiling the programs"
X	make all
X	make clean
X
X	echo "making directories"
X	[ -d $LOCAL ] || {
X		mkdir $LOCAL
X		chown root $LOCAL
X		chgrp users $LOCAL
X		chmod 0755 $LOCAL
X		}
X	[ -d $BIN ] || {
X		mkdir $BIN
X		chown bin $BIN
X		chgrp bin $BIN
X		chmod 0755 $BIN
X		}
X	[ -d $LIB ] || {
X		mkdir $LIB
X		chown cron $LIB
X		chgrp bin $LIB
X		chmod 0700 $LIB
X		}
X	[ -d $SPOOL ] || {
X		mkdir $SPOOL
X		chown cron $SPOOL
X		chgrp bin $SPOOL
X		chmod 0700 $SPOOL
X		}
X	[ -d $SPOOL/atjobs ] || {
X		mkdir $SPOOL/atjobs
X		chown cron $SPOOL/atjobs
X		chgrp bin $SPOOL/atjobs
X		chmod 0700 $SPOOL/atjobs
X		}
X	[ -d $SPOOL/crontabs ] || {
X		mkdir $SPOOL/crontabs
X		chown cron $SPOOL/crontabs
X		chgrp bin $SPOOL/crontabs
X		chmod 0700 $SPOOL/crontabs
X		}
X	[ -d $DAEMONS ] || {
X		mkdir $DAEMONS
X		chown root $DAEMONS
X		chgrp sys $DAEMONS
X		chmod 0750 $DAEMONS
X		}
X	for i in $SRC $mySRC $DOCS $myDOCS
X	do
X		[ -d $i ] || {
X			mkdir $i
X			chown $LOGNAME $i
X			chgrp users $i
X			chmod 0755 $i
X			}
X	done
X
X	echo "moving the programs to their proper places"
X	mv cron $DAEMONS/cron
X	chown root $DAEMONS/cron
X	chgrp bin $DAEMONS/cron
X	chmod 0700 $DAEMONS/cron
X
X	mv at $BIN/at
X	mv batch $BIN/batch		# a link to at
X	mv cronjob $BIN/cronjob		# a link to at
X	mv crtabj $BIN/crtabj		# a link to at
X	chown cron $BIN/at
X	chgrp bin $BIN/at
X	chmod 4711 $BIN/at
X
X	mv crontab $BIN/crontab
X	chown root $BIN/crontab
X	chgrp bin $BIN/crontab
X	chmod 4711 $BIN/crontab
X
X	echo "Ok, let's setup the allow and deny files."
X	rm -f $LIB/at.allow
X	rm -f $LIB/at.deny
X	rm -f $LIB/cron.allow
X	rm -f $LIB/cron.deny
X
X	cat <<END
X
XYou can choose to let at(1) and batch(1) be used by
Xall, some or none of the users in the following ways:
X
X  1 - allow everybody to use at(1) and batch(1)
X  2 - allow only the users you name
X  3 - allow everybody EXCEPT the users you name
X  4 - let only the superuser use it
X
XEND
X	echo "pick a number-> \c"
X	case "`line`" in
X	"1")
X		> $LIB/at.deny
X		chown cron $LIB/at.deny
X		chgrp other $LIB/at.deny
X		chmod 0600 $LIB/at.deny
X		;;
X	"2")
X		echo "enter blank name to end list"
X		while :
X		do
X			echo "enter allowed user's name-> \c"
X			read name
X			[ "$name" ] || break
X			echo $name >>$LIB/at.allow
X		done
X		chown cron $LIB/at.allow
X		chgrp other $LIB/at.allow
X		chmod 0600 $LIB/at.allow
X		;;
X	"3")
X		echo "enter blank name to end list"
X		while :
X		do
X			echo "enter denied user's name-> \c"
X			read name
X			[ "$name" ] || break
X			echo $name >>$LIB/at.deny
X		done
X		chown cron $LIB/at.deny
X		chgrp other $LIB/at.deny
X		chmod 0600 $LIB/at.deny
X		;;
X	*)
X		echo "ok, only the superuser can use it"
X		;;
X	esac
X
X	cat <<END
X
XYou can choose to let cronjob(1), crtabj(1) and
Xcrontab(1) be used by all, some or none of the users
Xin the following ways:
X
X  1 - allow everybody to use them
X  2 - allow only the users you name
X  3 - allow everybody EXCEPT the users you name
X  4 - let only the superuser use it
X
XEND
X	echo "pick a number-> \c"
X	case "`line`" in
X	"1")
X		> $LIB/cron.deny
X		chown cron $LIB/cron.deny
X		chgrp other $LIB/cron.deny
X		chmod 0600 $LIB/cron.deny
X		;;
X	"2")
X		echo "enter blank name to end list"
X		while :
X		do
X			echo "enter allowed user's name-> \c"
X			read name
X			[ "$name" ] || break
X			echo $name >>$LIB/cron.allow
X		done
X		chown cron $LIB/cron.allow
X		chgrp other $LIB/cron.allow
X		chmod 0600 $LIB/cron.allow
X		;;
X	"3")
X		echo "enter blank name to end list"
X		while :
X		do
X			echo "enter denied user's name-> \c"
X			read name
X			[ "$name" ] || break
X			echo $name >>$LIB/cron.deny
X		done
X		chown cron $LIB/cron.deny
X		chgrp other $LIB/cron.deny
X		chmod 0600 $LIB/cron.deny
X		;;
X	*)
X		echo "ok, only the superuser can use it"
X		;;
X	esac
X
X	echo "putting source code in $mySRC"
X	ln *.h *.c makefile version $mySRC
X	echo "putting manual pages in $myDOCS"
X	ln *.1 README Design_Notes Examples $myDOCS
X	chown $LOGNAME *.h *.c makefile version *.1 README Design_Notes Examples
X	chgrp users *.h *.c makefile version *.1 README Design_Notes Examples
X	chmod 0644 *.h *.c makefile version *.1 README Design_Notes Examples
X
Xfi
X
Xcat <<END
X
XThe installation of the cron(1M) facility is now
Xcomplete.  However you will have to reboot the
Xsystem in order to fire-up the daemon.
XEND
Xsleep 5
Xexit 0
SHAR_EOF
$TOUCH -am 1022115090 Install &&
chmod 0744 Install ||
echo "restore of Install failed"
set `wc -c Install`;Wc_c=$1
if test "$Wc_c" != "7286"; then
	echo original size 7286, current size $Wc_c
fi
fi
# ============= Name ==============
if test X"$1" != X"-c" -a -f 'Name'; then
	echo "File already exists: skipping 'Name'"
else
echo "x - extracting Name (Text)"
sed 's/^X//' << 'SHAR_EOF' > Name &&
XImproved cron(1M) facility by D.Lashomb
SHAR_EOF
$TOUCH -am 0728232689 Name &&
chmod 0644 Name ||
echo "restore of Name failed"
set `wc -c Name`;Wc_c=$1
if test "$Wc_c" != "40"; then
	echo original size 40, current size $Wc_c
fi
fi
# ============= README ==============
if test X"$1" != X"-c" -a -f 'README'; then
	echo "File already exists: skipping 'README'"
else
echo "x - extracting README (Text)"
sed 's/^X//' << 'SHAR_EOF' > README &&
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
XCron(1M) is a facility that allows you to schedule commands that will be
Xexecuted at a later time.  At(1), batch(1) and crontab(1) are available
Xon many unix systems, but not the AT&T UNIX-pc; so I wrote my own.
XBatch(1) is just a special form of at(1) which executes commands "now"
Xat low priority.  Crontab(1) lets users have their own crontab files.  A
Xnew cronjob(1) command is included which allows you to repeatedly execute
Xjobs according to a schedule.  Also included is a new crtabj(1) command
Xwhich actually underlies the crontab(1) command, but has some usefulness
Xto the user.
X
XI made some improvements over the standard sysV facility (as I understand
Xit).  The manual pages explain this stuff better than I can do here, but
Xhere are some highlights:
X
X	*  the ability to specify a nice value, even neg for root
X
X	*  great variety of listing options
X
X	*  accepts names like Sun Mon etc in times, dates, schedules
X
X	*  security strictly enforced by real uid
X
X	*  new cronjob command runs like at(1), but repetitively like
X	   crontab.  I use this instead of crontab, it's more efficient.
X
X	*  crtabj command is what really does crontab
X
X	*  everything (almost) compatable with standard sysV versions
X
X                          ***************
X
XThe facility is implemented in two parts: the user commands and the
Xdaemon.  These programs communicate through FIFOs.  Heres how it works:
X
XThe daemon is started at boot-time and runs in the background.  The 1st
X	thing it does is check the spool dir to see if there are any
X	jobs already there, and builds its in-mem job list.  The jobnumber
X	is the same as the filename for all jobs.  The daemon then writes
X	any free jobnumbers into the JNUMBS fifo, and enters its main loop
X	of sleeping for awhile, waking up and checking the FNAMES fifo.
X
XThe user calls the at(1) command, specifying the time when the job should
X	be run on the command line.  At(1) obtains a jobnumber from the
X	JNUMBS fifo and creates a file of that name in the spool dir.  It
X	writes some special information, eg. user's current environment,
X	into the job file and starts to read its stdin.  Everything that
X	the user types upto a cntl-D will be written into the job file.
X	When the user finishes, at(1) marks the end of the job, closes
X	the file and writes its name (=jobnumber) into the FNAMES fifo.
X
XThe next time the daemon wakes up, it will find the filename in the FNAMES
X	fifo and add it to its in-mem job list.  It then checks that list
X	to see if any jobs are scheduled to run at that time.  If it is
X	time to run a job, the daemon forks off a process that reads the
X	job file and recreates the environment that existed at the time
X	the job was written by the user, and runs the job.  The daemon 
X	removes the job file and makes the jobnumber available again by
X	writting it into the JNUMBS fifo.
X
XI tried to make things upwardly compatable with the sysV version, at least
Xfrom the user's point of view.  The implementation is obviously different:
X
XsysV:				| my version:
X--------------------------------|-----------------------------------------
Xone FIFO and signals for commu- | two FIFOs  JNUMBS daemon-->user
Xnication between daemon & user  |            FNAMES user-->daemon
X				|
Xatjobs filenames are an encoded | atjobs filenames are the same as the
Xform of execution time          | jobnumber.  other jobs here too.
X				|
Xcrontabs filenames are users'   | crontabs filenames are users' uid
Xlogin name			| number
X				|
Xsecurity based on login name	| security based on uid and login name
X				|
Xmultiple crontabs per uid if	| only one crontab per uid
Xlogin names different?		|
X				|
Xfile /usr/lib/cron/queuedefs    | user can specify nice value each job
Xtells daemon nice value and how | MAXKIDS daemon parameter sets limit
Xmany jobs to run at one time	| on how many jobs to run at one time
X				|
Xcrontabs run with limited env	| crontabs restore full user environment
X				|
Xcrontabs implemented ????	| crontab creates a separate job for
X				| every line in every crontab file
X
X                          ***************
X
XLike sysV, if you want to change your crontab you have to do something
Xlike this:
X		crontab -l > temp	# get a copy of my current crontab
X		crontab -r		# remove it from cron's inventory
X		vi temp			# make the changes
X		crontab < temp		# re-submit it to cron
X
X                          ***************
X
XCRONJOB vs CRONTAB
X------------------
XCronjob is a new command that can do many of the same things that crontab
Xis used for.  Like crontab, its jobs run repetitively according to a user
Xsupplied schedule.  Where crontab limits you to a single command per time
Xschedule (I'm talking about a single line in a crontab file here), cronjob
Xallows you to do a whole bunch of commands per scheduled execution.  What
Xusually happens with crontab is that the command part of a crontab line
Xactually calls a shell script.  With cronjob the job *is* the shell script.
XI/O overhead is lower because it's one less file that has to be openned.
XCPU load is lower because one less process that needs to be spawned.  There
Xare disadvantages, of course.  For one thing, with crontab everything is
Xin one place, easy to find.  With cronjob you've got to search every job
Xto find the one you are looking for.  Another thing is that with crontab
Xyou can do one >/dev/null, with cronjob you've got to do this for every
Xcommand in the "script".
X
X                          ***************
X
XINSTALLING THE CRON(1M) FACILITY:
X
X	(1) add a user called "cron" to the system
X	(2) edit cron.h to change parameters (optional)
X	(3) compile the programs
X	(4) create the proper directories and files
X	(5) move the programs where they belong
X	(6) create and edit the allow and deny files
X	(7) reboot to get the daemon running
X
XNote: an Install script for the UNIX-PC is included with this package.
XI don't know what method you use for installing software on your system.
XDo you use the user agent?  If so, cpio all the files together, add an
X+IN suffix and click on it with the mouse.  From the shell, all you'd
Xhave to do is su to root and run the Install script.  Of course, you can
Xalways do the installation "by hand" if you want.
X
XWARNING - the Install script modifies your /etc/passwd file!
X	  this facility uses a login name 'cron' for its operations.
X
X
X(1) Edit your /etc/passwd file to add a user named "cron".  Login "cron"
X    should have an impossible password like "VOID", its home directory
X    should be /usr/lib/cron and its shell should be /bin/sh.  Its group
X    id should be the same as other daemons like "lp".  On my system this
X    is the gid for "other".
X
X(2) Some of the compile-time #defines in cron.h that you may want to
X    change are:
X
X	NUMBJOBS - This determines the maximum number of jobs that the
X	daemon can handle.  Each job takes up 12 bytes in the FIFOs.  In
X	no case should you specify more jobs than the FIFOs can hold.  I
X	set it 400, which seems reasonable to me.
X
X	GRAINULARITY - is the number of seconds that the daemon sleeps
X	each wake/sleep cycle.  The smaller the number the quicker the
X	daemon will see a newly enterred job, but also the more overhead
X	the facility places on the system.  It is set to 60 and probably
X	should stay that way.
X
X	CATCHUP - This is the number of seconds behind "now" that a job
X	can be and still get executed.  AT_JOBs older than this will be
X	automatically removed by the daemon.  The at(1) command will not
X	allow you to enter jobs older than this either.  This parameter
X	has a few twists to it.  When you are enterring a job using the
X	at(1) command, "now" is measured from when you first called at(1).
X	So if it takes you a long time to type in a job, it's possible to
X	have the job refused because it is past CATCHUP (GRAINULARITY is
X	is also taken into account).  Now the daemon is another story.
X	It is possible to get the daemon to execute jobs older than the
X	compile-time CATCHUP parameter.  When the daemon is started, it
X	checks its environment for a shell variable of the same name, and
X	will override the compile-time parameter if there is one.  Also
X	the daemon can be called with a numeric argument on its command
X	line which will override both the compile-time and environment
X	settings.  Here's the reason for this scheme:  Recall that the
X	daemon checks for old jobs in the spool directory when it starts
X	up.  You may want jobs up to, say, 24 hours old executed when it
X	first starts up, but only allow "future" jobs to be enterred with
X	the at(1) command.  So you compile with CATCHUP #defined as 0,
X	but start the daemon with CATCHUP=86400 in its environment.
X
X	MAXKIDS - The daemon will only run this number of jobs on each
X	cycle.  If there are more jobs than this ready, they are pushed
X	ahead to the next cycle.  I #defined this to 10 for compiling.
X	Of course the more jobs the daemon runs at one time the more load
X	it puts on the system.  If you set this parameter too high, the
X	kernal will impose a limit, because it won't let the daemon have
X	any more processes.  Like CATCHUP, the compiled-in value of this
X	parameter can be overridden by using an environment variable or
X	command line argument when starting up the daemon.
X
X	MAXTRIES - Even if the daemon is successful in forking a process
X	to run a job, the system may fail to execute it because the owner
X	of the job is at his/her process limit.  The process will sleep
X	for 10 seconds and try again up to MAXTRIES number of times.  I
X	#defined this as 5 tries, seemed reasonable to me.  The 10 second
X	value is buried in the daemon's code, but it's not hard to find.
X
X	ZEROSECS - If this parameter is #defined, the schedule times you
X	enter with the at(1) command will have their seconds field set
X	to :00.  If it's not #defined, the seconds will be whatever the
X	current clock time's seconds are.  Having seconds = :00 seems
X	more intuitive.  Either way "at now" always uses the current 
X	clock time's seconds (otherwise it would be non-deterministic).
X
X	TZOVRIDE - If this parameter is #defined, the daemon's value for
X	the TZ environment variable will replace the user's value when
X	the job is run.  The reason for this is obscure, but here goes:
X	The environment variable TZ is used to determine whether daylight
X	savings time is in effect at a particular time.  The date of when
X	d.s.t. goes in effect used to be the last Sunday in April, and
X	this is the date compiled into the kernal and standard library.
X	The U.S. Congress has changed the date to the *first* Sunday in
X	April.  If you reset the system clock to compensate, the seconds
X	since Jan 1, 1970 GMT values of all your files and schedules will
X	be wrong.  What you could do is change the timezone instead.  But
X	the value of TZ in a user's job reflects what it was when the job
X	was created, not what it is when it gets executed.  By using
X	TZOVRIDE the TZ variable will get set to the value the daemon gets
X	when it is booted.  This will make things make things work right.
X	The best thing is to get Lenny Tropiano's daylight savings time
X	fix and install that on your system.
X
X	SET_LOGNAME - Normally the at(1) ... crontab(1) commands demand
X	that the LOGNAME environment variable be set correctly.  If this
X	parameter is #defined, the superuser (ie uid=0) gets "LOGNAME=root"
X	put in its environment whenever the usual LOGNAME check fails.
X	This allows you to "su" and use the commands.  Otherwise, you'd
X	have to login as root, use "su -", or setup LOGNAME by hand.
X
X(3) Compile the programs.  Just say "make" or "make all".  I've included
X    a simple makefile with these programs.  I usually compile with the
X    optimizer on, link with the shared library and strip the object files.
X
X(4) Create the following directories on your system if they don't already
X    exist.  Be sure to get owners, groups and permissions right!
X
X    /				root	root	drwxr-xr-x	exists
X    /usr			root	users	drwxr-xr-x	exists
X    /usr/lib			root	bin	drwxr-xr-x	exists
X    /usr/lib/cron		cron	bin	drwx------
X    /usr/spool			root	bin	drwxr-xr-x	exists
X    /usr/spool/cron		cron	bin	drwx------
X    /usr/spool/cron/atjobs	cron	bin	drwx------
X    /usr/spool/cron/crontabs	cron	bin	drwx------
X    /etc			root	sys	drwxr-xr-x	exists
X    /etc/daemons		root	sys	drwxr-x---	exists
X    /usr/local			root	users	drwxr-xr-x
X    /usr/local/bin		bin	bin	drwxr-xr-x
X
X(5) Now move the compiled programs into their proper directories.  Link
X    together at, batch, cronjob and crtabj: they're all the same program.
X    Again, make sure you get the owners, groups and permissions right!
X 
X    /etc/daemons/cron		root	bin	-rwx------
X    /usr/local/bin/at		cron	bin	-rws--x--x	\
X    /usr/local/bin/batch	cron	bin	-rws--x--x	 \ link
X    /usr/local/bin/cronjob	cron	bin	-rws--x--x	 /
X    /usr/local/bin/crtabj	cron	bin	-rws--x--x	/
X    /usr/local/bin/crontab	root	bin	-rws--x--x
X
X    Note: neihter the daemon nor the user commands are tied to any
X    particular directory.  You can put them anywhere you like.  Eg. you
X    could put at and batch in /usr/bin or /usr/lbin if that is the custom
X    on your machine.  I put the daemon in /etc/daemons because anything
X    in /etc/daemons is automatically executed on bootup in the UNIX-PC.
X
X(6) Create and edit the allow and deny files.  Again make sure you get
X    the owner, group and permissions right!
X
X    /usr/lib/cron/at.allow	cron	other	-rw-------
X    /usr/lib/cron/at.deny	cron	other	-rw-------
X    /usr/lib/cron/cron.allow	cron	other	-rw-------
X    /usr/lib/cron/cron.deny	cron	other	-rw-------
X
X	IF XX.allow exists, only those users whose names
X		are in it will be able to use this facilty.
X	ELSE IF XX.deny exists, users whose names are in
X		it will not be allowed to use this facility.
X	ELSE only the superuser will be allowed to use it.
X
X    These files, if used, are one name per line.  If you want to let
X    everybody use the at(1) and batch(1), create an empty at.deny file.
X    Similarly, for the cron.allow and cron.deny files.  An interesting
X    note here is that the code is setup to allow two users to clear the
X    log file: root and cron.  However if you refuse permission to cron
X    with the allowfile-denyfile mechanism, then only root will be able
X    to clean the log file.
X
X(7) Reboot to get the daemon running.  You may want to check the file
X    /etc/rc.  On the UNIX-PC, any programs in /etc/daemons are fired-up
X    automatically from /etc/rc at bootup-time.  If your system uses a
X    different scheme, you may have to add a line such as the following:
X
X		/etc/daemons/cron
X
X    to your /etc/rc file.  You might also want to make use of the CATCHUP
X    feature of the daemon by setting the environment variable or calling
X    the daemon with and argument, like this:
X
X		/etc/daemon/cron 86400	# catchup 24 hours
X
X
X                          ***************
X
X----------------------------------------------------------------
X	Caveats for the Cron Facility - D.Lashomb
X----------------------------------------------------------------
X
XThis cron facility was written for the AT&T Unix-PC model 7300.  I believe
Xit should port well to other sysV machines.  How well it will port to BSD
Xmachines, I don't know.
X
XUsers' login shells:  This facility will work with the Bourne shell (/bin/sh)
Xand the Korn shell (/bin/ksh).  I do not know if it will work with csh.  It
Xwill NOT work with login shells that are scripts - the login shell MUST be
Xa binary executable.  It will NOT work unless the shell can accept the
Xoptions "-c" and "-s" the way /bin/sh does.
X
XWhen the system is booted and the cron daemon starts-up, it checks the jobs
Xin its spool directory.  All jobs are checked for consistency and added to
Xthe daemon's inventory.  CR_TAB jobs are not checked to see if they belong
Xany particular crontab file.  The daemon does not re-parse the crontab files.
XThis *feature* has an advantage in that it allows the user to enter CR_TAB
Xjobs directly with the crtabj(1) command and have them survive a reboot.
XIt has the disadvantage in that spooled CR_TAB jobs have no hard relationship
Xwith the crontab files.
X
XStdout and stderr from users' jobs are mailled to the user using /bin/mail,
Xtherefore /bin/mail must exist on the system.
X
X----------------------------------------------------------------
XNone of the above caveats is insurmountable.  Perhaps some ambitious fellow
Xwill: pepper the code with #ifdef's to make more portable :-)
X
X                          ***************
X
SHAR_EOF
$TOUCH -am 1016164390 README &&
chmod 0644 README ||
echo "restore of README failed"
set `wc -c README`;Wc_c=$1
if test "$Wc_c" != "17482"; then
	echo original size 17482, current size $Wc_c
fi
fi
# ============= Remove ==============
if test X"$1" != X"-c" -a -f 'Remove'; then
	echo "File already exists: skipping 'Remove'"
else
echo "x - extracting Remove (Text)"
sed 's/^X//' << 'SHAR_EOF' > Remove &&
X#    Remove for cron(1M) facility  4/89 D.Lashomb
X
XLOCAL=/usr/local
XBIN=/usr/local/bin
XLIB=/usr/lib/cron
XSPOOL=/usr/spool/cron
XDAEMONS=/etc/daemons
XSRC=$HOME/Filecabinet/SRC
XmySRC=$SRC/Cron
XDOCS=$HOME/Filecabinet/DOCS
XmyDOCS=$DOCS/Cron
X
Xecho "killing the cron daemon"
XI=`ps -e | grep 'cron' | cut -c1-6`
Xfor i in $I
Xdo
X	kill -9 $i
Xdone
Xecho "shall I remove the 'cron' login"
Xecho "from your password file y/[n]? \c"
Xcase "`line`" in
X[Yy]*)	mytmp=/tmp/$$
X	grep -v '^cron:' /etc/passwd >$mytmp
X	cp $mytmp /etc/passwd
X	rm $mytmp
X	;;
Xesac
Xecho "removing my programs"
Xrm $DAEMONS/cron $BIN/at $BIN/batch $BIN/cronjob $BIN/crtabj $BIN/crontab
Xecho "removing my directories"
Xrm -r $LIB $SPOOL
Xecho "shall I remove $mySRC y/[n]? \c"
Xcase "`line`" in
X[Yy]*) rm -r $mySRC ;;
Xesac
Xecho "shall I remove $myDOCS y/[n]? \c"
Xcase "`line`" in
X[Yy]*) rm -r $myDOCS ;;
Xesac
Xecho "Remove done"
Xexit 0
SHAR_EOF
$TOUCH -am 0728232689 Remove &&
chmod 0744 Remove ||
echo "restore of Remove failed"
set `wc -c Remove`;Wc_c=$1
if test "$Wc_c" != "882"; then
	echo original size 882, current size $Wc_c
fi
fi
# ============= Size ==============
if test X"$1" != X"-c" -a -f 'Size'; then
	echo "File already exists: skipping 'Size'"
else
echo "x - extracting Size (Text)"
sed 's/^X//' << 'SHAR_EOF' > Size &&
X500
SHAR_EOF
$TOUCH -am 0728232689 Size &&
chmod 0644 Size ||
echo "restore of Size failed"
set `wc -c Size`;Wc_c=$1
if test "$Wc_c" != "4"; then
	echo original size 4, current size $Wc_c
fi
fi
echo "End of part 1, continue with part 2"
exit 0

donlash@uncle.uucp (Donald Lashomb) (01/14/91)

---- Cut Here and unpack ----
#!/bin/sh
# This is part 02 of a multipart archive
if touch 2>&1 | fgrep '[-amc]' > /dev/null
 then TOUCH=touch
 else TOUCH=true
fi
# ============= at.1 ==============
if test X"$1" != X"-c" -a -f 'at.1'; then
	echo "File already exists: skipping 'at.1'"
else
echo "x - extracting at.1 (Text)"
sed 's/^X//' << 'SHAR_EOF' > at.1 &&
X.TH AT 1 LOCAL
X.SH NAME
Xat, batch, cronjob, crtabj \- run commands at a later time
X.SH SYNOPSIS
X.B at
X[-{n|N} nice] time [{next|this}] [date] [+ increment]
X.br
X.B batch
X[-{n|N} nice]
X.br
X.B cronjob
X[-{n|N} nice] mins hrs mdays mons wdays
X.br
X.B crtabj
X[-{n|N} nice] mins hrs mdays mons wdays command
X.br
X{\fBat\fR|\fBbatch\fR|\fBcronjob\fR|\fBcrtabj\fR}
X-r[ae] {all|jobn ...}
X.br
X{\fBat\fR|\fBbatch\fR|\fBcronjob\fR|\fBcrtabj\fR}
X-x[ae] {all|jobn ...}
X.br
X{\fBat\fR|\fBbatch\fR|\fBcronjob\fR|\fBcrtabj\fR}
X-l[aefu] [all|jobn ...]
X.br
X{\fBat\fR|\fBbatch\fR|\fBcronjob\fR|\fBcrtabj\fR}
X-c
X.P
X.SH DESCRIPTION
X.IR At (1),
X.IR batch (1),
X.IR cronjob (1),
Xand
X.IR crtabj (1)
Xall allow you to enter commands that will be executed at a later time.
XWith
X.I at
Xyou specify the time and enter the commands you want executed through stdin.
XThese comands will be executed (once) when the time comes.
X.I Batch
Xworks like
X.I at
Xexcept the system picks the time.
XYou enter the commands you want executed through stdin,
Xand they will be executed (once) with minimal load on the system.
X.I Cronjob
Xallows you execute commands on a repetitve basis.
XYou enter the commands you want executed through stdin,
Xand the system executes them according to the schedule you provide.
X.I Crtabj
Xalso works repetitively but only executes a single command.
XAny input thru stdin is passed to the command's stdin when it is executed.
X.P
XThis information is saved by the
X.I "cron facility"
Xand assigned a job number which is reported to the user.
XWhen the time comes, the job will be executed by the shell specified
Xfor the user in the
X.B /etc/passwd
Xfile.
XThe job'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
XUsers are permitted to use
X.I at
Xand
X.I batch
Xif their name appears in the file
X.BI /usr/lib/cron/at.allow .
XIf that file does not exist,
Xthe file
X.B /usr/lib/cron/at.deny
Xis checked to determine if the user
Xshould be denied access to them.
XIf neither file exists, only the superuser is allowed to
Xsubmit a job.
XSimilarly, the use of
X.I cronjob
Xand
X.I crtabj
Xis controlled by the files
X.B /usr/lib/cron/cron.allow
Xand
X.BR /usr/lib/cron/cron.deny .
XThe allow/deny files consist of one user name
Xper line.
XIt should be noted that
X.IR at ,
X.IR batch ,
X.IR cronjob ,
Xand
X.I crtabj
Xcheck the real uid, not the effective uid of the user.
X.bp
X.P
X.IR At (1),
X.IR batch (1),
X.IR cronjob (1),
Xand
X.IR crtabj (1)
Xreport the job number and scheduled execution time on standard error.
X.P
XThe user can specify the nice value increment
Xto use when the job is executed by using the
X.B -n
Xoption.
XThe superuser can specify the
X.B -N
Xoption, in which case the
X.I nice
Xparameter is interpreted as negative.
X.P
X.ce
X.B at
X.ce
X.B ==========
XThe
X.I time
Xmay be specified as 1, 2, 3 or 4 digits.
XOne and two digit numbers are taken to be hours,
Xthree or four digits
Xto be hours and minutes.
XThe time may alternately be specified as two numbers
Xseparated by a colon, meaning
X.IR hour : minute .
XA suffix
X.B am
Xor
X.B pm
Xmay be appended;
Xotherwise a 24-hour clock time is understood.
XThe suffix
X.B zulu
Xmay be used to indicate GMT.
XA space between the number and the suffix is OK.
XThe special names
X.BR noon ,
X.BR midnight ,
Xand
X.B now
Xare also recognized.
X.P
XOptionally the word
X.B next
Xor
X.B this
Xcan be used in lieu of
X.I time
Xor in addition to it.
XIf used in place of
X.IR time ,
Xthe current clock time is assumed (ie
X.BR now ).
X.P
XAn optional
X.I date
Xmay be specified as either a day of the week,
Xor a month name followed by a day number (and possibly year number).
XA year number must be 4 digits and is separated from the day number
Xby a comma, a space or, a comma and a space.
XTwo special days,
X.B today
Xand
X.B tomorrow
Xare recognized.
XIf no
X.I date
Xis given,
X.B today
Xis assumed if the given
X.I time
Xis greater than the current time and
X.B tomorrow
Xis assumed if it is less.
XIf the given day of the week is less than the current day of the week,
Xnext week is assumed.
XIf the given month is less than the current month (and no year is
Xgiven), next year is assumed.
XPreceeding the
X.I date
Xwith
X.B next
Xor
X.B this
Xwill override these assumptions.
X.P
XThe optional
X.I increment
Xmust be preceeded by a
X.B +
X(plus sign) and
Xis simply
Xa number
Xsuffixed by one of the following:
X.BR minutes ,
X.BR hours ,
X.BR days ,
X.BR weeks ,
X.BR months ,
Xor
X.BR years .
XSpaces between the plus sign, number and suffix are optional.
X.P
XAdditional comments:
XOnly the first three letters of any word are needed.
XUpper or lower case letters are OK.
XThe following words are also recognized:
X.BR nxt ,
X.BR hrs ,
X.BR wks ,
X.BR mos ,
Xand
X.BR yrs .
XIf you use
X.B next
Xor
X.BR this ,
Xyou must specify a
X.IR date .
XYou can't use a year number,
X.BR today ,
Xor
X.B tomorrow
Xwith
X.B next
Xor
X.BR this .
X.B "Next Wed"
Xmeans the Wednesday of next week.
XLikewise
X.B "this Apr 19"
Xmeans April 19th of this year.
X.bp
X.P
XThus valid
X.I at
Xcommands include:
X.P
X.RS 8
X.nf
Xat 0815am Jan 24
Xat 8:15am Jan 24, 1990
Xat now + 1 day
Xat now tomorrow
Xat 5 pm next Friday
Xat 1700 this Fri +1week
X.fi
X.RE
X.P
X.ce
X.B batch
X.ce
X.B ==========
X.I Batch
Xsubmits a batch job to the facility.
XIt is almost equivalent to
X.BR "at now" .
XThe main difference is that the system only starts execution
Xon one batch job at a time.
XAn additional difference is that
X.I batch
Xdefaults to the maximum nice value if the
X.B -n
Xor
X.B -N
Xoption is not used.
X.P
X.ce
X.B cronjob
X.ce
X.B ==========
XA
X.I cronjob
Xschedule consists of 5 fields:
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.P
XYou should note that an asterisk is a special character to the shell,
Xso you should protect it from the shell with quotes.
XYou can enclose the entire schedule in quotes or individual fields,
Xhowever you want.
X.P
XThus valid
X.I cronjob
Xcommands include:
X.P
X.RS 8
X.nf
Xcronjob '30 5 * * Sun'
Xcronjob '5,10,15,20,25,30 * * * *'
Xcronjob 00 7-12,13-17 '* *' mon-FRI
Xcronjob 0 0 1 January \e*
X.fi
X.RE
X.bp
X.P
X.ce
X.B crtabj
X.ce
X.B ==========
X.I Crtabj
Xuses the same scheduling format as
X.IR cronjob .
XIn addition the
X.B command
Xto be executed is stated on the command line.
XI/O redirection can be applied to the
X.B command
Xif it is protected from the shell with quotes.
XLike the scheduling fields, the
X.BR command ,
Xits arguments, and I/O redirection can be separately quoted or
Xthe whole thing enclosed in quotes.
X.P
XValid
X.I crtabj
Xcommand lines include:
X.P
X.RS 8
X.nf
Xcrtabj '30 5 * * Sun echo hello world >/dev/tty0'
Xcrtabj '5,10,15,20,25,30 * * * *' 'who >>online'
Xcrtabj 00 7-17 '* *' 1-5 uudeamon.hr '>/dev/null'
Xcrtabj 0 0 1 January \e* date \e> `tty`
X.fi
X.RE
X.P
XNote:
X.I crtabj
Xis the underlying command for
X.IR crontab (1).
XIt would not usually be called directly by the user.
XSee
X.IR crontab (1)
Xfor more information.
X.SH OPTIONS
X.sp
X.ce
X.B scheduling jobs
X.TP 8
X.BI -n " nice"
XSpecifies the nice value increment to apply to the job when
Xit is executed.
X.TP 8
X.BI -N " nice"
XSpecifies a negative nice value increment to apply to the job
Xwhen it is executed.
XThis option can only be used by the superuser.
X.P
X.ce
X.B "removing jobs (-r)"
X.TP 8
X.B -r
XRemoves jobs.
XYou can specify which jobs to remove by either
X.IR jobn ,
Xwhich is the job number given to you previously by the
X.IR at ,
X.IR batch ,
X.IR cronjob ,
Xor
X.I crtabj
Xcommand;
Xor by using the word
X.BR all ,
Xmeaning all the jobs (of the corresponding type) belonging to you.
XYou can only remove your own jobs unless you are the superuser.
X.TP 8
X.B -a
XAny job type.  Without this option;
X.I at
Xonly looks for AT_JOBs,
X.I batch
Xonly looks for BATCHJobs,
X.I cronjob
Xonly looks for CR_JOBs,
Xand
X.I crtabj
Xonly looks for CR_TAB jobs.
X.TP 8
X.B -e
XEverybody.
XSo,
X.B "at -re all"
Xremoves all the AT_JOBs if you are the superuser.
X.bp
X.P
X.ce
X.B "executing jobs (-x)"
X.TP 8
X.B -x
XExecutes existing jobs.
XThis option allows you to kick off jobs "now",
Xinstead of waiting for their originally scheduled execution time.
XAT_JOBs are run, then removed.
XCR_JOBs and CR_TAB jobs are run and the original schedule resumed.
XThe usefulness of this option with BATCHJobs is nil.
XYou can specify which jobs to execute by either
X.IR jobn ,
Xwhich is the job number given to you previously by the
X.IR at ,
X.IR batch ,
X.IR cronjob ,
Xor
X.I crtabj
Xcommand;
Xor by using the word
X.BR all ,
Xmeaning all the jobs (of the corresponding type) belonging to you.
XYou can only execute your own jobs unless you are the superuser.
X.TP 8
X.B -a
XAny job type.  Without this option;
X.I at
Xonly looks for AT_JOBs,
X.I batch
Xonly looks for BATCHJobs,
X.I cronjob
Xonly looks for CR_JOBs,
Xand
X.I crtabj
Xonly looks for CR_TAB jobs.
X.TP 8
X.B -e
XEverybody.
XSo,
X.B "at -xe all"
Xexecutes all the AT_JOBs if you are the superuser.
X.P
X.ce
X.B "listing jobs (-l)"
X.TP 8
X.B -l
XListing jobs.
XDefaults to one job per line
Xshowing jobnumber, type of job (AT_JOB, BATCHJ, CR_JOB, or CR_TAB),
Xscheduled execution time (next scheduled time for
X.I cronjob
Xand
X.I crtabj
Xjobs),
Xuser's name, user's group, nice value, umask value and ulimit value.
XIf you don't specify any
X.I jobn
Xarguments
Xor if you use the word
X.BR all ,
Xlisting will default to all of the
X.RI ( at ,
X.IR batch ,
X.IR cronjob ,
Xor
X.IR crtabj )
Xjobs that belong to you.
X.TP 8
X.B -a
XAny job type.  Without this option;
X.I at
Xonly looks for AT_JOBs,
X.I batch
Xonly looks for BATCHJobs,
X.I cronjob
Xonly looks for CR_JOBs,
Xand
X.I crtabj
Xonly looks for CR_TAB jobs.
X.TP 8
X.B -e
XEverybody.
XSo,
X.B "at -le all"
Xlists everybody's AT_JOBs.
X.TP 8
X.B -f
XFull listing.  Displays a full multi-line listing of jobs;
Xcomplete with header, environment and user commands.
XOnly your own jobs, unless you are the superuser.
X.TP 8
X.B -u
XUser commands.  This option displays user supplied part of a job
X(ie. that part that was originally enterred through stdin).
XOnly your own jobs, unless you are the superuser.
X.P
X.ce
X.B "clean the log file (-c)"
X.TP 8
X.B -c
XThis option can only be used by the superuser or login cron.
XIt tells the daemon to clean its log file.
X.SH HINTS
XYou should check the scheduled execution time reported to you
Xto make sure that it is what you want.
XYou can press the interrupt key (DEL), to cancel the job
Xwhile you enterring commands, if you do it before you press cntl-D.
X.SH EXAMPLES
XThe
X.IR at ,
X.IR batch ,
Xand
X.I cronjob
Xcommands read from standard input to get the commands to be executed
Xat a later time.
X.IR Sh (1)
Xand
X.IR ksh (1)
Xprovide various ways of specifying standard input.
XWithin your commands, it may be useful to redirect standard output.
XThis sequence can be used at a terminal:
X.sp
X.nf
X.in +1i
Xbatch
Xnroff \fIfilename\fP >\fIoutfile\fP
X<control-d> (hold down control and press d)
X.in -1i
X.sp
X.fi
XThis sequence, which demonstrates redirecting standard
Xerror to a pipe, is useful in a shell procedure (the sequence of
Xoutput redirection specifications is significant):
X.sp
X.nf
X.in +1i
Xbatch <<!
Xnroff \fIfilename\fP 2>&1 >\fIoutfile\fP | mail \fIotherlogin\fP
X!
X.in -1i
X.fi
X.sp
XHad you wanted to mail errors to yourself, the redirection and pipe to
X.I mail
Xwould be unnecessary because
X.I batch
Xautomatically mails stdout and stderr to you.
X.sp
X.fi
XTo have a job reschedule itself, or another job, invoke
X.I at
Xfrom within the shell procedure.
XFirst prepare a
X.I shellfile
Xsimilar to the following:
X.sp
X.nf
X.in +1i
Xecho "Time to go home" > /dev/\fImytty\fR
Xat 5 pm tomorrow < \fIshellfile\fR
X.in -1i
X.sp
XAnd then kick it off by typing:
X.sp
X.in +1i
Xat 5pm today < \fIshellfile\fR
X.sp
X.in -1i
X.fi
XAn even better way is to use
X.I cronjob
Xfrom the terminal like this:
X.sp
X.nf
X.in +1i
Xcronjob '00 17 * * Mon-Fri'
Xecho "Time to go home" > /dev/\fImytty\fR
X<control-d>
X.in -1i
X.fi
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 job is executed.
X.SH CAVEATS
XThe shell which runs the commands saved by
X.IR at ,
X.IR batch ,
Xand
X.I cronjob
Xreads these commands from a file with the shell's stdin
Xconnected to this file.
XIf one of the commands also reads stdin,
Xit could mess up the shell.
XThis problem does not occur with
X.I crtabj
Xwhere the command's stdin is supposed to read it.
X.P
XThe syntax of the command line is probably upwardly compatable with the
Xstandard sysV version of
X.IR at (1),
Xbut I don't guarantee it.
XUnlike the standard sysV version, this version will allow you to do
X.BR "at now" .
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
Xcron(1M), crontab(1), 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
Xprocessing continued (like trying to remove another user's job),
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 too old) and was removed my the daemon.
X.SH AUTHOR
XDonald Lashomb  4/89, 10/90
SHAR_EOF
$TOUCH -am 1018215890 at.1 &&
chmod 0644 at.1 ||
echo "restore of at.1 failed"
set `wc -c at.1`;Wc_c=$1
if test "$Wc_c" != "15142"; then
	echo original size 15142, current size $Wc_c
fi
fi
# ============= at.c ==============
if test X"$1" != X"-c" -a -f 'at.c'; then
	echo "File already exists: skipping 'at.c'"
else
echo "x - extracting at.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > at.c &&
X/*  at(1), batch(1), cronjob(1) and crtabj(1) command program  */
X
X/* SYNOPSIS:  at [-{n|N} nice] time [date] [+ increment]		<<1>>
X *            batch [-{n|N} nice]					<<1>>
X *            cronjob [-{n|N} nice] mins hrs mdays mons wdays		<<1>>
X *            crtabj [-{n|N} nice] mins hrs mdays mons wdays cmd	<<1>>
X *
X *            {at|batch|cronjob|crtabj} -r[ae] {all|jobn ...}		<<2>>
X *            {at|batch|cronjob|crtabj} -x[ae] {all|jobn ...}		<<3>>
X *            {at|batch|cronjob|crtabj} -l[aefu] [all|jobn ...]		<<4>>
X *            {at|batch|cronjob|crtabj} -c				<<5>>
X *
X * <<1>> schedules new jobs:
X *	-n nice	specifiy nice value for job
X *	-N nice	negative nice value, su only (eg -N 20)
X *	time date increment - see parsetime.c
X *	schedule = mins hrs ... - see parsesched.c
X *
X * <<2>> -r removes jobs:
X *	-a	any jobtype (ie AT_JOB, BATCHJ, CR_JOB or CR_TAB)
X *	-e	everybody's, su only (no -e is just your jobs)
X *	all	all your jobs (or everybody's with -e for su)
X *	jobn	specify which jobs to remove by number (any for su)
X *
X * <<3>> -x executes queued jobs:
X *	-a	any jobtype (ie AT_JOB, BATCHJ, CR_JOB or CR_TAB)
X *	-e	everybody's, su only (no -e is just your jobs)
X *	all	all your jobs (or everybody's with -e for su)
X *	jobn	specify which jobs to remove by number (any for su)
X *
X * <<4>> -l list jobs:
X *	-a	any jobtype (ie AT_JOB, BATCHJ, CR_JOB or CR_TAB)
X *	-e	everybody's jobs
X *	-f	full listing incl job contents (-ef for su only)
X *	-u	user supplied part of job (-eu for su only)
X *	all	all your jobs (or everybody's with -e) [default]
X *	jobn	specify which job to list, default all
X *
X * <<5>> -c clean log:
X *	only the superuser or login cron
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#include <stdio.h>
X#include <fcntl.h>
X#include <string.h>
X#include <memory.h>
X#include <ctype.h>
X#include <sys/signal.h>
X#include <pwd.h>
X#include <grp.h>
X#include <errno.h>
X#include <sys/dir.h>
X#include "cron.h"
X#include "job.h"
X
X#define BADSIG		((int (*)()) -1)
X
Xextern char		**environ;
Xextern int		errno;
Xextern void		exit();
Xextern void		perror();
Xextern long		ulimit();
Xextern long		time();
Xextern char		*strrchr();
Xextern int		(*signal())();
Xextern int		getopt();
Xextern char		*optarg;
Xextern int		optind;
Xextern char		*getenv();
Xextern struct passwd	*getpwnam();
Xextern struct passwd	*getpwuid();
Xextern struct group	*getgrgid();
Xextern char		*ctime();
Xextern long		strtol();
X
Xextern long		parsetime();		/* link with parsetime.o  */
Xextern SCHED		*parsesched();		/* link with parsesched.o */
Xextern long		resched();		/* link with resched.o    */
Xextern char		*jtypes[];		/* link with job.o        */
Xextern int		openfifo();		/* \                      */
Xextern int		rdfifo();		/*  > link with fifo.o    */
Xextern int		wrfifo();		/* /                      */
Xextern int		realuid;		/* \                      */
Xextern struct passwd	*userpw;		/*  \ link with login.o   */
Xextern void		deny();			/*  /                     */
Xextern void		login();		/* /                      */
Xextern char		*getwd();		/* link with getwd.o      */
Xextern void		opendir();		/* \                      */
Xextern void		closedir();		/*  > link with dir.o     */
Xextern char		*ls();			/* /                      */
X
X
X/* global variables ------------------------------------------- */
X
XJOB		jjj = {0};			/* default job buffer     */
X
X
X/* static variables ------------------------------------------- */
X
Xstatic char		*myname;
Xstatic int		exval = 0;
Xstatic int		wrjbflg = 0;
Xstatic int		unlkflg = 0;
Xstatic int		jnum,fnam;
Xstatic char		fname[DIRSIZ+1] = "";
Xstatic char
Xcopyright[] = "at(1), batch(1), cronjob(1), crtabj(1) - (c)1989,1990 D.Lashomb";
X
X
X/* global routines -------------------------------------------- */
X
Xvoid fatal(str)
X	char *str;
X	{
X	int oerrno = errno;
X
X	fprintf(stderr,"%s: can't ",myname);
X	errno = oerrno;
X	perror(str);
X	if(wrjbflg) { wrjbflg=0; wrfifo(jnum); }
X	if(unlkflg) unlink(fname);
X	exit(1);
X	}
X
Xvoid crash(str)
X	char *str;
X	{
X	errno = 0;
X	fatal(str);
X	}
X
X
X/* static routines -------------------------------------------- */
X
Xstatic void usage()
X	{
X	fputs("\
Xusage: at [ -{n|N} nice ] time [{ next | this }] [ date ] [ + increment ]\n\
X       batch [ -{n|N} nice ]\n\
X       cronjob [ -{n|N} nice ] mins hrs mdays mons wdays\n\
X       crtabj [ -{n|N} nice ] mins hrs mdays mons wdays command\n\
X       { at | batch | cronjob | crtabj } -r[ae] { all | jobn ... }\n\
X       { at | batch | cronjob | crtabj } -x[ae] { all | jobn ... }\n\
X       { at | batch | cronjob | crtabj } -l[aefu] [ all | jobn ... ]\n\
X       { at | batch | cronjob | crtabj } -c\n\
X",stderr);
X	exit(2);
X	}
X
Xstatic int 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(wrjbflg) { wrjbflg=0; wrfifo(jnum); }
X	if(unlkflg) unlink(fname);
X	exit(1);
X	/*NOTREACHED*/
X	}
X
Xstatic void error(str)
X	char *str;
X	{
X	fprintf(stderr,"can't %s\n",str);
X	exval = 1;
X	}
X
X/* fclose job file - convienence routine ---------------------- */
X
Xstatic void closejobfile(jfp)
X	FILE	*jfp;
X	{
X	if(fclose(jfp) != 0)
X		fatal("close job file");
X	}
X
X
X/* ============================================================ */
X/*                  SEND NEW JOB TO THE DAEMON                  */
X/* ------------------------------------------------------------ */
X
Xstatic void addjob(msg,jtime,nicv,sched,cmd)
X	int	msg;
X	long	jtime;
X	int	nicv;
X	SCHED	*sched;
X	char	*cmd;
X	{
X	register char	**envp;
X	register int	envc,envz;
X	register char	*p;
X	register int	c;
X	int	jfd;
X	FILE	*jfp;
X	char	*shell;
X	char	wd[PATHSIZ];
X
X/* Get job slot ----------------------------------------------- */
X
X	if(rdfifo(jnum) == 0)			/* sets jjj.jnum */
X		crash("get job slot, try later");
X	wrjbflg = 1;
X
X/* Build job struct ------------------------------------------- */
X
X	/*	I do the time schedule part of the job struct first,
X	 *	so I can display the time on stderr quickly.  This
X	 *	makes it *appear* more responsive to the user.
X	 */
X
X	jjj.time = jtime;
X	if((msg == CR_JOB) || (msg == CR_TAB)) {
X		memcpy(jjj.min,sched->min,60);
X		memcpy(jjj.hour,sched->hour,24);
X		memcpy(jjj.mday,sched->mday,32);
X		memcpy(jjj.mon,sched->mon,12);
X		memcpy(jjj.wday,sched->wday,7);
X		if((jjj.time = resched(jtime)) < 0L)
X			crash("- scheduling error");
X		}
X	fprintf(stderr,"%d\t%s",jjj.jobn,ctime(&jjj.time));
X
X	jjj.link = NOMAGIC;		/* \                         */
X	jjj.envc = 0;			/*  > mark file not complete */
X	jjj.envz = 0;			/* /                         */
X	jjj.msg = msg;
X	jjj.uid = realuid;
X	jjj.gid = getgid();
X	jjj.nice = nicv;
X	jjj.umask = umask(UMASK);
X	jjj.ulimit = ulimit(1,0L);
X
X	shell = userpw->pw_shell;
X	if(getwd(wd,PATHSIZ) == NULL)
X		fatal("get current working dir");
X	if(cmd == NULL) cmd = "";
X
X/* create job file and write partially completed header ------- */
X
X	if(chdir(ATSPOOL) != 0)
X		fatal("cd to spool dir");
X	sprintf(fname,"%d",jjj.jobn);		/* fname[] > ascii jobn */
X	unlkflg = 1;
X	if(((jfd=open(fname,O_WRONLY|O_CREAT|O_EXCL,J_PERM)) == -1) || 
X	   ((jfp=fdopen(jfd,"w")) == NULL))
X		fatal("open job file");
X	if(fwrite((char *)&jjj,JOBSIZ,1,jfp) != 1)
X		fatal("write job file");
X
X/* write environment, etc strings to the job file ------------- */
X
X	/*
X	 * count number of char ptrs and bytes in environment
X	 * including shell, working dir, and crtabj command
X	 * and write this stuff to the job file
X	 */
X	envc = 3;		/* at least 4 char ptrs: shell, wd, cmd, NULL */
X	envz = 3;		/* at least 3 '\0' bytes for them             */
X	envp = environ;
X	while(++envc,((p = *envp++) != NULL))
X		while(++envz,((c=putc(*p++,jfp)) != '\0'))
X			if(c == EOF)
X				fatal("write job file");
X	jjj.envc = envc;
X	jjj.envz = envz+strlen(shell)+strlen(wd)+strlen(cmd);
X
X	if((
X	fputs(shell,jfp)	== EOF	) || (
X	putc('\0',jfp)		== EOF	) || (
X	fputs(wd,jfp)		== EOF	) || (
X	putc('\0',jfp)		== EOF	) || (
X	fputs(cmd,jfp)		== EOF	) || (
X	putc('\0',jfp)		== EOF	)
X	)
X		fatal("write job file");
X
X/* write user supplied part of job file ----------------------- */
X
X	while((c=getchar()) != EOF)
X		if(putc(c,jfp) == EOF)
X			fatal("write job file");
X	if(!feof(stdin))
X		fatal("read stdin");
X
X/* mark job done with MAGIC, (re)write completed header ------- */
X
X	jjj.link = MAGIC;
X	rewind(jfp);
X	if(fwrite((char *)&jjj,JOBSIZ,1,jfp) != 1)
X		fatal("write job file");
X	closejobfile(jfp);
X	
X/* send message to the daemon thru the fifo ------------------- */
X
X	if((jtime < (time((long *)0)-CATCHUP-GRAINULARITY)) && (msg == AT_JOB))
X		crash("do it, too late");
X
X	if(wrfifo(fnam) == 0)
X		fatal("write FNAM fifo");
X	wrjbflg = 0;
X	if(isatty(0))
X		fprintf(stderr,"%d\t%s",jjj.jobn,ctime(&jjj.time));
X	unlkflg = 0;
X	}
X
X/* ============================================================ */
X/*          UNLINK FILE AND SEND REMOVE MSG TO DAEMON           */
X/* ------------------------------------------------------------ */
X
Xstatic int rmvjob(msg,fnm,e,a)
X	int	msg;
X	char	*fnm;
X	int	e;
X	int	a;
X	{
X	FILE	*jfp;
X	int	jno;
X	char	*p;
X
X	jno = (int)strtol(fnm,&p,10);
X	if((p == fnm) || (*p != '\0')) {
X		return(-6);
X		}
X	if((jfp=fopen(fnm,"r")) == NULL) {
X		return(-1);
X		}
X	if(fread((char *)&jjj,JOBSIZ,1,jfp) != 1) {
X		closejobfile(jfp);
X		return(-2);
X		}
X	if((msg != jjj.msg) && (!a)) {
X		closejobfile(jfp);
X		return(-3);
X		}
X	if((realuid != jjj.uid) && (!e)) {
X		closejobfile(jfp);
X		return(-4);
X		}
X	unlink(fnm);
X
X	jjj.link = MAGIC;
X	jjj.jobn = jno;			/* important */
X	jjj.msg = REMOVE;		/* important */
X	jjj.uid = realuid;		/* important */
X	wrfifo(fnam);
X	closejobfile(jfp);
X	return(0);
X	}
X
X/* ============================================================ */
X/*                   SEND EXECUTE MSG TO DAEMON                 */
X/* ------------------------------------------------------------ */
X
Xstatic int execjob(msg,fnm,e,a)
X	int	msg;
X	char	*fnm;
X	int	e;
X	int	a;
X	{
X	FILE	*jfp;
X	int	jno;
X	char	*p;
X
X	jno = (int)strtol(fnm,&p,10);
X	if((p == fnm) || (*p != '\0')) {
X		return(-6);
X		}
X	if((jfp=fopen(fnm,"r")) == NULL) {
X		return(-1);
X		}
X	if(fread((char *)&jjj,JOBSIZ,1,jfp) != 1) {
X		closejobfile(jfp);
X		return(-2);
X		}
X	if((msg != jjj.msg) && (!a)) {
X		closejobfile(jfp);
X		return(-3);
X		}
X	if((realuid != jjj.uid) && (!e)) {
X		closejobfile(jfp);
X		return(-4);
X		}
X
X	jjj.link = MAGIC;
X	jjj.jobn = jno;			/* important */
X	jjj.msg = EXECUT;		/* important */
X	jjj.uid = realuid;		/* important */
X	wrfifo(fnam);
X	closejobfile(jfp);
X	return(0);
X	}
X
X/* ============================================================ */
X/*                          LIST JOBS                           */
X/* ------------------------------------------------------------ */
X
Xstatic void printlist(label,array,start,end,offset)
X	char		*label;
X	char		array[];
X	int		start;
X	register int	end;
X	int		offset;
X	{
X	register int	i,j;
X	int		comma;
X
X	comma = 0;
X	printf(label);
X	for(i=start;i<end;++i) {
X		if(array[i] != '\0') {
X			for(j=i+1;j<end;++j) {
X				if(array[j] == '\0') break;
X				}
X			if((j-i) == (end-start)) {
X				printf("*\n");
X				return;
X				}
X			--j;
X			if(comma) putchar(',');
X			switch(j-i) {
X			case 0:
X				printf("%d",i+offset);
X				break;
X			case 1:
X				printf("%d,%d",i+offset,j+offset);
X				break;
X			default:
X				printf("%d-%d",i+offset,j+offset);
X				}
X			comma = 1;
X			i = j;
X			}
X		}
X	putchar('\n');
X	}
X
Xstatic int printjob(msg,fnm,e,f,a,u)
X	int		msg;
X	char		*fnm;
X	int		e,f,a,u;
X	{
X	FILE		*jfp;
X	char		*tim;
X	struct passwd	*pw;
X	struct group	*gr;
X	register int	c,m,x,z;
X	char		line[LINESIZ];
X	char		*p;
X
X	(void)strtol(fnm,&p,10);
X	if((p == fnm) || (*p != '\0')) {
X		return(-6);
X		}
X	if((jfp=fopen(fnm,"r")) == NULL) {
X		return(-1);
X		}
X	if(fread((char *)&jjj,JOBSIZ,1,jfp) != 1) {
X		closejobfile(jfp);
X		return(-2);
X		}
X
X	m=jjj.msg;
X	if((m < MINMSG) || (m > MAXMSG)) m=MAXMSG+1;
X
X	if((msg != m) && (!a)) {
X		closejobfile(jfp);
X		return(-3);
X		}
X	if((realuid != jjj.uid) && (!e)) {
X		closejobfile(jfp);
X		return(-4);
X		}
X	if((m == CR_JOB) || (m == CR_TAB))
X		jjj.time = resched(time((long *)0));
X	if(jjj.time < 0L)
X		tim = "invalid time";
X	else {
X		tim = ctime(&jjj.time);
X		tim[strcspn(tim,"\n")] = '\0';	/* strip \n */
X		}
X	pw = getpwuid(jjj.uid);
X	gr = getgrgid(jjj.gid);
X
X	if(!(f||u)) {
X		printf("%d %s %s %s %s %d %#o %d\n",
X			jjj.jobn,
X			jtypes[m],
X			tim,
X			pw->pw_name,
X			gr->gr_name,
X			jjj.nice,
X			jjj.umask,
X			jjj.ulimit
X			);
X		closejobfile(jfp);
X		return(0);
X		}
X
X	/* f or u */
X
X	if((realuid != jjj.uid) && (realuid != 0)) {
X		closejobfile(jfp);
X		return(-5);
X		}
X
X	if(u) {
X		for(z=0;z<jjj.envz;++z) {
X			if(getc(jfp) == EOF)
X				fatal("read job file");
X			}
X		while(fgets(line,LINESIZ,jfp) != NULL)
X			fputs(line,stdout);
X		if(!feof(jfp))
X			fatal("read job file");
X		closejobfile(jfp);
X		return(0);
X		}
X
X	/* f */
X
X	printf("jobn=   %d\n",jjj.jobn);
X	printf("type=   %s\n",jtypes[m]);
X	printf("time=   %s\n",tim);
X	printf("uid=    %d %s\n",jjj.uid,pw->pw_name);
X	printf("gid=    %d %s\n",jjj.gid,gr->gr_name);
X	printf("nice=   %d\n",jjj.nice);
X	printf("umask=  %#o\n",jjj.umask);
X	printf("ulimit= %d\n",jjj.ulimit);
X
X	if((m == CR_JOB) || (m == CR_TAB)) {
X		/*
X		 *	x flags all mdays
X		 *	z flags all wdays
X		 *	c general index
X		 */
X		x = z = 0;
X		for(c=1;c<32;++c) x |= jjj.mday[c];
X		for(c=0;c<7;++c) z |= jjj.wday[c];
X
X		printf("\n<<schedule>>\n");
X		printlist("minutes= ",jjj.min,0,60,0);
X		printlist("hours=   ",jjj.hour,0,24,0);
X		if((x == 0) && (z != 0))
X			printf("mo-days= *\n");
X		else
X			printlist("mo-days= ",jjj.mday,1,32,0);
X		printlist("months=  ",jjj.mon,0,12,1);
X		if((z == 0) && (x != 0))
X			printf("wk-days= *\n");
X		else
X			printlist("wk-days= ",jjj.wday,0,7,0);
X		}
X
X	printf("\n<<environment>>\n");
X	for(x=2,z=0;z<jjj.envz;++z) {
X		switch(c=getc(jfp)) {
X		case EOF:
X			fatal("read job file");
X		case '\0':
X			if(++x == jjj.envc-2)
X				printf("\n\n<<shell>>");
X			else if(x == jjj.envc-1)
X				printf("\n\n<<working dir>>");
X			else if(x == jjj.envc)
X				printf("\n\n<<command>>");
X			c = '\n';
X		default:
X			putchar(c);
X			}
X		}
X	printf("\n<<user stdin>>\n");
X	while((c=getc(jfp)) != EOF)
X		putchar(c);
X
X	if(!feof(jfp))
X		fatal("read job file");
X	closejobfile(jfp);
X	return(0);
X	}
X
X
X/* ============================================================ */
X/*                        CLEAN LOG FILE                        */
X/* ------------------------------------------------------------ */
X
Xstatic void cleanlog()
X	{
X	jjj.link = MAGIC;
X	jjj.jobn = 0;
X	jjj.msg = CL_LOG;		/* important */
X	jjj.uid = realuid;		/* important */
X	wrfifo(fnam);
X	}
X
X
X/* ============================================================ */
X/*                            MAIN()                            */
X/* ------------------------------------------------------------ */
X
Xmain(argc,argv)
X	int	argc;
X	char	**argv;
X	{
X	int	arg0;
X	char	*allowfile,*denyfile;
X	char	*p;
X	int	c;
X	int	nicv;
X	int	Nflag,nflag,rflag,eflag,lflag,fflag,aflag,cflag,uflag,xflag;
X	long	jtime,now;
X	SCHED	*sched;
X
X/* Check arg0 ------------------------------------------------- */
X
X	myname = argv[0];			/*****************************/
X	if((p=strrchr(myname,'/')) != NULL)	/* shell doesn't always make */
X		myname = p+1;			/*  argv[0] == basename !!!  */
X						/*****************************/
X	if(strcmp(myname,"at") == 0) {
X		arg0 = AT_JOB;
X		allowfile = ATALLOW;
X		denyfile = ATDENY;
X		nicv = 0;
X		}
X	else if (strcmp(myname,"batch") == 0) {
X		arg0 = BATCHJ;
X		allowfile = ATALLOW;
X		denyfile = ATDENY;
X		nicv = MAXNICE;
X		}
X	else if	(strcmp(myname,"cronjob") == 0) {
X		arg0 = CR_JOB;
X		allowfile = CRALLOW;
X		denyfile = CRDENY;
X		nicv = 0;
X		}
X	else if	(strcmp(myname,"crtabj") == 0) {
X		arg0 = CR_TAB;
X		allowfile = CRALLOW;
X		denyfile = CRDENY;
X		nicv = 0;
X		}
X	else {
X		usage();
X		}
X
X/* Setup and check if user allowed to use this program -------- */
X
X	login(allowfile,denyfile);
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	jnum = openfifo(JNUM);		/* closed by exit() */
X	fnam = openfifo(FNAM);		/* closed by exit() */
X
X	Nflag=nflag=rflag=eflag=lflag=fflag=aflag=cflag=uflag=xflag=0;
X
X/* Process command line --------------------------------------- */
X
X/*	optind = 0;					ld(1) does this */
X	while((c=getopt(argc,argv,"N:n:aceflrux")) != EOF) {
X		switch(c) {
X		case 'N':
X			if(Nflag||nflag||rflag||eflag||
X			   lflag||fflag||aflag||cflag||uflag||xflag) usage();
X			if(realuid != 0) deny();
X			if(!isdigit(*optarg)) usage();
X			nicv = -(atoi(optarg));
X			Nflag = 1;
X			break;
X		case 'n':
X			if(Nflag||nflag||rflag||eflag||
X			   lflag||fflag||aflag||cflag||uflag||xflag) usage();
X			if(!isdigit(*optarg)) usage();
X			nicv = atoi(optarg);
X			nflag = 1;
X			break;
X		case 'r':
X			if(Nflag||nflag||rflag||
X			   lflag||fflag||cflag||uflag||xflag) usage();
X			rflag = 1; break;
X		case 'x':
X			if(Nflag||nflag||rflag||
X			   lflag||fflag||cflag||uflag||xflag) usage();
X			xflag = 1; break;
X		case 'e':
X			if(Nflag||nflag||eflag||cflag) usage();
X			eflag = 1; break;
X		case 'l':
X			if(Nflag||nflag||rflag||lflag||cflag||xflag) usage();
X			lflag = 1; break;
X		case 'f':
X			if(Nflag||nflag||rflag||fflag||cflag||xflag) usage();
X			fflag = 1; break;
X		case 'u':
X			if(Nflag||nflag||rflag||uflag||cflag||xflag) usage();
X			uflag = 1; break;
X		case 'a':
X			if(Nflag||nflag||aflag||cflag) usage();
X			aflag = 1; break;
X		case 'c':
X			if(Nflag||nflag||rflag||eflag||
X			   lflag||fflag||aflag||cflag||uflag||xflag) usage();
X			cflag = 1; break;
X		default:
X			usage();
X			}
X		}
X
X/* {at|batch|cronjob|crtabj} -c ------------------------- <<5>> */
X
Xif(cflag) {
X	if((realuid != 0) &&
X	   (strcmp(userpw->pw_name,"cron") != 0))
X		deny();
X	cleanlog();
X	exit(exval);
X	}
X
X/* at [-{n|N} nice] time [date] [+ increment] ----------- <<1>> */
X/* batch [-{n|N} nice] ---------------------------------- <<1>> */
X/* cronjob [-{n|N} nice] mins hrs mdays mons wdays ------ <<1>> */
X/* crtabj [-{n|N} nice] schedule command ---------------- <<1>> */
X
Xif(!(rflag||eflag||lflag||fflag||aflag||uflag||xflag)) {
X	now = time((long *)0);
X	switch(arg0) {
X	case AT_JOB:
X		if((jtime = parsetime(argc,argv)) == -1L) usage();
X		addjob(AT_JOB,jtime,nicv,(SCHED *)NULL,NULL);
X		break;
X	case BATCHJ:
X		if(optind != argc) usage();
X		addjob(BATCHJ,now,nicv,(SCHED *)NULL,NULL);
X		break;
X	case CR_JOB:
X		if((sched = parsesched(argc,argv,&p)) == NULL) usage();
X		if(*p != '\0') usage();
X		addjob(CR_JOB,now,nicv,sched,NULL);
X		break;
X	case CR_TAB:
X		if((sched = parsesched(argc,argv,&p)) == NULL) usage();
X		if(*p == '\0') usage();
X		*(strrchr(p,' ')) = '\0';		/* strip last space */
X		addjob(CR_TAB,now,nicv,sched,p);
X		}
X	exit(exval);
X	}
X
X/* for all the following, cd to spool dir --------------------- */
X
X	if(chdir(ATSPOOL) != 0)
X		fatal("cd to spool dir");
X
X/* {at|batch|cronjob|crtabj} -r[ae] {all|jobn ...} ------ <<2>> */
X
X	if(rflag) {
X		if(optind == argc) usage();
X		if((eflag) && (realuid != 0)) deny();
X		if(strcmp(argv[optind],"all") == 0) {
X			if(optind != (argc-1)) usage();
X
X			opendir();
X			while((p=ls()) != NULL) {
X				switch(rmvjob(arg0,p,eflag,aflag)) {
X				case -1:
X					error("open job file");
X					break;
X				case -2:
X					error("read job file");
X					break;
X				case -6:
X					error("- non-numeric fname, get help");
X					}
X				}
X			closedir();
X			exit(exval);
X			}
X
X		if(realuid == 0) eflag = 1;
X		while(optind < argc) {
X			switch(rmvjob(arg0,argv[optind++],eflag,aflag)) {
X			case -1:
X				error("open job file");
X				break;
X			case -2:
X				error("read job file");
X				break;
X			case -3:
X				error("- wrong job type");
X				break;
X			case -4:
X				error("- permission denied");
X				break;
X			case -6:
X				error("- non-numeric jobn");
X				}
X			}
X		exit(exval);
X		}
X
X/* {at|batch|cronjob|crtabj} -x[ae] {all|jobn ...} ------ <<3>> */
X
X	if(xflag) {
X		if(optind == argc) usage();
X		if((eflag) && (realuid != 0)) deny();
X		if(strcmp(argv[optind],"all") == 0) {
X			if(optind != (argc-1)) usage();
X
X			opendir();
X			while((p=ls()) != NULL) {
X				switch(execjob(arg0,p,eflag,aflag)) {
X				case -1:
X					error("open job file");
X					break;
X				case -2:
X					error("read job file");
X					break;
X				case -6:
X					error("- non-numeric fname, get help");
X					}
X				}
X			closedir();
X			exit(exval);
X			}
X
X		if(realuid == 0) eflag = 1;
X		while(optind < argc) {
X			switch(execjob(arg0,argv[optind++],eflag,aflag)) {
X			case -1:
X				error("open job file");
X				break;
X			case -2:
X				error("read job file");
X				break;
X			case -3:
X				error("- wrong job type");
X				break;
X			case -4:
X				error("- permission denied");
X				break;
X			case -6:
X				error("- non-numeric jobn");
X				}
X			}
X		exit(exval);
X		}
X
X/* {at|batch|cronjob|crtabj} -l[aefu] [all|jobn ...] ---- <<4>> */
X
X	if(lflag) {
X		c = 0;
X		if(optind == argc)
X			c = 1;
X		else
X			if(strcmp(argv[optind],"all") == 0) {
X				if(optind != (argc-1)) usage();
X				c = 1;
X				}
X		if(c) {
X			opendir();
X			while(p=ls()) {
X				switch(printjob(arg0,p,
X					eflag,fflag,aflag,uflag)) {
X				case -1:
X					error("open job file");
X					break;
X				case -2:
X					error("read job file");
X					break;
X				case -6:
X					error("- non-numeric fname, get help");
X					}
X				}
X			closedir();
X			exit(exval);
X			}
X
X		eflag = 1;
X		while(optind < argc) {
X			switch(printjob(arg0,argv[optind++],
X				eflag,fflag,aflag,uflag)) {
X			case -1:
X				error("open job file");
X				break;
X			case -2:
X				error("read job file");
X				break;
X			case -3:
X				error("- wrong job type");
X				break;
X			case -5:
X				error("- permission denied");
X				break;
X			case -6:
X				error("- non-numeric jobn");
X				}
X			}
X		exit(exval);
X		}
X
X	/* not valid option combination */
X	usage();
X
X	/*NOTREACHED*/
X	}
SHAR_EOF
$TOUCH -am 1025125590 at.c &&
chmod 0644 at.c ||
echo "restore of at.c failed"
set `wc -c at.c`;Wc_c=$1
if test "$Wc_c" != "22541"; then
	echo original size 22541, current size $Wc_c
fi
fi
# ============= convertjob.c ==============
if test X"$1" != X"-c" -a -f 'convertjob.c'; then
	echo "File already exists: skipping 'convertjob.c'"
else
echo "x - extracting convertjob.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > convertjob.c &&
X/* convertjob.c - Oct 8, 1990 D.Lashomb
X *
X * SYNOPSIS
X *	convertjob jobn >temp
X *
X * DESCRIPTION
X *	This is a throwaway program to convert old style cron facility
X *	jobs to the new style.
X *
X *	Pre-version-4.0 cron facility jobs used a magic number of zero
X *	and marked most jobs with an extra newline and a "# job done\n".
X *	Version 4.0 of the cron facility marks its jobs with a magic
X *	number of MAGIC (== 'c''r''o''n') and does away with the extra
X *	newline and the "# job done\n".
X *
X *	The purpose of this program is to make it easier for users of
X *	older versions of my cron facility to upgrade to the new version.
X *	Before firing up the new facility, you would convert the old
X *	jobs:
X *		$ su
X *		# ATSPOOL=/usr/spool/cron/atjobs
X *		# cd $ATSPOOL
X *		# for i in *
X *		> do
X *		>	<<wherever>>/convertjob $i >/tmp/convj.temp
X *		>	cat /tmp/convj.temp >$i	# preserve own,grp,mode
X *		> done
X *		# rm /tmp/convj.temp
X *
X * CAVEATS
X *	Because this is a throwaway program, the error checking is nil.
X */
X
X#include <stdio.h>
X#include <string.h>
X#include "cron.h"
X#include "job.h"
X
XJOB	jjj;
XFILE	*fp;
Xchar	line1[LINESIZ] = "";
Xchar	line2[LINESIZ] = "";
Xchar	line3[LINESIZ] = "";
Xint	l1flg = 0;
Xint	l2flg = 0;
Xint	l3flg = 0;
X
Xint getline()
X	{
X	if(l2flg) {
X		strcpy(line3,line2);
X		l3flg = 1;
X		}
X	if(l1flg) {
X		strcpy(line2,line1);
X		l2flg = 1;
X		}
X	if(fgets(line1,LINESIZ,fp) == NULL) {
X		l1flg = 0;
X		return(0);
X		}
X	l1flg = 1;
X	return(1);
X	}
X
Xvoid putline()
X	{
X	if(l3flg) {
X		l3flg = 0;
X		fputs(line3,stdout);
X		}
X	else if(l2flg) {
X		l2flg = 0;
X		fputs(line2,stdout);
X		}
X	else if(l1flg) {
X		l1flg = 0;
X		fputs(line1,stdout);
X		}
X	}
X
Xint isdone()
X	{
X	if((strcmp(line2,"\n") == 0) &&
X	   (strcmp(line1,"# job done\n") == 0))
X		return(1);
X	if((strcmp(line3,"\n") == 0) &&
X	   (strcmp(line2,"# job done\n") == 0))
X		return(1);
X	return(0);
X	}
X
Xmain(argc,argv)
X	int	argc;
X	char	**argv;
X	{
X
X	fp = fopen(argv[1],"r");
X
X	/* convert the header */
X	fread(&jjj,JOBSIZ,1,fp);
X	jjj.link = MAGIC;
X	fwrite(&jjj,JOBSIZ,1,stdout);
X
X	/* copy the environment */
X	while((jjj.envz)--)
X		fputc(fgetc(fp),stdout);
X
X	/* copy user input up to the "# job done\n" */
X	if(getline() && getline())
X		while(getline()) putline();
X	if(!isdone()) {
X		putline();
X		putline();
X		}
X
X	fclose(fp);
X	}
SHAR_EOF
$TOUCH -am 1008173890 convertjob.c &&
chmod 0644 convertjob.c ||
echo "restore of convertjob.c failed"
set `wc -c convertjob.c`;Wc_c=$1
if test "$Wc_c" != "2275"; then
	echo original size 2275, current size $Wc_c
fi
fi
# ============= cron.1 ==============
if test X"$1" != X"-c" -a -f 'cron.1'; then
	echo "File already exists: skipping 'cron.1'"
else
echo "x - extracting cron.1 (Text)"
sed 's/^X//' << 'SHAR_EOF' > cron.1 &&
X.TH CRON 1M LOCAL
X.SH NAME
Xcron \- clock daemon
X.SH SYNOPSIS
X.B /etc/daemons/cron
X[ catchup [ maxkids ]]
X.SH DESCRIPTION
XThe
X.I cron
Xdaemon executes commands at scheduled dates and times.
XBecause the
X.I cron
Xdaemon never exits, it should only be called once.
XThis is best done by running it from the
X.B /etc/rc
Xfile during the initialization process.
X.IR Cron ;
Xtogether with
X.IR at (1),
X.IR batch (1),
X.IR cronjob (1),
X.IR crtabj (1)
Xand
X.IR crontab (1);
Ximplements the
X.I "cron facility"
Xand gives you the ability to execute commands at a later time.
X.P
XUser commands are submitted to this daemon in the form of a
X.BR job .
XA
X.B job
Xconsists of a file containing vital information such as
Xa jobnumber, schedule time, user's uid, environment and the
Xactual commands that the user wants performed.
XThe daemon provides the jobnumber;
Xthe
X.IR at (1),
X.IR batch (1),
X.IR cronjob (1)
Xor
X.IR crtabj (1)
X.RI [ crontab (1)]
Xcommand provides the schedule time, uid, environment
Xand other vital information;
Xand, of course, the user provides the actual commands to be executed.
XJob files are kept in the
X.B atjobs
Xspool directory.
X.P
XWhen the time comes for a job to be executed, the daemon:
X.TP
X\(bu
Xforks off a process to run it
X.TP
X\(bu
Xrestores the user's environment
X.TP
X\(bu
Xconnects the standard input to the job file
X.TP
X\(bu
Xarranges for the standard output and standard error output to
Xbe mailed to the user
X.TP
X\(bu
Xfires up the user's shell to execute the commands
X.TP
X\(bu
Xand removes or reschedules the job.
X.P
XThe shell environment variables, current working directory,
Xumask and ulimit in effect when the job was created are preserved.
XOpen file descriptors, traps and priority are lost.
XException: the
X.B TZ
Xenvironment variable, see below.
X.P
XThe daemon keeps an in-memory list of pending jobs.
XWhen the daemon is started, it checks the spool directory to see
Xif there are any existing jobs, and rebuilds its in-memory list.
XThe jobnumber is the same as the job's filename.
XAny free jobnumbers are written into the JNUMBS fifo,
Xso the
X.IR at (1),
X.IR batch (1),
X.IR cronjob (1)
Xand
X.IR crtabj (1)
X.RI [ crontab (1)]
Xcommands can get them.
XThe daemon then goes into an endless sleep/wake cycle.
XEvery time it wakes up; it checks the FNAMES fifo to see if the
X.IR at (1),
X.IR batch (1),
X.IR cronjob (1)
Xor
X.IR crtabj (1)
X.RI [ crontab (1)]
Xcommands have submitted any new jobs,
Xchecks its in-memory list,
Xand runs any jobs that are due.
X.P
XThe daemon tries pretty hard to do the bidding of the users,
Xbut there are limits to everything!
X.P
XJobs may not execute exactly when the user has scheduled them
Xfor a number of reasons.
XThe biggest reason is because of the sleep/wake cycle of the
Xdaemon itself.
XThe length of this cycle is determined by the
X.I GRAINULARITY
Xparameter which is compiled into the daemon (usually 1 minute).
XThe daemon only looks backwards in time.
XIf it wakes up even one second before a job is scheduled,
Xit doesn't consider that job until the next cycle.
X.P
XThe
X.I CATCHUP
Xparameter is used to determine how "stale" a job can be
Xand still be executed.
XA default value is set when the daemon is compiled.
XThe default can be overridden by defining a variable,
X.BR CATCHUP ,
Xin the daemon's environment when it is started.
XThis, in turn, can be overridden by using the
X.I catchup
Xargument when starting the daemon.
XIn all cases the value is the number of seconds older than "now"
Xthat a job can be and still be considerred for execution.
XThis is useful for running jobs that are already in the
Xspool directory when the daemon starts up.
XThe action taken by the daemon for jobs older than the
X.I CATCHUP
Xparameter, depends on the type of job:
XAT_JOBs are removed.
XBATCHJobs are not affected.
XCR_JOBs and CR_TAB jobs are rescheduled.
X.P
XThe maximum number of jobs that the daemon will run each cycle
Xis controlled by the
X.I MAXKIDS
Xparameter.
XA default value is set when the daemon is compiled.
XThe default can be overridden by defining a variable,
X.BR MAXKIDS ,
Xin the daemon's environment when it is started.
XThis, in turn, can be overridden by using the
X.I maxkids
Xargument when starting the daemon.
XIf you set this parameter too high,
Xthe kernal will impose a limit
Xbecause it will run out of processes.
XIn any case, undone jobs are pushed ahead to the next cycle.
X.P
X.IR Batch (1)
Xjobs are handled slightly differently.
XThe daemon only considers execution of, at most, one BATCHJob per cycle.
XWhereas all the other jobs that are due are possible candidates for execution.
X.bp
X.P
XIt is still possible, after the daemon has forked a process
Xto run a job, for the system to fail to execute it because the job's
Xowner is at his/her process limit.
XThe process will sleep for a few seconds and try up to
X.I MAXTRIES
Xnumber of times to execute the job.
XThis limit is compiled into the daemon.
X.SH NOTES
XThe user must have the
X.B LOGNAME
Xenvironment variable set
Xor the job will not be executed.
X.P
XThe daemon is usually compiled to include code which sets the
X.B TZ
Xenvironment variable.  If the user's job has
X.B TZ
Xdefined, then the daemon's value will replace it.
XThis kluges around a problem with daylight savings time
Xcalculation in the kernal and stardard library.
X.P
XThis daemon does not wakeup "on the minute" like some other programs.
XRather it wakes up at even intervals from when it is started.
XSo if you have other things happening "on the minute",
Xyou could start this daemon on the 30-second mark;
Xmaking the system load more even.
X.P
XThe
X.IR crontab (1)
Xcommand saves a copy of the user's crontab file in the
X.B crontabs
Xdirectory.
XThe daemon makes no use of the crontab files.
X.P
XAn additional /usr/lib/crontab
Xfile is provided for the superuser by the
X.IR smgr (1M)
Xprogram in the UNIX-PC.
XIt has nothing to do with this program.
X.P
XAT_JOB, BATCHJ, and CR_JOB jobs are executed using
X.I shell
X.BR -s ,
Xso the job file is in essence a shell script that is fed into
Xthe shell's stdin.
XCR_TAB jobs are executed with
X.I shell
X.B -c
X.IR command ,
Xso the job file is fed into the command's stdin.
X.P
XWhen the daemon starts-up, all job files are checked for consistency.
XAny bad jobs are removed.
XThe consistency check is also performed
Xwhen the job is executed.
X.bp
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), crontab(1), init(1M), ksh(1), mail(1), nice(1), ps(1), sh(1).
X.SH DIAGNOSTICS
XAll actions are recorded in the log file.
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 owner of a job is not informed if the job can't be executed
X(eg too old) and was removed my the daemon.
X.SH AUTHOR
XDonald Lashomb  4/89, 10/90
SHAR_EOF
$TOUCH -am 1018215890 cron.1 &&
chmod 0644 cron.1 ||
echo "restore of cron.1 failed"
set `wc -c cron.1`;Wc_c=$1
if test "$Wc_c" != "7418"; then
	echo original size 7418, current size $Wc_c
fi
fi
# ============= cron.h ==============
if test X"$1" != X"-c" -a -f 'cron.h'; then
	echo "File already exists: skipping 'cron.h'"
else
echo "x - extracting cron.h (Text)"
sed 's/^X//' << 'SHAR_EOF' > cron.h &&
X/*  header file for cron(1M) facility  */
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#define NUMJOBS		400
X#define GRAINULARITY	((unsigned)60)
X#define CATCHUP		0
X#define MAXKIDS		10
X#define MAXTRIES	5
X#define LINESIZ		256
X#define PATHSIZ		256
X
X/*
X *	PATHSIZ and LINESIZ should both be reasonably big
X *	enough.  Messages are sprintf'ed and crontab files
X *	are fgets'ed into LINESIZ'ed buffers.  PATHSIZ
X *	should be big enough to hold the longest pathname
X *	to any of this facility's files.  LINESIZ should
X *	never be less than PATHSIZ either.
X */
X
X#define ZEROSECS
X#define TZOVRIDE
X#define SET_LOGNAME
X
X#define MAXLATENESS	(10*GRAINULARITY)
X#define SECS		60
X
X/*	MAXLATENESS and SECS are used to reset the daemon's
X *	idea of the time if the system clock changes.
X *
X *	MAXLATENESS should be >= GRAINULARITY.
X *
X *	If GRAINULARITY is < 60
X *	then define SECS = GRAINULARITY
X *	else define SECS = 60
X *
X *	note- can't use preprocessor to enforce these because
X *	      of (unsigned) cast in define of GRAINULARITY
X */
X
X/* --------------------------------------------------------------
X   FIFOs:  daemon ----JNUM----> user
X	   daemon <---FNAM----- user
X   -------------------------------------------------------------- */
X
X#ifndef DEBUG
X
X#define ATSPOOL		"/usr/spool/cron/atjobs"
X#define CRSPOOL		"/usr/spool/cron/crontabs"
X#define	JNUM		"/usr/lib/cron/JNUMBS"
X#define FNAM		"/usr/lib/cron/FNAMES"
X#define LOG		"/usr/lib/cron/log"
X#define ATDENY		"/usr/lib/cron/at.deny"
X#define ATALLOW		"/usr/lib/cron/at.allow"
X#define CRDENY		"/usr/lib/cron/cron.deny"
X#define CRALLOW		"/usr/lib/cron/cron.allow"
X
X#else
X
X#define ATSPOOL		"/u/install/Filecabinet/src/cron/spool/atjobs"
X#define CRSPOOL		"/u/install/Filecabinet/src/cron/spool/crontabs"
X#define	JNUM		"/u/install/Filecabinet/src/cron/lib/JNUMBS"
X#define FNAM		"/u/install/Filecabinet/src/cron/lib/FNAMES"
X#define LOG		"/u/install/Filecabinet/src/cron/lib/log"
X#define ATDENY		"/u/install/Filecabinet/src/cron/lib/at.deny"
X#define ATALLOW		"/u/install/Filecabinet/src/cron/lib/at.allow"
X#define CRDENY		"/u/install/Filecabinet/src/cron/lib/cron.deny"
X#define CRALLOW		"/u/install/Filecabinet/src/cron/lib/cron.allow"
X
X#endif
X
X/* --------------------------------------------------------------
X   File permissions, owners, groups:
X
X   /				root	root	drwxr-xr-x
X   /etc				root	sys	drwxr-xr-x
X   /etc/daemons			root	sys	drwxr-x---
X   /etc/daemons/cron		root	bin	-rwx------	note1
X   /usr				root	users	drwxr-xr-x
X   /usr/local			root	users	drwxr-xr-x
X   /usr/local/bin		bin	bin	drwxr-xr-x
X   /usr/local/bin/at		cron	bin	-rws--x--x	\
X   /usr/local/bin/batch		cron	bin	-rws--x--x	 \ link
X   /usr/local/bin/cronjob	cron	bin	-rws--x--x	 /
X   /usr/local/bin/crtabj	cron	bin	-rws--x--x	/
X   /usr/local/bin/crontab	root	bin	-rws--x--x	note2
X   /usr/lib			root	bin	drwxr-xr-x
X   /usr/lib/cron		cron	bin	drwx------
X   /usr/lib/cron/*		cron	other	-rw-------
X   /usr/spool			root	bin	drwxr-xr-x
X   /usr/spool/cron		cron	bin	drwx------
X   /usr/spool/cron/atjobs	cron	bin	drwx------
X   /usr/spool/cron/atjobs/*	cron	<<any>>	-rw-------
X   /usr/spool/cron/crontabs/*	root	<<any>>	-rw-------
X
X   note1: fireup the daemon once from /etc/rc, must be uid=0(root)
X   note2: must be SUID root because of bug in setuid() system call
X
X   -------------------------------------------------------------- */
X
X#define UMASK		077
X#define J_PERM		0600
X#define T_PERM		0600
X#define FIFO_PERM	0600
X#define LOG_PERM	0600
X
SHAR_EOF
$TOUCH -am 1018163090 cron.h &&
chmod 0644 cron.h ||
echo "restore of cron.h failed"
set `wc -c cron.h`;Wc_c=$1
if test "$Wc_c" != "4499"; then
	echo original size 4499, current size $Wc_c
fi
fi
echo "End of part 2, continue with part 3"
exit 0

donlash@uncle.uucp (Donald Lashomb) (01/14/91)

---- 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