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