Changeset 2725 in MondoRescue for branches/2.2.9/mindi-busybox/miscutils/crond.c
- Timestamp:
- Feb 25, 2011, 9:26:54 PM (13 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/2.2.9/mindi-busybox/miscutils/crond.c
r1765 r2725 9 9 * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 10 10 * 11 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.11 * Licensed under GPLv2 or later, see file LICENSE in this source tree. 12 12 */ 13 13 14 #include <sys/syslog.h>15 14 #include "libbb.h" 16 17 #ifndef CRONTABS 18 #define CRONTABS "/var/spool/cron/crontabs" 19 #endif 20 #ifndef TMPDIR 21 #define TMPDIR "/var/spool/cron" 22 #endif 15 #include <syslog.h> 16 17 /* glibc frees previous setenv'ed value when we do next setenv() 18 * of the same variable. uclibc does not do this! */ 19 #if (defined(__GLIBC__) && !defined(__UCLIBC__)) /* || OTHER_SAFE_LIBC... */ 20 # define SETENV_LEAKS 0 21 #else 22 # define SETENV_LEAKS 1 23 #endif 24 25 26 #define TMPDIR CONFIG_FEATURE_CROND_DIR 27 #define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs" 23 28 #ifndef SENDMAIL 24 # define SENDMAIL"sendmail"29 # define SENDMAIL "sendmail" 25 30 #endif 26 31 #ifndef SENDMAIL_ARGS 27 # define SENDMAIL_ARGS "-ti", "oem"32 # define SENDMAIL_ARGS "-ti" 28 33 #endif 29 34 #ifndef CRONUPDATE 30 # define CRONUPDATE"cron.update"35 # define CRONUPDATE "cron.update" 31 36 #endif 32 37 #ifndef MAXLINES 33 #define MAXLINES 256 /* max lines in non-root crontabs */ 34 #endif 38 # define MAXLINES 256 /* max lines in non-root crontabs */ 39 #endif 40 35 41 36 42 typedef struct CronFile { 37 struct CronFile *cf_ Next;38 struct CronLine *cf_ LineBase;39 char *cf_ User; /* username */40 int cf_Ready;/* bool: one or more jobs ready */41 int cf_Running;/* bool: one or more jobs running */42 int cf_Deleted; /* marked for deletion, ignore*/43 struct CronFile *cf_next; 44 struct CronLine *cf_lines; 45 char *cf_username; 46 smallint cf_wants_starting; /* bool: one or more jobs ready */ 47 smallint cf_has_running; /* bool: one or more jobs running */ 48 smallint cf_deleted; /* marked for deletion (but still has running jobs) */ 43 49 } CronFile; 44 50 45 51 typedef struct CronLine { 46 struct CronLine *cl_Next; 47 char *cl_Shell; /* shell command */ 48 pid_t cl_Pid; /* running pid, 0, or armed (-1) */ 49 int cl_MailFlag; /* running pid is for mail */ 50 int cl_MailPos; /* 'empty file' size */ 51 char cl_Mins[60]; /* 0-59 */ 52 char cl_Hrs[24]; /* 0-23 */ 53 char cl_Days[32]; /* 1-31 */ 54 char cl_Mons[12]; /* 0-11 */ 55 char cl_Dow[7]; /* 0-6, beginning sunday */ 52 struct CronLine *cl_next; 53 char *cl_cmd; /* shell command */ 54 pid_t cl_pid; /* >0:running, <0:needs to be started in this minute, 0:dormant */ 55 #if ENABLE_FEATURE_CROND_CALL_SENDMAIL 56 int cl_empty_mail_size; /* size of mail header only, 0 if no mailfile */ 57 char *cl_mailto; /* whom to mail results, may be NULL */ 58 #endif 59 /* ordered by size, not in natural order. makes code smaller: */ 60 char cl_Dow[7]; /* 0-6, beginning sunday */ 61 char cl_Mons[12]; /* 0-11 */ 62 char cl_Hrs[24]; /* 0-23 */ 63 char cl_Days[32]; /* 1-31 */ 64 char cl_Mins[60]; /* 0-59 */ 56 65 } CronLine; 57 66 58 #define RUN_RANOUT 1 59 #define RUN_RUNNING 2 60 #define RUN_FAILED 3 61 62 #define DaemonUid 0 63 64 #if ENABLE_DEBUG_CROND_OPTION 65 static unsigned DebugOpt; 66 #endif 67 68 static unsigned LogLevel = 8; 69 static const char *LogFile; 70 static const char *CDir = CRONTABS; 71 72 static void startlogger(void); 73 74 static void CheckUpdates(void); 75 static void SynchronizeDir(void); 76 static int TestJobs(time_t t1, time_t t2); 77 static void RunJobs(void); 78 static int CheckJobs(void); 79 80 static void RunJob(const char *user, CronLine * line); 81 82 #if ENABLE_FEATURE_CROND_CALL_SENDMAIL 83 static void EndJob(const char *user, CronLine * line); 67 68 #define DAEMON_UID 0 69 70 71 enum { 72 OPT_l = (1 << 0), 73 OPT_L = (1 << 1), 74 OPT_f = (1 << 2), 75 OPT_b = (1 << 3), 76 OPT_S = (1 << 4), 77 OPT_c = (1 << 5), 78 OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D, 79 }; 80 #if ENABLE_FEATURE_CROND_D 81 # define DebugOpt (option_mask32 & OPT_d) 84 82 #else 85 #define EndJob(user, line) line->cl_Pid = 0 86 #endif 87 88 static void DeleteFile(const char *userName); 89 90 static CronFile *FileBase; 91 92 83 # define DebugOpt 0 84 #endif 85 86 87 struct globals { 88 unsigned log_level; /* = 8; */ 89 time_t crontab_dir_mtime; 90 const char *log_filename; 91 const char *crontab_dir_name; /* = CRONTABS; */ 92 CronFile *cron_files; 93 #if SETENV_LEAKS 94 char *env_var_user; 95 char *env_var_home; 96 #endif 97 } FIX_ALIASING; 98 #define G (*(struct globals*)&bb_common_bufsiz1) 99 #define INIT_G() do { \ 100 G.log_level = 8; \ 101 G.crontab_dir_name = CRONTABS; \ 102 } while (0) 103 104 105 /* 0 is the most verbose, default 8 */ 106 #define LVL5 "\x05" 107 #define LVL7 "\x07" 108 #define LVL8 "\x08" 109 #define WARN9 "\x49" 110 #define DIE9 "\xc9" 111 /* level >= 20 is "error" */ 112 #define ERR20 "\x14" 113 114 static void crondlog(const char *ctl, ...) __attribute__ ((format (printf, 1, 2))); 93 115 static void crondlog(const char *ctl, ...) 94 116 { 95 117 va_list va; 96 const char *fmt; 97 int level = (int) (ctl[0] & 0xf); 98 int type = level == 20 ? 99 LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE); 100 118 int level = (ctl[0] & 0x1f); 101 119 102 120 va_start(va, ctl); 103 fmt = ctl + 1; 104 if (level >= LogLevel) { 105 106 #if ENABLE_DEBUG_CROND_OPTION 107 if (DebugOpt) { 108 vfprintf(stderr, fmt, va); 109 } else 110 #endif 111 if (LogFile == 0) { 112 vsyslog(type, fmt, va); 121 if (level >= (int)G.log_level) { 122 /* Debug mode: all to (non-redirected) stderr, */ 123 /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */ 124 if (!DebugOpt && G.log_filename) { 125 /* Otherwise (log to file): we reopen log file at every write: */ 126 int logfd = open_or_warn(G.log_filename, O_WRONLY | O_CREAT | O_APPEND); 127 if (logfd >= 0) 128 xmove_fd(logfd, STDERR_FILENO); 129 } 130 /* When we log to syslog, level > 8 is logged at LOG_ERR 131 * syslog level, level <= 8 is logged at LOG_INFO. */ 132 if (level > 8) { 133 bb_verror_msg(ctl + 1, va, /* strerr: */ NULL); 113 134 } else { 114 #if !ENABLE_DEBUG_CROND_OPTION 115 int logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600); 116 #else 117 int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600); 118 #endif 119 if (logfd >= 0) { 120 vdprintf(logfd, fmt, va); 121 close(logfd); 122 } 135 char *msg = NULL; 136 vasprintf(&msg, ctl + 1, va); 137 bb_info_msg("%s: %s", applet_name, msg); 138 free(msg); 123 139 } 124 140 } 125 141 va_end(va); 126 if (ctl[0] & 0 200) {142 if (ctl[0] & 0x80) 127 143 exit(20); 128 } 129 } 130 131 int crond_main(int ac, char **av); 132 int crond_main(int ac, char **av) 133 { 134 unsigned opt; 135 char *lopt, *Lopt, *copt; 136 USE_DEBUG_CROND_OPTION(char *dopt;) 137 138 opt_complementary = "f-b:b-f:S-L:L-S" USE_DEBUG_CROND_OPTION(":d-l"); 139 opterr = 0; /* disable getopt 'errors' message. */ 140 opt = getopt32(av, "l:L:fbSc:" USE_DEBUG_CROND_OPTION("d:"), 141 &lopt, &Lopt, &copt USE_DEBUG_CROND_OPTION(, &dopt)); 142 if (opt & 1) /* -l */ 143 LogLevel = xatou(lopt); 144 if (opt & 2) /* -L */ 145 if (*Lopt) 146 LogFile = Lopt; 147 if (opt & 32) /* -c */ 148 if (*copt) 149 CDir = copt; 150 #if ENABLE_DEBUG_CROND_OPTION 151 if (opt & 64) { /* -d */ 152 DebugOpt = xatou(dopt); 153 LogLevel = 0; 154 } 155 #endif 156 157 /* close stdin and stdout, stderr. 158 * close unused descriptors - don't need. 159 * optional detach from controlling terminal 160 */ 161 if (!(opt & 4)) 162 bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, av); 163 164 xchdir(CDir); 165 signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */ 166 167 startlogger(); /* need if syslog mode selected */ 168 169 /* 170 * main loop - synchronize to 1 second after the minute, minimum sleep 171 * of 1 second. 172 */ 173 crondlog("\011%s " BB_VER " started, log level %d\n", 174 applet_name, LogLevel); 175 176 SynchronizeDir(); 177 178 { 179 time_t t1 = time(NULL); 180 time_t t2; 181 long dt; 182 int rescan = 60; 183 short sleep_time = 60; 184 185 write_pidfile("/var/run/crond.pid"); 186 for (;;) { 187 sleep((sleep_time + 1) - (short) (time(NULL) % sleep_time)); 188 189 t2 = time(NULL); 190 dt = t2 - t1; 191 192 /* 193 * The file 'cron.update' is checked to determine new cron 194 * jobs. The directory is rescanned once an hour to deal 195 * with any screwups. 196 * 197 * check for disparity. Disparities over an hour either way 198 * result in resynchronization. A reverse-indexed disparity 199 * less then an hour causes us to effectively sleep until we 200 * match the original time (i.e. no re-execution of jobs that 201 * have just been run). A forward-indexed disparity less then 202 * an hour causes intermediate jobs to be run, but only once 203 * in the worst case. 204 * 205 * when running jobs, the inequality used is greater but not 206 * equal to t1, and less then or equal to t2. 207 */ 208 209 if (--rescan == 0) { 210 rescan = 60; 211 SynchronizeDir(); 212 } 213 CheckUpdates(); 214 #if ENABLE_DEBUG_CROND_OPTION 215 if (DebugOpt) 216 crondlog("\005Wakeup dt=%d\n", dt); 217 #endif 218 if (dt < -60 * 60 || dt > 60 * 60) { 219 t1 = t2; 220 crondlog("\111time disparity of %d minutes detected\n", dt / 60); 221 } else if (dt > 0) { 222 TestJobs(t1, t2); 223 RunJobs(); 224 sleep(5); 225 if (CheckJobs() > 0) { 226 sleep_time = 10; 227 } else { 228 sleep_time = 60; 229 } 230 t1 = t2; 231 } 232 } 233 } 234 return 0; /* not reached */ 235 } 236 237 static int ChangeUser(const char *user) 238 { 239 struct passwd *pas; 240 const char *err_msg; 241 242 /* 243 * Obtain password entry and change privileges 244 */ 245 pas = getpwnam(user); 246 if (pas == 0) { 247 crondlog("\011failed to get uid for %s", user); 248 return -1; 249 } 250 setenv("USER", pas->pw_name, 1); 251 setenv("HOME", pas->pw_dir, 1); 252 setenv("SHELL", DEFAULT_SHELL, 1); 253 254 /* 255 * Change running state to the user in question 256 */ 257 err_msg = change_identity_e2str(pas); 258 if (err_msg) { 259 crondlog("\011%s for user %s", err_msg, user); 260 return -1; 261 } 262 if (chdir(pas->pw_dir) < 0) { 263 crondlog("\011chdir failed: %s: %m", pas->pw_dir); 264 if (chdir(TMPDIR) < 0) { 265 crondlog("\011chdir failed: %s: %m", TMPDIR); 266 return -1; 267 } 268 } 269 return pas->pw_uid; 270 } 271 272 static void startlogger(void) 273 { 274 if (LogFile == 0) { 275 openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON); 276 } 277 #if ENABLE_DEBUG_CROND_OPTION 278 else { /* test logfile */ 279 int logfd; 280 281 logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600); 282 if (logfd >= 0) { 283 close(logfd); 284 } 285 } 286 #endif 287 } 288 144 } 289 145 290 146 static const char DowAry[] ALIGN1 = … … 298 154 ; 299 155 300 static char *ParseField(char *user, char *ary, int modvalue, int off,156 static void ParseField(char *user, char *ary, int modvalue, int off, 301 157 const char *names, char *ptr) 302 158 /* 'names' is a pointer to a set of 3-char abbreviations */ … … 306 162 int n2 = -1; 307 163 308 if (base == NULL) {309 return NULL;310 }311 312 while ( *ptr != ' ' && *ptr != '\t' && *ptr != '\n') {164 // this can't happen due to config_read() 165 /*if (base == NULL) 166 return;*/ 167 168 while (1) { 313 169 int skip = 0; 314 170 315 171 /* Handle numeric digit or symbol or '*' */ 316 317 172 if (*ptr == '*') { 318 n1 = 0; 173 n1 = 0; /* everything will be filled */ 319 174 n2 = modvalue - 1; 320 175 skip = 1; 321 176 ++ptr; 322 } else if (*ptr >= '0' && *ptr <= '9') { 177 } else if (isdigit(*ptr)) { 178 char *endp; 323 179 if (n1 < 0) { 324 n1 = strtol(ptr, & ptr, 10) + off;180 n1 = strtol(ptr, &endp, 10) + off; 325 181 } else { 326 n2 = strtol(ptr, &ptr, 10) + off; 327 } 182 n2 = strtol(ptr, &endp, 10) + off; 183 } 184 ptr = endp; /* gcc likes temp var for &endp */ 328 185 skip = 1; 329 186 } else if (names) { … … 346 203 347 204 /* handle optional range '-' */ 348 349 205 if (skip == 0) { 350 crondlog("\111failed user %s parsing %s\n", user, base); 351 return NULL; 206 goto err; 352 207 } 353 208 if (*ptr == '-' && n2 < 0) { … … 360 215 * in the character array appropriately. 361 216 */ 362 363 217 if (n2 < 0) { 364 218 n2 = n1; 365 219 } 366 220 if (*ptr == '/') { 367 skip = strtol(ptr + 1, &ptr, 10); 368 } 221 char *endp; 222 skip = strtol(ptr + 1, &endp, 10); 223 ptr = endp; /* gcc likes temp var for &endp */ 224 } 225 369 226 /* 370 227 * fill array, using a failsafe is the easiest way to prevent 371 228 * an endless loop 372 229 */ 373 374 230 { 375 231 int s0 = 1; … … 384 240 s0 = skip; 385 241 } 386 } 387 while (n1 != n2 && --failsafe); 388 389 if (failsafe == 0) { 390 crondlog("\111failed user %s parsing %s\n", user, base); 391 return NULL; 392 } 242 if (--failsafe == 0) { 243 goto err; 244 } 245 } while (n1 != n2); 393 246 } 394 247 if (*ptr != ',') { … … 400 253 } 401 254 402 if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { 403 crondlog("\111failed user %s parsing %s\n", user, base); 404 return NULL; 405 } 406 407 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') { 408 ++ptr; 409 } 410 #if ENABLE_DEBUG_CROND_OPTION 411 if (DebugOpt) { 255 if (*ptr) { 256 err: 257 crondlog(WARN9 "user %s: parse error at %s", user, base); 258 return; 259 } 260 261 if (DebugOpt && (G.log_level <= 5)) { /* like LVL5 */ 262 /* can't use crondlog, it inserts '\n' */ 412 263 int i; 413 414 for (i = 0; i < modvalue; ++i) { 415 crondlog("\005%d", ary[i]); 416 } 417 crondlog("\005\n"); 418 } 419 #endif 420 421 return ptr; 422 } 423 424 static void FixDayDow(CronLine * line) 425 { 426 int i; 264 for (i = 0; i < modvalue; ++i) 265 fprintf(stderr, "%d", (unsigned char)ary[i]); 266 bb_putchar_stderr('\n'); 267 } 268 } 269 270 static void FixDayDow(CronLine *line) 271 { 272 unsigned i; 427 273 int weekUsed = 0; 428 274 int daysUsed = 0; 429 275 430 for (i = 0; i < (int)(ARRAY_SIZE(line->cl_Dow)); ++i) {276 for (i = 0; i < ARRAY_SIZE(line->cl_Dow); ++i) { 431 277 if (line->cl_Dow[i] == 0) { 432 278 weekUsed = 1; … … 434 280 } 435 281 } 436 for (i = 0; i < (int)(ARRAY_SIZE(line->cl_Days)); ++i) {282 for (i = 0; i < ARRAY_SIZE(line->cl_Days); ++i) { 437 283 if (line->cl_Days[i] == 0) { 438 284 daysUsed = 1; … … 440 286 } 441 287 } 442 if (weekUsed && !daysUsed) { 443 memset(line->cl_Days, 0, sizeof(line->cl_Days)); 444 } 445 if (daysUsed && !weekUsed) { 446 memset(line->cl_Dow, 0, sizeof(line->cl_Dow)); 447 } 448 } 449 450 451 452 static void SynchronizeFile(const char *fileName) 453 { 454 int maxEntries = MAXLINES; 288 if (weekUsed != daysUsed) { 289 if (weekUsed) 290 memset(line->cl_Days, 0, sizeof(line->cl_Days)); 291 else /* daysUsed */ 292 memset(line->cl_Dow, 0, sizeof(line->cl_Dow)); 293 } 294 } 295 296 /* 297 * delete_cronfile() - delete user database 298 * 299 * Note: multiple entries for same user may exist if we were unable to 300 * completely delete a database due to running processes. 301 */ 302 //FIXME: we will start a new job even if the old job is running 303 //if crontab was reloaded: crond thinks that "new" job is different from "old" 304 //even if they are in fact completely the same. Example 305 //Crontab was: 306 // 0-59 * * * * job1 307 // 0-59 * * * * long_running_job2 308 //User edits crontab to: 309 // 0-59 * * * * job1_updated 310 // 0-59 * * * * long_running_job2 311 //Bug: crond can now start another long_running_job2 even if old one 312 //is still running. 313 //OTOH most other versions of cron do not wait for job termination anyway, 314 //they end up with multiple copies of jobs if they don't terminate soon enough. 315 static void delete_cronfile(const char *userName) 316 { 317 CronFile **pfile = &G.cron_files; 318 CronFile *file; 319 320 while ((file = *pfile) != NULL) { 321 if (strcmp(userName, file->cf_username) == 0) { 322 CronLine **pline = &file->cf_lines; 323 CronLine *line; 324 325 file->cf_has_running = 0; 326 file->cf_deleted = 1; 327 328 while ((line = *pline) != NULL) { 329 if (line->cl_pid > 0) { 330 file->cf_has_running = 1; 331 pline = &line->cl_next; 332 } else { 333 *pline = line->cl_next; 334 free(line->cl_cmd); 335 free(line); 336 } 337 } 338 if (file->cf_has_running == 0) { 339 *pfile = file->cf_next; 340 free(file->cf_username); 341 free(file); 342 continue; 343 } 344 } 345 pfile = &file->cf_next; 346 } 347 } 348 349 static void load_crontab(const char *fileName) 350 { 351 struct parser_t *parser; 352 struct stat sbuf; 455 353 int maxLines; 456 char buf[1024]; 457 458 if (strcmp(fileName, "root") == 0) { 459 maxEntries = 65535; 460 } 461 maxLines = maxEntries * 10; 462 463 if (fileName) { 464 FILE *fi; 465 466 DeleteFile(fileName); 467 468 fi = fopen(fileName, "r"); 469 if (fi != NULL) { 470 struct stat sbuf; 471 472 if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) { 473 CronFile *file = xzalloc(sizeof(CronFile)); 474 CronLine **pline; 475 476 file->cf_User = strdup(fileName); 477 pline = &file->cf_LineBase; 478 479 while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) { 480 CronLine line; 481 char *ptr; 482 483 trim(buf); 484 if (buf[0] == 0 || buf[0] == '#') { 485 continue; 486 } 487 if (--maxEntries == 0) { 488 break; 489 } 490 memset(&line, 0, sizeof(line)); 491 492 #if ENABLE_DEBUG_CROND_OPTION 493 if (DebugOpt) { 494 crondlog("\111User %s Entry %s\n", fileName, buf); 495 } 496 #endif 497 498 /* parse date ranges */ 499 ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf); 500 ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr); 501 ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr); 502 ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr); 503 ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr); 504 505 /* check failure */ 506 if (ptr == NULL) { 507 continue; 508 } 509 510 /* 511 * fix days and dow - if one is not * and the other 512 * is *, the other is set to 0, and vise-versa 513 */ 514 515 FixDayDow(&line); 516 517 *pline = xzalloc(sizeof(CronLine)); 518 **pline = line; 519 520 /* copy command */ 521 (*pline)->cl_Shell = strdup(ptr); 522 523 #if ENABLE_DEBUG_CROND_OPTION 524 if (DebugOpt) { 525 crondlog("\111 Command %s\n", ptr); 526 } 527 #endif 528 529 pline = &((*pline)->cl_Next); 530 } 531 *pline = NULL; 532 533 file->cf_Next = FileBase; 534 FileBase = file; 535 536 if (maxLines == 0 || maxEntries == 0) { 537 crondlog("\111Maximum number of lines reached for user %s\n", fileName); 538 } 539 } 540 fclose(fi); 541 } 542 } 543 } 544 545 static void CheckUpdates(void) 354 char *tokens[6]; 355 #if ENABLE_FEATURE_CROND_CALL_SENDMAIL 356 char *mailTo = NULL; 357 #endif 358 359 delete_cronfile(fileName); 360 361 if (!getpwnam(fileName)) { 362 crondlog(LVL7 "ignoring file '%s' (no such user)", fileName); 363 return; 364 } 365 366 parser = config_open(fileName); 367 if (!parser) 368 return; 369 370 maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES; 371 372 if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DAEMON_UID) { 373 CronFile *file = xzalloc(sizeof(CronFile)); 374 CronLine **pline; 375 int n; 376 377 file->cf_username = xstrdup(fileName); 378 pline = &file->cf_lines; 379 380 while (1) { 381 CronLine *line; 382 383 if (!--maxLines) 384 break; 385 n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY); 386 if (!n) 387 break; 388 389 if (DebugOpt) 390 crondlog(LVL5 "user:%s entry:%s", fileName, parser->data); 391 392 /* check if line is setting MAILTO= */ 393 if (0 == strncmp(tokens[0], "MAILTO=", 7)) { 394 #if ENABLE_FEATURE_CROND_CALL_SENDMAIL 395 free(mailTo); 396 mailTo = (tokens[0][7]) ? xstrdup(&tokens[0][7]) : NULL; 397 #endif /* otherwise just ignore such lines */ 398 continue; 399 } 400 /* check if a minimum of tokens is specified */ 401 if (n < 6) 402 continue; 403 *pline = line = xzalloc(sizeof(*line)); 404 /* parse date ranges */ 405 ParseField(file->cf_username, line->cl_Mins, 60, 0, NULL, tokens[0]); 406 ParseField(file->cf_username, line->cl_Hrs, 24, 0, NULL, tokens[1]); 407 ParseField(file->cf_username, line->cl_Days, 32, 0, NULL, tokens[2]); 408 ParseField(file->cf_username, line->cl_Mons, 12, -1, MonAry, tokens[3]); 409 ParseField(file->cf_username, line->cl_Dow, 7, 0, DowAry, tokens[4]); 410 /* 411 * fix days and dow - if one is not "*" and the other 412 * is "*", the other is set to 0, and vise-versa 413 */ 414 FixDayDow(line); 415 #if ENABLE_FEATURE_CROND_CALL_SENDMAIL 416 /* copy mailto (can be NULL) */ 417 line->cl_mailto = xstrdup(mailTo); 418 #endif 419 /* copy command */ 420 line->cl_cmd = xstrdup(tokens[5]); 421 if (DebugOpt) { 422 crondlog(LVL5 " command:%s", tokens[5]); 423 } 424 pline = &line->cl_next; 425 //bb_error_msg("M[%s]F[%s][%s][%s][%s][%s][%s]", mailTo, tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]); 426 } 427 *pline = NULL; 428 429 file->cf_next = G.cron_files; 430 G.cron_files = file; 431 432 if (maxLines == 0) { 433 crondlog(WARN9 "user %s: too many lines", fileName); 434 } 435 } 436 config_close(parser); 437 } 438 439 static void process_cron_update_file(void) 546 440 { 547 441 FILE *fi; 548 442 char buf[256]; 549 443 550 fi = fopen (CRONUPDATE, "r");444 fi = fopen_for_read(CRONUPDATE); 551 445 if (fi != NULL) { 552 remove(CRONUPDATE);446 unlink(CRONUPDATE); 553 447 while (fgets(buf, sizeof(buf), fi) != NULL) { 554 SynchronizeFile(strtok(buf, " \t\r\n")); 448 /* use first word only */ 449 skip_non_whitespace(buf)[0] = '\0'; 450 load_crontab(buf); 555 451 } 556 452 fclose(fi); … … 558 454 } 559 455 560 static void SynchronizeDir(void) 561 { 562 /* Attempt to delete the database. */ 563 564 for (;;) { 565 CronFile *file; 566 567 for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next); 568 if (file == NULL) { 569 break; 570 } 571 DeleteFile(file->cf_User); 572 } 573 574 /* 575 * Remove cron update file 576 * 577 * Re-chdir, in case directory was renamed & deleted, or otherwise 578 * screwed up. 579 * 580 * scan directory and add associated users 581 */ 582 583 remove(CRONUPDATE); 584 if (chdir(CDir) < 0) { 585 crondlog("\311cannot find %s\n", CDir); 586 } 456 static void rescan_crontab_dir(void) 457 { 458 CronFile *file; 459 460 /* Delete all files until we only have ones with running jobs (or none) */ 461 again: 462 for (file = G.cron_files; file; file = file->cf_next) { 463 if (!file->cf_deleted) { 464 delete_cronfile(file->cf_username); 465 goto again; 466 } 467 } 468 469 /* Remove cron update file */ 470 unlink(CRONUPDATE); 471 /* Re-chdir, in case directory was renamed & deleted */ 472 if (chdir(G.crontab_dir_name) < 0) { 473 crondlog(DIE9 "chdir(%s)", G.crontab_dir_name); 474 } 475 476 /* Scan directory and add associated users */ 587 477 { 588 478 DIR *dir = opendir("."); 589 479 struct dirent *den; 590 480 591 if (dir) { 592 while ((den = readdir(dir))) { 593 if (strchr(den->d_name, '.') != NULL) { 594 continue; 595 } 596 if (getpwnam(den->d_name)) { 597 SynchronizeFile(den->d_name); 598 } else { 599 crondlog("\007ignoring %s\n", den->d_name); 600 } 601 } 602 closedir(dir); 603 } else { 604 crondlog("\311cannot open current dir!\n"); 605 } 606 } 607 } 608 609 610 /* 611 * DeleteFile() - delete user database 612 * 613 * Note: multiple entries for same user may exist if we were unable to 614 * completely delete a database due to running processes. 615 */ 616 617 static void DeleteFile(const char *userName) 618 { 619 CronFile **pfile = &FileBase; 620 CronFile *file; 621 622 while ((file = *pfile) != NULL) { 623 if (strcmp(userName, file->cf_User) == 0) { 624 CronLine **pline = &file->cf_LineBase; 625 CronLine *line; 626 627 file->cf_Running = 0; 628 file->cf_Deleted = 1; 629 630 while ((line = *pline) != NULL) { 631 if (line->cl_Pid > 0) { 632 file->cf_Running = 1; 633 pline = &line->cl_Next; 634 } else { 635 *pline = line->cl_Next; 636 free(line->cl_Shell); 637 free(line); 638 } 639 } 640 if (file->cf_Running == 0) { 641 *pfile = file->cf_Next; 642 free(file->cf_User); 643 free(file); 644 } else { 645 pfile = &file->cf_Next; 646 } 647 } else { 648 pfile = &file->cf_Next; 649 } 650 } 651 } 652 653 /* 654 * TestJobs() 655 * 656 * determine which jobs need to be run. Under normal conditions, the 657 * period is about a minute (one scan). Worst case it will be one 658 * hour (60 scans). 659 */ 660 661 static int TestJobs(time_t t1, time_t t2) 662 { 663 int nJobs = 0; 664 time_t t; 665 666 /* Find jobs > t1 and <= t2 */ 667 668 for (t = t1 - t1 % 60; t <= t2; t += 60) { 669 if (t > t1) { 670 struct tm *tp = localtime(&t); 671 CronFile *file; 672 CronLine *line; 673 674 for (file = FileBase; file; file = file->cf_Next) { 675 #if ENABLE_DEBUG_CROND_OPTION 676 if (DebugOpt) 677 crondlog("\005FILE %s:\n", file->cf_User); 678 #endif 679 if (file->cf_Deleted) 680 continue; 681 for (line = file->cf_LineBase; line; line = line->cl_Next) { 682 #if ENABLE_DEBUG_CROND_OPTION 683 if (DebugOpt) 684 crondlog("\005 LINE %s\n", line->cl_Shell); 685 #endif 686 if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour] && 687 (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday]) 688 && line->cl_Mons[tp->tm_mon]) { 689 #if ENABLE_DEBUG_CROND_OPTION 690 if (DebugOpt) { 691 crondlog("\005 JobToDo: %d %s\n", 692 line->cl_Pid, line->cl_Shell); 693 } 694 #endif 695 if (line->cl_Pid > 0) { 696 crondlog("\010 process already running: %s %s\n", 697 file->cf_User, line->cl_Shell); 698 } else if (line->cl_Pid == 0) { 699 line->cl_Pid = -1; 700 file->cf_Ready = 1; 701 ++nJobs; 702 } 703 } 704 } 705 } 706 } 707 } 708 return nJobs; 709 } 710 711 static void RunJobs(void) 712 { 713 CronFile *file; 714 CronLine *line; 715 716 for (file = FileBase; file; file = file->cf_Next) { 717 if (file->cf_Ready) { 718 file->cf_Ready = 0; 719 720 for (line = file->cf_LineBase; line; line = line->cl_Next) { 721 if (line->cl_Pid < 0) { 722 723 RunJob(file->cf_User, line); 724 725 crondlog("\010USER %s pid %3d cmd %s\n", 726 file->cf_User, line->cl_Pid, line->cl_Shell); 727 if (line->cl_Pid < 0) { 728 file->cf_Ready = 1; 729 } 730 else if (line->cl_Pid > 0) { 731 file->cf_Running = 1; 732 } 733 } 734 } 735 } 736 } 737 } 738 739 /* 740 * CheckJobs() - check for job completion 741 * 742 * Check for job completion, return number of jobs still running after 743 * all done. 744 */ 745 746 static int CheckJobs(void) 747 { 748 CronFile *file; 749 CronLine *line; 750 int nStillRunning = 0; 751 752 for (file = FileBase; file; file = file->cf_Next) { 753 if (file->cf_Running) { 754 file->cf_Running = 0; 755 756 for (line = file->cf_LineBase; line; line = line->cl_Next) { 757 if (line->cl_Pid > 0) { 758 int status; 759 int r = wait4(line->cl_Pid, &status, WNOHANG, NULL); 760 761 if (r < 0 || r == line->cl_Pid) { 762 EndJob(file->cf_User, line); 763 if (line->cl_Pid) { 764 file->cf_Running = 1; 765 } 766 } else if (r == 0) { 767 file->cf_Running = 1; 768 } 769 } 770 } 771 } 772 nStillRunning += file->cf_Running; 773 } 774 return nStillRunning; 775 } 776 777 481 if (!dir) 482 crondlog(DIE9 "chdir(%s)", "."); /* exits */ 483 while ((den = readdir(dir)) != NULL) { 484 if (strchr(den->d_name, '.') != NULL) { 485 continue; 486 } 487 load_crontab(den->d_name); 488 } 489 closedir(dir); 490 } 491 } 492 493 #if SETENV_LEAKS 494 /* We set environment *before* vfork (because we want to use vfork), 495 * so we cannot use setenv() - repeated calls to setenv() may leak memory! 496 * Using putenv(), and freeing memory after unsetenv() won't leak */ 497 static void safe_setenv(char **pvar_val, const char *var, const char *val) 498 { 499 char *var_val = *pvar_val; 500 501 if (var_val) { 502 bb_unsetenv_and_free(var_val); 503 } 504 *pvar_val = xasprintf("%s=%s", var, val); 505 putenv(*pvar_val); 506 } 507 #endif 508 509 static void set_env_vars(struct passwd *pas) 510 { 511 #if SETENV_LEAKS 512 safe_setenv(&G.env_var_user, "USER", pas->pw_name); 513 safe_setenv(&G.env_var_home, "HOME", pas->pw_dir); 514 /* if we want to set user's shell instead: */ 515 /*safe_setenv(G.env_var_shell, "SHELL", pas->pw_shell);*/ 516 #else 517 xsetenv("USER", pas->pw_name); 518 xsetenv("HOME", pas->pw_dir); 519 #endif 520 /* currently, we use constant one: */ 521 /*setenv("SHELL", DEFAULT_SHELL, 1); - done earlier */ 522 } 523 524 static void change_user(struct passwd *pas) 525 { 526 /* careful: we're after vfork! */ 527 change_identity(pas); /* - initgroups, setgid, setuid */ 528 if (chdir(pas->pw_dir) < 0) { 529 crondlog(WARN9 "chdir(%s)", pas->pw_dir); 530 if (chdir(TMPDIR) < 0) { 531 crondlog(DIE9 "chdir(%s)", TMPDIR); /* exits */ 532 } 533 } 534 } 535 536 // TODO: sendmail should be _run-time_ option, not compile-time! 778 537 #if ENABLE_FEATURE_CROND_CALL_SENDMAIL 779 static void 780 ForkJob(const char *user, CronLine * line, int mailFd, 781 const char *prog, const char *cmd, const char *arg, const char *mailf) 782 { 783 /* Fork as the user in question and run program */ 784 pid_t pid = fork(); 785 786 line->cl_Pid = pid; 538 539 static pid_t 540 fork_job(const char *user, int mailFd, 541 const char *prog, 542 const char *shell_cmd /* if NULL, we run sendmail */ 543 ) { 544 struct passwd *pas; 545 pid_t pid; 546 547 /* prepare things before vfork */ 548 pas = getpwnam(user); 549 if (!pas) { 550 crondlog(WARN9 "can't get uid for %s", user); 551 goto err; 552 } 553 set_env_vars(pas); 554 555 pid = vfork(); 787 556 if (pid == 0) { 788 557 /* CHILD */ 789 790 /* Change running state to the user in question */ 791 792 if (ChangeUser(user) < 0) { 793 exit(0); 794 } 795 #if ENABLE_DEBUG_CROND_OPTION 558 /* initgroups, setgid, setuid, and chdir to home or TMPDIR */ 559 change_user(pas); 796 560 if (DebugOpt) { 797 crondlog("\005Child Running %s\n", prog); 798 } 799 #endif 800 561 crondlog(LVL5 "child running %s", prog); 562 } 801 563 if (mailFd >= 0) { 802 dup2(mailFd, mailf != NULL); 803 dup2((mailf ? mailFd : 1), 2); 804 close(mailFd); 805 } 806 execl(prog, prog, cmd, arg, NULL); 807 crondlog("\024cannot exec, user %s cmd %s %s %s\n", user, prog, cmd, arg); 808 if (mailf) { 809 fdprintf(1, "Exec failed: %s -c %s\n", prog, arg); 810 } 811 exit(0); 812 } else if (pid < 0) { 564 xmove_fd(mailFd, shell_cmd ? 1 : 0); 565 dup2(1, 2); 566 } 567 /* crond 3.0pl1-100 puts tasks in separate process groups */ 568 bb_setpgrp(); 569 execlp(prog, prog, (shell_cmd ? "-c" : SENDMAIL_ARGS), shell_cmd, (char *) NULL); 570 crondlog(ERR20 "can't execute '%s' for user %s", prog, user); 571 if (shell_cmd) { 572 fdprintf(1, "Exec failed: %s -c %s\n", prog, shell_cmd); 573 } 574 _exit(EXIT_SUCCESS); 575 } 576 577 if (pid < 0) { 813 578 /* FORK FAILED */ 814 crondlog("\024cannot fork, user %s\n", user); 815 line->cl_Pid = 0; 816 if (mailf) { 817 remove(mailf); 818 } 819 } else if (mailf) { 820 /* PARENT, FORK SUCCESS 821 * rename mail-file based on pid of process 822 */ 823 char mailFile2[128]; 824 825 snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d", user, pid); 826 rename(mailf, mailFile2); 827 } 579 crondlog(ERR20 "can't vfork"); 580 err: 581 pid = 0; 582 } /* else: PARENT, FORK SUCCESS */ 583 828 584 /* 829 585 * Close the mail file descriptor.. we can't just leave it open in 830 586 * a structure, closing it later, because we might run out of descriptors 831 587 */ 832 833 588 if (mailFd >= 0) { 834 589 close(mailFd); 835 590 } 836 } 837 838 static void RunJob(const char *user, CronLine * line) 591 return pid; 592 } 593 594 static void start_one_job(const char *user, CronLine *line) 839 595 { 840 596 char mailFile[128]; 841 int mailFd; 842 843 line->cl_Pid = 0; 844 line->cl_MailFlag = 0; 845 846 /* open mail file - owner root so nobody can screw with it. */ 847 848 snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, getpid()); 849 mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600); 850 597 int mailFd = -1; 598 599 line->cl_pid = 0; 600 line->cl_empty_mail_size = 0; 601 602 if (line->cl_mailto) { 603 /* Open mail file (owner is root so nobody can screw with it) */ 604 snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, getpid()); 605 mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600); 606 607 if (mailFd >= 0) { 608 fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", line->cl_mailto, 609 line->cl_cmd); 610 line->cl_empty_mail_size = lseek(mailFd, 0, SEEK_CUR); 611 } else { 612 crondlog(ERR20 "can't create mail file %s for user %s, " 613 "discarding output", mailFile, user); 614 } 615 } 616 617 line->cl_pid = fork_job(user, mailFd, DEFAULT_SHELL, line->cl_cmd); 851 618 if (mailFd >= 0) { 852 line->cl_MailFlag = 1;853 fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user,854 line->cl_Shell);855 line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR);856 } else {857 crondlog("\024cannot create mail file user %s file %s, output to /dev/null\n", user, mailFile);858 }859 860 ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile);619 if (line->cl_pid <= 0) { 620 unlink(mailFile); 621 } else { 622 /* rename mail-file based on pid of process */ 623 char *mailFile2 = xasprintf("%s/cron.%s.%d", TMPDIR, user, (int)line->cl_pid); 624 rename(mailFile, mailFile2); // TODO: xrename? 625 free(mailFile2); 626 } 627 } 861 628 } 862 629 863 630 /* 864 * EndJob - called when job terminates and when mail terminates631 * process_finished_job - called when job terminates and when mail terminates 865 632 */ 866 867 static void EndJob(const char *user, CronLine * line) 868 { 633 static void process_finished_job(const char *user, CronLine *line) 634 { 635 pid_t pid; 869 636 int mailFd; 870 637 char mailFile[128]; 871 638 struct stat sbuf; 872 639 873 /* No job */874 875 if ( line->cl_Pid <= 0) {876 line->cl_Pid = 0;640 pid = line->cl_pid; 641 line->cl_pid = 0; 642 if (pid <= 0) { 643 /* No job */ 877 644 return; 878 645 } 646 if (line->cl_empty_mail_size <= 0) { 647 /* End of job and no mail file, or end of sendmail job */ 648 return; 649 } 879 650 880 651 /* 881 * End of job and no mail file882 * End of sendmail job652 * End of primary job - check for mail file. 653 * If size has changed and the file is still valid, we send it. 883 654 */ 884 885 snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, line->cl_Pid); 886 line->cl_Pid = 0; 887 888 if (line->cl_MailFlag != 1) { 889 return; 890 } 891 line->cl_MailFlag = 0; 892 893 /* 894 * End of primary job - check for mail file. If size has increased and 895 * the file is still valid, we sendmail it. 896 */ 897 655 snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, (int)pid); 898 656 mailFd = open(mailFile, O_RDONLY); 899 remove(mailFile);657 unlink(mailFile); 900 658 if (mailFd < 0) { 901 659 return; 902 660 } 903 661 904 if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid 905 || sbuf.st_nlink != 0 || sbuf.st_size == line->cl_MailPos 662 if (fstat(mailFd, &sbuf) < 0 663 || sbuf.st_uid != DAEMON_UID 664 || sbuf.st_nlink != 0 665 || sbuf.st_size == line->cl_empty_mail_size 906 666 || !S_ISREG(sbuf.st_mode) 907 667 ) { … … 909 669 return; 910 670 } 911 ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL); 912 } 913 #else 914 /* crond without sendmail */ 915 916 static void RunJob(const char *user, CronLine * line) 917 { 671 line->cl_empty_mail_size = 0; 672 /* if (line->cl_mailto) - always true if cl_empty_mail_size was nonzero */ 673 line->cl_pid = fork_job(user, mailFd, SENDMAIL, NULL); 674 } 675 676 #else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */ 677 678 static void start_one_job(const char *user, CronLine *line) 679 { 680 struct passwd *pas; 681 pid_t pid; 682 683 pas = getpwnam(user); 684 if (!pas) { 685 crondlog(WARN9 "can't get uid for %s", user); 686 goto err; 687 } 688 689 /* Prepare things before vfork */ 690 set_env_vars(pas); 691 918 692 /* Fork as the user in question and run program */ 919 pid_t pid = fork(); 920 693 pid = vfork(); 921 694 if (pid == 0) { 922 695 /* CHILD */ 923 924 /* Change running state to the user in question */ 925 926 if (ChangeUser(user) < 0) { 927 exit(0); 928 } 929 #if ENABLE_DEBUG_CROND_OPTION 696 /* initgroups, setgid, setuid, and chdir to home or TMPDIR */ 697 change_user(pas); 930 698 if (DebugOpt) { 931 crondlog( "\005Child Running %s\n", DEFAULT_SHELL);932 } 933 #endif 934 935 execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_ Shell,NULL);936 crondlog( "\024cannot exec, user %s cmd %s -c %s\n", user,937 DEFAULT_SHELL, line->cl_Shell);938 exit(0);939 } elseif (pid < 0) {699 crondlog(LVL5 "child running %s", DEFAULT_SHELL); 700 } 701 /* crond 3.0pl1-100 puts tasks in separate process groups */ 702 bb_setpgrp(); 703 execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_cmd, (char *) NULL); 704 crondlog(ERR20 "can't execute '%s' for user %s", DEFAULT_SHELL, user); 705 _exit(EXIT_SUCCESS); 706 } 707 if (pid < 0) { 940 708 /* FORK FAILED */ 941 crondlog("\024cannot, user %s\n", user); 709 crondlog(ERR20 "can't vfork"); 710 err: 942 711 pid = 0; 943 712 } 944 line->cl_Pid = pid; 945 } 946 #endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */ 713 line->cl_pid = pid; 714 } 715 716 #define process_finished_job(user, line) ((line)->cl_pid = 0) 717 718 #endif /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */ 719 720 /* 721 * Determine which jobs need to be run. Under normal conditions, the 722 * period is about a minute (one scan). Worst case it will be one 723 * hour (60 scans). 724 */ 725 static void flag_starting_jobs(time_t t1, time_t t2) 726 { 727 time_t t; 728 729 /* Find jobs > t1 and <= t2 */ 730 731 for (t = t1 - t1 % 60; t <= t2; t += 60) { 732 struct tm *ptm; 733 CronFile *file; 734 CronLine *line; 735 736 if (t <= t1) 737 continue; 738 739 ptm = localtime(&t); 740 for (file = G.cron_files; file; file = file->cf_next) { 741 if (DebugOpt) 742 crondlog(LVL5 "file %s:", file->cf_username); 743 if (file->cf_deleted) 744 continue; 745 for (line = file->cf_lines; line; line = line->cl_next) { 746 if (DebugOpt) 747 crondlog(LVL5 " line %s", line->cl_cmd); 748 if (line->cl_Mins[ptm->tm_min] 749 && line->cl_Hrs[ptm->tm_hour] 750 && (line->cl_Days[ptm->tm_mday] || line->cl_Dow[ptm->tm_wday]) 751 && line->cl_Mons[ptm->tm_mon] 752 ) { 753 if (DebugOpt) { 754 crondlog(LVL5 " job: %d %s", 755 (int)line->cl_pid, line->cl_cmd); 756 } 757 if (line->cl_pid > 0) { 758 crondlog(LVL8 "user %s: process already running: %s", 759 file->cf_username, line->cl_cmd); 760 } else if (line->cl_pid == 0) { 761 line->cl_pid = -1; 762 file->cf_wants_starting = 1; 763 } 764 } 765 } 766 } 767 } 768 } 769 770 static void start_jobs(void) 771 { 772 CronFile *file; 773 CronLine *line; 774 775 for (file = G.cron_files; file; file = file->cf_next) { 776 if (!file->cf_wants_starting) 777 continue; 778 779 file->cf_wants_starting = 0; 780 for (line = file->cf_lines; line; line = line->cl_next) { 781 pid_t pid; 782 if (line->cl_pid >= 0) 783 continue; 784 785 start_one_job(file->cf_username, line); 786 pid = line->cl_pid; 787 crondlog(LVL8 "USER %s pid %3d cmd %s", 788 file->cf_username, (int)pid, line->cl_cmd); 789 if (pid < 0) { 790 file->cf_wants_starting = 1; 791 } 792 if (pid > 0) { 793 file->cf_has_running = 1; 794 } 795 } 796 } 797 } 798 799 /* 800 * Check for job completion, return number of jobs still running after 801 * all done. 802 */ 803 static int check_completions(void) 804 { 805 CronFile *file; 806 CronLine *line; 807 int num_still_running = 0; 808 809 for (file = G.cron_files; file; file = file->cf_next) { 810 if (!file->cf_has_running) 811 continue; 812 813 file->cf_has_running = 0; 814 for (line = file->cf_lines; line; line = line->cl_next) { 815 int r; 816 817 if (line->cl_pid <= 0) 818 continue; 819 820 r = waitpid(line->cl_pid, NULL, WNOHANG); 821 if (r < 0 || r == line->cl_pid) { 822 process_finished_job(file->cf_username, line); 823 if (line->cl_pid == 0) { 824 /* sendmail was not started for it */ 825 continue; 826 } 827 /* else: sendmail was started, job is still running, fall thru */ 828 } 829 /* else: r == 0: "process is still running" */ 830 file->cf_has_running = 1; 831 } 832 //FIXME: if !file->cf_has_running && file->deleted: delete it! 833 //otherwise deleted entries will stay forever, right? 834 num_still_running += file->cf_has_running; 835 } 836 return num_still_running; 837 } 838 839 int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 840 int crond_main(int argc UNUSED_PARAM, char **argv) 841 { 842 time_t t2; 843 int rescan; 844 int sleep_time; 845 unsigned opts; 846 847 INIT_G(); 848 849 /* "-b after -f is ignored", and so on for every pair a-b */ 850 opt_complementary = "f-b:b-f:S-L:L-S" IF_FEATURE_CROND_D(":d-l") 851 ":l+:d+"; /* -l and -d have numeric param */ 852 opts = getopt32(argv, "l:L:fbSc:" IF_FEATURE_CROND_D("d:"), 853 &G.log_level, &G.log_filename, &G.crontab_dir_name 854 IF_FEATURE_CROND_D(,&G.log_level)); 855 /* both -d N and -l N set the same variable: G.log_level */ 856 857 if (!(opts & OPT_f)) { 858 /* close stdin, stdout, stderr. 859 * close unused descriptors - don't need them. */ 860 bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv); 861 } 862 863 if (!(opts & OPT_d) && G.log_filename == NULL) { 864 /* logging to syslog */ 865 openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON); 866 logmode = LOGMODE_SYSLOG; 867 } 868 869 xchdir(G.crontab_dir_name); 870 //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */ 871 xsetenv("SHELL", DEFAULT_SHELL); /* once, for all future children */ 872 crondlog(LVL8 "crond (busybox "BB_VER") started, log level %d", G.log_level); 873 rescan_crontab_dir(); 874 write_pidfile("/var/run/crond.pid"); 875 876 /* Main loop */ 877 t2 = time(NULL); 878 rescan = 60; 879 sleep_time = 60; 880 for (;;) { 881 struct stat sbuf; 882 time_t t1; 883 long dt; 884 885 t1 = t2; 886 887 /* Synchronize to 1 minute, minimum 1 second */ 888 sleep(sleep_time - (time(NULL) % sleep_time) + 1); 889 890 t2 = time(NULL); 891 dt = (long)t2 - (long)t1; 892 893 /* 894 * The file 'cron.update' is checked to determine new cron 895 * jobs. The directory is rescanned once an hour to deal 896 * with any screwups. 897 * 898 * Check for time jump. Disparities over an hour either way 899 * result in resynchronization. A negative disparity 900 * less than an hour causes us to effectively sleep until we 901 * match the original time (i.e. no re-execution of jobs that 902 * have just been run). A positive disparity less than 903 * an hour causes intermediate jobs to be run, but only once 904 * in the worst case. 905 * 906 * When running jobs, the inequality used is greater but not 907 * equal to t1, and less then or equal to t2. 908 */ 909 if (stat(G.crontab_dir_name, &sbuf) != 0) 910 sbuf.st_mtime = 0; /* force update (once) if dir was deleted */ 911 if (G.crontab_dir_mtime != sbuf.st_mtime) { 912 G.crontab_dir_mtime = sbuf.st_mtime; 913 rescan = 1; 914 } 915 if (--rescan == 0) { 916 rescan = 60; 917 rescan_crontab_dir(); 918 } 919 process_cron_update_file(); 920 if (DebugOpt) 921 crondlog(LVL5 "wakeup dt=%ld", dt); 922 if (dt < -60 * 60 || dt > 60 * 60) { 923 crondlog(WARN9 "time disparity of %ld minutes detected", dt / 60); 924 /* and we do not run any jobs in this case */ 925 } else if (dt > 0) { 926 /* Usual case: time advances forward, as expected */ 927 flag_starting_jobs(t1, t2); 928 start_jobs(); 929 if (check_completions() > 0) { 930 /* some jobs are still running */ 931 sleep_time = 10; 932 } else { 933 sleep_time = 60; 934 } 935 } 936 /* else: time jumped back, do not run any jobs */ 937 } /* for (;;) */ 938 939 return 0; /* not reached */ 940 }
Note:
See TracChangeset
for help on using the changeset viewer.