xref: /dragonfly/usr.sbin/cron/cron/cron.c (revision 2d8a3be7)
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  * $FreeBSD: src/usr.sbin/cron/cron/cron.c,v 1.9.2.2 2001/05/28 23:37:26 babkin Exp $
18  * $DragonFly: src/usr.sbin/cron/cron/cron.c,v 1.3 2003/11/03 19:31:36 eirikn Exp $
19  */
20 
21 #define	MAIN_PROGRAM
22 
23 
24 #include "cron.h"
25 #include <sys/signal.h>
26 #if SYS_TIME_H
27 # include <sys/time.h>
28 #else
29 # include <time.h>
30 #endif
31 
32 
33 static	void	usage(void),
34 		run_reboot_jobs(cron_db *),
35 		cron_tick(cron_db *),
36 		cron_sync(void),
37 		cron_sleep(cron_db *),
38 		cron_clean(cron_db *),
39 #ifdef USE_SIGCHLD
40 		sigchld_handler(int),
41 #endif
42 		sighup_handler(int),
43 		parse_args(int c, char *v[]);
44 
45 static time_t	last_time = 0;
46 static int	dst_enabled = 0;
47 
48 static void
49 usage() {
50     char **dflags;
51 
52 	fprintf(stderr, "usage: cron [-s] [-o] [-x debugflag[,...]]\n");
53 	fprintf(stderr, "\ndebugflags: ");
54 
55         for(dflags = DebugFlagNames; *dflags; dflags++) {
56 		fprintf(stderr, "%s ", *dflags);
57 	}
58         fprintf(stderr, "\n");
59 
60 	exit(ERROR_EXIT);
61 }
62 
63 
64 int
65 main(argc, argv)
66 	int	argc;
67 	char	*argv[];
68 {
69 	cron_db	database;
70 
71 	ProgramName = argv[0];
72 
73 #if defined(BSD)
74 	setlinebuf(stdout);
75 	setlinebuf(stderr);
76 #endif
77 
78 	parse_args(argc, argv);
79 
80 #ifdef USE_SIGCHLD
81 	(void) signal(SIGCHLD, sigchld_handler);
82 #else
83 	(void) signal(SIGCLD, SIG_IGN);
84 #endif
85 	(void) signal(SIGHUP, sighup_handler);
86 
87 	acquire_daemonlock(0);
88 	set_cron_uid();
89 	set_cron_cwd();
90 
91 #if defined(POSIX)
92 	setenv("PATH", _PATH_DEFPATH, 1);
93 #endif
94 
95 	/* if there are no debug flags turned on, fork as a daemon should.
96 	 */
97 # if DEBUGGING
98 	if (DebugFlags) {
99 # else
100 	if (0) {
101 # endif
102 		(void) fprintf(stderr, "[%d] cron started\n", getpid());
103 	} else {
104 		if (daemon(1, 0) == -1) {
105 			log_it("CRON",getpid(),"DEATH","can't become daemon");
106 			exit(0);
107 		}
108 	}
109 
110 	acquire_daemonlock(0);
111 	database.head = NULL;
112 	database.tail = NULL;
113 	database.mtime = (time_t) 0;
114 	load_database(&database);
115 	run_reboot_jobs(&database);
116 	cron_sync();
117 	while (TRUE) {
118 # if DEBUGGING
119 	    /* if (!(DebugFlags & DTEST)) */
120 # endif /*DEBUGGING*/
121 			cron_sleep(&database);
122 
123 		load_database(&database);
124 
125 		/* do this iteration
126 		 */
127 		cron_tick(&database);
128 
129 		/* sleep 1 minute
130 		 */
131 		TargetTime += 60;
132 	}
133 }
134 
135 
136 static void
137 run_reboot_jobs(db)
138 	cron_db *db;
139 {
140 	register user		*u;
141 	register entry		*e;
142 
143 	for (u = db->head;  u != NULL;  u = u->next) {
144 		for (e = u->crontab;  e != NULL;  e = e->next) {
145 			if (e->flags & WHEN_REBOOT) {
146 				job_add(e, u);
147 			}
148 		}
149 	}
150 	(void) job_runqueue();
151 }
152 
153 
154 static void
155 cron_tick(db)
156 	cron_db	*db;
157 {
158 	static struct tm	lasttm;
159 	static time_t	diff = 0, /* time difference in seconds from the last offset change */
160 		difflimit = 0; /* end point for the time zone correction */
161 	struct tm	otztm; /* time in the old time zone */
162 	int		otzminute, otzhour, otzdom, otzmonth, otzdow;
163  	register struct tm	*tm = localtime(&TargetTime);
164 	register int		minute, hour, dom, month, dow;
165 	register user		*u;
166 	register entry		*e;
167 
168 	/* make 0-based values out of these so we can use them as indicies
169 	 */
170 	minute = tm->tm_min -FIRST_MINUTE;
171 	hour = tm->tm_hour -FIRST_HOUR;
172 	dom = tm->tm_mday -FIRST_DOM;
173 	month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
174 	dow = tm->tm_wday -FIRST_DOW;
175 
176 	Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d)\n",
177 		getpid(), minute, hour, dom, month, dow))
178 
179 	if (dst_enabled && last_time != 0
180 	&& TargetTime > last_time /* exclude stepping back */
181 	&& tm->tm_gmtoff != lasttm.tm_gmtoff ) {
182 
183 		diff = tm->tm_gmtoff - lasttm.tm_gmtoff;
184 
185 		if ( diff > 0 ) { /* ST->DST */
186 			/* mark jobs for an earlier run */
187 			difflimit = TargetTime + diff;
188 			for (u = db->head;  u != NULL;  u = u->next) {
189 				for (e = u->crontab;  e != NULL;  e = e->next) {
190 					e->flags &= ~NOT_UNTIL;
191 					if ( e->lastrun >= TargetTime )
192 						e->lastrun = 0;
193 					/* not include the ends of hourly ranges */
194 					if ( e->lastrun < TargetTime - 3600 )
195 						e->flags |= RUN_AT;
196 					else
197 						e->flags &= ~RUN_AT;
198 				}
199 			}
200 		} else { /* diff < 0 : DST->ST */
201 			/* mark jobs for skipping */
202 			difflimit = TargetTime - diff;
203 			for (u = db->head;  u != NULL;  u = u->next) {
204 				for (e = u->crontab;  e != NULL;  e = e->next) {
205 					e->flags |= NOT_UNTIL;
206 					e->flags &= ~RUN_AT;
207 				}
208 			}
209 		}
210 	}
211 
212 	if (diff != 0) {
213 		/* if the time was reset of the end of special zone is reached */
214 		if (last_time == 0 || TargetTime >= difflimit) {
215 			/* disable the TZ switch checks */
216 			diff = 0;
217 			difflimit = 0;
218 			for (u = db->head;  u != NULL;  u = u->next) {
219 				for (e = u->crontab;  e != NULL;  e = e->next) {
220 					e->flags &= ~(RUN_AT|NOT_UNTIL);
221 				}
222 			}
223 		} else {
224 			/* get the time in the old time zone */
225 			time_t difftime = TargetTime + tm->tm_gmtoff - diff;
226 			gmtime_r(&difftime, &otztm);
227 
228 			/* make 0-based values out of these so we can use them as indicies
229 			 */
230 			otzminute = otztm.tm_min -FIRST_MINUTE;
231 			otzhour = otztm.tm_hour -FIRST_HOUR;
232 			otzdom = otztm.tm_mday -FIRST_DOM;
233 			otzmonth = otztm.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
234 			otzdow = otztm.tm_wday -FIRST_DOW;
235 		}
236 	}
237 
238 	/* the dom/dow situation is odd.  '* * 1,15 * Sun' will run on the
239 	 * first and fifteenth AND every Sunday;  '* * * * Sun' will run *only*
240 	 * on Sundays;  '* * 1,15 * *' will run *only* the 1st and 15th.  this
241 	 * is why we keep 'e->dow_star' and 'e->dom_star'.  yes, it's bizarre.
242 	 * like many bizarre things, it's the standard.
243 	 */
244 	for (u = db->head;  u != NULL;  u = u->next) {
245 		for (e = u->crontab;  e != NULL;  e = e->next) {
246 			Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n",
247 					  env_get("LOGNAME", e->envp),
248 					  e->uid, e->gid, e->cmd))
249 
250 			if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) {
251 				if (bit_test(e->minute, otzminute)
252 				 && bit_test(e->hour, otzhour)
253 				 && bit_test(e->month, otzmonth)
254 				 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
255 					  ? (bit_test(e->dow,otzdow) && bit_test(e->dom,otzdom))
256 					  : (bit_test(e->dow,otzdow) || bit_test(e->dom,otzdom))
257 					)
258 				   ) {
259 					if ( e->flags & RUN_AT ) {
260 						e->flags &= ~RUN_AT;
261 						e->lastrun = TargetTime;
262 						job_add(e, u);
263 						continue;
264 					} else
265 						e->flags &= ~NOT_UNTIL;
266 				} else if ( e->flags & NOT_UNTIL )
267 					continue;
268 			}
269 
270 			if (bit_test(e->minute, minute)
271 			 && bit_test(e->hour, hour)
272 			 && bit_test(e->month, month)
273 			 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
274 			      ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
275 			      : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
276 			    )
277 			   ) {
278 				e->flags &= ~RUN_AT;
279 				e->lastrun = TargetTime;
280 				job_add(e, u);
281 			}
282 		}
283 	}
284 
285 	last_time = TargetTime;
286 	lasttm = *tm;
287 }
288 
289 
290 /* the task here is to figure out how long it's going to be until :00 of the
291  * following minute and initialize TargetTime to this value.  TargetTime
292  * will subsequently slide 60 seconds at a time, with correction applied
293  * implicitly in cron_sleep().  it would be nice to let cron execute in
294  * the "current minute" before going to sleep, but by restarting cron you
295  * could then get it to execute a given minute's jobs more than once.
296  * instead we have the chance of missing a minute's jobs completely, but
297  * that's something sysadmin's know to expect what with crashing computers..
298  */
299 static void
300 cron_sync() {
301  	register struct tm	*tm;
302 
303 	TargetTime = time((time_t*)0);
304 	tm = localtime(&TargetTime);
305 	TargetTime += (60 - tm->tm_sec);
306 }
307 
308 
309 static void
310 cron_sleep(db)
311 	cron_db	*db;
312 {
313 	int	seconds_to_wait = 0;
314 
315 	/*
316 	 * Loop until we reach the top of the next minute, sleep when possible.
317 	 */
318 
319 	for (;;) {
320 		seconds_to_wait = (int) (TargetTime - time((time_t*)0));
321 
322 		/*
323 		 * If the seconds_to_wait value is insane, jump the cron
324 		 */
325 
326 		if (seconds_to_wait < -600 || seconds_to_wait > 600) {
327 			cron_clean(db);
328 			cron_sync();
329 			continue;
330 		}
331 
332 		Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
333 			getpid(), (long)TargetTime, seconds_to_wait))
334 
335 		/*
336 		 * If we've run out of wait time or there are no jobs left
337 		 * to run, break
338 		 */
339 
340 		if (seconds_to_wait <= 0)
341 			break;
342 		if (job_runqueue() == 0) {
343 			Debug(DSCH, ("[%d] sleeping for %d seconds\n",
344 				getpid(), seconds_to_wait))
345 
346 			sleep(seconds_to_wait);
347 		}
348 	}
349 }
350 
351 
352 /* if the time was changed abruptly, clear the flags related
353  * to the daylight time switch handling to avoid strange effects
354  */
355 
356 static void
357 cron_clean(db)
358 	cron_db	*db;
359 {
360 	user		*u;
361 	entry		*e;
362 
363 	last_time = 0;
364 
365 	for (u = db->head;  u != NULL;  u = u->next) {
366 		for (e = u->crontab;  e != NULL;  e = e->next) {
367 			e->flags &= ~(RUN_AT|NOT_UNTIL);
368 		}
369 	}
370 }
371 
372 #ifdef USE_SIGCHLD
373 static void
374 sigchld_handler(x) {
375 	WAIT_T		waiter;
376 	PID_T		pid;
377 
378 	for (;;) {
379 #ifdef POSIX
380 		pid = waitpid(-1, &waiter, WNOHANG);
381 #else
382 		pid = wait3(&waiter, WNOHANG, (struct rusage *)0);
383 #endif
384 		switch (pid) {
385 		case -1:
386 			Debug(DPROC,
387 				("[%d] sigchld...no children\n", getpid()))
388 			return;
389 		case 0:
390 			Debug(DPROC,
391 				("[%d] sigchld...no dead kids\n", getpid()))
392 			return;
393 		default:
394 			Debug(DPROC,
395 				("[%d] sigchld...pid #%d died, stat=%d\n",
396 				getpid(), pid, WEXITSTATUS(waiter)))
397 		}
398 	}
399 }
400 #endif /*USE_SIGCHLD*/
401 
402 
403 static void
404 sighup_handler(x) {
405 	log_close();
406 }
407 
408 
409 static void
410 parse_args(argc, argv)
411 	int	argc;
412 	char	*argv[];
413 {
414 	int	argch;
415 
416 	while ((argch = getopt(argc, argv, "osx:")) != -1) {
417 		switch (argch) {
418 		case 'o':
419 			dst_enabled = 0;
420 			break;
421 		case 's':
422 			dst_enabled = 1;
423 			break;
424 		case 'x':
425 			if (!set_debug_flags(optarg))
426 				usage();
427 			break;
428 		default:
429 			usage();
430 		}
431 	}
432 }
433 
434