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