1/* Copyright 1988,1990,1993,1994 by Paul Vixie
2 * All rights reserved
3 *
4 * Distribute freely, except: don't remove my name from the source or
5 * documentation (don't take credit for my work), mark your changes (don't
6 * get me blamed for your possible bugs), don't alter or remove this
7 * notice.  May be sold if buildable source is provided to buyer.  No
8 * warrantee of any kind, express or implied, is included with this
9 * software; use at your own risk, responsibility for damages (if any) to
10 * anyone resulting from the use of this software rests entirely with the
11 * user.
12 *
13 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
14 * I'll try to keep a version up to date.  I can be reached as follows:
15 * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
16 */
17
18#if !defined(lint) && !defined(LINT)
19static const char rcsid[] =
20  "$FreeBSD$";
21#endif
22
23#define	MAIN_PROGRAM
24
25
26#include "cron.h"
27#include <sys/mman.h>
28#include <sys/signal.h>
29#if SYS_TIME_H
30# include <sys/time.h>
31#else
32# include <time.h>
33#endif
34
35
36static	void	usage(void),
37		run_reboot_jobs(cron_db *),
38		cron_tick(cron_db *, int),
39		cron_sync(int),
40		cron_sleep(cron_db *, int),
41		cron_clean(cron_db *),
42#ifdef USE_SIGCHLD
43		sigchld_handler(int),
44#endif
45		sighup_handler(int),
46		parse_args(int c, char *v[]);
47
48static int	run_at_secres(cron_db *);
49
50static time_t	last_time = 0;
51static int	dst_enabled = 0;
52struct pidfh *pfh;
53
54static void
55usage() {
56#if DEBUGGING
57    char **dflags;
58#endif
59
60	fprintf(stderr, "usage: cron [-j jitter] [-J rootjitter] "
61			"[-m mailto] [-s] [-o] [-x debugflag[,...]]\n");
62#if DEBUGGING
63	fprintf(stderr, "\ndebugflags: ");
64
65        for(dflags = DebugFlagNames; *dflags; dflags++) {
66		fprintf(stderr, "%s ", *dflags);
67	}
68        fprintf(stderr, "\n");
69#endif
70
71	exit(ERROR_EXIT);
72}
73
74static void
75open_pidfile(void)
76{
77	char	pidfile[MAX_FNAME];
78	char	buf[MAX_TEMPSTR];
79	int	otherpid;
80
81	(void) snprintf(pidfile, sizeof(pidfile), PIDFILE, PIDDIR);
82	pfh = pidfile_open(pidfile, 0600, &otherpid);
83	if (pfh == NULL) {
84		if (errno == EEXIST) {
85			snprintf(buf, sizeof(buf),
86			    "cron already running, pid: %d", otherpid);
87		} else {
88			snprintf(buf, sizeof(buf),
89			    "can't open or create %s: %s", pidfile,
90			    strerror(errno));
91		}
92		log_it("CRON", getpid(), "DEATH", buf);
93		errx(ERROR_EXIT, "%s", buf);
94	}
95}
96
97int
98main(argc, argv)
99	int	argc;
100	char	*argv[];
101{
102	cron_db	database;
103	int runnum;
104	int secres1, secres2;
105	struct tm *tm;
106
107	ProgramName = argv[0];
108
109#if defined(BSD)
110	setlinebuf(stdout);
111	setlinebuf(stderr);
112#endif
113
114	parse_args(argc, argv);
115
116#ifdef USE_SIGCHLD
117	(void) signal(SIGCHLD, sigchld_handler);
118#else
119	(void) signal(SIGCLD, SIG_IGN);
120#endif
121	(void) signal(SIGHUP, sighup_handler);
122
123	open_pidfile();
124	set_cron_uid();
125	set_cron_cwd();
126
127#if defined(POSIX)
128	setenv("PATH", _PATH_DEFPATH, 1);
129#endif
130
131	/* if there are no debug flags turned on, fork as a daemon should.
132	 */
133# if DEBUGGING
134	if (DebugFlags) {
135# else
136	if (0) {
137# endif
138		(void) fprintf(stderr, "[%d] cron started\n", getpid());
139	} else {
140		if (daemon(1, 0) == -1) {
141			pidfile_remove(pfh);
142			log_it("CRON",getpid(),"DEATH","can't become daemon");
143			exit(0);
144		}
145	}
146
147	if (madvise(NULL, 0, MADV_PROTECT) != 0)
148		log_it("CRON", getpid(), "WARNING", "madvise() failed");
149
150	pidfile_write(pfh);
151	database.head = NULL;
152	database.tail = NULL;
153	database.mtime = (time_t) 0;
154	load_database(&database);
155	secres1 = secres2 = run_at_secres(&database);
156	run_reboot_jobs(&database);
157	cron_sync(secres1);
158	runnum = 0;
159	while (TRUE) {
160# if DEBUGGING
161	    /* if (!(DebugFlags & DTEST)) */
162# endif /*DEBUGGING*/
163			cron_sleep(&database, secres1);
164
165		if (secres1 == 0 || runnum % 60 == 0) {
166			load_database(&database);
167			secres2 = run_at_secres(&database);
168			if (secres2 != secres1) {
169				secres1 = secres2;
170				if (secres1 != 0) {
171					runnum = 0;
172				} else {
173					/*
174					 * Going from 1 sec to 60 sec res. If we
175					 * are already at minute's boundary, so
176					 * let it run, otherwise schedule for the
177					 * next minute.
178					 */
179					tm = localtime(&TargetTime);
180					if (tm->tm_sec > 0)  {
181						cron_sync(secres2);
182						continue;
183					}
184				}
185			}
186		}
187
188		/* do this iteration
189		 */
190		cron_tick(&database, secres1);
191
192		/* sleep 1 or 60 seconds
193		 */
194		TargetTime += (secres1 != 0) ? 1 : 60;
195		runnum += 1;
196	}
197}
198
199
200static void
201run_reboot_jobs(db)
202	cron_db *db;
203{
204	register user		*u;
205	register entry		*e;
206
207	for (u = db->head;  u != NULL;  u = u->next) {
208		for (e = u->crontab;  e != NULL;  e = e->next) {
209			if (e->flags & WHEN_REBOOT) {
210				job_add(e, u);
211			}
212		}
213	}
214	(void) job_runqueue();
215}
216
217
218static void
219cron_tick(cron_db *db, int secres)
220{
221	static struct tm	lasttm;
222	static time_t	diff = 0, /* time difference in seconds from the last offset change */
223		difflimit = 0; /* end point for the time zone correction */
224	struct tm	otztm; /* time in the old time zone */
225	int		otzsecond, otzminute, otzhour, otzdom, otzmonth, otzdow;
226 	register struct tm	*tm = localtime(&TargetTime);
227	register int		second, minute, hour, dom, month, dow;
228	register user		*u;
229	register entry		*e;
230
231	/* make 0-based values out of these so we can use them as indicies
232	 */
233	second = (secres == 0) ? 0 : tm->tm_sec -FIRST_SECOND;
234	minute = tm->tm_min -FIRST_MINUTE;
235	hour = tm->tm_hour -FIRST_HOUR;
236	dom = tm->tm_mday -FIRST_DOM;
237	month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
238	dow = tm->tm_wday -FIRST_DOW;
239
240	Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d,%d)\n",
241		getpid(), second, minute, hour, dom, month, dow))
242
243	if (dst_enabled && last_time != 0
244	&& TargetTime > last_time /* exclude stepping back */
245	&& tm->tm_gmtoff != lasttm.tm_gmtoff ) {
246
247		diff = tm->tm_gmtoff - lasttm.tm_gmtoff;
248
249		if ( diff > 0 ) { /* ST->DST */
250			/* mark jobs for an earlier run */
251			difflimit = TargetTime + diff;
252			for (u = db->head;  u != NULL;  u = u->next) {
253				for (e = u->crontab;  e != NULL;  e = e->next) {
254					e->flags &= ~NOT_UNTIL;
255					if ( e->lastrun >= TargetTime )
256						e->lastrun = 0;
257					/* not include the ends of hourly ranges */
258					if ( e->lastrun < TargetTime - 3600 )
259						e->flags |= RUN_AT;
260					else
261						e->flags &= ~RUN_AT;
262				}
263			}
264		} else { /* diff < 0 : DST->ST */
265			/* mark jobs for skipping */
266			difflimit = TargetTime - diff;
267			for (u = db->head;  u != NULL;  u = u->next) {
268				for (e = u->crontab;  e != NULL;  e = e->next) {
269					e->flags |= NOT_UNTIL;
270					e->flags &= ~RUN_AT;
271				}
272			}
273		}
274	}
275
276	if (diff != 0) {
277		/* if the time was reset of the end of special zone is reached */
278		if (last_time == 0 || TargetTime >= difflimit) {
279			/* disable the TZ switch checks */
280			diff = 0;
281			difflimit = 0;
282			for (u = db->head;  u != NULL;  u = u->next) {
283				for (e = u->crontab;  e != NULL;  e = e->next) {
284					e->flags &= ~(RUN_AT|NOT_UNTIL);
285				}
286			}
287		} else {
288			/* get the time in the old time zone */
289			time_t difftime = TargetTime + tm->tm_gmtoff - diff;
290			gmtime_r(&difftime, &otztm);
291
292			/* make 0-based values out of these so we can use them as indicies
293			 */
294			otzsecond = (secres == 0) ? 0 : otztm.tm_sec -FIRST_SECOND;
295			otzminute = otztm.tm_min -FIRST_MINUTE;
296			otzhour = otztm.tm_hour -FIRST_HOUR;
297			otzdom = otztm.tm_mday -FIRST_DOM;
298			otzmonth = otztm.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
299			otzdow = otztm.tm_wday -FIRST_DOW;
300		}
301	}
302
303	/* the dom/dow situation is odd.  '* * 1,15 * Sun' will run on the
304	 * first and fifteenth AND every Sunday;  '* * * * Sun' will run *only*
305	 * on Sundays;  '* * 1,15 * *' will run *only* the 1st and 15th.  this
306	 * is why we keep 'e->dow_star' and 'e->dom_star'.  yes, it's bizarre.
307	 * like many bizarre things, it's the standard.
308	 */
309	for (u = db->head;  u != NULL;  u = u->next) {
310		for (e = u->crontab;  e != NULL;  e = e->next) {
311			Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n",
312					  env_get("LOGNAME", e->envp),
313					  e->uid, e->gid, e->cmd))
314
315			if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) {
316				if (bit_test(e->second, otzsecond)
317				 && bit_test(e->minute, otzminute)
318				 && bit_test(e->hour, otzhour)
319				 && bit_test(e->month, otzmonth)
320				 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
321					  ? (bit_test(e->dow,otzdow) && bit_test(e->dom,otzdom))
322					  : (bit_test(e->dow,otzdow) || bit_test(e->dom,otzdom))
323					)
324				   ) {
325					if ( e->flags & RUN_AT ) {
326						e->flags &= ~RUN_AT;
327						e->lastrun = TargetTime;
328						job_add(e, u);
329						continue;
330					} else
331						e->flags &= ~NOT_UNTIL;
332				} else if ( e->flags & NOT_UNTIL )
333					continue;
334			}
335
336			if (bit_test(e->second, second)
337			 && bit_test(e->minute, minute)
338			 && bit_test(e->hour, hour)
339			 && bit_test(e->month, month)
340			 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
341			      ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
342			      : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
343			    )
344			   ) {
345				e->flags &= ~RUN_AT;
346				e->lastrun = TargetTime;
347				job_add(e, u);
348			}
349		}
350	}
351
352	last_time = TargetTime;
353	lasttm = *tm;
354}
355
356
357/* the task here is to figure out how long it's going to be until :00 of the
358 * following minute and initialize TargetTime to this value.  TargetTime
359 * will subsequently slide 60 seconds at a time, with correction applied
360 * implicitly in cron_sleep().  it would be nice to let cron execute in
361 * the "current minute" before going to sleep, but by restarting cron you
362 * could then get it to execute a given minute's jobs more than once.
363 * instead we have the chance of missing a minute's jobs completely, but
364 * that's something sysadmin's know to expect what with crashing computers..
365 */
366static void
367cron_sync(int secres) {
368 	struct tm *tm;
369
370	TargetTime = time((time_t*)0);
371	if (secres != 0) {
372		TargetTime += 1;
373	} else {
374		tm = localtime(&TargetTime);
375		TargetTime += (60 - tm->tm_sec);
376	}
377}
378
379static void
380timespec_subtract(struct timespec *result, struct timespec *x,
381    struct timespec *y)
382{
383	*result = *x;
384	result->tv_sec -= y->tv_sec;
385	result->tv_nsec -= y->tv_nsec;
386	if (result->tv_nsec < 0) {
387		result->tv_sec--;
388		result->tv_nsec += 1000000000;
389	}
390}
391
392static void
393cron_sleep(cron_db *db, int secres)
394{
395	int seconds_to_wait;
396	int rval;
397	struct timespec ctime, ttime, stime, remtime;
398
399	/*
400	 * Loop until we reach the top of the next minute, sleep when possible.
401	 */
402
403	for (;;) {
404		clock_gettime(CLOCK_REALTIME, &ctime);
405		ttime.tv_sec = TargetTime;
406		ttime.tv_nsec = 0;
407		timespec_subtract(&stime, &ttime, &ctime);
408
409		/*
410		 * If the seconds_to_wait value is insane, jump the cron
411		 */
412
413		if (stime.tv_sec < -600 || stime.tv_sec > 600) {
414			cron_clean(db);
415			cron_sync(secres);
416			continue;
417		}
418
419		seconds_to_wait = (stime.tv_nsec > 0) ? stime.tv_sec + 1 :
420		    stime.tv_sec;
421
422		Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
423			getpid(), (long)TargetTime, seconds_to_wait))
424
425		/*
426		 * If we've run out of wait time or there are no jobs left
427		 * to run, break
428		 */
429
430		if (stime.tv_sec < 0)
431			break;
432		if (job_runqueue() == 0) {
433			Debug(DSCH, ("[%d] sleeping for %d seconds\n",
434				getpid(), seconds_to_wait))
435
436			for (;;) {
437				rval = nanosleep(&stime, &remtime);
438				if (rval == 0 || errno != EINTR)
439					break;
440				stime.tv_sec = remtime.tv_sec;
441				stime.tv_nsec = remtime.tv_nsec;
442			}
443		}
444	}
445}
446
447
448/* if the time was changed abruptly, clear the flags related
449 * to the daylight time switch handling to avoid strange effects
450 */
451
452static void
453cron_clean(db)
454	cron_db	*db;
455{
456	user		*u;
457	entry		*e;
458
459	last_time = 0;
460
461	for (u = db->head;  u != NULL;  u = u->next) {
462		for (e = u->crontab;  e != NULL;  e = e->next) {
463			e->flags &= ~(RUN_AT|NOT_UNTIL);
464		}
465	}
466}
467
468#ifdef USE_SIGCHLD
469static void
470sigchld_handler(int x)
471{
472	WAIT_T		waiter;
473	PID_T		pid;
474
475	for (;;) {
476#ifdef POSIX
477		pid = waitpid(-1, &waiter, WNOHANG);
478#else
479		pid = wait3(&waiter, WNOHANG, (struct rusage *)0);
480#endif
481		switch (pid) {
482		case -1:
483			Debug(DPROC,
484				("[%d] sigchld...no children\n", getpid()))
485			return;
486		case 0:
487			Debug(DPROC,
488				("[%d] sigchld...no dead kids\n", getpid()))
489			return;
490		default:
491			Debug(DPROC,
492				("[%d] sigchld...pid #%d died, stat=%d\n",
493				getpid(), pid, WEXITSTATUS(waiter)))
494		}
495	}
496}
497#endif /*USE_SIGCHLD*/
498
499
500static void
501sighup_handler(int x)
502{
503	log_close();
504}
505
506
507static void
508parse_args(argc, argv)
509	int	argc;
510	char	*argv[];
511{
512	int	argch;
513	char	*endp;
514
515	while ((argch = getopt(argc, argv, "j:J:m:osx:")) != -1) {
516		switch (argch) {
517		case 'j':
518			Jitter = strtoul(optarg, &endp, 10);
519			if (*optarg == '\0' || *endp != '\0' || Jitter > 60)
520				errx(ERROR_EXIT,
521				     "bad value for jitter: %s", optarg);
522			break;
523		case 'J':
524			RootJitter = strtoul(optarg, &endp, 10);
525			if (*optarg == '\0' || *endp != '\0' || RootJitter > 60)
526				errx(ERROR_EXIT,
527				     "bad value for root jitter: %s", optarg);
528			break;
529		case 'm':
530			defmailto = optarg;
531			break;
532		case 'o':
533			dst_enabled = 0;
534			break;
535		case 's':
536			dst_enabled = 1;
537			break;
538		case 'x':
539			if (!set_debug_flags(optarg))
540				usage();
541			break;
542		default:
543			usage();
544		}
545	}
546}
547
548static int
549run_at_secres(cron_db *db)
550{
551	user *u;
552	entry *e;
553
554	for (u = db->head;  u != NULL;  u = u->next) {
555		for (e = u->crontab;  e != NULL;  e = e->next) {
556			if ((e->flags & SEC_RES) != 0)
557				return 1;
558		}
559	}
560	return 0;
561}
562