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