Changeset View
Standalone View
head/usr.sbin/cron/cron/database.c
Show All 39 Lines | |||||
void | void | ||||
load_database(old_db) | load_database(old_db) | ||||
cron_db *old_db; | cron_db *old_db; | ||||
{ | { | ||||
DIR *dir; | DIR *dir; | ||||
struct stat statbuf; | struct stat statbuf; | ||||
struct stat syscron_stat; | struct stat syscron_stat; | ||||
time_t maxmtime; | |||||
DIR_T *dp; | DIR_T *dp; | ||||
cron_db new_db; | cron_db new_db; | ||||
user *u, *nu; | user *u, *nu; | ||||
struct { | |||||
const char *name; | |||||
struct stat st; | |||||
} syscrontabs [] = { | |||||
{ SYSCRONTABS }, | |||||
{ LOCALSYSCRONTABS } | |||||
}; | |||||
int i; | |||||
Debug(DLOAD, ("[%d] load_database()\n", getpid())) | Debug(DLOAD, ("[%d] load_database()\n", getpid())) | ||||
/* before we start loading any data, do a stat on SPOOL_DIR | /* 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 | * 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. | * cached any of the database), we'll see the changes next time. | ||||
*/ | */ | ||||
if (stat(SPOOL_DIR, &statbuf) < OK) { | if (stat(SPOOL_DIR, &statbuf) < OK) { | ||||
log_it("CRON", getpid(), "STAT FAILED", SPOOL_DIR); | log_it("CRON", getpid(), "STAT FAILED", SPOOL_DIR); | ||||
(void) exit(ERROR_EXIT); | (void) exit(ERROR_EXIT); | ||||
} | } | ||||
/* track system crontab file | /* track system crontab file | ||||
*/ | */ | ||||
if (stat(SYSCRONTAB, &syscron_stat) < OK) | if (stat(SYSCRONTAB, &syscron_stat) < OK) | ||||
syscron_stat.st_mtime = 0; | syscron_stat.st_mtime = 0; | ||||
maxmtime = TMAX(statbuf.st_mtime, syscron_stat.st_mtime); | |||||
for (i = 0; i < nitems(syscrontabs); i++) { | |||||
if (stat(syscrontabs[i].name, &syscrontabs[i].st) != -1) { | |||||
jilles: This means that modifying an existing file in /etc/cron.d is not detected. That is an uncommon… | |||||
baptAuthorUnsubmitted Not Done Inline ActionsThat is the same behaviour as how users crontabs are detected to have been modified. I find it pretty convenient :) might be inefficient if lots of cron files have been added any better idea? bapt: That is the same behaviour as how users crontabs are detected to have been modified. I find it… | |||||
knuUnsubmitted Not Done Inline ActionsThe cron(8) man page reads: "Note that the crontab(1) command updates the modification time of the spool directory whenever it changes a crontab." So for user crontabs it is transparent to users because they always edit their crontab via crontab(1). On the other hand there's no tool support for /etc/cron.d/*, so you have to manually edit a file in it just to find out crond will not reload it unless you touch /etc/cron.d by hand. I think we should follow Debian's cron which properly checks modification times of individual files in cron.d: https://anonscm.debian.org/cgit/pkg-cron/pkg-cron.git/tree/database.c?id=1215fdaddba158f32b10f0cc041e634505d50cb8#n118 knu: The cron(8) man page reads: "Note that the crontab(1) command updates the modification time of… | |||||
maxmtime = TMAX(syscrontabs[i].st.st_mtime, maxmtime); | |||||
} else { | |||||
syscrontabs[i].st.st_mtime = 0; | |||||
} | |||||
} | |||||
/* if spooldir's mtime has not changed, we don't need to fiddle with | /* if spooldir's mtime has not changed, we don't need to fiddle with | ||||
* the database. | * the database. | ||||
* | * | ||||
* Note that old_db->mtime is initialized to 0 in main(), and | * Note that old_db->mtime is initialized to 0 in main(), and | ||||
* so is guaranteed to be different than the stat() mtime the first | * so is guaranteed to be different than the stat() mtime the first | ||||
* time this function is called. | * time this function is called. | ||||
*/ | */ | ||||
if (old_db->mtime == TMAX(statbuf.st_mtime, syscron_stat.st_mtime)) { | if (old_db->mtime == maxmtime) { | ||||
Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n", | Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n", | ||||
getpid())) | getpid())) | ||||
return; | return; | ||||
} | } | ||||
/* something's different. make a new database, moving unchanged | /* something's different. make a new database, moving unchanged | ||||
* elements from the old database, reloading elements that have | * elements from the old database, reloading elements that have | ||||
* actually changed. Whatever is left in the old database when | * actually changed. Whatever is left in the old database when | ||||
* we're done is chaff -- crontabs that disappeared. | * we're done is chaff -- crontabs that disappeared. | ||||
*/ | */ | ||||
new_db.mtime = TMAX(statbuf.st_mtime, syscron_stat.st_mtime); | new_db.mtime = maxmtime; | ||||
new_db.head = new_db.tail = NULL; | new_db.head = new_db.tail = NULL; | ||||
if (syscron_stat.st_mtime) { | if (syscron_stat.st_mtime) { | ||||
process_crontab("root", SYS_NAME, | process_crontab("root", SYS_NAME, | ||||
SYSCRONTAB, &syscron_stat, | SYSCRONTAB, &syscron_stat, | ||||
&new_db, old_db); | &new_db, old_db); | ||||
} | |||||
for (i = 0; i < nitems(syscrontabs); i++) { | |||||
char tabname[MAXPATHLEN]; | |||||
if (syscrontabs[i].st.st_mtime == 0) | |||||
continue; | |||||
if (!(dir = opendir(syscrontabs[i].name))) { | |||||
log_it("CRON", getpid(), "OPENDIR FAILED", | |||||
syscrontabs[i].name); | |||||
(void) exit(ERROR_EXIT); | |||||
} | |||||
while (NULL != (dp = readdir(dir))) { | |||||
if (dp->d_name[0] == '.') | |||||
continue; | |||||
if (dp->d_type != DT_REG) | |||||
jillesUnsubmitted Done Inline ActionsWe have filesystems that return DT_UNKNOWN (such as NFS with default options), in which case an fstatat is required. This check also prevents following symlinks in /etc/cron.d which Debian's cron allows. jilles: We have filesystems that return DT_UNKNOWN (such as NFS with default options), in which case an… | |||||
baptAuthorUnsubmitted Not Done Inline ActionsFixed thanks bapt: Fixed thanks | |||||
continue; | |||||
snprintf(tabname, sizeof(tabname), "%s/%s", | |||||
syscrontabs[i].name, dp->d_name); | |||||
process_crontab("root", SYS_NAME, tabname, | |||||
knuUnsubmitted Not Done Inline ActionsLoading all crontabs into a single namespace that is the same as /etc/crontab ("root") means environment variable settings defined in a file will be overwritten by those in another. So, one crontab file installed by a package may affect system jobs, which does not seem great to me. Debian seems to allocate separate namespaces for each cron.d file by using the filenames: https://anonscm.debian.org/cgit/pkg-cron/pkg-cron.git/tree/database.c?id=1215fdaddba158f32b10f0cc041e634505d50cb8#n247 knu: Loading all crontabs into a single namespace that is the same as /etc/crontab ("root") means… | |||||
&syscrontabs[i].st, &new_db, old_db); | |||||
} | |||||
closedir(dir); | |||||
} | } | ||||
/* we used to keep this dir open all the time, for the sake of | /* we used to keep this dir open all the time, for the sake of | ||||
* efficiency. however, we need to close it in every fork, and | * efficiency. however, we need to close it in every fork, and | ||||
* we fork a lot more often than the mtime of the dir changes. | * we fork a lot more often than the mtime of the dir changes. | ||||
*/ | */ | ||||
if (!(dir = opendir(SPOOL_DIR))) { | if (!(dir = opendir(SPOOL_DIR))) { | ||||
log_it("CRON", getpid(), "OPENDIR FAILED", SPOOL_DIR); | log_it("CRON", getpid(), "OPENDIR FAILED", SPOOL_DIR); | ||||
▲ Show 20 Lines • Show All 163 Lines • Show Last 20 Lines |
This means that modifying an existing file in /etc/cron.d is not detected. That is an uncommon thing to do.