/* Distributed Checksum Clearinghouse
 *
 * open, create, and check DCC server output flood mapped file
 *
 * --C-LICENSE--
 * $Revision: 1.67 $
 */

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

FLOD_MMAPS *flod_mmaps;
DCC_PATH flod_mmap_path;
DCC_PATH flod_path;

static int mmap_fd = -1;


void
flod_mmap_path_set(void)
{
	dcc_fnm2rel_good(&flod_mmap_path,
			 grey_on ? GREY_FLOD_NM : DCCD_FLOD_NM,
			 ".map");
	dcc_fnm2rel_good(&flod_path,
			 grey_on ? GREY_FLOD_NM : DCCD_FLOD_NM,
			 0);
}



u_char
flod_mmap_sync(DCC_EMSG *emsg, u_char touch)
{
	u_char result = 1;

	if (flod_mmap_path.c[0] == '\0')
		flod_mmap_path_set();

	if (flod_mmaps
	    && 0 > MSYNC(flod_mmaps, sizeof(*flod_mmaps), MS_SYNC)) {
		dcc_pemsg(EX_IOERR, emsg, "msync(%s): %s",
			  flod_mmap_path.c, ERROR_STR());
		result = 0;
		emsg = 0;
	}

	if (touch) {
		if (mmap_fd >= 0
		    && 0 > fsync(mmap_fd)) {
			dcc_pemsg(EX_IOERR, emsg, "fsync(%s): %s",
				  flod_mmap_path.c, ERROR_STR());
			result = 0;
			emsg = 0;
		}

		/* Ensure that the mtime changes at least occassionally.
		 * Several systems do not update the mtimes of files modified
		 * with mmap().  Some like BSD/OS delay changing the mtime until
		 * the file is accessed with read().  Others including
		 * filesystems on some versions of Linux apparently never change
		 * the mtime. */
		if (!dcc_set_mtime(emsg, flod_mmap_path.c, mmap_fd, 0)) {
			result = 0;
			emsg = 0;
		}
	}

	return result;
}



u_char					/* 1=no problems, 0=complaints */
flod_unmap(DCC_EMSG *emsg, const DCCD_STATS *dccd_stats)
{
	u_char result = 1;

	if (!flod_mmap_sync(emsg, 0)) {
		result = 0;
		emsg = 0;
	}

	if (flod_mmaps) {
		if (dccd_stats)
			memcpy(&flod_mmaps->dccd_stats, dccd_stats,
			       sizeof(flod_mmaps->dccd_stats));
		if (0 > munmap((void *)flod_mmaps, sizeof(*flod_mmaps))) {
			dcc_pemsg(EX_IOERR, emsg, "munmap(%s,%d): %s",
				  flod_mmap_path.c,
				  ISZ(*flod_mmaps), ERROR_STR());
			result = 0;
			emsg = 0;
		}
		flod_mmaps = 0;
	}

	if (mmap_fd >= 0) {
		if (close(mmap_fd) < 0) {
			dcc_pemsg(EX_IOERR, emsg, "close(%s): %s",
				  flod_mmap_path.c, ERROR_STR());
			result = 0;
			emsg = 0;
		}
		mmap_fd = -1;
	}

	return result;
}



static int				/* 1=success, 0=retry, -1=fatal */
flod_mmap_try(DCC_EMSG *emsg,
	      const DB_SN *sn,
	      u_char rw)		/* 0=rdonly/unlocked, 1=write/locked */
{
	int flags;
	struct stat sb;
	union {
	    FLOD_MMAPS	m;
	    u_char	b[1];
	} init;
	void *p;
	int i;

	if (flod_mmap_path.c[0] == '\0')
		flod_mmap_path_set();

	if (rw)
		mmap_fd = dcc_lock_open(emsg, flod_mmap_path.c, O_RDWR|O_CREAT,
					DCC_LOCK_OPEN_NOWAIT,
					DCC_LOCK_ALL_FILE, 0);
	else
		mmap_fd = dcc_lock_open(emsg, flod_mmap_path.c, O_RDONLY,
					DCC_LOCK_OPEN_NOLOCK, 0, 0);
	if (mmap_fd == -1)
		return (errno == EWOULDBLOCK) ? -1 : 0;

	if (fstat(mmap_fd, &sb) < 0) {
		dcc_pemsg(EX_IOERR, emsg, "stat(%s): %s",
			  flod_mmap_path.c, ERROR_STR());
		flod_unmap(0, 0);
		return 0;
	}
	if (0 > fcntl(mmap_fd, F_SETFD, FD_CLOEXEC)) {
		dcc_pemsg(EX_IOERR, emsg, "fcntl(%s, FD_CLOEXEC): %s",
			  flod_mmap_path.c, ERROR_STR());
		flod_unmap(0, 0);
		return 0;
	}

	if (sb.st_size == 0 && rw) {
		memset(&init, 0, sizeof(init));
		strcpy(init.m.magic, FLOD_MMAP_MAGIC);
		if (sn)
			init.m.sn = *sn;
		i = write(mmap_fd, &init, sizeof(init));
		if (i < 0) {
			dcc_pemsg(EX_IOERR, emsg, "write(%s, init): %s",
				  flod_mmap_path.c, ERROR_STR());
			flod_unmap(0, 0);
			return -1;
		}
		if (i != ISZ(init)) {
			dcc_pemsg(EX_IOERR, emsg,
				  "write(%s, init)=%d instead of %d",
				  flod_mmap_path.c, i, ISZ(init));
			flod_unmap(0, 0);
			return -1;
		}
	} else {
		i = read(mmap_fd, &init, sizeof(init));
		if (i < 0) {
			dcc_pemsg(EX_IOERR, emsg, "read(%s, init): %s",
				  flod_mmap_path.c, ERROR_STR());
			flod_unmap(0, 0);
			return -1;
		}
		if (i < ISZ(init)) {
			if (i < sb.st_size) {
				dcc_pemsg(EX_IOERR, emsg, "read(%s, init)=%d",
					  flod_mmap_path.c, i);
				flod_unmap(0, 0);
				return -1;
			}
		}
	}

	if (sb.st_size >= (off_t)sizeof(init.m.magic)
	    && strcmp(init.m.magic, FLOD_MMAP_MAGIC)) {
		dcc_pemsg(EX_IOERR, emsg, "wrong magic in %s;"
			  " \"%s\" instead of \""FLOD_MMAP_MAGIC"\"",
			  flod_mmap_path.c, esc_magic(init.m.magic,
						      sizeof(init.m.magic)));
		flod_unmap(0, 0);
		return 0;
	}

	flags = rw ? (PROT_READ|PROT_WRITE) : PROT_READ;
	p = mmap(0, sizeof(*flod_mmaps), flags, MAP_SHARED, mmap_fd, 0);
	if (p == MAP_FAILED) {
		dcc_pemsg(EX_IOERR, emsg, "mmap(%s): %s",
			  flod_mmap_path.c, ERROR_STR());
		flod_unmap(0, 0);
		return 0;
	}
	flod_mmaps = p;

	if (sb.st_size == 0) {
		dcc_pemsg(EX_IOERR, emsg, "%s (re)created", flod_mmap_path.c);
	} else if (sb.st_size != sizeof(FLOD_MMAPS)) {
		dcc_pemsg(EX_IOERR, emsg, "%s has size %d instead of %d",
			  flod_mmap_path.c, (int)sb.st_size, ISZ(FLOD_MMAPS));
		flod_unmap(0, 0);
		return 0;
	}

	if (sn
	    && memcmp(&flod_mmaps->sn, sn, sizeof(flod_mmaps->sn))) {
		char sn1_buf[30], sn2_buf[32];
		dcc_pemsg(EX_IOERR, emsg, "s/n %s instead of %s in %s",
			  ts2str(sn1_buf, sizeof(sn1_buf), &flod_mmaps->sn),
			  ts2str(sn2_buf, sizeof(sn2_buf), sn),
			  flod_mmap_path.c);
		flod_unmap(0, 0);
		return 0;
	}

	return 1;
}



u_char					/* 0=failed, 1=mapped */
flod_mmap(DCC_EMSG *emsg,
	  const DB_SN *sn,
	  const DCCD_STATS *dccd_stats,
	  u_char rw)			/* 0=rdonly/unlocked, 1=write/locked */
{
	int i;

	if (!flod_unmap(emsg, dccd_stats) && emsg) {
		dcc_trace_msg("%s", emsg->c);
		emsg->c[0] = '\0';
	}

	/* try to open the existing file */
	i = flod_mmap_try(emsg, sn, rw);

	/* finished if that went well or we are not allowed to fix things */
	if (i != 0 || !rw)
		return i > 0;

	/* delete the file if it is broken */
	if (emsg && emsg->c[0] != '\0') {
		dcc_trace_msg("%s", emsg->c);
		emsg->c[0] = '\0';
	}
	if (0 > unlink(flod_mmap_path.c)
	    && errno != ENOENT) {
		dcc_pemsg(EX_IOERR, emsg, "unlink(%s): %s",
			  flod_mmap_path.c, ERROR_STR());
		flod_unmap(0, 0);
		return 0;
	}
	flod_unmap(0, 0);

	/* try to recreate the file */
	if (flod_mmap_try(emsg, sn, 1) > 0) {
		if (emsg) {
			dcc_trace_msg("%s", emsg->c);
			emsg->c[0] = '\0';
		}
		return 1;
	}

	flod_unmap(0, 0);
	return 0;
}



const char *
flod_stats_printf(char *buf, int buf_len,
		  int st,		/* 0=off, 1=restarting, 2=on */
		  int oflods_total,
		  int oflods_active,
		  int iflods_active)
{
	snprintf(buf, buf_len,
		 "flood %s %3d streams  %d out active %d in",
		 st == 0 ? "off" : st == 1 ? "restarting" : "on",
		 oflods_total, oflods_active, iflods_active);
	return buf;
}



#define FIELD_SEP "  "

static void
mmap_fg_sub(char **bufp, int *buf_lenp, const char *str)
{
	int i;

	if (*buf_lenp == 0)
		return;

	i = snprintf(*bufp, *buf_lenp, FIELD_SEP"%s", str);
	if (*buf_lenp <= i) {
		*buf_lenp = 0;
	} else {
		*bufp += i;
		*buf_lenp -= i;
	}
}


const char *
socks_type_str(const FLOD_MMAP *mp)
{
	if (!mp || (mp->flags & FLODMAP_FG_SOCKS))
		return "SOCKS";
	if (mp->flags & FLODMAP_FG_NAT)
		return "NAT";
	if (mp->flags & FLODMAP_FG_NAT_AUTO)
		return "auto-NAT";
	return "?socks?";
}



static void
mmap_id_map(char **bufp, int *buf_lenp, const SRVR_ID_MAPS *maps)
{
	const char *sep;
	const char *result;
	int m, i;

	sep = FIELD_SEP;
	for (m = 0; m < maps->len; ++m) {
		result = "???";
		switch ((ID_MAP_RESULT)maps->entry[m].result) {
		case ID_MAP_NO: result = "ok"; break;
		case ID_MAP_REJ: result = "reject"; break;
		case ID_MAP_SELF: result = "self"; break;
		}
		if (maps->entry[m].lo == maps->entry[m].hi) {
			i = snprintf(*bufp, *buf_lenp, "%s%d->%s", sep,
				     maps->entry[m].lo,
				     result);
		} else if (maps->entry[m].lo == DCC_SRVR_ID_MIN
			   && maps->entry[m].hi == DCC_SRVR_ID_MAX) {
			i = snprintf(*bufp, *buf_lenp, "%sall->%s", sep,
				     result);
		} else {
			i = snprintf(*bufp, *buf_lenp, "%s%d-%d->%s", sep,
				     maps->entry[m].lo, maps->entry[m].hi,
				     result);
		}
		if (*buf_lenp <= i) {
			*buf_lenp = 0;
			return;
		}
		*bufp += i;
		*buf_lenp -= i;
		sep = ",";
	}
}



const char *
flodmap_fg(char *buf, int buf_len, const FLOD_MMAP *mp)
{
	char *buf0 = buf;

	if (!buf_len)
		return "";
	*buf = '\0';
	if (!mp)
		return buf0;

	if ((mp->flags & FLODMAP_FG_IN_OFF)
	    && (mp->flags & FLODMAP_FG_OUT_OFF)) {
		mmap_fg_sub(&buf, &buf_len, "off");
	} else {
		if (mp->flags & FLODMAP_FG_IN_OFF)
			mmap_fg_sub(&buf, &buf_len, "in off");
		if (mp->flags & FLODMAP_FG_OUT_OFF)
			mmap_fg_sub(&buf, &buf_len, "out off");
	}

	if (mp->flags & FLODMAP_FG_ROGUE)
		mmap_fg_sub(&buf, &buf_len, DCC_XHDR_ID_ROGUE);

	if (mp->flags & FLODMAP_FG_REWINDING)
		mmap_fg_sub(&buf, &buf_len, "rewinding");

	if (mp->flags & FLODMAP_FG_NEED_RWD)
		mmap_fg_sub(&buf, &buf_len, "need rewind");
	else if (mp->flags & FLODMAP_FG_FFWD_IN)
		mmap_fg_sub(&buf, &buf_len, "need FFWD");

	if (mp->flags & FLODMAP_FG_PASSIVE)
		mmap_fg_sub(&buf, &buf_len, "passive");
	if ((mp->flags & FLODMAP_FG_OUT_SRVR)
	    && !(mp->flags & FLODMAP_FG_PASSIVE))
		mmap_fg_sub(&buf, &buf_len, "forced passive");

	if (!(mp->flags & FLODMAP_FG_IN_SRVR)) {
		if (mp->flags & (FLODMAP_FG_SOCKS | FLODMAP_FG_NAT
				 | FLODMAP_FG_NAT_AUTO))
			mmap_fg_sub(&buf, &buf_len, socks_type_str(mp));
	} else {
		if (mp->flags & FLODMAP_FG_SOCKS)
			mmap_fg_sub(&buf, &buf_len, "rejected SOCKS");
		if (mp->flags & FLODMAP_FG_NAT)
			mmap_fg_sub(&buf, &buf_len, "rejected NAT");
	}

	if (mp->flags & FLODMAP_FG_LEAF)
		mmap_fg_sub(&buf, &buf_len, "leaf");

	if (mp->flags & FLODMAP_FG_FFWD_IN)
		mmap_fg_sub(&buf, &buf_len, "want fast-forward");

	if (mp->flags & FLODMAP_FG_USE_2PASSWD)
		mmap_fg_sub(&buf, &buf_len, "2nd password");

	if (mp->flags & FLODMAP_FG_IPv4)
		mmap_fg_sub(&buf, &buf_len, "IPv4");

	if (mp->flags & FLODMAP_FG_IPv6)
		mmap_fg_sub(&buf, &buf_len, "IPv6");

	if (!grey_on) {
		if (mp->flags & FLODMAP_FG_REP_PEER_REJ)
			mmap_fg_sub(&buf, &buf_len, "rej reps");
		else if (!(mp->flags & FLODMAP_FG_REP_PEER_ON)
			 && (mp->flags & FLODMAP_FG_IN_CONN))
			mmap_fg_sub(&buf, &buf_len, "broken reps");
	} else {
		if (mp->flags & FLODMAP_FG_REP_PEER_ON)
			mmap_fg_sub(&buf, &buf_len, "bad reps");
		else if (!grey_on)
			mmap_fg_sub(&buf, &buf_len, "no reps");
	}

	mmap_id_map(&buf, &buf_len, &mp->o_id_maps);
	mmap_id_map(&buf, &buf_len, &mp->i_id_maps);

	return buf0;
}



/* this function lets clients such as dbclean know when flooding is quiet */
int					/* -1=sick, 0=off, 1=not off */
flod_running(const char *st)
{
	char off_buf[11];
	int out, active, in;

	if (4 != sscanf(st, "flood %10s %d streams %d out active %d in",
			off_buf, &out, &active, &in))
		return 1;

	if (strcmp(off_buf, "off"))
		return 1;
	return active != 0 || in != 0;
}
