/*
 * Copyright (c) 2000-2004 Proofpoint, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include <sm/gen.h>
SM_RCSID("@(#)$Id: rpool.c,v 1.29 2013-11-22 20:51:43 ca Exp $")

/*
**  resource pools
**  For documentation, see rpool.html
*/

#include <sm/exc.h>
#include <sm/heap.h>
#include <sm/rpool.h>
#include <sm/varargs.h>
#include <sm/conf.h>
#if _FFR_PERF_RPOOL
# include <syslog.h>
#endif

const char SmRpoolMagic[] = "sm_rpool";

typedef union
{
	SM_POOLLINK_T	link;
	char		align[SM_ALIGN_SIZE];
} SM_POOLHDR_T;

static char	*sm_rpool_allocblock_x __P((SM_RPOOL_T *, size_t));
static char	*sm_rpool_allocblock __P((SM_RPOOL_T *, size_t));

/*
**  Tune this later
*/

#define POOLSIZE		4096
#define BIG_OBJECT_RATIO	10

/*
**  SM_RPOOL_ALLOCBLOCK_X -- allocate a new block for an rpool.
**
**	Parameters:
**		rpool -- rpool to which the block should be added.
**		size -- size of block.
**
**	Returns:
**		Pointer to block.
**
**	Exceptions:
**		F:sm_heap -- out of memory
*/

static char *
sm_rpool_allocblock_x(rpool, size)
	SM_RPOOL_T *rpool;
	size_t size;
{
	SM_POOLLINK_T *p;

	p = sm_malloc_x(sizeof(SM_POOLHDR_T) + size);
	p->sm_pnext = rpool->sm_pools;
	rpool->sm_pools = p;
	return (char*) p + sizeof(SM_POOLHDR_T);
}

/*
**  SM_RPOOL_ALLOCBLOCK -- allocate a new block for an rpool.
**
**	Parameters:
**		rpool -- rpool to which the block should be added.
**		size -- size of block.
**
**	Returns:
**		Pointer to block, NULL on failure.
*/

static char *
sm_rpool_allocblock(rpool, size)
	SM_RPOOL_T *rpool;
	size_t size;
{
	SM_POOLLINK_T *p;

	p = sm_malloc(sizeof(SM_POOLHDR_T) + size);
	if (p == NULL)
		return NULL;
	p->sm_pnext = rpool->sm_pools;
	rpool->sm_pools = p;
	return (char*) p + sizeof(SM_POOLHDR_T);
}

/*
**  SM_RPOOL_MALLOC_TAGGED_X -- allocate memory from rpool
**
**	Parameters:
**		rpool -- rpool from which memory should be allocated;
**			can be NULL, use sm_malloc() then.
**		size -- size of block.
**		file -- filename.
**		line -- line number in file.
**		group -- heap group for debugging.
**
**	Returns:
**		Pointer to block.
**
**	Exceptions:
**		F:sm_heap -- out of memory
**
**	Notice: XXX
**		if size == 0 and the rpool is new (no memory
**		allocated yet) NULL is returned!
**		We could solve this by
**		- wasting 1 byte (size < avail)
**		- checking for rpool->sm_poolptr != NULL
**		- not asking for 0 sized buffer
*/

void *
#if SM_HEAP_CHECK
sm_rpool_malloc_tagged_x(rpool, size, file, line, group)
	SM_RPOOL_T *rpool;
	size_t size;
	char *file;
	int line;
	int group;
#else /* SM_HEAP_CHECK */
sm_rpool_malloc_x(rpool, size)
	SM_RPOOL_T *rpool;
	size_t size;
#endif /* SM_HEAP_CHECK */
{
	char *ptr;

	if (rpool == NULL)
		return sm_malloc_tagged_x(size, file, line, group);

	/* Ensure that size is properly aligned. */
	if (size & SM_ALIGN_BITS)
		size = (size & ~SM_ALIGN_BITS) + SM_ALIGN_SIZE;

	/* The common case.  This is optimized for speed. */
	if (size <= rpool->sm_poolavail)
	{
		ptr = rpool->sm_poolptr;
		rpool->sm_poolptr += size;
		rpool->sm_poolavail -= size;
		return ptr;
	}

	/*
	**  The slow case: we need to call malloc.
	**  The SM_REQUIRE assertion is deferred until now, for speed.
	**  That's okay: we set rpool->sm_poolavail to 0 when we free an rpool,
	**  so the common case code won't be triggered on a dangling pointer.
	*/

	SM_REQUIRE(rpool->sm_magic == SmRpoolMagic);

	/*
	**  If size > sm_poolsize, then malloc a new block especially for
	**  this request.  Future requests will be allocated from the
	**  current pool.
	**
	**  What if the current pool is mostly unallocated, and the current
	**  request is larger than the available space, but < sm_poolsize?
	**  If we discard the current pool, and start allocating from a new
	**  pool, then we will be wasting a lot of space.  For this reason,
	**  we malloc a block just for the current request if size >
	**  sm_bigobjectsize, where sm_bigobjectsize <= sm_poolsize.
	**  Thus, the most space that we will waste at the end of a pool
	**  is sm_bigobjectsize - 1.
	*/

	if (size > rpool->sm_bigobjectsize)
	{
#if _FFR_PERF_RPOOL
		++rpool->sm_nbigblocks;
#endif
		return sm_rpool_allocblock_x(rpool, size);
	}
	SM_ASSERT(rpool->sm_bigobjectsize <= rpool->sm_poolsize);
	ptr = sm_rpool_allocblock_x(rpool, rpool->sm_poolsize);
	rpool->sm_poolptr = ptr + size;
	rpool->sm_poolavail = rpool->sm_poolsize - size;
#if _FFR_PERF_RPOOL
	++rpool->sm_npools;
#endif
	return ptr;
}

/*
**  SM_RPOOL_MALLOC_TAGGED -- allocate memory from rpool
**
**	Parameters:
**		rpool -- rpool from which memory should be allocated;
**			can be NULL, use sm_malloc() then.
**		size -- size of block.
**		file -- filename.
**		line -- line number in file.
**		group -- heap group for debugging.
**
**	Returns:
**		Pointer to block, NULL on failure.
**
**	Notice: XXX
**		if size == 0 and the rpool is new (no memory
**		allocated yet) NULL is returned!
**		We could solve this by
**		- wasting 1 byte (size < avail)
**		- checking for rpool->sm_poolptr != NULL
**		- not asking for 0 sized buffer
*/

void *
#if SM_HEAP_CHECK
sm_rpool_malloc_tagged(rpool, size, file, line, group)
	SM_RPOOL_T *rpool;
	size_t size;
	char *file;
	int line;
	int group;
#else /* SM_HEAP_CHECK */
sm_rpool_malloc(rpool, size)
	SM_RPOOL_T *rpool;
	size_t size;
#endif /* SM_HEAP_CHECK */
{
	char *ptr;

	if (rpool == NULL)
		return sm_malloc_tagged(size, file, line, group);

	/* Ensure that size is properly aligned. */
	if (size & SM_ALIGN_BITS)
		size = (size & ~SM_ALIGN_BITS) + SM_ALIGN_SIZE;

	/* The common case.  This is optimized for speed. */
	if (size <= rpool->sm_poolavail)
	{
		ptr = rpool->sm_poolptr;
		rpool->sm_poolptr += size;
		rpool->sm_poolavail -= size;
		return ptr;
	}

	/*
	**  The slow case: we need to call malloc.
	**  The SM_REQUIRE assertion is deferred until now, for speed.
	**  That's okay: we set rpool->sm_poolavail to 0 when we free an rpool,
	**  so the common case code won't be triggered on a dangling pointer.
	*/

	SM_REQUIRE(rpool->sm_magic == SmRpoolMagic);

	/*
	**  If size > sm_poolsize, then malloc a new block especially for
	**  this request.  Future requests will be allocated from the
	**  current pool.
	**
	**  What if the current pool is mostly unallocated, and the current
	**  request is larger than the available space, but < sm_poolsize?
	**  If we discard the current pool, and start allocating from a new
	**  pool, then we will be wasting a lot of space.  For this reason,
	**  we malloc a block just for the current request if size >
	**  sm_bigobjectsize, where sm_bigobjectsize <= sm_poolsize.
	**  Thus, the most space that we will waste at the end of a pool
	**  is sm_bigobjectsize - 1.
	*/

	if (size > rpool->sm_bigobjectsize)
	{
#if _FFR_PERF_RPOOL
		++rpool->sm_nbigblocks;
#endif
		return sm_rpool_allocblock(rpool, size);
	}
	SM_ASSERT(rpool->sm_bigobjectsize <= rpool->sm_poolsize);
	ptr = sm_rpool_allocblock(rpool, rpool->sm_poolsize);
	if (ptr == NULL)
		return NULL;
	rpool->sm_poolptr = ptr + size;
	rpool->sm_poolavail = rpool->sm_poolsize - size;
#if _FFR_PERF_RPOOL
	++rpool->sm_npools;
#endif
	return ptr;
}

/*
**  SM_RPOOL_NEW_X -- create a new rpool.
**
**	Parameters:
**		parent -- pointer to parent rpool, can be NULL.
**
**	Returns:
**		Pointer to new rpool.
*/

SM_RPOOL_T *
sm_rpool_new_x(parent)
	SM_RPOOL_T *parent;
{
	SM_RPOOL_T *rpool;

	rpool = sm_malloc_x(sizeof(SM_RPOOL_T));
	if (parent == NULL)
		rpool->sm_parentlink = NULL;
	else
	{
		SM_TRY
			rpool->sm_parentlink = sm_rpool_attach_x(parent,
					(SM_RPOOL_RFREE_T) sm_rpool_free,
					(void *) rpool);
		SM_EXCEPT(exc, "*")
			sm_free(rpool);
			sm_exc_raise_x(exc);
		SM_END_TRY
	}
	rpool->sm_magic = SmRpoolMagic;

	rpool->sm_poolsize = POOLSIZE - sizeof(SM_POOLHDR_T);
	rpool->sm_bigobjectsize = rpool->sm_poolsize / BIG_OBJECT_RATIO;
	rpool->sm_poolptr = NULL;
	rpool->sm_poolavail = 0;
	rpool->sm_pools = NULL;

	rpool->sm_rptr = NULL;
	rpool->sm_ravail = 0;
	rpool->sm_rlists = NULL;
#if _FFR_PERF_RPOOL
	rpool->sm_nbigblocks = 0;
	rpool->sm_npools = 0;
#endif

	return rpool;
}

/*
**  SM_RPOOL_SETSIZES -- set sizes for rpool.
**
**	Parameters:
**		poolsize -- size of a single rpool block.
**		bigobjectsize -- if this size is exceeded, an individual
**			block is allocated (must be less or equal poolsize).
**
**	Returns:
**		none.
*/

void
sm_rpool_setsizes(rpool, poolsize, bigobjectsize)
	SM_RPOOL_T *rpool;
	size_t poolsize;
	size_t bigobjectsize;
{
	SM_REQUIRE(poolsize >= bigobjectsize);
	if (poolsize == 0)
		poolsize = POOLSIZE - sizeof(SM_POOLHDR_T);
	if (bigobjectsize == 0)
		bigobjectsize = poolsize / BIG_OBJECT_RATIO;
	rpool->sm_poolsize = poolsize;
	rpool->sm_bigobjectsize = bigobjectsize;
}

/*
**  SM_RPOOL_FREE -- free an rpool and release all of its resources.
**
**	Parameters:
**		rpool -- rpool to free.
**
**	Returns:
**		none.
*/

void
sm_rpool_free(rpool)
	SM_RPOOL_T *rpool;
{
	SM_RLIST_T *rl, *rnext;
	SM_RESOURCE_T *r, *rmax;
	SM_POOLLINK_T *pp, *pnext;

	if (rpool == NULL)
		return;

	/*
	**  It's important to free the resources before the memory pools,
	**  because the resource free functions might modify the contents
	**  of the memory pools.
	*/

	rl = rpool->sm_rlists;
	if (rl != NULL)
	{
		rmax = rpool->sm_rptr;
		for (;;)
		{
			for (r = rl->sm_rvec; r < rmax; ++r)
			{
				if (r->sm_rfree != NULL)
					r->sm_rfree(r->sm_rcontext);
			}
			rnext = rl->sm_rnext;
			sm_free(rl);
			if (rnext == NULL)
				break;
			rl = rnext;
			rmax = &rl->sm_rvec[SM_RLIST_MAX];
		}
	}

	/*
	**  Now free the memory pools.
	*/

	for (pp = rpool->sm_pools; pp != NULL; pp = pnext)
	{
		pnext = pp->sm_pnext;
		sm_free(pp);
	}

	/*
	**  Disconnect rpool from its parent.
	*/

	if (rpool->sm_parentlink != NULL)
		*rpool->sm_parentlink = NULL;

	/*
	**  Setting these fields to zero means that any future to attempt
	**  to use the rpool after it is freed will cause an assertion failure.
	*/

	rpool->sm_magic = NULL;
	rpool->sm_poolavail = 0;
	rpool->sm_ravail = 0;

#if _FFR_PERF_RPOOL
	if (rpool->sm_nbigblocks > 0 || rpool->sm_npools > 1)
		syslog(LOG_NOTICE,
			"perf: rpool=%lx, sm_nbigblocks=%d, sm_npools=%d",
			(long) rpool, rpool->sm_nbigblocks, rpool->sm_npools);
	rpool->sm_nbigblocks = 0;
	rpool->sm_npools = 0;
#endif /* _FFR_PERF_RPOOL */
	sm_free(rpool);
}

/*
**  SM_RPOOL_ATTACH_X -- attach a resource to an rpool.
**
**	Parameters:
**		rpool -- rpool to which resource should be attached.
**		rfree -- function to call when rpool is freed.
**		rcontext -- argument for function to call when rpool is freed.
**
**	Returns:
**		Pointer to allocated function.
**
**	Exceptions:
**		F:sm_heap -- out of memory
*/

SM_RPOOL_ATTACH_T
sm_rpool_attach_x(rpool, rfree, rcontext)
	SM_RPOOL_T *rpool;
	SM_RPOOL_RFREE_T rfree;
	void *rcontext;
{
	SM_RLIST_T *rl;
	SM_RPOOL_ATTACH_T a;

	SM_REQUIRE_ISA(rpool, SmRpoolMagic);

	if (rpool->sm_ravail == 0)
	{
		rl = sm_malloc_x(sizeof(SM_RLIST_T));
		rl->sm_rnext = rpool->sm_rlists;
		rpool->sm_rlists = rl;
		rpool->sm_rptr = rl->sm_rvec;
		rpool->sm_ravail = SM_RLIST_MAX;
	}

	a = &rpool->sm_rptr->sm_rfree;
	rpool->sm_rptr->sm_rfree = rfree;
	rpool->sm_rptr->sm_rcontext = rcontext;
	++rpool->sm_rptr;
	--rpool->sm_ravail;
	return a;
}

#if DO_NOT_USE_STRCPY
/*
**  SM_RPOOL_STRDUP_X -- Create a copy of a C string
**
**	Parameters:
**		rpool -- rpool to use.
**		s -- the string to copy.
**
**	Returns:
**		pointer to newly allocated string.
*/

char *
# if SM_HEAP_CHECK > 2
sm_rpool_strdup_tagged_x
# else
sm_rpool_strdup_x
# endif
	(rpool, s
# if SM_HEAP_CHECK > 2
	, tag, line, group
# endif
	)
	SM_RPOOL_T *rpool;
	const char *s;
# if SM_HEAP_CHECK > 2
	char *tag;
	int line;
	int group;
# else
#  define tag  "sm_rpool_strdup_x"
#  define line 1
#  define group 1
# endif
{
	size_t l;
	char *n;

	l = strlen(s);
	SM_ASSERT(l + 1 > l);
# if SM_HEAP_CHECK
	n = sm_rpool_malloc_tagged_x(rpool, l + 1, tag, line, group);
# else
	n = sm_rpool_malloc_x(rpool, l + 1);
# endif
	sm_strlcpy(n, s, l + 1);
	return n;
}
# if SM_HEAP_CHECK <= 2
#  undef tag
#  undef line
#  undef group
# endif
#endif /* DO_NOT_USE_STRCPY */
