New feature: "?" for time specifications, means a single time selected at

random from within the range at file read time. Very useful if you
want to avoid having a fleet of machines melt a server by all trying to
contact it at a precise time every morning. See docs for details.

Reviewed by: christos, apb, vixie, others.

XXX apb suggests, quite reasonably, that ?10-16/2 should mean
something like 10,12,14,16 or 11,13,15. I'm too lazy to do it right
now, but it should be done.

XXX vixie suggests, quite reasonably, that if you're using "?" one
should delay randomly by 0-59 seconds. In the modern NTP world, you
could imagine that with a million well synchronized machines the
second just at the minute would be hit quite hard. I'm too lazy to do
it right now, but it should be done.

XXX cron needs to be updated to Vixie's cron 4.1 code.
This commit is contained in:
perry 2009-04-04 16:05:10 +00:00
parent 0c046062f0
commit 4cfab35c9c
2 changed files with 67 additions and 4 deletions

View File

@ -1,4 +1,4 @@
.\" $NetBSD: crontab.5,v 1.11 2004/09/02 11:41:27 jmmv Exp $
.\" $NetBSD: crontab.5,v 1.12 2009/04/04 16:05:10 perry Exp $
.\"
.\"/* Copyright 1988,1990,1993,1994 by Paul Vixie
.\" * All rights reserved
@ -135,6 +135,20 @@ with a hyphen. The specified range is inclusive. For example,
8-11 for an ``hours'' entry specifies execution at hours 8, 9, 10
and 11.
.PP
A field may begin with a question mark (?), which indicates a
single value randomly selected when the crontab file is read.
If the field contains only a question mark, the value is randomly
selected from the range of all possible values for the field.
If the question mark precedes a range, the value is randomly selected
from the range.
For example, ``? ?2-5 * * *'' specifies that a task will be performed
daily between 2:00am and and 5:59am at a time randomly selected when
the crontab file is first read.
As just one example, this feature can be used to prevent a large
number of hosts from contacting a server simultaneously and
overloading it by staggering the time at which a download script is
executed.
.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''.
.PP
@ -211,6 +225,7 @@ MAILTO=paul
0 22 * * 1-5 mail -s "It's 10pm" joe%Joe,%%Where are your kids?%
23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday"
5 4 * * sun echo "run at 5 after 4 every sunday"
? ?2-4 1,15 * * echo "random between 2am-4:59am on the 1st and 15th"
.fi
.SH SEE ALSO
cron(8), crontab(1)

View File

@ -1,4 +1,4 @@
/* $NetBSD: entry.c,v 1.9 2008/02/16 07:26:00 matt Exp $ */
/* $NetBSD: entry.c,v 1.10 2009/04/04 16:05:10 perry Exp $ */
/* Copyright 1988,1990,1993,1994 by Paul Vixie
* All rights reserved
@ -22,7 +22,7 @@
#if 0
static char rcsid[] = "Id: entry.c,v 2.12 1994/01/17 03:20:37 vixie Exp";
#else
__RCSID("$NetBSD: entry.c,v 1.9 2008/02/16 07:26:00 matt Exp $");
__RCSID("$NetBSD: entry.c,v 1.10 2009/04/04 16:05:10 perry Exp $");
#endif
#endif
@ -358,6 +358,18 @@ get_list(bitstr_t *bits, /* one bit per flag, default=FALSE */
}
static int
random_with_range(int low, int high)
{
/* Kind of crappy error detection, but...
*/
if (low >= high)
return low;
else
return arc4random() % (high - low + 1) + low;
}
static char
get_range(bitstr_t *bits, /* one bit per flag, default=FALSE */
int low, /* bounds, impl. offset for bitstr */
@ -371,24 +383,44 @@ get_range(bitstr_t *bits, /* one bit per flag, default=FALSE */
int i;
int num1, num2, num3;
int qmark, star;
qmark = star = FALSE;
Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
if (ch == '*') {
/* '*' means "first-last" but can still be modified by /step
*/
star = TRUE;
num1 = low;
num2 = high;
ch = get_char(file);
if (ch == EOF)
return EOF;
} else {
} else if (ch == '?') {
qmark = TRUE;
ch = get_char(file);
if (ch == EOF)
return EOF;
if (!isdigit(ch)) {
num1 = random_with_range(low, high);
if (EOF == set_element(bits, low, high, num1))
return EOF;
return ch;
}
}
if (!star) {
if (EOF == (ch = get_number(&num1, low, names, ch, file)))
return EOF;
if (ch != '-') {
/* not a range, it's a single number.
* a single number after '?' is bogus.
*/
if (qmark)
return EOF;
if (EOF == set_element(bits, low, high, num1))
return EOF;
return ch;
@ -404,12 +436,28 @@ get_range(bitstr_t *bits, /* one bit per flag, default=FALSE */
ch = get_number(&num2, low, names, ch, file);
if (ch == EOF)
return EOF;
/* if we have a random range, it is really
* like having a single number.
*/
if (qmark) {
if (num1 > num2)
return EOF;
num1 = random_with_range(num1, num2);
if (EOF == set_element(bits, low, high, num1))
return EOF;
return ch;
}
}
}
/* check for step size
*/
if (ch == '/') {
/* '?' is incompatible with '/'
*/
if (qmark)
return EOF;
/* eat the slash
*/
ch = get_char(file);