rsalz@bbn.com (Rich Salz) (10/10/90)
Submitted-by: Paul A Vixie <vixie@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@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@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@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@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@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@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@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@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@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@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@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@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@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@uunet.uu.net. Use a domain-based address or give alternate paths, or you may lose out.