12311Sjkh/* Copyright 1988,1990,1993,1994 by Paul Vixie 22311Sjkh * All rights reserved 32311Sjkh * 42311Sjkh * Distribute freely, except: don't remove my name from the source or 52311Sjkh * documentation (don't take credit for my work), mark your changes (don't 62311Sjkh * get me blamed for your possible bugs), don't alter or remove this 72311Sjkh * notice. May be sold if buildable source is provided to buyer. No 82311Sjkh * warrantee of any kind, express or implied, is included with this 92311Sjkh * software; use at your own risk, responsibility for damages (if any) to 102311Sjkh * anyone resulting from the use of this software rests entirely with the 112311Sjkh * user. 122311Sjkh * 132311Sjkh * Send bug reports, bug fixes, enhancements, requests, flames, etc., and 142311Sjkh * I'll try to keep a version up to date. I can be reached as follows: 152311Sjkh * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul 162311Sjkh */ 172311Sjkh 182311Sjkh#if !defined(lint) && !defined(LINT) 1929452Scharnierstatic const char rcsid[] = 2050479Speter "$FreeBSD$"; 212311Sjkh#endif 222311Sjkh 232311Sjkh/* vix 26jan87 [RCS has the log] 242311Sjkh */ 252311Sjkh 262311Sjkh 272311Sjkh#include "cron.h" 282311Sjkh#include <fcntl.h> 292311Sjkh#include <sys/stat.h> 302311Sjkh#include <sys/file.h> 312311Sjkh 322311Sjkh 332311Sjkh#define TMAX(a,b) ((a)>(b)?(a):(b)) 342311Sjkh 352311Sjkh 36173412Skevlostatic void process_crontab(char *, char *, char *, 372311Sjkh struct stat *, 38173412Skevlo cron_db *, cron_db *); 392311Sjkh 402311Sjkh 412311Sjkhvoid 422311Sjkhload_database(old_db) 432311Sjkh cron_db *old_db; 442311Sjkh{ 452311Sjkh DIR *dir; 462311Sjkh struct stat statbuf; 472311Sjkh struct stat syscron_stat; 482311Sjkh DIR_T *dp; 492311Sjkh cron_db new_db; 502311Sjkh user *u, *nu; 512311Sjkh 522311Sjkh Debug(DLOAD, ("[%d] load_database()\n", getpid())) 532311Sjkh 542311Sjkh /* before we start loading any data, do a stat on SPOOL_DIR 552311Sjkh * so that if anything changes as of this moment (i.e., before we've 562311Sjkh * cached any of the database), we'll see the changes next time. 572311Sjkh */ 582311Sjkh if (stat(SPOOL_DIR, &statbuf) < OK) { 592311Sjkh log_it("CRON", getpid(), "STAT FAILED", SPOOL_DIR); 602311Sjkh (void) exit(ERROR_EXIT); 612311Sjkh } 622311Sjkh 632311Sjkh /* track system crontab file 642311Sjkh */ 652311Sjkh if (stat(SYSCRONTAB, &syscron_stat) < OK) 662311Sjkh syscron_stat.st_mtime = 0; 672311Sjkh 682311Sjkh /* if spooldir's mtime has not changed, we don't need to fiddle with 692311Sjkh * the database. 702311Sjkh * 712311Sjkh * Note that old_db->mtime is initialized to 0 in main(), and 722311Sjkh * so is guaranteed to be different than the stat() mtime the first 732311Sjkh * time this function is called. 742311Sjkh */ 752311Sjkh if (old_db->mtime == TMAX(statbuf.st_mtime, syscron_stat.st_mtime)) { 762311Sjkh Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n", 772311Sjkh getpid())) 782311Sjkh return; 792311Sjkh } 802311Sjkh 812311Sjkh /* something's different. make a new database, moving unchanged 822311Sjkh * elements from the old database, reloading elements that have 832311Sjkh * actually changed. Whatever is left in the old database when 842311Sjkh * we're done is chaff -- crontabs that disappeared. 852311Sjkh */ 862311Sjkh new_db.mtime = TMAX(statbuf.st_mtime, syscron_stat.st_mtime); 872311Sjkh new_db.head = new_db.tail = NULL; 882311Sjkh 892311Sjkh if (syscron_stat.st_mtime) { 90170890Syar process_crontab("root", SYS_NAME, 912311Sjkh SYSCRONTAB, &syscron_stat, 922311Sjkh &new_db, old_db); 932311Sjkh } 942311Sjkh 952311Sjkh /* we used to keep this dir open all the time, for the sake of 962311Sjkh * efficiency. however, we need to close it in every fork, and 972311Sjkh * we fork a lot more often than the mtime of the dir changes. 982311Sjkh */ 992311Sjkh if (!(dir = opendir(SPOOL_DIR))) { 1002311Sjkh log_it("CRON", getpid(), "OPENDIR FAILED", SPOOL_DIR); 1012311Sjkh (void) exit(ERROR_EXIT); 1022311Sjkh } 1032311Sjkh 1042311Sjkh while (NULL != (dp = readdir(dir))) { 1052311Sjkh char fname[MAXNAMLEN+1], 1062311Sjkh tabname[MAXNAMLEN+1]; 1072311Sjkh 1082311Sjkh /* avoid file names beginning with ".". this is good 1092311Sjkh * because we would otherwise waste two guaranteed calls 1102311Sjkh * to getpwnam() for . and .., and also because user names 1112311Sjkh * starting with a period are just too nasty to consider. 1122311Sjkh */ 1132311Sjkh if (dp->d_name[0] == '.') 1142311Sjkh continue; 1152311Sjkh 11620573Spst (void) strncpy(fname, dp->d_name, sizeof(fname)); 11720573Spst fname[sizeof(fname)-1] = '\0'; 11820573Spst (void) snprintf(tabname, sizeof tabname, CRON_TAB(fname)); 1192311Sjkh 1202311Sjkh process_crontab(fname, fname, tabname, 1212311Sjkh &statbuf, &new_db, old_db); 1222311Sjkh } 1232311Sjkh closedir(dir); 1242311Sjkh 1252311Sjkh /* if we don't do this, then when our children eventually call 1262311Sjkh * getpwnam() in do_command.c's child_process to verify MAILTO=, 1272311Sjkh * they will screw us up (and v-v). 1282311Sjkh */ 1292311Sjkh endpwent(); 1302311Sjkh 1312311Sjkh /* whatever's left in the old database is now junk. 1322311Sjkh */ 1332311Sjkh Debug(DLOAD, ("unlinking old database:\n")) 1342311Sjkh for (u = old_db->head; u != NULL; u = nu) { 1352311Sjkh Debug(DLOAD, ("\t%s\n", u->name)) 1362311Sjkh nu = u->next; 1372311Sjkh unlink_user(old_db, u); 1382311Sjkh free_user(u); 1392311Sjkh } 1402311Sjkh 1412311Sjkh /* overwrite the database control block with the new one. 1422311Sjkh */ 1432311Sjkh *old_db = new_db; 1442311Sjkh Debug(DLOAD, ("load_database is done\n")) 1452311Sjkh} 1462311Sjkh 1472311Sjkh 1482311Sjkhvoid 1492311Sjkhlink_user(db, u) 1502311Sjkh cron_db *db; 1512311Sjkh user *u; 1522311Sjkh{ 1532311Sjkh if (db->head == NULL) 1542311Sjkh db->head = u; 1552311Sjkh if (db->tail) 1562311Sjkh db->tail->next = u; 1572311Sjkh u->prev = db->tail; 1582311Sjkh u->next = NULL; 1592311Sjkh db->tail = u; 1602311Sjkh} 1612311Sjkh 1622311Sjkh 1632311Sjkhvoid 1642311Sjkhunlink_user(db, u) 1652311Sjkh cron_db *db; 1662311Sjkh user *u; 1672311Sjkh{ 1682311Sjkh if (u->prev == NULL) 1692311Sjkh db->head = u->next; 1702311Sjkh else 1712311Sjkh u->prev->next = u->next; 1722311Sjkh 1732311Sjkh if (u->next == NULL) 1742311Sjkh db->tail = u->prev; 1752311Sjkh else 1762311Sjkh u->next->prev = u->prev; 1772311Sjkh} 1782311Sjkh 1792311Sjkh 1802311Sjkhuser * 1812311Sjkhfind_user(db, name) 1822311Sjkh cron_db *db; 1832311Sjkh char *name; 1842311Sjkh{ 1852311Sjkh char *env_get(); 1862311Sjkh user *u; 1872311Sjkh 1882311Sjkh for (u = db->head; u != NULL; u = u->next) 1892311Sjkh if (!strcmp(u->name, name)) 1902311Sjkh break; 1912311Sjkh return u; 1922311Sjkh} 1932311Sjkh 1942311Sjkh 1952311Sjkhstatic void 1962311Sjkhprocess_crontab(uname, fname, tabname, statbuf, new_db, old_db) 1972311Sjkh char *uname; 1982311Sjkh char *fname; 1992311Sjkh char *tabname; 2002311Sjkh struct stat *statbuf; 2012311Sjkh cron_db *new_db; 2022311Sjkh cron_db *old_db; 2032311Sjkh{ 2042311Sjkh struct passwd *pw = NULL; 2052311Sjkh int crontab_fd = OK - 1; 2062311Sjkh user *u; 2072311Sjkh 208170890Syar if (strcmp(fname, SYS_NAME) && !(pw = getpwnam(uname))) { 2092311Sjkh /* file doesn't have a user in passwd file. 2102311Sjkh */ 2112311Sjkh log_it(fname, getpid(), "ORPHAN", "no passwd entry"); 2122311Sjkh goto next_crontab; 2132311Sjkh } 2142311Sjkh 2152311Sjkh if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK) { 2162311Sjkh /* crontab not accessible? 2172311Sjkh */ 2182311Sjkh log_it(fname, getpid(), "CAN'T OPEN", tabname); 2192311Sjkh goto next_crontab; 2202311Sjkh } 2212311Sjkh 2222311Sjkh if (fstat(crontab_fd, statbuf) < OK) { 2232311Sjkh log_it(fname, getpid(), "FSTAT FAILED", tabname); 2242311Sjkh goto next_crontab; 2252311Sjkh } 2262311Sjkh 2272311Sjkh Debug(DLOAD, ("\t%s:", fname)) 2282311Sjkh u = find_user(old_db, fname); 2292311Sjkh if (u != NULL) { 2302311Sjkh /* if crontab has not changed since we last read it 2312311Sjkh * in, then we can just use our existing entry. 2322311Sjkh */ 2332311Sjkh if (u->mtime == statbuf->st_mtime) { 2342311Sjkh Debug(DLOAD, (" [no change, using old data]")) 2352311Sjkh unlink_user(old_db, u); 2362311Sjkh link_user(new_db, u); 2372311Sjkh goto next_crontab; 2382311Sjkh } 2392311Sjkh 2402311Sjkh /* before we fall through to the code that will reload 2412311Sjkh * the user, let's deallocate and unlink the user in 2422311Sjkh * the old database. This is more a point of memory 2432311Sjkh * efficiency than anything else, since all leftover 2442311Sjkh * users will be deleted from the old database when 2452311Sjkh * we finish with the crontab... 2462311Sjkh */ 2472311Sjkh Debug(DLOAD, (" [delete old data]")) 2482311Sjkh unlink_user(old_db, u); 2492311Sjkh free_user(u); 2502311Sjkh log_it(fname, getpid(), "RELOAD", tabname); 2512311Sjkh } 2522311Sjkh u = load_user(crontab_fd, pw, fname); 2532311Sjkh if (u != NULL) { 2542311Sjkh u->mtime = statbuf->st_mtime; 2552311Sjkh link_user(new_db, u); 2562311Sjkh } 2572311Sjkh 2582311Sjkhnext_crontab: 2592311Sjkh if (crontab_fd >= OK) { 2602311Sjkh Debug(DLOAD, (" [done]\n")) 2612311Sjkh close(crontab_fd); 2622311Sjkh } 2632311Sjkh} 264