1 /* $OpenBSD: cron.c,v 1.32 2003/03/10 15:27:17 millert Exp $ */ 2 3 /* Copyright 1988,1990,1993,1994 by Paul Vixie 4 * All rights reserved 5 */ 6 7 /* 8 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. 9 * 10 * Permission to use, copy, modify, and distribute this software for any 11 * purpose with or without fee is hereby granted, provided that the above 12 * copyright notice and this permission notice appear in all copies. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS 15 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE 17 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 18 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 19 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 20 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 21 * SOFTWARE. 22 */ 23 24 #if !defined(lint) && !defined(LINT) 25 static const char rcsid[] = "$OpenBSD: cron.c,v 1.32 2003/03/10 15:27:17 millert Exp $"; 26 #endif 27 28 #define MAIN_PROGRAM 29 30 #include "cron.h" 31 32 enum timejump { negative, small, medium, large }; 33 34 static void usage(void), 35 run_reboot_jobs(cron_db *), 36 find_jobs(int, cron_db *, int, int), 37 set_time(int), 38 cron_sleep(int), 39 sigchld_handler(int), 40 sighup_handler(int), 41 sigchld_reaper(void), 42 quit(int), 43 parse_args(int c, char *v[]); 44 45 static volatile sig_atomic_t got_sighup, got_sigchld; 46 static int timeRunning, virtualTime, clockTime, cronSock; 47 static long GMToff; 48 static cron_db database; 49 static at_db at_database; 50 static double batch_maxload = BATCH_MAXLOAD; 51 52 static void 53 usage(void) { 54 const char **dflags; 55 56 fprintf(stderr, "usage: %s [-l load_avg] [-n] [-x [", ProgramName); 57 for (dflags = DebugFlagNames; *dflags; dflags++) 58 fprintf(stderr, "%s%s", *dflags, dflags[1] ? "," : "]"); 59 fprintf(stderr, "]\n"); 60 exit(ERROR_EXIT); 61 } 62 63 int 64 main(int argc, char *argv[]) { 65 struct sigaction sact; 66 int fd; 67 68 ProgramName = argv[0]; 69 70 setlocale(LC_ALL, ""); 71 72 #if defined(BSD) 73 setlinebuf(stdout); 74 setlinebuf(stderr); 75 #endif 76 77 NoFork = 0; 78 parse_args(argc, argv); 79 80 bzero((char *)&sact, sizeof sact); 81 sigemptyset(&sact.sa_mask); 82 sact.sa_flags = 0; 83 #ifdef SA_RESTART 84 sact.sa_flags |= SA_RESTART; 85 #endif 86 sact.sa_handler = sigchld_handler; 87 (void) sigaction(SIGCHLD, &sact, NULL); 88 sact.sa_handler = sighup_handler; 89 (void) sigaction(SIGHUP, &sact, NULL); 90 sact.sa_handler = quit; 91 (void) sigaction(SIGINT, &sact, NULL); 92 (void) sigaction(SIGTERM, &sact, NULL); 93 sact.sa_handler = SIG_IGN; 94 (void) sigaction(SIGPIPE, &sact, NULL); 95 (void) sigaction(SIGUSR1, &sact, NULL); /* XXX */ 96 97 acquire_daemonlock(0); 98 set_cron_uid(); 99 set_cron_cwd(); 100 101 if (putenv("PATH="_PATH_DEFPATH) < 0) { 102 log_it("CRON", getpid(), "DEATH", "can't malloc"); 103 exit(1); 104 } 105 106 /* if there are no debug flags turned on, fork as a daemon should. 107 */ 108 if (DebugFlags) { 109 #if DEBUGGING 110 (void) fprintf(stderr, "[%ld] cron started\n", (long)getpid()); 111 #endif 112 } else if (NoFork == 0) { 113 switch (fork()) { 114 case -1: 115 log_it("CRON",getpid(),"DEATH","can't fork"); 116 exit(0); 117 break; 118 case 0: 119 /* child process */ 120 (void) setsid(); 121 if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) >= 0) { 122 (void) dup2(fd, STDIN); 123 (void) dup2(fd, STDOUT); 124 (void) dup2(fd, STDERR); 125 if (fd != STDERR) 126 (void) close(fd); 127 } 128 log_it("CRON",getpid(),"STARTUP",CRON_VERSION); 129 break; 130 default: 131 /* parent process should just die */ 132 _exit(0); 133 } 134 } 135 136 acquire_daemonlock(0); 137 cronSock = open_socket(); 138 database.head = NULL; 139 database.tail = NULL; 140 database.mtime = (time_t) 0; 141 load_database(&database); 142 at_database.head = NULL; 143 at_database.tail = NULL; 144 at_database.mtime = (time_t) 0; 145 scan_atjobs(&at_database, NULL); 146 set_time(TRUE); 147 run_reboot_jobs(&database); 148 timeRunning = virtualTime = clockTime; 149 150 /* 151 * Too many clocks, not enough time (Al. Einstein) 152 * These clocks are in minutes since the epoch, adjusted for timezone. 153 * virtualTime: is the time it *would* be if we woke up 154 * promptly and nobody ever changed the clock. It is 155 * monotonically increasing... unless a timejump happens. 156 * At the top of the loop, all jobs for 'virtualTime' have run. 157 * timeRunning: is the time we last awakened. 158 * clockTime: is the time when set_time was last called. 159 */ 160 while (TRUE) { 161 int timeDiff; 162 enum timejump wakeupKind; 163 164 /* ... wait for the time (in minutes) to change ... */ 165 do { 166 cron_sleep(timeRunning + 1); 167 set_time(FALSE); 168 } while (clockTime == timeRunning); 169 timeRunning = clockTime; 170 171 /* 172 * Calculate how the current time differs from our virtual 173 * clock. Classify the change into one of 4 cases. 174 */ 175 timeDiff = timeRunning - virtualTime; 176 177 /* shortcut for the most common case */ 178 if (timeDiff == 1) { 179 virtualTime = timeRunning; 180 find_jobs(virtualTime, &database, TRUE, TRUE); 181 } else { 182 if (timeDiff > (3*MINUTE_COUNT) || 183 timeDiff < -(3*MINUTE_COUNT)) 184 wakeupKind = large; 185 else if (timeDiff > 5) 186 wakeupKind = medium; 187 else if (timeDiff > 0) 188 wakeupKind = small; 189 else 190 wakeupKind = negative; 191 192 switch (wakeupKind) { 193 case small: 194 /* 195 * case 1: timeDiff is a small positive number 196 * (wokeup late) run jobs for each virtual 197 * minute until caught up. 198 */ 199 Debug(DSCH, ("[%ld], normal case %d minutes to go\n", 200 (long)getpid(), timeDiff)) 201 do { 202 if (job_runqueue()) 203 sleep(10); 204 virtualTime++; 205 find_jobs(virtualTime, &database, 206 TRUE, TRUE); 207 } while (virtualTime < timeRunning); 208 break; 209 210 case medium: 211 /* 212 * case 2: timeDiff is a medium-sized positive 213 * number, for example because we went to DST 214 * run wildcard jobs once, then run any 215 * fixed-time jobs that would otherwise be 216 * skipped if we use up our minute (possible, 217 * if there are a lot of jobs to run) go 218 * around the loop again so that wildcard jobs 219 * have a chance to run, and we do our 220 * housekeeping. 221 */ 222 Debug(DSCH, ("[%ld], DST begins %d minutes to go\n", 223 (long)getpid(), timeDiff)) 224 /* run wildcard jobs for current minute */ 225 find_jobs(timeRunning, &database, TRUE, FALSE); 226 227 /* run fixed-time jobs for each minute missed */ 228 do { 229 if (job_runqueue()) 230 sleep(10); 231 virtualTime++; 232 find_jobs(virtualTime, &database, 233 FALSE, TRUE); 234 set_time(FALSE); 235 } while (virtualTime< timeRunning && 236 clockTime == timeRunning); 237 break; 238 239 case negative: 240 /* 241 * case 3: timeDiff is a small or medium-sized 242 * negative num, eg. because of DST ending. 243 * Just run the wildcard jobs. The fixed-time 244 * jobs probably have already run, and should 245 * not be repeated. Virtual time does not 246 * change until we are caught up. 247 */ 248 Debug(DSCH, ("[%ld], DST ends %d minutes to go\n", 249 (long)getpid(), timeDiff)) 250 find_jobs(timeRunning, &database, TRUE, FALSE); 251 break; 252 default: 253 /* 254 * other: time has changed a *lot*, 255 * jump virtual time, and run everything 256 */ 257 Debug(DSCH, ("[%ld], clock jumped\n", 258 (long)getpid())) 259 virtualTime = timeRunning; 260 find_jobs(timeRunning, &database, TRUE, TRUE); 261 } 262 } 263 264 /* Jobs to be run (if any) are loaded; clear the queue. */ 265 job_runqueue(); 266 267 /* Run any jobs in the at queue. */ 268 atrun(&at_database, batch_maxload, 269 timeRunning * SECONDS_PER_MINUTE - GMToff); 270 271 /* Check to see if we received a signal while running jobs. */ 272 if (got_sighup) { 273 got_sighup = 0; 274 log_close(); 275 } 276 if (got_sigchld) { 277 got_sigchld = 0; 278 sigchld_reaper(); 279 } 280 load_database(&database); 281 scan_atjobs(&at_database, NULL); 282 } 283 } 284 285 static void 286 run_reboot_jobs(cron_db *db) { 287 user *u; 288 entry *e; 289 290 for (u = db->head; u != NULL; u = u->next) { 291 for (e = u->crontab; e != NULL; e = e->next) { 292 if (e->flags & WHEN_REBOOT) 293 job_add(e, u); 294 } 295 } 296 (void) job_runqueue(); 297 } 298 299 static void 300 find_jobs(int vtime, cron_db *db, int doWild, int doNonWild) { 301 time_t virtualSecond = vtime * SECONDS_PER_MINUTE; 302 struct tm *tm = gmtime(&virtualSecond); 303 int minute, hour, dom, month, dow; 304 user *u; 305 entry *e; 306 307 /* make 0-based values out of these so we can use them as indicies 308 */ 309 minute = tm->tm_min -FIRST_MINUTE; 310 hour = tm->tm_hour -FIRST_HOUR; 311 dom = tm->tm_mday -FIRST_DOM; 312 month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; 313 dow = tm->tm_wday -FIRST_DOW; 314 315 Debug(DSCH, ("[%ld] tick(%d,%d,%d,%d,%d) %s %s\n", 316 (long)getpid(), minute, hour, dom, month, dow, 317 doWild?" ":"No wildcard",doNonWild?" ":"Wildcard only")) 318 319 /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the 320 * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* 321 * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this 322 * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. 323 * like many bizarre things, it's the standard. 324 */ 325 for (u = db->head; u != NULL; u = u->next) { 326 for (e = u->crontab; e != NULL; e = e->next) { 327 Debug(DSCH|DEXT, ("user [%s:%ld:%ld:...] cmd=\"%s\"\n", 328 e->pwd->pw_name, (long)e->pwd->pw_uid, 329 (long)e->pwd->pw_gid, e->cmd)) 330 if (bit_test(e->minute, minute) && 331 bit_test(e->hour, hour) && 332 bit_test(e->month, month) && 333 ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) 334 ? (bit_test(e->dow,dow) && bit_test(e->dom,dom)) 335 : (bit_test(e->dow,dow) || bit_test(e->dom,dom)) 336 ) 337 ) { 338 if ((doNonWild && 339 !(e->flags & (MIN_STAR|HR_STAR))) || 340 (doWild && (e->flags & (MIN_STAR|HR_STAR)))) 341 job_add(e, u); 342 } 343 } 344 } 345 } 346 347 /* 348 * Set StartTime and clockTime to the current time. 349 * These are used for computing what time it really is right now. 350 * Note that clockTime is a unix wallclock time converted to minutes. 351 */ 352 static void 353 set_time(int initialize) { 354 struct tm tm; 355 static int isdst; 356 357 StartTime = time(NULL); 358 359 /* We adjust the time to GMT so we can catch DST changes. */ 360 tm = *localtime(&StartTime); 361 if (initialize || tm.tm_isdst != isdst) { 362 isdst = tm.tm_isdst; 363 GMToff = get_gmtoff(&StartTime, &tm); 364 Debug(DSCH, ("[%ld] GMToff=%ld\n", 365 (long)getpid(), (long)GMToff)) 366 } 367 clockTime = (StartTime + GMToff) / (time_t)SECONDS_PER_MINUTE; 368 } 369 370 /* 371 * Try to just hit the next minute. 372 */ 373 static void 374 cron_sleep(int target) { 375 int fd, nfds; 376 unsigned char poke; 377 struct timeval t1, t2, tv; 378 struct sockaddr_un s_un; 379 socklen_t sunlen; 380 static fd_set *fdsr; 381 382 gettimeofday(&t1, NULL); 383 t1.tv_sec += GMToff; 384 tv.tv_sec = (target * SECONDS_PER_MINUTE - t1.tv_sec) + 1; 385 tv.tv_usec = 0; 386 387 if (fdsr == NULL) { 388 fdsr = (fd_set *)calloc(howmany(cronSock + 1, NFDBITS), 389 sizeof(fd_mask)); 390 } 391 392 while (timerisset(&tv) && tv.tv_sec < 65) { 393 Debug(DSCH, ("[%ld] Target time=%ld, sec-to-wait=%ld\n", 394 (long)getpid(), (long)target*SECONDS_PER_MINUTE, tv.tv_sec)) 395 396 poke = 0; 397 if (fdsr) 398 FD_SET(cronSock, fdsr); 399 /* Sleep until we time out, get a poke, or get a signal. */ 400 nfds = select(cronSock + 1, fdsr, NULL, NULL, &tv); 401 if (nfds == 0) 402 break; /* timer expired */ 403 if (nfds == -1 && errno != EINTR) 404 break; /* an error occurred */ 405 if (nfds > 0) { 406 Debug(DSCH, ("[%ld] Got a poke on the socket\n", 407 (long)getpid())) 408 fd = accept(cronSock, (struct sockaddr *)&s_un, &sunlen); 409 if (fd >= 0) { 410 (void) read(fd, &poke, 1); 411 close(fd); 412 if (poke & RELOAD_CRON) 413 load_database(&database); 414 if (poke & RELOAD_AT) { 415 /* 416 * We run any pending at jobs right 417 * away so that "at now" really runs 418 * jobs immediately. 419 */ 420 gettimeofday(&t2, NULL); 421 if (scan_atjobs(&at_database, &t2)) 422 atrun(&at_database, 423 batch_maxload, t2.tv_sec); 424 } 425 } 426 } else { 427 /* Interrupted by a signal. */ 428 if (got_sighup) { 429 got_sighup = 0; 430 log_close(); 431 } 432 if (got_sigchld) { 433 got_sigchld = 0; 434 sigchld_reaper(); 435 } 436 } 437 438 /* Adjust tv and continue where we left off. */ 439 gettimeofday(&t2, NULL); 440 t2.tv_sec += GMToff; 441 timersub(&t2, &t1, &t1); 442 timersub(&tv, &t1, &tv); 443 memcpy(&t1, &t2, sizeof(t1)); 444 if (tv.tv_sec < 0) 445 tv.tv_sec = 0; 446 if (tv.tv_usec < 0) 447 tv.tv_usec = 0; 448 } 449 } 450 451 static void 452 sighup_handler(int x) { 453 got_sighup = 1; 454 } 455 456 static void 457 sigchld_handler(int x) { 458 got_sigchld = 1; 459 } 460 461 static void 462 quit(int x) { 463 (void) unlink(_PATH_CRON_PID); 464 _exit(0); 465 } 466 467 static void 468 sigchld_reaper(void) { 469 WAIT_T waiter; 470 PID_T pid; 471 472 do { 473 pid = waitpid(-1, &waiter, WNOHANG); 474 switch (pid) { 475 case -1: 476 if (errno == EINTR) 477 continue; 478 Debug(DPROC, 479 ("[%ld] sigchld...no children\n", 480 (long)getpid())) 481 break; 482 case 0: 483 Debug(DPROC, 484 ("[%ld] sigchld...no dead kids\n", 485 (long)getpid())) 486 break; 487 default: 488 Debug(DPROC, 489 ("[%ld] sigchld...pid #%ld died, stat=%d\n", 490 (long)getpid(), (long)pid, WEXITSTATUS(waiter))) 491 break; 492 } 493 } while (pid > 0); 494 } 495 496 static void 497 parse_args(int argc, char *argv[]) { 498 int argch; 499 char *ep; 500 501 while (-1 != (argch = getopt(argc, argv, "l:nx:"))) { 502 switch (argch) { 503 case 'l': 504 errno = 0; 505 batch_maxload = strtod(optarg, &ep); 506 if (*ep != '\0' || ep == optarg || errno == ERANGE || 507 batch_maxload < 0) { 508 fprintf(stderr, "Illegal load average: %s\n", 509 optarg); 510 usage(); 511 } 512 break; 513 case 'n': 514 NoFork = 1; 515 break; 516 case 'x': 517 if (!set_debug_flags(optarg)) 518 usage(); 519 break; 520 default: 521 usage(); 522 } 523 } 524 } 525