/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 1994-2002 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Methods of the cfsd_maptbl classes.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <synch.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/utsname.h>
#include <sys/vfs.h>
#include <sys/cred.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/fs/cachefs_fs.h>
#include <sys/fs/cachefs_dlog.h>
#include <mdbug/mdbug.h>
#include "cfsd.h"
#include "cfsd_maptbl.h"

/*
 *			cfsd_maptbl_create
 *
 * Description:
 *	Constructor for the cfsd_maptbl class.
 *	Just does some setup not much else.
 * Arguments:
 * Returns:
 * Preconditions:
 */
cfsd_maptbl_object_t *
cfsd_maptbl_create(void)
{
	cfsd_maptbl_object_t *maptbl_object_p;

	dbug_enter("cfsd_maptbl_create");

	maptbl_object_p = cfsd_calloc(sizeof (cfsd_maptbl_object_t));

	maptbl_object_p->i_fid = -1;
	maptbl_object_p->i_pa = NULL;
	maptbl_object_p->i_paoff = 0;
	maptbl_object_p->i_paend = 0;
	maptbl_object_p->i_palen = 0;
	dbug_leave("cfsd_maptbl_create");
	return (maptbl_object_p);
}

/*
 *			cfsd_maptbl_destroy
 *
 * Description:
 *	Destructor for the cfsd_maptbl class.
 * Arguments:
 * Returns:
 * Preconditions:
 */
void
cfsd_maptbl_destroy(cfsd_maptbl_object_t *maptbl_object_p)
{
	dbug_enter("cfsd_maptbl_destroy");
	dbug_precond(maptbl_object_p);
	maptbl_teardown(maptbl_object_p);
	cfsd_free(maptbl_object_p);
	dbug_leave("cfsd_maptbl_destroy");
}

/*
 *			maptbl_domap
 *
 * Description:
 *	Maps in the specified section of the file.
 * Arguments:
 *	off	The offset to map in.  Must be i_pagesize aligned.
 * Returns:
 *	Returns 0 for success or an errno value on failure.
 * Preconditions:
 */
int
maptbl_domap(cfsd_maptbl_object_t *maptbl_object_p, off_t off)
{
	int xx;
	int len;

	dbug_enter("maptbl_domap");
	dbug_precond(maptbl_object_p);
	dbug_precond(maptbl_object_p->i_fid >= 0);

	len = maptbl_object_p->i_maplen;

	maptbl_object_p->i_stat_mapmove++;

	/* destroy old mapping if it exists */
	if (maptbl_object_p->i_pa) {
		/* determine how far we have to move the map */
		maptbl_object_p->i_stat_mapdist +=
		    abs(maptbl_object_p->i_paoff - off);

		/* remove the map */
		xx = munmap(maptbl_object_p->i_pa, maptbl_object_p->i_palen);
		if (xx == -1) {
			xx = errno;
			dbug_print(("error", "Could not unmap %s, %d, %p, %d",
			    maptbl_object_p->i_name, xx, maptbl_object_p->i_pa,
			    maptbl_object_p->i_palen));
		}
		maptbl_object_p->i_pa = NULL;
		maptbl_object_p->i_palen = 0;
		maptbl_object_p->i_paoff = 0;
		maptbl_object_p->i_paend = 0;
	}

	/* do the mapping */
	maptbl_object_p->i_pa =
	    mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED,
	    maptbl_object_p->i_fid, off);
	if (maptbl_object_p->i_pa == MAP_FAILED) {
		xx = errno;
		dbug_print(("error",
		    "Could not map %s, error %d, off %d, len %d",
		    maptbl_object_p->i_name, xx, off, len));
		maptbl_object_p->i_pa = NULL;
		dbug_leave("maptbl_domap");
		return (xx);
	}

	maptbl_object_p->i_palen = len;
	maptbl_object_p->i_paoff = off;
	maptbl_object_p->i_paend = off + len - 1;
	dbug_leave("maptbl_domap");
	return (0);
}

/*
 *			maptbl_getaddr
 *
 * Description:
 *	Returns an address of a particular entry in the file.
 * Arguments:
 *	index
 * Returns:
 *	Returns NULL for a failure with the mapping file.
 * Preconditions:
 */
caddr_t
maptbl_getaddr(cfsd_maptbl_object_t *maptbl_object_p, int index)
{
	off_t start;
	off_t end;
	caddr_t pa;

	dbug_enter("maptbl_getaddr");
	dbug_precond(maptbl_object_p);
	dbug_precond(index < maptbl_object_p->i_entries);

	/* find the boundaries of the entry */
	start = index * sizeof (struct cfs_dlog_mapping_space);
	end = start + sizeof (struct cfs_dlog_mapping_space) - 1;

	/* map the entry in if necessary */
	if ((start < maptbl_object_p->i_paoff) ||
		(maptbl_object_p->i_paend < end)) {
		if (maptbl_domap(maptbl_object_p,
		    start & maptbl_object_p->i_pagemask)) {
			dbug_leave("maptbl_getaddr");
			return (NULL);
		}
	}

	/* make an address and return it */
	pa = maptbl_object_p->i_pa + (start - maptbl_object_p->i_paoff);
	dbug_leave("maptbl_getaddr");
	return (pa);
}

/*
 *			maptbl_cidhashaddr
 *
 * Description:
 *	Finds the address of the specified cid by hashing to
 *	the appropriate entry.  If the cid does not already
 *	exist in the file, then the address of where it should
 *	reside is returned.
 * Arguments:
 *	cid
 *	addrp
 * Returns:
 *	Returns 0 for success, 1 if entry not found, -1 if an
 *	error occurs in the mapping file.
 * Preconditions:
 */
int
maptbl_cidhashaddr(cfsd_maptbl_object_t *maptbl_object_p,
	cfs_cid_t cid,
	caddr_t *addrp)
{
	ino64_t *pa;
	int index;
	ino64_t fileno;
	int start_index;

	dbug_enter("maptbl_cidhashaddr");
	dbug_precond(maptbl_object_p);
	dbug_precond(addrp);

	maptbl_object_p->i_stat_requests++;

	/* get the index from the first hash function */
	index = maptbl_hash1(maptbl_object_p, cid);

	maptbl_object_p->i_stat_probes++;

	/* get the address of the entry */
	pa = (ino64_t *)maptbl_getaddr(maptbl_object_p, index);
	if (pa == NULL) {
		dbug_leave("maptbl_cidhashaddr");
		return (-1);
	}
	fileno = *pa;

	/* check for match */
	if (fileno == cid.cid_fileno) {
		*addrp = (caddr_t)pa;
		dbug_leave("maptbl_cidhashaddr");
		return (0);
	}

	/* check for not found */
	if (fileno == 0) {
		*addrp = (caddr_t)pa;
		dbug_leave("maptbl_cidhashaddr");
		return (1);
	}

	/* get the index from the second hash function */
	index = maptbl_hash2(maptbl_object_p, cid, index);

	/* do a linear search for a match or empty entry */
	start_index = index;
	do {
		maptbl_object_p->i_stat_probes++;

		/* get the address of the entry */
		pa = (ino64_t *)maptbl_getaddr(maptbl_object_p, index);
		if (pa == NULL) {
			dbug_leave("maptbl_cidhashaddr");
			return (-1);
		}
		fileno = *pa;

		/* check for match */
		if (fileno == cid.cid_fileno) {
			*addrp = (caddr_t)pa;
			dbug_leave("maptbl_cidhashaddr");
			return (0);
		}

		/* check for not found */
		if (fileno == 0) {
			*addrp = (caddr_t)pa;
			dbug_leave("maptbl_cidhashaddr");
			return (1);
		}

		/* move to the next entry */
		index++;
		index = index % maptbl_object_p->i_entries;
	} while (start_index != index);

	/* table full, this is bad */
	dbug_print(("error", "Table is full"));
	dbug_leave("maptbl_cidhashaddr");
	return (-1);
}

/*
 *			maptbl_hash1
 *
 * Description:
 *	Hashes a cid into an index into the table.
 * Arguments:
 *	cid
 * Returns:
 *	Returns the index.
 * Preconditions:
 */
int
maptbl_hash1(cfsd_maptbl_object_t *maptbl_object_p, cfs_cid_t cid)
{
	unsigned int xx;
	unsigned int a, b;

	dbug_precond(maptbl_object_p);
#if 0
	xx = cid.cid_fileno % i_entries;
#else
	a = cid.cid_fileno >> 16;
	b = a ^ cid.cid_fileno;
	xx = b % maptbl_object_p->i_entries;
#endif
	return (xx);
}

/*
 *			maptbl_hash2
 *
 * Description:
 *	Hashes a cid into an index into the table.
 * Arguments:
 *	cid
 *	index
 * Returns:
 *	Returns the index.
 * Preconditions:
 */
int
maptbl_hash2(cfsd_maptbl_object_t *maptbl_object_p, cfs_cid_t cid, int index)
{
	unsigned int xx;
	unsigned int a, b, c, d;

	dbug_precond(maptbl_object_p);
#if 0
	a = cid.cid_fileno & 0x0ff;
	b = (cid.cid_fileno >> 8) & 0x0ff;
	b = cid.cid_fileno ^ a ^ b;
	xx = b % maptbl_object_p->i_hash2mod;
#else
	a = cid.cid_fileno & 0x0ff;
	b = (cid.cid_fileno >> 8) & 0x0ff;
	c = (cid.cid_fileno >> 16) & 0x0ff;
	d = (cid.cid_fileno >> 24) & 0x0ff;
	xx = cid.cid_fileno ^ (a << 8) ^ b ^ c ^ d;
	xx = xx % maptbl_object_p->i_hash2mod;
#endif
	xx = (index + xx) % maptbl_object_p->i_entries;
	return (xx);
}

/*
 *			maptbl_setup
 *
 * Description:
 *	Performs setup for the cfsd_maptbl class.
 *	This routine must be called before other routines are used.
 * Arguments:
 *	filename
 * Returns:
 *	Returns 0 for success or an errno value.
 * Preconditions:
 *	precond(filename)
 */
int
maptbl_setup(cfsd_maptbl_object_t *maptbl_object_p, const char *filename)
{
	int xx;
	struct stat sinfo;
	off_t offset;
	long *lp;
	size_t cnt;
	off_t size;

	dbug_enter("maptbl_setup");
	dbug_precond(maptbl_object_p);
	dbug_precond(filename);

	/* clean up from a previous setup */
	maptbl_teardown(maptbl_object_p);

	strlcpy(maptbl_object_p->i_name, filename,
	    sizeof (maptbl_object_p->i_name));
	dbug_print(("info", "filename %s", maptbl_object_p->i_name));

	/* get the page info */
	maptbl_object_p->i_pagesize = PAGESIZE;
	maptbl_object_p->i_pagemask = PAGEMASK;
	maptbl_object_p->i_maplen = maptbl_object_p->i_pagesize * 100;

	/* open the file */
	maptbl_object_p->i_fid = open(maptbl_object_p->i_name,
	    O_RDWR | O_NONBLOCK);
	if (maptbl_object_p->i_fid == -1) {
		xx = errno;
		dbug_print(("error",
		    "Could not open %s, %d", maptbl_object_p->i_name, xx));
		dbug_leave("maptbl_setup");
		return (xx);
	}

	/* get the size and type of file */
	xx = fstat(maptbl_object_p->i_fid, &sinfo);
	if (xx) {
		xx = errno;
		dbug_print(("error",
		    "Could not stat %s, %d", maptbl_object_p->i_name, xx));
		dbug_leave("maptbl_setup");
		return (xx);
	}
	maptbl_object_p->i_size = sinfo.st_size;

	/* sanity check, better be a regular file */
	if (!S_ISREG(sinfo.st_mode)) {
		xx = ENOTSUP;
		dbug_print(("error",
		    "%s Not a regular file.", maptbl_object_p->i_name));
		dbug_leave("maptbl_setup");
		return (xx);
	}

	/* determine number of entries */
	maptbl_object_p->i_entries =
	    maptbl_object_p->i_size / sizeof (struct cfs_dlog_mapping_space);

	/* set up modulo value for second hash function */
	maptbl_object_p->i_hash2mod = (maptbl_object_p->i_entries / 2) + 1;

	/* initialize statistic gathering */
	maptbl_object_p->i_stat_requests = 0;
	maptbl_object_p->i_stat_probes = 0;
	maptbl_object_p->i_stat_mapmove = 0;
	maptbl_object_p->i_stat_mapdist = 0;
	maptbl_object_p->i_stat_filled = 0;

	/* zero the file */
	for (offset = 0; offset < maptbl_object_p->i_size;
		offset += maptbl_object_p->i_maplen) {
		/* map in a section of the file */
		xx = maptbl_domap(maptbl_object_p, offset);
		if (xx) {
			dbug_leave("maptbl_setup");
			return (xx);
		}
		/* zero this section of the file */
		lp = (long *)maptbl_object_p->i_pa;
		size = maptbl_object_p->i_size - offset;
		if (size < maptbl_object_p->i_palen) {
			cnt = size / sizeof (long);
		} else {
			cnt = maptbl_object_p->i_palen / sizeof (long);
			dbug_assert((cnt * sizeof (long)) ==
			    maptbl_object_p->i_palen);
		}
		memset(lp, 0, cnt * sizeof (*lp));
	}

	/* return success */
	dbug_leave("maptbl_setup");
	return (0);
}

/*
 *			maptbl_teardown
 *
 * Description:
 * Arguments:
 * Returns:
 * Preconditions:
 */
void
maptbl_teardown(cfsd_maptbl_object_t *maptbl_object_p)
{
	int xx;

	dbug_enter("maptbl_teardown");
	dbug_precond(maptbl_object_p);

	if (maptbl_object_p->i_pa) {
		xx = munmap(maptbl_object_p->i_pa, maptbl_object_p->i_palen);
		if (xx == -1) {
			xx = errno;
			dbug_print(("error", "Could not unmap %s, %d, %p, %d",
			    maptbl_object_p->i_name, xx, maptbl_object_p->i_pa,
			    maptbl_object_p->i_palen));
		}
		maptbl_object_p->i_pa = NULL;
	}
	maptbl_object_p->i_paoff = 0;
	maptbl_object_p->i_paend = 0;
	maptbl_object_p->i_palen = 0;

	if (maptbl_object_p->i_fid != -1) {
		if (close(maptbl_object_p->i_fid))
			dbug_print(("err", "cannot close maptbl fd, error %d",
			    errno));
		maptbl_object_p->i_fid = -1;
	}
	dbug_leave("maptbl_teardown");
}

/*
 *			maptbl_get
 *
 * Description:
 *	Gets the mapping info for the specified cid.
 * Arguments:
 *	cid
 *	valuep
 * Returns:
 *	Returns 0 for success, 1 if entry not found, -1 if an
 *	error occurs in the mapping file.
 * Preconditions:
 *	precond(valuep)
 */
int
maptbl_get(cfsd_maptbl_object_t *maptbl_object_p,
	cfs_cid_t cid,
	struct cfs_dlog_mapping_space *valuep)
{
	int xx;
	struct cfs_dlog_mapping_space *pa;

	dbug_enter("maptbl_get");
	dbug_precond(maptbl_object_p);
	dbug_precond(valuep);

	if (maptbl_object_p->i_entries == 0) {
		dbug_leave("maptbl_get");
		return (1);
	}
	xx = maptbl_cidhashaddr(maptbl_object_p, cid, (caddr_t *)&pa);
	if (xx == 0)
		*valuep = *pa;
	dbug_leave("maptbl_get");
	return (xx);
}

/*
 *			maptbl_set
 *
 * Description:
 *	Sets the mapping info for the cid.
 *	If insert is 1 then if the entry is not found it is put in the
 *	table.
 * Arguments:
 *	valuep
 *	insert
 * Returns:
 *	Returns 0 if mapping info placed in the table, 1 if entry
 *	is not found an insert is 0, -1 if an error occurs in the
 *	mapping file.
 * Preconditions:
 *	precond(valuep)
 */
int
maptbl_set(cfsd_maptbl_object_t *maptbl_object_p,
	struct cfs_dlog_mapping_space *valuep,
	int insert)
{
	int xx;
	struct cfs_dlog_mapping_space *pa;

	dbug_enter("maptbl_set");
	dbug_precond(maptbl_object_p);
	dbug_precond(valuep);

	dbug_assert(maptbl_object_p->i_entries > 0);

	xx = maptbl_cidhashaddr(maptbl_object_p, valuep->ms_cid,
	    (caddr_t *)&pa);
	if ((xx == 0) || ((xx == 1) && insert)) {
		*pa = *valuep;
		if (xx == 1)
			maptbl_object_p->i_stat_filled++;
		xx = 0;
	}
	dbug_leave("maptbl_set");
	return (xx);
}

/*
 *			maptbl_dumpstats
 *
 * Description:
 *	Prints out various stats about the hashing.
 * Arguments:
 * Returns:
 * Preconditions:
 */
void
maptbl_dumpstats(cfsd_maptbl_object_t *maptbl_object_p)
{
	int xx;
	double dd;

	dbug_enter("maptbl_dumpstats");
	dbug_precond(maptbl_object_p);

	dbug_print(("dump", "Total Entries %d", maptbl_object_p->i_entries));
	dbug_print(("dump", "Filled Entries %d",
	    maptbl_object_p->i_stat_filled));
	dbug_print(("dump", "Requests %d", maptbl_object_p->i_stat_requests));
	dbug_print(("dump", "Probes %d", maptbl_object_p->i_stat_probes));
	dbug_print(("dump", "Map Moves %d", maptbl_object_p->i_stat_mapmove));
	dbug_print(("dump", "Mapping Size %d", maptbl_object_p->i_maplen));
	dbug_print(("dump", "File Size %d", maptbl_object_p->i_size));
	if (maptbl_object_p->i_stat_requests == 0) {
		dbug_leave("maptbl_dumpstats");
		return;
	}
	dd = (double)maptbl_object_p->i_stat_probes /
	    maptbl_object_p->i_stat_requests;
	dbug_print(("dump", "Probes per Request %.2f", dd));

	dd = (double)maptbl_object_p->i_stat_mapmove /
	    maptbl_object_p->i_stat_requests;
	dbug_print(("dump", "Mmap moves per Request %.2f", dd));

	xx = maptbl_object_p->i_stat_mapdist / maptbl_object_p->i_stat_mapmove;
	dbug_print(("dump", "Average distance per mmap moves %d", xx));

	xx = ((100.0 * maptbl_object_p->i_stat_filled) /
	    maptbl_object_p->i_entries) + .5;
	dbug_print(("dump", "Table filled %d%%", xx));

	dbug_leave("maptbl_dumpstats");
}