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