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