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