/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 1996, 1997, 1998, 1999, 2000
 *	Sleepycat Software.  All rights reserved.
 */

#include "db_config.h"

#ifndef lint
static const char revid[] = "$Id: lock_region.c,v 11.30 2000/10/16 18:03:05 bostic Exp $";
#endif /* not lint */

#ifndef NO_SYSTEM_INCLUDES
#include <sys/types.h>

#include <errno.h>
#include <string.h>
#endif

#ifdef	HAVE_RPC
#include "db_server.h"
#endif

#include "db_int.h"
#include "db_shash.h"
#include "lock.h"

#ifdef	HAVE_RPC
#include "gen_client_ext.h"
#include "rpc_client_ext.h"
#endif

static int  __lock_init __P((DB_ENV *, DB_LOCKTAB *));
static size_t
	    __lock_region_size __P((DB_ENV *));

#ifdef MUTEX_SYSTEM_RESOURCES
static size_t __lock_region_maint __P((DB_ENV *));
#endif

/*
 * This conflict array is used for concurrent db access (CDB).  It
 * uses the same locks as the db_rw_conflict array, but adds an IW
 * mode to be used for write cursors.
 */
static u_int8_t const db_cdb_conflicts[] = {
	/*		N   R   W  IW */
	/*    N */	0,  0,  0,  0,
	/*    R */	0,  0,  1,  0,
	/*    W */	0,  1,  1,  1,
	/*   IW */	0,  0,  1,  1
};

/*
 * __lock_dbenv_create --
 *	Lock specific creation of the DB_ENV structure.
 *
 * PUBLIC: void __lock_dbenv_create __P((DB_ENV *));
 */
void
__lock_dbenv_create(dbenv)
	DB_ENV *dbenv;
{
	dbenv->lk_max = DB_LOCK_DEFAULT_N;

	dbenv->set_lk_conflicts = __lock_set_lk_conflicts;
	dbenv->set_lk_detect = __lock_set_lk_detect;
	dbenv->set_lk_max = __lock_set_lk_max;

#ifdef	HAVE_RPC
	/*
	 * If we have a client, overwrite what we just set up to point
	 * to the client functions.
	 */
	if (F_ISSET(dbenv, DB_ENV_RPCCLIENT)) {
		dbenv->set_lk_conflicts = __dbcl_set_lk_conflict;
		dbenv->set_lk_detect = __dbcl_set_lk_detect;
		dbenv->set_lk_max = __dbcl_set_lk_max;
	}
#endif
}

/*
 * __lock_dbenv_close --
 *	Lock specific destruction of the DB_ENV structure.
 *
 * PUBLIC: void __lock_dbenv_close __P((DB_ENV *));
 */
void
__lock_dbenv_close(dbenv)
	DB_ENV *dbenv;
{
	if (!F_ISSET(dbenv, DB_ENV_USER_ALLOC) && dbenv->lk_conflicts != NULL) {
		__os_free(dbenv->lk_conflicts,
		    dbenv->lk_modes * dbenv->lk_modes);
		dbenv->lk_conflicts = NULL;
	}
}

/*
 * __lock_open --
 *	Internal version of lock_open: only called from DB_ENV->open.
 *
 * PUBLIC: int __lock_open __P((DB_ENV *));
 */
int
__lock_open(dbenv)
	DB_ENV *dbenv;
{
	DB_LOCKREGION *region;
	DB_LOCKTAB *lt;
	size_t size;
	int ret;

	/* Create the lock table structure. */
	if ((ret = __os_calloc(dbenv, 1, sizeof(DB_LOCKTAB), &lt)) != 0)
		return (ret);
	lt->dbenv = dbenv;

	/* Join/create the lock region. */
	lt->reginfo.type = REGION_TYPE_LOCK;
	lt->reginfo.id = INVALID_REGION_ID;
	lt->reginfo.mode = dbenv->db_mode;
	lt->reginfo.flags = REGION_JOIN_OK;
	if (F_ISSET(dbenv, DB_ENV_CREATE))
		F_SET(&lt->reginfo, REGION_CREATE_OK);
	size = __lock_region_size(dbenv);
	if ((ret = __db_r_attach(dbenv, &lt->reginfo, size)) != 0)
		goto err;

	/* If we created the region, initialize it. */
	if (F_ISSET(&lt->reginfo, REGION_CREATE))
		if ((ret = __lock_init(dbenv, lt)) != 0)
			goto err;

	/* Set the local addresses. */
	region = lt->reginfo.primary =
	    R_ADDR(&lt->reginfo, lt->reginfo.rp->primary);

	/* Check for incompatible automatic deadlock detection requests. */
	if (dbenv->lk_detect != DB_LOCK_NORUN) {
		if (region->detect != DB_LOCK_NORUN &&
		    dbenv->lk_detect != DB_LOCK_DEFAULT &&
		    region->detect != dbenv->lk_detect) {
			__db_err(dbenv,
		    "lock_open: incompatible deadlock detector mode");
			ret = EINVAL;
			goto err;
		}

		/*
		 * Upgrade if our caller wants automatic detection, and it
		 * was not currently being done, whether or not we created
		 * the region.
		 */
		if (region->detect == DB_LOCK_NORUN)
			region->detect = dbenv->lk_detect;
	}

	/* Set remaining pointers into region. */
	lt->conflicts = (u_int8_t *)R_ADDR(&lt->reginfo, region->conf_off);
	lt->obj_tab = (DB_HASHTAB *)R_ADDR(&lt->reginfo, region->obj_off);
	lt->locker_tab = (DB_HASHTAB *)R_ADDR(&lt->reginfo, region->locker_off);

	R_UNLOCK(dbenv, &lt->reginfo);

	dbenv->lk_handle = lt;
	return (0);

err:	if (lt->reginfo.addr != NULL) {
		if (F_ISSET(&lt->reginfo, REGION_CREATE))
			ret = __db_panic(dbenv, ret);
		R_UNLOCK(dbenv, &lt->reginfo);
		(void)__db_r_detach(dbenv, &lt->reginfo, 0);
	}
	__os_free(lt, sizeof(*lt));
	return (ret);
}

/*
 * __lock_init --
 *	Initialize the lock region.
 */
static int
__lock_init(dbenv, lt)
	DB_ENV *dbenv;
	DB_LOCKTAB *lt;
{
	const u_int8_t *lk_conflicts;
	struct __db_lock *lp;
	DB_LOCKER *lidp;
	DB_LOCKOBJ *op;
	DB_LOCKREGION *region;
#ifdef MUTEX_SYSTEM_RESOURCES
	size_t maint_size;
#endif
	u_int32_t i, lk_modes, nelements;
	u_int8_t *addr;
	int ret;

	if ((ret = __db_shalloc(lt->reginfo.addr,
	    sizeof(DB_LOCKREGION), 0, &lt->reginfo.primary)) != 0)
		goto mem_err;
	lt->reginfo.rp->primary = R_OFFSET(&lt->reginfo, lt->reginfo.primary);
	region = lt->reginfo.primary;
	memset(region, 0, sizeof(*region));

	/* Select a conflict matrix if none specified. */
	if (dbenv->lk_modes == 0)
		if (CDB_LOCKING(dbenv)) {
			lk_modes = DB_LOCK_RW_N + 1;
			lk_conflicts = db_cdb_conflicts;
		} else {
			lk_modes = DB_LOCK_RW_N;
			lk_conflicts = db_rw_conflicts;
		}
	else {
		lk_modes = dbenv->lk_modes;
		lk_conflicts = dbenv->lk_conflicts;
	}

	region->id = 0;
	region->need_dd = 0;
	region->detect = DB_LOCK_NORUN;
	region->maxlocks = dbenv->lk_max;
	region->table_size = __db_tablesize(dbenv->lk_max);
	region->nmodes = lk_modes;
	region->nlockers = 0;
	region->maxnlockers = 0;
	region->nconflicts = 0;
	region->nrequests = 0;
	region->nreleases = 0;
	region->ndeadlocks = 0;

	nelements = region->table_size;

	/* Allocate room for the conflict matrix and initialize it. */
	if ((ret =
	    __db_shalloc(lt->reginfo.addr, lk_modes * lk_modes, 0, &addr)) != 0)
		goto mem_err;
	memcpy(addr, lk_conflicts, lk_modes * lk_modes);
	region->conf_off = R_OFFSET(&lt->reginfo, addr);

	/* Allocate room for the object hash table and initialize it. */
	if ((ret = __db_shalloc(lt->reginfo.addr,
	    nelements * sizeof(DB_HASHTAB), 0, &addr)) != 0)
		goto mem_err;
	__db_hashinit(addr, nelements);
	region->obj_off = R_OFFSET(&lt->reginfo, addr);

	/* Allocate room for the locker hash table and initialize it. */
	if ((ret = __db_shalloc(lt->reginfo.addr,
	    nelements * sizeof(DB_HASHTAB), 0, &addr)) != 0)
		goto mem_err;
	__db_hashinit(addr, nelements);
	region->locker_off = R_OFFSET(&lt->reginfo, addr);

#ifdef	MUTEX_SYSTEM_RESOURCES
	maint_size = __lock_region_maint(dbenv);
	/* Allocate room for the locker maintenance info and initialize it. */
	if ((ret = __db_shalloc(lt->reginfo.addr,
	    sizeof(REGMAINT) + maint_size, 0, &addr)) != 0)
		goto mem_err;
	__db_maintinit(&lt->reginfo, addr, maint_size);
	region->maint_off = R_OFFSET(&lt->reginfo, addr);
#endif

	/*
	 * Initialize locks onto a free list. Initialize and lock the mutex
	 * so that when we need to block, all we need do is try to acquire
	 * the mutex.
	 */
	SH_TAILQ_INIT(&region->free_locks);
	for (i = 0; i < region->maxlocks; ++i) {
		if ((ret = __db_shalloc(lt->reginfo.addr,
		    sizeof(struct __db_lock), MUTEX_ALIGN, &lp)) != 0)
			goto mem_err;
		lp->status = DB_LSTAT_FREE;
		if ((ret = __db_shmutex_init(dbenv, &lp->mutex,
		    R_OFFSET(&lt->reginfo, &lp->mutex) + DB_FCNTL_OFF_LOCK,
		    MUTEX_SELF_BLOCK, &lt->reginfo,
		    (REGMAINT *)R_ADDR(&lt->reginfo, region->maint_off))) != 0)
			return (ret);
		MUTEX_LOCK(&lp->mutex, lt->dbenv->lockfhp);
		SH_TAILQ_INSERT_HEAD(&region->free_locks, lp, links, __db_lock);
	}

	/* Initialize objects onto a free list.  */
	SH_TAILQ_INIT(&region->dd_objs);
	SH_TAILQ_INIT(&region->free_objs);
	for (i = 0; i < region->maxlocks; ++i) {
		if ((ret = __db_shalloc(lt->reginfo.addr,
		    sizeof(DB_LOCKOBJ), 0, &op)) != 0)
			goto mem_err;
		SH_TAILQ_INSERT_HEAD(
		    &region->free_objs, op, links, __db_lockobj);
	}

	/* Initialize lockers onto a free list.  */
	SH_TAILQ_INIT(&region->free_lockers);
	for (i = 0; i < region->maxlocks; ++i) {
		if ((ret = __db_shalloc(lt->reginfo.addr,
		    sizeof(DB_LOCKER), 0, &lidp)) != 0) {
mem_err:	__db_err(dbenv, "Unable to allocate memory for the lock table");
		return (ret);
	}
		SH_TAILQ_INSERT_HEAD(
		    &region->free_lockers, lidp, links, __db_locker);
	}

	return (0);
}

/*
 * __lock_close --
 *	Internal version of lock_close: only called from db_appinit.
 *
 * PUBLIC: int __lock_close __P((DB_ENV *));
 */
int
__lock_close(dbenv)
	DB_ENV *dbenv;
{
	DB_LOCKTAB *lt;
	int ret;

	lt = dbenv->lk_handle;

	/* Detach from the region. */
	ret = __db_r_detach(dbenv, &lt->reginfo, 0);

	__os_free(lt, sizeof(*lt));

	dbenv->lk_handle = NULL;
	return (ret);
}

/*
 * __lock_region_size --
 *	Return the region size.
 */
static size_t
__lock_region_size(dbenv)
	DB_ENV *dbenv;
{
	size_t retval;
	u_int32_t i, nelements, nlocks;
	int gb;

	gb = 0;
#ifdef DIAGNOSTIC
	/*
	 * The below precise calculation doesn't leave enough space for guard
	 * bytes, of which there is one plus whatever alignment wastage for
	 * each __db_shalloc.  Give ourselves some breathing room.
	 */
	gb = 1;
#endif

	nlocks = dbenv->lk_max;
	nelements = __db_tablesize(dbenv->lk_max);

	/*
	 * Figure out how much space we're going to need.  This list should
	 * map one-to-one with the __db_shalloc calls in __lock_init.
	 */
	retval = 0;
	retval += ALIGN(sizeof(DB_LOCKREGION) + gb, 1);
	retval += ALIGN(dbenv->lk_modes * dbenv->lk_modes + gb, 1);
	retval += ALIGN(nelements * (sizeof(DB_HASHTAB) + gb), 1);
	retval += ALIGN(nelements * (sizeof(DB_HASHTAB) + gb), 1);
#ifdef MUTEX_SYSTEM_RESOURCES
	retval += ALIGN(sizeof(REGMAINT) + __lock_region_maint(dbenv) + gb, 1);
#endif
	for (i = 0; i < nlocks; ++i)
		retval +=
		    ALIGN(sizeof(struct __db_lock) + gb , MUTEX_ALIGN);
	for (i = 0; i < nlocks; ++i)
		retval += ALIGN(sizeof(DB_LOCKOBJ) + gb , 1);
	for (i = 0; i < nlocks; ++i)
		retval += ALIGN(sizeof(DB_LOCKER) + gb , 1);

	/*
	 * Aproximate the memory allocation overhead.  This breaks the
	 * abstraction a little, but seems better than a WAG.
	 */
	 retval += nlocks * (2 * sizeof(ssize_t) + sizeof(size_t));

	/*
	 * Include 16 bytes of string space per lock.  DB doesn't use it
	 * because we pre-allocate lock space for DBTs in the structure.
	 */
	retval += ALIGN(nlocks * 16, sizeof(size_t));

	/* And we keep getting this wrong, let's be generous. */
	retval += 16 * 1024;

	return (retval);
}

#ifdef MUTEX_SYSTEM_RESOURCES
/*
 * __lock_region_maint --
 *	Return the amount of space needed for region maintenance info.
 */
static size_t
__lock_region_maint(dbenv)
	DB_ENV *dbenv;
{
	size_t s;

	s = sizeof(MUTEX *) * dbenv->lk_max;
	return (s);
}
#endif

/*
 * __lock_region_destroy
 *	Destroy any region maintenance info.
 *
 * PUBLIC: void __lock_region_destroy __P((DB_ENV *, REGINFO *));
 */
void
__lock_region_destroy(dbenv, infop)
	DB_ENV *dbenv;
	REGINFO *infop;
{
	DB_LOCKREGION *region;

	COMPQUIET(dbenv, NULL);
	region = R_ADDR(infop, infop->rp->primary);  

	__db_shlocks_destroy(infop,
	    (REGMAINT *)R_ADDR(infop, region->maint_off));
	return;
}
