diff -ru cronie-1.4.11/src/database.c cronie-1.4.11_patched/src/database.c --- cronie-1.4.11/src/database.c 2018-10-19 15:29:55.630225195 +0200 +++ cronie-1.4.11_patched/src/database.c 2018-10-19 15:32:14.552093860 +0200 @@ -48,6 +48,7 @@ #include "pathnames.h" #define TMAX(a,b) ((a)>(b)?(a):(b)) +#define TMIN(a,b) ((a)<(b)?(a):(b)) /* size of the event structure, not counting name */ #define EVENT_SIZE (sizeof (struct inotify_event)) @@ -237,6 +238,8 @@ if ((crontab_fd = check_open(tabname, uname, pw, &mtime)) == -1) goto next_crontab; + mtime = TMIN(new_db->mtime, mtime); + Debug(DLOAD, ("\t%s:", fname)); if (old_db != NULL) @@ -261,7 +264,7 @@ * we finish with the crontab... */ Debug(DLOAD, (" [delete old data]")); - unlink_user(old_db, u); + unlink_user(old_db, u); free_user(u); log_it(fname, getpid(), "RELOAD", tabname, 0); } @@ -328,18 +331,18 @@ cron_db new_db; DIR_T *dp; DIR *dir; - struct timeval time; + struct timeval timev; fd_set rfds; int retval; char buf[BUF_LEN]; pid_t pid = getpid(); - time.tv_sec = 0; - time.tv_usec = 0; + timev.tv_sec = 0; + timev.tv_usec = 0; FD_ZERO(&rfds); FD_SET(old_db->ifd, &rfds); - retval = select(old_db->ifd + 1, &rfds, NULL, NULL, &time); + retval = select(old_db->ifd + 1, &rfds, NULL, NULL, &timev); if (retval == -1) { if (errno != EINTR) log_it("CRON", pid, "INOTIFY", "select failed", errno); @@ -348,6 +351,7 @@ else if (FD_ISSET(old_db->ifd, &rfds)) { new_db.head = new_db.tail = NULL; new_db.ifd = old_db->ifd; + new_db.mtime = time(NULL) - 1; while ((retval = read(old_db->ifd, buf, sizeof (buf))) == -1 && errno == EINTR) ; @@ -452,14 +456,17 @@ DIR *dir; pid_t pid = getpid(); int is_local = 0; + time_t now; Debug(DLOAD, ("[%ld] load_database()\n", (long) pid)); - /* before we start loading any data, do a stat on SPOOL_DIR - * so that if anything changes as of this moment (i.e., before we've - * cached any of the database), we'll see the changes next time. - */ - if (stat(SPOOL_DIR, &statbuf) < OK) { + now = time(NULL); + + /* before we start loading any data, do a stat on SPOOL_DIR + * so that if anything changes as of this moment (i.e., before we've + * cached any of the database), we'll see the changes next time. + */ + if (stat(SPOOL_DIR, &statbuf) < OK) { log_it("CRON", pid, "STAT FAILED", SPOOL_DIR, errno); statbuf.st_mtime = 0; } @@ -492,13 +499,17 @@ * Note that old_db->mtime is initialized to 0 in main(), and * so is guaranteed to be different than the stat() mtime the first * time this function is called. + * + * We also use now - 1 as the upper bound of timestamp to avoid race, + * when a crontab is updated twice in a single second when we are + * just reading it. */ - if (old_db->mtime == TMAX(crond_stat.st_mtime, - TMAX(statbuf.st_mtime, syscron_stat.st_mtime)) + if (old_db->mtime == TMIN(now - 1, TMAX(crond_stat.st_mtime, + TMAX(statbuf.st_mtime, syscron_stat.st_mtime))) ) { Debug(DLOAD, ("[%ld] spool dir mtime unch, no load needed.\n", (long) pid)); - return 0; + return 0; } /* something's different. make a new database, moving unchanged @@ -506,8 +517,7 @@ * actually changed. Whatever is left in the old database when * we're done is chaff -- crontabs that disappeared. */ - new_db.mtime = TMAX(crond_stat.st_mtime, - TMAX(statbuf.st_mtime, syscron_stat.st_mtime)); + new_db.mtime = now - 1; new_db.head = new_db.tail = NULL; #if defined WITH_INOTIFY new_db.ifd = old_db->ifd;