Date: 17 Apr 1997 13:33:15 -0000 From: thompson@squirrel.tgsoft.com To: FreeBSD-gnats-submit@freebsd.org Subject: bin/3314: /etc/daily did not run on April 6, 1997 Message-ID: <19970417133315.7323.qmail@squirrel.tgsoft.com> Resent-Message-ID: <199704171340.GAA01935@freefall.freebsd.org>
index | next in thread | raw e-mail
>Number: 3314
>Category: bin
>Synopsis: /etc/daily did not run on April 6, 1997
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: freebsd-bugs
>State: open
>Class: sw-bug
>Submitter-Id: current-users
>Arrival-Date: Thu Apr 17 06:40:01 PDT 1997
>Last-Modified:
>Originator: mark thompson
>Organization:
tgsoft
>Release: FreeBSD 2.2.1-RELEASE i386
>Environment:
Standard 2.2.1, and many BSDs before it
>Description:
cron is simply not equipped to handle time changes caused by the
arrival and departure of daylight savings time. Since this is a
common event around the world, it seems that it should be.
>How-To-Repeat:
Submit a cron job that executes a few minutes hence, then adjust your system
clock to an hour from now.
>Fix:
Simple patch enclosed
diff -c -r -b ./cron/cron.8 cron/NEW/cron/cron.8
*** ./cron/cron.8 Sun Jun 30 15:11:50 1996
--- cron/NEW/cron/cron.8 Thu Apr 17 06:17:38 1997
***************
*** 13,18 ****
--- 13,21 ----
.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
.\" * I'll try to keep a version up to date. I can be reached as follows:
.\" * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ .\"
+ .\" * new timekeeping (handle daylight savings time) by
+ .\" * mark thompson <thompson@tgsoft.com> ... so don't blame Paul
.\" */
.\"
.\" $Id: cron.8,v 1.2 1996/06/30 22:11:50 wosch Exp $
***************
*** 54,59 ****
--- 57,77 ----
.IR Crontab (1)
command updates the modtime of the spool directory whenever it changes a
crontab.
+ .PP
+ Special considerations exist when the clock is changed by less than 3
+ hours, for example at the beginning and end of daylight savings
+ time. If the time has moved forwards, those jobs which would have
+ run in the time that was skipped will be run soon after the change.
+ Conversely, if the time has moved backwards by less than 3 hours,
+ those jobs that fall into the repeated time will not be run.
+ .PP
+ Only jobs that run at a particular time (not specified as
+ @hourly, nor with '*' in the hour or minute specifier) are
+ affected. Jobs which are specified with wildcards are run based on the
+ new time immediately.
+ .PP
+ Clock changes of more than 3 hours are considered to be corrections to
+ the clock, and the new time is used immediately.
.SH "SEE ALSO"
crontab(1), crontab(5)
.SH AUTHOR
diff -c -r -b ./cron/cron.c cron/NEW/cron/cron.c
*** ./cron/cron.c Sun Jun 30 15:11:51 1996
--- cron/NEW/cron/cron.c Thu Apr 17 06:05:25 1997
***************
*** 13,18 ****
--- 13,21 ----
* Send bug reports, bug fixes, enhancements, requests, flames, etc., and
* I'll try to keep a version up to date. I can be reached as follows:
* Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ *
+ * New timekeeping (main after first load_database, last if in find_jobs,
+ * set_time and cron_sleep) by mark thompson <thompson@tgsoft.com>
*/
#if !defined(lint) && !defined(LINT)
***************
*** 34,42 ****
static void usage __P((void)),
run_reboot_jobs __P((cron_db *)),
! cron_tick __P((cron_db *)),
! cron_sync __P((void)),
! cron_sleep __P((void)),
#ifdef USE_SIGCHLD
sigchld_handler __P((int)),
#endif
--- 37,45 ----
static void usage __P((void)),
run_reboot_jobs __P((cron_db *)),
! find_jobs __P((time_min, cron_db *, int, int)),
! set_time __P((void)),
! cron_sleep __P((time_min)),
#ifdef USE_SIGCHLD
sigchld_handler __P((int)),
#endif
***************
*** 93,104 ****
/* if there are no debug flags turned on, fork as a daemon should.
*/
! # if DEBUGGING
if (DebugFlags) {
! # else
! if (0) {
! # endif
(void) fprintf(stderr, "[%d] cron started\n", getpid());
} else {
switch (fork()) {
case -1:
--- 96,106 ----
/* if there are no debug flags turned on, fork as a daemon should.
*/
!
if (DebugFlags) {
! #if DEBUGGING
(void) fprintf(stderr, "[%d] cron started\n", getpid());
+ #endif
} else {
switch (fork()) {
case -1:
***************
*** 121,143 ****
database.tail = NULL;
database.mtime = (time_t) 0;
load_database(&database);
run_reboot_jobs(&database);
! cron_sync();
while (TRUE) {
! # if DEBUGGING
! /* if (!(DebugFlags & DTEST)) */
! # endif /*DEBUGGING*/
! cron_sleep();
load_database(&database);
! /* do this iteration
! */
! cron_tick(&database);
! /* sleep 1 minute
! */
! TargetTime += 60;
}
}
--- 123,232 ----
database.tail = NULL;
database.mtime = (time_t) 0;
load_database(&database);
+
+ set_time();
run_reboot_jobs(&database);
! timeRunning = virtualTime = clockTime;
!
! /*
! * too many clocks, not enough time (Al. Einstein)
! * These clocks are in minutes since the epoch (time()/60).
! * virtualTime is the time it *would* be if we woke up promptly and nobody
! * ever changed the clock. It is monotonically increasing...
! * unless a timejump happens.
! * At the top of the loop, all jobs for 'virtualTime' have run.
! * timeRunning is the time we last awakened.
! * clockTime is the time when set_time was last called.
! */
while (TRUE) {
! time_min timeDiff;
! int wakeupKind;
load_database(&database);
! /* ... wait for the time (in minutes) to change ... */
! do {
! cron_sleep(timeRunning + 1);
! set_time();
! } while (clockTime == timeRunning);
! timeRunning = clockTime;
!
! /* ... calculate how the current time differs from our virtual */
! /* clock. Classify the change into one of 4 cases */
! timeDiff = timeRunning - virtualTime;
!
! /* shortcut for the most common case */
! if (timeDiff == 1) {
! virtualTime = timeRunning;
! find_jobs(virtualTime, &database, TRUE, TRUE);
! }
! else {
! wakeupKind = -1;
! if (timeDiff > -(3*MINUTE_COUNT)) wakeupKind = 0;
! if (timeDiff > 0) wakeupKind = 1;
! if (timeDiff > 5) wakeupKind = 2;
! if (timeDiff > (3*MINUTE_COUNT)) wakeupKind = 3;
!
! switch (wakeupKind) {
! /* case 1: timeDiff is a small positive number (wokeup late) */
! /* run jobs for each virtual minute until caught up. */
! case 1:
! Debug(DSCH, ("[%d], normal case %d minutes to go\n",
! getpid(), timeRunning - virtualTime))
! do {
! if (job_runqueue()) sleep(10);
! virtualTime++;
! find_jobs(virtualTime, &database, TRUE, TRUE);
! } while (virtualTime < timeRunning);
! break;
!
! /* case 2: timeDiff is a medium-sized positive number, */
! /* for example because we went to DST */
! /* run wildcard jobs once, then run any fixed-time jobs that*/
! /* would otherwise be skipped */
! /* if we use up our minute (possible, if there are a lot of */
! /* jobs to run) go around the loop again so that wildcard */
! /* jobs have a chance to run, and we do our housekeeping */
! case 2:
! Debug(DSCH, ("[%d], DST begins %d minutes to go\n",
! getpid(), timeRunning - virtualTime))
! /* run wildcard jobs for current minute */
! find_jobs(timeRunning, &database, TRUE, FALSE);
!
! /* run fixed-time jobs for each minute missed */
! do {
! if (job_runqueue()) sleep(10);
!
! virtualTime++;
! find_jobs(virtualTime, &database, FALSE, TRUE);
!
! set_time();
! } while (virtualTime < timeRunning &&
! clockTime == timeRunning);
! break;
!
! /* case 3: timeDiff is a small or medium-sized negative num. */
! /* eg. because of DST ending */
! /* just run the wildcard jobs. The fixed-time jobs probably */
! /* have already run, and should not be repeated */
! /* virtual time does not change until we are caught up */
! case 0:
! Debug(DSCH, ("[%d], DST ends %d minutes to go\n",
! getpid(), virtualTime - timeRunning))
! find_jobs(timeRunning, &database, TRUE, FALSE);
! break;
!
! /* other: time has changed a *lot* */
! /* jump virtual time, and run everything */
! default:
! Debug(DSCH, ("[%d], clock jumped\n", getpid()))
! virtualTime = timeRunning;
! find_jobs(timeRunning, &database, TRUE, TRUE);
! }
! }
! /* jobs to be run (if any) are loaded. clear the queue */
! job_runqueue();
}
}
***************
*** 161,170 ****
static void
! cron_tick(db)
cron_db *db;
{
! register struct tm *tm = localtime(&TargetTime);
register int minute, hour, dom, month, dow;
register user *u;
register entry *e;
--- 250,263 ----
static void
! find_jobs(vtime, db, doWild, doNonWild)
! time_min vtime;
cron_db *db;
+ int doWild;
+ int doNonWild;
{
! time_t virtualSecond = vtime * SECONDS_PER_MINUTE;
! register struct tm *tm = localtime(&virtualSecond);
register int minute, hour, dom, month, dow;
register user *u;
register entry *e;
***************
*** 177,184 ****
month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
dow = tm->tm_wday -FIRST_DOW;
! Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d)\n",
! getpid(), minute, hour, dom, month, dow))
/* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
* first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
--- 270,278 ----
month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
dow = tm->tm_wday -FIRST_DOW;
! Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d) %s %s\n",
! getpid(), minute, hour, dom, month, dow,
! doWild?" ":"No wildcard",doNonWild?" ":"Wildcard only"))
/* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
* first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
***************
*** 199,204 ****
--- 293,300 ----
: (bit_test(e->dow,dow) || bit_test(e->dom,dom))
)
) {
+ if ((doNonWild && !(e->flags & (MIN_STAR|HR_STAR)))
+ || (doWild && (e->flags & (MIN_STAR|HR_STAR))))
job_add(e, u);
}
}
***************
*** 206,255 ****
}
! /* the task here is to figure out how long it's going to be until :00 of the
! * following minute and initialize TargetTime to this value. TargetTime
! * will subsequently slide 60 seconds at a time, with correction applied
! * implicitly in cron_sleep(). it would be nice to let cron execute in
! * the "current minute" before going to sleep, but by restarting cron you
! * could then get it to execute a given minute's jobs more than once.
! * instead we have the chance of missing a minute's jobs completely, but
! * that's something sysadmin's know to expect what with crashing computers..
*/
static void
! cron_sync() {
! register struct tm *tm;
!
! TargetTime = time((time_t*)0);
! tm = localtime(&TargetTime);
! TargetTime += (60 - tm->tm_sec);
}
!
static void
! cron_sleep() {
register int seconds_to_wait;
! do {
! seconds_to_wait = (int) (TargetTime - time((time_t*)0));
Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
! getpid(), TargetTime, seconds_to_wait))
! /* if we intend to sleep, this means that it's finally
! * time to empty the job queue (execute it).
! *
! * if we run any jobs, we'll probably screw up our timing,
! * so go recompute.
! *
! * note that we depend here on the left-to-right nature
! * of &&, and the short-circuiting.
! */
! } while (seconds_to_wait > 0 && job_runqueue());
!
! while (seconds_to_wait > 0) {
! Debug(DSCH, ("[%d] sleeping for %d seconds\n",
! getpid(), seconds_to_wait))
! seconds_to_wait = (int) sleep((unsigned int) seconds_to_wait);
! }
}
--- 302,334 ----
}
! /*
! * set StartTime and clockTime to the current time.
! * these are used for computing what time it really is right now.
! * note that clockTime is a unix wallclock time converted to minutes
*/
static void
! set_time() {
! StartTime = time((time_t *)0);
! clockTime = StartTime / (unsigned long)SECONDS_PER_MINUTE;
}
! /*
! * try to just hit the next minute
! */
static void
! cron_sleep(target)
! time_min target;
! {
register int seconds_to_wait;
+ register struct tm *tm;
! seconds_to_wait = (int)(target*SECONDS_PER_MINUTE - time((time_t*)0)) + 1;
Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
! getpid(), target*SECONDS_PER_MINUTE, seconds_to_wait))
! if (seconds_to_wait > 0 && seconds_to_wait < 65)
! sleep((unsigned int) seconds_to_wait);
}
diff -c -r -b ./cron/cron.h cron/NEW/cron/cron.h
*** ./cron/cron.h Sun Aug 4 17:31:24 1996
--- cron/NEW/cron/cron.h Wed Apr 16 06:28:15 1997
***************
*** 120,125 ****
--- 120,129 ----
LineNumber = ln; \
}
+ typedef long time_min;
+
+ #define SECONDS_PER_MINUTE 60
+
#define FIRST_MINUTE 0
#define LAST_MINUTE 59
#define MINUTE_COUNT (LAST_MINUTE - FIRST_MINUTE + 1)
***************
*** 162,167 ****
--- 166,173 ----
#define DOM_STAR 0x01
#define DOW_STAR 0x02
#define WHEN_REBOOT 0x04
+ #define MIN_STAR 0x08
+ #define HR_STAR 0x10
} entry;
/* the crontab database will be a list of the
***************
*** 256,262 ****
char *ProgramName;
int LineNumber;
! time_t TargetTime;
# if DEBUGGING
int DebugFlags;
--- 262,271 ----
char *ProgramName;
int LineNumber;
! time_t StartTime;
! time_min timeRunning;
! time_min virtualTime;
! time_min clockTime;
# if DEBUGGING
int DebugFlags;
***************
*** 264,269 ****
--- 273,280 ----
"ext", "sch", "proc", "pars", "load", "misc", "test", "bit",
NULL /* NULL must be last element */
};
+ # else
+ #define DebugFlags 0
# endif /* DEBUGGING */
#else /*MAIN_PROGRAM*/
extern char *copyright[],
***************
*** 271,277 ****
*DowNames[],
*ProgramName;
extern int LineNumber;
! extern time_t TargetTime;
# if DEBUGGING
extern int DebugFlags;
extern char *DebugFlagNames[];
--- 282,291 ----
*DowNames[],
*ProgramName;
extern int LineNumber;
! extern time_t StartTime;
! extern time_min timeRunning;
! extern time_min virtualTime;
! extern time_min clockTime;
# if DEBUGGING
extern int DebugFlags;
extern char *DebugFlagNames[];
diff -c -r -b ./cron/database.c cron/NEW/cron/database.c
*** ./cron/database.c Mon Sep 9 20:38:20 1996
--- cron/NEW/cron/database.c Wed Apr 16 08:33:51 1997
***************
*** 13,18 ****
--- 13,22 ----
* Send bug reports, bug fixes, enhancements, requests, flames, etc., and
* I'll try to keep a version up to date. I can be reached as follows:
* Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ *
+ * stored database time is hash of time of SPOOL_DIR and of /etc/crontab
+ * so that clock fluctuations will be correctly detected
+ * mark thompson <thompson@tgsoft.com>
*/
#if !defined(lint) && !defined(LINT)
***************
*** 30,36 ****
#define TMAX(a,b) ((a)>(b)?(a):(b))
!
static void process_crontab __P((char *, char *, char *,
struct stat *,
--- 34,40 ----
#define TMAX(a,b) ((a)>(b)?(a):(b))
! #define HASH(a,b) ((a)+(b))
static void process_crontab __P((char *, char *, char *,
struct stat *,
***************
*** 71,77 ****
* so is guaranteed to be different than the stat() mtime the first
* time this function is called.
*/
! if (old_db->mtime == TMAX(statbuf.st_mtime, syscron_stat.st_mtime)) {
Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n",
getpid()))
return;
--- 75,81 ----
* so is guaranteed to be different than the stat() mtime the first
* time this function is called.
*/
! if (old_db->mtime == HASH(statbuf.st_mtime, syscron_stat.st_mtime)) {
Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n",
getpid()))
return;
***************
*** 82,88 ****
* actually changed. Whatever is left in the old database when
* we're done is chaff -- crontabs that disappeared.
*/
! new_db.mtime = TMAX(statbuf.st_mtime, syscron_stat.st_mtime);
new_db.head = new_db.tail = NULL;
if (syscron_stat.st_mtime) {
--- 86,92 ----
* actually changed. Whatever is left in the old database when
* we're done is chaff -- crontabs that disappeared.
*/
! new_db.mtime = HASH(statbuf.st_mtime, syscron_stat.st_mtime);
new_db.head = new_db.tail = NULL;
if (syscron_stat.st_mtime) {
diff -c -r -b ./cron/do_command.c cron/NEW/cron/do_command.c
*** ./cron/do_command.c Sun Sep 10 06:02:56 1995
--- cron/NEW/cron/do_command.c Wed Apr 16 05:25:23 1997
***************
*** 388,394 ****
e->cmd);
# if defined(MAIL_DATE)
fprintf(mail, "Date: %s\n",
! arpadate(&TargetTime));
# endif /* MAIL_DATE */
for (env = e->envp; *env; env++)
fprintf(mail, "X-Cron-Env: <%s>\n",
--- 388,394 ----
e->cmd);
# if defined(MAIL_DATE)
fprintf(mail, "Date: %s\n",
! arpadate(&StartTime));
# endif /* MAIL_DATE */
for (env = e->envp; *env; env++)
fprintf(mail, "X-Cron-Env: <%s>\n",
diff -c -r -b ./lib/entry.c cron/NEW/lib/entry.c
*** ./lib/entry.c Mon Aug 28 14:30:46 1995
--- cron/NEW/lib/entry.c Wed Apr 16 08:30:14 1997
***************
*** 19,25 ****
static char rcsid[] = "$Id: entry.c,v 1.4 1995/08/28 21:30:46 mpp Exp $";
#endif
! /* vix 26jan87 [RCS'd; rest of log is in RCS file]
* vix 01jan87 [added line-level error recovery]
* vix 31dec86 [added /step to the from-to range, per bob@acornrc]
* vix 30dec86 [written]
--- 19,27 ----
static char rcsid[] = "$Id: entry.c,v 1.4 1995/08/28 21:30:46 mpp Exp $";
#endif
! /*
! * -mt 15Apr97 add HR_STAR and MIN_STAR [<thompson@tgsoft.com>]
! * vix 26jan87 [RCS'd; rest of log is in RCS file]
* vix 01jan87 [added line-level error recovery]
* vix 31dec86 [added /step to the from-to range, per bob@acornrc]
* vix 30dec86 [written]
***************
*** 153,158 ****
--- 155,161 ----
bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
+ e->flags |= HR_STAR;
} else {
ecode = e_timespec;
goto eof;
***************
*** 160,165 ****
--- 163,170 ----
} else {
Debug(DPARS, ("load_entry()...about to parse numerics\n"))
+ if (ch == '*')
+ e->flags |= MIN_STAR;
ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
PPC_NULL, ch, file);
if (ch == EOF) {
***************
*** 170,175 ****
--- 175,182 ----
/* hours
*/
+ if (ch == '*')
+ e->flags |= HR_STAR;
ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
PPC_NULL, ch, file);
if (ch == EOF) {
>Audit-Trail:
>Unformatted:
home |
help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?19970417133315.7323.qmail>
