1 /* Copyright 1988,1990,1993,1994 by Paul Vixie 2 * All rights reserved 3 */ 4 5 /* 6 * Copyright (c) 1997 by Internet Software Consortium 7 * 8 * Permission to use, copy, modify, and distribute this software for any 9 * purpose with or without fee is hereby granted, provided that the above 10 * copyright notice and this permission notice appear in all copies. 11 * 12 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS 13 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 14 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE 15 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 16 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 17 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 18 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 19 * SOFTWARE. 20 */ 21 22 #if !defined(lint) && !defined(LINT) 23 static const char rcsid[] = 24 "$Id: database.c,v 1.3 1998/08/14 00:32:38 vixie Exp $"; 25 #endif 26 27 /* vix 26jan87 [RCS has the log] 28 */ 29 30 #include "cron.h" 31 32 #define TMAX(a,b) ((a)>(b)?(a):(b)) 33 34 static void process_crontab(const char *, const char *, 35 const char *, struct stat *, 36 cron_db *, cron_db *); 37 38 void 39 load_database(cron_db *old_db) 40 { 41 struct stat statbuf, syscron_stat, st; 42 cron_db new_db; 43 DIR_T *dp; 44 DIR *dir; 45 user *u, *nu; 46 time_t maxmtime; 47 struct { 48 const char *name; 49 struct stat st; 50 } syscrontabs [] = { 51 { SYSCRONTABS }, 52 { LOCALSYSCRONTABS } 53 }; 54 int i, ret; 55 56 Debug(DLOAD, ("[%d] load_database()\n", getpid())) 57 58 /* before we start loading any data, do a stat on SPOOL_DIR 59 * so that if anything changes as of this moment (i.e., before we've 60 * cached any of the database), we'll see the changes next time. 61 */ 62 if (stat(SPOOL_DIR, &statbuf) < OK) { 63 log_it("CRON", getpid(), "STAT FAILED", SPOOL_DIR); 64 (void) exit(ERROR_EXIT); 65 } 66 67 /* track system crontab file 68 */ 69 if (stat(SYSCRONTAB, &syscron_stat) < OK) 70 syscron_stat.st_mtime = 0; 71 72 maxmtime = TMAX(statbuf.st_mtime, syscron_stat.st_mtime); 73 74 for (i = 0; i < nitems(syscrontabs); i++) { 75 if (stat(syscrontabs[i].name, &syscrontabs[i].st) != -1) { 76 maxmtime = TMAX(syscrontabs[i].st.st_mtime, maxmtime); 77 /* Traverse into directory */ 78 if (!(dir = opendir(syscrontabs[i].name))) 79 continue; 80 while (NULL != (dp = readdir(dir))) { 81 if (dp->d_name[0] == '.') 82 continue; 83 ret = fstatat(dirfd(dir), dp->d_name, &st, 0); 84 if (ret != 0 || !S_ISREG(st.st_mode)) 85 continue; 86 maxmtime = TMAX(st.st_mtime, maxmtime); 87 } 88 closedir(dir); 89 } else { 90 syscrontabs[i].st.st_mtime = 0; 91 } 92 } 93 94 /* if spooldir's mtime has not changed, we don't need to fiddle with 95 * the database. 96 * 97 * Note that old_db->mtime is initialized to 0 in main(), and 98 * so is guaranteed to be different than the stat() mtime the first 99 * time this function is called. 100 */ 101 if (old_db->mtime == maxmtime) { 102 Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n", 103 getpid())) 104 return; 105 } 106 107 /* something's different. make a new database, moving unchanged 108 * elements from the old database, reloading elements that have 109 * actually changed. Whatever is left in the old database when 110 * we're done is chaff -- crontabs that disappeared. 111 */ 112 new_db.mtime = maxmtime; 113 new_db.head = new_db.tail = NULL; 114 115 if (syscron_stat.st_mtime) { 116 process_crontab("root", SYS_NAME, 117 SYSCRONTAB, &syscron_stat, 118 &new_db, old_db); 119 } 120 121 for (i = 0; i < nitems(syscrontabs); i++) { 122 char tabname[MAXPATHLEN]; 123 if (syscrontabs[i].st.st_mtime == 0) 124 continue; 125 if (!(dir = opendir(syscrontabs[i].name))) { 126 log_it("CRON", getpid(), "OPENDIR FAILED", 127 syscrontabs[i].name); 128 (void) exit(ERROR_EXIT); 129 } 130 131 while (NULL != (dp = readdir(dir))) { 132 if (dp->d_name[0] == '.') 133 continue; 134 if (fstatat(dirfd(dir), dp->d_name, &st, 0) == 0 && 135 !S_ISREG(st.st_mode)) 136 continue; 137 snprintf(tabname, sizeof(tabname), "%s/%s", 138 syscrontabs[i].name, dp->d_name); 139 process_crontab("root", SYS_NAME, tabname, 140 &syscrontabs[i].st, &new_db, old_db); 141 } 142 closedir(dir); 143 } 144 145 /* we used to keep this dir open all the time, for the sake of 146 * efficiency. however, we need to close it in every fork, and 147 * we fork a lot more often than the mtime of the dir changes. 148 */ 149 if (!(dir = opendir(SPOOL_DIR))) { 150 log_it("CRON", getpid(), "OPENDIR FAILED", SPOOL_DIR); 151 (void) exit(ERROR_EXIT); 152 } 153 154 while (NULL != (dp = readdir(dir))) { 155 char fname[MAXNAMLEN+1], tabname[MAXNAMLEN+1]; 156 157 /* avoid file names beginning with ".". this is good 158 * because we would otherwise waste two guaranteed calls 159 * to getpwnam() for . and .., and also because user names 160 * starting with a period are just too nasty to consider. 161 */ 162 if (dp->d_name[0] == '.') 163 continue; 164 165 (void) strncpy(fname, dp->d_name, sizeof(fname)); 166 fname[sizeof(fname)-1] = '\0'; 167 168 if (snprintf(tabname, sizeof tabname, CRON_TAB(fname)) 169 >= sizeof(tabname)) 170 continue; /* XXX log? */ 171 172 process_crontab(fname, fname, tabname, 173 &statbuf, &new_db, old_db); 174 } 175 closedir(dir); 176 177 /* if we don't do this, then when our children eventually call 178 * getpwnam() in do_command.c's child_process to verify MAILTO=, 179 * they will screw us up (and v-v). 180 */ 181 endpwent(); 182 183 /* whatever's left in the old database is now junk. 184 */ 185 Debug(DLOAD, ("unlinking old database:\n")) 186 for (u = old_db->head; u != NULL; u = nu) { 187 Debug(DLOAD, ("\t%s\n", u->name)) 188 nu = u->next; 189 unlink_user(old_db, u); 190 free_user(u); 191 } 192 193 /* overwrite the database control block with the new one. 194 */ 195 *old_db = new_db; 196 Debug(DLOAD, ("load_database is done\n")) 197 } 198 199 void 200 link_user(cron_db *db, user *u) 201 { 202 if (db->head == NULL) 203 db->head = u; 204 if (db->tail) 205 db->tail->next = u; 206 u->prev = db->tail; 207 u->next = NULL; 208 db->tail = u; 209 } 210 211 void 212 unlink_user(cron_db *db, user *u) 213 { 214 if (u->prev == NULL) 215 db->head = u->next; 216 else 217 u->prev->next = u->next; 218 219 if (u->next == NULL) 220 db->tail = u->prev; 221 else 222 u->next->prev = u->prev; 223 } 224 225 user * 226 find_user(cron_db *db, const char *name) 227 { 228 user *u; 229 230 for (u = db->head; u != NULL; u = u->next) 231 if (strcmp(u->name, name) == 0) 232 break; 233 return (u); 234 } 235 236 static void 237 process_crontab(const char *uname, const char *fname, const char *tabname, 238 struct stat *statbuf, cron_db *new_db, cron_db *old_db) 239 { 240 struct passwd *pw = NULL; 241 int crontab_fd = OK - 1; 242 user *u; 243 entry *e; 244 time_t now; 245 246 if (strcmp(fname, SYS_NAME) != 0 && !(pw = getpwnam(uname))) { 247 /* file doesn't have a user in passwd file. 248 */ 249 log_it(fname, getpid(), "ORPHAN", "no passwd entry"); 250 goto next_crontab; 251 } 252 253 if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK) { 254 /* crontab not accessible? 255 */ 256 log_it(fname, getpid(), "CAN'T OPEN", tabname); 257 goto next_crontab; 258 } 259 260 if (fstat(crontab_fd, statbuf) < OK) { 261 log_it(fname, getpid(), "FSTAT FAILED", tabname); 262 goto next_crontab; 263 } 264 265 Debug(DLOAD, ("\t%s:", fname)) 266 u = find_user(old_db, fname); 267 if (u != NULL) { 268 /* if crontab has not changed since we last read it 269 * in, then we can just use our existing entry. 270 */ 271 if (u->mtime == statbuf->st_mtime) { 272 Debug(DLOAD, (" [no change, using old data]")) 273 unlink_user(old_db, u); 274 link_user(new_db, u); 275 goto next_crontab; 276 } 277 278 /* before we fall through to the code that will reload 279 * the user, let's deallocate and unlink the user in 280 * the old database. This is more a point of memory 281 * efficiency than anything else, since all leftover 282 * users will be deleted from the old database when 283 * we finish with the crontab... 284 */ 285 Debug(DLOAD, (" [delete old data]")) 286 unlink_user(old_db, u); 287 free_user(u); 288 log_it(fname, getpid(), "RELOAD", tabname); 289 } 290 u = load_user(crontab_fd, pw, fname); 291 if (u != NULL) { 292 u->mtime = statbuf->st_mtime; 293 /* 294 * TargetTime == 0 when we're initially populating the database, 295 * and TargetTime > 0 any time after that (i.e. we're reloading 296 * cron.d/ files because they've been created/modified). In the 297 * latter case, we should check for any interval jobs and run 298 * them 'n' seconds from the time the job was loaded/reloaded. 299 * Otherwise, they will not be run until cron is restarted. 300 */ 301 if (TargetTime != 0) { 302 now = time(NULL); 303 for (e = u->crontab; e != NULL; e = e->next) { 304 if ((e->flags & INTERVAL) != 0) 305 e->lastexit = now; 306 } 307 } 308 link_user(new_db, u); 309 } 310 311 next_crontab: 312 if (crontab_fd >= OK) { 313 Debug(DLOAD, (" [done]\n")) 314 close(crontab_fd); 315 } 316 } 317