| 1 | #if !defined(lint) && !defined(LINT) |
| 2 | static char rcsid[] = "$Header: database.c,v 2.1 90/07/18 00:23:51 vixie Exp $"; |
| 3 | #endif |
| 4 | |
| 5 | /* vix 26jan87 [RCS has the log] |
| 6 | */ |
| 7 | |
| 8 | /* Copyright 1988,1990 by Paul Vixie |
| 9 | * All rights reserved |
| 10 | * |
| 11 | * Distribute freely, except: don't remove my name from the source or |
| 12 | * documentation (don't take credit for my work), mark your changes (don't |
| 13 | * get me blamed for your possible bugs), don't alter or remove this |
| 14 | * notice. May be sold if buildable source is provided to buyer. No |
| 15 | * warrantee of any kind, express or implied, is included with this |
| 16 | * software; use at your own risk, responsibility for damages (if any) to |
| 17 | * anyone resulting from the use of this software rests entirely with the |
| 18 | * user. |
| 19 | * |
| 20 | * Send bug reports, bug fixes, enhancements, requests, flames, etc., and |
| 21 | * I'll try to keep a version up to date. I can be reached as follows: |
| 22 | * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013, |
| 23 | * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul |
| 24 | */ |
| 25 | |
| 26 | |
| 27 | #include "cron.h" |
| 28 | #include <pwd.h> |
| 29 | #if defined(BSD) |
| 30 | # include <sys/file.h> |
| 31 | # include <sys/dir.h> |
| 32 | #endif |
| 33 | #if defined(ATT) |
| 34 | # include <sys/file.h> |
| 35 | # include <ndir.h> |
| 36 | # include <fcntl.h> |
| 37 | #endif |
| 38 | |
| 39 | |
| 40 | extern void perror(), exit(); |
| 41 | |
| 42 | |
| 43 | void |
| 44 | load_database(old_db) |
| 45 | cron_db *old_db; |
| 46 | { |
| 47 | extern void link_user(), unlink_user(), free_user(); |
| 48 | extern user *load_user(), *find_user(); |
| 49 | extern char *env_get(); |
| 50 | |
| 51 | static DIR *dir = NULL; |
| 52 | |
| 53 | struct stat statbuf; |
| 54 | struct direct *dp; |
| 55 | cron_db new_db; |
| 56 | user *u; |
| 57 | |
| 58 | Debug(DLOAD, ("[%d] load_database()\n", getpid())) |
| 59 | |
| 60 | /* before we start loading any data, do a stat on SPOOL_DIR |
| 61 | * so that if anything changes as of this moment (i.e., before we've |
| 62 | * cached any of the database), we'll see the changes next time. |
| 63 | */ |
| 64 | if (stat(SPOOL_DIR, &statbuf) < OK) |
| 65 | { |
| 66 | log_it("CROND", getpid(), "STAT FAILED", SPOOL_DIR); |
| 67 | (void) exit(ERROR_EXIT); |
| 68 | } |
| 69 | |
| 70 | /* if spooldir's mtime has not changed, we don't need to fiddle with |
| 71 | * the database. Note that if /etc/passwd changes (like, someone's |
| 72 | * UID/GID/HOME/SHELL, we won't see it. Maybe we should |
| 73 | * keep an mtime for the passwd file? HINT |
| 74 | * |
| 75 | * Note that old_db->mtime is initialized to 0 in main(), and |
| 76 | * so is guaranteed to be different than the stat() mtime the first |
| 77 | * time this function is called. |
| 78 | */ |
| 79 | if (old_db->mtime == statbuf.st_mtime) |
| 80 | { |
| 81 | Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n", |
| 82 | getpid())) |
| 83 | return; |
| 84 | } |
| 85 | |
| 86 | /* make sure the dir is open. only happens the first time, since |
| 87 | * the DIR is static and we don't close it. Rewind the dir. |
| 88 | */ |
| 89 | if (dir == NULL) |
| 90 | { |
| 91 | if (!(dir = opendir(SPOOL_DIR))) |
| 92 | { |
| 93 | log_it("CROND", getpid(), "OPENDIR FAILED", SPOOL_DIR); |
| 94 | (void) exit(ERROR_EXIT); |
| 95 | } |
| 96 | } |
| 97 | (void) rewinddir(dir); |
| 98 | |
| 99 | /* something's different. make a new database, moving unchanged |
| 100 | * elements from the old database, reloading elements that have |
| 101 | * actually changed. Whatever is left in the old database when |
| 102 | * we're done is chaff -- crontabs that disappeared. |
| 103 | */ |
| 104 | new_db.mtime = statbuf.st_mtime; |
| 105 | new_db.head = new_db.tail = NULL; |
| 106 | |
| 107 | while (NULL != (dp = readdir(dir))) |
| 108 | { |
| 109 | extern struct passwd *getpwnam(); |
| 110 | struct passwd *pw; |
| 111 | int crontab_fd; |
| 112 | char fname[MAXNAMLEN+1], |
| 113 | tabname[MAXNAMLEN+1]; |
| 114 | |
| 115 | (void) strncpy(fname, dp->d_name, (int) dp->d_namlen); |
| 116 | fname[dp->d_namlen] = '\0'; |
| 117 | |
| 118 | /* avoid file names beginning with ".". this is good |
| 119 | * because we would otherwise waste two guaranteed calls |
| 120 | * to getpwnam() for . and .., and also because user names |
| 121 | * starting with a period are just too nasty to consider. |
| 122 | */ |
| 123 | if (fname[0] == '.') |
| 124 | goto next_crontab; |
| 125 | |
| 126 | if (NULL == (pw = getpwnam(fname))) |
| 127 | { |
| 128 | /* file doesn't have a user in passwd file. |
| 129 | */ |
| 130 | log_it(fname, getpid(), "ORPHAN", "no passwd entry"); |
| 131 | goto next_crontab; |
| 132 | } |
| 133 | |
| 134 | sprintf(tabname, CRON_TAB(fname)); |
| 135 | if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK) |
| 136 | { |
| 137 | /* crontab not accessible? |
| 138 | */ |
| 139 | log_it(fname, getpid(), "CAN'T OPEN", tabname); |
| 140 | goto next_crontab; |
| 141 | } |
| 142 | |
| 143 | if (fstat(crontab_fd, &statbuf) < OK) |
| 144 | { |
| 145 | log_it(fname, getpid(), "FSTAT FAILED", tabname); |
| 146 | goto next_crontab; |
| 147 | } |
| 148 | |
| 149 | Debug(DLOAD, ("\t%s:", fname)) |
| 150 | u = find_user(old_db, fname); |
| 151 | if (u != NULL) |
| 152 | { |
| 153 | /* if crontab has not changed since we last read it |
| 154 | * in, then we can just use our existing entry. |
| 155 | * note that we do not check for changes in the |
| 156 | * passwd entry (uid, home dir, etc). HINT |
| 157 | */ |
| 158 | if (u->mtime == statbuf.st_mtime) |
| 159 | { |
| 160 | Debug(DLOAD, (" [no change, using old data]")) |
| 161 | unlink_user(old_db, u); |
| 162 | link_user(&new_db, u); |
| 163 | goto next_crontab; |
| 164 | } |
| 165 | |
| 166 | /* before we fall through to the code that will reload |
| 167 | * the user, let's deallocate and unlink the user in |
| 168 | * the old database. This is more a point of memory |
| 169 | * efficiency than anything else, since all leftover |
| 170 | * users will be deleted from the old database when |
| 171 | * we finish with the crontab... |
| 172 | */ |
| 173 | Debug(DLOAD, (" [delete old data]")) |
| 174 | unlink_user(old_db, u); |
| 175 | free_user(u); |
| 176 | } |
| 177 | u = load_user( |
| 178 | crontab_fd, |
| 179 | pw->pw_name, |
| 180 | pw->pw_uid, |
| 181 | pw->pw_gid, |
| 182 | pw->pw_dir, |
| 183 | pw->pw_shell |
| 184 | ); |
| 185 | if (u != NULL) |
| 186 | { |
| 187 | u->mtime = statbuf.st_mtime; |
| 188 | link_user(&new_db, u); |
| 189 | } |
| 190 | next_crontab: |
| 191 | if (crontab_fd >= OK) { |
| 192 | Debug(DLOAD, (" [done]\n")) |
| 193 | close(crontab_fd); |
| 194 | } |
| 195 | } |
| 196 | /* if we don't do this, then when our children eventually call |
| 197 | * getpwnam() in do_command.c's child_process to verify MAILTO=, |
| 198 | * they will screw us up (and v-v). |
| 199 | * |
| 200 | * (this was lots of fun to find...) |
| 201 | */ |
| 202 | endpwent(); |
| 203 | |
| 204 | /* whatever's left in the old database is now junk. |
| 205 | */ |
| 206 | Debug(DLOAD, ("unlinking old database:\n")) |
| 207 | for (u = old_db->head; u != NULL; u = u->next) |
| 208 | { |
| 209 | Debug(DLOAD, ("\t%s\n", env_get(USERENV, u->envp))) |
| 210 | unlink_user(old_db, u); |
| 211 | free_user(u); |
| 212 | } |
| 213 | |
| 214 | /* overwrite the database control block with the new one. |
| 215 | */ |
| 216 | Debug(DLOAD, ("installing new database\n")) |
| 217 | #if defined(BSD) |
| 218 | /* BSD has structure assignments */ |
| 219 | *old_db = new_db; |
| 220 | #endif |
| 221 | #if defined(ATT) |
| 222 | /* ATT, well, I don't know. Use memcpy(). */ |
| 223 | memcpy(old_db, &new_db, sizeof(cron_db)); |
| 224 | #endif |
| 225 | Debug(DLOAD, ("load_database is done\n")) |
| 226 | } |
| 227 | |
| 228 | |
| 229 | void |
| 230 | link_user(db, u) |
| 231 | cron_db *db; |
| 232 | user *u; |
| 233 | { |
| 234 | if (db->head == NULL) |
| 235 | db->head = u; |
| 236 | if (db->tail) |
| 237 | db->tail->next = u; |
| 238 | u->prev = db->tail; |
| 239 | u->next = NULL; |
| 240 | db->tail = u; |
| 241 | } |
| 242 | |
| 243 | |
| 244 | void |
| 245 | unlink_user(db, u) |
| 246 | cron_db *db; |
| 247 | user *u; |
| 248 | { |
| 249 | if (u->prev == NULL) |
| 250 | db->head = u->next; |
| 251 | else |
| 252 | u->prev->next = u->next; |
| 253 | |
| 254 | if (u->next == NULL) |
| 255 | db->tail = u->prev; |
| 256 | else |
| 257 | u->next->prev = u->prev; |
| 258 | } |
| 259 | |
| 260 | |
| 261 | user * |
| 262 | find_user(db, name) |
| 263 | cron_db *db; |
| 264 | char *name; |
| 265 | { |
| 266 | char *env_get(); |
| 267 | user *u; |
| 268 | |
| 269 | for (u = db->head; u != NULL; u = u->next) |
| 270 | if (!strcmp(env_get(USERENV, u->envp), name)) |
| 271 | break; |
| 272 | return u; |
| 273 | } |