diff --git a/usr.sbin/cron/cron/cron.c b/usr.sbin/cron/cron/cron.c index 48f431d9368c..460dd58dbd0d 100644 --- a/usr.sbin/cron/cron/cron.c +++ b/usr.sbin/cron/cron/cron.c @@ -46,7 +46,9 @@ static void usage(void), parse_args(int c, char *v[]); static int run_at_secres(cron_db *); +static void find_interval_entry(pid_t); +static cron_db database; static time_t last_time = 0; static int dst_enabled = 0; static int dont_daemonize = 0; @@ -100,7 +102,6 @@ main(argc, argv) int argc; char *argv[]; { - cron_db database; int runnum; int secres1, secres2; struct tm *tm; @@ -154,8 +155,8 @@ main(argc, argv) database.mtime = (time_t) 0; load_database(&database); secres1 = secres2 = run_at_secres(&database); - run_reboot_jobs(&database); cron_sync(secres1); + run_reboot_jobs(&database); runnum = 0; while (TRUE) { # if DEBUGGING @@ -210,6 +211,9 @@ run_reboot_jobs(db) if (e->flags & WHEN_REBOOT) { job_add(e, u); } + if (e->flags & INTERVAL) { + e->lastexit = TargetTime; + } } } (void) job_runqueue(); @@ -313,6 +317,13 @@ cron_tick(cron_db *db, int secres) env_get("LOGNAME", e->envp), e->uid, e->gid, e->cmd)) + if (e->flags & INTERVAL) { + if (e->lastexit > 0 && + TargetTime >= e->lastexit + e->interval) + job_add(e, u); + continue; + } + if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) { if (bit_test(e->second, otzsecond) && bit_test(e->minute, otzminute) @@ -489,6 +500,7 @@ sigchld_handler(int x) ("[%d] sigchld...no dead kids\n", getpid())) return; default: + find_interval_entry(pid); Debug(DPROC, ("[%d] sigchld...pid #%d died, stat=%d\n", getpid(), pid, WEXITSTATUS(waiter))) @@ -557,9 +569,26 @@ run_at_secres(cron_db *db) for (u = db->head; u != NULL; u = u->next) { for (e = u->crontab; e != NULL; e = e->next) { - if ((e->flags & SEC_RES) != 0) + if ((e->flags & (SEC_RES | INTERVAL)) != 0) return 1; } } return 0; } + +static void +find_interval_entry(pid_t pid) +{ + user *u; + entry *e; + + for (u = database.head; u != NULL; u = u->next) { + for (e = u->crontab; e != NULL; e = e->next) { + if ((e->flags & INTERVAL) && e->child == pid) { + e->lastexit = time(NULL); + e->child = 0; + break; + } + } + } +} diff --git a/usr.sbin/cron/cron/cron.h b/usr.sbin/cron/cron/cron.h index a6810be84005..bb4f5287daea 100644 --- a/usr.sbin/cron/cron/cron.h +++ b/usr.sbin/cron/cron/cron.h @@ -168,19 +168,29 @@ typedef struct _entry { #endif char **envp; char *cmd; - bitstr_t bit_decl(second, SECOND_COUNT); - bitstr_t bit_decl(minute, MINUTE_COUNT); - bitstr_t bit_decl(hour, HOUR_COUNT); - bitstr_t bit_decl(dom, DOM_COUNT); - bitstr_t bit_decl(month, MONTH_COUNT); - bitstr_t bit_decl(dow, DOW_COUNT); + union { + struct { + bitstr_t bit_decl(second, SECOND_COUNT); + bitstr_t bit_decl(minute, MINUTE_COUNT); + bitstr_t bit_decl(hour, HOUR_COUNT); + bitstr_t bit_decl(dom, DOM_COUNT); + bitstr_t bit_decl(month, MONTH_COUNT); + bitstr_t bit_decl(dow, DOW_COUNT); + }; + struct { + time_t lastexit; + time_t interval; + pid_t child; + }; + }; int flags; #define DOM_STAR 0x01 #define DOW_STAR 0x02 #define WHEN_REBOOT 0x04 -#define RUN_AT 0x08 +#define RUN_AT 0x08 #define NOT_UNTIL 0x10 #define SEC_RES 0x20 +#define INTERVAL 0x40 time_t lastrun; } entry; diff --git a/usr.sbin/cron/cron/do_command.c b/usr.sbin/cron/cron/do_command.c index 4d7c1f439d0e..f62303a5df29 100644 --- a/usr.sbin/cron/cron/do_command.c +++ b/usr.sbin/cron/cron/do_command.c @@ -47,6 +47,8 @@ do_command(e, u) entry *e; user *u; { + pid_t pid; + Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", getpid(), e->cmd, u->name, e->uid, e->gid)) @@ -57,9 +59,11 @@ do_command(e, u) * vfork() is unsuitable, since we have much to do, and the parent * needs to be able to run off and fork other processes. */ - switch (fork()) { + switch ((pid = fork())) { case -1: log_it("CRON",getpid(),"error","can't fork"); + if (e->flags & INTERVAL) + e->lastexit = time(NULL); break; case 0: /* child process */ @@ -70,6 +74,12 @@ do_command(e, u) break; default: /* parent process */ + Debug(DPROC, ("[%d] main process forked child #%d, " + "returning to work\n", getpid(), pid)) + if (e->flags & INTERVAL) { + e->lastexit = 0; + e->child = pid; + } break; } Debug(DPROC, ("[%d] main process returning to work\n", getpid())) diff --git a/usr.sbin/cron/crontab/crontab.5 b/usr.sbin/cron/crontab/crontab.5 index acd6cb5381a6..697242b4d0fc 100644 --- a/usr.sbin/cron/crontab/crontab.5 +++ b/usr.sbin/cron/crontab/crontab.5 @@ -17,7 +17,7 @@ .\" .\" $FreeBSD$ .\" -.Dd January 5, 2016 +.Dd June 6, 2018 .Dt CRONTAB 5 .Os .Sh NAME @@ -220,7 +220,10 @@ would cause a command to be run at 4:30 am on the 1st and 15th of each month, plus every Friday. .Pp Instead of the first five fields, -one of eight special strings may appear: +a line may start with +.Sq @ +symbol followed either by one of eight special strings or by a numeric value. +The recognized special strings are: .Bd -literal -offset indent string meaning ------ ------- @@ -235,6 +238,16 @@ string meaning @every_minute Run once a minute, "*/1 * * * *". @every_second Run once a second. .Ed +.Pp +The +.Sq @ +symbol followed by a numeric value has a special notion of running +a job that much seconds after completion of previous invocation of +the job. +Unlike regular syntax, it guarantees not to overlap two or more +invocations of the same job. +The first run is scheduled specified amount of seconds after cron +has started. .Sh EXAMPLE CRON FILE .Bd -literal @@ -251,6 +264,8 @@ 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" +# run at 5 minutes intervals, no matter how long it takes +@300 svnlite up /usr/src .Ed .Sh SEE ALSO .Xr crontab 1 , @@ -292,7 +307,7 @@ either). .Pp All of the .Sq @ -commands that can appear in place of the first five fields +directives that can appear in place of the first five fields are extensions. .Sh AUTHORS .An Paul Vixie Aq Mt paul@vix.com diff --git a/usr.sbin/cron/lib/entry.c b/usr.sbin/cron/lib/entry.c index 57f72554d344..a8ec3ae8b568 100644 --- a/usr.sbin/cron/lib/entry.c +++ b/usr.sbin/cron/lib/entry.c @@ -132,6 +132,9 @@ load_entry(file, error_func, pw, envp) } if (ch == '@') { + long interval; + char *endptr; + /* all of these should be flagged and load-limited; i.e., * instead of @hourly meaning "0 * * * *" it should mean * "close to the front of every hour but not 'til the @@ -209,6 +212,13 @@ load_entry(file, error_func, pw, envp) 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)); + } else if (*cmd != '\0' && + (interval = strtol(cmd, &endptr, 10)) > 0 && + *endptr == '\0') { + Debug(DPARS, ("load_entry()... %ld seconds " + "since last run\n", interval)) + e->interval = interval; + e->flags = INTERVAL; } else { ecode = e_timespec; goto eof;