/* Distributed Checksum Clearinghouse
 *
 * database lister
 *
 * Copyright (c) 2005 by Rhyolite Software
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE
 * 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.2.74-1.59 $Revision$
 */

#include "srvr_defs.h"
#include "dcc_xhdr.h"
#include "dcc_ck.h"

static DCC_EMSG dcc_emsg;

static int verbose;
#define VERBOSE_HASH 3
static u_char no_hash;
static u_char no_data;
static u_char matching;

static struct {
    DCC_CK_TYPES type;
    DCC_SUM	sum;
} search_cksums[16];
static int num_search_cksums;

static DCC_TS search_ts[16];
static int num_search_ts;

DCC_SRVR_ID search_ids[16];
static int num_search_ids;

static DB_PTR page_offset;
static DB_PTR dbaddr;
static int max_pathlen;

static DB_HADDR hash_fsize;
static char dcc_db_nm[] = DB_DCC_NAME;
static char grey_db_nm[] = DB_GREY_NAME;
static char *def_argv[2];
static const char *homedir;

static const DB_VERSION_BUF version_buf = DB_VERSION_STR;

static const char hash_magic[sizeof(HASH_ENTRY)] = DB_HASH_MAGIC;


static int save_cksum(DCC_EMSG, DCC_WF *, const char *, int, \
		      DCC_CK_TYPES, DCC_SUM, DCC_TGTS);
static void list_cleaned(const DB_MAGIC *);
static void list_flod(void);
static DB_PTR list_db(void);
static int open_hash(const char *);
static void list_hash(int, const char *);


static void NRATTRIB
usage(void)
{
	dcc_logbad(EX_USAGE, "usage: [-vVHD] [-G on | off] [-h homedir]"
		   " [-C 'type h1 h2 h3 h4']\n"
		   "   [-I server-Id] [-A dbptr] [-L pathlen] [-P pages]"
		   " [-T timestamp]\n"
		   "   [file file2 ...]");
}



int NRATTRIB
main(int argc, char **argv)
{
	int file_num;
	DCC_PATH hash_nm;
	int hash_fd;
	u_char print_version = 0;
	char tbuf[80];
	const char *cp;
	struct timeval tv;
	int usecs;
	struct tm tm;
	char *p;
	int i;

	dcc_syslog_init(0, argv[0], 0);
	dup2(1, 2);			/* put error messages in context */

	while ((i = getopt(argc, argv, "vVHDG:h:C:I:A:L:P:T:")) != EOF) {
		switch (i) {
		case 'v':
			++verbose;
			break;

		case 'V':
			fprintf(stderr, DCC_VERSION"\n");
			print_version = 1;
			break;

		case 'H':
			no_hash = 1;
			break;

		case 'D':
			no_data = 1;
			break;

		case 'G':
			if (!strcasecmp(optarg, "on")) {
				grey_on = 1;
			} else if (!strcasecmp(optarg, "off")) {
				grey_on = 0;
			} else {
				usage();
			}
			break;

		case 'h':
			homedir = optarg;
			break;

		case 'C':
			if (num_search_cksums >= DIM(search_cksums)) {
				dcc_error_msg("too many -C checksums");
				break;
			}
			no_hash = 1;
			matching = 1;
			cp = dcc_parse_word(0, tbuf, sizeof(tbuf),
					    optarg, "checksum type", 0, 0);
			if (!cp)
				exit(1);
			if (!strcasecmp(tbuf, "hex")) {
				cp = dcc_parse_word(0, tbuf, sizeof(tbuf),
						    cp, "checksum type",
						    0, 0);
				if (!cp)
					exit(1);
			}
			if (!dcc_parse_hex_ck(dcc_emsg, 0, 0, 0,
					      tbuf, cp, 0, save_cksum))
				dcc_logbad(EX_USAGE, "%s", dcc_emsg);
			break;

		case 'I':
			if (num_search_ids >= DIM(search_ids)) {
				dcc_error_msg("too many -I IDs");
				break;
			}
			if (!dcc_get_srvr_id(dcc_emsg,
					     &search_ids[num_search_ids],
					     optarg, 0, 0, 0))
				dcc_logbad(dcc_ex_code, "%s", dcc_emsg);
			++num_search_ids;
			no_hash = 1;
			matching = 1;
			break;

		case 'A':
			dbaddr = strtoul(optarg, &p, 0);
			if (*p != '\0')
				dcc_logbad(EX_USAGE,
					   "invalid database address \"%s\"",
					   optarg);
			no_hash = 1;
			matching = 1;
			break;

		case 'L':
			max_pathlen = strtoul(optarg, &p, 0);
			if (*p != '\0')
				dcc_logbad(EX_USAGE,
					   "invalid path length \"%s\"",
					   optarg);
			no_hash = 1;
			matching = 1;
			break;

		case 'P':
			page_offset = strtoul(optarg, &p, 0);
			if (*p != '\0')
				dcc_logbad(EX_USAGE,
					   "invalid number of pages \"%s\"",
					   optarg);
			no_hash = 1;
			matching = 1;
			break;

		case 'T':
			if (num_search_ts >= DIM(search_ts)) {
				dcc_error_msg("too many -I timestamps");
				break;
			}
			memset(&tm, 0, sizeof(tm));
			if (7 != sscanf(optarg, "%d/%d/%d %d:%d:%d.%d",
					&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
					&tm.tm_hour, &tm.tm_min, &tm.tm_sec,
					&usecs)
			    || tm.tm_mon <= 0)
				dcc_logbad(EX_USAGE,"bad timestamp \"%s\"",
					   optarg);
			--tm.tm_mon;
			tm.tm_year += 100;
			tv.tv_sec = mktime(&tm);
			if (tv.tv_sec == -1
			    || usecs < 0
			    || usecs > 1000000)
				dcc_logbad(EX_USAGE,"invalid timestamp \"%s\"",
					   optarg);
			tv.tv_usec = usecs;
			dcc_timeval2ts(search_ts[num_search_ts++], &tv, 0);
			no_hash = 1;
			matching = 1;
			break;

		default:
			usage();
		}
	}
	argc -= optind;
	argv += optind;
	def_argv[0] = grey_on ? grey_db_nm : dcc_db_nm;
	if (argc == 0) {
		if (print_version)
			exit(EX_OK);
		argv = def_argv;
		argc = 1;
	}

	dcc_clnt_unthread_init();

	if (!dcc_cdhome(dcc_emsg, homedir))
		dcc_error_msg("%s", dcc_emsg);

	if (num_search_cksums != 0) {
		if (no_data)
			dcc_logbad(EX_USAGE, "-C and -D are incompatible");
	}

	if (dbaddr != 0 && page_offset != 0)
		dcc_logbad(EX_USAGE, "-P and -A are incompatible");

	for (file_num = 1; *argv != 0; ++argv, ++file_num) {
		BUFCPY(db_nm, *argv);
		snprintf(hash_nm, sizeof(hash_nm), "%s"DB_HASH_SUFFIX, db_nm);

		if (file_num != 1)
			fputc('\n', stdout);
		if (verbose || argc > 1)
			printf("  %s\n", DCC_NM2PATH(db_nm));

		hash_fd = open_hash(hash_nm);
		db_csize = list_db();
		if (!db_csize)
			continue;

		if (hash_fd >= 0)
			list_hash(hash_fd, hash_nm);

		close(hash_fd);
	}

	exit(EX_OK);
}



static int
save_cksum(DCC_EMSG emsg UATTRIB, DCC_WF *wf UATTRIB,
	   const char *fnm UATTRIB, int lineno UATTRIB,
	   DCC_CK_TYPES type, DCC_SUM sum, DCC_TGTS tgts UATTRIB)
{
	search_cksums[num_search_cksums].type = type;
	memcpy(search_cksums[num_search_cksums].sum, sum, sizeof(DCC_SUM));
	++num_search_cksums;
	return 1;
}



static DB_PTR				/* size of contents or 0=failed */
list_db(void)
{
#define RCD_PAT "%-27s %-8.8s %-10.10s %7s      "L_H8PAT"\n"
#define RCD_PAT1(s) RCD_PAT, s,  "", "", ""
	int fd;
	DB_MAGIC magic_buf;
	struct stat sb;
	DB_RCD rcd;
	DB_RCD_CK *rcd_ck;
	int rcd_len, rcds, white_rcds, sums, white_sums;
	DB_PTR rcd_lim, rcd_link, rcd_prev;
	DCC_TGTS tgts;
	char ts_buf[40], id_buf[30];
	char tgts_buf[20];
	char ck_buf[sizeof(DCC_SUM)*3+2];
	enum {NO_LB,			/* no label */
		WHITE_LB,		/* whitelist section labelled */
		DATE_LB			/* normal section labelled */
	} last_lb = NO_LB;
	u_char rpt_match, kept, printed_rcd;
	int i;

	fd = open(db_nm, O_RDONLY, 0);
	if (fd < 0) {
		dcc_error_msg("open(%s): %s", DCC_NM2PATH(db_nm), ERROR_STR());
		close(fd);
		return 0;
	}

	i = read_db(dcc_emsg, &magic_buf, sizeof(magic_buf),
		    fd, 0, db_nm);
	if (i != sizeof(magic_buf)) {
		if (i < 0)
			dcc_error_msg("%s", dcc_emsg);
		else
			dcc_error_msg("found only %d bytes of magic in %s",
				      i, DCC_NM2PATH(db_nm));
		close(fd);
		return 0;
	}

	if (memcmp(magic_buf.s.version, version_buf,
		   sizeof(magic_buf.s.version))) {
		dcc_error_msg("%s contains the wrong magic \"%.*s\"",
			      DCC_NM2PATH(db_nm),
			      ISZ(version_buf), magic_buf.s.version);
	}
	if (0 > fstat(fd, &sb)) {
		dcc_error_msg("stat(%s): %s",
			      DCC_NM2PATH(db_nm), ERROR_STR());
		close(fd);
		return 0;
	}
	if ((DB_PTR)sb.st_size < magic_buf.s.db_csize) {
		dcc_error_msg("%s says it contains "L_DPAT
			      " bytes instead of "OFF_DPAT,
			      DCC_NM2PATH(db_nm),
			      magic_buf.s.db_csize, sb.st_size);
	}

	if (verbose > 0) {
		list_cleaned(&magic_buf);
		list_flod();
	}

	if (sb.st_size == sizeof(magic_buf)) {
		dcc_error_msg("%s contains no checksums",
			      DCC_NM2PATH(db_nm));
		close(fd);
		return 0;
	}

	if (no_data)
		return magic_buf.s.db_csize;

	/* list the records in the database */
	rcds = 0;
	white_rcds = 0;
	sums = 0;
	white_sums = 0;
	if (dbaddr != 0) {
		page_offset = sb.st_size - dbaddr;
		if (page_offset <= 0) {
			page_offset = 0;
		} else {
			page_offset = ((page_offset + magic_buf.s.page_size -1)
				       / magic_buf.s.page_size);
		}
	}
	if (page_offset == 0) {
		rcd_link = DB_PTR_BASE;
	} else {
		rcd_link = sb.st_size / magic_buf.s.page_size;
		if (rcd_link < page_offset)
			rcd_link = 0;
		else
			rcd_link -= page_offset;
		rcd_link *= magic_buf.s.page_size;
		if (rcd_link < DB_PTR_BASE)
			rcd_link = DB_PTR_BASE;
	}
	rcd_lim = ((verbose > 2)
		   ? (DB_PTR)sb.st_size : (DB_PTR)magic_buf.s.db_csize);
	printed_rcd = 0;
	read_rcd_invalidate();
	while (rcd_link < rcd_lim) {
		rcd_len = read_rcd(dcc_emsg, &rcd, fd, rcd_link, db_nm);
		if (rcd_len <= 0) {
			if (rcd_len == 0)
				break;
			/* ignore fragmentary reports at the end */
			if (rcd_link > magic_buf.s.db_csize - DB_RCD_PAD) {
				printf(RCD_PAT1("    page padding"), rcd_link);
				printed_rcd = 1;
				break;
			}
			dcc_error_msg("%s", dcc_emsg);
			close(fd);
			return 0;
		}

		/* usually skip padding */
		if (rcd.fgs_num_cks == 0) {
			if (verbose > 1) {
				printf(RCD_PAT1("    page padding"), rcd_link);
				printed_rcd = 1;
			}
			rcd_link += rcd_len;
			continue;
		}

		/* skip until the desired first address */
		if (rcd_link < dbaddr) {
			rcd_link += rcd_len;
			continue;
		}

		rpt_match = 0;

		/* if we have a target checksum, display only its reports */
		if (num_search_ids > 0) {
			for (i = 0; i < num_search_ids; ++i) {
				if (search_ids[i] == DB_RCD_ID(&rcd)) {
					rpt_match = 1;
					goto got_id;
				}
			}
			rcd_link += rcd_len;
			continue;
got_id:;
		}

		if (num_search_cksums > 0) {
			for (i = 0; i >= 0 && i < num_search_cksums; ++i) {
				for (rcd_ck = rcd.cks;
				     rcd_ck < &rcd.cks[DB_NUM_CKS(&rcd)];
				     ++rcd_ck) {
					if (DB_CK_TYPE(rcd_ck)
					    == search_cksums[i].type
					    && !memcmp(search_cksums[i].sum,
						       rcd_ck->sum,
						       sizeof(DCC_SUM))) {
					    rpt_match = 1;
					    goto got_ck;
					}
				}
			}
			rcd_link += rcd_len;
			continue;
got_ck:;
		}

		if (num_search_ts > 0
		    && DB_RCD_ID(&rcd) != DCC_ID_WHITE) {
			for (i = 0; i < num_search_ts; ++i) {
				if (!memcmp(rcd.ts, search_ts[i],
					    sizeof(rcd.ts))) {
					rpt_match = 1;
					goto got_ts;
				}
			}
			rcd_link += rcd_len;
			continue;
got_ts:;
		}

		if (max_pathlen != 0
		    && DB_RCD_ID(&rcd) != DCC_ID_WHITE) {
			DCC_FLOD_PATH_ID *id;
			DCC_SRVR_ID psrvr;
			int pathlen = 0;

			for (rcd_ck = rcd.cks;
			     rcd_ck < &rcd.cks[DB_NUM_CKS(&rcd)]
			     && pathlen < max_pathlen;
			     ++rcd_ck) {
				if (DB_CK_TYPE(rcd_ck) != DCC_CK_FLOD_PATH)
					break;
				id = (DCC_FLOD_PATH_ID *)&rcd_ck->sum;
				for (i = 0; i < DCC_NUM_FLOD_PATH; ++i, ++id) {
					psrvr = ((id->hi<<8) | id->lo);
					if (psrvr == DCC_ID_INVALID)
					    break;
					++pathlen;
				}
			}
			if (pathlen < max_pathlen) {
				rcd_link += rcd_len;
				continue;
			}
			rpt_match = 1;
		}

		++rcds;
		if (DB_RCD_ID(&rcd) == DCC_ID_WHITE) {
			++white_rcds;
			if (last_lb != WHITE_LB) {
				last_lb = WHITE_LB;
				strcpy(ts_buf, "\n"DCC_XHDR_ID_WHITE);
			} else {
				ts_buf[0] = '\0';
			}
		} else {
			if (last_lb != DATE_LB) {
				last_lb = DATE_LB;
				if (rpt_match || verbose > 0)
					putchar('\n');
			}
			if (rpt_match || verbose > 0)
				dcc_ts2str(ts_buf, sizeof(ts_buf), rcd.ts);
		}

		/* display separator between whitelist and ordinary entries
		 * along with the timestamp and the rest of the first line
		 * of a report */
		if (rpt_match
		    || verbose >= 2
		    || (verbose > 0 && DB_RCD_ID(&rcd) != DCC_ID_WHITE)) {
			if (last_lb == DATE_LB) {
				tgts = DB_TGTS_RCD_RAW(&rcd);
				printf(RCD_PAT, ts_buf,
				       (tgts == 0)
				       ? "deleted"
				       : dcc_cnt2str(tgts_buf, sizeof(tgts_buf),
						     tgts, grey_on),
				       dcc_srvr_id2str(id_buf, sizeof(id_buf),
						       DB_RCD_ID(&rcd)),
				       DB_RCD_TRIMMED(&rcd) ? "trimmed"
				       : DB_RCD_SUMRY(&rcd) ? "summary"
				       : DB_RCD_DELAY(&rcd) ? "delayed"
				       : "",
				       rcd_link);
			} else {
				printf(RCD_PAT1(ts_buf), rcd_link);
			}
			printed_rcd = 1;
		}

		/* display a report */
		for (rcd_ck = rcd.cks;
		     rcd_ck < &rcd.cks[DB_NUM_CKS(&rcd)];
		     ++rcd_ck) {
			++sums;
			/* always count whitelist entries,
			 * but display only as requested */
			if (DB_RCD_ID(&rcd) == DCC_ID_WHITE) {
				++white_sums;
				if (verbose < 2 && !rpt_match)
					continue;
			} else {
				if (verbose < 1 && !rpt_match)
					continue;
			}

			/* decode the special checksum that is a path */
			if (DB_CK_TYPE(rcd_ck)== DCC_CK_FLOD_PATH) {
				DCC_SRVR_ID psrvr;
				DCC_FLOD_PATH_ID *path_id, *path_id_lim;
				const char *s;

				path_id = (DCC_FLOD_PATH_ID *)rcd_ck->sum;
				path_id_lim = path_id+DCC_NUM_FLOD_PATH;
				s = "     path: ";
				do {
					psrvr = (path_id->hi<<8) | path_id->lo;
					if (psrvr == DCC_ID_INVALID)
					    break;
					printf("%s%d", s, psrvr);
					s = "<-";
				} while (++path_id < path_id_lim);
				printf("%s\n", s);
				continue;
			}

			kept = (!DB_TEST_NOKEEP(magic_buf.s.nokeep_cks,
						DB_CK_TYPE(rcd_ck))
				|| DB_RCD_ID(&rcd) == DCC_ID_WHITE);

			printf(" %c%-12.12s %-10.10s %-31s",
			       DB_CK_OBS(rcd_ck) ? '*' : ' ',
			       dcc_type2str_err(DB_CK_TYPE(rcd_ck), 0, 1),
			       !kept
			       ? "" : dcc_cnt2str(tgts_buf, sizeof(tgts_buf),
						  DB_TGTS_CK(rcd_ck), grey_on),
			       dcc_ck2str(ck_buf, sizeof(ck_buf),
					  DB_CK_TYPE(rcd_ck), rcd_ck->sum));
			rcd_prev = DB_PTR_EX(rcd_ck->prev);
			if (rcd_prev == DB_PTR_NULL)
				printf(" %8s", "");
			else if (DB_PTR_IS_BAD_FULL(rcd_prev))
				printf(" bogus "L_H8PAT, rcd_prev);
			else
				printf(" "L_H8PAT, rcd_prev);
			if (db_hash_len != 0
			    && kept)
				printf(" %x", db_hash(DB_CK_TYPE(rcd_ck),
						      rcd_ck->sum));
			putchar('\n');
		}

		rcd_link += rcd_len;
	}

	close(fd);
	if (verbose || matching) {
		/* print address after the last record,
		 * but only if we printed a record */
		if (printed_rcd)
			printf(RCD_PAT1(""), rcd_link);
		putchar('\n');
	}
	if (!matching) {
		printf("%7d records containing %d checksums\n",
		       rcds, sums);
		if (!grey_on)
			printf("%7d non-whitelist records containing"
			       " %d checksums\n",
			       rcds-white_rcds, sums-white_sums);
	}
	return magic_buf.s.db_csize;
}



const char *
secs2str(char *buf, u_int buf_len, u_int32_t secs)
{
	const char *dim;

	if (!buf_len)
		return 0;
	if (secs == 0) {
		STRLIMCPY(buf, buf_len, "never");
	} else {
		if (secs % (24*60*60) == 0) {
			dim = " day";
			secs /= (24*60*60);
		} else if (secs % (60*60) == 0) {
			dim = " hour";
			secs /= (60*60);
		} else {
			dim = "";
		}
		snprintf(buf, buf_len, "%d%s%s",
			 secs, dim, (secs > 1 && dim) ? "s" : "");
	}
	return buf;
}



/* display the expiration information in the database header */
static void
list_cleaned(const DB_MAGIC *magic)
{
#define CLEANED_PAT	"     %12s %c %17.17s  %8s  %8s  %5s"
#define CLEANED_PAT_VB3	"     %12s %c %17.17s  %8s  %8s  %5s %8s"
	DCC_CK_TYPES type;
	char allsecs_buf[20];
	char spamsecs_buf[20];
	char tgts_buf[20];
	char flod_thold_buf[20];

	printf("     %s%spage size %#-8x s/n %s\n",
	       (magic->s.flags & DB_MAGIC_ST_GREY)
	       ? "Greylist  " : "",
	       !(magic->s.flags & DB_MAGIC_ST_SELF_CLEAN) ? ""
	       : !(magic->s.flags & DB_MAGIC_ST_SELF_CLEAN2) ? "self-cleaned  "
	       : "twice self-cleaned  ",
	       magic->s.page_size, dcc_ts2str_err(magic->s.sn));

	if (magic->s.flags & DB_MAGIC_ST_GREY)
		printf(CLEANED_PAT,
		       "", ' ', "expired    ", "window", "pass", "threshold");
	else
		printf(CLEANED_PAT,
		       "", ' ', "expired    ", "", "bulk  ", "threshold");
	for (type = DCC_CK_TYPE_FIRST; type <= DCC_CK_TYPE_LAST; ++type) {
		if ((type == DCC_CK_SRVR_ID
		     || DB_TEST_NOKEEP(magic->s.nokeep_cks, type))
		    && verbose < 3)
			continue;
		secs2str(allsecs_buf, sizeof(allsecs_buf),
			 magic->s.ex_secs[type].all);
		if (magic->s.ex_secs[type].all != magic->s.ex_secs[type].spam
		    || verbose >= 3) {
			secs2str(spamsecs_buf, sizeof(spamsecs_buf),
				 magic->s.ex_secs[type].spam);
			if ((magic->s.flags & DB_MAGIC_ST_GREY)
			    && verbose < 3) {
				tgts_buf[0] = '\0';
			} else {
				dcc_cnt2str(tgts_buf, sizeof(tgts_buf),
					    magic->s.ex_secs[type].clean_thold,
					    grey_on);
			}
		} else {
			spamsecs_buf[0] = '\0';
			tgts_buf[0] = '\0';
		}
		dcc_cnt2str(flod_thold_buf, sizeof(flod_thold_buf),
			    magic->s.flod_tholds[type], grey_on);
		printf(verbose >= 3 ? "\n"CLEANED_PAT_VB3 : "\n"CLEANED_PAT,
		       dcc_type2str_err(type, 0, 1),
		       DB_TEST_NOKEEP(magic->s.nokeep_cks, type) ? '*' : ' ',
		       dcc_ts2str_err(magic->s.ex_ts[type].all),
		       allsecs_buf, spamsecs_buf, tgts_buf, flod_thold_buf);
	}
#undef CLEANED_PAT
}



static void
list_flod(void)
{
	FLOD_MMAP *mp;
	DCC_PATH path;
	u_char first;

	/* display the flood map only for default database */
	if (strcmp(dcc_fnm2path(path, db_nm), DCC_NM2PATH(def_argv[0]))) {
		putchar('\n');
	} else if (!flod_mmap(dcc_emsg, 0, 0, 0, 1)) {
		dcc_error_msg("\n\n%s", dcc_emsg);
	} else if (strcmp(flod_mmaps->magic, FLOD_MMAP_MAGIC)) {
		dcc_error_msg("\n\n%s contains the wrong magic \"%.*s\"",
			      FLOD_MMAP_PATH(grey_on),
			      ISZ(flod_mmaps->magic), flod_mmaps->magic);
		if (!flod_unmap(dcc_emsg, 0))
			dcc_error_msg("%s", dcc_emsg);
	} else {
		first = 1;
		fputs("\n\n  ", stdout);
		fputs(FLOD_MMAP_NM(grey_on), stdout);
		printf("  s/n %s   delay position "L_HPAT"\n",
		       dcc_ts2str_err(flod_mmaps->sn),
		       flod_mmaps->delay_pos);
		for (mp = flod_mmaps->mmaps;
		     mp <= LAST(flod_mmaps->mmaps);
		     ++mp) {
			if (mp->hostname[0] == '\0')
				continue;
			if (first) {
				first = 0;
				printf("%32s %5s %9s %s\n",
				       "peer", "", "ID", "position");
			}
			printf("%32.*s,%-5.*s %9d "L_H8PAT"%s%s%s%s%s\n",
			       ISZ(mp->hostname), mp->hostname,
			       ISZ(mp->portname), mp->portname,
			       mp->rem_id,
			       mp->confirm_pos,
			       (mp->flags & OFLOD_MMAP_FG_REWINDING)
			       ? "  rewinding" : "",
			       ((mp->flags & OFLOD_MMAP_FG_NEED_REWIND)
				? "  need rewind"
				: (mp->flags & OFLOD_MMAP_FG_FFWD_IN)
				? "  need FFWD"
				: ""),
			       (mp->flags & OFLOD_MMAP_FG_PASSWD_NEXT)
			       ? "  alt-password" : "",
			       (mp->flags & OFLOD_MMAP_FG_IPv4)
			       ? "  IPv4" : "",
			       (mp->flags & OFLOD_MMAP_FG_IPv6)
			       ? "  IPv6" : "");

		}
		if (!flod_unmap(dcc_emsg, 0))
			dcc_error_msg("%s", dcc_emsg);
	}
}



static int
open_hash(const char *hash_nm)
{
	struct stat sb;
	int hash_fd;

	db_hash_len = 0;
	hash_fd = open(hash_nm, O_RDONLY, 0);
	if (0 > hash_fd) {
		dcc_error_msg("open(%s): %s",
			      DCC_NM2PATH(hash_nm), ERROR_STR());
		return -1;
	}
	if (0 > fstat(hash_fd, &sb)) {
		dcc_error_msg("stat(%s): %s",
			      DCC_NM2PATH(hash_nm), ERROR_STR());
		close(hash_fd);
		return -1;
	}
	hash_fsize = sb.st_size;
	db_hash_len = hash_fsize/sizeof(HASH_ENTRY);
	if ((hash_fsize % sizeof(HASH_ENTRY)) != 0) {
		dcc_error_msg("%s has size %u, not a multiple of %d",
			      DCC_NM2PATH(hash_nm),
			      hash_fsize, ISZ(HASH_ENTRY));
		db_hash_len = 0;
		close(hash_fd);
		return -1;
	}
	if (db_hash_len < MIN_HASH_ENTRIES) {
		dcc_error_msg("%s has too few records, %d bytes",
			      DCC_NM2PATH(hash_nm), hash_fsize);
		db_hash_len = 0;
		close(hash_fd);
		return -1;
	}

	return hash_fd;
}



#define HASH_MAP_LEN	(1024*1024)
#define HASH_MAP_NUM	16
typedef struct hash_map {
    struct hash_map *fwd, *bak;
    HASH_ENTRY	*buf;
    DB_HADDR	base;
    DB_HADDR	lim;
    off_t	offset;
    int		size;
} HASH_MAP;
static HASH_MAP hash_maps[HASH_MAP_NUM];
static HASH_MAP *hash_map_newest;
static int hash_map_fd;
static const char *hash_map_nm;


static u_char
hash_munmap(HASH_MAP *mp)
{
	if (!mp->buf)
		return 1;

	if (0 > munmap((void *)mp->buf, mp->size)) {
		dcc_error_msg("munmap(%s,%d): %s",
			      hash_map_nm, mp->size, ERROR_STR());
		return 0;
	}
	mp->buf = 0;
	return 1;
}



static u_char
hash_map_clear(int hash_fd, const char *hash_nm)
{
	HASH_MAP *mp;
	int i;

	hash_map_fd = hash_fd;
	hash_map_nm = hash_nm;

	mp = hash_maps;
	for (i = 0; i < DIM(hash_maps); ++i, ++mp) {

		if (i == DIM(hash_maps)-1)
			mp->fwd = hash_maps;
		else
			mp->fwd = mp+1;
		if (i == 0)
			mp->bak = LAST(hash_maps);
		else
			mp->bak = mp-1;
	}
	hash_map_newest = hash_maps;

	for (mp = hash_maps; mp <= LAST(hash_maps); ++mp) {
		if (!hash_munmap(mp))
			return 0;
	}

	return 1;
}



static void
hash_map_ref(HASH_MAP *mp)
{
	if (hash_map_newest != mp) {
		mp->fwd->bak = mp->bak;
		mp->bak->fwd = mp->fwd;
		mp->fwd = hash_map_newest;
		mp->bak = hash_map_newest->bak;
		mp->fwd->bak = mp;
		mp->bak->fwd = mp;
		hash_map_newest = mp;
	}
}



static HASH_ENTRY *
entry_mmap(DB_HADDR haddr)
{
	HASH_MAP *mp;
	void *p;
	int i;

	for (i = 0, mp = hash_map_newest;
	     i < DIM(hash_maps);
	     ++i, mp = mp->fwd) {
		if (!mp->buf)
			continue;
		if (haddr >= mp->base
		    && haddr < mp->lim) {
			hash_map_ref(mp);
			return mp->buf + (haddr - mp->base);
		}
	}

	mp = hash_map_newest->bak;
	hash_munmap(mp);

	mp->base = haddr -  haddr%HASH_MAP_LEN;
	mp->offset = mp->base*sizeof(HASH_ENTRY);
	mp->size = hash_fsize - mp->offset;
	if (mp->size > HASH_MAP_LEN*ISZ(HASH_ENTRY))
		mp->size = HASH_MAP_LEN*ISZ(HASH_ENTRY);
	mp->lim = mp->base + mp->size/sizeof(HASH_ENTRY);
	p = mmap(0, mp->size, PROT_READ, MAP_SHARED, hash_map_fd, mp->offset);
	if (p != MAP_FAILED) {
		mp->buf = p;
		hash_map_ref(mp);
		return mp->buf + (haddr - mp->base);
	}
	dcc_error_msg("mmap(%s,%d,%d): %s",
		      hash_map_nm, (int)mp->size, (int)mp->offset,
		      ERROR_STR());
	return 0;
}



static void
list_hash(int hash_fd, const char *hash_nm)
{
	HASH_ENTRY *entry = 0;
	int collisions, chains, chain_lens, max_chain_len, chain_len;
	DB_HADDR fwd, bak, rcd_num;
	DB_HADDR db_hash_used_stored, db_hash_len_stored;
	DB_PTR rcd, db_csize_stored;

	if (verbose >= VERBOSE_HASH)
		printf("\n %s\n", DCC_NM2PATH(hash_nm));

	if (!hash_map_clear(hash_fd, hash_nm))
		return;

	if (no_hash)
		return;

	db_hash_used_stored = 0;
	db_hash_used = DB_HADDR_MIN;
	collisions = 0;
	chains = 0;
	chain_lens = 0;
	max_chain_len = 1;
	for (rcd_num = 0; rcd_num < db_hash_len; ++rcd_num) {
		entry = entry_mmap(rcd_num);
		if (!entry)
			break;

		/* deal with the special, first entries */
		if (rcd_num == DB_HADDR_MAGIC) {
			if (memcmp(entry, hash_magic, sizeof(entry))) {
				dcc_error_msg("%s contains the wrong magic",
					      DCC_NM2PATH(hash_nm));
			}
			if (verbose > VERBOSE_HASH) {
				printf("     magic: \"%.*s\"\n",
				       ISZ(entry), (char*)entry);
			}
			continue;
		}

		fwd = DB_HADDR_EX(entry->fwd);
		bak = DB_HADDR_EX(entry->bak);

		if (rcd_num == DB_HADDR_FREE) {
			if ((DB_HADDR_INVALID(fwd) && fwd != DB_HADDR_FREE)
			    || (DB_HADDR_INVALID(bak) && bak != DB_HADDR_FREE)
			    || DB_HPTR_EX(entry->rcd) != DB_PTR_NULL
			    || !HE_IS_FREE(entry)) {
				dcc_error_msg("%s free list broken",
					      DCC_NM2PATH(hash_nm));
			}
			if (verbose == VERBOSE_HASH) {
				printf("    first free: %x\n",
				       fwd);
			} else if (verbose > VERBOSE_HASH) {
				printf("      free: %6x %6x\n",
				       fwd, bak);
			}
			continue;
		}

		if (rcd_num == DB_HADDR_SIZES) {
			if (verbose > VERBOSE_HASH) {
				printf("     sizes: %6x %6x\n",
				       fwd, bak);
			}
			db_hash_len_stored = DB_HADDR_EX(entry->HASH_STORE_LEN);
			if (db_hash_len != db_hash_len_stored
			    && (db_hash_len_stored != 0
				|| verbose > VERBOSE_HASH)) {
				dcc_error_msg("%s has %d entries but claims %d",
					      DCC_NM2PATH(hash_nm),
					      HASH_LEN_EXT(db_hash_len),
					      HASH_LEN_EXT(db_hash_len_stored));
			}
			db_hash_used_stored = DB_HADDR_EX(entry
							->HASH_STORE_USED);
			if (db_hash_used_stored > db_hash_len) {
				dcc_error_msg("%s contains only %d"
					      " entries but %d used",
					DCC_NM2PATH(hash_nm),
					HASH_LEN_EXT(db_hash_len),
					HASH_LEN_EXT((db_hash_used_stored)));
			}
			if (db_hash_used_stored == db_hash_len) {
				dcc_error_msg("%s overflows with %d entries",
					      DCC_NM2PATH(hash_nm),
					      HASH_LEN_EXT(db_hash_len));
			}
			db_csize_stored=DB_HPTR_EX(entry->HASH_STORE_DB_CSIZE);
			if (db_csize_stored != db_csize) {
				dcc_error_msg("%s claims %s contains "L_DPAT
					      " bytes instead of "L_DPAT,
					      DCC_NM2PATH(hash_nm), db_nm,
					      db_csize_stored, db_csize);
			}
			continue;
		}

		/* deal with a free entry */
		if (HE_IS_FREE(entry)) {
			if (verbose >= VERBOSE_HASH)
				printf("    %6x: %6x %6x\n",
				       rcd_num, fwd, bak);
			continue;
		}

		/* deal with a used entry */
		rcd = DB_HPTR_EX(entry->rcd);
		++db_hash_used;
		if (DB_PTR_IS_BAD_FULL(rcd))
			dcc_error_msg("bad hash table data link at"
				      " %x to "L_HPAT,
				      rcd_num, rcd);
		if (DB_HADDR_INVALID(fwd) && fwd != DB_HADDR_NULL)
			dcc_error_msg("bad hash forward link at %x to %x",
				      rcd_num, fwd);
		if (DB_HADDR_INVALID(bak) && bak != DB_HADDR_NULL)
			dcc_error_msg("bad hash back link at %x to %x",
				      rcd_num, bak);
		if (verbose >= VERBOSE_HASH)
			printf("    %6x: %6x %6x "L_H8PAT" %s\n",
			       rcd_num, fwd, bak, rcd,
			       dcc_type2str_err(HE_TYPE(entry), 0, 1));
		if (bak != DB_HADDR_NULL) {
			++collisions;
		} else {
			++chains;
			bak = rcd_num;
			chain_len = 1;
			while (!DB_HADDR_INVALID(fwd)) {
				if (++chain_len > 100) {
					dcc_error_msg("possible hash chain loop"
						      " starting at %x"
						      " continuing through %x",
						      rcd_num, fwd);
					break;
				}
				entry = entry_mmap(fwd);
				if (!entry)
					break;
				if (HE_IS_FREE(entry)
				    || DB_HADDR_EX(entry->bak) != bak) {
					dcc_error_msg("broken hash chain"
						      " starting at %x at %x",
						      rcd_num, fwd);
					break;
				}
				bak = fwd;
				fwd = DB_HADDR_EX(entry->fwd);
			}
			chain_lens += chain_len;
			if (max_chain_len < chain_len)
				max_chain_len = chain_len;
		}
	}

	hash_map_clear(0, 0);

	if (db_hash_used_stored != db_hash_used) {
		if (db_hash_used_stored == 0)
			dcc_error_msg("%s was not cleanly closed but has"
				      " %d entries",
				      DCC_NM2PATH(hash_nm),
				      HASH_LEN_EXT(db_hash_used));
		else
			dcc_error_msg("%s claims to have filled"
				      " %d entries but has %d",
				      DCC_NM2PATH(hash_nm),
				      HASH_LEN_EXT(db_hash_used_stored),
				      HASH_LEN_EXT(db_hash_used));
	}

	if (verbose >= VERBOSE_HASH)
		putchar('\n');
	printf("%7d hash entries total  %d or %d%% used  %d free"
	       "  %.2f%% collisions\n",
	       HASH_LEN_EXT(db_hash_len),
	       HASH_LEN_EXT(db_hash_used),
	       (HASH_LEN_EXT(db_hash_used)*100) / HASH_LEN_EXT(db_hash_len),
	       HASH_LEN_EXT(db_hash_len) - HASH_LEN_EXT(db_hash_used),
	       collisions*100.0/HASH_LEN_EXT(db_hash_len));
	if (chains != 0)
		printf("%7d hash chains  max length %d  average length %.1f\n",
		       chains, max_chain_len, chain_lens*1.0/chains);
}
