1 /* Copyright 1988,1990,1993,1994 by Paul Vixie 2 * All rights reserved 3 * 4 * Distribute freely, except: don't remove my name from the source or 5 * documentation (don't take credit for my work), mark your changes (don't 6 * get me blamed for your possible bugs), don't alter or remove this 7 * notice. May be sold if buildable source is provided to buyer. No 8 * warrantee of any kind, express or implied, is included with this 9 * software; use at your own risk, responsibility for damages (if any) to 10 * anyone resulting from the use of this software rests entirely with the 11 * user. 12 * 13 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and 14 * I'll try to keep a version up to date. I can be reached as follows: 15 * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul 16 * 17 * $FreeBSD: src/usr.sbin/cron/cron/cron.c,v 1.9.2.2 2001/05/28 23:37:26 babkin Exp $ 18 * $DragonFly: src/usr.sbin/cron/cron/cron.c,v 1.8 2006/03/18 20:29:50 dillon Exp $ 19 */ 20 21 #define MAIN_PROGRAM 22 23 24 #include "cron.h" 25 #include <sys/signal.h> 26 #if SYS_TIME_H 27 # include <sys/time.h> 28 #else 29 # include <time.h> 30 #endif 31 32 33 static void usage(void), 34 run_reboot_jobs(cron_db *), 35 cron_tick(cron_db *), 36 cron_sync(void), 37 cron_sleep(cron_db *), 38 cron_clean(cron_db *), 39 #ifdef USE_SIGCHLD 40 sigchld_handler(int), 41 #endif 42 sighup_handler(int), 43 parse_args(int c, char *v[]); 44 45 static time_t last_time = 0; 46 static int dst_enabled = 0; 47 48 static void 49 usage(void) 50 { 51 char **dflags; 52 53 fprintf(stderr, "usage: cron [-j jitter] [-J rootjitter] " 54 "[-s] [-o] [-x debugflag[,...]]\n"); 55 56 for(dflags = DebugFlagNames; *dflags; dflags++) { 57 fprintf(stderr, "%s ", *dflags); 58 } 59 fprintf(stderr, "\n"); 60 61 exit(ERROR_EXIT); 62 } 63 64 65 int 66 main(int argc, char **argv) 67 { 68 cron_db database; 69 70 ProgramName = argv[0]; 71 72 #if defined(BSD) 73 setlinebuf(stdout); 74 setlinebuf(stderr); 75 #endif 76 77 parse_args(argc, argv); 78 79 #ifdef USE_SIGCHLD 80 signal(SIGCHLD, sigchld_handler); 81 #else 82 signal(SIGCLD, SIG_IGN); 83 #endif 84 signal(SIGHUP, sighup_handler); 85 86 acquire_daemonlock(0); 87 set_cron_uid(); 88 set_cron_cwd(); 89 90 #if defined(POSIX) 91 if (setenv("PATH", _PATH_DEFPATH, 1) == -1) { 92 log_it("CRON", getpid(), "DEATH", 93 "setenv: cannot set PATH"); 94 exit(0); 95 } 96 #endif 97 98 /* if there are no debug flags turned on, fork as a daemon should. 99 */ 100 # if DEBUGGING 101 if (DebugFlags) { 102 # else 103 if (0) { 104 # endif 105 fprintf(stderr, "[%d] cron started\n", getpid()); 106 } else { 107 if (daemon(1, 0) == -1) { 108 log_it("CRON",getpid(),"DEATH","can't become daemon"); 109 exit(0); 110 } 111 } 112 113 acquire_daemonlock(0); 114 database.head = NULL; 115 database.tail = NULL; 116 database.mtime = (time_t) 0; 117 load_database(&database); 118 run_reboot_jobs(&database); 119 cron_sync(); 120 while (TRUE) { 121 # if DEBUGGING 122 /* if (!(DebugFlags & DTEST)) */ 123 # endif /*DEBUGGING*/ 124 cron_sleep(&database); 125 126 load_database(&database); 127 128 /* do this iteration 129 */ 130 cron_tick(&database); 131 132 /* sleep 1 minute 133 */ 134 TargetTime += 60; 135 } 136 } 137 138 139 static void 140 run_reboot_jobs(cron_db *db) 141 { 142 user *u; 143 entry *e; 144 145 for (u = db->head; u != NULL; u = u->next) { 146 for (e = u->crontab; e != NULL; e = e->next) { 147 if (e->flags & WHEN_REBOOT) { 148 job_add(e, u); 149 } 150 } 151 } 152 job_runqueue(); 153 } 154 155 156 static void 157 cron_tick(cron_db *db) 158 { 159 static struct tm lasttm; 160 static time_t diff; /* delta time from the last offset change */ 161 static time_t difflimit; /* end point for the time zone correction */ 162 struct tm otztm; /* time in the old time zone */ 163 int otzminute, otzhour, otzdom, otzmonth, otzdow; 164 struct tm *tm; 165 int minute, hour, dom, month, dow; 166 user *u; 167 entry *e; 168 169 tm = localtime(&TargetTime); 170 /* make 0-based values out of these so we can use them as indicies 171 */ 172 minute = tm->tm_min -FIRST_MINUTE; 173 hour = tm->tm_hour -FIRST_HOUR; 174 dom = tm->tm_mday -FIRST_DOM; 175 month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; 176 dow = tm->tm_wday -FIRST_DOW; 177 178 Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d)\n", 179 getpid(), minute, hour, dom, month, dow)) 180 181 if (dst_enabled && last_time != 0 182 && TargetTime > last_time /* exclude stepping back */ 183 && tm->tm_gmtoff != lasttm.tm_gmtoff ) { 184 185 diff = tm->tm_gmtoff - lasttm.tm_gmtoff; 186 187 if ( diff > 0 ) { /* ST->DST */ 188 /* mark jobs for an earlier run */ 189 difflimit = TargetTime + diff; 190 for (u = db->head; u != NULL; u = u->next) { 191 for (e = u->crontab; e != NULL; e = e->next) { 192 e->flags &= ~NOT_UNTIL; 193 if ( e->lastrun >= TargetTime ) 194 e->lastrun = 0; 195 /* not include the ends of hourly ranges */ 196 if ( e->lastrun < TargetTime - 3600 ) 197 e->flags |= RUN_AT; 198 else 199 e->flags &= ~RUN_AT; 200 } 201 } 202 } else { /* diff < 0 : DST->ST */ 203 /* mark jobs for skipping */ 204 difflimit = TargetTime - diff; 205 for (u = db->head; u != NULL; u = u->next) { 206 for (e = u->crontab; e != NULL; e = e->next) { 207 e->flags |= NOT_UNTIL; 208 e->flags &= ~RUN_AT; 209 } 210 } 211 } 212 } 213 214 if (diff != 0) { 215 /* if the time was reset of the end of special zone is reached */ 216 if (last_time == 0 || TargetTime >= difflimit) { 217 /* disable the TZ switch checks */ 218 diff = 0; 219 difflimit = 0; 220 for (u = db->head; u != NULL; u = u->next) { 221 for (e = u->crontab; e != NULL; e = e->next) { 222 e->flags &= ~(RUN_AT|NOT_UNTIL); 223 } 224 } 225 } else { 226 /* get the time in the old time zone */ 227 time_t difftime = TargetTime + tm->tm_gmtoff - diff; 228 gmtime_r(&difftime, &otztm); 229 230 /* make 0-based values out of these so we can use them as indicies 231 */ 232 otzminute = otztm.tm_min -FIRST_MINUTE; 233 otzhour = otztm.tm_hour -FIRST_HOUR; 234 otzdom = otztm.tm_mday -FIRST_DOM; 235 otzmonth = otztm.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; 236 otzdow = otztm.tm_wday -FIRST_DOW; 237 } 238 } 239 240 /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the 241 * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* 242 * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this 243 * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. 244 * like many bizarre things, it's the standard. 245 */ 246 for (u = db->head; u != NULL; u = u->next) { 247 for (e = u->crontab; e != NULL; e = e->next) { 248 Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n", 249 env_get("LOGNAME", e->envp), 250 e->uid, e->gid, e->cmd)) 251 252 if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) { 253 if (bit_test(e->minute, otzminute) 254 && bit_test(e->hour, otzhour) 255 && bit_test(e->month, otzmonth) 256 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) 257 ? (bit_test(e->dow,otzdow) && bit_test(e->dom,otzdom)) 258 : (bit_test(e->dow,otzdow) || bit_test(e->dom,otzdom)) 259 ) 260 ) { 261 if ( e->flags & RUN_AT ) { 262 e->flags &= ~RUN_AT; 263 e->lastrun = TargetTime; 264 job_add(e, u); 265 continue; 266 } else 267 e->flags &= ~NOT_UNTIL; 268 } else if ( e->flags & NOT_UNTIL ) 269 continue; 270 } 271 272 if (bit_test(e->minute, minute) 273 && bit_test(e->hour, hour) 274 && bit_test(e->month, month) 275 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) 276 ? (bit_test(e->dow,dow) && bit_test(e->dom,dom)) 277 : (bit_test(e->dow,dow) || bit_test(e->dom,dom)) 278 ) 279 ) { 280 e->flags &= ~RUN_AT; 281 e->lastrun = TargetTime; 282 job_add(e, u); 283 } 284 } 285 } 286 287 last_time = TargetTime; 288 lasttm = *tm; 289 } 290 291 292 /* the task here is to figure out how long it's going to be until :00 of the 293 * following minute and initialize TargetTime to this value. TargetTime 294 * will subsequently slide 60 seconds at a time, with correction applied 295 * implicitly in cron_sleep(). it would be nice to let cron execute in 296 * the "current minute" before going to sleep, but by restarting cron you 297 * could then get it to execute a given minute's jobs more than once. 298 * instead we have the chance of missing a minute's jobs completely, but 299 * that's something sysadmin's know to expect what with crashing computers.. 300 */ 301 static void 302 cron_sync(void) 303 { 304 struct tm *tm; 305 306 TargetTime = time(NULL); 307 tm = localtime(&TargetTime); 308 TargetTime += (60 - tm->tm_sec); 309 } 310 311 312 static void 313 cron_sleep(cron_db *db) 314 { 315 int seconds_to_wait = 0; 316 317 /* 318 * Loop until we reach the top of the next minute, sleep when possible. 319 */ 320 321 for (;;) { 322 seconds_to_wait = (int) (TargetTime - time(NULL)); 323 324 /* 325 * If the seconds_to_wait value is insane, jump the cron 326 */ 327 328 if (seconds_to_wait < -600 || seconds_to_wait > 600) { 329 cron_clean(db); 330 cron_sync(); 331 continue; 332 } 333 334 Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n", 335 getpid(), (long)TargetTime, seconds_to_wait)) 336 337 /* 338 * If we've run out of wait time or there are no jobs left 339 * to run, break 340 */ 341 342 if (seconds_to_wait <= 0) 343 break; 344 if (job_runqueue() == 0) { 345 Debug(DSCH, ("[%d] sleeping for %d seconds\n", 346 getpid(), seconds_to_wait)) 347 348 sleep(seconds_to_wait); 349 } 350 } 351 } 352 353 354 /* if the time was changed abruptly, clear the flags related 355 * to the daylight time switch handling to avoid strange effects 356 */ 357 358 static void 359 cron_clean(cron_db *db) 360 { 361 user *u; 362 entry *e; 363 364 last_time = 0; 365 366 for (u = db->head; u != NULL; u = u->next) { 367 for (e = u->crontab; e != NULL; e = e->next) { 368 e->flags &= ~(RUN_AT|NOT_UNTIL); 369 } 370 } 371 } 372 373 #ifdef USE_SIGCHLD 374 static void 375 sigchld_handler(int x) 376 { 377 WAIT_T waiter; 378 PID_T pid; 379 380 for (;;) { 381 #ifdef POSIX 382 pid = waitpid(-1, &waiter, WNOHANG); 383 #else 384 pid = wait3(&waiter, WNOHANG, NULL); 385 #endif 386 switch (pid) { 387 case -1: 388 Debug(DPROC, 389 ("[%d] sigchld...no children\n", getpid())) 390 return; 391 case 0: 392 Debug(DPROC, 393 ("[%d] sigchld...no dead kids\n", getpid())) 394 return; 395 default: 396 Debug(DPROC, 397 ("[%d] sigchld...pid #%d died, stat=%d\n", 398 getpid(), pid, WEXITSTATUS(waiter))) 399 } 400 } 401 } 402 #endif /*USE_SIGCHLD*/ 403 404 405 static void 406 sighup_handler(int x) 407 { 408 log_close(); 409 } 410 411 412 static void 413 parse_args(int argc, char **argv) 414 { 415 int argch; 416 char *endp; 417 418 while ((argch = getopt(argc, argv, "j:J:osx:")) != -1) { 419 switch (argch) { 420 case 'j': 421 Jitter = strtoul(optarg, &endp, 10); 422 if (*optarg == '\0' || *endp != '\0' || Jitter > 60) 423 errx(ERROR_EXIT, 424 "bad value for jitter: %s", optarg); 425 break; 426 case 'J': 427 RootJitter = strtoul(optarg, &endp, 10); 428 if (*optarg == '\0' || *endp != '\0' || RootJitter > 60) 429 errx(ERROR_EXIT, 430 "bad value for root jitter: %s", optarg); 431 break; 432 case 'o': 433 dst_enabled = 0; 434 break; 435 case 's': 436 dst_enabled = 1; 437 break; 438 case 'x': 439 if (!set_debug_flags(optarg)) 440 usage(); 441 break; 442 default: 443 usage(); 444 } 445 } 446 } 447 448