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) 23static 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 32static 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 42static int run_at_secres(cron_db *); 43static void find_interval_entry(pid_t); 44 45static cron_db database; 46static time_t last_time = 0; 47static int dst_enabled = 0; 48static int dont_daemonize = 0; 49struct pidfh *pfh; 50 51static void 52usage(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 72static void 73open_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 94int 95main(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 187static void 188run_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 206static void 207cron_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 */ 363static void 364cron_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 376static void 377timespec_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 389static void 390cron_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 449static void 450cron_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 464static void 465sigchld_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 490static void 491sighup_handler(int x) 492{ 493 log_close(); 494} 495 496static void 497parse_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 538static int 539run_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 553static void 554find_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