xref: /openbsd-src/usr.sbin/cron/cron.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: cron.c,v 1.15 2001/08/11 20:47:14 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.15 2001/08/11 20:47:14 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 		sigchld_reaper(void),
39 		parse_args(int c, char *v[]);
40 
41 static	volatile sig_atomic_t	got_sighup, got_sigchld;
42 static	int			timeRunning, virtualTime, clockTime;
43 static	long			GMToff;
44 
45 static void
46 usage(void) {
47 	const char **dflags;
48 
49 	fprintf(stderr, "usage:  %s [-x [", ProgramName);
50 	for (dflags = DebugFlagNames; *dflags; dflags++)
51 		fprintf(stderr, "%s%s", *dflags, dflags[1] ? "," : "]");
52 	fprintf(stderr, "]\n");
53 	exit(ERROR_EXIT);
54 }
55 
56 int
57 main(int argc, char *argv[]) {
58 	cron_db	database;
59 	struct sigaction sact;
60 
61 	ProgramName = argv[0];
62 
63 	setlocale(LC_ALL, "");
64 
65 #if defined(BSD)
66 	setlinebuf(stdout);
67 	setlinebuf(stderr);
68 #endif
69 
70 	parse_args(argc, argv);
71 
72 	bzero((char *)&sact, sizeof sact);
73 	sigemptyset(&sact.sa_mask);
74 	sact.sa_flags = 0;
75 #ifdef SA_RESTART
76 	sact.sa_flags |= SA_RESTART;
77 #endif
78 	sact.sa_handler = sigchld_handler;
79 	(void) sigaction(SIGCHLD, &sact, NULL);
80 	sact.sa_handler = sighup_handler;
81 	(void) sigaction(SIGHUP, &sact, NULL);
82 
83 	acquire_daemonlock(0);
84 	set_cron_uid();
85 	set_cron_cwd();
86 
87 	if (putenv("PATH="_PATH_DEFPATH) == -1) {
88 		log_it("CRON",getpid(),"DEATH","can't malloc");
89 		exit(1);
90 	}
91 
92 	/* if there are no debug flags turned on, fork as a daemon should.
93 	 */
94 	if (DebugFlags) {
95 #if DEBUGGING
96 		(void) fprintf(stderr, "[%ld] cron started\n", (long)getpid());
97 #endif
98 	} else {
99 		switch (fork()) {
100 		case -1:
101 			log_it("CRON",getpid(),"DEATH","can't fork");
102 			exit(0);
103 			break;
104 		case 0:
105 			/* child process */
106 			log_it("CRON",getpid(),"STARTUP","fork ok");
107 			(void) setsid();
108 			break;
109 		default:
110 			/* parent process should just die */
111 			_exit(0);
112 		}
113 	}
114 
115 	acquire_daemonlock(0);
116 	database.head = NULL;
117 	database.tail = NULL;
118 	database.mtime = (time_t) 0;
119 	load_database(&database);
120 	set_time(1);
121 	run_reboot_jobs(&database);
122 	timeRunning = virtualTime = clockTime;
123 
124 	/*
125 	 * Too many clocks, not enough time (Al. Einstein)
126 	 * These clocks are in minutes since the epoch, adjusted for timezone.
127 	 * virtualTime: is the time it *would* be if we woke up
128 	 * promptly and nobody ever changed the clock. It is
129 	 * monotonically increasing... unless a timejump happens.
130 	 * At the top of the loop, all jobs for 'virtualTime' have run.
131 	 * timeRunning: is the time we last awakened.
132 	 * clockTime: is the time when set_time was last called.
133 	 */
134 	while (TRUE) {
135 		int timeDiff;
136 		int wakeupKind;
137 
138 		if (got_sighup) {
139 			got_sighup = 0;
140 			log_close();
141 		}
142 		if (got_sigchld) {
143 			got_sigchld = 0;
144 			sigchld_reaper();
145 		}
146 
147 		load_database(&database);
148 		/* ... wait for the time (in minutes) to change ... */
149 		do {
150 			cron_sleep(timeRunning + 1);
151 			set_time(0);
152 		} while (clockTime == timeRunning);
153 		timeRunning = clockTime;
154 
155 		/*
156 		 * Calculate how the current time differs from our virtual
157 		 * clock.  Classify the change into one of 4 cases.
158 		 */
159 		timeDiff = timeRunning - virtualTime;
160 
161 		/* shortcut for the most common case */
162 		if (timeDiff == 1) {
163 			virtualTime = timeRunning;
164 			find_jobs(virtualTime, &database, TRUE, TRUE);
165 		} else {
166 			wakeupKind = -1;
167 			if (timeDiff > -(3*MINUTE_COUNT))
168 				wakeupKind = 0;
169 			if (timeDiff > 0)
170 				wakeupKind = 1;
171 			if (timeDiff > 5)
172 				wakeupKind = 2;
173 			if (timeDiff > (3*MINUTE_COUNT))
174 				wakeupKind = 3;
175 
176 			switch (wakeupKind) {
177 			case 1:
178 				/*
179 				 * case 1: timeDiff is a small positive number
180 				 * (wokeup late) run jobs for each virtual
181 				 * minute until caught up.
182 				 */
183 				Debug(DSCH, ("[%d], normal case %d minutes to go\n",
184 				    getpid(), timeDiff))
185 				do {
186 					if (job_runqueue())
187 						sleep(10);
188 					virtualTime++;
189 					find_jobs(virtualTime, &database,
190 					    TRUE, TRUE);
191 				} while (virtualTime < timeRunning);
192 				break;
193 
194 			case 2:
195 				/*
196 				 * case 2: timeDiff is a medium-sized positive
197 				 * number, for example because we went to DST
198 				 * run wildcard jobs once, then run any
199 				 * fixed-time jobs that would otherwise be
200 				 * skipped if we use up our minute (possible,
201 				 * if there are a lot of jobs to run) go
202 				 * around the loop again so that wildcard jobs
203 				 * have a chance to run, and we do our
204 				 * housekeeping.
205 				 */
206 				Debug(DSCH, ("[%d], DST begins %d minutes to go\n",
207 				    getpid(), timeDiff))
208 				/* run wildcard jobs for current minute */
209 				find_jobs(timeRunning, &database, TRUE, FALSE);
210 
211 				/* run fixed-time jobs for each minute missed */
212 				do {
213 					if (job_runqueue())
214 						sleep(10);
215 					virtualTime++;
216 					find_jobs(virtualTime, &database,
217 					    FALSE, TRUE);
218 					set_time(0);
219 				} while (virtualTime< timeRunning &&
220 				    clockTime == timeRunning);
221 				break;
222 
223 			case 0:
224 				/*
225 				 * case 3: timeDiff is a small or medium-sized
226 				 * negative num, eg. because of DST ending.
227 				 * Just run the wildcard jobs. The fixed-time
228 				 * jobs probably have already run, and should
229 				 * not be repeated.  Virtual time does not
230 				 * change until we are caught up.
231 				 */
232 				Debug(DSCH, ("[%d], DST ends %d minutes to go\n",
233 				    getpid(), timeDiff))
234 				find_jobs(timeRunning, &database, TRUE, FALSE);
235 				break;
236 			default:
237 				/*
238 				 * other: time has changed a *lot*,
239 				 * jump virtual time, and run everything
240 				 */
241 				Debug(DSCH, ("[%d], clock jumped\n", getpid()))
242 				virtualTime = timeRunning;
243 				find_jobs(timeRunning, &database, TRUE, TRUE);
244 			}
245 		}
246 
247 		/* Jobs to be run (if any) are loaded; clear the queue. */
248 		job_runqueue();
249 	}
250 }
251 
252 static void
253 run_reboot_jobs(cron_db *db) {
254 	user *u;
255 	entry *e;
256 
257 	for (u = db->head; u != NULL; u = u->next) {
258 		for (e = u->crontab; e != NULL; e = e->next) {
259 			if (e->flags & WHEN_REBOOT)
260 				job_add(e, u);
261 		}
262 	}
263 	(void) job_runqueue();
264 }
265 
266 static void
267 find_jobs(int vtime, cron_db *db, int doWild, int doNonWild) {
268 	time_t virtualSecond  = vtime * SECONDS_PER_MINUTE;
269 	struct tm *tm = gmtime(&virtualSecond);
270 	int minute, hour, dom, month, dow;
271 	user *u;
272 	entry *e;
273 
274 	/* make 0-based values out of these so we can use them as indicies
275 	 */
276 	minute = tm->tm_min -FIRST_MINUTE;
277 	hour = tm->tm_hour -FIRST_HOUR;
278 	dom = tm->tm_mday -FIRST_DOM;
279 	month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
280 	dow = tm->tm_wday -FIRST_DOW;
281 
282 	Debug(DSCH, ("[%ld] tick(%d,%d,%d,%d,%d) %s %s\n",
283 		     (long)getpid(), minute, hour, dom, month, dow,
284 		     doWild?" ":"No wildcard",doNonWild?" ":"Wildcard only"))
285 
286 	/* the dom/dow situation is odd.  '* * 1,15 * Sun' will run on the
287 	 * first and fifteenth AND every Sunday;  '* * * * Sun' will run *only*
288 	 * on Sundays;  '* * 1,15 * *' will run *only* the 1st and 15th.  this
289 	 * is why we keep 'e->dow_star' and 'e->dom_star'.  yes, it's bizarre.
290 	 * like many bizarre things, it's the standard.
291 	 */
292 	for (u = db->head; u != NULL; u = u->next) {
293 		for (e = u->crontab; e != NULL; e = e->next) {
294 			Debug(DSCH|DEXT, ("user [%s:%ld:%ld:...] cmd=\"%s\"\n",
295 					  env_get("LOGNAME", e->envp),
296 					  (long)e->uid, (long)e->gid, e->cmd))
297 			if (bit_test(e->minute, minute) &&
298 			    bit_test(e->hour, hour) &&
299 			    bit_test(e->month, month) &&
300 			    ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
301 			      ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
302 			      : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
303 			    )
304 			   ) {
305 				if ((doNonWild &&
306 				    !(e->flags & (MIN_STAR|HR_STAR))) ||
307 				    (doWild && (e->flags & (MIN_STAR|HR_STAR))))
308 					job_add(e, u);
309 			}
310 		}
311 	}
312 }
313 
314 /*
315  * Set StartTime and clockTime to the current time.
316  * These are used for computing what time it really is right now.
317  * Note that clockTime is a unix wallclock time converted to minutes.
318  */
319 static void
320 set_time(int initialize) {
321 	struct tm *tm;
322 	static int isdst;
323 
324 	StartTime = time(NULL);
325 
326 	/* We adjust the time to GMT so we can catch DST changes. */
327 	tm = localtime(&StartTime);
328 	if (initialize || tm->tm_isdst != isdst) {
329 		isdst = tm->tm_isdst;
330 		GMToff = get_gmtoff(&StartTime, tm);
331 	}
332 	clockTime = (StartTime + GMToff) / (time_t)SECONDS_PER_MINUTE;
333 }
334 
335 /*
336  * Try to just hit the next minute.
337  */
338 static void
339 cron_sleep(int target) {
340 	time_t t;
341 	int seconds_to_wait;
342 
343 	t = time(NULL) + GMToff;
344 	seconds_to_wait = (int)(target * SECONDS_PER_MINUTE - t) + 1;
345 	Debug(DSCH, ("[%ld] Target time=%ld, sec-to-wait=%d\n",
346 	    (long)getpid(), (long)target*SECONDS_PER_MINUTE, seconds_to_wait))
347 
348 	if (seconds_to_wait > 0 && seconds_to_wait < 65)
349 		sleep((unsigned int) seconds_to_wait);
350 }
351 
352 static void
353 sighup_handler(int x) {
354 	got_sighup = 1;
355 }
356 
357 static void
358 sigchld_handler(int x) {
359 	got_sigchld = 1;
360 }
361 
362 static void
363 sigchld_reaper() {
364 	WAIT_T waiter;
365 	PID_T pid;
366 
367 	do {
368 		pid = waitpid(-1, &waiter, WNOHANG);
369 		switch (pid) {
370 		case -1:
371 			if (errno == EINTR)
372 				continue;
373 			Debug(DPROC,
374 			      ("[%ld] sigchld...no children\n",
375 			       (long)getpid()))
376 			break;
377 		case 0:
378 			Debug(DPROC,
379 			      ("[%ld] sigchld...no dead kids\n",
380 			       (long)getpid()))
381 			break;
382 		default:
383 			Debug(DPROC,
384 			      ("[%ld] sigchld...pid #%ld died, stat=%d\n",
385 			       (long)getpid(), (long)pid, WEXITSTATUS(waiter)))
386 		}
387 	} while (pid > 0);
388 }
389 
390 static void
391 parse_args(int argc, char *argv[]) {
392 	int argch;
393 
394 	while (-1 != (argch = getopt(argc, argv, "x:"))) {
395 		switch (argch) {
396 		default:
397 			usage();
398 		case 'x':
399 			if (!set_debug_flags(optarg))
400 				usage();
401 			break;
402 		}
403 	}
404 }
405