v23i029: A cron/crontab replacement, Part02/02

Rich Salz rsalz at bbn.com
Wed Oct 10 23:53:51 AEST 1990


Submitted-by: Paul A Vixie <vixie at vixie.sf.ca.us>
Posting-number: Volume 23, Issue 29
Archive-name: vixie-cron/part02

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 2 (of 3)."
# Contents:  cron.h crond.c crontab.5 crontab.c database.c entry.c
# Wrapped by vixie at volition.pa.dec.com on Wed Jul 18 00:32:48 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'cron.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'cron.h'\"
else
echo shar: Extracting \"'cron.h'\" \(7161 characters\)
sed "s/^X//" >'cron.h' <<'END_OF_FILE'
X/* cron.h - header for vixie's cron
X *
X * $Header: cron.h,v 2.1 90/07/18 00:23:47 vixie Exp $
X * $Source: /jove_u3/vixie/src/cron/RCS/cron.h,v $
X * $Revision: 2.1 $
X * $Log:	cron.h,v $
X * Revision 2.1  90/07/18  00:23:47  vixie
X * Baseline for 4.4BSD release
X * 
X * Revision 2.0  88/12/10  04:57:39  vixie
X * V2 Beta
X * 
X * Revision 1.2  88/11/29  13:05:46  vixie
X * seems to work on Ultrix 3.0 FT1
X * 
X * Revision 1.1  88/11/14  12:27:49  vixie
X * Initial revision
X * 
X * Revision 1.4  87/05/02  17:33:08  paul
X * baseline for mod.sources release
X *
X * vix 14jan87 [0 or 7 can be sunday; thanks, mwm at berkeley]
X * vix 30dec86 [written]
X */
X
X/* Copyright 1988,1990 by Paul Vixie
X * All rights reserved
X *
X * Distribute freely, except: don't remove my name from the source or
X * documentation (don't take credit for my work), mark your changes (don't
X * get me blamed for your possible bugs), don't alter or remove this
X * notice.  May be sold if buildable source is provided to buyer.  No
X * warrantee of any kind, express or implied, is included with this
X * software; use at your own risk, responsibility for damages (if any) to
X * anyone resulting from the use of this software rests entirely with the
X * user.
X *
X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
X * I'll try to keep a version up to date.  I can be reached as follows:
X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
X * paul at vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
X */
X
X#ifndef	_CRON_FLAG
X#define	_CRON_FLAG
X
X#include <stdio.h>
X#include <ctype.h>
X#include <bitstring.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X
X#include "config.h"
X
X	/* these are really immutable, and are
X	 *   defined for symbolic convenience only
X	 * TRUE, FALSE, and ERR must be distinct
X	 */
X#define TRUE		1
X#define FALSE		0
X	/* system calls return this on success */
X#define OK		0
X	/*   or this on error */
X#define ERR		(-1)
X
X	/* meaningless cookie for smart compilers that will pick their
X	 * own register variables; this makes the code neater.
X	 */
X#define	local		/**/
X
X	/* turn this on to get '-x' code */
X#ifndef DEBUGGING
X#define DEBUGGING	FALSE
X#endif
X
X#define READ_PIPE	0	/* which end of a pipe pair do you read? */
X#define WRITE_PIPE	1	/*   or write to? */
X#define STDIN		0	/* what is stdin's file descriptor? */
X#define STDOUT		1	/*   stdout's? */
X#define STDERR		2	/*   stderr's? */
X#define ERROR_EXIT	1	/* exit() with this will scare the shell */
X#define	OK_EXIT		0	/* exit() with this is considered 'normal' */
X#define	MAX_FNAME	100	/* max length of internally generated fn */
X#define	MAX_COMMAND	1000	/* max length of internally generated cmd */
X#define	MAX_ENVSTR	1000	/* max length of envvar=value\0 strings */
X#define	MAX_TEMPSTR	100	/* obvious */
X#define	MAX_UNAME	20	/* max length of username, should be overkill */
X#define	ROOT_UID	0	/* don't change this, it really must be root */
X#define	ROOT_USER	"root"	/* ditto */
X
X				/* NOTE: these correspond to DebugFlagNames,
X				 *	defined below.
X				 */
X#define	DEXT		0x0001	/* extend flag for other debug masks */
X#define	DSCH		0x0002	/* scheduling debug mask */
X#define	DPROC		0x0004	/* process control debug mask */
X#define	DPARS		0x0008	/* parsing debug mask */
X#define	DLOAD		0x0010	/* database loading debug mask */
X#define	DMISC		0x0020	/* misc debug mask */
X#define	DTEST		0x0040	/* test mode: don't execute any commands */
X
X				/* the code does not depend on any of vfork's
X				 * side-effects; it just uses it as a quick
X				 * fork-and-exec.
X				 */
X#if defined(BSD)
X# define VFORK		vfork
X#endif
X#if defined(ATT)
X# define VFORK		fork
X#endif
X
X#define	CRON_TAB(u)	"%s/%s", SPOOL_DIR, u
X#define	REG		register
X#define	PPC_NULL	((char **)NULL)
X
X#ifndef MAXHOSTNAMELEN
X#define MAXHOSTNAMELEN 64
X#endif
X
X#define	Skip_Blanks(c, f) \
X			while (c == '\t' || c == ' ') \
X				c = get_char(f);
X
X#define	Skip_Nonblanks(c, f) \
X			while (c!='\t' && c!=' ' && c!='\n' && c != EOF) \
X				c = get_char(f);
X
X#define	Skip_Line(c, f) \
X			do {c = get_char(f);} while (c != '\n' && c != EOF);
X
X#if DEBUGGING
X# define Debug(mask, message) \
X			if ( (DebugFlags & (mask) ) == (mask) ) \
X				printf message;
X#else /* !DEBUGGING */
X# define Debug(mask, message) \
X			;
X#endif /* DEBUGGING */
X
X#define	MkLower(ch)	(isupper(ch) ? tolower(ch) : ch)
X#define	MkUpper(ch)	(islower(ch) ? toupper(ch) : ch)
X#define	Set_LineNum(ln)	{Debug(DPARS|DEXT,("linenum=%d\n",ln)); \
X			 LineNumber = ln; \
X			}
X
X#define	FIRST_MINUTE	0
X#define	LAST_MINUTE	59
X#define	MINUTE_COUNT	(LAST_MINUTE - FIRST_MINUTE + 1)
X
X#define	FIRST_HOUR	0
X#define	LAST_HOUR	23
X#define	HOUR_COUNT	(LAST_HOUR - FIRST_HOUR + 1)
X
X#define	FIRST_DOM	1
X#define	LAST_DOM	31
X#define	DOM_COUNT	(LAST_DOM - FIRST_DOM + 1)
X
X#define	FIRST_MONTH	1
X#define	LAST_MONTH	12
X#define	MONTH_COUNT	(LAST_MONTH - FIRST_MONTH + 1)
X
X/* note on DOW: 0 and 7 are both Sunday, for compatibility reasons. */
X#define	FIRST_DOW	0
X#define	LAST_DOW	7
X#define	DOW_COUNT	(LAST_DOW - FIRST_DOW + 1)
X
X			/* each user's crontab will be held as a list of
X			 * the following structure.
X			 *
X			 * These are the cron commands.
X			 */
X
typedef	struct	_entry
X	{
X		struct _entry	*next;
X		char		*cmd;
X		bitstr_t	bit_decl(minute, MINUTE_COUNT);
X		bitstr_t	bit_decl(hour,   HOUR_COUNT);
X		bitstr_t	bit_decl(dom,    DOM_COUNT);
X		bitstr_t	bit_decl(month,  MONTH_COUNT);
X		bitstr_t	bit_decl(dow,    DOW_COUNT);
X		int		flags;
X# define	DOM_STAR	0x1
X# define	DOW_STAR	0x2
X# define	WHEN_REBOOT	0x4
X	}
X	entry;
X
X			/* the crontab database will be a list of the
X			 * following structure, one element per user.
X			 *
X			 * These are the crontabs.
X			 */
X
typedef	struct	_user
X	{
X		struct _user	*next, *prev;	/* links */
X		int		uid;		/* uid from passwd file */
X		int		gid;		/* gid from passwd file */
X		char		**envp;		/* environ for commands */
X		time_t		mtime;		/* last modtime of crontab */
X		entry		*crontab;	/* this person's crontab */
X	}
X	user;
X
typedef	struct	_cron_db
X	{
X		user		*head, *tail;	/* links */
X		time_t		mtime;		/* last modtime on spooldir */
X	}
X	cron_db;
X
X				/* in the C tradition, we only create
X				 * variables for the main program, just
X				 * extern them elsewhere.
X				 */
X
X#ifdef MAIN_PROGRAM
X
X# if !defined(LINT) && !defined(lint)
X		static char *copyright[] = {
X			"@(#) Copyright (C) 1988, 1989, 1990 by Paul Vixie",
X			"@(#) All rights reserved"
X		};
X# endif
X
X		char *MonthNames[] = {
X			"Jan", "Feb", "Mar", "Apr", "May", "Jun",
X			"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
X			NULL};
X		char *DowNames[] = {
X			"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
X			NULL};
X		char *ProgramName;
X		int LineNumber;
X		time_t TargetTime;
X
X# if DEBUGGING
X		int DebugFlags;
X		char *DebugFlagNames[] = {	/* sync with #defines */
X			"ext", "sch", "proc", "pars", "load", "misc", "test",
X			NULL};	/* NULL must be last element */
X# endif /* DEBUGGING */
X
X#else /* !MAIN_PROGRAM */
X
X		extern char	*MonthNames[];
X		extern char	*DowNames[];
X		extern char	*ProgramName;
X		extern int	LineNumber;
X		extern time_t	TargetTime;
X# if DEBUGGING
X		extern int	DebugFlags;
X		extern char	*DebugFlagNames[];
X# endif /* DEBUGGING */
X#endif /* MAIN_PROGRAM */
X
X
X#endif	/* _CRON_FLAG */
END_OF_FILE
if test 7161 -ne `wc -c <'cron.h'`; then
    echo shar: \"'cron.h'\" unpacked with wrong size!
fi
# end of 'cron.h'
fi
if test -f 'crond.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'crond.c'\"
else
echo shar: Extracting \"'crond.c'\" \(7067 characters\)
sed "s/^X//" >'crond.c' <<'END_OF_FILE'
X#if !defined(lint) && !defined(LINT)
static char rcsid[] = "$Header: crond.c,v 2.1 90/07/18 00:23:53 vixie Exp $";
X#endif
X
X/* Copyright 1988,1990 by Paul Vixie
X * All rights reserved
X *
X * Distribute freely, except: don't remove my name from the source or
X * documentation (don't take credit for my work), mark your changes (don't
X * get me blamed for your possible bugs), don't alter or remove this
X * notice.  May be sold if buildable source is provided to buyer.  No
X * warrantee of any kind, express or implied, is included with this
X * software; use at your own risk, responsibility for damages (if any) to
X * anyone resulting from the use of this software rests entirely with the
X * user.
X *
X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
X * I'll try to keep a version up to date.  I can be reached as follows:
X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
X * paul at vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
X */
X
X
X#define	MAIN_PROGRAM
X
X
X#include "cron.h"
X#include <sys/time.h>
X#include <sys/signal.h>
X#include <sys/types.h>
X#if defined(BSD)
X# include <sys/wait.h>
X# include <sys/resource.h>
X#endif /*BSD*/
X
extern int	fprintf(), fork(), unlink();
extern time_t	time();
extern void	exit();
extern unsigned	sleep();
X
void
usage()
X{
X	(void) fprintf(stderr, "usage:  %s [-x debugflag[,...]]\n", ProgramName);
X	(void) exit(ERROR_EXIT);
X}
X
X
int
main(argc, argv)
X	int	argc;
X	char	*argv[];
X{
X	extern void	set_cron_uid(), be_different(), load_database(),
X			set_cron_cwd(), open_logfile();
X
X	static void	cron_tick(), cron_sleep(), cron_sync(),
X			sigchld_handler(), parse_args(), run_reboot_jobs();
X
X	auto cron_db	database;
X
X	ProgramName = argv[0];
X
X#if defined(BSD)
X	setlinebuf(stdout);
X	setlinebuf(stderr);
X#endif
X
X	parse_args(argc, argv);
X
X# if DEBUGGING
X	/* if there are no debug flags turned on, fork as a daemon should.
X	 */
X	if (DebugFlags)
X	{
X		(void) fprintf(stderr, "[%d] crond started\n", getpid());
X	}
X	else
X# endif /*DEBUGGING*/
X	{
X		switch (fork())
X		{
X		case -1:
X			log_it("CROND",getpid(),"DEATH","can't fork");
X			exit(0);
X			break;
X		case 0:
X			/* child process */
X			be_different();
X			break;
X		default:
X			/* parent process should just die */
X			_exit(0);
X		}
X	}
X
X#if defined(BSD)
X	(void) signal(SIGCHLD, sigchld_handler);
X#endif /*BSD*/
X
X#if defined(ATT)
X	(void) signal(SIGCLD, SIG_IGN);
X#endif /*ATT*/
X
X	acquire_daemonlock();
X	set_cron_uid();
X	set_cron_cwd();
X	database.head = NULL;
X	database.tail = NULL;
X	database.mtime = (time_t) 0;
X	load_database(&database);
X	run_reboot_jobs(&database);
X	cron_sync();
X	while (TRUE)
X	{
X# if DEBUGGING
X		if (!(DebugFlags & DTEST))
X# endif /*DEBUGGING*/
X			cron_sleep();
X
X		load_database(&database);
X
X		/* do this iteration
X		 */
X		cron_tick(&database);
X
X		/* sleep 1 minute
X		 */
X		TargetTime += 60;
X	}
X}
X
X
static void
run_reboot_jobs(db)
X	cron_db *db;
X{
X	extern void		job_add();
X	extern int		job_runqueue();
X	register user		*u;
X	register entry		*e;
X
X	for (u = db->head;  u != NULL;  u = u->next) {
X		for (e = u->crontab;  e != NULL;  e = e->next) {
X			if (e->flags & WHEN_REBOOT) {
X				job_add(e->cmd, u);
X			}
X		}
X	}
X	(void) job_runqueue();
X}
X
X
static void
cron_tick(db)
X	cron_db	*db;
X{
X	extern void		job_add();
X	extern char		*env_get();
X	extern struct tm	*localtime();
X 	register struct tm	*tm = localtime(&TargetTime);
X	local int		minute, hour, dom, month, dow;
X	register user		*u;
X	register entry		*e;
X
X	/* make 0-based values out of these so we can use them as indicies
X	 */
X	minute = tm->tm_min -FIRST_MINUTE;
X	hour = tm->tm_hour -FIRST_HOUR;
X	dom = tm->tm_mday -FIRST_DOM;
X	month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
X	dow = tm->tm_wday -FIRST_DOW;
X
X	Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d)\n",
X		getpid(), minute, hour, dom, month, dow))
X
X	/* the dom/dow situation is odd.  '* * 1,15 * Sun' will run on the
X	 * first and fifteenth AND every Sunday;  '* * * * Sun' will run *only*
X	 * on Sundays;  '* * 1,15 * *' will run *only* the 1st and 15th.  this
X	 * is why we keep 'e->dow_star' and 'e->dom_star'.  yes, it's bizarre.
X	 * like many bizarre things, it's the standard.
X	 */
X	for (u = db->head;  u != NULL;  u = u->next) {
X		Debug(DSCH|DEXT, ("user [%s:%d:%d:...]\n",
X			env_get(USERENV,u->envp), u->uid, u->gid))
X		for (e = u->crontab;  e != NULL;  e = e->next) {
X			Debug(DSCH|DEXT, ("entry [%s]\n", e->cmd))
X			if (bit_test(e->minute, minute)
X			 && bit_test(e->hour, hour)
X			 && bit_test(e->month, month)
X			 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
X			      ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
X			      : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
X			    )
X			   ) {
X				job_add(e->cmd, u);
X			}
X		}
X	}
X}
X
X
X/* the task here is to figure out how long it's going to be until :00 of the
X * following minute and initialize TargetTime to this value.  TargetTime
X * will subsequently slide 60 seconds at a time, with correction applied
X * implicitly in cron_sleep().  it would be nice to let crond execute in
X * the "current minute" before going to sleep, but by restarting cron you
X * could then get it to execute a given minute's jobs more than once.
X * instead we have the chance of missing a minute's jobs completely, but
X * that's something sysadmin's know to expect what with crashing computers..
X */
static void
cron_sync()
X{
X	extern struct tm	*localtime();
X 	register struct tm	*tm;
X
X	TargetTime = time((time_t*)0);
X	tm = localtime(&TargetTime);
X	TargetTime += (60 - tm->tm_sec);
X}
X
X
static void
cron_sleep()
X{
X	extern void	do_command();
X	extern int	job_runqueue();
X	register int	seconds_to_wait;
X
X	do {
X		seconds_to_wait = (int) (TargetTime - time((time_t*)0));
X		Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
X			getpid(), TargetTime, seconds_to_wait))
X
X		/* if we intend to sleep, this means that it's finally
X		 * time to empty the job queue (execute it).
X		 *
X		 * if we run any jobs, we'll probably screw up our timing,
X		 * so go recompute.
X		 *
X		 * note that we depend here on the left-to-right nature
X		 * of &&, and the short-circuiting.
X		 */
X	} while (seconds_to_wait > 0 && job_runqueue());
X
X	if (seconds_to_wait > 0)
X	{
X		Debug(DSCH, ("[%d] sleeping for %d seconds\n",
X			getpid(), seconds_to_wait))
X		(void) sleep((unsigned int) seconds_to_wait);
X	}
X}
X
X
X#if defined(BSD)
static void
sigchld_handler()
X{
X	union wait	waiter;
X	int		pid;
X
X	for (;;)
X	{
X		pid = wait3(&waiter, WNOHANG, (struct rusage *)0);
X		switch (pid)
X		{
X		case -1:
X			Debug(DPROC,
X				("[%d] sigchld...no children\n", getpid()))
X			return;
X		case 0:
X			Debug(DPROC,
X				("[%d] sigchld...no dead kids\n", getpid()))
X			return;
X		default:
X			Debug(DPROC,
X				("[%d] sigchld...pid #%d died, stat=%d\n",
X				getpid(), pid, waiter.w_status))
X		}
X	}
X}
X#endif /*BSD*/
X
X
static void
parse_args(argc, argv)
X	int	argc;
X	char	*argv[];
X{
X	extern	int	optind, getopt();
X	extern	void	usage();
X	extern	char	*optarg;
X
X	int	argch;
X
X	while (EOF != (argch = getopt(argc, argv, "x:")))
X	{
X		switch (argch)
X		{
X		default:
X			usage();
X		case 'x':
X			if (!set_debug_flags(optarg))
X				usage();
X			break;
X		}
X	}
X}
END_OF_FILE
if test 7067 -ne `wc -c <'crond.c'`; then
    echo shar: \"'crond.c'\" unpacked with wrong size!
fi
# end of 'crond.c'
fi
if test -f 'crontab.5' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'crontab.5'\"
else
echo shar: Extracting \"'crontab.5'\" \(7270 characters\)
sed "s/^X//" >'crontab.5' <<'END_OF_FILE'
X.\" $Header: crontab.5,v 2.1 90/07/18 00:23:50 vixie Exp $
X.\" 
X.\"/* Copyright 1988,1990 by Paul Vixie
X.\" * All rights reserved
X.\" *
X.\" * Distribute freely, except: don't remove my name from the source or
X.\" * documentation (don't take credit for my work), mark your changes (don't
X.\" * get me blamed for your possible bugs), don't alter or remove this
X.\" * notice.  May be sold if buildable source is provided to buyer.  No
X.\" * warrantee of any kind, express or implied, is included with this
X.\" * software; use at your own risk, responsibility for damages (if any) to
X.\" * anyone resulting from the use of this software rests entirely with the
X.\" * user.
X.\" *
X.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
X.\" * I'll try to keep a version up to date.  I can be reached as follows:
X.\" * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
X.\" * paul at vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
X.\" */
X.TH CRONTAB 5 "15 January 1990"
X.UC 4
X.SH NAME
crontab \- tables for driving cron
X.SH DESCRIPTION
A
X.I crontab
file contains instructions to the
X.IR crond (8)
daemon of the general form: ``run this command at this time on this date''.
XEach user has their own crontab, and commands in any given crontab will be
executed as the user who owns the crontab.  Uucp and News will usually have
their own crontabs, eliminating the need for explicitly running
X.IR su (1)
as part of a cron command.
X.PP
Blank lines and leading spaces and tabs are ignored.  Lines whose first
non-space character is a pound-sign (#) are comments, and are ignored.
Note that comments are not allowed on the same line as cron commands, since
they will be taken to be part of the command.  Similarly, comments are not
allowed on the same line as environment variable settings.
X.PP
An active line in a crontab will be either an environment setting or a cron
command.  An environment setting is of the form,
X.PP
X    name = value
X.PP
where the spaces around the equal-sign (=) are optional, and any subsequent
non-leading spaces in
X.I value
will be part of the value assigned to
X.IR name .
The
X.I value
string may be placed in quotes (single or double, but matching) to preserve
leading or trailing blanks.
X.PP
Several environment variables are set up
automatically by the
X.IR crond (8)
daemon from the /etc/passwd line of the crontab's owner: USER, HOME, and SHELL.
HOME and SHELL may be overridden by settings in the crontab; USER may not.
X.PP
X(Note: for UUCP, always set SHELL=/bin/sh, or
X.IR crond (8)
will cheerfully try to execute your commands using /usr/lib/uucp/uucico.)
X.PP
X(Another note: the USER variable is sometimes called LOGNAME or worse on
System V... on these systems, LOGNAME will be set rather than USER.)
X.PP
In addition to USER, HOME, and SHELL,
X.IR crond (8)
will look at MAILTO if it has any reason to send mail as a result of running
commands in ``this'' crontab.  If MAILTO is defined (and non-empty), mail is
sent to the user so named.  If MAILTO is defined but empty (MAILTO=""), no
mail will be sent.  Otherwise mail is sent to the owner of the crontab.  This
option is useful if you decide on /bin/mail instead of /usr/lib/sendmail as
your mailer when you install cron -- /bin/mail doesn't do aliasing, and UUCP
usually doesn't read its mail.
X.PP
The format of a cron command is very much the V7 standard, with a number of
upward-compatible extensions.  Each line has five time and date fields,
followed by a command.  Commands are executed by
X.IR crond (8)
when the minute, hour, and month of year fields match the current time,
X.I and
when at least one of the two day fields (day of month, or day of week)
match the current time (see ``Note'' below).
X.IR crond (8)
examines cron entries once every minute.
The time and date fields are:
X.IP
X.ta 1.5i
field	allowed values
X.br
X-----	--------------
X.br
minute	0-59
X.br
hour	0-23
X.br
day of month	0-31
X.br
month	0-12 (or names, see below)
X.br
day of week	0-7 (0 or 7 is Sun, or use names)
X.br
X.PP
A field may be an asterisk (*), which always matches the
current time.
X.PP
Ranges of numbers are allowed.  Ranges are two numbers separated
with a hyphen.  The specified range is inclusive.  For example,
X8-11 for an ``hours'' entry specifies execution at hours 8, 9, 10
and 11.
X.PP
Lists are allowed.  A list is a set of numbers (or ranges)
separated by commas.  Examples: ``1,2,5,9'', ``0-4,8-12''.
X.PP
Step values can be used in conjunction with ranges.  Following
a range with ``/<number>'' specifies skips of the number's value
through the range.  For example, ``0-23/2'' can be used in the hours
field to specify command execution every other hour (the alternative
in the V7 standard is ``0,2,4,6,8,10,12,14,16,18,20,22'').
X.PP
Names can also be used for the ``month'' and ``day of week''
fields.  Use the first three letters of the particular
day or month (case doesn't matter).  Ranges or
lists of names are not allowed.
X.PP
The ``sixth'' field (the rest of the line) specifies the command to be
run.
The entire command portion of the line, up to a newline or %
character, will be executed by the user's login shell or by the shell
specified in the SHELL variable of the cronfile.
Percent-signs (%) in the command, unless escaped with backslash
X(\\), will be changed into newline characters, and all data
after the first % will be sent to the command as standard
input.
X.PP
Note: The day of a command's execution can be specified by two
fields \(em day of month, and day of week.  If both fields are
restricted (ie, aren't *), the command will be run when
X.I either
field matches the current time.  For example,
X.br
X``30 4 1,15 * 5''
would cause a command to be run at 4:30 am on the 1st and 15th of each
month, plus every Friday.
X.SH EXAMPLE CRON FILE
X.nf
X
X# use /bin/sh to run commands, no matter what /etc/passwd says
SHELL=/bin/sh
X# mail any output to `paul', no matter whose crontab this is
MAILTO=paul
X#
X# run five minutes after midnight, every day
X5 0 * * *       $HOME/bin/daily.job >> $HOME/tmp/out 2>&1
X# run at 2:15pm on the first of every month -- output mailed to paul
X15 14 1 * *     $HOME/bin/monthly
X# run at 10 pm on weekdays, annoy Joe
X0 22 * * 1-5	mail -s "It's 10pm" joe%Joe,%%Where are your kids?%
X23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday"
X5 4 * * sun     echo "run at 5 after 4 every sunday"
X.fi
X.SH SEE ALSO
crond(8), crontab(1)
X.SH EXTENSIONS
When specifying day of week, both day 0 and day 7 will be considered Sunday.
BSD and ATT seem to disagree about this.
X.PP
Lists and ranges are allowed to co-exist in the same field.  "1-3,7-9" would
be rejected by ATT or BSD cron -- they want to see "1-3" or "7,8,9" ONLY.
X.PP
Ranges can include "steps", so "1-9/2" is the same as "1,3,5,7,9".
X.PP
Names of months or days of the week can be specified by name.
X.PP
XEnvironment variables can be set in the crontab.  In BSD or ATT, the
environment handed to child processes is basically the one from /etc/rc.
X.PP
Command output is mailed to the crontab owner (BSD can't do this), can be
mailed to a person other than the crontab owner (SysV can't do this), or the
feature can be turned off and no mail will be sent at all (SysV can't do this
either).
X.SH AUTHOR
X.nf
Paul Vixie, paul at vixie.sf.ca.us
END_OF_FILE
if test 7270 -ne `wc -c <'crontab.5'`; then
    echo shar: \"'crontab.5'\" unpacked with wrong size!
fi
# end of 'crontab.5'
fi
if test -f 'crontab.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'crontab.c'\"
else
echo shar: Extracting \"'crontab.c'\" \(8715 characters\)
sed "s/^X//" >'crontab.c' <<'END_OF_FILE'
X#if !defined(lint) && !defined(LINT)
static char rcsid[] = "$Header: crontab.c,v 2.2 90/07/18 00:23:56 vixie Exp $";
X#endif
X
X/* Revision 1.5  87/05/02  17:33:22  paul
X * pokecron?  (RCS file has the rest of the log)
X * 
X * Revision 1.5  87/05/02  17:33:22  paul
X * baseline for mod.sources release
X * 
X * Revision 1.4  87/03/31  13:11:48  paul
X * I won't say that rs at mirror gave me this idea but crontab uses getopt() now
X * 
X * Revision 1.3  87/03/30  23:43:48  paul
X * another suggestion from rs at mirror:
X *   use getpwuid(getuid)->pw_name instead of getenv("USER")
X *   this is a boost to security...
X * 
X * Revision 1.2  87/02/11  17:40:12  paul
X * changed command syntax to allow append and replace instead of append as
X * default and no replace at all.
X * 
X * Revision 1.1  87/01/26  23:49:06  paul
X * Initial revision
X */
X
X/* Copyright 1988,1990 by Paul Vixie
X * All rights reserved
X *
X * Distribute freely, except: don't remove my name from the source or
X * documentation (don't take credit for my work), mark your changes (don't
X * get me blamed for your possible bugs), don't alter or remove this
X * notice.  May be sold if buildable source is provided to buyer.  No
X * warrantee of any kind, express or implied, is included with this
X * software; use at your own risk, responsibility for damages (if any) to
X * anyone resulting from the use of this software rests entirely with the
X * user.
X *
X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
X * I'll try to keep a version up to date.  I can be reached as follows:
X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
X * paul at vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
X */
X
X
X#define	MAIN_PROGRAM
X
X
X#include "cron.h"
X#include <pwd.h>
X#include <errno.h>
X#include <sys/file.h>
X#if defined(BSD)
X# include <sys/time.h>
X#endif  /*BSD*/
X
extern	char	*sprintf();
X
X
static int	Pid;
static char	User[MAX_UNAME], RealUser[MAX_UNAME];
static char	Filename[MAX_FNAME];
static FILE	*NewCrontab;
static int	CheckErrorCount;
static enum	{opt_unknown, opt_list, opt_delete, opt_replace}
X		Option;
X
extern void	log_it();
X
X#if DEBUGGING
static char	*Options[] = {"???", "list", "delete", "replace"};
X#endif
X
static void
usage()
X{
X	fprintf(stderr, "usage:  %s [-u user] ...\n", ProgramName);
X	fprintf(stderr, " ... -l         (list user's crontab)\n");
X	fprintf(stderr, " ... -d         (delete user's crontab)\n");
X	fprintf(stderr, " ... -r file    (replace user's crontab)\n");
X	exit(ERROR_EXIT);
X}
X
X
main(argc, argv)
X	int	argc;
X	char	*argv[];
X{
X	void	parse_args(), set_cron_uid(), set_cron_cwd(),
X		list_cmd(), delete_cmd(), replace_cmd();
X
X	Pid = getpid();
X	ProgramName = argv[0];
X#if defined(BSD)
X	setlinebuf(stderr);
X#endif
X	parse_args(argc, argv);		/* sets many globals, opens a file */
X	set_cron_uid();
X	set_cron_cwd();
X	if (!allowed(User)) {
X		fprintf(stderr,
X			"You (%s) are not allowed to use this program (%s)\n",
X			User, ProgramName);
X		fprintf(stderr, "See crontab(1) for more information\n");
X		log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
X		exit(ERROR_EXIT);
X	}
X	switch (Option)
X	{
X	case opt_list:		list_cmd();
X				break;
X	case opt_delete:	delete_cmd();
X				break;
X	case opt_replace:	replace_cmd();
X				break;
X	}
X}
X	
X
static void
parse_args(argc, argv)
X	int	argc;
X	char	*argv[];
X{
X	void		usage();
X	char		*getenv(), *strcpy();
X	int		getuid();
X	struct passwd	*getpwnam();
X	extern int	getopt(), optind;
X	extern char	*optarg;
X
X	struct passwd	*pw;
X	int		argch;
X
X	if (!(pw = getpwuid(getuid())))
X	{
X		fprintf(stderr, "%s: your UID isn't in the passwd file.\n",
X			ProgramName);
X		fprintf(stderr, "bailing out.\n");
X		exit(ERROR_EXIT);
X	}
X	strcpy(User, pw->pw_name);
X	strcpy(RealUser, User);
X	Filename[0] = '\0';
X	Option = opt_unknown;
X	while (EOF != (argch = getopt(argc, argv, "u:ldr:x:")))
X	{
X		switch (argch)
X		{
X		case 'x':
X			if (!set_debug_flags(optarg))
X				usage();
X			break;
X		case 'u':
X			if (getuid() != ROOT_UID)
X			{
X				fprintf(stderr,
X					"must be privileged to use -u\n");
X				exit(ERROR_EXIT);
X			}
X			if ((struct passwd *)NULL == getpwnam(optarg))
X			{
X				fprintf(stderr, "%s:  user `%s' unknown\n",
X					ProgramName, optarg);
X				exit(ERROR_EXIT);
X			}
X			(void) strcpy(User, optarg);
X			break;
X		case 'l':
X			if (Option != opt_unknown)
X				usage();
X			Option = opt_list;
X			break;
X		case 'd':
X			if (Option != opt_unknown)
X				usage();
X			Option = opt_delete;
X			break;
X		case 'r':
X			if (Option != opt_unknown)
X				usage();
X			Option = opt_replace;
X			(void) strcpy(Filename, optarg);
X			break;
X		default:
X			usage();
X		}
X	}
X
X	endpwent();
X
X	if (Option == opt_unknown || argv[optind] != NULL)
X		usage();
X
X	if (Option == opt_replace) {
X		if (!Filename[0]) {
X			/* getopt(3) says this can't be true
X			 * but I'm paranoid today.
X			 */
X			fprintf(stderr, "filename must be given for -a or -r\n");
X			usage();
X		}
X		/* we have to open the file here because we're going to
X		 * chdir(2) into /var/cron before we get around to
X		 * reading the file.
X		 */
X		if (!strcmp(Filename, "-")) {
X			NewCrontab = stdin;
X		} else {
X			if (!(NewCrontab = fopen(Filename, "r"))) {
X				perror(Filename);
X				exit(ERROR_EXIT);
X			}
X		}
X	}
X
X	Debug(DMISC, ("user=%s, file=%s, option=%s\n",
X					User, Filename, Options[(int)Option]))
X}
X
X
static void
list_cmd()
X{
X	extern	errno;
X	char	n[MAX_FNAME];
X	FILE	*f;
X	int	ch;
X
X	log_it(RealUser, Pid, "LIST", User);
X	(void) sprintf(n, CRON_TAB(User));
X	if (!(f = fopen(n, "r")))
X	{
X		if (errno == ENOENT)
X			fprintf(stderr, "no crontab for %s\n", User);
X		else
X			perror(n);
X		exit(ERROR_EXIT);
X	}
X
X	/* file is open. copy to stdout, close.
X	 */
X	Set_LineNum(1)
X	while (EOF != (ch = get_char(f)))
X		putchar(ch);
X	fclose(f);
X}
X
X
static void
delete_cmd()
X{
X	extern	errno;
X	int	unlink();
X	void	poke_daemon();
X	char	n[MAX_FNAME];
X
X	log_it(RealUser, Pid, "DELETE", User);
X	(void) sprintf(n, CRON_TAB(User));
X	if (unlink(n))
X	{
X		if (errno == ENOENT)
X			fprintf(stderr, "no crontab for %s\n", User);
X		else
X			perror(n);
X		exit(ERROR_EXIT);
X	}
X	poke_daemon();
X}
X
X
static void
check_error(msg)
X	char	*msg;
X{
X	CheckErrorCount += 1;
X	fprintf(stderr, "\"%s\", line %d: %s\n", Filename, LineNumber, msg);
X}
X
X
static void
replace_cmd()
X{
X	char	*sprintf();
X	entry	*load_entry();
X	int	load_env();
X	int	unlink();
X	void	free_entry();
X	void	check_error();
X	void	poke_daemon();
X	extern	errno;
X
X	char	n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
X	FILE	*tmp;
X	int	ch;
X	entry	*e;
X	int	status;
X	time_t	now = time(NULL);
X
X	(void) sprintf(n, "tmp.%d", Pid);
X	(void) sprintf(tn, CRON_TAB(n));
X	if (!(tmp = fopen(tn, "w"))) {
X		perror(tn);
X		exit(ERROR_EXIT);
X	}
X
X	/* write a signature at the top of the file.  for brian.
X	 */
X	fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now));
X	fprintf(tmp, "# (Cron version -- %s)\n", rcsid);
X
X	/* copy the crontab to the tmp
X	 */
X	Set_LineNum(1)
X	while (EOF != (ch = get_char(NewCrontab)))
X		putc(ch, tmp);
X	fclose(NewCrontab);
X	fflush(tmp);  rewind(tmp);
X
X	if (ferror(tmp)) {
X		fprintf("%s: error while writing new crontab to %s\n",
X			ProgramName, tn);
X		fclose(tmp);  unlink(tn);
X		exit(ERROR_EXIT);
X	}
X
X	/* check the syntax of the file being installed.
X	 */
X
X	/* BUG: was reporting errors after the EOF if there were any errors
X	 * in the file proper -- kludged it by stopping after first error.
X	 *		vix 31mar87
X	 */
X	CheckErrorCount = 0;
X	while (!CheckErrorCount && (status = load_env(envstr, tmp)) >= OK)
X	{
X		if (status == FALSE)
X		{
X			if (NULL != (e = load_entry(NewCrontab, check_error)))
X				free((char *) e);
X		}
X	}
X
X	if (CheckErrorCount != 0)
X	{
X		fprintf(stderr, "errors in crontab file, can't install.\n");
X		fclose(tmp);  unlink(tn);
X		exit(ERROR_EXIT);
X	}
X
X	if (fchown(fileno(tmp), ROOT_UID, -1) < OK)
X	{
X		perror("chown");
X		fclose(tmp);  unlink(tn);
X		exit(ERROR_EXIT);
X	}
X
X	if (fchmod(fileno(tmp), 0600) < OK)
X	{
X		perror("chown");
X		fclose(tmp);  unlink(tn);
X		exit(ERROR_EXIT);
X	}
X
X	if (fclose(tmp) == EOF) {
X		perror("fclose");
X		unlink(tn);
X		exit(ERROR_EXIT);
X	}
X
X	(void) sprintf(n, CRON_TAB(User));
X	if (rename(tn, n))
X	{
X		fprintf(stderr, "%s: error renaming %s to %s\n",
X			ProgramName, tn, n);
X		perror("rename");
X		unlink(tn);
X		exit(ERROR_EXIT);
X	}
X	log_it(RealUser, Pid, "REPLACE", User);
X
X	poke_daemon();
X}
X
X
static void
poke_daemon()
X{
X#if defined(BSD)
X	struct timeval tvs[2];
X	struct timezone tz;
X
X	(void) gettimeofday(&tvs[0], &tz);
X	tvs[1] = tvs[0];
X	if (utimes(SPOOL_DIR, tvs) < OK)
X	{
X		fprintf(stderr, "crontab: can't update mtime on spooldir\n");
X		perror(SPOOL_DIR);
X		return;
X	}
X#endif  /*BSD*/
X#if defined(ATT)
X	if (utime(SPOOL_DIR, NULL) < OK)
X	{
X		fprintf(stderr, "crontab: can't update mtime on spooldir\n");
X		perror(SPOOL_DIR);
X		return;
X	}
X#endif  /*ATT*/
X}
END_OF_FILE
if test 8715 -ne `wc -c <'crontab.c'`; then
    echo shar: \"'crontab.c'\" unpacked with wrong size!
fi
# end of 'crontab.c'
fi
if test -f 'database.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'database.c'\"
else
echo shar: Extracting \"'database.c'\" \(6768 characters\)
sed "s/^X//" >'database.c' <<'END_OF_FILE'
X#if !defined(lint) && !defined(LINT)
static char rcsid[] = "$Header: database.c,v 2.1 90/07/18 00:23:51 vixie Exp $";
X#endif
X
X/* vix 26jan87 [RCS has the log]
X */
X
X/* Copyright 1988,1990 by Paul Vixie
X * All rights reserved
X *
X * Distribute freely, except: don't remove my name from the source or
X * documentation (don't take credit for my work), mark your changes (don't
X * get me blamed for your possible bugs), don't alter or remove this
X * notice.  May be sold if buildable source is provided to buyer.  No
X * warrantee of any kind, express or implied, is included with this
X * software; use at your own risk, responsibility for damages (if any) to
X * anyone resulting from the use of this software rests entirely with the
X * user.
X *
X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
X * I'll try to keep a version up to date.  I can be reached as follows:
X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
X * paul at vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
X */
X
X
X#include "cron.h"
X#include <pwd.h>
X#if defined(BSD)
X# include <sys/file.h>
X# include <sys/dir.h>
X#endif
X#if defined(ATT)
X# include <sys/file.h>
X# include <ndir.h>
X# include <fcntl.h>
X#endif
X
X
extern void	perror(), exit();
X
X
void
load_database(old_db)
X	cron_db		*old_db;
X{
X	extern void	link_user(), unlink_user(), free_user();
X	extern user	*load_user(), *find_user();
X	extern char	*env_get();
X
X	static DIR	*dir = NULL;
X
X	struct stat	statbuf;
X	struct direct	*dp;
X	cron_db		new_db;
X	user		*u;
X
X	Debug(DLOAD, ("[%d] load_database()\n", getpid()))
X
X	/* before we start loading any data, do a stat on SPOOL_DIR
X	 * so that if anything changes as of this moment (i.e., before we've
X	 * cached any of the database), we'll see the changes next time.
X	 */
X	if (stat(SPOOL_DIR, &statbuf) < OK)
X	{
X		log_it("CROND", getpid(), "STAT FAILED", SPOOL_DIR);
X		(void) exit(ERROR_EXIT);
X	}
X
X	/* if spooldir's mtime has not changed, we don't need to fiddle with
X	 * the database.  Note that if /etc/passwd changes (like, someone's
X	 * UID/GID/HOME/SHELL, we won't see it.  Maybe we should
X	 * keep an mtime for the passwd file?  HINT
X	 *
X	 * Note that old_db->mtime is initialized to 0 in main(), and
X	 * so is guaranteed to be different than the stat() mtime the first
X	 * time this function is called.
X	 */
X	if (old_db->mtime == statbuf.st_mtime)
X	{
X		Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n",
X			getpid()))
X		return;
X	}
X
X	/* make sure the dir is open.  only happens the first time, since
X	 * the DIR is static and we don't close it.  Rewind the dir.
X	 */
X	if (dir == NULL)
X	{
X		if (!(dir = opendir(SPOOL_DIR)))
X		{
X			log_it("CROND", getpid(), "OPENDIR FAILED", SPOOL_DIR);
X			(void) exit(ERROR_EXIT);
X		}
X	}
X	(void) rewinddir(dir);
X
X	/* something's different.  make a new database, moving unchanged
X	 * elements from the old database, reloading elements that have
X	 * actually changed.  Whatever is left in the old database when
X	 * we're done is chaff -- crontabs that disappeared.
X	 */
X	new_db.mtime = statbuf.st_mtime;
X	new_db.head = new_db.tail = NULL;
X
X	while (NULL != (dp = readdir(dir)))
X	{
X		extern struct passwd	*getpwnam();
X		struct passwd		*pw;
X		int			crontab_fd;
X		char			fname[MAXNAMLEN+1],
X					tabname[MAXNAMLEN+1];
X
X		(void) strncpy(fname, dp->d_name, (int) dp->d_namlen);
X		fname[dp->d_namlen] = '\0';
X
X		/* avoid file names beginning with ".".  this is good
X		 * because we would otherwise waste two guaranteed calls
X		 * to getpwnam() for . and .., and also because user names
X		 * starting with a period are just too nasty to consider.
X		 */
X		if (fname[0] == '.')
X			goto next_crontab;
X
X		if (NULL == (pw = getpwnam(fname)))
X		{
X			/* file doesn't have a user in passwd file.
X			 */
X			log_it(fname, getpid(), "ORPHAN", "no passwd entry");
X			goto next_crontab;
X		}
X
X		sprintf(tabname, CRON_TAB(fname));
X		if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK)
X		{
X			/* crontab not accessible?
X			 */
X			log_it(fname, getpid(), "CAN'T OPEN", tabname);
X			goto next_crontab;
X		}
X
X		if (fstat(crontab_fd, &statbuf) < OK)
X		{
X			log_it(fname, getpid(), "FSTAT FAILED", tabname);
X			goto next_crontab;
X		}
X
X		Debug(DLOAD, ("\t%s:", fname))
X		u = find_user(old_db, fname);
X		if (u != NULL)
X		{
X			/* if crontab has not changed since we last read it
X			 * in, then we can just use our existing entry.
X			 * note that we do not check for changes in the
X			 * passwd entry (uid, home dir, etc).  HINT
X			 */
X			if (u->mtime == statbuf.st_mtime)
X			{
X				Debug(DLOAD, (" [no change, using old data]"))
X				unlink_user(old_db, u);
X				link_user(&new_db, u);
X				goto next_crontab;
X			}
X
X			/* before we fall through to the code that will reload
X			 * the user, let's deallocate and unlink the user in
X			 * the old database.  This is more a point of memory
X			 * efficiency than anything else, since all leftover
X			 * users will be deleted from the old database when
X			 * we finish with the crontab...
X			 */
X			Debug(DLOAD, (" [delete old data]"))
X			unlink_user(old_db, u);
X			free_user(u);
X		}
X		u = load_user(
X			crontab_fd,
X			pw->pw_name,
X			pw->pw_uid,
X			pw->pw_gid,
X			pw->pw_dir,
X			pw->pw_shell
X		);
X		if (u != NULL)
X		{
X			u->mtime = statbuf.st_mtime;
X			link_user(&new_db, u);
X		}
next_crontab:
X		if (crontab_fd >= OK) {
X			Debug(DLOAD, (" [done]\n"))
X			close(crontab_fd);
X		}
X	}
X	/* if we don't do this, then when our children eventually call
X	 * getpwnam() in do_command.c's child_process to verify MAILTO=,
X	 * they will screw us up (and v-v).
X	 *
X	 * (this was lots of fun to find...)
X	 */
X	endpwent();
X
X	/* whatever's left in the old database is now junk.
X	 */
X	Debug(DLOAD, ("unlinking old database:\n"))
X	for (u = old_db->head;  u != NULL;  u = u->next)
X	{
X		Debug(DLOAD, ("\t%s\n", env_get(USERENV, u->envp)))
X		unlink_user(old_db, u);
X		free_user(u);
X	}
X
X	/* overwrite the database control block with the new one.
X	 */
X	Debug(DLOAD, ("installing new database\n"))
X#if defined(BSD)
X	/* BSD has structure assignments */
X	*old_db = new_db;
X#endif
X#if defined(ATT)
X	/* ATT, well, I don't know.  Use memcpy(). */
X	memcpy(old_db, &new_db, sizeof(cron_db));
X#endif
X	Debug(DLOAD, ("load_database is done\n"))
X}
X
X
void
link_user(db, u)
X	cron_db	*db;
X	user	*u;
X{
X	if (db->head == NULL)
X		db->head = u;
X	if (db->tail)
X		db->tail->next = u;
X	u->prev = db->tail;
X	u->next = NULL;
X	db->tail = u;
X}
X
X
void
unlink_user(db, u)
X	cron_db	*db;
X	user	*u;
X{
X	if (u->prev == NULL)
X		db->head = u->next;
X	else
X		u->prev->next = u->next;
X
X	if (u->next == NULL)
X		db->tail = u->prev;
X	else
X		u->next->prev = u->prev;
X}
X
X
user *
find_user(db, name)
X	cron_db	*db;
X	char	*name;
X{
X	char	*env_get();
X	user	*u;
X
X	for (u = db->head;  u != NULL;  u = u->next)
X		if (!strcmp(env_get(USERENV, u->envp), name))
X			break;
X	return u;
X}
END_OF_FILE
if test 6768 -ne `wc -c <'database.c'`; then
    echo shar: \"'database.c'\" unpacked with wrong size!
fi
# end of 'database.c'
fi
if test -f 'entry.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'entry.c'\"
else
echo shar: Extracting \"'entry.c'\" \(11561 characters\)
sed "s/^X//" >'entry.c' <<'END_OF_FILE'
X#if !defined(lint) && !defined(LINT)
static char rcsid[] = "$Header: entry.c,v 2.1 90/07/18 00:23:41 vixie Exp $";
X#endif
X
X/* vix 26jan87 [RCS'd; rest of log is in RCS file]
X * vix 01jan87 [added line-level error recovery]
X * vix 31dec86 [added /step to the from-to range, per bob at acornrc]
X * vix 30dec86 [written]
X */
X
X
X/* Copyright 1988,1990 by Paul Vixie
X * All rights reserved
X *
X * Distribute freely, except: don't remove my name from the source or
X * documentation (don't take credit for my work), mark your changes (don't
X * get me blamed for your possible bugs), don't alter or remove this
X * notice.  May be sold if buildable source is provided to buyer.  No
X * warrantee of any kind, express or implied, is included with this
X * software; use at your own risk, responsibility for damages (if any) to
X * anyone resulting from the use of this software rests entirely with the
X * user.
X *
X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
X * I'll try to keep a version up to date.  I can be reached as follows:
X * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
X * paul at vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
X */
X
X
X#include "cron.h"
X
typedef	enum
X	{e_none, e_minute, e_hour, e_dom, e_month, e_dow, e_cmd, e_timespec}
X	ecode_e;
static char *ecodes[] =
X	{
X		"no error",
X		"bad minute",
X		"bad hour",
X		"bad day-of-month",
X		"bad month",
X		"bad day-of-week",
X		"bad command",
X		"bad time specifier"
X	};
X
void
free_entry(e)
X	entry	*e;
X{
X	int	free();
X
X	(void) free(e->cmd);
X	(void) free(e);
X}
X
X
entry *
load_entry(file, error_func)
X	FILE	*file;
X	void	(*error_func)();
X{
X	/* this function reads one crontab entry -- the next -- from a file.
X	 * it skips any leading blank lines, ignores comments, and returns
X	 * EOF if for any reason the entry can't be read and parsed.
X	 *
X	 * the entry IS parsed here, btw.
X	 *
X	 * syntax:
X	 *	minutes hours doms months dows cmd\n
X	 */
X
X	extern int	free();
X	extern char	*malloc(), *savestr();
X	extern void	unget_char();
X	static char	get_list();
X
X	ecode_e	ecode = e_none;
X	entry	*e;
X	int	ch;
X	void	skip_comments();
X	char	cmd[MAX_COMMAND];
X
X	e = (entry *) calloc(sizeof(entry), sizeof(char));
X
X	Debug(DPARS, ("load_entry()...about to eat comments\n"))
X
X	skip_comments(file);
X
X	ch = get_char(file);
X
X	/* ch is now the first useful character of a useful line.
X	 * it may be an @special or it may be the first character
X	 * of a list of minutes.
X	 */
X
X	if (ch == '@')
X	{
X		/* all of these should be flagged and load-limited; i.e.,
X		 * instead of @hourly meaning "0 * * * *" it should mean
X		 * "close to the front of every hour but not 'til the
X		 * system load is low".  Problems are: how do you know
X		 * what "low" means? (save me from /etc/crond.conf!) and:
X		 * how to guarantee low variance (how low is low?), which
X		 * means how to we run roughly every hour -- seems like
X		 * we need to keep a history or let the first hour set
X		 * the schedule, which means we aren't load-limited
X		 * anymore.  too much for my overloaded brain. (vix, jan90)
X		 * HINT
X		 */
X		ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
X		if (!strcmp("reboot", cmd)) {
X			e->flags |= WHEN_REBOOT;
X		} else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
X			bit_set(e->minute, 0);
X			bit_set(e->hour, 0);
X			bit_set(e->dom, 0);
X			bit_set(e->month, 0);
X			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
X		} else if (!strcmp("monthly", cmd)) {
X			bit_set(e->minute, 0);
X			bit_set(e->hour, 0);
X			bit_set(e->dom, 0);
X			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
X			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
X		} else if (!strcmp("weekly", cmd)) {
X			bit_set(e->minute, 0);
X			bit_set(e->hour, 0);
X			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
X			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
X			bit_set(e->dow, 0);
X		} else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
X			bit_set(e->minute, 0);
X			bit_set(e->hour, 0);
X			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
X			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
X			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
X		} else if (!strcmp("hourly", cmd)) {
X			bit_set(e->minute, 0);
X			bit_set(e->hour, (LAST_HOUR-FIRST_HOUR+1));
X			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
X			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
X			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
X		} else {
X			ecode = e_timespec;
X			goto eof;
X		}
X	}
X	else
X	{
X		Debug(DPARS, ("load_entry()...about to parse numerics\n"))
X
X		ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
X				PPC_NULL, ch, file);
X		if (ch == EOF)
X		{
X			ecode = e_minute;
X			goto eof;
X		}
X
X		/* hours
X		 */
X
X		ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
X				PPC_NULL, ch, file);
X		if (ch == EOF)
X		{
X			ecode = e_hour;
X			goto eof;
X		}
X
X		/* DOM (days of month)
X		 */
X
X		if (ch == '*') e->flags |= DOM_STAR;
X		ch = get_list(e->dom, FIRST_DOM, LAST_DOM, PPC_NULL, ch, file);
X		if (ch == EOF)
X		{
X			ecode = e_dom;
X			goto eof;
X		}
X
X		/* month
X		 */
X
X		ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
X				MonthNames, ch, file);
X		if (ch == EOF)
X		{
X			ecode = e_month;
X			goto eof;
X		}
X
X		/* DOW (days of week)
X		 */
X
X		if (ch == '*') e->flags |= DOW_STAR;
X		ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
X				DowNames, ch, file);
X		if (ch == EOF)
X		{
X			ecode = e_dow;
X			goto eof;
X		}
X	}
X
X	/* make sundays equivilent */
X	if (bit_test(e->dow, 0) || bit_test(e->dow, 7))
X	{
X		bit_set(e->dow, 0);
X		bit_set(e->dow, 7);
X	}
X
X	Debug(DPARS, ("load_entry()...about to parse command\n"))
X
X	/* ch is first character of a command.  everything up to the next
X	 * \n or EOF is part of the command... too bad we don't know in
X	 * advance how long it will be, since we need to malloc a string
X	 * for it... so, we limit it to MAX_COMMAND
X	 */ 
X	unget_char(ch, file);
X	ch = get_string(cmd, MAX_COMMAND, file, "\n");
X
X	/* a file without a \n before the EOF is rude, so we'll complain...
X	 */
X	if (ch == EOF)
X	{
X		ecode = e_cmd;
X		goto eof;
X	}
X
X	/* got the command in the 'cmd' string; save it in *e.
X	 */
X	e->cmd = savestr(cmd);
X
X	Debug(DPARS, ("load_entry()...returning successfully\n"))
X
X	/* success, fini, return pointer to the entry we just created...
X	 */
X	return e;
X
eof:	/* if we want to return EOF, we have to jump down here and
X	 * free the entry we've been building.
X	 *
X	 * now, in some cases, a parse routine will have returned EOF to
X	 * indicate an error, but the file is not actually done.  since, in
X	 * that case, we only want to skip the line with the error on it,
X	 * we'll do that here.
X	 *
X	 * many, including the author, see what's below as evil programming
X	 * practice: since I didn't want to change the structure of this
X	 * whole function to support this error recovery, I recurse.  Cursed!
X	 * (At least it's tail-recursion, as if it matters in C - vix/8feb88)
X	 * I'm seriously considering using (another) GOTO...   argh!
X	 * (this does not get less disgusting over time.  vix/15nov88)
X	 */
X
X	(void) free(e);
X
X	if (feof(file))
X		return NULL;
X
X	if (error_func)
X		(*error_func)(ecodes[(int)ecode]);
X	do  {ch = get_char(file);}
X	while (ch != EOF && ch != '\n');
X	if (ch == EOF)
X		return NULL;
X	return load_entry(file, error_func);
X}
X
X
static char
get_list(bits, low, high, names, ch, file)
X	bitstr_t	*bits;		/* one bit per flag, default=FALSE */
X	int		low, high;	/* bounds, impl. offset for bitstr */
X	char		*names[];	/* NULL or *[] of names for these elements */
X	int		ch;		/* current character being processed */
X	FILE		*file;		/* file being read */
X{
X	static char	get_range();
X	register int	done;
X
X	/* we know that we point to a non-blank character here;
X	 * must do a Skip_Blanks before we exit, so that the
X	 * next call (or the code that picks up the cmd) can
X	 * assume the same thing.
X	 */
X
X	Debug(DPARS|DEXT, ("get_list()...entered\n"))
X
X	/* list = "*" | range {"," range}
X	 */
X	
X	if (ch == '*')
X	{
X		/* '*' means 'all elements'.
X		 */
X		bit_nset(bits, 0, (high-low+1));
X		goto exit;
X	}
X
X	/* clear the bit string, since the default is 'off'.
X	 */
X	bit_nclear(bits, 0, (high-low+1));
X
X	/* process all ranges
X	 */
X	done = FALSE;
X	while (!done)
X	{
X		ch = get_range(bits, low, high, names, ch, file);
X		if (ch == ',')
X			ch = get_char(file);
X		else
X			done = TRUE;
X	}
X
exit:	/* exiting.  skip to some blanks, then skip over the blanks.
X	 */
X	Skip_Nonblanks(ch, file)
X	Skip_Blanks(ch, file)
X
X	Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
X
X	return ch;
X}
X
X
static char
get_range(bits, low, high, names, ch, file)
X	bitstr_t	*bits;		/* one bit per flag, default=FALSE */
X	int		low, high;	/* bounds, impl. offset for bitstr */
X	char		*names[];	/* NULL or names of elements */
X	int		ch;		/* current character being processed */
X	FILE		*file;		/* file being read */
X{
X	/* range = number | number "-" number [ "/" number ]
X	 */
X
X	static int	set_element();
X	static char	get_number();
X	register int	i;
X	auto int	num1, num2, num3;
X
X	Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
X
X	if (EOF == (ch = get_number(&num1, low, names, ch, file)))
X		return EOF;
X
X	if (ch != '-')
X	{
X		/* not a range, it's a single number.
X		 */
X		if (EOF == set_element(bits, low, high, num1))
X			return EOF;
X	}
X	else
X	{
X		/* eat the dash
X		 */
X		ch = get_char(file);
X		if (ch == EOF)
X			return EOF;
X
X		/* get the number following the dash
X		 */
X		ch = get_number(&num2, low, names, ch, file);
X		if (ch == EOF)
X			return EOF;
X
X		/* check for step size
X		 */
X		if (ch == '/')
X		{
X			/* eat the slash
X			 */
X			ch = get_char(file);
X			if (ch == EOF)
X				return EOF;
X
X			/* get the step size -- note: we don't pass the
X			 * names here, because the number is not an
X			 * element id, it's a step size.  'low' is
X			 * sent as a 0 since there is no offset either.
X			 */
X			ch = get_number(&num3, 0, PPC_NULL, ch, file);
X			if (ch == EOF)
X				return EOF;
X		}
X		else
X		{
X			/* no step.  default==1.
X			 */
X			num3 = 1;
X		}
X
X		/* range. set all elements from num1 to num2, stepping
X		 * by num3.  (the step is a downward-compatible extension
X		 * proposed conceptually by bob at acornrc, syntactically
X		 * designed then implmented by paul vixie).
X		 */
X		for (i = num1;  i <= num2;  i += num3)
X			if (EOF == set_element(bits, low, high, i))
X				return EOF;
X	}
X	return ch;
X}
X
X
static char
get_number(numptr, low, names, ch, file)
X	int	*numptr;
X	int	low;
X	char	*names[];
X	char	ch;
X	FILE	*file;
X{
X	char	temp[MAX_TEMPSTR], *pc;
X	int	len, i, all_digits;
X
X	/* collect alphanumerics into our fixed-size temp array
X	 */
X	pc = temp;
X	len = 0;
X	all_digits = TRUE;
X	while (isalnum(ch))
X	{
X		if (++len >= MAX_TEMPSTR)
X			return EOF;
X
X		*pc++ = ch;
X
X		if (!isdigit(ch))
X			all_digits = FALSE;
X
X		ch = get_char(file);
X	}
X	*pc = '\0';
X
X	/* try to find the name in the name list
X	 */
X	if (names)
X		for (i = 0;  names[i] != NULL;  i++)
X		{
X			Debug(DPARS|DEXT,
X				("get_num, compare(%s,%s)\n", names[i], temp))
X			if (!nocase_strcmp(names[i], temp))
X			{
X				*numptr = i+low;
X				return ch;
X			}
X		}
X
X	/* no name list specified, or there is one and our string isn't
X	 * in it.  either way: if it's all digits, use its magnitude.
X	 * otherwise, it's an error.
X	 */
X	if (all_digits)
X	{
X		*numptr = atoi(temp);
X		return ch;
X	}
X
X	return EOF;
X}
X
X
static int
set_element(bits, low, high, number)
X	bitstr_t	*bits; 		/* one bit per flag, default=FALSE */
X	int		low;
X	int		high;
X	int		number;
X{
X	Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
X
X	if (number < low || number > high)
X		return EOF;
X
X	Debug(DPARS|DEXT, ("bit_set(%x,%d)\n",bits,(number-low)))
X	bit_set(bits, (number-low));
X	Debug(DPARS|DEXT, ("bit_set succeeded\n"))
X	return OK;
X}
END_OF_FILE
if test 11561 -ne `wc -c <'entry.c'`; then
    echo shar: \"'entry.c'\" unpacked with wrong size!
fi
# end of 'entry.c'
fi
echo shar: End of archive 2 \(of 3\).
cp /dev/null ark2isdone
MISSING=""
for I in 1 2 3 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 3 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0

exit 0 # Just in case...
-- 
Please send comp.sources.unix-related mail to rsalz at uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.



More information about the Comp.sources.unix mailing list