1 /* $OpenBSD: database.c,v 1.5 2001/02/18 19:48:33 millert Exp $ */ 2 /* Copyright 1988,1990,1993,1994 by Paul Vixie 3 * All rights reserved 4 */ 5 6 /* 7 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. 8 * 9 * Permission to use, copy, modify, and distribute this software for any 10 * purpose with or without fee is hereby granted, provided that the above 11 * copyright notice and this permission notice appear in all copies. 12 * 13 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS 14 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 15 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE 16 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 17 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 18 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 19 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 20 * SOFTWARE. 21 */ 22 23 #if !defined(lint) && !defined(LINT) 24 static char rcsid[] = "$OpenBSD: database.c,v 1.5 2001/02/18 19:48:33 millert Exp $"; 25 #endif 26 27 /* vix 26jan87 [RCS has the log] 28 */ 29 30 #include "cron.h" 31 32 #define HASH(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 struct stat statbuf, syscron_stat; 41 cron_db new_db; 42 DIR_T *dp; 43 DIR *dir; 44 user *u, *nu; 45 46 Debug(DLOAD, ("[%ld] load_database()\n", (long)getpid())) 47 48 /* before we start loading any data, do a stat on SPOOL_DIR 49 * so that if anything changes as of this moment (i.e., before we've 50 * cached any of the database), we'll see the changes next time. 51 */ 52 if (stat(SPOOL_DIR, &statbuf) < OK) { 53 log_it("CRON", getpid(), "STAT FAILED", SPOOL_DIR); 54 (void) exit(ERROR_EXIT); 55 } 56 57 /* track system crontab file 58 */ 59 if (stat(SYSCRONTAB, &syscron_stat) < OK) 60 syscron_stat.st_mtime = 0; 61 62 /* if spooldir's mtime has not changed, we don't need to fiddle with 63 * the database. 64 * 65 * Note that old_db->mtime is initialized to 0 in main(), and 66 * so is guaranteed to be different than the stat() mtime the first 67 * time this function is called. 68 */ 69 if (old_db->mtime == HASH(statbuf.st_mtime, syscron_stat.st_mtime)) { 70 Debug(DLOAD, ("[%ld] spool dir mtime unch, no load needed.\n", 71 (long)getpid())) 72 return; 73 } 74 75 /* something's different. make a new database, moving unchanged 76 * elements from the old database, reloading elements that have 77 * actually changed. Whatever is left in the old database when 78 * we're done is chaff -- crontabs that disappeared. 79 */ 80 new_db.mtime = HASH(statbuf.st_mtime, syscron_stat.st_mtime); 81 new_db.head = new_db.tail = NULL; 82 83 if (syscron_stat.st_mtime) { 84 process_crontab("root", "*system*", 85 SYSCRONTAB, &syscron_stat, 86 &new_db, old_db); 87 } 88 89 /* we used to keep this dir open all the time, for the sake of 90 * efficiency. however, we need to close it in every fork, and 91 * we fork a lot more often than the mtime of the dir changes. 92 */ 93 if (!(dir = opendir(SPOOL_DIR))) { 94 log_it("CRON", getpid(), "OPENDIR FAILED", SPOOL_DIR); 95 (void) exit(ERROR_EXIT); 96 } 97 98 while (NULL != (dp = readdir(dir))) { 99 char fname[MAXNAMLEN+1], tabname[MAXNAMLEN]; 100 101 /* avoid file names beginning with ".". this is good 102 * because we would otherwise waste two guaranteed calls 103 * to getpwnam() for . and .., and also because user names 104 * starting with a period are just too nasty to consider. 105 */ 106 if (dp->d_name[0] == '.') 107 continue; 108 109 if (strlen(dp->d_name) >= sizeof fname) 110 continue; /* XXX log? */ 111 (void) strcpy(fname, dp->d_name); 112 113 if (!glue_strings(tabname, sizeof tabname, SPOOL_DIR, 114 fname, '/')) 115 continue; /* XXX log? */ 116 117 process_crontab(fname, fname, tabname, 118 &statbuf, &new_db, old_db); 119 } 120 closedir(dir); 121 122 /* if we don't do this, then when our children eventually call 123 * getpwnam() in do_command.c's child_process to verify MAILTO=, 124 * they will screw us up (and v-v). 125 */ 126 endpwent(); 127 128 /* whatever's left in the old database is now junk. 129 */ 130 Debug(DLOAD, ("unlinking old database:\n")) 131 for (u = old_db->head; u != NULL; u = nu) { 132 Debug(DLOAD, ("\t%s\n", u->name)) 133 nu = u->next; 134 unlink_user(old_db, u); 135 free_user(u); 136 } 137 138 /* overwrite the database control block with the new one. 139 */ 140 *old_db = new_db; 141 Debug(DLOAD, ("load_database is done\n")) 142 } 143 144 void 145 link_user(cron_db *db, user *u) { 146 if (db->head == NULL) 147 db->head = u; 148 if (db->tail) 149 db->tail->next = u; 150 u->prev = db->tail; 151 u->next = NULL; 152 db->tail = u; 153 } 154 155 void 156 unlink_user(cron_db *db, user *u) { 157 if (u->prev == NULL) 158 db->head = u->next; 159 else 160 u->prev->next = u->next; 161 162 if (u->next == NULL) 163 db->tail = u->prev; 164 else 165 u->next->prev = u->prev; 166 } 167 168 user * 169 find_user(cron_db *db, const char *name) { 170 user *u; 171 172 for (u = db->head; u != NULL; u = u->next) 173 if (strcmp(u->name, name) == 0) 174 break; 175 return (u); 176 } 177 178 static void 179 process_crontab(const char *uname, const char *fname, const char *tabname, 180 struct stat *statbuf, cron_db *new_db, cron_db *old_db) 181 { 182 struct passwd *pw = NULL; 183 int crontab_fd = OK - 1; 184 user *u; 185 186 if (strcmp(fname, "*system*") != 0 && !(pw = getpwnam(uname))) { 187 /* file doesn't have a user in passwd file. 188 */ 189 log_it(fname, getpid(), "ORPHAN", "no passwd entry"); 190 goto next_crontab; 191 } 192 193 if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK) { 194 /* crontab not accessible? 195 */ 196 log_it(fname, getpid(), "CAN'T OPEN", tabname); 197 goto next_crontab; 198 } 199 200 if (fstat(crontab_fd, statbuf) < OK) { 201 log_it(fname, getpid(), "FSTAT FAILED", tabname); 202 goto next_crontab; 203 } 204 205 Debug(DLOAD, ("\t%s:", fname)) 206 u = find_user(old_db, fname); 207 if (u != NULL) { 208 /* if crontab has not changed since we last read it 209 * in, then we can just use our existing entry. 210 */ 211 if (u->mtime == statbuf->st_mtime) { 212 Debug(DLOAD, (" [no change, using old data]")) 213 unlink_user(old_db, u); 214 link_user(new_db, u); 215 goto next_crontab; 216 } 217 218 /* before we fall through to the code that will reload 219 * the user, let's deallocate and unlink the user in 220 * the old database. This is more a point of memory 221 * efficiency than anything else, since all leftover 222 * users will be deleted from the old database when 223 * we finish with the crontab... 224 */ 225 Debug(DLOAD, (" [delete old data]")) 226 unlink_user(old_db, u); 227 free_user(u); 228 log_it(fname, getpid(), "RELOAD", tabname); 229 } 230 u = load_user(crontab_fd, pw, fname); 231 if (u != NULL) { 232 u->mtime = statbuf->st_mtime; 233 link_user(new_db, u); 234 } 235 236 next_crontab: 237 if (crontab_fd >= OK) { 238 Debug(DLOAD, (" [done]\n")) 239 close(crontab_fd); 240 } 241 } 242