1 /* Copyright 1988,1990,1993,1994 by Paul Vixie
2 * All rights reserved
3 */
4
5 /*
6 * Copyright (c) 1997 by Internet Software Consortium
7 *
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
13 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
14 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
15 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
16 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
17 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
18 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
19 * SOFTWARE.
20 */
21
22 #if !defined(lint) && !defined(LINT)
23 static const char rcsid[] =
24 "$Id: cron.c,v 1.3 1998/08/14 00:32:36 vixie Exp $";
25 #endif
26
27 #define MAIN_PROGRAM
28
29 #include "cron.h"
30 #include <sys/mman.h>
31
32 static void usage(void),
33 run_reboot_jobs(cron_db *),
34 cron_tick(cron_db *, int),
35 cron_sync(int),
36 cron_sleep(cron_db *, int),
37 cron_clean(cron_db *),
38 sigchld_handler(int),
39 sighup_handler(int),
40 parse_args(int c, char *v[]);
41
42 static int run_at_secres(cron_db *);
43 static void find_interval_entry(pid_t);
44
45 static cron_db database;
46 static time_t last_time = 0;
47 static int dst_enabled = 0;
48 static int dont_daemonize = 0;
49 struct pidfh *pfh;
50
51 static void
usage(void)52 usage(void)
53 {
54 #if DEBUGGING
55 const char **dflags;
56 #endif
57
58 fprintf(stderr, "usage: cron [-j jitter] [-J rootjitter] "
59 "[-m mailto] [-n] [-s] [-o] [-x debugflag[,...]]\n");
60 #if DEBUGGING
61 fprintf(stderr, "\ndebugflags: ");
62
63 for (dflags = DebugFlagNames; *dflags; dflags++) {
64 fprintf(stderr, "%s ", *dflags);
65 }
66 fprintf(stderr, "\n");
67 #endif
68
69 exit(ERROR_EXIT);
70 }
71
72 static void
open_pidfile(void)73 open_pidfile(void)
74 {
75 const char *pidfile = PIDDIR PIDFILE;
76 char buf[MAX_TEMPSTR];
77 int otherpid;
78
79 pfh = pidfile_open(pidfile, 0600, &otherpid);
80 if (pfh == NULL) {
81 if (errno == EEXIST) {
82 snprintf(buf, sizeof(buf),
83 "cron already running, pid: %d", otherpid);
84 } else {
85 snprintf(buf, sizeof(buf),
86 "can't open or create %s: %s", pidfile,
87 strerror(errno));
88 }
89 log_it("CRON", getpid(), "DEATH", buf);
90 errx(ERROR_EXIT, "%s", buf);
91 }
92 }
93
94 int
main(int argc,char * argv[])95 main(int argc, char *argv[])
96 {
97 int runnum;
98 int secres1, secres2;
99 struct tm *tm;
100
101 ProgramName = argv[0];
102
103 #if defined(BSD)
104 setlinebuf(stdout);
105 setlinebuf(stderr);
106 #endif
107
108 parse_args(argc, argv);
109
110 (void) signal(SIGCHLD, sigchld_handler);
111 (void) signal(SIGHUP, sighup_handler);
112
113 open_pidfile();
114 set_cron_uid();
115 set_cron_cwd();
116
117 putenv("PATH="_PATH_DEFPATH);
118
119 /* if there are no debug flags turned on, fork as a daemon should.
120 */
121 # if DEBUGGING
122 if (DebugFlags) {
123 # else
124 if (0) {
125 # endif
126 (void) fprintf(stderr, "[%d] cron started\n", getpid());
127 } else if (dont_daemonize == 0) {
128 if (daemon(1, 0) == -1) {
129 pidfile_remove(pfh);
130 log_it("CRON",getpid(),"DEATH","can't become daemon");
131 exit(0);
132 }
133 }
134
135 if (madvise(NULL, 0, MADV_PROTECT) != 0)
136 log_it("CRON", getpid(), "WARNING", "madvise() failed");
137
138 pidfile_write(pfh);
139 database.head = NULL;
140 database.tail = NULL;
141 database.mtime = (time_t) 0;
142 load_database(&database);
143 secres1 = secres2 = run_at_secres(&database);
144 cron_sync(secres1);
145 run_reboot_jobs(&database);
146 runnum = 0;
147 while (TRUE) {
148 # if DEBUGGING
149 /* if (!(DebugFlags & DTEST)) */
150 # endif /*DEBUGGING*/
151 cron_sleep(&database, secres1);
152
153 if (secres1 == 0 || runnum % 60 == 0) {
154 load_database(&database);
155 secres2 = run_at_secres(&database);
156 if (secres2 != secres1) {
157 secres1 = secres2;
158 if (secres1 != 0) {
159 runnum = 0;
160 } else {
161 /*
162 * Going from 1 sec to 60 sec res. If we
163 * are already at minute's boundary, so
164 * let it run, otherwise schedule for the
165 * next minute.
166 */
167 tm = localtime(&TargetTime);
168 if (tm->tm_sec > 0) {
169 cron_sync(secres2);
170 continue;
171 }
172 }
173 }
174 }
175
176 /* do this iteration
177 */
178 cron_tick(&database, secres1);
179
180 /* sleep 1 or 60 seconds
181 */
182 TargetTime += (secres1 != 0) ? 1 : 60;
183 runnum += 1;
184 }
185 }
186
187 static void
188 run_reboot_jobs(cron_db *db)
189 {
190 user *u;
191 entry *e;
192
193 for (u = db->head; u != NULL; u = u->next) {
194 for (e = u->crontab; e != NULL; e = e->next) {
195 if (e->flags & WHEN_REBOOT) {
196 job_add(e, u);
197 }
198 if (e->flags & INTERVAL) {
199 e->lastexit = TargetTime;
200 }
201 }
202 }
203 (void) job_runqueue();
204 }
205
206 static void
207 cron_tick(cron_db *db, int secres)
208 {
209 static struct tm lasttm;
210 /* time difference in seconds from the last offset change */
211 static time_t diff = 0;
212 /* end point for the time zone correction */
213 static time_t difflimit = 0;
214 /* time in the old time zone */
215 struct tm otztm;
216 int otzsecond, otzminute, otzhour, otzdom, otzmonth, otzdow;
217 struct tm *tm = localtime(&TargetTime);
218 int second, minute, hour, dom, month, dow;
219 user *u;
220 entry *e;
221
222 /* make 0-based values out of these so we can use them as indices
223 */
224 second = (secres == 0) ? 0 : tm->tm_sec -FIRST_SECOND;
225 minute = tm->tm_min -FIRST_MINUTE;
226 hour = tm->tm_hour -FIRST_HOUR;
227 dom = tm->tm_mday -FIRST_DOM;
228 month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
229 dow = tm->tm_wday -FIRST_DOW;
230
231 Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d,%d)\n",
232 getpid(), second, minute, hour, dom, month, dow))
233
234 if (dst_enabled && last_time != 0
235 && TargetTime > last_time /* exclude stepping back */
236 && tm->tm_gmtoff != lasttm.tm_gmtoff ) {
237
238 diff = tm->tm_gmtoff - lasttm.tm_gmtoff;
239
240 if ( diff > 0 ) { /* ST->DST */
241 /* mark jobs for an earlier run */
242 difflimit = TargetTime + diff;
243 for (u = db->head; u != NULL; u = u->next) {
244 for (e = u->crontab; e != NULL; e = e->next) {
245 e->flags &= ~NOT_UNTIL;
246 if ( e->lastrun >= TargetTime )
247 e->lastrun = 0;
248 /* not include the ends of hourly ranges */
249 if ( e->lastrun < TargetTime - 3600 )
250 e->flags |= RUN_AT;
251 else
252 e->flags &= ~RUN_AT;
253 }
254 }
255 } else { /* diff < 0 : DST->ST */
256 /* mark jobs for skipping */
257 difflimit = TargetTime - diff;
258 for (u = db->head; u != NULL; u = u->next) {
259 for (e = u->crontab; e != NULL; e = e->next) {
260 e->flags |= NOT_UNTIL;
261 e->flags &= ~RUN_AT;
262 }
263 }
264 }
265 }
266
267 if (diff != 0) {
268 /* if the time was reset of the end of special zone is reached */
269 if (last_time == 0 || TargetTime >= difflimit) {
270 /* disable the TZ switch checks */
271 diff = 0;
272 difflimit = 0;
273 for (u = db->head; u != NULL; u = u->next) {
274 for (e = u->crontab; e != NULL; e = e->next) {
275 e->flags &= ~(RUN_AT|NOT_UNTIL);
276 }
277 }
278 } else {
279 /* get the time in the old time zone */
280 time_t difftime = TargetTime + tm->tm_gmtoff - diff;
281 gmtime_r(&difftime, &otztm);
282
283 /* make 0-based values out of these so we can use them as indices
284 */
285 otzsecond = (secres == 0) ? 0 : otztm.tm_sec -FIRST_SECOND;
286 otzminute = otztm.tm_min -FIRST_MINUTE;
287 otzhour = otztm.tm_hour -FIRST_HOUR;
288 otzdom = otztm.tm_mday -FIRST_DOM;
289 otzmonth = otztm.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
290 otzdow = otztm.tm_wday -FIRST_DOW;
291 }
292 }
293
294 /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
295 * first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
296 * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this
297 * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre.
298 * like many bizarre things, it's the standard.
299 */
300 for (u = db->head; u != NULL; u = u->next) {
301 for (e = u->crontab; e != NULL; e = e->next) {
302 Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n",
303 env_get("LOGNAME", e->envp),
304 e->uid, e->gid, e->cmd))
305
306 if (e->flags & INTERVAL) {
307 if (e->lastexit > 0 &&
308 TargetTime >= e->lastexit + e->interval)
309 job_add(e, u);
310 continue;
311 }
312
313 if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) {
314 if (bit_test(e->second, otzsecond) &&
315 bit_test(e->minute, otzminute) &&
316 bit_test(e->hour, otzhour) &&
317 bit_test(e->month, otzmonth) &&
318 ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
319 ? (bit_test(e->dow,otzdow) && bit_test(e->dom,otzdom))
320 : (bit_test(e->dow,otzdow) || bit_test(e->dom,otzdom))
321 )
322 ) {
323 if ( e->flags & RUN_AT ) {
324 e->flags &= ~RUN_AT;
325 e->lastrun = TargetTime;
326 job_add(e, u);
327 continue;
328 } else
329 e->flags &= ~NOT_UNTIL;
330 } else if ( e->flags & NOT_UNTIL )
331 continue;
332 }
333
334 if (bit_test(e->second, second) &&
335 bit_test(e->minute, minute) &&
336 bit_test(e->hour, hour) &&
337 bit_test(e->month, month) &&
338 ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
339 ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
340 : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
341 )
342 ) {
343 e->flags &= ~RUN_AT;
344 e->lastrun = TargetTime;
345 job_add(e, u);
346 }
347 }
348 }
349
350 last_time = TargetTime;
351 lasttm = *tm;
352 }
353
354 /* the task here is to figure out how long it's going to be until :00 of the
355 * following minute and initialize TargetTime to this value. TargetTime
356 * will subsequently slide 60 seconds at a time, with correction applied
357 * implicitly in cron_sleep(). it would be nice to let cron execute in
358 * the "current minute" before going to sleep, but by restarting cron you
359 * could then get it to execute a given minute's jobs more than once.
360 * instead we have the chance of missing a minute's jobs completely, but
361 * that's something sysadmin's know to expect what with crashing computers..
362 */
363 static void
364 cron_sync(int secres) {
365 struct tm *tm;
366
367 TargetTime = time((time_t*)0);
368 if (secres != 0) {
369 TargetTime += 1;
370 } else {
371 tm = localtime(&TargetTime);
372 TargetTime += (60 - tm->tm_sec);
373 }
374 }
375
376 static void
377 timespec_subtract(struct timespec *result, struct timespec *x,
378 struct timespec *y)
379 {
380 *result = *x;
381 result->tv_sec -= y->tv_sec;
382 result->tv_nsec -= y->tv_nsec;
383 if (result->tv_nsec < 0) {
384 result->tv_sec--;
385 result->tv_nsec += 1000000000;
386 }
387 }
388
389 static void
390 cron_sleep(cron_db *db, int secres)
391 {
392 int seconds_to_wait;
393 int rval;
394 struct timespec ctime, ttime, stime, remtime;
395
396 /*
397 * Loop until we reach the top of the next minute, sleep when possible.
398 */
399
400 for (;;) {
401 clock_gettime(CLOCK_REALTIME, &ctime);
402 ttime.tv_sec = TargetTime;
403 ttime.tv_nsec = 0;
404 timespec_subtract(&stime, &ttime, &ctime);
405
406 /*
407 * If the seconds_to_wait value is insane, jump the cron
408 */
409
410 if (stime.tv_sec < -600 || stime.tv_sec > 600) {
411 cron_clean(db);
412 cron_sync(secres);
413 continue;
414 }
415
416 seconds_to_wait = (stime.tv_nsec > 0) ? stime.tv_sec + 1 :
417 stime.tv_sec;
418
419 Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
420 getpid(), (long)TargetTime, seconds_to_wait))
421
422 /*
423 * If we've run out of wait time or there are no jobs left
424 * to run, break
425 */
426
427 if (stime.tv_sec < 0)
428 break;
429 if (job_runqueue() == 0) {
430 Debug(DSCH, ("[%d] sleeping for %d seconds\n",
431 getpid(), seconds_to_wait))
432
433 for (;;) {
434 rval = nanosleep(&stime, &remtime);
435 if (rval == 0 || errno != EINTR)
436 break;
437 stime.tv_sec = remtime.tv_sec;
438 stime.tv_nsec = remtime.tv_nsec;
439 }
440 }
441 }
442 }
443
444
445 /* if the time was changed abruptly, clear the flags related
446 * to the daylight time switch handling to avoid strange effects
447 */
448
449 static void
450 cron_clean(cron_db *db)
451 {
452 user *u;
453 entry *e;
454
455 last_time = 0;
456
457 for (u = db->head; u != NULL; u = u->next) {
458 for (e = u->crontab; e != NULL; e = e->next) {
459 e->flags &= ~(RUN_AT|NOT_UNTIL);
460 }
461 }
462 }
463
464 static void
465 sigchld_handler(int x)
466 {
467 WAIT_T waiter;
468 PID_T pid;
469
470 for (;;) {
471 pid = waitpid(-1, &waiter, WNOHANG);
472 switch (pid) {
473 case -1:
474 Debug(DPROC,
475 ("[%d] sigchld...no children\n", getpid()))
476 return;
477 case 0:
478 Debug(DPROC,
479 ("[%d] sigchld...no dead kids\n", getpid()))
480 return;
481 default:
482 find_interval_entry(pid);
483 Debug(DPROC,
484 ("[%d] sigchld...pid #%d died, stat=%d\n",
485 getpid(), pid, WEXITSTATUS(waiter)))
486 }
487 }
488 }
489
490 static void
491 sighup_handler(int x)
492 {
493 log_close();
494 }
495
496 static void
497 parse_args(int argc, char *argv[])
498 {
499 int argch;
500 char *endp;
501
502 while ((argch = getopt(argc, argv, "j:J:m:nosx:")) != -1) {
503 switch (argch) {
504 case 'j':
505 Jitter = strtoul(optarg, &endp, 10);
506 if (*optarg == '\0' || *endp != '\0' || Jitter > 60)
507 errx(ERROR_EXIT,
508 "bad value for jitter: %s", optarg);
509 break;
510 case 'J':
511 RootJitter = strtoul(optarg, &endp, 10);
512 if (*optarg == '\0' || *endp != '\0' || RootJitter > 60)
513 errx(ERROR_EXIT,
514 "bad value for root jitter: %s", optarg);
515 break;
516 case 'm':
517 defmailto = optarg;
518 break;
519 case 'n':
520 dont_daemonize = 1;
521 break;
522 case 'o':
523 dst_enabled = 0;
524 break;
525 case 's':
526 dst_enabled = 1;
527 break;
528 case 'x':
529 if (!set_debug_flags(optarg))
530 usage();
531 break;
532 default:
533 usage();
534 }
535 }
536 }
537
538 static int
539 run_at_secres(cron_db *db)
540 {
541 user *u;
542 entry *e;
543
544 for (u = db->head; u != NULL; u = u->next) {
545 for (e = u->crontab; e != NULL; e = e->next) {
546 if ((e->flags & (SEC_RES | INTERVAL)) != 0)
547 return 1;
548 }
549 }
550 return 0;
551 }
552
553 static void
554 find_interval_entry(pid_t pid)
555 {
556 user *u;
557 entry *e;
558
559 for (u = database.head; u != NULL; u = u->next) {
560 for (e = u->crontab; e != NULL; e = e->next) {
561 if ((e->flags & INTERVAL) && e->child == pid) {
562 e->lastexit = time(NULL);
563 e->child = 0;
564 break;
565 }
566 }
567 }
568 }
569