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.4 2003/11/16 11:51:14 eirikn 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 [-s] [-o] [-x debugflag[,...]]\n"); 54 fprintf(stderr, "\ndebugflags: "); 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 (void) signal(SIGCHLD, sigchld_handler); 81 #else 82 (void) signal(SIGCLD, SIG_IGN); 83 #endif 84 (void) signal(SIGHUP, sighup_handler); 85 86 acquire_daemonlock(0); 87 set_cron_uid(); 88 set_cron_cwd(); 89 90 #if defined(POSIX) 91 setenv("PATH", _PATH_DEFPATH, 1); 92 #endif 93 94 /* if there are no debug flags turned on, fork as a daemon should. 95 */ 96 # if DEBUGGING 97 if (DebugFlags) { 98 # else 99 if (0) { 100 # endif 101 (void) fprintf(stderr, "[%d] cron started\n", getpid()); 102 } else { 103 if (daemon(1, 0) == -1) { 104 log_it("CRON",getpid(),"DEATH","can't become daemon"); 105 exit(0); 106 } 107 } 108 109 acquire_daemonlock(0); 110 database.head = NULL; 111 database.tail = NULL; 112 database.mtime = (time_t) 0; 113 load_database(&database); 114 run_reboot_jobs(&database); 115 cron_sync(); 116 while (TRUE) { 117 # if DEBUGGING 118 /* if (!(DebugFlags & DTEST)) */ 119 # endif /*DEBUGGING*/ 120 cron_sleep(&database); 121 122 load_database(&database); 123 124 /* do this iteration 125 */ 126 cron_tick(&database); 127 128 /* sleep 1 minute 129 */ 130 TargetTime += 60; 131 } 132 } 133 134 135 static void 136 run_reboot_jobs(cron_db *db) 137 { 138 register user *u; 139 register entry *e; 140 141 for (u = db->head; u != NULL; u = u->next) { 142 for (e = u->crontab; e != NULL; e = e->next) { 143 if (e->flags & WHEN_REBOOT) { 144 job_add(e, u); 145 } 146 } 147 } 148 (void) job_runqueue(); 149 } 150 151 152 static void 153 cron_tick(cron_db *db) 154 { 155 static struct tm lasttm; 156 static time_t diff = 0, /* time difference in seconds from the last offset change */ 157 difflimit = 0; /* end point for the time zone correction */ 158 struct tm otztm; /* time in the old time zone */ 159 int otzminute, otzhour, otzdom, otzmonth, otzdow; 160 register struct tm *tm = localtime(&TargetTime); 161 register int minute, hour, dom, month, dow; 162 register user *u; 163 register entry *e; 164 165 /* make 0-based values out of these so we can use them as indicies 166 */ 167 minute = tm->tm_min -FIRST_MINUTE; 168 hour = tm->tm_hour -FIRST_HOUR; 169 dom = tm->tm_mday -FIRST_DOM; 170 month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; 171 dow = tm->tm_wday -FIRST_DOW; 172 173 Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d)\n", 174 getpid(), minute, hour, dom, month, dow)) 175 176 if (dst_enabled && last_time != 0 177 && TargetTime > last_time /* exclude stepping back */ 178 && tm->tm_gmtoff != lasttm.tm_gmtoff ) { 179 180 diff = tm->tm_gmtoff - lasttm.tm_gmtoff; 181 182 if ( diff > 0 ) { /* ST->DST */ 183 /* mark jobs for an earlier run */ 184 difflimit = TargetTime + diff; 185 for (u = db->head; u != NULL; u = u->next) { 186 for (e = u->crontab; e != NULL; e = e->next) { 187 e->flags &= ~NOT_UNTIL; 188 if ( e->lastrun >= TargetTime ) 189 e->lastrun = 0; 190 /* not include the ends of hourly ranges */ 191 if ( e->lastrun < TargetTime - 3600 ) 192 e->flags |= RUN_AT; 193 else 194 e->flags &= ~RUN_AT; 195 } 196 } 197 } else { /* diff < 0 : DST->ST */ 198 /* mark jobs for skipping */ 199 difflimit = TargetTime - diff; 200 for (u = db->head; u != NULL; u = u->next) { 201 for (e = u->crontab; e != NULL; e = e->next) { 202 e->flags |= NOT_UNTIL; 203 e->flags &= ~RUN_AT; 204 } 205 } 206 } 207 } 208 209 if (diff != 0) { 210 /* if the time was reset of the end of special zone is reached */ 211 if (last_time == 0 || TargetTime >= difflimit) { 212 /* disable the TZ switch checks */ 213 diff = 0; 214 difflimit = 0; 215 for (u = db->head; u != NULL; u = u->next) { 216 for (e = u->crontab; e != NULL; e = e->next) { 217 e->flags &= ~(RUN_AT|NOT_UNTIL); 218 } 219 } 220 } else { 221 /* get the time in the old time zone */ 222 time_t difftime = TargetTime + tm->tm_gmtoff - diff; 223 gmtime_r(&difftime, &otztm); 224 225 /* make 0-based values out of these so we can use them as indicies 226 */ 227 otzminute = otztm.tm_min -FIRST_MINUTE; 228 otzhour = otztm.tm_hour -FIRST_HOUR; 229 otzdom = otztm.tm_mday -FIRST_DOM; 230 otzmonth = otztm.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; 231 otzdow = otztm.tm_wday -FIRST_DOW; 232 } 233 } 234 235 /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the 236 * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* 237 * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this 238 * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. 239 * like many bizarre things, it's the standard. 240 */ 241 for (u = db->head; u != NULL; u = u->next) { 242 for (e = u->crontab; e != NULL; e = e->next) { 243 Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n", 244 env_get("LOGNAME", e->envp), 245 e->uid, e->gid, e->cmd)) 246 247 if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) { 248 if (bit_test(e->minute, otzminute) 249 && bit_test(e->hour, otzhour) 250 && bit_test(e->month, otzmonth) 251 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) 252 ? (bit_test(e->dow,otzdow) && bit_test(e->dom,otzdom)) 253 : (bit_test(e->dow,otzdow) || bit_test(e->dom,otzdom)) 254 ) 255 ) { 256 if ( e->flags & RUN_AT ) { 257 e->flags &= ~RUN_AT; 258 e->lastrun = TargetTime; 259 job_add(e, u); 260 continue; 261 } else 262 e->flags &= ~NOT_UNTIL; 263 } else if ( e->flags & NOT_UNTIL ) 264 continue; 265 } 266 267 if (bit_test(e->minute, minute) 268 && bit_test(e->hour, hour) 269 && bit_test(e->month, month) 270 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) 271 ? (bit_test(e->dow,dow) && bit_test(e->dom,dom)) 272 : (bit_test(e->dow,dow) || bit_test(e->dom,dom)) 273 ) 274 ) { 275 e->flags &= ~RUN_AT; 276 e->lastrun = TargetTime; 277 job_add(e, u); 278 } 279 } 280 } 281 282 last_time = TargetTime; 283 lasttm = *tm; 284 } 285 286 287 /* the task here is to figure out how long it's going to be until :00 of the 288 * following minute and initialize TargetTime to this value. TargetTime 289 * will subsequently slide 60 seconds at a time, with correction applied 290 * implicitly in cron_sleep(). it would be nice to let cron execute in 291 * the "current minute" before going to sleep, but by restarting cron you 292 * could then get it to execute a given minute's jobs more than once. 293 * instead we have the chance of missing a minute's jobs completely, but 294 * that's something sysadmin's know to expect what with crashing computers.. 295 */ 296 static void 297 cron_sync(void) 298 { 299 register struct tm *tm; 300 301 TargetTime = time((time_t*)0); 302 tm = localtime(&TargetTime); 303 TargetTime += (60 - tm->tm_sec); 304 } 305 306 307 static void 308 cron_sleep(cron_db *db) 309 { 310 int seconds_to_wait = 0; 311 312 /* 313 * Loop until we reach the top of the next minute, sleep when possible. 314 */ 315 316 for (;;) { 317 seconds_to_wait = (int) (TargetTime - time((time_t*)0)); 318 319 /* 320 * If the seconds_to_wait value is insane, jump the cron 321 */ 322 323 if (seconds_to_wait < -600 || seconds_to_wait > 600) { 324 cron_clean(db); 325 cron_sync(); 326 continue; 327 } 328 329 Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n", 330 getpid(), (long)TargetTime, seconds_to_wait)) 331 332 /* 333 * If we've run out of wait time or there are no jobs left 334 * to run, break 335 */ 336 337 if (seconds_to_wait <= 0) 338 break; 339 if (job_runqueue() == 0) { 340 Debug(DSCH, ("[%d] sleeping for %d seconds\n", 341 getpid(), seconds_to_wait)) 342 343 sleep(seconds_to_wait); 344 } 345 } 346 } 347 348 349 /* if the time was changed abruptly, clear the flags related 350 * to the daylight time switch handling to avoid strange effects 351 */ 352 353 static void 354 cron_clean(cron_db *db) 355 { 356 user *u; 357 entry *e; 358 359 last_time = 0; 360 361 for (u = db->head; u != NULL; u = u->next) { 362 for (e = u->crontab; e != NULL; e = e->next) { 363 e->flags &= ~(RUN_AT|NOT_UNTIL); 364 } 365 } 366 } 367 368 #ifdef USE_SIGCHLD 369 static void 370 sigchld_handler(int x) 371 { 372 WAIT_T waiter; 373 PID_T pid; 374 375 for (;;) { 376 #ifdef POSIX 377 pid = waitpid(-1, &waiter, WNOHANG); 378 #else 379 pid = wait3(&waiter, WNOHANG, (struct rusage *)0); 380 #endif 381 switch (pid) { 382 case -1: 383 Debug(DPROC, 384 ("[%d] sigchld...no children\n", getpid())) 385 return; 386 case 0: 387 Debug(DPROC, 388 ("[%d] sigchld...no dead kids\n", getpid())) 389 return; 390 default: 391 Debug(DPROC, 392 ("[%d] sigchld...pid #%d died, stat=%d\n", 393 getpid(), pid, WEXITSTATUS(waiter))) 394 } 395 } 396 } 397 #endif /*USE_SIGCHLD*/ 398 399 400 static void 401 sighup_handler(int x) 402 { 403 log_close(); 404 } 405 406 407 static void 408 parse_args(int argc, char **argv) 409 { 410 int argch; 411 412 while ((argch = getopt(argc, argv, "osx:")) != -1) { 413 switch (argch) { 414 case 'o': 415 dst_enabled = 0; 416 break; 417 case 's': 418 dst_enabled = 1; 419 break; 420 case 'x': 421 if (!set_debug_flags(optarg)) 422 usage(); 423 break; 424 default: 425 usage(); 426 } 427 } 428 } 429 430