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