12311Sjkh/* Copyright 1988,1990,1993,1994 by Paul Vixie 22311Sjkh * All rights reserved 32311Sjkh * 42311Sjkh * Distribute freely, except: don't remove my name from the source or 52311Sjkh * documentation (don't take credit for my work), mark your changes (don't 62311Sjkh * get me blamed for your possible bugs), don't alter or remove this 72311Sjkh * notice. May be sold if buildable source is provided to buyer. No 82311Sjkh * warrantee of any kind, express or implied, is included with this 92311Sjkh * software; use at your own risk, responsibility for damages (if any) to 102311Sjkh * anyone resulting from the use of this software rests entirely with the 112311Sjkh * user. 122311Sjkh * 132311Sjkh * Send bug reports, bug fixes, enhancements, requests, flames, etc., and 142311Sjkh * I'll try to keep a version up to date. I can be reached as follows: 152311Sjkh * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul 162311Sjkh */ 172311Sjkh 182311Sjkh#if !defined(lint) && !defined(LINT) 1929452Scharnierstatic const char rcsid[] = 2050479Speter "$FreeBSD$"; 212311Sjkh#endif 222311Sjkh 232311Sjkh#define MAIN_PROGRAM 242311Sjkh 252311Sjkh 262311Sjkh#include "cron.h" 27199804Sattilio#include <sys/mman.h> 282311Sjkh#include <sys/signal.h> 292311Sjkh#if SYS_TIME_H 302311Sjkh# include <sys/time.h> 312311Sjkh#else 322311Sjkh# include <time.h> 332311Sjkh#endif 342311Sjkh 352311Sjkh 36173412Skevlostatic void usage(void), 37173412Skevlo run_reboot_jobs(cron_db *), 38173412Skevlo cron_tick(cron_db *), 39173412Skevlo cron_sync(void), 40173412Skevlo cron_sleep(cron_db *), 41173412Skevlo cron_clean(cron_db *), 422311Sjkh#ifdef USE_SIGCHLD 43173412Skevlo sigchld_handler(int), 442311Sjkh#endif 45173412Skevlo sighup_handler(int), 46173412Skevlo parse_args(int c, char *v[]); 472311Sjkh 4874010Sbabkinstatic time_t last_time = 0; 4974010Sbabkinstatic int dst_enabled = 0; 50149430Spjdstruct pidfh *pfh; 512311Sjkh 522311Sjkhstatic void 532311Sjkhusage() { 5416859Swosch char **dflags; 5516859Swosch 56129280Syar fprintf(stderr, "usage: cron [-j jitter] [-J rootjitter] " 57180096Smarck "[-m mailto] [-s] [-o] [-x debugflag[,...]]\n"); 5816859Swosch fprintf(stderr, "\ndebugflags: "); 5916859Swosch 6016859Swosch for(dflags = DebugFlagNames; *dflags; dflags++) { 6116859Swosch fprintf(stderr, "%s ", *dflags); 6216859Swosch } 6316859Swosch fprintf(stderr, "\n"); 6416859Swosch 652311Sjkh exit(ERROR_EXIT); 662311Sjkh} 672311Sjkh 68149430Spjdstatic void 69149430Spjdopen_pidfile(void) 70149430Spjd{ 71149430Spjd char pidfile[MAX_FNAME]; 72149430Spjd char buf[MAX_TEMPSTR]; 73149430Spjd int otherpid; 742311Sjkh 75149430Spjd (void) snprintf(pidfile, sizeof(pidfile), PIDFILE, PIDDIR); 76150214Spjd pfh = pidfile_open(pidfile, 0600, &otherpid); 77149430Spjd if (pfh == NULL) { 78149430Spjd if (errno == EEXIST) { 79149430Spjd snprintf(buf, sizeof(buf), 80149430Spjd "cron already running, pid: %d", otherpid); 81149430Spjd } else { 82149430Spjd snprintf(buf, sizeof(buf), 83149430Spjd "can't open or create %s: %s", pidfile, 84149430Spjd strerror(errno)); 85149430Spjd } 86149430Spjd log_it("CRON", getpid(), "DEATH", buf); 87149430Spjd errx(ERROR_EXIT, "%s", buf); 88149430Spjd } 89149430Spjd} 90149430Spjd 912311Sjkhint 922311Sjkhmain(argc, argv) 932311Sjkh int argc; 942311Sjkh char *argv[]; 952311Sjkh{ 962311Sjkh cron_db database; 9771407Sbabkin 982311Sjkh ProgramName = argv[0]; 992311Sjkh 1002311Sjkh#if defined(BSD) 1012311Sjkh setlinebuf(stdout); 1022311Sjkh setlinebuf(stderr); 1032311Sjkh#endif 1042311Sjkh 1052311Sjkh parse_args(argc, argv); 1062311Sjkh 1072311Sjkh#ifdef USE_SIGCHLD 1082311Sjkh (void) signal(SIGCHLD, sigchld_handler); 1092311Sjkh#else 1102311Sjkh (void) signal(SIGCLD, SIG_IGN); 1112311Sjkh#endif 1122311Sjkh (void) signal(SIGHUP, sighup_handler); 1132311Sjkh 114149430Spjd open_pidfile(); 1152311Sjkh set_cron_uid(); 1162311Sjkh set_cron_cwd(); 1172311Sjkh 1182311Sjkh#if defined(POSIX) 1192311Sjkh setenv("PATH", _PATH_DEFPATH, 1); 1202311Sjkh#endif 1212311Sjkh 1222311Sjkh /* if there are no debug flags turned on, fork as a daemon should. 1232311Sjkh */ 1242311Sjkh# if DEBUGGING 1252311Sjkh if (DebugFlags) { 1262311Sjkh# else 1272311Sjkh if (0) { 1282311Sjkh# endif 1292311Sjkh (void) fprintf(stderr, "[%d] cron started\n", getpid()); 1302311Sjkh } else { 13173955Speter if (daemon(1, 0) == -1) { 132149430Spjd pidfile_remove(pfh); 13373955Speter log_it("CRON",getpid(),"DEATH","can't become daemon"); 1342311Sjkh exit(0); 1352311Sjkh } 1362311Sjkh } 1372311Sjkh 138199804Sattilio if (madvise(NULL, 0, MADV_PROTECT) != 0) 139199804Sattilio log_it("CRON", getpid(), "WARNING", "madvise() failed"); 140199804Sattilio 141149430Spjd pidfile_write(pfh); 1422311Sjkh database.head = NULL; 1432311Sjkh database.tail = NULL; 1442311Sjkh database.mtime = (time_t) 0; 1452311Sjkh load_database(&database); 1462311Sjkh run_reboot_jobs(&database); 1472311Sjkh cron_sync(); 1482311Sjkh while (TRUE) { 1492311Sjkh# if DEBUGGING 15016859Swosch /* if (!(DebugFlags & DTEST)) */ 1512311Sjkh# endif /*DEBUGGING*/ 15274010Sbabkin cron_sleep(&database); 1532311Sjkh 1542311Sjkh load_database(&database); 1552311Sjkh 1562311Sjkh /* do this iteration 1572311Sjkh */ 1582311Sjkh cron_tick(&database); 1592311Sjkh 1602311Sjkh /* sleep 1 minute 1612311Sjkh */ 1622311Sjkh TargetTime += 60; 1632311Sjkh } 1642311Sjkh} 1652311Sjkh 1662311Sjkh 1672311Sjkhstatic void 1682311Sjkhrun_reboot_jobs(db) 1692311Sjkh cron_db *db; 1702311Sjkh{ 1712311Sjkh register user *u; 1722311Sjkh register entry *e; 1732311Sjkh 1742311Sjkh for (u = db->head; u != NULL; u = u->next) { 1752311Sjkh for (e = u->crontab; e != NULL; e = e->next) { 1762311Sjkh if (e->flags & WHEN_REBOOT) { 1772311Sjkh job_add(e, u); 1782311Sjkh } 1792311Sjkh } 1802311Sjkh } 1812311Sjkh (void) job_runqueue(); 1822311Sjkh} 1832311Sjkh 1842311Sjkh 1852311Sjkhstatic void 1862311Sjkhcron_tick(db) 1872311Sjkh cron_db *db; 1882311Sjkh{ 18974010Sbabkin static struct tm lasttm; 19074010Sbabkin static time_t diff = 0, /* time difference in seconds from the last offset change */ 19174010Sbabkin difflimit = 0; /* end point for the time zone correction */ 19274010Sbabkin struct tm otztm; /* time in the old time zone */ 19374010Sbabkin int otzminute, otzhour, otzdom, otzmonth, otzdow; 1942311Sjkh register struct tm *tm = localtime(&TargetTime); 1952311Sjkh register int minute, hour, dom, month, dow; 1962311Sjkh register user *u; 1972311Sjkh register entry *e; 1982311Sjkh 1992311Sjkh /* make 0-based values out of these so we can use them as indicies 2002311Sjkh */ 2012311Sjkh minute = tm->tm_min -FIRST_MINUTE; 2022311Sjkh hour = tm->tm_hour -FIRST_HOUR; 2032311Sjkh dom = tm->tm_mday -FIRST_DOM; 2042311Sjkh month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; 2052311Sjkh dow = tm->tm_wday -FIRST_DOW; 2062311Sjkh 2072311Sjkh Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d)\n", 2082311Sjkh getpid(), minute, hour, dom, month, dow)) 2092311Sjkh 21074010Sbabkin if (dst_enabled && last_time != 0 21174010Sbabkin && TargetTime > last_time /* exclude stepping back */ 21274010Sbabkin && tm->tm_gmtoff != lasttm.tm_gmtoff ) { 21374010Sbabkin 21474010Sbabkin diff = tm->tm_gmtoff - lasttm.tm_gmtoff; 21574010Sbabkin 21674010Sbabkin if ( diff > 0 ) { /* ST->DST */ 21774010Sbabkin /* mark jobs for an earlier run */ 21874010Sbabkin difflimit = TargetTime + diff; 21974010Sbabkin for (u = db->head; u != NULL; u = u->next) { 22074010Sbabkin for (e = u->crontab; e != NULL; e = e->next) { 22174010Sbabkin e->flags &= ~NOT_UNTIL; 22274010Sbabkin if ( e->lastrun >= TargetTime ) 22374010Sbabkin e->lastrun = 0; 22474010Sbabkin /* not include the ends of hourly ranges */ 22574010Sbabkin if ( e->lastrun < TargetTime - 3600 ) 22674010Sbabkin e->flags |= RUN_AT; 22774010Sbabkin else 22874010Sbabkin e->flags &= ~RUN_AT; 22974010Sbabkin } 23074010Sbabkin } 23174010Sbabkin } else { /* diff < 0 : DST->ST */ 23274010Sbabkin /* mark jobs for skipping */ 23374010Sbabkin difflimit = TargetTime - diff; 23474010Sbabkin for (u = db->head; u != NULL; u = u->next) { 23574010Sbabkin for (e = u->crontab; e != NULL; e = e->next) { 23674010Sbabkin e->flags |= NOT_UNTIL; 23774010Sbabkin e->flags &= ~RUN_AT; 23874010Sbabkin } 23974010Sbabkin } 24074010Sbabkin } 24174010Sbabkin } 24274010Sbabkin 24374010Sbabkin if (diff != 0) { 24474010Sbabkin /* if the time was reset of the end of special zone is reached */ 24574010Sbabkin if (last_time == 0 || TargetTime >= difflimit) { 24674010Sbabkin /* disable the TZ switch checks */ 24774010Sbabkin diff = 0; 24874010Sbabkin difflimit = 0; 24974010Sbabkin for (u = db->head; u != NULL; u = u->next) { 25074010Sbabkin for (e = u->crontab; e != NULL; e = e->next) { 25174010Sbabkin e->flags &= ~(RUN_AT|NOT_UNTIL); 25274010Sbabkin } 25374010Sbabkin } 25474010Sbabkin } else { 25574010Sbabkin /* get the time in the old time zone */ 25674010Sbabkin time_t difftime = TargetTime + tm->tm_gmtoff - diff; 25774010Sbabkin gmtime_r(&difftime, &otztm); 25874010Sbabkin 25974010Sbabkin /* make 0-based values out of these so we can use them as indicies 26074010Sbabkin */ 26174010Sbabkin otzminute = otztm.tm_min -FIRST_MINUTE; 26274010Sbabkin otzhour = otztm.tm_hour -FIRST_HOUR; 26374010Sbabkin otzdom = otztm.tm_mday -FIRST_DOM; 26474010Sbabkin otzmonth = otztm.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; 26574010Sbabkin otzdow = otztm.tm_wday -FIRST_DOW; 26674010Sbabkin } 26774010Sbabkin } 26874010Sbabkin 2692311Sjkh /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the 2702311Sjkh * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* 2712311Sjkh * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this 2722311Sjkh * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. 2732311Sjkh * like many bizarre things, it's the standard. 2742311Sjkh */ 2752311Sjkh for (u = db->head; u != NULL; u = u->next) { 2762311Sjkh for (e = u->crontab; e != NULL; e = e->next) { 2772311Sjkh Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n", 2782311Sjkh env_get("LOGNAME", e->envp), 2792311Sjkh e->uid, e->gid, e->cmd)) 28074010Sbabkin 28174010Sbabkin if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) { 28274010Sbabkin if (bit_test(e->minute, otzminute) 28374010Sbabkin && bit_test(e->hour, otzhour) 28474010Sbabkin && bit_test(e->month, otzmonth) 28574010Sbabkin && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) 28674010Sbabkin ? (bit_test(e->dow,otzdow) && bit_test(e->dom,otzdom)) 28774010Sbabkin : (bit_test(e->dow,otzdow) || bit_test(e->dom,otzdom)) 28874010Sbabkin ) 28974010Sbabkin ) { 29074010Sbabkin if ( e->flags & RUN_AT ) { 29174010Sbabkin e->flags &= ~RUN_AT; 29274010Sbabkin e->lastrun = TargetTime; 29374010Sbabkin job_add(e, u); 29474010Sbabkin continue; 29574010Sbabkin } else 29674010Sbabkin e->flags &= ~NOT_UNTIL; 29774010Sbabkin } else if ( e->flags & NOT_UNTIL ) 29874010Sbabkin continue; 29974010Sbabkin } 30074010Sbabkin 30171407Sbabkin if (bit_test(e->minute, minute) 3022311Sjkh && bit_test(e->hour, hour) 3032311Sjkh && bit_test(e->month, month) 3042311Sjkh && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) 3052311Sjkh ? (bit_test(e->dow,dow) && bit_test(e->dom,dom)) 3062311Sjkh : (bit_test(e->dow,dow) || bit_test(e->dom,dom)) 3072311Sjkh ) 3082311Sjkh ) { 30974010Sbabkin e->flags &= ~RUN_AT; 31074010Sbabkin e->lastrun = TargetTime; 3112311Sjkh job_add(e, u); 3122311Sjkh } 3132311Sjkh } 3142311Sjkh } 31574010Sbabkin 31674010Sbabkin last_time = TargetTime; 31774010Sbabkin lasttm = *tm; 3182311Sjkh} 3192311Sjkh 3202311Sjkh 3212311Sjkh/* the task here is to figure out how long it's going to be until :00 of the 3222311Sjkh * following minute and initialize TargetTime to this value. TargetTime 3232311Sjkh * will subsequently slide 60 seconds at a time, with correction applied 3242311Sjkh * implicitly in cron_sleep(). it would be nice to let cron execute in 3252311Sjkh * the "current minute" before going to sleep, but by restarting cron you 3262311Sjkh * could then get it to execute a given minute's jobs more than once. 3272311Sjkh * instead we have the chance of missing a minute's jobs completely, but 3282311Sjkh * that's something sysadmin's know to expect what with crashing computers.. 3292311Sjkh */ 3302311Sjkhstatic void 3312311Sjkhcron_sync() { 3322311Sjkh register struct tm *tm; 3332311Sjkh 3342311Sjkh TargetTime = time((time_t*)0); 3352311Sjkh tm = localtime(&TargetTime); 3362311Sjkh TargetTime += (60 - tm->tm_sec); 3372311Sjkh} 3382311Sjkh 3392311Sjkh 3402311Sjkhstatic void 34174010Sbabkincron_sleep(db) 34274010Sbabkin cron_db *db; 34374010Sbabkin{ 34441723Sdillon int seconds_to_wait = 0; 3452311Sjkh 34641723Sdillon /* 34741723Sdillon * Loop until we reach the top of the next minute, sleep when possible. 34841723Sdillon */ 34941723Sdillon 35041723Sdillon for (;;) { 3512311Sjkh seconds_to_wait = (int) (TargetTime - time((time_t*)0)); 35241723Sdillon 35341723Sdillon /* 35441723Sdillon * If the seconds_to_wait value is insane, jump the cron 35541723Sdillon */ 35641723Sdillon 35741723Sdillon if (seconds_to_wait < -600 || seconds_to_wait > 600) { 35874010Sbabkin cron_clean(db); 35941723Sdillon cron_sync(); 36041723Sdillon continue; 36141723Sdillon } 36241723Sdillon 3632311Sjkh Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n", 36437450Sbde getpid(), (long)TargetTime, seconds_to_wait)) 3652311Sjkh 36641723Sdillon /* 36741723Sdillon * If we've run out of wait time or there are no jobs left 36841723Sdillon * to run, break 3692311Sjkh */ 3702311Sjkh 37141723Sdillon if (seconds_to_wait <= 0) 37241723Sdillon break; 37341723Sdillon if (job_runqueue() == 0) { 37441723Sdillon Debug(DSCH, ("[%d] sleeping for %d seconds\n", 37541723Sdillon getpid(), seconds_to_wait)) 37641723Sdillon 37741723Sdillon sleep(seconds_to_wait); 37841723Sdillon } 3792311Sjkh } 3802311Sjkh} 3812311Sjkh 3822311Sjkh 38374010Sbabkin/* if the time was changed abruptly, clear the flags related 38474010Sbabkin * to the daylight time switch handling to avoid strange effects 38574010Sbabkin */ 38674010Sbabkin 38774010Sbabkinstatic void 38874010Sbabkincron_clean(db) 38974010Sbabkin cron_db *db; 39074010Sbabkin{ 39174010Sbabkin user *u; 39274010Sbabkin entry *e; 39374010Sbabkin 39474010Sbabkin last_time = 0; 39574010Sbabkin 39674010Sbabkin for (u = db->head; u != NULL; u = u->next) { 39774010Sbabkin for (e = u->crontab; e != NULL; e = e->next) { 39874010Sbabkin e->flags &= ~(RUN_AT|NOT_UNTIL); 39974010Sbabkin } 40074010Sbabkin } 40174010Sbabkin} 40274010Sbabkin 4032311Sjkh#ifdef USE_SIGCHLD 4042311Sjkhstatic void 405160521Sstefanfsigchld_handler(int x) 406160521Sstefanf{ 4072311Sjkh WAIT_T waiter; 4082311Sjkh PID_T pid; 4092311Sjkh 4102311Sjkh for (;;) { 4112311Sjkh#ifdef POSIX 4122311Sjkh pid = waitpid(-1, &waiter, WNOHANG); 4132311Sjkh#else 4142311Sjkh pid = wait3(&waiter, WNOHANG, (struct rusage *)0); 4152311Sjkh#endif 4162311Sjkh switch (pid) { 4172311Sjkh case -1: 4182311Sjkh Debug(DPROC, 4192311Sjkh ("[%d] sigchld...no children\n", getpid())) 4202311Sjkh return; 4212311Sjkh case 0: 4222311Sjkh Debug(DPROC, 4232311Sjkh ("[%d] sigchld...no dead kids\n", getpid())) 4242311Sjkh return; 4252311Sjkh default: 4262311Sjkh Debug(DPROC, 4272311Sjkh ("[%d] sigchld...pid #%d died, stat=%d\n", 4282311Sjkh getpid(), pid, WEXITSTATUS(waiter))) 4292311Sjkh } 4302311Sjkh } 4312311Sjkh} 4322311Sjkh#endif /*USE_SIGCHLD*/ 4332311Sjkh 4342311Sjkh 4352311Sjkhstatic void 436160521Sstefanfsighup_handler(int x) 437160521Sstefanf{ 4382311Sjkh log_close(); 4392311Sjkh} 4402311Sjkh 4412311Sjkh 4422311Sjkhstatic void 4432311Sjkhparse_args(argc, argv) 4442311Sjkh int argc; 4452311Sjkh char *argv[]; 4462311Sjkh{ 4472311Sjkh int argch; 448129280Syar char *endp; 4492311Sjkh 450180096Smarck while ((argch = getopt(argc, argv, "j:J:m:osx:")) != -1) { 4512311Sjkh switch (argch) { 452129280Syar case 'j': 453129280Syar Jitter = strtoul(optarg, &endp, 10); 454129280Syar if (*optarg == '\0' || *endp != '\0' || Jitter > 60) 455129280Syar errx(ERROR_EXIT, 456129280Syar "bad value for jitter: %s", optarg); 457129280Syar break; 458129280Syar case 'J': 459129280Syar RootJitter = strtoul(optarg, &endp, 10); 460129280Syar if (*optarg == '\0' || *endp != '\0' || RootJitter > 60) 461129280Syar errx(ERROR_EXIT, 462129280Syar "bad value for root jitter: %s", optarg); 463129280Syar break; 464180096Smarck case 'm': 465180096Smarck defmailto = optarg; 466180096Smarck break; 46774010Sbabkin case 'o': 46874010Sbabkin dst_enabled = 0; 46974010Sbabkin break; 47074010Sbabkin case 's': 47174010Sbabkin dst_enabled = 1; 47274010Sbabkin break; 4732311Sjkh case 'x': 4742311Sjkh if (!set_debug_flags(optarg)) 4752311Sjkh usage(); 4762311Sjkh break; 47716859Swosch default: 47816859Swosch usage(); 4792311Sjkh } 4802311Sjkh } 4812311Sjkh} 48274010Sbabkin 483