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