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