1 /* $OpenBSD: cron.c,v 1.15 2001/08/11 20:47:14 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.15 2001/08/11 20:47:14 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 sigchld_reaper(void), 39 parse_args(int c, char *v[]); 40 41 static volatile sig_atomic_t got_sighup, got_sigchld; 42 static int timeRunning, virtualTime, clockTime; 43 static long GMToff; 44 45 static void 46 usage(void) { 47 const char **dflags; 48 49 fprintf(stderr, "usage: %s [-x [", ProgramName); 50 for (dflags = DebugFlagNames; *dflags; dflags++) 51 fprintf(stderr, "%s%s", *dflags, dflags[1] ? "," : "]"); 52 fprintf(stderr, "]\n"); 53 exit(ERROR_EXIT); 54 } 55 56 int 57 main(int argc, char *argv[]) { 58 cron_db database; 59 struct sigaction sact; 60 61 ProgramName = argv[0]; 62 63 setlocale(LC_ALL, ""); 64 65 #if defined(BSD) 66 setlinebuf(stdout); 67 setlinebuf(stderr); 68 #endif 69 70 parse_args(argc, argv); 71 72 bzero((char *)&sact, sizeof sact); 73 sigemptyset(&sact.sa_mask); 74 sact.sa_flags = 0; 75 #ifdef SA_RESTART 76 sact.sa_flags |= SA_RESTART; 77 #endif 78 sact.sa_handler = sigchld_handler; 79 (void) sigaction(SIGCHLD, &sact, NULL); 80 sact.sa_handler = sighup_handler; 81 (void) sigaction(SIGHUP, &sact, NULL); 82 83 acquire_daemonlock(0); 84 set_cron_uid(); 85 set_cron_cwd(); 86 87 if (putenv("PATH="_PATH_DEFPATH) == -1) { 88 log_it("CRON",getpid(),"DEATH","can't malloc"); 89 exit(1); 90 } 91 92 /* if there are no debug flags turned on, fork as a daemon should. 93 */ 94 if (DebugFlags) { 95 #if DEBUGGING 96 (void) fprintf(stderr, "[%ld] cron started\n", (long)getpid()); 97 #endif 98 } else { 99 switch (fork()) { 100 case -1: 101 log_it("CRON",getpid(),"DEATH","can't fork"); 102 exit(0); 103 break; 104 case 0: 105 /* child process */ 106 log_it("CRON",getpid(),"STARTUP","fork ok"); 107 (void) setsid(); 108 break; 109 default: 110 /* parent process should just die */ 111 _exit(0); 112 } 113 } 114 115 acquire_daemonlock(0); 116 database.head = NULL; 117 database.tail = NULL; 118 database.mtime = (time_t) 0; 119 load_database(&database); 120 set_time(1); 121 run_reboot_jobs(&database); 122 timeRunning = virtualTime = clockTime; 123 124 /* 125 * Too many clocks, not enough time (Al. Einstein) 126 * These clocks are in minutes since the epoch, adjusted for timezone. 127 * virtualTime: is the time it *would* be if we woke up 128 * promptly and nobody ever changed the clock. It is 129 * monotonically increasing... unless a timejump happens. 130 * At the top of the loop, all jobs for 'virtualTime' have run. 131 * timeRunning: is the time we last awakened. 132 * clockTime: is the time when set_time was last called. 133 */ 134 while (TRUE) { 135 int timeDiff; 136 int wakeupKind; 137 138 if (got_sighup) { 139 got_sighup = 0; 140 log_close(); 141 } 142 if (got_sigchld) { 143 got_sigchld = 0; 144 sigchld_reaper(); 145 } 146 147 load_database(&database); 148 /* ... wait for the time (in minutes) to change ... */ 149 do { 150 cron_sleep(timeRunning + 1); 151 set_time(0); 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 wakeupKind = -1; 167 if (timeDiff > -(3*MINUTE_COUNT)) 168 wakeupKind = 0; 169 if (timeDiff > 0) 170 wakeupKind = 1; 171 if (timeDiff > 5) 172 wakeupKind = 2; 173 if (timeDiff > (3*MINUTE_COUNT)) 174 wakeupKind = 3; 175 176 switch (wakeupKind) { 177 case 1: 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 Debug(DSCH, ("[%d], normal case %d minutes to go\n", 184 getpid(), timeDiff)) 185 do { 186 if (job_runqueue()) 187 sleep(10); 188 virtualTime++; 189 find_jobs(virtualTime, &database, 190 TRUE, TRUE); 191 } while (virtualTime < timeRunning); 192 break; 193 194 case 2: 195 /* 196 * case 2: timeDiff is a medium-sized positive 197 * number, for example because we went to DST 198 * run wildcard jobs once, then run any 199 * fixed-time jobs that would otherwise be 200 * skipped if we use up our minute (possible, 201 * if there are a lot of jobs to run) go 202 * around the loop again so that wildcard jobs 203 * have a chance to run, and we do our 204 * housekeeping. 205 */ 206 Debug(DSCH, ("[%d], DST begins %d minutes to go\n", 207 getpid(), timeDiff)) 208 /* run wildcard jobs for current minute */ 209 find_jobs(timeRunning, &database, TRUE, FALSE); 210 211 /* run fixed-time jobs for each minute missed */ 212 do { 213 if (job_runqueue()) 214 sleep(10); 215 virtualTime++; 216 find_jobs(virtualTime, &database, 217 FALSE, TRUE); 218 set_time(0); 219 } while (virtualTime< timeRunning && 220 clockTime == timeRunning); 221 break; 222 223 case 0: 224 /* 225 * case 3: timeDiff is a small or medium-sized 226 * negative num, eg. because of DST ending. 227 * Just run the wildcard jobs. The fixed-time 228 * jobs probably have already run, and should 229 * not be repeated. Virtual time does not 230 * change until we are caught up. 231 */ 232 Debug(DSCH, ("[%d], DST ends %d minutes to go\n", 233 getpid(), timeDiff)) 234 find_jobs(timeRunning, &database, TRUE, FALSE); 235 break; 236 default: 237 /* 238 * other: time has changed a *lot*, 239 * jump virtual time, and run everything 240 */ 241 Debug(DSCH, ("[%d], clock jumped\n", getpid())) 242 virtualTime = timeRunning; 243 find_jobs(timeRunning, &database, TRUE, TRUE); 244 } 245 } 246 247 /* Jobs to be run (if any) are loaded; clear the queue. */ 248 job_runqueue(); 249 } 250 } 251 252 static void 253 run_reboot_jobs(cron_db *db) { 254 user *u; 255 entry *e; 256 257 for (u = db->head; u != NULL; u = u->next) { 258 for (e = u->crontab; e != NULL; e = e->next) { 259 if (e->flags & WHEN_REBOOT) 260 job_add(e, u); 261 } 262 } 263 (void) job_runqueue(); 264 } 265 266 static void 267 find_jobs(int vtime, cron_db *db, int doWild, int doNonWild) { 268 time_t virtualSecond = vtime * SECONDS_PER_MINUTE; 269 struct tm *tm = gmtime(&virtualSecond); 270 int minute, hour, dom, month, dow; 271 user *u; 272 entry *e; 273 274 /* make 0-based values out of these so we can use them as indicies 275 */ 276 minute = tm->tm_min -FIRST_MINUTE; 277 hour = tm->tm_hour -FIRST_HOUR; 278 dom = tm->tm_mday -FIRST_DOM; 279 month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; 280 dow = tm->tm_wday -FIRST_DOW; 281 282 Debug(DSCH, ("[%ld] tick(%d,%d,%d,%d,%d) %s %s\n", 283 (long)getpid(), minute, hour, dom, month, dow, 284 doWild?" ":"No wildcard",doNonWild?" ":"Wildcard only")) 285 286 /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the 287 * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* 288 * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this 289 * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. 290 * like many bizarre things, it's the standard. 291 */ 292 for (u = db->head; u != NULL; u = u->next) { 293 for (e = u->crontab; e != NULL; e = e->next) { 294 Debug(DSCH|DEXT, ("user [%s:%ld:%ld:...] cmd=\"%s\"\n", 295 env_get("LOGNAME", e->envp), 296 (long)e->uid, (long)e->gid, e->cmd)) 297 if (bit_test(e->minute, minute) && 298 bit_test(e->hour, hour) && 299 bit_test(e->month, month) && 300 ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) 301 ? (bit_test(e->dow,dow) && bit_test(e->dom,dom)) 302 : (bit_test(e->dow,dow) || bit_test(e->dom,dom)) 303 ) 304 ) { 305 if ((doNonWild && 306 !(e->flags & (MIN_STAR|HR_STAR))) || 307 (doWild && (e->flags & (MIN_STAR|HR_STAR)))) 308 job_add(e, u); 309 } 310 } 311 } 312 } 313 314 /* 315 * Set StartTime and clockTime to the current time. 316 * These are used for computing what time it really is right now. 317 * Note that clockTime is a unix wallclock time converted to minutes. 318 */ 319 static void 320 set_time(int initialize) { 321 struct tm *tm; 322 static int isdst; 323 324 StartTime = time(NULL); 325 326 /* We adjust the time to GMT so we can catch DST changes. */ 327 tm = localtime(&StartTime); 328 if (initialize || tm->tm_isdst != isdst) { 329 isdst = tm->tm_isdst; 330 GMToff = get_gmtoff(&StartTime, tm); 331 } 332 clockTime = (StartTime + GMToff) / (time_t)SECONDS_PER_MINUTE; 333 } 334 335 /* 336 * Try to just hit the next minute. 337 */ 338 static void 339 cron_sleep(int target) { 340 time_t t; 341 int seconds_to_wait; 342 343 t = time(NULL) + GMToff; 344 seconds_to_wait = (int)(target * SECONDS_PER_MINUTE - t) + 1; 345 Debug(DSCH, ("[%ld] Target time=%ld, sec-to-wait=%d\n", 346 (long)getpid(), (long)target*SECONDS_PER_MINUTE, seconds_to_wait)) 347 348 if (seconds_to_wait > 0 && seconds_to_wait < 65) 349 sleep((unsigned int) seconds_to_wait); 350 } 351 352 static void 353 sighup_handler(int x) { 354 got_sighup = 1; 355 } 356 357 static void 358 sigchld_handler(int x) { 359 got_sigchld = 1; 360 } 361 362 static void 363 sigchld_reaper() { 364 WAIT_T waiter; 365 PID_T pid; 366 367 do { 368 pid = waitpid(-1, &waiter, WNOHANG); 369 switch (pid) { 370 case -1: 371 if (errno == EINTR) 372 continue; 373 Debug(DPROC, 374 ("[%ld] sigchld...no children\n", 375 (long)getpid())) 376 break; 377 case 0: 378 Debug(DPROC, 379 ("[%ld] sigchld...no dead kids\n", 380 (long)getpid())) 381 break; 382 default: 383 Debug(DPROC, 384 ("[%ld] sigchld...pid #%ld died, stat=%d\n", 385 (long)getpid(), (long)pid, WEXITSTATUS(waiter))) 386 } 387 } while (pid > 0); 388 } 389 390 static void 391 parse_args(int argc, char *argv[]) { 392 int argch; 393 394 while (-1 != (argch = getopt(argc, argv, "x:"))) { 395 switch (argch) { 396 default: 397 usage(); 398 case 'x': 399 if (!set_debug_flags(optarg)) 400 usage(); 401 break; 402 } 403 } 404 } 405