+#define process_finished_job(user, line) ((line)->cl_pid = 0)
+
+#endif /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */
+
+/*
+ * Determine which jobs need to be run. Under normal conditions, the
+ * period is about a minute (one scan). Worst case it will be one
+ * hour (60 scans).
+ */
+static void flag_starting_jobs(time_t t1, time_t t2)
+{
+ time_t t;
+
+ /* Find jobs > t1 and <= t2 */
+
+ for (t = t1 - t1 % 60; t <= t2; t += 60) {
+ struct tm *ptm;
+ CronFile *file;
+ CronLine *line;
+
+ if (t <= t1)
+ continue;
+
+ ptm = localtime(&t);
+ for (file = G.cron_files; file; file = file->cf_next) {
+ log5("file %s:", file->cf_username);
+ if (file->cf_deleted)
+ continue;
+ for (line = file->cf_lines; line; line = line->cl_next) {
+ log5(" line %s", line->cl_cmd);
+ if (line->cl_Mins[ptm->tm_min]
+ && line->cl_Hrs[ptm->tm_hour]
+ && (line->cl_Days[ptm->tm_mday] || line->cl_Dow[ptm->tm_wday])
+ && line->cl_Mons[ptm->tm_mon]
+ ) {
+ log5(" job: %d %s",
+ (int)line->cl_pid, line->cl_cmd);
+ if (line->cl_pid > 0) {
+ log8("user %s: process already running: %s",
+ file->cf_username, line->cl_cmd);
+ } else if (line->cl_pid == 0) {
+ line->cl_pid = START_ME_NORMAL;
+ file->cf_wants_starting = 1;
+ }
+ }
+ }
+ }
+ }
+}
+
+#if ENABLE_FEATURE_CROND_SPECIAL_TIMES
+static int touch_reboot_file(void)
+{
+ int fd = open(CRON_REBOOT, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, 0000);
+ if (fd >= 0) {
+ close(fd);
+ return 1;
+ }
+ /* File (presumably) exists - this is not the first run after reboot */
+ return 0;
+}
+#endif
+
+static void start_jobs(int wants_start)
+{
+ CronFile *file;
+ CronLine *line;
+
+ for (file = G.cron_files; file; file = file->cf_next) {
+ if (!file->cf_wants_starting)
+ continue;
+
+ file->cf_wants_starting = 0;
+ for (line = file->cf_lines; line; line = line->cl_next) {
+ pid_t pid;
+ if (line->cl_pid != wants_start)
+ continue;
+
+ pid = start_one_job(file->cf_username, line);
+ log8("USER %s pid %3d cmd %s",
+ file->cf_username, (int)pid, line->cl_cmd);
+ if (pid < 0) {
+ file->cf_wants_starting = 1;
+ }
+ if (pid > 0) {
+ file->cf_has_running = 1;
+ }
+ }
+ }
+}
+
+/*
+ * Check for job completion, return number of jobs still running after
+ * all done.
+ */
+static int check_completions(void)
+{
+ CronFile *file;
+ CronLine *line;
+ int num_still_running = 0;
+
+ for (file = G.cron_files; file; file = file->cf_next) {
+ if (!file->cf_has_running)
+ continue;
+
+ file->cf_has_running = 0;
+ for (line = file->cf_lines; line; line = line->cl_next) {
+ int r;
+
+ if (line->cl_pid <= 0)
+ continue;
+
+ r = waitpid(line->cl_pid, NULL, WNOHANG);
+ if (r < 0 || r == line->cl_pid) {
+ process_finished_job(file->cf_username, line);
+ if (line->cl_pid == 0) {
+ /* sendmail was not started for it */
+ continue;
+ }
+ /* else: sendmail was started, job is still running, fall thru */
+ }
+ /* else: r == 0: "process is still running" */
+ file->cf_has_running = 1;
+ }
+//FIXME: if !file->cf_has_running && file->deleted: delete it!
+//otherwise deleted entries will stay forever, right?
+ num_still_running += file->cf_has_running;
+ }
+ return num_still_running;
+}
+
+static void reopen_logfile_to_stderr(void)
+{
+ if (G.log_filename) {
+ int logfd = open_or_warn(G.log_filename, O_WRONLY | O_CREAT | O_APPEND);
+ if (logfd >= 0)
+ xmove_fd(logfd, STDERR_FILENO);
+ }
+}
+
+int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int crond_main(int argc UNUSED_PARAM, char **argv)
+{
+ time_t t2;
+ unsigned rescan;
+ unsigned sleep_time;
+ unsigned opts;
+
+ INIT_G();
+
+ opts = getopt32(argv, "^"
+ "l:L:fbSc:" IF_FEATURE_CROND_D("d:")
+ "\0"
+ /* "-b after -f is ignored", and so on for every pair a-b */
+ "f-b:b-f:S-L:L-S" IF_FEATURE_CROND_D(":d-l")
+ /* -l and -d have numeric param */
+ ":l+" IF_FEATURE_CROND_D(":d+")
+ ,
+ &G.log_level, &G.log_filename, &G.crontab_dir_name
+ IF_FEATURE_CROND_D(,&G.log_level)
+ );
+ /* both -d N and -l N set the same variable: G.log_level */
+
+ if (!(opts & OPT_f)) {
+ /* close stdin, stdout, stderr.
+ * close unused descriptors - don't need them. */
+ bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
+ }
+
+ if (!(opts & OPT_d) && G.log_filename == NULL) {
+ /* logging to syslog */
+ openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON);
+ logmode = LOGMODE_SYSLOG;
+ }
+
+ //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
+
+ reopen_logfile_to_stderr();
+ xchdir(G.crontab_dir_name);
+ /* $SHELL, or current UID's shell, or DEFAULT_SHELL */
+ /* Useful on Android where DEFAULT_SHELL /bin/sh may not exist */
+ G.default_shell = xstrdup(get_shell_name());
+
+ log8("crond (busybox "BB_VER") started, log level %d", G.log_level);
+ rescan_crontab_dir();
+ write_pidfile(CONFIG_PID_FILE_PATH "/crond.pid");
+#if ENABLE_FEATURE_CROND_SPECIAL_TIMES
+ if (touch_reboot_file())
+ start_jobs(START_ME_REBOOT); /* start @reboot entries, if any */
+#endif
+
+ /* Main loop */
+ t2 = time(NULL);
+ rescan = 60;
+ sleep_time = 60;
+ for (;;) {
+ struct stat sbuf;
+ time_t t1;
+ long dt;
+
+ /* Synchronize to 1 minute, minimum 1 second */
+ t1 = t2;
+ sleep(sleep_time - (time(NULL) % sleep_time));
+ t2 = time(NULL);
+ dt = (long)t2 - (long)t1;
+
+ reopen_logfile_to_stderr();
+
+ /*
+ * The file 'cron.update' is checked to determine new cron
+ * jobs. The directory is rescanned once an hour to deal
+ * with any screwups.
+ *
+ * Check for time jump. Disparities over an hour either way
+ * result in resynchronization. A negative disparity
+ * less than an hour causes us to effectively sleep until we
+ * match the original time (i.e. no re-execution of jobs that
+ * have just been run). A positive disparity less than
+ * an hour causes intermediate jobs to be run, but only once
+ * in the worst case.
+ *
+ * When running jobs, the inequality used is greater but not
+ * equal to t1, and less then or equal to t2.
+ */
+ if (stat(G.crontab_dir_name, &sbuf) != 0)
+ sbuf.st_mtime = 0; /* force update (once) if dir was deleted */
+ if (G.crontab_dir_mtime != sbuf.st_mtime) {
+ G.crontab_dir_mtime = sbuf.st_mtime;
+ rescan = 1;
+ }
+ if (--rescan == 0) {
+ rescan = 60;
+ rescan_crontab_dir();
+ }
+ process_cron_update_file();
+ log5("wakeup dt=%ld", dt);
+ if (dt < -60 * 60 || dt > 60 * 60) {
+ bb_error_msg("time disparity of %ld minutes detected", dt / 60);
+ /* and we do not run any jobs in this case */
+ } else if (dt > 0) {
+ /* Usual case: time advances forward, as expected */
+ flag_starting_jobs(t1, t2);
+ start_jobs(START_ME_NORMAL);
+ sleep_time = 60;
+ if (check_completions() > 0) {
+ /* some jobs are still running */
+ sleep_time = 10;
+ }
+ }
+ /* else: time jumped back, do not run any jobs */
+ } /* for (;;) */
+
+ return 0; /* not reached */
+}