1/*	$NetBSD: cron.c,v 1.7 2010/10/02 12:22:20 tron Exp $	*/
2
3/* Copyright 1988,1990,1993,1994 by Paul Vixie
4 * All rights reserved
5 */
6
7/*
8 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
9 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
10 *
11 * Permission to use, copy, modify, and distribute this software for any
12 * purpose with or without fee is hereby granted, provided that the above
13 * copyright notice and this permission notice appear in all copies.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
21 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 */
23#include <sys/cdefs.h>
24#if !defined(lint) && !defined(LINT)
25#if 0
26static char rcsid[] = "Id: cron.c,v 1.12 2004/01/23 18:56:42 vixie Exp";
27#else
28__RCSID("$NetBSD: cron.c,v 1.7 2010/10/02 12:22:20 tron Exp $");
29#endif
30#endif
31
32#define	MAIN_PROGRAM
33
34#include "cron.h"
35
36enum timejump { negative, small, medium, large };
37
38static	void	usage(void),
39		run_reboot_jobs(cron_db *),
40		find_jobs(time_t, cron_db *, int, int),
41		set_time(int),
42		cron_sleep(int),
43		sigchld_handler(int),
44		sighup_handler(int),
45		sigchld_reaper(void),
46		quit(int),
47		parse_args(int c, char *v[]);
48
49static	volatile sig_atomic_t	got_sighup, got_sigchld;
50static	time_t			timeRunning, virtualTime, clockTime;
51static	long			GMToff;
52
53static void
54usage(void) {
55	const char **dflags;
56
57	(void)fprintf(stderr, "usage:  %s [-n] [-x [", getprogname());
58	for (dflags = DebugFlagNames; *dflags; dflags++)
59		(void)fprintf(stderr, "%s%s", *dflags, dflags[1] ? "," : "]");
60	(void)fprintf(stderr, "]\n");
61	exit(ERROR_EXIT);
62}
63
64int
65main(int argc, char *argv[]) {
66	struct sigaction sact;
67	cron_db	database;
68
69	setprogname(argv[0]);
70	(void)setlocale(LC_ALL, "");
71
72	(void)setvbuf(stdout, NULL, _IOLBF, 0);
73	(void)setvbuf(stderr, NULL, _IOLBF, 0);
74
75	NoFork = 0;
76	parse_args(argc, argv);
77
78	(void)memset(&sact, 0, sizeof sact);
79	(void)sigemptyset(&sact.sa_mask);
80	sact.sa_flags = 0;
81#ifdef SA_RESTART
82	sact.sa_flags |= SA_RESTART;
83#endif
84	sact.sa_handler = sigchld_handler;
85	(void) sigaction(SIGCHLD, &sact, NULL);
86	sact.sa_handler = sighup_handler;
87	(void) sigaction(SIGHUP, &sact, NULL);
88	sact.sa_handler = quit;
89	(void) sigaction(SIGINT, &sact, NULL);
90	(void) sigaction(SIGTERM, &sact, NULL);
91
92	acquire_daemonlock(0);
93	set_cron_uid();
94	set_cron_cwd();
95
96	if (setenv("PATH", _PATH_DEFPATH, 1) < 0) {
97		log_it("CRON", getpid(), "DEATH", "can't malloc");
98		exit(1);
99	}
100
101	/* if there are no debug flags turned on, fork as a daemon should.
102	 */
103	if (DebugFlags) {
104#if DEBUGGING
105		(void)fprintf(stderr, "[%ld] cron started\n", (long)getpid());
106#endif
107	} else if (NoFork == 0) {
108		if (daemon(1, 0)) {
109			log_it("CRON",getpid(),"DEATH","can't fork");
110			exit(1);
111		}
112	}
113
114	acquire_daemonlock(0);
115	database.head = NULL;
116	database.tail = NULL;
117	database.mtime = (time_t) 0;
118	load_database(&database);
119	set_time(TRUE);
120	run_reboot_jobs(&database);
121	timeRunning = virtualTime = clockTime;
122
123	/*
124	 * Too many clocks, not enough time (Al. Einstein)
125	 * These clocks are in minutes since the epoch, adjusted for timezone.
126	 * virtualTime: is the time it *would* be if we woke up
127	 * promptly and nobody ever changed the clock. It is
128	 * monotonically increasing... unless a timejump happens.
129	 * At the top of the loop, all jobs for 'virtualTime' have run.
130	 * timeRunning: is the time we last awakened.
131	 * clockTime: is the time when set_time was last called.
132	 */
133	for (;;) {
134		int timeDiff;
135		enum timejump wakeupKind;
136
137		/* ... wait for the time (in minutes) to change ... */
138		do {
139			cron_sleep(timeRunning + 1);
140			set_time(FALSE);
141		} while (clockTime == timeRunning);
142		timeRunning = clockTime;
143
144		/*
145		 * Calculate how the current time differs from our virtual
146		 * clock.  Classify the change into one of 4 cases.
147		 */
148		timeDiff = timeRunning - virtualTime;
149
150		/* shortcut for the most common case */
151		if (timeDiff == 1) {
152			virtualTime = timeRunning;
153			find_jobs(virtualTime, &database, TRUE, TRUE);
154		} else {
155			if (timeDiff > (3*MINUTE_COUNT) ||
156			    timeDiff < -(3*MINUTE_COUNT))
157				wakeupKind = large;
158			else if (timeDiff > 5)
159				wakeupKind = medium;
160			else if (timeDiff > 0)
161				wakeupKind = small;
162			else
163				wakeupKind = negative;
164
165			switch (wakeupKind) {
166			case small:
167				/*
168				 * case 1: timeDiff is a small positive number
169				 * (wokeup late) run jobs for each virtual
170				 * minute until caught up.
171				 */
172				Debug(DSCH, ("[%ld], normal case %d minutes to go\n",
173				    (long)getpid(), timeDiff));
174				do {
175					if (job_runqueue())
176						(void)sleep(10);
177					virtualTime++;
178					find_jobs(virtualTime, &database,
179					    TRUE, TRUE);
180				} while (virtualTime < timeRunning);
181				break;
182
183			case medium:
184				/*
185				 * case 2: timeDiff is a medium-sized positive
186				 * number, for example because we went to DST
187				 * run wildcard jobs once, then run any
188				 * fixed-time jobs that would otherwise be
189				 * skipped if we use up our minute (possible,
190				 * if there are a lot of jobs to run) go
191				 * around the loop again so that wildcard jobs
192				 * have a chance to run, and we do our
193				 * housekeeping.
194				 */
195				Debug(DSCH, ("[%ld], DST begins %d minutes to go\n",
196				    (long)getpid(), timeDiff));
197				/* run wildcard jobs for current minute */
198				find_jobs(timeRunning, &database, TRUE, FALSE);
199
200				/* run fixed-time jobs for each minute missed */
201				do {
202					if (job_runqueue())
203						(void)sleep(10);
204					virtualTime++;
205					find_jobs(virtualTime, &database,
206					    FALSE, TRUE);
207					set_time(FALSE);
208				} while (virtualTime < timeRunning &&
209				    clockTime == timeRunning);
210				break;
211
212			case negative:
213				/*
214				 * case 3: timeDiff is a small or medium-sized
215				 * negative num, eg. because of DST ending.
216				 * Just run the wildcard jobs. The fixed-time
217				 * jobs probably have already run, and should
218				 * not be repeated.  Virtual time does not
219				 * change until we are caught up.
220				 */
221				Debug(DSCH, ("[%ld], DST ends %d minutes to go\n",
222				    (long)getpid(), timeDiff));
223				find_jobs(timeRunning, &database, TRUE, FALSE);
224				break;
225			default:
226				/*
227				 * other: time has changed a *lot*,
228				 * jump virtual time, and run everything
229				 */
230				Debug(DSCH, ("[%ld], clock jumped\n",
231				    (long)getpid()));
232				virtualTime = timeRunning;
233				find_jobs(timeRunning, &database, TRUE, TRUE);
234			}
235		}
236
237		/* Jobs to be run (if any) are loaded; clear the queue. */
238		(void)job_runqueue();
239
240		/* Check to see if we received a signal while running jobs. */
241		if (got_sighup) {
242			got_sighup = 0;
243			log_close();
244		}
245		if (got_sigchld) {
246			got_sigchld = 0;
247			sigchld_reaper();
248		}
249		load_database(&database);
250	}
251}
252
253static void
254run_reboot_jobs(cron_db *db) {
255	user *u;
256	entry *e;
257
258	for (u = db->head; u != NULL; u = u->next) {
259		for (e = u->crontab; e != NULL; e = e->next) {
260			if (e->flags & WHEN_REBOOT)
261				job_add(e, u, StartTime);
262		}
263	}
264	(void) job_runqueue();
265}
266
267static const char *
268bitprint(const bitstr_t *b, size_t l)
269{
270	size_t i, set, clear;
271	static char result[1024];
272	char tmp[1024];
273
274	result[0] = '\0';
275	for (i = 0;;) {
276		for (; i < l; i++)
277			if (bit_test(b, i))
278				break;
279		if (i == l)
280			return result;
281		set = i;
282		for (; i < l; i++)
283			if (!bit_test(b, i))
284				break;
285		clear = i;
286		if (set == 0 && clear == l) {
287			snprintf(result, sizeof(result), "*");
288			return result;
289		}
290		if (clear == l || set == clear - 1)
291			snprintf(tmp, sizeof(tmp), ",%zu", set);
292		else
293			snprintf(tmp, sizeof(tmp), ",%zu-%zu", set, clear - 1);
294		if (result[0] == '\0')
295			strlcpy(result, tmp + 1, sizeof(result));
296		else
297			strlcat(result, tmp, sizeof(result));
298	}
299}
300
301static const char *
302flagsprint(int flags)
303{
304	static char result[1024];
305
306	result[0] = '\0';
307	if (flags & MIN_STAR)
308		strlcat(result, ",min", sizeof(result));
309	if (flags & HR_STAR)
310		strlcat(result, ",hr", sizeof(result));
311	if (flags & DOM_STAR)
312		strlcat(result, ",dom", sizeof(result));
313	if (flags & DOW_STAR)
314		strlcat(result, ",dow", sizeof(result));
315	if (flags & WHEN_REBOOT)
316		strlcat(result, ",reboot", sizeof(result));
317	if (flags & DONT_LOG)
318		strlcat(result, ",nolog", sizeof(result));
319
320	return result + (result[0] == ',');
321}
322
323static void
324printone(char *res, size_t len, const char *b)
325{
326	int comma = strchr(b, ',') != NULL;
327	if (comma)
328		strlcat(res, "[", len);
329	strlcat(res, b, len);
330	strlcat(res, "]," + (comma ? 0 : 1), len);
331}
332
333static const char *
334tick(const entry *e)
335{
336	static char result[1024];
337
338	result[0] = '\0';
339	printone(result, sizeof(result), bitprint(e->minute, MINUTE_COUNT));
340	printone(result, sizeof(result), bitprint(e->hour, HOUR_COUNT));
341	printone(result, sizeof(result), bitprint(e->dom, DOM_COUNT));
342	printone(result, sizeof(result), bitprint(e->month, MONTH_COUNT));
343	printone(result, sizeof(result), bitprint(e->dow, DOW_COUNT));
344	strlcat(result, "flags=", sizeof(result));
345	strlcat(result, flagsprint(e->flags), sizeof(result));
346	return result;
347}
348
349static void
350find_jobs(time_t vtime, cron_db *db, int doWild, int doNonWild) {
351	time_t virtualSecond = vtime * SECONDS_PER_MINUTE;
352	struct tm *tm;
353	int minute, hour, dom, month, dow;
354	user *u;
355	entry *e;
356#ifndef CRON_LOCALTIME
357	char *orig_tz, *job_tz;
358
359#define maketime(tz1, tz2) do { \
360	char *t = tz1; \
361	if (t != NULL && *t != '\0') \
362		setenv("TZ", t, 1); \
363	else if ((tz2) != NULL) \
364		setenv("TZ", (tz2), 1); \
365	else \
366		unsetenv("TZ"); \
367	tzset(); \
368	tm = localtime(&virtualSecond); \
369	minute = tm->tm_min -FIRST_MINUTE; \
370	hour = tm->tm_hour -FIRST_HOUR; \
371	dom = tm->tm_mday -FIRST_DOM; \
372	month = tm->tm_mon + 1 /* 0..11 -> 1..12 */ -FIRST_MONTH; \
373	dow = tm->tm_wday -FIRST_DOW; \
374	} while (/*CONSTCOND*/0)
375
376	orig_tz = getenv("TZ");
377	maketime(NULL, orig_tz);
378#else
379	tm = gmtime(&virtualSecond);
380#endif
381
382	Debug(DSCH, ("[%ld] tick(%d,%d,%d,%d,%d) %s %s\n",
383		     (long)getpid(), minute, hour, dom, month, dow,
384		     doWild?" ":"No wildcard",doNonWild?" ":"Wildcard only"));
385
386	/* the dom/dow situation is odd.  '* * 1,15 * Sun' will run on the
387	 * first and fifteenth AND every Sunday;  '* * * * Sun' will run *only*
388	 * on Sundays;  '* * 1,15 * *' will run *only* the 1st and 15th.  this
389	 * is why we keep 'e->dow_star' and 'e->dom_star'.  yes, it's bizarre.
390	 * like many bizarre things, it's the standard.
391	 */
392	for (u = db->head; u != NULL; u = u->next) {
393		for (e = u->crontab; e != NULL; e = e->next) {
394#ifndef CRON_LOCALTIME
395			job_tz = env_get("CRON_TZ", e->envp);
396			maketime(job_tz, orig_tz);
397#else
398#define job_tz "N/A"
399#define orig_tz "N/A"
400#endif
401			Debug(DSCH|DEXT, ("user [%s:%ld:%ld:...] "
402			    "[jobtz=%s, origtz=%s] "
403			    "tick(%s), cmd=\"%s\"\n",
404			    e->pwd->pw_name, (long)e->pwd->pw_uid,
405			    (long)e->pwd->pw_gid, job_tz, orig_tz,
406			    tick(e), e->cmd));
407			if (bit_test(e->minute, minute) &&
408			    bit_test(e->hour, hour) &&
409			    bit_test(e->month, month) &&
410			    ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
411			      ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
412			      : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
413			    )
414			   ) {
415				if ((doNonWild &&
416				    !(e->flags & (MIN_STAR|HR_STAR))) ||
417				    (doWild && (e->flags & (MIN_STAR|HR_STAR))))
418					job_add(e, u, StartTime);
419			}
420		}
421	}
422#ifndef CRON_LOCALTIME
423	if (orig_tz != NULL)
424		setenv("TZ", orig_tz, 1);
425	else
426		unsetenv("TZ");
427#endif
428}
429
430/*
431 * Set StartTime and clockTime to the current time.
432 * These are used for computing what time it really is right now.
433 * Note that clockTime is a unix wallclock time converted to minutes.
434 */
435static void
436set_time(int initialize) {
437
438#ifdef CRON_LOCALTIME
439	struct tm tm;
440	static int isdst;
441
442	StartTime = time(NULL);
443	/* We adjust the time to GMT so we can catch DST changes. */
444	tm = *localtime(&StartTime);
445	if (initialize || tm.tm_isdst != isdst) {
446		isdst = tm.tm_isdst;
447		GMToff = get_gmtoff(&StartTime, &tm);
448		Debug(DSCH, ("[%ld] GMToff=%ld\n",
449		    (long)getpid(), (long)GMToff));
450	}
451#else
452	StartTime = time(NULL);
453#endif
454	clockTime = (StartTime + GMToff) / (time_t)SECONDS_PER_MINUTE;
455}
456
457/*
458 * Try to just hit the next minute.
459 */
460static void
461cron_sleep(int target) {
462	time_t t1, t2;
463	int seconds_to_wait;
464
465	t1 = time(NULL) + GMToff;
466	seconds_to_wait = (int)(target * SECONDS_PER_MINUTE - t1);
467	Debug(DSCH, ("[%ld] Target time=%lld, sec-to-wait=%d\n",
468	    (long)getpid(), (long long)target*SECONDS_PER_MINUTE,
469	    seconds_to_wait));
470
471	while (seconds_to_wait > 0 && seconds_to_wait < 65) {
472		(void)sleep((unsigned int) seconds_to_wait);
473
474		/*
475		 * Check to see if we were interrupted by a signal.
476		 * If so, service the signal(s) then continue sleeping
477		 * where we left off.
478		 */
479		if (got_sighup) {
480			got_sighup = 0;
481			log_close();
482		}
483		if (got_sigchld) {
484			got_sigchld = 0;
485			sigchld_reaper();
486		}
487		t2 = time(NULL) + GMToff;
488		seconds_to_wait -= (int)(t2 - t1);
489		t1 = t2;
490	}
491}
492
493/*ARGSUSED*/
494static void
495sighup_handler(int x __unused) {
496	got_sighup = 1;
497}
498
499/*ARGSUSED*/
500static void
501sigchld_handler(int x __unused) {
502	got_sigchld = 1;
503}
504
505/*ARGSUSED*/
506static void
507quit(int x __unused) {
508	(void) unlink(_PATH_CRON_PID);
509	_exit(0);
510}
511
512static void
513sigchld_reaper(void) {
514	WAIT_T waiter;
515	PID_T pid;
516
517	do {
518		pid = waitpid(-1, &waiter, WNOHANG);
519		switch (pid) {
520		case -1:
521			if (errno == EINTR)
522				continue;
523			Debug(DPROC,
524			      ("[%ld] sigchld...no children\n",
525			       (long)getpid()));
526			break;
527		case 0:
528			Debug(DPROC,
529			      ("[%ld] sigchld...no dead kids\n",
530			       (long)getpid()));
531			break;
532		default:
533			Debug(DPROC,
534			      ("[%ld] sigchld...pid #%ld died, stat=%d\n",
535			       (long)getpid(), (long)pid, WEXITSTATUS(waiter)));
536			break;
537		}
538	} while (pid > 0);
539}
540
541static void
542parse_args(int argc, char *argv[]) {
543	int argch;
544
545	while (-1 != (argch = getopt(argc, argv, "nx:"))) {
546		switch (argch) {
547		default:
548			usage();
549			break;
550		case 'x':
551			if (!set_debug_flags(optarg))
552				usage();
553			break;
554		case 'n':
555			NoFork = 1;
556			break;
557		}
558	}
559}
560