Mercurial > notdcc
view srvrlib/db.c @ 1:9b8d79ac0dc3
blindly adding debian patch for old version
author | Peter Gervai <grin@grin.hu> |
---|---|
date | Tue, 10 Mar 2009 14:25:08 +0100 |
parents | c7f6b056b673 |
children |
line wrap: on
line source
/* Distributed Checksum Clearinghouse * * server database functions * * Copyright (c) 2008 by Rhyolite Software, LLC * * This agreement is not applicable to any entity which sells anti-spam * solutions to others or provides an anti-spam solution as part of a * security solution sold to other entities, or to a private network * which employs the DCC or uses data provided by operation of the DCC * but does not provide corresponding data to other users. * * Permission to use, copy, modify, and distribute this software without * changes for any purpose with or without fee is hereby granted, provided * that the above copyright notice and this permission notice appear in all * copies and any distributed versions or copies are either unchanged * or not called anything similar to "DCC" or "Distributed Checksum * Clearinghouse". * * Parties not eligible to receive a license under this agreement can * obtain a commercial license to use DCC by contacting Rhyolite Software * at sales@rhyolite.com. * * A commercial license would be for Distributed Checksum and Reputation * Clearinghouse software. That software includes additional features. This * free license for Distributed ChecksumClearinghouse Software does not in any * way grant permision to use Distributed Checksum and Reputation Clearinghouse * software * * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. * * Rhyolite Software DCC 1.3.103-1.214 $Revision$ */ #include "srvr_defs.h" #include <syslog.h> #include <sys/resource.h> #if defined(HAVE_HW_PHYSMEM) || defined(HAVE_BOOTTIME) #include <sys/sysctl.h> #endif #ifdef HAVE_PSTAT_GETSTATIC /* HP-UX */ #include <sys/pstat.h> #endif DB_STATS db_stats; DB_STATES db_sts; DCC_PATH db_path_buf; int db_fd = -1; DCC_PATH db_nm; int db_hash_fd = -1; DCC_PATH db_hash_nm; struct timeval db_locked; /* 1=database not locked */ struct timeval db_time; int db_debug; u_char grey_on; static u_char db_use_write; /* 0=no 1=if RAM big enough 2=always */ static u_char db_dirty; static u_char db_rdonly; int db_failed_line; /* bad happened at this line # */ const char *db_failed_file; /* in this file */ static u_char db_invalidate; /* do not write to the files */ /* Without mmap(MAP_NOSYNC) as on Solaris or a good msync() as on BSD/OS, * we must rely on the kernel's update/syncer/bufdaemon/etc. So in this * case just fondle the mmap()'ed pages and hope things work out. * * With a msync() and with mmap(MAP_NOSYNC), use MAP_NOSYNC if we can because * some systems flush too quickly while others such as FreeBSD 6.1 stall * for seconds while thinking about flushing the database. * But with mmap(MAP_NOSYNC) we leave large amounts of data in RAM that take * too long time to be pushed to the disk when the system is shutting down. * So * - hit only those chunks of memory with real data or changes to data * with msync(). Trust dbclean to rebuild everything else at need. * * - when it seems the system is being shut down, delete the hash table * and let it be rebuilt when the system is rebooted. When the * hash table is rebuilt, "obsolete" markings in the data file that * might have been lost will be remade. * * A third case involves dccd -F. It requires that all changes be pushed to * the disk whenever dccd unlocks the database so that dbclean can see changes * dccd makes. It also requires that dbclean write all of its changes so * that dccd will find them when it reopens the database. */ #if !defined(MAP_NOSYNC) || defined(HAVE_OLD_MSYNC) || !defined(HAVE_BOOTTIME) #undef USE_MAP_NOSYNC #else #define USE_MAP_NOSYNC #endif static u_char db_not_synced; /* database unsynchronized with disk */ #define DCC_MADV_WILLNEED(p) 0 #ifdef MADV_WILLNEED #undef DCC_MADV_WILLNEED #define DCC_MADV_WILLNEED(p) madvise(p, db_pagesize, MADV_WILLNEED) #endif #ifdef POSIX_MADV_WILLNEED #undef DCC_MADV_WILLNEED #define DCC_MADV_WILLNEED(p) posix_madvise(p, db_pagesize, POSIX_MADV_WILLNEED) #endif #define DCC_MADV_RANDOM(p) 0 #ifdef MADV_RANDOM #undef DCC_MADV_RANDOM #define DCC_MADV_RANDOM(p) madvise(p, db_pagesize, MADV_RANDOM) #endif #ifdef POSIX_MADV_RANDOM #undef DCC_MADV_RANDOM #define DCC_MADV_RANDOM(p) posix_madvise(p, db_pagesize, POSIX_MADV_RANDOM) #endif #define DCC_MADV_DONTNEED(p) 0 /* The Linux people claim that it is just fine that their notion of * MADV_DONTNEED implies discarding changes to data. Worse, some versions of * Linux/GNU libc define POSIX_MADV_DONTNEED as the data-corrupting Linux * MADV_DONTNEED. This seems to be because they cannot admit their mistake of * not distinguishing between the functions of MADV_FREE and MADV_DONTNEED and * their misreading of other systems' documentation for MADV_DONTNEED */ #ifndef linux #ifdef MADV_DONTNEED #undef DCC_MADV_DONTNEED #define DCC_MADV_DONTNEED(p) madvise(p, db_pagesize, MADV_DONTNEED) #endif #ifdef POSIX_MADV_DONTNEED #undef DCC_MADV_DONTNEED #define DCC_MADV_DONTNEED(p) posix_madvise(p, db_pagesize, POSIX_MADV_DONTNEED) #endif #endif /* !linux */ #define DCC_MADV_FREE(p) 0 #ifdef MADV_FREE #undef DCC_MADV_FREE #define DCC_MADV_FREE(p) madvise(p, db_pagesize, MADV_FREE) #endif #ifdef POSIX_MADV_FREE #undef DCC_MADV_FREE #define DCC_MADV_FREE(p) posix_madvise(p, db_pagesize, POSIX_MADV_FREE) #endif u_char db_minimum_map; /* this is dccd & dbclean is running */ int db_buf_total; /* total # of db buffers */ DB_PTR db_max_rss; /* maximum db resident set size */ DB_PTR db_max_byte; /* maximum db bytes in both files */ static u_int system_pagesize; /* kernel page size */ static DB_BUF db_bufs[DB_BUF_MAX]; /* control mmap()'ed blocks */ static DB_BUF *buf_oldest, *buf_newest; #define DB_HASH_TOTAL DB_BUF_MAX static DB_BUF *db_buf_hash[DB_HASH_TOTAL]; /* fancy 16-bit multiplicative hash assumes multiplication needs 1 cycle * and so the hash is faster than dealing with a collision */ #define DB_BUF_HASH(pnum,t) (&db_buf_hash[((((pnum)*(t)*0x9ccf) & 0xffff) \ * DB_BUF_MAX) >> 16]) time_t db_need_flush_secs; static time_t db_urgent_need_flush_secs; const DB_VERSION_BUF db_version_buf = DB_VERSION_STR; DB_PARMS db_parms; static DB_PARMS db_parms_stored; DCC_TGTS db_tholds[DCC_DIM_CKS]; u_int db_pagesize; /* size of 1 mmap()'ed buffer */ static u_int db_pagesize_part; DB_HOFF db_hash_fsize; /* size of hash table file */ static u_int hash_clear_pg_num; DB_HADDR db_hash_len; /* # of hash table entries */ DB_HADDR db_hash_divisor; /* modulus */ DB_HADDR db_hash_used; /* # of hash table entries in use */ u_int db_hash_page_len; /* # of HASH_ENTRY's per buffer */ DB_HADDR db_max_hash_entries = 0; /* after db_buf_init()*/ DB_PTR db_fsize; /* size of database file */ DB_PTR db_csize; /* size of database contents in bytes */ static DB_PTR db_csize_stored_hash; /* DB size stored in hash file */ static DB_HADDR db_hash_used_stored_hash; u_int db_page_max; /* only padding after this in DB buf */ static DB_PTR db_window_size; /* size of mmap() window */ char db_window_size_str[128]; static char db_physmem_str[80]; static const u_char dcc_ck_fuzziness[DCC_DIM_CKS] = { 0, /* DCC_CK_INVALID */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_IP */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_ENV_FROM */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_FROM */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_SUB */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_MESSAGE_ID */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_RECEIVED */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_BODY */ DCC_CK_FUZ_LVL1, /* DCC_CK_FUZ1 */ DCC_CK_FUZ_LVL2, /* DCC_CK_FUZ2 */ DCC_CK_FUZ_LVL_REP, /* DCC_CK_REP_TOTAL */ DCC_CK_FUZ_LVL_REP, /* DCC_CK_REP_BULK */ DCC_CK_FUZ_LVL2, /* DCC_CK_SRVR_ID */ DCC_CK_FUZ_LVL2 /* DCC_CK_ENV_TO */ }; static const u_char grey_ck_fuzziness[DCC_DIM_CKS] = { 0, /* DCC_CK_INVALID */ DCC_CK_FUZ_LVL2, /* DCC_CK_IP */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_ENV_FROM */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_FROM */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_SUB */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_MESSAGE_ID */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_RECEIVED */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_BODY */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_FUZ1 */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_FUZ2 */ DCC_CK_FUZ_LVL_NO, /* DCC_CK_GREY_MSG */ DCC_CK_FUZ_LVL1, /* DCC_CK_GREY_TRIPLE */ DCC_CK_FUZ_LVL1, /* DCC_CK_SRVR_ID */ DCC_CK_FUZ_LVL1 /* DCC_CK_ENV_TO */ }; const u_char *db_ck_fuzziness = dcc_ck_fuzziness; static u_char buf_flush(DCC_EMSG, DB_BUF *, u_char); static u_char buf_munmap(DCC_EMSG, DB_BUF *); static DB_BUF *find_buf(DCC_EMSG, DB_BUF_TYPE, DB_PG_NUM); static u_char map_hash(DCC_EMSG, DB_HADDR, DB_STATE *, u_char); static u_char map_hash_ctl(DCC_EMSG, u_char); static u_char map_db(DCC_EMSG, DB_PTR, u_int, DB_STATE *, u_char); static u_char db_set_sizes(DCC_EMSG); /* compute the least common multiple of two numbers */ static u_int lcm(u_int n, u_int m) { u_int r, x, gcd; /* first get the gcd of the two numbers */ if (n >= m) { x = n; gcd = m; } else { x = m; gcd = n; } for (;;) { r = x % gcd; if (r == 0) return n * (m / gcd); x = gcd; gcd = r; } } const char * db_ptr2str(DB_PTR val) { static int bufno; static struct { char str[16]; } bufs[4]; char *s; const char *units; if (val == 0) return "0"; s = bufs[bufno].str; bufno = (bufno+1) % DIM(bufs); if (val % (1024*1024*1024) == 0) { val /= (1024*1024*1024); units = "GB"; } else if (val % (1024*1024) == 0) { val /= (1024*1024); units = "MB"; } else if (val % 1024 == 0) { val /= 1024; units = "KB"; } else { units = ""; } if (val > 1000*1000*1000) snprintf(s, sizeof(bufs[0].str), "%d,%03d,%03d,%03d%s", (int)(val / (1000*1000*1000)), (int)(val / (1000*1000)) % 1000, (int)(val / 1000) % 1000, (int)(val % 1000), units); else if (val > 1000*1000) snprintf(s, sizeof(bufs[0].str), "%d,%03d,%03d%s", (int)(val / (1000*1000)), (int)(val / 1000) % 1000, (int)(val % 1000), units); else if (val > 1000*10) snprintf(s, sizeof(bufs[0].str), "%d,%03d%s", (int)(val / 1000), (int)(val % 1000), units); else snprintf(s, sizeof(bufs[0].str), "%d%s", (int)val, units); return s; } const char * size2str(char *buf, u_int buf_len, double num, u_char bytes_or_entries) /* 0=number 1=bytes */ { const char *units; double k; k = bytes_or_entries ? 1024.0 : 1000.0; if (num < k) { units = ""; } else if (num < k*k) { num /= k; units = "K"; } else if (num < k*k*k) { num /= k*k; units = "M"; } else { num /= k*k*k; units = "G"; } if ((int)num >= 100) snprintf(buf, buf_len, "%.0f%s", num, units); else snprintf(buf, buf_len, "%.2g%s", num, units); return buf; } void PATTRIB(5,6) db_failure(int linenum, const char *file, int ex_code, DCC_EMSG emsg, const char *p, ...) { va_list args; if (!db_failed_line) { db_failed_line = linenum; db_failed_file = file; } va_start(args, p); dcc_vpemsg(ex_code, emsg, p, args); va_end(args); } void PATTRIB(3,4) db_error_msg(int linenum, const char *file, const char *p, ...) { va_list args; if (!db_failed_line) { db_failed_line = linenum; db_failed_file = file; } va_start(args, p); dcc_verror_msg(p, args); va_end(args); } double /* hashes or bytes/second */ db_add_rate(const DB_PARMS *parms, u_char hash_or_db) /* 1=hash */ { struct timeval sn; time_t new_rate_secs; time_t total_secs; double added, cur, prev; total_secs = parms->rate_secs; if (hash_or_db) { added = parms->hash_added; cur = parms->hash_used; prev = parms->old_hash_used; } else { added = parms->db_added; cur = parms->db_csize; prev = parms->old_db_csize; } if (total_secs <= 0 || total_secs > DB_MAX_RATE_SECS || added <= 0.0) { added = 0.0; total_secs = 0; } dcc_ts2timeval(&sn, &parms->sn); new_rate_secs = parms->last_rate_sec - sn.tv_sec; if (new_rate_secs > 0 && new_rate_secs <= DB_MAX_RATE_SECS && cur > prev) { total_secs += new_rate_secs; added += cur - prev; } if (total_secs <= DB_MIN_RATE_SECS) return -1.0; return added / total_secs; } DB_NOKEEP_CKS def_nokeep_cks(void) { DCC_CK_TYPES type; DB_NOKEEP_CKS nokeep = 0; for (type = DCC_CK_TYPE_FIRST; type <= DCC_CK_TYPE_LAST; ++type) { if (DB_GLOBAL_NOKEEP(grey_on, type)) DB_SET_NOKEEP(nokeep, type); } DB_SET_NOKEEP(nokeep, DCC_CK_INVALID); DB_SET_NOKEEP(nokeep, DCC_CK_FLOD_PATH); return nokeep; } void set_db_tholds(DB_NOKEEP_CKS nokeep) { DCC_CK_TYPES type; for (type = 0; type < DIM(db_tholds); ++type) { db_tholds[type] = (DB_TEST_NOKEEP(nokeep, type) ? DCC_TGTS_INVALID : DCC_CK_IS_REP_CMN(grey_on, type) ? DCC_TGTS_INVALID : grey_on ? 1 : type == DCC_CK_SRVR_ID ? 1 : BULK_THRESHOLD); } } static const char * buf2path(const DB_BUF *b) { switch (b->buf_type) { case DB_BUF_TYPE_HASH: return db_hash_nm; case DB_BUF_TYPE_DB: return db_nm; case DB_BUF_TYPE_FREE: default: dcc_logbad(EX_SOFTWARE, "impossible buffer type for a path"); } } static int buf2fd(const DB_BUF *b) { switch (b->buf_type) { case DB_BUF_TYPE_HASH: return db_hash_fd; case DB_BUF_TYPE_DB: return db_fd; case DB_BUF_TYPE_FREE: default: dcc_logbad(EX_SOFTWARE, "impossible buffer type for fd"); } } static void rel_db_state(DB_STATE *st) { DB_BUF *b; b = st->b; if (!b) return; st->b = 0; st->d.v = 0; st->s.rptr = DB_PTR_BAD; if (--b->lock_cnt < 0) dcc_logbad(EX_SOFTWARE,"negative database buffer lock"); } void rel_db_states(void) { DB_STATE *st; for (st = &db_sts.rcd; st <= &db_sts.hash_ctl; ++st) { rel_db_state(st); } } /* release one or all unneeded buffers */ u_char /* 0=problem 1=did nothing 2=did>=1 */ db_unload(DCC_EMSG emsg, u_char some) /* 0=all, 1=only one, 2=finished */ { DB_BUF *b; u_char result; result = 1; for (b = buf_oldest; b != 0; b = b->newer) { if (b->buf_type == DB_BUF_TYPE_FREE || b->lock_cnt != 0) continue; if (some == 2 && !(b->flags & DB_BUF_FG_USE_WRITE) && 0 > DCC_MADV_DONTNEED(b->buf.v)) dcc_error_msg("madvise(DONTNEED %s,%#x): %s", buf2path(b), db_pagesize, ERROR_STR()); if (!buf_munmap(emsg, b)) { emsg = 0; result = 0; } else if (result) { result = 2; } if (some == 1) return result; } return result; } static u_char buf_write_part(DCC_EMSG emsg, DB_BUF *b, off_t offset, void *buf, int len) { int i; offset += (off_t)b->pg_num * (off_t)db_pagesize; if (offset != lseek(buf2fd(b), offset, SEEK_SET)) { db_failure(__LINE__,__FILE__, EX_IOERR, emsg, "buf_write_part lseek(%s,"OFF_HPAT"): %s", buf2path(b), offset, ERROR_STR()); return 0; } i = write(buf2fd(b), buf, len); if (i != len) { db_failure(__LINE__,__FILE__, EX_IOERR, emsg, "buf_write_part(%s,%u)=%d: %s", buf2path(b), len, i, ERROR_STR()); return 0; } return 1; } /* push part of a buffer toward the disk * this can be needed even when the file has been opened and mapped * read-only by dbclean */ static u_char buf_flush_part(DCC_EMSG emsg, DB_BUF *b, u_int part, /* DB_BUF_NUM_PARTS=buffer */ u_char async UATTRIB) { u_int flush_len; char *flush_base; DB_BUF_FM bit; bit = PART2BIT(part) & (b->flush | b->flush_urgent); if (!bit) return 1; /* Send a new buffer to disk at once. */ if (b->flags & DB_BUF_FG_EXTENSION) { DB_BUF *b1, *b0; u_char result; /* To give the file system a chance to make the hash table * contiguous, first write all preceding new buffers. * In almost all cases, there will be none. */ result = 1; do { b0 = b; for (b1 = buf_oldest; b1 != 0; b1 = b1->newer) { if (!(b1->flags & DB_BUF_FG_EXTENSION) || b1->buf_type != b0->buf_type || b1->pg_num >= b0->pg_num) continue; b0 = b1; } b0->flags &= ~DB_BUF_FG_EXTENSION; b0->flush = 0; b0->flush_urgent = 0; if (!db_invalidate && !buf_write_part(emsg, b0, 0, b0->buf.c, db_pagesize)) result = 0; } while (b0 != b); return result; } flush_base = b->ranges[part].lo; flush_len = b->ranges[part].hi - flush_base; b->flush &= ~bit; b->flush_urgent &= ~bit; if (db_invalidate) return 1; if (b->flags & DB_BUF_FG_USE_WRITE) { static char *wbuf; static u_int wbuf_len; /* In at least FreeBSD you cannot write() to the file * that underlies a mmap() region from that region */ if (wbuf_len < db_pagesize_part) { /* the page size for the current file * might be different from the old file */ if (wbuf) free(wbuf); wbuf_len = db_pagesize_part; wbuf = malloc(wbuf_len); } memcpy(wbuf, flush_base, flush_len); return buf_write_part(emsg, b, flush_base - b->buf.c, wbuf, flush_len); #ifndef HAVE_OLD_MSYNC } else if (async) { if (0 > MSYNC(flush_base, flush_len, MS_ASYNC)) { db_failure(__LINE__,__FILE__, EX_IOERR, emsg, "msync(db buffer %s,%#lx,%#x,MS_ASYNC): %s", buf2path(b), (long)flush_base, flush_len, ERROR_STR()); return 0; } #endif } else { if (0 > MSYNC(flush_base, flush_len, MS_SYNC)) { db_failure(__LINE__,__FILE__, EX_IOERR, emsg, "msync(db buffer %s,%#lx,%#x,MS_SYNC): %s", buf2path(b), (long)flush_base, flush_len, ERROR_STR()); return 0; } } return 1; } static u_char buf_flush(DCC_EMSG emsg, DB_BUF *b, u_char async) { u_int part; DB_BUF_FM bits; u_char result = 1; bits = b->flush_urgent | b->flush; for (part = 0; bits != 0 && part < DB_BUF_NUM_PARTS; ++part) { if (bits & PART2BIT(part)) { if (!buf_flush_part(emsg, b, part, async)) { emsg = 0; result = 0; } bits = b->flush_urgent | b->flush; } } return result; } /* Try to keep the data clean so that the fsync() required by Solaris * when the file is unloaded is not too expensive. * Try to flush frequently so that we don't stall as long in msync(). */ void db_flush_needed(void) { static DB_BUF *next_b = db_bufs; static u_int next_part; DB_BUF *b; u_int part, all_parts; int buf_num; u_char worked; /* send to the disk changes that cannot be recreated by dbclean */ if (db_urgent_need_flush_secs != 0 && DB_IS_TIME(db_urgent_need_flush_secs, DB_URGENT_NEED_FLUSH_SECS)) { worked = 0; for (b = buf_newest; b; b = b->older) { if (b->buf_type == DB_BUF_TYPE_FREE) continue; for (part = 0; b->flush_urgent != 0 && part < DB_BUF_NUM_PARTS; ++part) { if ((b->flush_urgent & PART2BIT(part))) { buf_flush_part(0, b, part, 1); worked = 1; } } /* Switch new data pages to mmap() * when this is not dbclean, since only dccd calls here * they are not using mmap() * they are either hash table pages or * not the last page in the file */ if ((b->flags & DB_BUF_FG_USE_WRITE) && !db_use_write && (b->buf_type != DB_BUF_TYPE_DB || (DB_PTR2PG_NUM(db_csize-1, db_pagesize) != b->pg_num))) { if (b->lock_cnt != 0) rel_db_states(); buf_munmap(0, b); } } /* Keep the clock running if we did any work. This tends to * avoid stalls caused by colliding with the FreeBSD syncer */ if (worked) { gettimeofday(&db_time, 0); db_urgent_need_flush_secs = (db_time.tv_sec + DB_URGENT_NEED_FLUSH_SECS); } else { db_urgent_need_flush_secs = 0; } } /* assume there will be nothing more to do */ db_need_flush_secs = db_urgent_need_flush_secs; #ifdef USE_MAP_NOSYNC /* if we are using mmap(MAP_NOSYNC), then there are no bits * set in any b->flush words except that of the recent * DB_BUF_FG_USE_WRITE extensions of the file. It is best to let * those blocks stay in RAM until the whole buffer is flushed and * switched to mmap above */ if (!db_use_write) return; #endif b = next_b; part = next_part; all_parts = DB_PARTS_PER_FLUSH; for (buf_num = DIM(db_bufs); buf_num >= 0; --buf_num) { if (b > LAST(db_bufs)) { part = 0; b = db_bufs; } if (!b->flush || part >= DB_BUF_NUM_PARTS || b->buf_type == DB_BUF_TYPE_FREE) { part = 0; ++b; continue; } while (part < DB_BUF_NUM_PARTS) { if (b->flush & PART2BIT(part)) { buf_flush_part(0, b, part, 1); if (--all_parts == 0) { next_part = part+1; next_b = b; db_need_flush_secs = (db_time.tv_sec + DB_NEED_FLUSH_SECS); return; } if (!b->flush) part = DB_BUF_NUM_PARTS; } ++part; } } } /* occassionally flush an unlocked data buffer for dbclean * dbclean mostly changes only the current record, so get started * writing the data to avoid stalling the system at the end. */ u_char db_flush_db(DCC_EMSG emsg UATTRIB) { #ifdef USE_MAP_NOSYNC DB_BUF *b; int limit; int pg_num; /* Gently push the new hash table to disk. * The disk image will never be accurate. This only allocates space. * Do not do this for systems that lack mmap(NOSYNC) such as Linux * that thrash themselves as the hash table is being built. A * long pause when the database is closed is not as bad as spending * hours building the hash table. */ while (hash_clear_pg_num < db_hash_fsize/db_hash_page_len) { pg_num = hash_clear_pg_num++; for (b = buf_oldest; b != 0; b = b->newer) { if (b->pg_num != pg_num || b->buf_type != DB_BUF_TYPE_HASH) continue; if (!(b->flags & DB_BUF_FG_EXTENSION)) break; if (b->lock_cnt != 0) rel_db_states(); return buf_munmap(emsg, b); } /* look for the next page if this one has already * been flushed */ } /* flush some ordinary buffers */ limit = 2; for (b = buf_oldest; b != 0; b = b->newer) { if (b->flush_urgent == 0 || b->buf_type == DB_BUF_TYPE_FREE || b->lock_cnt != 0) continue; if (!buf_flush(emsg, b, 1)) return 0; if (--limit <= 0) return 1; } #endif return 1; } /* mark part of a buffer dirty * "Urgent" changes are flushed by a timer. Ordinary changes * are often ignored and expected to be rebuilt if the system crashes. * That the hash table is deleted as the system is shut down while the * database must be flushed from the system's buffer cache is a reason * to keep the disk image of the database good. */ void db_set_flush(DB_STATE *st, u_char urgent, u_int len) { DB_BUF *b; DB_BUF_FM bit, new_bits, old_bits; char *buf_base, *part_end, *start, *end; u_int part, i; /* nothing to do if the kernel is handling it * or if we are letting this change be reconstructed by dbclean */ b = st->b; if (!(b->flags & DB_BUF_FG_USE_WRITE)) { #ifdef USE_MAP_NOSYNC if (!urgent) #endif return; } start = st->d.c; buf_base = b->buf.c; /* Increase to even pages in the hope that the file system might * be able to page-flip. This might at least avoid reading into the * buffer cache to honor a write(). Besides, Solaris' msync() handles * only even pages. */ i = (start - buf_base) % system_pagesize; start -= i; len += i; len = ((len + system_pagesize-1) / system_pagesize) * system_pagesize; end = start + len; if (end > buf_base+db_pagesize) dcc_logbad(EX_SOFTWARE, "inflated dirty buffer size"); part = (start - buf_base) / db_pagesize_part; part_end = buf_base + part * db_pagesize_part; bit = PART2BIT(part); new_bits = 0; old_bits = b->flush | b->flush_urgent; do { part_end += db_pagesize_part; if (part_end > end) part_end = end; if (!(old_bits & bit)) { b->ranges[part].lo = start; b->ranges[part].hi = part_end; } else { if (b->ranges[part].lo > start) b->ranges[part].lo = start; if (b->ranges[part].hi < part_end) b->ranges[part].hi = part_end; } new_bits |= bit; start = part_end; bit <<= 1; ++part; } while (part_end < end); if (urgent) { b->flush_urgent |= new_bits; if (!db_urgent_need_flush_secs) { db_urgent_need_flush_secs = (db_time.tv_sec + DB_URGENT_NEED_FLUSH_SECS); if (db_need_flush_secs == 0) db_need_flush_secs = db_urgent_need_flush_secs; } } else { b->flush |= new_bits; if (db_need_flush_secs == 0 || db_need_flush_secs > db_time.tv_sec+DB_NEED_FLUSH_SECS) db_need_flush_secs = db_time.tv_sec+DB_NEED_FLUSH_SECS; } } /* Shut down the database, including flushing and releasing all * mmap()'ed buffers * Do nothing to the files for mode=-1 because the file is new and garbage * or the caller is a fork of the server shedding memory. */ u_char db_close(int mode) /* -1=invalidate, 0=dirty, 1=clean */ { u_char result; if (mode >= 0) { /* flush the data and then release and flush the dirty flags */ result = make_clean(mode == 0 ? 0 : 1); if (!db_unload(0, 0)) result = 0; } else { db_invalidate = 1; rel_db_states(); result = (db_unload(0, 0) > 0); } /* Close the hash table first because the server is often * waiting for the lock on the main file held by dbclean. * Destroy the hash table if it is bad */ if (db_hash_fd >= 0) { if (0 > close(db_hash_fd)) { dcc_pemsg(EX_IOERR, 0, "close(%s): %s", db_hash_nm, ERROR_STR()); result = 0; } db_hash_fd = -1; } if (db_fd >= 0) { if (0 > close(db_fd)) { dcc_pemsg(EX_IOERR, 0, "close(%s): %s", db_nm, ERROR_STR()); result = 0; } db_fd = -1; } db_locked.tv_sec = 0; return result; } /* Delete the hash table if the system is being rebooted and we * don't trust the file system to get all of the hash table. This might * make system shut down faster */ void db_stop(void) { if (db_hash_fd < 0 || !DB_IS_LOCKED() || !db_not_synced || db_hash_nm[0] == '\0') return; if (0 > unlink(db_hash_nm) && errno != ENOENT) dcc_error_msg("unlink(%s): %s", db_hash_nm, ERROR_STR()); } /* see if (another) instance of dbclean is already running */ static int dbclean_lock_fd = -1; static DCC_PATH dbclean_lock_nm; u_char /* 1=no (other) dbclean */ lock_dbclean(DCC_EMSG emsg, const char *cur_db_nm) { char pid[32]; int i; fnm2rel_good(dbclean_lock_nm, cur_db_nm, DB_LOCK_SUFFIX); dbclean_lock_fd = dcc_lock_open(emsg, dbclean_lock_nm, O_RDWR|O_CREAT, DCC_LOCK_OPEN_NOWAIT, DCC_LOCK_ALL_FILE, 0); if (dbclean_lock_fd < 0) return 0; i = 1+snprintf(pid, sizeof(pid), "%ld\n", (long)getpid()); if (i != write(dbclean_lock_fd, pid, i)) dcc_logbad(EX_IOERR, "write(%s, pid): %s", dbclean_lock_nm, ERROR_STR()); /* Let anyone write in it in case we are running as root * and get interrupted by a crash or gdb. A stray, stale * private lock file cannot be locked */ chmod(dbclean_lock_nm, 0666); return 1; } void unlock_dbclean(void) { if (dbclean_lock_fd >= 0) { if (0 > unlink(dbclean_lock_nm)) dcc_error_msg("unlink(%s): %s", dbclean_lock_nm, ERROR_STR()); close(dbclean_lock_fd); dbclean_lock_fd = -1; } } /* This locking does only multiple-readers/single-writer */ int /* -1=failed, 0=was not locked, 1=was */ db_lock(void) { struct stat sb; if (DB_IS_LOCKED()) return 1; if (!dcc_exlock_fd(0, db_fd, DCC_LOCK_ALL_FILE, 15*60, "", db_nm)) return -1; if (0 > fstat(db_fd, &sb)) { db_failure(__LINE__,__FILE__, EX_IOERR, 0, "stat(%s): %s", db_nm, ERROR_STR()); return -1; } if (db_fsize != (DB_HOFF)sb.st_size) { if (db_fsize > (DB_HOFF)sb.st_size || !db_rdonly) { db_failure(__LINE__,__FILE__, EX_IOERR, 0, "%s size changed from "OFF_HPAT " to "OFF_HPAT, db_nm, db_fsize, sb.st_size); return -1; } db_fsize = sb.st_size; } db_locked = db_time; return 0; } /* flush buffers to make the disk reasonably correct but not perfect * This does not compensate for a lack of coherent mmap() in the system. * * It leaves the disk only as accurate as implied by db_not_synced. * This flushes buffers marked either urgent and ordinarily dirty. * If db_not_synced is set, then non-urgent dirty bits are not set. */ static u_char make_clean_flush(void) { DB_BUF *b; u_char result; result = 1; for (b = buf_oldest; b != 0; b = b->newer) { if (b->buf_type == DB_BUF_TYPE_FREE) continue; if (!buf_flush(0, b, 0)) result = 0; } return result; } /* push all of our database changes to the disk and try to clear the dirty bit * do not necessarily unmap anything */ u_char make_clean(u_char clean) /* 0=leave hash marked dirty, */ { /* 1=marked clean, 2=fsync */ u_char need_db_fsync, result; struct stat sb; rel_db_states(); result = 1; /* quit if we are giving up */ if (db_invalidate) return result; if (db_failed_line) clean = 0; if (!make_clean_flush()) { clean = 0; result = 0; } /* simply unlock all of the buffers if they are clean * and do not need to (or cannot) be synchronized with fsync() */ if (!db_dirty && (clean < 2 /* not asked to synchronize */ || db_rdonly /* cannot be synchronized */ || !db_not_synced)) /* does not need to be synchronized */ return result; need_db_fsync = (clean == 2); /* Send the meta-data to disk so that other processes * such as dbclean can find the new length of the file * on Solaris. Otherwise the file looks broken because * its contained data length can be larger than its * inode size on Solaris. */ if (!need_db_fsync && clean) { if (0 > fstat(db_fd, &sb)) { dcc_error_msg("make_clean fstat(%s): %s", db_nm, ERROR_STR()); need_db_fsync = 1; } else if (db_fsize != (DB_HOFF)sb.st_size) { if (db_debug) quiet_trace_msg("need fsync() because db_fsize=" OFF_HPAT" but stat="OFF_HPAT, db_fsize, sb.st_size); need_db_fsync = 1; } } if (need_db_fsync && 0 > fsync(db_fd)) { dcc_error_msg("make_clean fsync(%s): %s", db_nm, ERROR_STR()); clean = 0; result = 0; } if (clean && !map_hash_ctl(0, 0)) { clean = 0; result = 0; } if (clean == 2) { if (0 > fsync(db_hash_fd)) { dcc_error_msg("make_clean fsync(%s): %s", db_hash_nm, ERROR_STR()); clean = 0; result = 0; } else { db_not_synced = 0; db_sts.hash_ctl.d.vals->s.flags &= ~HASH_CTL_FG_NOSYNC; SET_FLUSH_HCTL(1); if (!make_clean_flush()) { clean = 0; result = 0; } } } /* Clean the dirty flag in the hash table. * With luck, this will reach the disk after everything else. */ if (clean && !(db_sts.hash_ctl.d.vals->s.flags & HASH_CTL_FG_CLEAN)) { db_sts.hash_ctl.d.vals->s.flags |= HASH_CTL_FG_CLEAN; SET_FLUSH_HCTL(0); } /* finally flush the flag in the hash table */ rel_db_states(); if (!make_clean_flush()) result = 0; if (clean) db_dirty = 0; return result; } /* mark the hash file and so the database dirty */ static u_char db_make_dirty(DCC_EMSG emsg) { if (db_dirty) return 1; if (!DB_IS_LOCKED()) { dcc_logbad(EX_SOFTWARE, "dirtying unlocked database"); return 0; } if (db_rdonly) dcc_logbad(EX_SOFTWARE, "dirtying read-only database"); if (!map_hash_ctl(emsg, 0)) return 0; db_sts.hash_ctl.d.vals->s.flags &= ~HASH_CTL_FG_CLEAN; #ifdef USE_MAP_NOSYNC if (!(db_sts.hash_ctl.d.vals->s.flags & HASH_CTL_FG_NOSYNC)) { db_sts.hash_ctl.d.vals->s.synced = time(0); db_sts.hash_ctl.d.vals->s.flags |= HASH_CTL_FG_NOSYNC; } db_not_synced = 1; #endif SET_FLUSH_HCTL(1); if (!buf_flush_part(emsg, db_sts.hash_ctl.b, 0, 0)) return 0; db_dirty = 1; return 1; } /* (start to) unlock the database */ u_char /* 0=failed, 1=at least started */ db_unlock(void) { DB_BUF *b; int result; if (!DB_IS_LOCKED()) return 1; /* Clear the dirty bit in the database because we may not * be able to lock the database later to clear the dirty bit. * Dbclean needs to see the dirty bit clear. */ result = make_clean(1); /* Release DB_BUF_FG_USE_WRITE buffers because they are not consistent * among processes * Release everything if dccd wants stay out of RAM in favor * of dbclean */ for (b = buf_oldest; b != 0; b = b->newer) { if (b->buf_type == DB_BUF_TYPE_FREE) continue; if (db_minimum_map || (b->flags & DB_BUF_FG_USE_WRITE)) buf_munmap(0, b); } if (!dcc_unlock_fd(0, db_fd, DCC_LOCK_ALL_FILE, "", db_nm)) result = 0; db_locked.tv_sec = 0; return result; } static const char * mbyte2str(DB_PTR val) { return db_ptr2str(val*1024*1024); } #if defined(RLIMIT_AS) || defined(RLIMIT_RSS) || defined(RLIMIT_FSIZE) static DB_PTR use_rlimit(int resource, const char *rlimit_nm, DB_PTR cur_val, DB_PTR min_val, const char *val_nm) { struct rlimit limit_old, limit_new; DB_PTR new_val; if (0 > getrlimit(resource, &limit_old)) { dcc_error_msg("getrlimit(%s): %s", rlimit_nm, ERROR_STR()); return cur_val; } if ((DB_PTR)limit_old.rlim_cur >= cur_val+DB_PAD_MBYTE*1024) return cur_val; /* assume we are root and try to increase the hard limit */ if ((DB_PTR)limit_new.rlim_max < cur_val+DB_PAD_BYTE) { limit_new = limit_old; limit_new.rlim_max = cur_val+DB_PAD_BYTE; if (0 > setrlimit(resource, &limit_new)) { if (db_debug) quiet_trace_msg("setrlimit(%s, " L_DPAT","L_DPAT"): %s", rlimit_nm, (DB_PTR)limit_new.rlim_cur, (DB_PTR)limit_new.rlim_max, ERROR_STR()); } else { if (0 > getrlimit(resource, &limit_old)) { dcc_error_msg("getrlimit(%s): %s", rlimit_nm, ERROR_STR()); return cur_val; } } } limit_new = limit_old; if ((DB_PTR)limit_new.rlim_max < min_val+DB_PAD_BYTE) limit_new.rlim_max = min_val + DB_PAD_BYTE; limit_new.rlim_cur = limit_new.rlim_max; if ((DB_PTR)limit_new.rlim_cur > cur_val+DB_PAD_BYTE) limit_new.rlim_cur = cur_val+DB_PAD_BYTE; if (0 > setrlimit(resource, &limit_new)) { dcc_error_msg("setrlimit(%s, "L_DPAT","L_DPAT"): %s", rlimit_nm, (DB_PTR)limit_new.rlim_cur, (DB_PTR)limit_new.rlim_max, ERROR_STR()); new_val = limit_old.rlim_cur - DB_PAD_BYTE; if (new_val < min_val) new_val = min_val; } else { if (limit_old.rlim_cur < limit_new.rlim_cur && db_debug) quiet_trace_msg("increased %s from %s to %s", rlimit_nm, db_ptr2str(limit_old.rlim_cur), #ifdef RLIM_INFINITY (limit_new.rlim_cur == RLIM_INFINITY) ? "infinity" : #endif db_ptr2str(limit_new.rlim_cur)); new_val = limit_new.rlim_cur - DB_PAD_BYTE; } if (cur_val > new_val) { quiet_trace_msg("%s reduced %s from %s to %s", rlimit_nm, val_nm, db_ptr2str(cur_val), db_ptr2str(new_val)); return new_val; } return cur_val; } #endif static void get_db_max_rss(void) { DB_PTR old_val, new_val, db_min_mbyte, db_min_byte, db_max_mbyte; int physmem_str_len; DB_PTR physmem; /* use default maximum if maximum is bogus or unset by ./configure */ db_max_mbyte = MAX_MAX_DB_MBYTE; #if DB_MAX_MBYTE != 0 db_max_mbyte = DB_MAX_MBYTE; if (db_max_mbyte < DB_MIN_MIN_MBYTE || db_max_mbyte > MAX_MAX_DB_MBYTE) { quiet_trace_msg("ignore bad ./configure --with-max-db-mem=%d", DB_MAX_MBYTE); db_max_mbyte = MAX_MAX_DB_MBYTE; } else if (db_debug) { quiet_trace_msg("DB max=%s" " from ./configure --with-max-db-mem=%d", mbyte2str(db_max_mbyte), DB_MAX_MBYTE); } #endif #ifndef HAVE_BIG_FILES /* we need big off_t for files larger than 2 GBytes */ if (db_max_mbyte > DB_MAX_2G_MBYTE) { old_val = db_max_mbyte; db_max_mbyte= DB_MAX_2G_MBYTE; if (db_debug) quiet_trace_msg("32-bit off_t reduced DB max from %s" " to %s", mbyte2str(old_val), mbyte2str(db_max_mbyte)); } #endif /* use default if ./configure --with-db-memory=MB is bogus or unset */ #if DB_MIN_MBYTE == 0 db_min_mbyte = 64; #else db_min_mbyte = DB_MIN_MBYTE; if (db_min_mbyte < DB_MIN_MIN_MBYTE) { quiet_trace_msg("ignore bad ./configure --with-db-memory=%d", DB_MIN_MBYTE); db_min_mbyte = DB_DEF_MIN_MBYTE; } else if (db_min_mbyte > db_max_mbyte) { quiet_trace_msg("ignore ./configure --with-db-memory=%d" " > DB max=%s", mbyte2str(db_max_mbyte)); db_min_mbyte = DB_DEF_MIN_MBYTE; } else if (db_debug) { quiet_trace_msg("use ./configure --with-db-memory=%d", DB_MIN_MBYTE); } #endif db_min_byte = db_min_mbyte * (1024*1024); db_max_byte = db_max_mbyte * (1024*1024); #ifdef RLIMIT_FSIZE db_max_mbyte = (use_rlimit(RLIMIT_FSIZE, "RLIMIT_FSIZE", db_max_byte, db_min_byte, "DB max") / (1024*1024)); db_max_byte = db_max_mbyte * (1024*1024); #endif /* RLIMIT_FSIZE */ physmem = 0; #ifdef HAVE_PHYSMEM_TOTAL /* maybe someday physmem_total() will be widely available */ physmem = physmem_total(); if (db_debug) quiet_trace_msg("real=%s from physmem_total()", db_ptr2str(physmem)); #endif #ifdef HAVE__SC_PHYS_PAGES if (physmem == 0) { long pages, sizepage; if ((pages = sysconf(_SC_PHYS_PAGES)) == -1) { dcc_error_msg("sysconf(_SC_PHYS_PAGES): %s", ERROR_STR()); } else if ((sizepage = sysconf(_SC_PAGESIZE)) == -1) { dcc_error_msg("sysconf(_SC_PAGESIZE): %s", ERROR_STR()); } else { physmem = (DB_PTR)pages * (DB_PTR)sizepage; if (db_debug) quiet_trace_msg("real=%s" " from sysconf(_SC_PHYS_PAGES)" " and sysconf(_SC_PAGESIZE)", db_ptr2str(physmem)); } } #endif #ifdef HAVE_HW_PHYSMEM if (physmem == 0) { int mib[2] = {CTL_HW, HW_PHYSMEM}; unsigned long int hw_physmem; size_t hw_physmem_len; hw_physmem_len = sizeof(hw_physmem); if (0 > sysctl(mib, 2, &hw_physmem, &hw_physmem_len, 0,0)) { dcc_error_msg("sysctl(HW_PHYSMEM): %s", ERROR_STR()); } else { physmem = hw_physmem; if (db_debug) quiet_trace_msg("real=%s from sysctl(mib)", db_ptr2str(physmem)); } } #endif #ifdef HAVE_PSTAT_GETSTATIC if (physmem == 0) { struct pst_static pss; if (0 > pstat_getstatic(&pss, sizeof pss, 1, 0)) { dcc_error_msg("pstat_getstatic(): %s", ERROR_STR()); } else if (pss.physical_memory <= 0 || pss.page_size < 0) { dcc_error_msg("pstat_getstatic() says" " physical_memory=%d page_size=%d", pss.physical_memory, pss.page_size); } else { physmem = ((DB_PTR)pss.physical_memory * (DB_PTR)pss.page_size); if (db_debug) quiet_trace_msg("real=%s" " from pstat_getstatic()", db_ptr2str(physmem)); } } #endif physmem_str_len = 0; db_physmem_str[0] = '\0'; if (physmem == 0) { quiet_trace_msg("failed to get real memory size"); } else { physmem_str_len = snprintf(db_physmem_str, sizeof(db_physmem_str), " real=%s", db_ptr2str(physmem)); /* Try to use half of physical memory * if there is less than 2 GByte * all except 512 MByte between 2 GByte and 4 GByte, * and all but 1 GByte if there is more than 4 GByte */ if (physmem/(1024*1024) < 2*1024) new_val = physmem/2; else if (physmem/(1024*1024) <= 4*1024) new_val = physmem - 512*(1024*1024); else new_val = physmem - 1024*(1024*1024); if (new_val < db_min_byte) { if (db_debug) quiet_trace_msg("real=%s would give DB max=%s" " smaller than minimum %s", db_ptr2str(physmem), db_ptr2str(new_val), mbyte2str(db_min_mbyte)); new_val = db_min_byte; } if (db_max_byte > new_val) { old_val = db_max_byte; db_max_mbyte = new_val / (1024*1024); db_max_byte = db_max_mbyte * (1024*1024); if (db_debug) quiet_trace_msg("real=%s reduced DB max" " from %s to %s", db_ptr2str(physmem), db_ptr2str(old_val), db_ptr2str(db_max_byte)); } } /* window need not be larger than the limit on the database size */ db_max_rss = db_max_byte; #ifdef RLIMIT_AS /* try not to break process virtual memory limit, * but only if it is not ridiculously tiny */ db_max_rss = use_rlimit(RLIMIT_AS, "RLIMIT_AS", db_max_rss, db_min_byte, "max RSS"); #endif /* RLIMIT_AS */ #ifdef RLIMIT_RSS /* try not to break process resident memory limit * but only if it is not ridiculously tiny */ db_max_rss = use_rlimit(RLIMIT_RSS, "RLIMIT_RSS", db_max_rss, db_min_byte, "max RSS"); #endif /* RLIMIT_RSS */ /* limit the database to the window size */ if (db_max_byte > db_max_rss) { old_val = db_max_mbyte; db_max_mbyte = db_max_rss / (1024*1024); db_max_byte = db_max_mbyte * (1024*1024); if (db_debug) quiet_trace_msg("max RSS reduced DB max from %s to %s", mbyte2str(old_val), mbyte2str(db_max_mbyte)); } #ifndef HAVE_64BIT_PTR /* We cannot use a window larger than 2 GBytes on most systems without * big pointers. Among the things that break is trying to mmap() more * than 2 GBytes. So limit the window on 32-bit systems to a little * less than 2 GBytes and the database to not much more */ if (db_max_rss > DB_MAX_2G_MBYTE*(1024*1024)) { if (db_debug) quiet_trace_msg("32-bit pointers reduced max RSS" " from %s to %s", db_ptr2str(db_max_rss), mbyte2str(DB_MAX_2G_MBYTE)); db_max_rss = DB_MAX_2G_MBYTE*(1024*1024); new_val = db_max_rss+db_max_rss/4; if (db_max_byte > new_val) { old_val = db_max_mbyte; db_max_mbyte = new_val / (1024*1024); db_max_byte = db_max_mbyte * (1024*1024); if (db_debug) quiet_trace_msg("32-bit pointers reduced DB max" " from %s to %s", mbyte2str(old_val), mbyte2str(db_max_mbyte)); } } #endif snprintf(&db_physmem_str[physmem_str_len], sizeof(db_physmem_str) - physmem_str_len, " max RSS=%s DB max=%s", db_ptr2str(db_max_rss), mbyte2str(db_max_mbyte)); } /* Pick a buffer size that will hold an integral number of DB hash * table entries and is a multiple of system's page size. * The entire hash table should reside in memory * if the system has enough memory. */ u_int db_get_pagesize(u_int old_pagesize, /* 0 or required page size */ u_int tgt_pagesize) /* 0 or target page size */ { u_int min_pagesize, max_pagesize; /* Ask the operating system only once so we don't get differing * answers and so compute a varying page size. * Some systems can't keep their stories straight. */ if (db_max_rss == 0) get_db_max_rss(); /* Compute the least common multiple of the system page and * the DB hash table entry size. * This will give us the smallest page size that we can use. */ system_pagesize = getpagesize(); min_pagesize = lcm(system_pagesize, sizeof(HASH_ENTRY)); /* The kludge to speed conversion of database addresses to page numbers * and offsets on 32-bit systems depends on the page size being * a multiple of 256 */ if ((min_pagesize % (1<<DB_PTR_SHIFT)) != 0) dcc_logbad(EX_SOFTWARE, "page size not a multiple of 256"); /* The DB buffer or page size must also be a multiple of the * the end-of-page padding used in the main database file. */ if (sizeof(DB_RCD) % DB_RCD_HDR_LEN != 0) dcc_logbad(EX_SOFTWARE, "DB padding size %d" " is not a divisor of DB entry size %d", DB_RCD_HDR_LEN, ISZ(DB_RCD)); if (DB_RCD_LEN_MAX % DB_RCD_HDR_LEN != 0) dcc_logbad(EX_SOFTWARE, "DB record not a multiple of header size"); min_pagesize = lcm(min_pagesize, DB_RCD_HDR_LEN); /* Use the old buffer size if available so we are not confused * by padding at the ends of the old pages. * Fail if it is impossible. This should cause dbclean to * rebuild the database. */ if (old_pagesize != 0) { if ((old_pagesize % min_pagesize) != 0) return 0; /* adjust the number of buffers to fit our window size */ db_buf_total = db_max_rss / old_pagesize; if (db_buf_total < (int)DB_BUF_MIN) return 0; if (db_buf_total > DB_BUF_MAX) db_buf_total = DB_BUF_MAX; return old_pagesize; } db_buf_total = DB_BUF_MAX; max_pagesize = db_max_rss / db_buf_total; max_pagesize -= max_pagesize % min_pagesize; /* If we have a target page size, try to use it instead of the * maximum page size allowed by the resident set size. * Normal DCC databases grow large and want pages as large as possible * but greylist databases are often small. * We also want a tiny page when first reading the parameters while * opening. */ if (tgt_pagesize != 0 && tgt_pagesize < max_pagesize) { tgt_pagesize -= tgt_pagesize % min_pagesize; if (tgt_pagesize < min_pagesize) tgt_pagesize = min_pagesize; return tgt_pagesize; } else if (max_pagesize > min_pagesize) { return max_pagesize; } else { return min_pagesize; } } /* (re)create the buffer pool * The buffers are small blocks that point to the real mmap()'ed memory. */ u_char db_buf_init(u_int old_pagesize, /* 0 or required page size */ u_int tgt_pagesize) /* 0 or target page size */ { DB_BUF *b, *bprev, *bnext; int i; db_pagesize = db_get_pagesize(old_pagesize, tgt_pagesize); if (db_pagesize == 0) return 0; /* The fragments of pages must be multiples of system pages * so that msync() on Solaris can be given multiples of system * pages. It's also a generally good idea. */ db_pagesize_part = db_pagesize/DB_BUF_NUM_PARTS; db_pagesize_part = ((db_pagesize_part + system_pagesize-1) / system_pagesize) * system_pagesize; db_page_max = db_pagesize - DB_RCD_HDR_LEN; db_hash_page_len = db_pagesize/sizeof(HASH_ENTRY); db_max_hash_entries = (MAX_HASH_ENTRIES - MAX_HASH_ENTRIES % db_hash_page_len); memset(db_bufs, 0, sizeof(db_bufs)); b = db_bufs; buf_oldest = b; bprev = 0; for (i = db_buf_total; --i != 0; b = bnext) { bnext = b+1; b->older = bprev; b->newer = bnext; bprev = b; } b->older = bprev; buf_newest = b; memset(db_buf_hash, 0, sizeof(db_buf_hash)); return 1; } static u_char make_new_hash(DCC_EMSG emsg, DB_HADDR new_hash_len) { struct stat sb; HASH_ENTRY *hash; DB_HADDR next_haddr, cur_haddr, prev_haddr; u_int pagenum; if (getuid() == 0) { /* if we are running as root, * don't change the owner of the database */ if (0 > fstat(db_fd, &sb)) { dcc_pemsg(EX_IOERR, emsg, "fstat(%s): %s", db_nm, ERROR_STR()); return 0; } if (0 > fchown(db_hash_fd, sb.st_uid, sb.st_gid)) { dcc_pemsg(EX_IOERR, emsg, "fchown(%s,%d,%d): %s", db_hash_nm, (int)sb.st_uid, (int)sb.st_gid, ERROR_STR()); return 0; } } if (new_hash_len < MIN_HASH_ENTRIES) new_hash_len = MIN_HASH_ENTRIES; /* Increase the requested hash table size to a multiple of the database * page size. The page size is chosen to be a multiple of the size of * a single hash table entry. */ db_hash_fsize = (((DB_HOFF)new_hash_len)*sizeof(HASH_ENTRY) + db_pagesize-1); db_hash_fsize -= db_hash_fsize % db_pagesize; new_hash_len = db_hash_fsize / sizeof(HASH_ENTRY); if (new_hash_len > db_max_hash_entries) new_hash_len = db_max_hash_entries; /* create the empty hash table file */ rel_db_states(); if (!db_unload(emsg, 0)) return 0; if (0 > ftruncate(db_hash_fd, 0)) { dcc_pemsg(EX_IOERR, emsg, "truncate(%s,"L_HPAT"): %s", db_hash_nm, db_csize, ERROR_STR()); return 0; } db_hash_len = new_hash_len; db_hash_used_stored_hash = db_hash_used = DB_HADDR_BASE; db_hash_divisor = get_db_hash_divisor(db_hash_len); /* Clear new hash file by linking its entries into the free list */ /* map and clear the first page */ if (!map_hash_ctl(emsg, 1)) return 0; /* create the header */ strcpy(db_sts.hash_ctl.d.vals->s.magic, HASH_MAGIC_STR); db_sts.hash_ctl.d.vals->s.free_fwd = DB_HADDR_BASE; db_sts.hash_ctl.d.vals->s.free_bak = db_hash_len-1; db_sts.hash_ctl.d.vals->s.len = db_hash_len; db_sts.hash_ctl.d.vals->s.divisor = db_hash_divisor; db_sts.hash_ctl.d.vals->s.used = DB_HADDR_BASE; db_sts.hash_ctl.d.vals->s.synced = time(0); db_dirty = 1; #ifdef USE_MAP_NOSYNC db_sts.hash_ctl.d.vals->s.synced = time(0); db_sts.hash_ctl.d.vals->s.flags |= HASH_CTL_FG_NOSYNC; db_not_synced = 1; #endif /* Link the hash table entries in the first and following pages. * The page size is chosen to be a multiple of the size of a * single hash table entry. */ prev_haddr = FREE_HADDR_END; cur_haddr = DB_HADDR_BASE; next_haddr = cur_haddr+1; hash = &db_sts.hash_ctl.d.vals->h[DB_HADDR_BASE]; pagenum = 0; for (;;) { do { DB_HADDR_CP(hash->bak, prev_haddr); if (next_haddr == db_hash_len) DB_HADDR_CP(hash->fwd, FREE_HADDR_END); else DB_HADDR_CP(hash->fwd, next_haddr); ++hash; prev_haddr = cur_haddr; cur_haddr = next_haddr++; } while (cur_haddr % db_hash_page_len != 0); if (++pagenum >= db_hash_fsize/db_pagesize) break; if (!map_hash(emsg, cur_haddr, &db_sts.free, 1)) return 0; db_sts.free.b->flush_urgent = (DB_BUF_FM)-1; hash = db_sts.free.d.h; } hash_clear_pg_num = 0; return 1; } static u_char check_old_hash(DCC_EMSG emsg) { static const u_char magic[sizeof(((HASH_CTL*)0)->s.magic) ] = HASH_MAGIC_STR; const HASH_CTL *vals; struct stat sb; u_char old_db; /* check the size of the existing hash file */ if (0 > fstat(db_hash_fd, &sb)) { dcc_pemsg(EX_IOERR, emsg, "stat(%s): %s", db_hash_nm, ERROR_STR()); return 0; } db_hash_fsize = sb.st_size; if ((db_hash_fsize % sizeof(HASH_ENTRY)) != 0) { dcc_pemsg(EX_DATAERR, emsg, "%s has size "OFF_DPAT"," " not a multiple of %d", db_hash_nm, db_hash_fsize, ISZ(HASH_ENTRY)); return 0; } db_hash_len = db_hash_fsize/sizeof(HASH_ENTRY); if (db_hash_len < MIN_HASH_ENTRIES) { dcc_pemsg(EX_DATAERR, emsg, "%s has too few records, "OFF_DPAT" bytes", db_hash_nm, db_hash_fsize); return 0; } /* check the magic number */ if (!map_hash_ctl(emsg, 0)) return 0; vals = db_sts.hash_ctl.d.vals; if (memcmp(vals->s.magic, &magic, sizeof(magic))) { dcc_pemsg(EX_DATAERR, emsg, "%s has the wrong magic \"%.*s\"", db_hash_nm, ISZ(HASH_ENTRY), vals->s.magic); return 0; } if (!(vals->s.flags & HASH_CTL_FG_CLEAN)) { dcc_pemsg(EX_DATAERR, emsg, "%s was not closed cleanly", db_hash_nm); return 0; } if (vals->s.flags & HASH_CTL_FG_NOSYNC) { #ifdef HAVE_BOOTTIME int mib[2] = {CTL_KERN, KERN_BOOTTIME}; size_t boottime_len; #endif struct timeval boottime; boottime.tv_sec = 0x7fffffff; #ifdef HAVE_BOOTTIME boottime_len = sizeof(boottime); if (0 > sysctl(mib, 2, &boottime, &boottime_len, 0, 0)) { dcc_error_msg("sysctl(KERN_BOOTTIME): %s", ERROR_STR()); } #endif if (vals->s.synced <= boottime.tv_sec) { dcc_pemsg(EX_DATAERR, emsg, "%s was not synchronized;" " synced=%d boottime=%d", db_hash_nm, (int)vals->s.synced, (int)boottime.tv_sec); return 0; } db_not_synced = 1; } if (DB_HADDR_INVALID(vals->s.free_fwd) && (vals->s.free_fwd != FREE_HADDR_END || vals->s.free_fwd != vals->s.free_bak)) { dcc_pemsg(EX_DATAERR, emsg, "%s has a broken free list head of %#x", db_hash_nm, vals->s.free_fwd); return 0; } if (DB_HADDR_INVALID(vals->s.free_bak) && (vals->s.free_bak != FREE_HADDR_END || vals->s.free_fwd != vals->s.free_bak)) { dcc_pemsg(EX_DATAERR, emsg, "%s has a broken free list tail of %#x", db_hash_nm, vals->s.free_bak); return 0; } if (db_hash_len != vals->s.len) { dcc_pemsg(EX_DATAERR, emsg, "%s has %d entries but claims %d", db_hash_nm, db_hash_len, vals->s.len); return 0; } db_hash_divisor = vals->s.divisor; if (db_hash_divisor < MIN_HASH_DIVISOR || db_hash_divisor >= db_hash_len) { dcc_pemsg(EX_DATAERR, emsg, "%s has hash divisor %d", db_hash_nm, db_hash_len); return 0; } db_hash_used_stored_hash = db_hash_used = vals->s.used; if (db_hash_used < DB_HADDR_BASE) { dcc_pemsg(EX_DATAERR, emsg, "%s contains impossible %u entries", db_hash_nm, HADDR2LEN(db_hash_used)); return 0; } if (db_hash_used >= db_hash_len) { if (db_hash_used > db_hash_len) dcc_pemsg(EX_DATAERR, emsg, "%s contains only %u entries but %u used", db_hash_nm, HADDR2LEN(db_hash_len), HADDR2LEN(db_hash_used)); else dcc_pemsg(EX_DATAERR, emsg, "%s is filled with %u entries", db_hash_nm, HADDR2LEN(db_hash_len)); return 0; } /* old databases lack the growth values */ old_db = 0; if (!db_rdonly && db_parms.old_db_csize == 0 && db_parms.db_added == 0 && db_parms.hash_used == 0 && db_parms.old_hash_used == 0 && db_parms.hash_added == 0 && db_parms.rate_secs == 0 && db_parms.last_rate_sec == 0) { quiet_trace_msg("repair database growth measurements"); db_parms.old_db_csize = db_parms.db_csize; old_db = 1; } if (db_hash_used != db_parms.hash_used && db_hash_fsize != 0) { if (old_db) { quiet_trace_msg("repair db_parms.old hash_used" " and old_hash_used"); db_parms.old_hash_used = db_hash_used; db_parms.hash_used = db_hash_used; } else { dcc_pemsg(EX_DATAERR, emsg, "%s contains %d" " entries instead of the %d that %s claims", db_hash_nm, db_hash_used, db_parms.hash_used, db_nm); return 0; } } db_csize_stored_hash = vals->s.db_csize; if (db_csize_stored_hash != db_csize && db_hash_fsize != 0) { dcc_pemsg(EX_DATAERR, emsg, "%s contains "L_DPAT " bytes instead of the "L_DPAT" that %s claims", db_nm, db_csize, db_csize_stored_hash, db_hash_nm); return 0; } return 1; } /* open the files and generally get ready to work */ u_char /* 0=failed, 1=ok */ db_open(DCC_EMSG emsg, int new_db_fd, /* -1 or already open db_fd */ const char *new_db_nm, DB_HADDR new_hash_len, /* 0 or # of entries */ DB_OPEN_MODES mode) /* DB_OPEN_* */ { u_int cur_pagesize; int hash_flags, db_open_flags; struct stat db_sb; # define OPEN_BAIL() {if (new_db_fd >= 0) db_fd = -1; \ db_close(-1); return 0;} db_close(1); db_failed_line = __LINE__; db_failed_file = __FILE__; db_not_synced = 0; db_minimum_map = 0; db_invalidate = 0; db_dirty = 0; db_locked.tv_sec = 0; db_rdonly = (mode & DB_OPEN_RDONLY) != 0; db_use_write = (mode & DB_OPEN_MMAP_WRITE) != 0; memset(&db_stats, 0, sizeof(db_stats)); if (!new_db_nm && db_nm[0] == '\0') new_db_nm = grey_on ? DB_GREY_NAME : DB_DCC_NAME; if (new_db_nm) { if (!fnm2rel(db_nm, new_db_nm, 0) || !fnm2rel(db_hash_nm, db_nm, DB_HASH_SUFFIX)) { dcc_pemsg(EX_DATAERR, emsg, "invalid DB nm \"%s\"", new_db_nm); return 0; } } if (new_db_fd >= 0) { if (new_hash_len != 0) { dcc_logbad(EX_SOFTWARE, "extending db_open(%s) without locking", db_nm); return 0; } if (!db_rdonly) { dcc_logbad(EX_SOFTWARE, "db_open(%s) read/write without locking", db_nm); return 0; } db_open_flags = O_RDONLY; hash_flags = O_RDONLY; db_fd = new_db_fd; } else { db_open_flags = O_RDWR; if (new_hash_len != 0) { if (db_rdonly) { dcc_logbad(EX_SOFTWARE, "db_open(%s) creating read-only", db_nm); return 0; } hash_flags = O_RDWR | O_CREAT; } else { /* must open the file read/write to lock it */ hash_flags = O_RDWR; } db_fd = dcc_lock_open(emsg, db_nm, db_open_flags, (mode & DB_OPEN_LOCK_NOWAIT) ? DCC_LOCK_OPEN_NOWAIT : 0, DCC_LOCK_ALL_FILE, 0); if (db_fd == -1) { db_close(-1); return 0; } } gettimeofday(&db_time, 0); db_locked = db_time; if (0 > fstat(db_fd, &db_sb)) { dcc_pemsg(EX_IOERR, emsg, "stat(%s): %s", db_nm, ERROR_STR()); OPEN_BAIL(); return 0; } db_csize = db_fsize = db_sb.st_size; if (db_fsize < ISZ(DB_HDR)) { dcc_pemsg(EX_IOERR, emsg, "%s with %d bytes is too small to be a DCC database", db_nm, (int)db_fsize); OPEN_BAIL(); } /* check the header of the database file by temporarily mapping it */ db_buf_init(0, sizeof(DB_HDR)); if (!map_db(emsg, 0, sizeof(DB_HDR), &db_sts.db_parms, 0)) OPEN_BAIL(); db_parms_stored = *db_sts.db_parms.d.parms; db_parms = *db_sts.db_parms.d.parms; if (memcmp(db_parms.version, db_version_buf, sizeof(db_version_buf))) { dcc_pemsg(EX_DATAERR, emsg, "%s contains the wrong magic string \"%.*s\"", db_nm, ISZ(db_parms.version), db_parms.version); OPEN_BAIL(); } if (!(db_parms.flags & DB_PARM_FG_GREY) != !grey_on) { dcc_pemsg(EX_DATAERR, emsg, "%s is%s a greylist database but must%s be", db_nm, (db_parms.flags & DB_PARM_FG_GREY) ? "" : " not", grey_on ? "" : " not"); OPEN_BAIL(); } cur_pagesize = db_parms.pagesize; DB_SET_NOKEEP(db_parms.nokeep_cks, DCC_CK_INVALID); DB_SET_NOKEEP(db_parms.nokeep_cks, DCC_CK_FLOD_PATH); set_db_tholds(db_parms.nokeep_cks); db_ck_fuzziness = grey_on ? grey_ck_fuzziness : dcc_ck_fuzziness; db_csize = db_parms.db_csize; if (db_csize < sizeof(DB_HDR)) { dcc_pemsg(EX_DATAERR, emsg, "%s says it contains "L_DPAT" bytes" " or fewer than the minimum of %d", db_nm, db_csize, DB_PTR_BASE); /* that is a fatal error if we are not rebuilding */ if (new_hash_len != 0) OPEN_BAIL(); } if (db_csize > db_fsize) { dcc_pemsg(EX_DATAERR, emsg, "%s says it contains "L_DPAT" bytes" " or more than the actual size of "OFF_DPAT, db_nm, db_csize, db_fsize); /* that is a fatal error if we are not rebuilding */ if (new_hash_len != 0) OPEN_BAIL(); } /* The buffer or page size we use must be the page size used to * write the files. Try to change our size to match the file */ if (cur_pagesize != db_pagesize) { db_invalidate = 1; rel_db_states(); if (!db_unload(emsg, 0)) OPEN_BAIL(); db_invalidate = 0; if (!db_buf_init(cur_pagesize, 0)) { dcc_error_msg("%s has page size %d" " incompatible with %d in %s", db_nm, cur_pagesize, db_get_pagesize(0, 0), path2fnm(db_hash_nm)); OPEN_BAIL(); } } db_csize_stored_hash = 0; db_hash_len = 0; db_hash_fd = open(db_hash_nm, hash_flags, 0666); if (db_hash_fd < 0) { dcc_pemsg(EX_IOERR, emsg, "open(%s): %s", db_hash_nm, ERROR_STR()); OPEN_BAIL(); } if (0 > fcntl(db_hash_fd, F_SETFD, FD_CLOEXEC)) { dcc_pemsg(EX_IOERR, emsg, "fcntl(%s, FD_CLOEXEC): %s", db_hash_nm, ERROR_STR()); OPEN_BAIL(); } if (new_hash_len != 0) { if (!make_new_hash(emsg, new_hash_len)) OPEN_BAIL(); } else { if (!check_old_hash(emsg)) OPEN_BAIL(); } if (db_fsize % db_pagesize != 0) { dcc_pemsg(EX_DATAERR, emsg, "%s has size "OFF_HPAT"," " not a multiple of its page size of %#x", db_nm, db_fsize, db_pagesize); OPEN_BAIL(); } if (db_fsize > db_csize + db_pagesize || db_csize > db_fsize) { dcc_pemsg(EX_DATAERR, emsg, "%s has size "OFF_HPAT" but claims "L_HPAT, db_nm, db_fsize, db_csize); OPEN_BAIL(); } #ifndef USE_MAP_NOSYNC /* Use `dbclean -F` on systems without mmap(NOSYNC) but with lots of * RAM. Some Linux systems otherwise take too long to run dbclean. */ if (mode & DB_OPEN_MMAP_WRITE_NOSYNC) { if (db_max_rss > db_fsize + db_hash_fsize) db_use_write = 1; if (db_debug) quiet_trace_msg("db_max_rss="OFF_HPAT " db_fsize+db_hash_fsize="OFF_HPAT " so%s use -F", db_max_rss, db_fsize+db_hash_fsize, db_use_write ? "" : " do not"); } #endif db_window_size = (DB_PTR)db_pagesize * db_buf_total; snprintf(db_window_size_str, sizeof(db_window_size_str), "window=%s%s", db_ptr2str(db_window_size), db_physmem_str); rel_db_states(); db_failed_line = 0; return 1; #undef OPEN_BAIL } static u_char buf_munmap(DCC_EMSG emsg, DB_BUF *b) { u_char result; if (b->lock_cnt != 0) dcc_logbad(EX_SOFTWARE, "unmapping locked DB buffer"); result = buf_flush(emsg, b, 1); if (db_invalidate) { if (0 > DCC_MADV_FREE(b->buf.v)) dcc_error_msg("madvise(FREE %s,%#x): %s", buf2path(b), db_pagesize, ERROR_STR()); } if (0 > munmap(b->buf.v, db_pagesize)) { db_failure(__LINE__,__FILE__, EX_IOERR, emsg, "munmap(%s,%d): %s", buf2path(b), db_pagesize, ERROR_STR()); result = 0; } b->buf.v = 0; b->pg_num = -1; b->buf_type = DB_BUF_TYPE_FREE; return result; } static u_char buf_mmap(DCC_EMSG emsg, DB_BUF *b, DB_PG_NUM pg_num, u_char extend) { int prot, flags; off_t offset; int fd; void *p; int retry; u_char unloaded; offset = (off_t)pg_num * (off_t)db_pagesize; fd = buf2fd(b); if (extend) { offset = 0; #if defined(MAP_ANON)|| defined(MAP_ANONYMOUS) fd = -1; b->flags |= DB_BUF_FG_USE_WRITE | DB_BUF_FG_EXTENSION; #ifdef MAP_ANONYMOUS /* Linux redefines things and requires either MAP_ANON * or MAP_PRIVATE; */ flags = MAP_ANONYMOUS| MAP_PRIVATE; #else flags = MAP_ANON | MAP_PRIVATE; #endif /* MAP_ANONYMOUS */ #else /* have neither MAP_ANON nor MAP_ANONYMOUS */ b->flags |= DB_BUF_FG_USE_WRITE; flags = MAP_PRIVATE; #endif } else if (db_rdonly) { flags = MAP_SHARED; } else if (db_use_write && !db_minimum_map) { /* write() buffers instead of letting the Solaris virtual * memory system do it. Solaris will bog the system down doing * nothing but flushing dirty mmap() pages * We cannot use this hack in two processes simultaneously, * so do not use it in dccd while dbclean is running */ b->flags |= DB_BUF_FG_USE_WRITE; flags = MAP_PRIVATE; } else { #ifdef USE_MAP_NOSYNC flags = (MAP_SHARED | MAP_NOSYNC); #else flags = MAP_SHARED; #endif } prot = db_rdonly ? PROT_READ : (PROT_READ | PROT_WRITE); for (retry = 1, unloaded = 2; unloaded > 1; ++retry) { p = mmap(0, db_pagesize, prot, flags, fd, offset); if (p == MAP_FAILED) { if (errno == EACCES || errno == EBADF || errno == EINVAL || errno == ENODEV || retry > 20) { dcc_pemsg(EX_IOERR, emsg, "try #%d"" mmap(%s" " %#x,%#x,%#x,%d,"OFF_HPAT"): %s", retry, buf2path(b), db_pagesize, prot, flags, fd, offset, ERROR_STR()); return 0; } dcc_error_msg("try #%d mmap(%s" " %#x,%#x,%#x,%d,"OFF_HPAT"): %s", retry, buf2path(b), db_pagesize, prot, flags, fd, offset, ERROR_STR()); /* #define MMAP_FAIL_DEBUG 3 */ #ifdef MMAP_FAIL_DEBUG } else if (((uint)random() % MMAP_FAIL_DEBUG) == 0) { /* pretend mmap() failed randomly */ dcc_error_msg(" test fail #%d mmap(%s,%#x,"OFF_HPAT")", retry, buf2path(b), db_pagesize, offset); if (0 > munmap(p, db_pagesize)) dcc_error_msg( "test munmap(): %s", ERROR_STR()); #endif } else { /* It worked. * Say so if it was not the first attempt. */ if (retry != 1) dcc_error_msg("try #%d" " mmap(%s,%#x,"OFF_HPAT") ok", retry, buf2path(b), db_pagesize, offset); break; } /* mmap() fails occassionally on some systems, * so try to release something and try again */ unloaded = db_unload(0, 1); } b->buf.v = p; b->flush = 0; b->flush_urgent = 0; if (extend) return 1; /* madvise() on some systems including FreeBSD uses a lot of CPU cycles, * so it should not be done unless it is likely to do significant good. * Get all of our buffers if there is plenty of memory * and we are not trying to stay out of the way of dbclean. */ if (!db_minimum_map && db_fsize <= db_max_rss) { /* The flat file would fit. If the hash table would also * fit, tell the kernel to be aggressive */ if (db_fsize + db_hash_fsize <= db_max_rss && 0 > DCC_MADV_WILLNEED(p)) dcc_error_msg("madvise(WILLNEED %s,%#x): %s", buf2path(b), db_pagesize, ERROR_STR()); } else { if (0 > DCC_MADV_RANDOM(p)) dcc_error_msg("madvise(RANDOM %s,%#x): %s", buf2path(b), db_pagesize, ERROR_STR()); } return 1; } /* get a free buffer for a chunk of either the hash table or database files */ static DB_BUF * get_free_buf(DCC_EMSG emsg, DB_BUF **bh) { DB_BUF *b; /* Look for an unlocked buffer. * We know there is one because we have more buffers than * can be locked simultaneously. */ b = buf_oldest; for (;;) { if (!b) dcc_logbad(EX_SOFTWARE, "broken DB buffer MRU chain"); if (!b->lock_cnt) break; b = b->newer; } /* Found an unlocked buffer. * Unlink it from its hash chain. */ if (b->fwd) b->fwd->bak = b->bak; if (b->bak) b->bak->fwd = b->fwd; else if (b->hash) *b->hash = b->fwd; if (b->buf_type != DB_BUF_TYPE_FREE) { if (!buf_munmap(emsg, b)) return 0; } b->flags = 0; /* put it on the new hash chain */ b->bak = 0; b->hash = bh; b->fwd = *bh; *bh = b; if (b->fwd) b->fwd->bak = b; return b; } static DB_BUF * find_buf(DCC_EMSG emsg, DB_BUF_TYPE buf_type, DB_PG_NUM pg_num) { DB_BUF *b, **bh; bh = DB_BUF_HASH(pg_num, buf_type); b = *bh; for (;;) { if (!b) { /* we ran off the end of the buffer hash chain, * so get a free buffer */ b = get_free_buf(emsg, bh); if (!b) return 0; b->buf_type = buf_type; b->pg_num = pg_num; break; } if (b->buf_type == buf_type && b->pg_num == pg_num) break; /* found the buffer we need */ b = b->fwd; } /* make the buffer newest */ if (buf_newest != b) { /* unlink it */ b->newer->older = b->older; if (b->older) b->older->newer = b->newer; else buf_oldest = b->newer; /* insert it at the head of the MRU list */ b->newer = 0; b->older = buf_newest; buf_newest->newer = b; buf_newest = b; } return b; } static DB_BUF * find_st_buf(DCC_EMSG emsg, DB_BUF_TYPE buf_type, DB_STATE *st, DB_PG_NUM pg_num, u_char extend) { DB_BUF *b; /* release previous buffer unless it is the right one */ b = st->b; if (b) { if (b->pg_num == pg_num && b->buf_type == buf_type) return b; /* already have the target buffer */ st->b = 0; st->d.v = 0; if (--b->lock_cnt < 0) dcc_logbad(EX_SOFTWARE, "bad database buffer lock"); } /* look for the buffer */ b = find_buf(emsg, buf_type, pg_num); if (!b) return 0; ++b->lock_cnt; if (b->buf.v) { if (extend && !(b->flags & DB_BUF_FG_USE_WRITE)) dcc_logbad(EX_SOFTWARE, "extending ordinary buffer"); } else { /* map it if it was not already known */ if (!buf_mmap(emsg, b, pg_num, extend)) { b->buf_type = DB_BUF_TYPE_FREE; b->pg_num = -1; if (--b->lock_cnt != 0) dcc_logbad(EX_SOFTWARE, "stolen database buffer lock %d", b->lock_cnt); return 0; } if (buf_type == DB_BUF_TYPE_DB) ++db_stats.db_mmaps; else if (buf_type == DB_BUF_TYPE_HASH) ++db_stats.hash_mmaps; } st->b = b; st->d.v = 0; return b; } static u_char map_hash_ctl(DCC_EMSG emsg, u_char new) { DB_BUF *b; b = find_st_buf(emsg, DB_BUF_TYPE_HASH, &db_sts.hash_ctl, 0, new); if (!b) return 0; db_sts.hash_ctl.s.haddr = 0; db_sts.hash_ctl.d.v = b->buf.v; return 1; } /* mmap() a hash table entry */ static u_char map_hash(DCC_EMSG emsg, DB_HADDR haddr, /* this entry */ DB_STATE *st, /* point this to the entry */ u_char new) { DB_PG_NUM pg_num; DB_PG_OFF pg_off; DB_BUF *b; if (haddr >= db_hash_len || haddr < DB_HADDR_BASE) { dcc_pemsg(EX_DATAERR, emsg, "invalid hash address %#x", haddr); return 0; } pg_num = haddr / db_hash_page_len; pg_off = haddr % db_hash_page_len; b = find_st_buf(emsg, DB_BUF_TYPE_HASH, st, pg_num, new); if (!b) return 0; st->s.haddr = haddr; st->d.h = &b->buf.h[pg_off]; return 1; } /* unlink a hash table entry from the free list * uses db_sts.tmp */ static u_char unlink_free_hash(DCC_EMSG emsg, DB_STATE *hash_st) /* remove this from the free list */ { DB_HADDR fwd, bak; if (!db_make_dirty(emsg)) return 0; fwd = DB_HADDR_EX(hash_st->d.h->fwd); bak = DB_HADDR_EX(hash_st->d.h->bak); if (!HE_IS_FREE(hash_st->d.h) || (DB_HADDR_INVALID(fwd) && fwd != FREE_HADDR_END) || (DB_HADDR_INVALID(bak) && bak != FREE_HADDR_END) || DB_HPTR_EX(hash_st->d.h->rcd) != DB_PTR_NULL) { dcc_pemsg(EX_DATAERR, emsg, "bad hash free list entry at %#x", hash_st->s.haddr); return 0; } if (fwd != FREE_HADDR_END) { if (!map_hash(emsg, fwd, &db_sts.tmp, 0)) return 0; if (DB_HADDR_EX(db_sts.tmp.d.h->bak) != hash_st->s.haddr) { dcc_pemsg(EX_DATAERR, emsg, "free %#x --> bad-free %#x", hash_st->s.haddr, fwd); return 0; } DB_HADDR_CP(db_sts.tmp.d.h->bak, bak); SET_FLUSH_HE(&db_sts.tmp); } else { if (!map_hash_ctl(emsg, 0)) return 0; if (db_sts.hash_ctl.d.vals->s.free_bak != hash_st->s.haddr) { dcc_pemsg(EX_DATAERR, emsg, "free %#x --> bad-free %#x", hash_st->s.haddr, fwd); return 0; } db_sts.hash_ctl.d.vals->s.free_bak = bak; SET_FLUSH_HCTL(0); } if (bak != FREE_HADDR_END) { if (!map_hash(emsg, bak, &db_sts.tmp, 0)) return 0; if (DB_HADDR_EX(db_sts.tmp.d.h->fwd) != hash_st->s.haddr) { dcc_pemsg(EX_DATAERR, emsg, "bad free %#x <-- free %#x", bak, hash_st->s.haddr); return 0; } DB_HADDR_CP(db_sts.tmp.d.h->fwd, fwd); SET_FLUSH_HE(&db_sts.tmp); } else { if (!map_hash_ctl(emsg, 0)) return 0; if (db_sts.hash_ctl.d.vals->s.free_fwd != hash_st->s.haddr) { dcc_pemsg(EX_DATAERR, emsg, "free %#x --> bad-free %#x", hash_st->s.haddr, bak); return 0; } db_sts.hash_ctl.d.vals->s.free_fwd = fwd; SET_FLUSH_HCTL(0); } memset(hash_st->d.h, 0, sizeof(HASH_ENTRY)); SET_FLUSH_HE(hash_st); ++db_hash_used; return 1; } /* get a free hash table entry and leave db_sts.free pointing to it */ static u_char /* 0=failed, 1=got it */ get_free_hash(DCC_EMSG emsg, DB_HADDR result) /* try near here */ { DB_HADDR pg_start, pg_lim, bak; int i; if (db_hash_len <= db_hash_used) { dcc_pemsg(EX_OSFILE, emsg, "no free hash table entry;" " %d of %d used", db_hash_used, db_hash_len); return 0; } /* Look first near the target */ if (result < DB_HADDR_BASE) result = DB_HADDR_BASE; pg_start = result - (result % db_hash_page_len); pg_lim = pg_start + db_hash_page_len-1; if (pg_lim >= db_hash_len) pg_lim = db_hash_len-1; for (i = 0; i < 3 && ++result < pg_lim; ++i) { if (!map_hash(emsg, result, &db_sts.free, 0)) return 0; if (HE_IS_FREE(db_sts.free.d.h)) return unlink_free_hash(emsg, &db_sts.free); } /* check the local ad hoc free list at the end of the page */ if (!map_hash(emsg, pg_lim, &db_sts.free, 0)) return 0; if (HE_IS_FREE(db_sts.free.d.h)) { /* the ad hoc free list is not empty, * so try to use the previous entry */ bak = DB_HADDR_EX(db_sts.free.d.h->bak); if (bak != FREE_HADDR_END) { if (!map_hash(emsg, bak, &db_sts.free, 0)) return 0; } return unlink_free_hash(emsg, &db_sts.free); } /* Give up and search from the start of the free list. This happens * only when the current and all preceding pages are full. */ if (!map_hash_ctl(emsg, 0)) return 0; result = db_sts.hash_ctl.d.vals->s.free_fwd; if (DB_HADDR_INVALID(result)) { dcc_pemsg(EX_DATAERR, emsg, "broken hash free list head of %#x", result); return 0; } if (!map_hash(emsg, result, &db_sts.free, 0)) return 0; return unlink_free_hash(emsg, &db_sts.free); } /* mmap() a database entry * We assume that no database entry spans buffers, * and that there are enough buffers to accomodate all possible * concurrent requests. */ static u_char map_db(DCC_EMSG emsg, DB_PTR rptr, /* address of the record */ u_int tgt_len, /* its length */ DB_STATE *st, /* point this to the record */ u_char extend) { DB_PG_NUM pg_num; DB_PG_OFF pg_off; DB_BUF *b; if (rptr+tgt_len > db_fsize) { db_failure(__LINE__,__FILE__, EX_DATAERR, emsg, "invalid database address "L_HPAT" or length %d" " past db_fsize "OFF_HPAT" in %s", rptr, tgt_len, db_fsize, db_nm); return 0; } /* Try to optimize this to avoid udivdi3() and umoddi3(), * because they are a major time sink here on 32-bit systems */ pg_num = DB_PTR2PG_NUM(rptr, db_pagesize); #ifdef HAVE_64BIT_LONG pg_off = rptr % db_pagesize; #else pg_off = rptr - pg_num*(DB_PTR)db_pagesize; #endif /* do not go past the end of a buffer */ if (tgt_len+pg_off > db_pagesize) { db_failure(__LINE__,__FILE__, EX_DATAERR, emsg, "invalid database address "L_HPAT " or length %#x in %s", rptr, tgt_len, db_nm); return 0; } b = find_st_buf(emsg, DB_BUF_TYPE_DB, st, pg_num, extend); if (!b) return 0; st->s.rptr = rptr; st->d.r = (DB_RCD *)&b->buf.c[pg_off]; return 1; } u_char /* 0=failed, 1=got it */ db_map_rcd(DCC_EMSG emsg, DB_STATE *rcd_st, /* point this to the record */ DB_PTR rptr, /* that is here */ int *rcd_lenp) /* put its length here */ { u_int rcd_len; if (DB_PTR_IS_BAD(rptr)) { dcc_pemsg(EX_DATAERR, emsg, "getting bogus record at "L_HPAT", in %s", rptr, db_nm); return 0; } if (!map_db(emsg, rptr, DB_RCD_HDR_LEN, rcd_st, 0)) return 0; rcd_len = DB_RCD_LEN(rcd_st->d.r); if (&rcd_st->d.c[rcd_len] > &rcd_st->b->buf.c[db_pagesize]) { dcc_pemsg(EX_DATAERR, emsg, "invalid checksum count %d at "L_HPAT" in %s", DB_NUM_CKS(rcd_st->d.r), rptr, db_nm); return 0; } if (rcd_lenp) *rcd_lenp = rcd_len; return 1; } /* write the new sizes of the files into the files */ static u_char db_set_sizes(DCC_EMSG emsg) { u_char result = 1; if (db_hash_fd != -1 && (db_csize_stored_hash != db_csize || db_hash_used_stored_hash != db_hash_used)) { if (!map_hash_ctl(emsg, 0)) { result = 0; } else { db_sts.hash_ctl.d.vals->s.db_csize = db_csize; db_csize_stored_hash = db_csize; db_sts.hash_ctl.d.vals->s.used = db_hash_used; db_hash_used_stored_hash = db_hash_used; SET_FLUSH_HCTL(0); } } if (db_fd != -1 && (db_parms_stored.db_csize != db_csize || db_parms_stored.hash_used != db_hash_used)) { if (!map_db(emsg, 0, sizeof(DB_HDR), &db_sts.db_parms, 0)) { result = 0; } else { db_sts.db_parms.d.parms->db_csize = db_csize; db_parms_stored.db_csize = db_csize; db_parms.db_csize = db_csize; db_sts.db_parms.d.parms->hash_used = db_hash_used; db_parms_stored.hash_used = db_hash_used; db_parms.hash_used = db_hash_used; db_sts.db_parms.d.parms->last_rate_sec = db_time.tv_sec; db_parms_stored.last_rate_sec = db_time.tv_sec; db_parms.last_rate_sec = db_time.tv_sec; db_set_flush(&db_sts.db_parms, 1, sizeof(DB_PARMS)); } } return result; } /* write the database parameters into the magic number headers of the files */ u_char db_flush_parms(DCC_EMSG emsg) { if (!db_set_sizes(emsg)) return 0; if (db_fd == -1) return 1; if (memcmp(&db_parms, &db_parms_stored, sizeof(db_parms))) { if (!map_db(emsg, 0, sizeof(DB_HDR), &db_sts.db_parms, 0)) return 0; db_parms.pagesize = db_pagesize; *db_sts.db_parms.d.parms = db_parms; db_parms_stored = db_parms; db_set_flush(&db_sts.db_parms, 1, sizeof(DB_PARMS)); } return 1; } /* find a checksum in an already mapped record */ DB_RCD_CK * /* 0=not found, 1=broken database */ db_find_ck(DCC_EMSG emsg, DB_RCD *rcd, DB_PTR rptr, DCC_CK_TYPES type) /* find this type of checksum */ { DB_RCD_CK *rcd_ck; int i; rcd_ck = rcd->cks; i = DB_NUM_CKS(rcd); if (i >= DCC_NUM_CKS) { dcc_pemsg(EX_DATAERR, emsg, "impossible %d checksums in "L_HPAT" in %s", i, rptr, db_nm); return (DB_RCD_CK *)1; } for (; i != 0; --i, ++rcd_ck) { if (DB_CK_TYPE(rcd_ck) == type) return rcd_ck; } return 0; } /* find a checksum type known to be in a record */ DB_RCD_CK * /* 0=it's not there */ db_map_rcd_ck(DCC_EMSG emsg, DB_STATE *rcd_st, /* point this to the record */ DB_PTR rptr, /* that is here */ DCC_CK_TYPES type) /* find this type of checksum */ { DB_RCD_CK *rcd_ck; if (!db_map_rcd(emsg, rcd_st, rptr, 0)) return 0; rcd_ck = db_find_ck(emsg, rcd_st->d.r, rptr, type); if (rcd_ck == (DB_RCD_CK *)1) return 0; if (rcd_ck == 0) { dcc_pemsg(EX_DATAERR, emsg, "missing \"%s\" checksum in "L_HPAT" in %s", DB_TYPE2STR(type), rptr, db_nm); return 0; } return rcd_ck; } static inline u_char /* 1=has a small prime factor */ modulus_has_divisor(DB_HADDR len) { static int primes[] = { 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499}; int *p; for (p = &primes[0]; p <= LAST(primes); ++p) { if ((len % *p) == 0) return 1; } return 0; } /* Get a modulus for the hash function that is tolerably likely to be * relatively prime to most inputs. The worst that happens when the modulus * is composite is that large multiples of its factors will suffer more * collisions. */ DB_HADDR get_db_hash_divisor(DB_HADDR len) { DB_HADDR divisor; divisor = len - DB_HADDR_BASE; if (!(divisor & 1)) --divisor; while (divisor >= MIN_HASH_ENTRIES) { if (modulus_has_divisor(divisor)) divisor -= 2; else break; } return divisor; } DB_HADDR db_hash(DCC_CK_TYPES type, const DCC_SUM sum) { u_int64_t accum, wrap; const u_int32_t *wp; union { DCC_SUM sum; u_int32_t words[4]; } buf; int align; DB_HADDR haddr; #ifdef HAVE_64BIT_PTR align = (u_int64_t)sum & 3; #else align = (u_int)sum & 3; #endif if (align == 0) { /* We almost always take this branch because database * records contain 12+N*24 bytes. That also implies that * we should not hope for better than 4 byte alignment. */ wp = (u_int32_t *)sum; } else { memcpy(buf.sum, sum, sizeof(buf.sum)); wp = buf.words; } /* MD5 checksums are uniformly distributed, and so DCC_SUMs are * directly useful for hashing except when they are server-IDs */ accum = *wp++; accum += *wp++; wrap = accum >>32; accum <<= 32; accum += wrap + type; accum += *wp++; accum += *wp; haddr = accum % db_hash_divisor; haddr += DB_HADDR_BASE; /* do not hash into the last slot of a page, because it is used to * find local free slots */ if (haddr % db_hash_page_len == db_hash_page_len-1) { ++haddr; if (haddr >= db_hash_len) haddr = DB_HADDR_BASE; } return haddr; } /* look for a checksum in the hash table * return with an excuse, the home slot, or the last entry on * the collision chain */ DB_FOUND db_lookup(DCC_EMSG emsg, DCC_CK_TYPES type, const DCC_SUM sum, DB_HADDR lo, /* postpone if out of this window */ DB_HADDR hi, DB_STATE *hash_st, /* hash block for record or related */ DB_STATE *rcd_st, /* put the record or garbage here */ DB_RCD_CK **prcd_ck) /* point to cksum if found */ { DB_HADDR haddr, haddr_fwd, haddr_bak; DB_PTR db_ptr; DB_RCD_CK *found_ck; DB_HADDR failsafe; haddr = db_hash(type, sum); if (haddr < lo || haddr > hi) { if (lo == 0 && hi == MAX_HASH_ENTRIES) { dcc_pemsg(EX_DATAERR, emsg, "out of range hash address"); return DB_FOUND_SYSERR; } return DB_FOUND_LATER; } if (prcd_ck) *prcd_ck = 0; if (!map_hash(emsg, haddr, hash_st, 0)) return DB_FOUND_SYSERR; if (HE_IS_FREE(hash_st->d.h)) return DB_FOUND_EMPTY; if (!DB_HADDR_C_NULL(hash_st->d.h->bak)) return DB_FOUND_INTRUDER; /* We know that the current hash table entry is in its home slot. * It might be for the key or checksum we are looking for * or it might be for some other checksum with the same hash value. */ for (failsafe = 0; failsafe <= db_hash_len; ++failsafe) { if (HE_CMP(hash_st->d.h, type, sum)) { /* This hash table entry could be for our target * checksum. Read the corresponding record so we * decide whether we have a hash collision or we * have found a record containing our target checksum. * * find right type of checksum in the record */ db_ptr = DB_HPTR_EX(hash_st->d.h->rcd); found_ck = db_map_rcd_ck(emsg, rcd_st, db_ptr, type); if (!found_ck) return DB_FOUND_SYSERR; if (!memcmp(sum, found_ck->sum, sizeof(DCC_SUM))) { if (prcd_ck) *prcd_ck = found_ck; return DB_FOUND_IT; } } /* This DB record was a hash collision, or for a checksum * other than our target. * Fail if this is the end of the hash chain */ haddr_fwd = DB_HADDR_EX(hash_st->d.h->fwd); if (haddr_fwd == DB_HADDR_NULL) return DB_FOUND_CHAIN; if (DB_HADDR_INVALID(haddr_fwd)) { dcc_pemsg(EX_DATAERR, emsg, "broken hash chain fwd-link" " #%d %#x at %#x in %s", failsafe, haddr_fwd, haddr, db_hash_nm); return DB_FOUND_SYSERR; } if (!map_hash(emsg, haddr_fwd, hash_st, 0)) return DB_FOUND_SYSERR; haddr_bak = DB_HADDR_EX(hash_st->d.h->bak); if (haddr_bak != haddr) { dcc_pemsg(EX_DATAERR, emsg, "broken hash chain links #%d," " %#x-->%#x but %#x<--%#x in %s", failsafe, haddr, haddr_fwd, haddr_bak, haddr_fwd, db_hash_nm); return DB_FOUND_SYSERR; } haddr = haddr_fwd; } dcc_pemsg(EX_DATAERR, emsg, "infinite hash chain at %#x in %s", haddr, db_hash_nm); return DB_FOUND_SYSERR; } /* combine checksums */ DCC_TGTS db_sum_ck(DCC_TGTS prev, /* previous sum */ DCC_TGTS rcd_tgts, /* from the record */ DCC_CK_TYPES type UATTRIB) { DCC_TGTS res; /* This arithmetic must be commutative (after handling deleted * values), because inter-server flooding causes records to appear in * the database out of temporal order. * * DCC_TGTS_TOO_MANY can be thought of as a count of plus infinity. * DCC_TGTS_OK is like minus infinity. * DCC_TGTS_OK2 like half of minus infinity * DCC_TGTS_TOO_MANY (plus infinity) added to DCC_TGTS_OK (minus * infinity) or DCC_TGTS_OK2 yields DCC_TGTS_OK or DCC_TGTS_OK2. * * Reputations never reach infinity. * * Claims of not-spam from all clients are discarded as they arrive * and before here. They can only come from the local white list */ #define SUM_OK_DEL(p,r) { \ if (rcd_tgts == DCC_TGTS_OK || prev == DCC_TGTS_OK) \ return DCC_TGTS_OK; \ if (rcd_tgts == DCC_TGTS_OK2 || prev == DCC_TGTS_OK2) \ return DCC_TGTS_OK2; \ if (rcd_tgts == DCC_TGTS_DEL) \ return prev; \ } res = prev+rcd_tgts; if (res <= DCC_TGTS_TOO_MANY) return res; SUM_OK_DEL(prev, rcd_tgts); return DCC_TGTS_TOO_MANY; #undef SUM_OK_DEL } /* delete all reports that contain the given checksum */ static u_char /* 1=done, 0=broken database */ del_ck(DCC_EMSG emsg, DCC_TGTS *res, /* residual targets after deletion */ const DB_RCD *new, /* delete reports older than this one */ DCC_CK_TYPES type, /* delete this type of checksum */ DB_RCD_CK *prev_ck, /* starting with this one */ DB_STATE *prev_st) /* use this scratch state block */ { DB_PTR prev; *res = 0; for (;;) { /* delete reports that are older than the delete request */ if (dcc_ts_newer_ts(&new->ts, &prev_st->d.r->ts) && DB_RCD_ID(prev_st->d.r) != DCC_ID_WHITE) { DB_TGTS_RCD_SET(prev_st->d.r, 0); DB_TGTS_CK_SET(prev_ck, 0); SET_FLUSH_RCD(prev_st, 1); } else { /* sum reports that are not deleted */ *res = db_sum_ck(*res, DB_TGTS_RCD(prev_st->d.r), type); } prev = DB_PTR_EX(prev_ck->prev); if (prev == DB_PTR_NULL) return 1; prev_ck = db_map_rcd_ck(emsg, prev_st, prev, type); if (!prev_ck) return 0; } } /* see if the new and preceding records are from the same era */ static inline u_char /* 1=different eras */ ck_old_spam(const DB_RCD *new, const DCC_TS* prev, DCC_CK_TYPES type) { struct timeval tv; time_t secs; DCC_TS past; secs = db_parms.ex_secs[type].spam; if (secs > DCC_OLD_SPAM_SECS) secs = DCC_OLD_SPAM_SECS; dcc_ts2timeval(&tv, &new->ts); dcc_timeval2ts(&past, &tv, -secs); return dcc_ts_older_ts(prev, &past); } /* Mark reports made obsolete by a spam report * A new report of spam makes sufficiently old reports obsolete. * * Sufficiently recent non-obsolete reports make a new report obsolete, * or at least not worth spending bandwidth to flood. * "Sufficiently recent" should be defined so that this server and * its downstream flooding peers always have reports of the checksums * in the report. So we want to keep (not make obsolete) at least one * report per expiration duration. We cannot know the expiration durations * of our peers, but we known DB_EXPIRE_SPAMSECS_DEF_MIN which influences * DCC_OLD_SPAM_SECS. * * However, if another checksum in the new report was kept, then * prefer marking old checksums obsolete. * * db_sts.rcd points to the new record * db_sts.rcd2 points the the previous record and is changed */ static u_char /* 1=done, 0=broken database */ ck_obs_spam(DCC_EMSG emsg, const DB_RCD *new, DCC_TGTS new_tgts, DB_RCD_CK *new_ck, DCC_CK_TYPES type, /* check this type of checksum */ DB_RCD_CK *prev_ck, /* starting with this one */ DCC_TGTS prev_ck_tgts, u_char *keeping_new) /* 1=already keeping the new record */ { int limit; DB_PTR prev; limit = 100; for (;;) { /* preceding white listed entries make new entries obsolete */ if (DB_RCD_ID(db_sts.rcd2.d.r) == DCC_ID_WHITE) { new_ck->type_fgs |= DB_CK_FG_OBS; SET_FLUSH_RCD(&db_sts.rcd, 1); return 1; } if (DB_CK_OBS(prev_ck) || DB_TGTS_RCD(db_sts.rcd2.d.r) == 0) { /* notice duplicates and * don't look forever for recent non-obsolete report */ if (!memcmp(&new->ts, &db_sts.rcd2.d.r->ts, sizeof(new->ts)) || --limit == 0) { *keeping_new = 1; return 1; } } else if (prev_ck_tgts != DCC_TGTS_TOO_MANY) { /* Mark this predecessor obsolete because it * was before the checksum became spam. */ prev_ck->type_fgs |= DB_CK_FG_OBS; SET_FLUSH_RCD(&db_sts.rcd2, 0); /* continue backwards to mark more non-spam * predecessors obsolete */ } else if (!*keeping_new && ck_old_spam(new, &db_sts.rcd2.d.r->ts, type)) { /* We do not yet have a reason to keep the new report * and this predecessor is at or after a spam report. * We need the new report because it and the * predecessor are from different eras. * If the new report is not of spam, it will be * compressed with a preceding spam report. */ *keeping_new = 1; /* The predecessor is not needed if the new record * is for spam */ if (new_tgts == DCC_TGTS_TOO_MANY) { prev_ck->type_fgs |= DB_CK_FG_OBS; SET_FLUSH_RCD(&db_sts.rcd2, 0); } /* We're finished, because all older preceding reports * were marked obsolete when this older predecessor * was linked. */ return 1; } else { /* this predecessor is about as recent as the new * record, so the new record is unneeded noise that * would bloat other servers' databases. */ new_ck->type_fgs |= DB_CK_FG_OBS; return 1; } prev = DB_PTR_EX(prev_ck->prev); if (prev == DB_PTR_NULL) { /* the new record is a new report of spam */ *keeping_new = 1; return 1; } prev_ck = db_map_rcd_ck(emsg, &db_sts.rcd2, prev, type); if (!prev_ck) return 0; prev_ck_tgts = DB_TGTS_CK(prev_ck); } } /* mark extra server-ID declarations obsolete * * db_sts.rcd points to the new record * db_sts.rcd2 points the the previous record and is changed */ static u_char /* 1=done, 0=broken database */ srvr_id_ck(DCC_EMSG emsg, const DB_RCD *new, DB_RCD_CK *new_ck, DB_RCD_CK *prev_ck) /* starting with this one */ { DB_PTR prev; DCC_SRVR_ID new_id, prev_id; struct timeval tv; DCC_TS week_ts; dcc_ts2timeval(&tv, &new->ts); tv.tv_usec = 0; tv.tv_sec -= tv.tv_sec % (7*24*60*60); dcc_timeval2ts(&week_ts, &tv, 0); new_id = DB_RCD_ID(new); for (;;) { /* mark duplicate older declarations and deletions obsolete */ prev_id = DB_RCD_ID(db_sts.rcd2.d.r); if (!DCC_ID_SRVR_TYPE(prev_id) || DB_TGTS_RCD(db_sts.rcd2.d.r) == 0) { if (dcc_ts_newer_ts(&db_sts.rcd2.d.r->ts, &new->ts)) { new_ck->type_fgs |= DB_CK_FG_OBS; SET_FLUSH_RCD(&db_sts.rcd, 1); } else { prev_ck->type_fgs |= DB_CK_FG_OBS; SET_FLUSH_RCD(&db_sts.rcd2, 1); } return 1; } /* Keep many identical type declarations as a kludge to ensure * that rewound flooding sends type declarations early. * Keep only one delcaration per week. */ if (DCC_ID_SRVR_TYPE(new_id)) { /* Zap the new declaration and stop if the * new declaration is older than the predecessor. */ if (dcc_ts_newer_ts(&db_sts.rcd2.d.r->ts, &new->ts)) { new_ck->type_fgs |= DB_CK_FG_OBS; SET_FLUSH_RCD(&db_sts.rcd, 1); return 1; } /* Stop when we find a duplicate type declaration * of a different week */ if (prev_id == new_id && dcc_ts_older_ts(&db_sts.rcd2.d.r->ts, &week_ts)) { return 1; } /* continue zapping preceding declarations */ prev_ck->type_fgs |= DB_CK_FG_OBS; SET_FLUSH_RCD(&db_sts.rcd2, 1); } prev = DB_PTR_EX(prev_ck->prev); if (prev == DB_PTR_NULL) return 1; prev_ck = db_map_rcd_ck(emsg, &db_sts.rcd2, prev, DCC_CK_SRVR_ID); if (!prev_ck) return 0; } } /* Install pointers in the hash table for a record and fix the accumulated * counts in the record pointed to by db_sts.rcd * Use db_sts.rcd, db_sts.hash, db_sts.rcd2, db_sts.free, db_sts.tmp * The caller must deal with db_make_dirty() */ u_char /* 0=failed, 1=done */ db_link_rcd(DCC_EMSG emsg, DB_HADDR lo, DB_HADDR hi) { DCC_TGTS res; DB_RCD *rcd; DB_RCD_CK *prev_ck; DB_RCD_CK *rcd_ck; DCC_CK_TYPES rcd_type; DCC_TGTS rcd_tgts, prev_ck_tgts; int ck_num; DB_HADDR haddr; u_char keeping_new; keeping_new = 0; rcd = db_sts.rcd.d.r; rcd_tgts = DB_TGTS_RCD_RAW(rcd); rcd_ck = rcd->cks; ck_num = DB_NUM_CKS(rcd); if (ck_num > DIM(rcd->cks)) { dcc_pemsg(EX_OSFILE, emsg, "bogus checksum count %#x at "L_HPAT" in %s", rcd->fgs_num_cks, db_sts.rcd.s.rptr, db_nm); return 0; } for (; ck_num > 0; --ck_num, ++rcd_ck) { rcd_type = DB_CK_TYPE(rcd_ck); if (!DCC_CK_OK_DB(grey_on, rcd_type)) { dcc_pemsg(EX_OSFILE, emsg, "invalid checksum type %s at "L_HPAT" in %s", DB_TYPE2STR(rcd_type), db_sts.rcd.s.rptr, db_nm); return 0; } rcd_ck->prev = DB_PTR_CP(DB_PTR_NULL); /* Do not link paths or whitelist file and line numbers */ if (rcd_type == DCC_CK_FLOD_PATH) { DB_TGTS_CK_SET(rcd_ck, 0); continue; } /* Do not link or total some checksums unless they are * whitelist entries. If they are whitelist entries, they * will eventually get set to DCC_TGTS_OK or DCC_TGTS_OK2. * Blacklist entries are noticed later by server-ID * or do not matter DCC_TGTS_TOO_MANY. */ if (DB_TEST_NOKEEP(db_parms.nokeep_cks, rcd_type) && DB_RCD_ID(rcd) != DCC_ID_WHITE) { DB_TGTS_CK_SET(rcd_ck, 1); continue; } res = (rcd_tgts == DCC_TGTS_DEL) ? 0 : rcd_tgts; switch (db_lookup(emsg, rcd_type, rcd_ck->sum, lo, hi, &db_sts.hash, &db_sts.rcd2, &prev_ck)) { case DB_FOUND_SYSERR: return 0; case DB_FOUND_LATER: continue; case DB_FOUND_IT: /* We found the checksum * Update the hash table to point to the new record */ DB_HPTR_CP(db_sts.hash.d.h->rcd, db_sts.rcd.s.rptr); SET_FLUSH_HE(&db_sts.hash); /* link new record to existing record */ rcd_ck->prev = DB_PTR_CP(db_sts.rcd2.s.rptr); /* delete predecessors to a delete request * and compute the remaining sum */ if (rcd_tgts == DCC_TGTS_DEL) { if (!del_ck(emsg, &res, rcd, rcd_type, prev_ck, &db_sts.rcd2)) return 0; /* delete requests are obsolete if the * checksum is whitelisted */ if (res == DCC_TGTS_OK || res == DCC_TGTS_OK2) rcd_ck->type_fgs |= DB_CK_FG_OBS; break; } /* Simple checksum with a predecessor * This does not do the substantial extra work * to notice all delete requests that arrived early. * That problem is handled by the incoming flood * duplicate report detection mechanism. * We must detect precessors that were deleted because * they are partial duplicates of the new record. */ prev_ck_tgts = DB_TGTS_CK(prev_ck); if (DB_RCD_SUMRY(rcd)) res = prev_ck_tgts; else res = db_sum_ck(prev_ck_tgts, res, rcd_type); if ((res == DCC_TGTS_OK || res == DCC_TGTS_OK2 || (DB_RCD_ID(db_sts.rcd2.d.r) == DCC_ID_WHITE)) && DB_RCD_ID(rcd) != DCC_ID_WHITE){ /* obsolete whitelisted checksums */ rcd_ck->type_fgs |= DB_CK_FG_OBS; break; } if (res == DCC_TGTS_TOO_MANY) { /* mark obsolete unneeded reports of spam */ if (!DB_CK_OBS(rcd_ck) && !ck_obs_spam(emsg, rcd, rcd_tgts, rcd_ck, rcd_type, prev_ck, prev_ck_tgts, &keeping_new)) return 0; /* (broken database) */ } else if (rcd_type == DCC_CK_SRVR_ID) { /* mark obsolete server-ID assertions */ if (!DB_CK_OBS(rcd_ck) && !srvr_id_ck(emsg, rcd, rcd_ck, prev_ck)) return 0; /* (broken database) */ } break; case DB_FOUND_EMPTY: /* We found an empty hash table slot. * Update the slot to point to our new record * after removing it from the free list, * which marks it dirty. */ if (!unlink_free_hash(emsg, &db_sts.hash)) return 0; DB_HPTR_CP(db_sts.hash.d.h->rcd, db_sts.rcd.s.rptr); HE_MERGE(db_sts.hash.d.h,rcd_type, rcd_ck->sum); if (res >= BULK_THRESHOLD) keeping_new = 1; break; case DB_FOUND_CHAIN: /* We found a hash collision, a chain of 1 or more * records with the same hash value. * Get a free slot, link it to the end of the * existing chain, and point it to the new record. * The buffer containing the free slot is marked * dirty when it is removed from the free list. */ if (!get_free_hash(emsg, db_sts.hash.s.haddr)) return 0; DB_HADDR_CP(db_sts.free.d.h->bak, db_sts.hash.s.haddr); DB_HADDR_CP(db_sts.hash.d.h->fwd, db_sts.free.s.haddr); DB_HPTR_CP(db_sts.free.d.h->rcd, db_sts.rcd.s.rptr); HE_MERGE(db_sts.free.d.h,rcd_type, rcd_ck->sum); SET_FLUSH_HE(&db_sts.hash); if (res >= BULK_THRESHOLD) keeping_new = 1; break; case DB_FOUND_INTRUDER: /* The home hash slot for our key contains an * intruder. Move it to a new free slot */ if (!get_free_hash(emsg, db_sts.hash.s.haddr)) return 0; *db_sts.free.d.h = *db_sts.hash.d.h; /* re-link the neighbors of the intruder */ haddr = DB_HADDR_EX(db_sts.free.d.h->bak); if (haddr == DB_HADDR_NULL) { dcc_pemsg(EX_DATAERR, emsg, "bad hash chain reverse link at %#x" " in %s", haddr, db_hash_nm); return 0; } if (!map_hash(emsg, haddr, &db_sts.tmp, 0)) return 0; DB_HADDR_CP(db_sts.tmp.d.h->fwd, db_sts.free.s.haddr); SET_FLUSH_HE(&db_sts.tmp); haddr = DB_HADDR_EX(db_sts.hash.d.h->fwd); if (haddr != DB_HADDR_NULL) { if (!map_hash(emsg, haddr, &db_sts.tmp, 0)) return 0; DB_HADDR_CP(db_sts.tmp.d.h->bak, db_sts.free.s.haddr); SET_FLUSH_HE(&db_sts.tmp); } /* install the new entry in its home slot */ DB_HADDR_CP(db_sts.hash.d.h->fwd, DB_HADDR_NULL); DB_HADDR_CP(db_sts.hash.d.h->bak, DB_HADDR_NULL); DB_HPTR_CP(db_sts.hash.d.h->rcd, db_sts.rcd.s.rptr); HE_MERGE(db_sts.hash.d.h,rcd_type, rcd_ck->sum); SET_FLUSH_HE(&db_sts.hash); if (res >= BULK_THRESHOLD) keeping_new = 1; break; } /* Fix the checksum's total in the record */ DB_TGTS_CK_SET(rcd_ck, res); SET_FLUSH_RCD(&db_sts.rcd, 0); } return db_set_sizes(emsg); } /* Add a record to the database and the hash table * The record must be known to be valid * Use db_sts.rcd, db_sts.hash, db_sts.rcd2, db_sts.free, db_sts.tmp * On exit db_sts.rcd points to the new record in the database */ DB_PTR /* 0=failed */ db_add_rcd(DCC_EMSG emsg, const DB_RCD *new_rcd) { u_int new_rcd_len, pad_len; DB_PTR new_db_csize, rcd_pos, new_page_num; DB_BUF *b; if (!db_make_dirty(emsg)) return 0; new_rcd_len = (sizeof(*new_rcd) - sizeof(new_rcd->cks) + (DB_NUM_CKS(new_rcd) * sizeof(new_rcd->cks[0]))); rcd_pos = db_csize; new_db_csize = rcd_pos+new_rcd_len; new_page_num = DB_PTR2PG_NUM(new_db_csize, db_pagesize); if (new_page_num == DB_PTR2PG_NUM(db_csize, db_pagesize)) { if (!map_db(emsg, rcd_pos, new_rcd_len, &db_sts.rcd, 0)) return 0; } else { /* fill with zeros to get past a page boundary. */ pad_len = new_page_num*db_pagesize - db_csize; pad_len = (((pad_len + DB_RCD_HDR_LEN-1) / DB_RCD_HDR_LEN) * DB_RCD_HDR_LEN); if (pad_len != 0) { if (!map_db(emsg, db_csize, pad_len, &db_sts.rcd, 0)) return 0; memset(db_sts.rcd.d.r, 0, pad_len); db_set_flush(&db_sts.rcd, 1, pad_len); db_csize += pad_len; rcd_pos = db_csize; new_db_csize = rcd_pos+new_rcd_len; } /* extend the file by writing a full page to it with write(), * because extending by mmap() often does not work */ db_fsize = db_csize+db_pagesize; if (!map_db(emsg, rcd_pos, db_pagesize, &db_sts.rcd, 1)) return 0; b = db_sts.rcd.b; b->flush = (DB_BUF_FM)-1; /* push new page to disk if dblist or dbclean is running */ if (db_minimum_map) { rel_db_state(&db_sts.rcd); if (!buf_munmap(emsg, b)) return 0; if (!map_db(emsg, rcd_pos, new_rcd_len, &db_sts.rcd, 0)) return 0; } } /* install the record */ memcpy(db_sts.rcd.d.r, new_rcd, new_rcd_len); /* Mark its buffer to be sent to the disk to keep the database * as good as possible even if we crash. We don't need to worry * about later changes to the hash links because dbclean will * rebuild them if we crash */ db_set_flush(&db_sts.rcd, 1, new_rcd_len); db_csize = new_db_csize; /* install pointers in the hash table * and update the total counts in the record */ if (!db_link_rcd(emsg, 0, MAX_HASH_ENTRIES)) return 0; ++db_stats.adds; return rcd_pos; }