#include <cron.h>
+#if defined WITH_INOTIFY
+int inotify_enabled;
+#else
+#define inotify_enabled 0
+#endif
+
enum timejump { negative, small, medium, large };
static void usage(void),
static int timeRunning, virtualTime, clockTime;
static long GMToff;
+#if defined WITH_INOTIFY
+int wd1, wd2, wd3, wd4;
+
+void
+set_cron_watched(int fd) {
+ int ret1, ret2, ret3;
+
+ wd1 = inotify_add_watch(fd, CRONDIR, IN_MODIFY | IN_DELETE | IN_CREATE);
+ if (wd1 < 0)
+ log_it("CRON",getpid(),"This directory can't be watched",strerror(errno));
+
+ wd2 = inotify_add_watch(fd, RH_CROND_DIR, IN_MODIFY | IN_DELETE | IN_CREATE);
+ if (wd2 < 0)
+ log_it("CRON",getpid(),"This directory can't be watched",strerror(errno));
+
+ wd3 = inotify_add_watch(fd, SYSCRONTAB, IN_MODIFY | IN_DELETE | IN_CREATE);
+ if (wd3 < 0)
+ log_it("CRON",getpid(),"This file can't be watched ",strerror(errno));
+
+ wd4 = inotify_add_watch(fd, "/var/spool/cron/", IN_MODIFY | IN_DELETE | IN_CREATE);
+ if (wd4 < 0)
+ log_it("CRON",getpid(),"This file can't be watched ",strerror(errno));
+
+ if (wd1 <0 || wd2<0 || wd3<0 || wd4<0) {
+ inotify_enabled = 0;
+ syslog(LOG_INFO, "CRON (%s) ERROR: run without inotify support");
+ }
+ else
+ inotify_enabled = 1;
+}
+
+void
+set_cron_unwatched(int fd) {
+ int ret1, ret2, ret3, ret4;
+
+ ret1 = inotify_rm_watch(fd, wd1);
+ ret2 = inotify_rm_watch(fd, wd2);
+ ret3 = inotify_rm_watch(fd, wd3);
+ ret4 = inotify_rm_watch(fd, wd4);
+}
+#endif
+
static void
usage(void) {
const char **dflags;
int fd;
char *cs;
+#if defined WITH_INOTIFY
+ int fildes;
+ fildes = inotify_init();
+ if (fildes < 0)
+ perror ("inotify_init");
+#endif
+
ProgramName = argv[0];
setlocale(LC_ALL, "");
acquire_daemonlock(0);
set_cron_uid();
+#if defined WITH_INOTIFY
+ set_cron_watched(fildes);
+#endif
set_cron_cwd();
if (putenv("PATH="_PATH_DEFPATH) < 0) {
if (fd != STDERR)
(void) close(fd);
}
- log_it("CRON",getpid(),"STARTUP",PACKAGE_VERSION);
+ if (inotify_enabled) {
+#if defined WITH_INOTIFY
+ log_it("CRON",getpid(),"STARTUP INOTIFY",PACKAGE_VERSION);
+#endif
+ }
+ else {
+ log_it("CRON",getpid(),"STARTUP",PACKAGE_VERSION);
+ }
break;
default:
/* parent process should just die */
acquire_daemonlock(0);
database.head = NULL;
database.tail = NULL;
- database.mtime = (time_t) 0;
- load_database(&database);
+ if (inotify_enabled) {
+#if defined WITH_INOTIFY
+ load_inotify_database(&database, fildes);
+#endif
+ }
+ else {
+ database.mtime = (time_t) 0;
+ load_database(&database);
+ }
set_time(TRUE);
run_reboot_jobs(&database);
timeRunning = virtualTime = clockTime;
* clock. Classify the change into one of 4 cases.
*/
timeDiff = timeRunning - virtualTime;
-
- load_database(&database);
+ if (inotify_enabled) {
+#if defined WITH_INOTIFY
+ check_inotify_database(&database, fildes);
+#endif
+ }
+ else
+ load_database(&database);
+
/* shortcut for the most common case */
if (timeDiff == 1) {
virtualTime = timeRunning;
if (job_runqueue())
sleep(10);
virtualTime++;
- find_jobs(virtualTime, &database,
- TRUE, TRUE);
+ find_jobs(virtualTime, &database, TRUE, TRUE);
} while (virtualTime < timeRunning);
break;
/* Check to see if we received a signal while running jobs. */
if (got_sighup) {
got_sighup = 0;
+ if (!inotify_enabled)
database.mtime = (time_t) 0;
log_close();
}
sigchld_reaper();
}
}
+#if defined WITH_INOTIFY
+ set_cron_unwatched(fildes);
+
+ int ret;
+ ret = close(fildes);
+ if (ret)
+ perror ("close");
+#endif
}
static void
Debug(DSCH, ("[%ld] tick(%d,%d,%d,%d,%d) %s %s\n",
(long)getpid(), minute, hour, dom, month, dow,
doWild?" ":"No wildcard",doNonWild?" ":"Wildcard only"))
-
/* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
* first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
* on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this
Debug(DSCH|DEXT, ("user [%s:%ld:%ld:...] cmd=\"%s\"\n",
e->pwd->pw_name, (long)e->pwd->pw_uid,
(long)e->pwd->pw_gid, e->cmd))
-
job_tz = env_get("CRON_TZ", e->envp);
maketime(job_tz, orig_tz);
+ /* here we test whether time is NOW */
if (bit_test(e->minute, minute) &&
bit_test(e->hour, hour) &&
bit_test(e->month, month) &&
if ((doNonWild &&
!(e->flags & (MIN_STAR|HR_STAR))) ||
(doWild && (e->flags & (MIN_STAR|HR_STAR))))
- job_add(e, u);
+ job_add(e, u); /*will add job, if it isn't in queue already for NOW.*/
}
}
}
*/
if (got_sighup) {
got_sighup = 0;
- db->mtime = (time_t) 0;
+ if (!inotify_enabled)
+ db->mtime = (time_t) 0;
log_close();
}
if (got_sigchld) {
#define TMAX(a,b) ((a)>(b)?(a):(b))
+/* size of the event structure, not counting name */
+#define EVENT_SIZE (sizeof (struct inotify_event))
+
+/* reasonable guess as to size of 1024 events */
+#define BUF_LEN (1024 * (EVENT_SIZE + 16))
+
+#if defined WITH_INOTIFY
+/* state say if we change the crontable */
+#define RELOAD 1
+void unlink_inotify_database(cron_db *, cron_db , int);
+#endif
+
static void process_crontab(const char *, const char *,
const char *, struct stat *,
cron_db *, cron_db *);
static void max_mtime( char *dir_name, struct stat *max_st );
/* record max mtime of any file under dir_name in max_st */
+#if defined WITH_INOTIFY
+void
+process_inotify_crontab(const char *uname, const char *fname, const char *tabname,
+ cron_db *new_db, cron_db *old_db, int fd, int state) {
+ struct passwd *pw = NULL;
+ int crontab_fd = OK - 1;
+ user *u;
+ int crond_crontab = (fname == NULL) && (strcmp(tabname, SYSCRONTAB) != 0);
+
+ if (fname == NULL) {
+ /* must be set to something for logging purposes.
+ */
+ fname = "*system*";
+ } else if ((pw = getpwnam(uname)) == NULL) {
+ /* file doesn't have a user in passwd file.
+ */
+ log_it("CRON", getpid(), "ORPHAN", "no passwd entry");
+ goto next_crontab;
+ }
+
+/* need to rewrite this part because of statbuf related to stat'ing */
+/*
+ if (PermitAnyCrontab == 0) {
+ }
+*/
+ Debug(DLOAD, ("\t%s:", fname))
+ u = find_user(old_db, fname, crond_crontab ? tabname : NULL ); /* goes only through database in memory */
+
+ /* in first run is database empty and we need jump this section */
+ if (u != NULL) {
+ /* if crontab has not changed since we last read it
+ * in, then we can just use our existing entry.
+ */
+ /* 6 because we want string reload or none */
+ if (state != RELOAD) {
+ Debug(DLOAD, (" [no change, using old data]"))
+ unlink_user(old_db, u);
+ link_user(new_db, u);
+ goto next_crontab;
+ }
+ /* before we fall through to the code that will reload
+ * the user, let's deallocate and unlink the user in
+ * the old database. This is more a point of memory
+ * efficiency than anything else, since all leftover
+ * users will be deleted from the old database when
+ * we finish with the crontab...
+ */
+
+ Debug(DLOAD, (" [delete old data]"))
+ unlink_user(old_db, u);
+ free_user(u);
+ Debug(DSCH, ("RELOAD %s\n", tabname))
+ }
+ if ((crontab_fd = open(tabname, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0)) < OK) {
+ log_it("CRON", getpid(), "CAN'T OPEN", tabname);
+ goto next_crontab;
+ }
+
+ u = load_user(crontab_fd, pw, uname, fname, tabname); /* touch the disk */
+ if (u != NULL)
+ link_user(new_db, u);
+
+ next_crontab:
+ if (crontab_fd >= OK) {
+ Debug(DLOAD, (" [done]\n"))
+ close(crontab_fd);
+ }
+}
+
+void
+load_inotify_database(cron_db *old_db, int fd) {
+ cron_db new_db;
+ DIR_T *dp;
+ DIR *dir;
+
+ new_db.head = new_db.tail = NULL;
+ process_inotify_crontab("root", NULL, SYSCRONTAB, &new_db, old_db, fd, RELOAD);
+
+ /* RH_CROND_DIR /etc/cron.d */
+ if (!(dir = opendir(RH_CROND_DIR))) {
+ log_it("CRON", getpid(), "OPENDIR FAILED", RH_CROND_DIR);
+ (void) exit(ERROR_EXIT);
+ }
+ while (NULL != (dp = readdir(dir))) {
+ char tabname[MAXNAMLEN+1];
+
+ if (not_a_crontab(dp))
+ continue;
+
+ if (!glue_strings(tabname, sizeof tabname, RH_CROND_DIR, dp->d_name, '/'))
+ continue;
+
+ process_inotify_crontab("root", NULL, tabname, &new_db, old_db, fd, RELOAD);
+ }
+ closedir(dir);
+ /* SPOOL_DIR */
+ if (!(dir = opendir(SPOOL_DIR))) {
+ syslog(LOG_INFO, "CRON: OPENDIR FAILED %s", SPOOL_DIR);
+ (void) exit(ERROR_EXIT);
+ }
+
+ while (NULL != (dp = readdir(dir))) {
+ char fname[MAXNAMLEN+1], tabname[MAXNAMLEN+1];
+
+ if (not_a_crontab(dp))
+ continue;
+
+ strncpy(fname, dp->d_name, MAXNAMLEN);
+
+ if (!glue_strings(tabname, sizeof tabname, SPOOL_DIR, fname, '/'))
+ continue;
+
+ process_inotify_crontab(fname, fname, tabname, &new_db, old_db, fd, RELOAD);
+ }
+ closedir(dir);
+
+ unlink_inotify_database(old_db, new_db, fd);
+}
+
+void
+check_inotify_database(cron_db *old_db, int fd) {
+ cron_db new_db;
+ DIR_T *dp;
+ DIR *dir;
+ struct timeval time;
+ fd_set rfds;
+ int retval = 0;
+ char buf[BUF_LEN];
+
+ time.tv_sec = 1;
+ time.tv_usec = 0;
+
+ FD_ZERO(&rfds);
+ FD_SET(fd, &rfds);
+
+ retval = select(fd + 1, &rfds, NULL, NULL, &time);
+ if (retval == -1) {
+ perror("select()");
+ syslog(LOG_INFO, "CRON: select failed: %s", strerror(errno));
+ }
+ else if (FD_ISSET(fd, &rfds)) {
+ new_db.head = new_db.tail = NULL;
+ if (read(fd, buf, sizeof(buf)) == -1)
+ log_it("CRON", getpid(), "reading problem",buf);
+ process_inotify_crontab("root", NULL, SYSCRONTAB, &new_db, old_db, fd, RELOAD);
+
+ if (!(dir = opendir(RH_CROND_DIR))) {
+ log_it("CRON", getpid(), "OPENDIR FAILED", RH_CROND_DIR);
+ (void) exit(ERROR_EXIT);
+ }
+ while (NULL != (dp = readdir(dir))) {
+ char tabname[MAXNAMLEN+1];
+
+ if (not_a_crontab(dp))
+ continue;
+
+ if (!glue_strings(tabname, sizeof tabname, RH_CROND_DIR, dp->d_name, '/'))
+ continue;
+ process_inotify_crontab("root", NULL, tabname, &new_db, old_db, fd, RELOAD);
+ }
+ closedir(dir);
+
+ if (!(dir = opendir(SPOOL_DIR))) {
+ syslog(LOG_INFO, "CRON: OPENDIR FAILED %s", SPOOL_DIR);
+ (void) exit(ERROR_EXIT);
+ }
+ while (NULL != (dp = readdir(dir))) {
+ char fname[MAXNAMLEN+1], tabname[MAXNAMLEN+1];
+
+ if (not_a_crontab(dp))
+ continue;
+
+ strncpy(fname, dp->d_name, MAXNAMLEN);
+
+ if (!glue_strings(tabname, sizeof tabname, SPOOL_DIR, dp->d_name, '/'))
+ continue;
+ process_inotify_crontab(fname, fname, tabname, &new_db, old_db, fd, RELOAD);
+ }
+ closedir(dir);
+ }
+ else {
+ new_db.head = new_db.tail = NULL;
+ process_inotify_crontab("root", NULL, SYSCRONTAB, &new_db, old_db, fd, !RELOAD);
+ if (!(dir = opendir(RH_CROND_DIR))) {
+ log_it("CRON", getpid(), "OPENDIR FAILED", RH_CROND_DIR);
+ (void) exit(ERROR_EXIT);
+ }
+
+ while (NULL != (dp = readdir(dir))) {
+ char tabname[MAXNAMLEN+1];
+
+ if (not_a_crontab(dp))
+ continue;
+
+ if (!glue_strings(tabname, sizeof tabname, RH_CROND_DIR, dp->d_name, '/'))
+ continue;
+ process_inotify_crontab("root", NULL, tabname, &new_db, old_db, fd, !RELOAD);
+ }
+ closedir(dir);
+
+ if (!(dir = opendir(SPOOL_DIR))) {
+ syslog(LOG_INFO, "CRON: OPENDIR FAILED %s", SPOOL_DIR);
+ (void) exit(ERROR_EXIT);
+ }
+
+ while (NULL != (dp = readdir(dir))) {
+ char fname[MAXNAMLEN+1], tabname[MAXNAMLEN+1];
+
+ if (not_a_crontab(dp))
+ continue;
+
+ strncpy(fname, dp->d_name, MAXNAMLEN);
+
+ if (!glue_strings(tabname, sizeof tabname, SPOOL_DIR, fname, '/'))
+ continue;
+
+ process_inotify_crontab(fname, fname, tabname, &new_db, old_db, fd, !RELOAD);
+ }
+ closedir(dir);
+ }
+ FD_CLR(fd, &rfds);
+
+ unlink_inotify_database(old_db, new_db, fd);
+}
+
+void
+unlink_inotify_database(cron_db *old_db, cron_db new_db, int fd) {
+ user *u, *nu;
+ /* whatever's left in the old database is now junk.
+ */
+ Debug(DLOAD, ("unlinking old database:\n"))
+ for (u = old_db->head; u != NULL; u = nu) {
+ Debug(DLOAD, ("\t%s\n", u->name))
+ nu = u->next;
+ unlink_user(old_db, u);
+ free_user(u);
+ }
+
+ /* overwrite the database control block with the new one.
+ */
+ *old_db = new_db;
+ Debug(DLOAD, ("load_database is done\n"))
+}
+
+/*void
+read_dir(char *dir_name, cron_db *new_db, cron_db *old_db, int fd, int state) {
+ DIR *dir;
+ DIR_T *dp;
+
+ if (!(dir = opendir(dir_name))) {
+ syslog(LOG_INFO, "CRON: OPENDIR FAILED %s", dir_name);
+ (void) exit(ERROR_EXIT);
+ }
+
+ while (NULL != (dp = readdir(dir))) {
+ char tabname[MAXNAMLEN+1];
+
+ if (not_a_crontab(dp))
+ continue;
+
+ if (!glue_strings(tabname, sizeof tabname, dir_name, dp->d_name, '/'))
+ continue;
+ process_inotify_crontab("root", NULL, tabname, &new_db, old_db, fd, state);
+ }
+ closedir(dir);
+}
+*/
+#endif
+
void
load_database(cron_db *old_db) {
struct stat statbuf, syscron_stat, crond_stat;