/*
 * Copyright (c) 2000-2003, 2005 Sendmail, 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/generic.h"
SM_RCSID("@(#)$Id: rpool.c,v 1.29 2005/02/11 21:44:03 ca Exp $")

/*
**  resource pools
**
**	ToDo:
**	- rpool_clear: clear out all data + resources, but don't
**		free the rpool itself. Just reset all counters etc.
**		This way we can just reuse the rpool.
**	- reorganize: put organizational data outside the blocks
**		that will be returned to the caller: this helps
**		to align memory requests. Moreover, it may make
**		rpool_clear easier to implement.
**	- figure out whether more specific errors should be returned,
**	  i.e., if memory is exceeded: was it due to self-imposed
**	  restriction (maxsize) or not enough memory from OS?
**	  This complicates the program (one more parameter almost
**	  everywhere). Is it worth it?
**
**  Notice: this isn't thread safe by itself: if an rpool is
**	used by multiple threads, it must be externally locked.
*/

#include "sm/assert.h"
#include "sm/heap.h"
#include "sm/rpool.h"
#include "sm/rpool-int.h"
#include "sm/memops.h"
#include "sm/varargs.h"
#include "sm/limits.h"
#if SM_PERF_RPOOL
# include <syslog.h>
#endif

/*
**  fixme: Tune this?!
*/

/* size of a single rpool */
#define POOLSIZE		4096
#define BIG_OBJECT_RATIO	10

/* maximum size of an rpool, including all of its blocks */
#ifndef SM_RPOOL_MAXSIZE
# if defined(SIZE_T_MAX) && SIZE_T_MAX < INT_MAX
#  define SM_RPOOL_MAXSIZE	(SIZE_T_MAX >> 2)
# else /* defined(SIZE_T_MAX) && SIZE_T_MAX < INT_MAX */
#  define SM_RPOOL_MAXSIZE	(INT_MAX >> 2)
# endif /* defined(SIZE_T_MAX) && SIZE_T_MAX < INT_MAX */
#endif /* SM_RPOOL_MAXSIZE */

/*
**  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(sm_rpool_P rpool, size_t size)
{
	sm_rp_plink_T *p;

	p = sm_malloc(sizeof(sm_rp_hdr_T) + size);
	if (p == NULL)
		return NULL;
	p->sm_rp_pnext = rpool->sm_rp_pools;
	rpool->sm_rp_pools = p;
	return (char *) p + sizeof(sm_rp_hdr_T);
}

/*
**  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.
#if SM_HEAP_CHECK
**		file -- filename.
**		line -- line number in file.
**		group -- heap group for debugging.
#endif * SM_HEAP_CHECK *
**
**	Returns:
**		Pointer to block, NULL on failure.
**
**	Notice:
**		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_rp_ptr != NULL
**		- not asking for 0 sized buffer
*/

void *
#if SM_HEAP_CHECK
sm_rpool_malloc_tagged(sm_rpool_P rpool, size_t size, char *file, int line,
			int group)
#else
sm_rpool_malloc(sm_rpool_P rpool, size_t size)
#endif
{
	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;

	/* Maximum size exceeded? */
	if (size > rpool->sm_rp_maxsize - rpool->sm_rp_totsize )
		return NULL;

	/* The common case.  This is optimized for speed. */
	if (size <= rpool->sm_rp_avail)
	{
		ptr = rpool->sm_rp_ptr;
		rpool->sm_rp_ptr += size;
		rpool->sm_rp_avail -= size;
		rpool->sm_rp_totsize += 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_rp_avail 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 == SM_RPOOL_MAGIC);

	/*
	**  If size > sm_rp_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_rp_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_rp_bigobjsize, where sm_rp_bigobjsize <= sm_rp_poolsize.
	**  Thus, the most space that we will waste at the end of a pool
	**  is sm_rp_bigobjsize - 1.
	*/

	if (size > rpool->sm_rp_bigobjsize)
	{
#if SM_PERF_RPOOL
		++rpool->sm_rp_nbigblocks;
#endif
		rpool->sm_rp_totsize += size;
		return sm_rpool_allocblock(rpool, size);
	}
	SM_ASSERT(rpool->sm_rp_bigobjsize <= rpool->sm_rp_poolsize);
	ptr = sm_rpool_allocblock(rpool, rpool->sm_rp_poolsize);
	if (ptr == NULL)
		return NULL;
	rpool->sm_rp_ptr = ptr + size;
	rpool->sm_rp_avail = rpool->sm_rp_poolsize - size;
	rpool->sm_rp_totsize += size;
#if SM_PERF_RPOOL
	++rpool->sm_rp_npools;
#endif
	return ptr;
}

/*
**  SM_RPOOL_REALLOC[_TAGGED] -- reallocate memory from rpool
**
**	Parameters:
**		rpool -- rpool from which memory should be allocated;
**			can be NULL, use sm_malloc() then.
**		ptr -- old pointer.
**		oldsize -- old size of block.
**		newsize -- new size of block.
#if SM_HEAP_CHECK
**		file -- filename.
**		line -- line number in file.
**		group -- heap group for debugging.
#endif * SM_HEAP_CHECK *
**
**	Returns:
**		Pointer to block, NULL on failure.
**
**	Notice:
**		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_rp_ptr != NULL
**		- not asking for 0 sized buffer
*/

void *
#if SM_HEAP_CHECK
sm_rpool_realloc_tagged(sm_rpool_P rpool, void *oldptr, size_t oldsize,
			size_t newsize,
			char *file, int line, int group)
#else
sm_rpool_realloc(sm_rpool_P rpool, void *oldptr, size_t oldsize, size_t newsize)
#endif
{
	char *nptr;

	if (rpool == NULL)
		return sm_realloc(oldptr, newsize);

	SM_ASSERT(oldsize <= newsize);

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

	/* Maximum size exceeded? */
	if (newsize > rpool->sm_rp_maxsize - rpool->sm_rp_totsize )
		return NULL;

	/* The common case.  This is optimized for speed. */
	if (newsize <= rpool->sm_rp_avail)
	{
		nptr = rpool->sm_rp_ptr;
		rpool->sm_rp_ptr += newsize;
		rpool->sm_rp_avail -= newsize;
		rpool->sm_rp_totsize += newsize;
		sm_memcpy(nptr, oldptr, oldsize);
		return nptr;
	}

	/*
	**  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_rp_avail 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 == SM_RPOOL_MAGIC);

	/*
	**  If size > sm_rp_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_rp_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_rp_bigobjsize, where sm_rp_bigobjsize <= sm_rp_poolsize.
	**  Thus, the most space that we will waste at the end of a pool
	**  is sm_rp_bigobjsize - 1.
	*/

	if (newsize > rpool->sm_rp_bigobjsize)
	{
		nptr = sm_rpool_allocblock(rpool, newsize);
		if (nptr == NULL)
			return NULL;
		rpool->sm_rp_totsize += newsize;
#if SM_PERF_RPOOL
		++rpool->sm_rp_nbigblocks;
#endif
		sm_memcpy(nptr, oldptr, oldsize);
		return nptr;
	}
	SM_ASSERT(rpool->sm_rp_bigobjsize <= rpool->sm_rp_poolsize);
	nptr = sm_rpool_allocblock(rpool, rpool->sm_rp_poolsize);
	if (nptr == NULL)
		return NULL;
	rpool->sm_rp_ptr = nptr + newsize;
	rpool->sm_rp_avail = rpool->sm_rp_poolsize - newsize;
	rpool->sm_rp_totsize += newsize;
#if SM_PERF_RPOOL
	++rpool->sm_rp_npools;
#endif
	sm_memcpy(nptr, oldptr, oldsize);
	return nptr;
}

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

sm_rpool_P
sm_rpool_new(sm_rpool_P parent)
{
	sm_rpool_P rpool;

	rpool = sm_malloc(sizeof(*rpool));
	if (rpool == NULL)
		return NULL;
	if (parent == NULL)
	{
		/* no parent, no need to attach */
		rpool->sm_rp_parent = NULL;
	}
	else
	{
		/* attach this rpool to its parent */
		rpool->sm_rp_parent = sm_rpool_attach(parent,
					(sm_rp_rsrcfree_T) sm_rpool_delete,
					(void *) rpool);
		if (rpool->sm_rp_parent == NULL)
		{
			sm_free_size(rpool, sizeof(*rpool));
			return NULL;
		}
	}
	rpool->sm_magic = SM_RPOOL_MAGIC;

	rpool->sm_rp_poolsize = POOLSIZE - sizeof(sm_rp_hdr_T);
	rpool->sm_rp_bigobjsize = rpool->sm_rp_poolsize / BIG_OBJECT_RATIO;
	rpool->sm_rp_ptr = NULL;
	rpool->sm_rp_avail = 0;
	rpool->sm_rp_maxsize = SM_RPOOL_MAXSIZE;
	rpool->sm_rp_totsize = 0;
	rpool->sm_rp_pools = NULL;

	rpool->sm_rp_rptr = NULL;
	rpool->sm_rp_ravail = 0;
	rpool->sm_rp_rlists = NULL;
#if SM_PERF_RPOOL
	rpool->sm_rp_nbigblocks = 0;
	rpool->sm_rp_npools = 0;
#endif

	return rpool;
}

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

void
sm_rpool_setsizes(sm_rpool_P rpool, size_t poolsize, size_t bigobjectsize,
		size_t maxsize)
{
	SM_REQUIRE(poolsize >= bigobjectsize);
	if (poolsize == 0)
		poolsize = POOLSIZE - sizeof(sm_rp_hdr_T);
	if (bigobjectsize == 0)
		bigobjectsize = poolsize / BIG_OBJECT_RATIO;
	rpool->sm_rp_poolsize = poolsize;
	rpool->sm_rp_bigobjsize = bigobjectsize;
	rpool->sm_rp_maxsize = (maxsize == 0) ? SM_RPOOL_MAXSIZE : maxsize;
}

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

/*
fixme: rename this??
currently:	new/delete
alternatives:	start/stop
		open/close
		init/term
		initialize/terminate
		begin/end
*/

void
sm_rpool_delete(sm_rpool_P rpool)
{
	sm_rp_rlist_T *rl, *rnext;
	sm_rp_rsrc_T *r, *rmax;
	sm_rp_plink_T *pp, *pnext;

	if (rpool == NULL)
		return;
	SM_REQUIRE_ISA(rpool, SM_RPOOL_MAGIC);

	/*
	**  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_rp_rlists;
	if (rl != NULL)
	{
		rmax = rpool->sm_rp_rptr;
		for (;;)
		{
			for (r = rl->sm_rpl_vec; r < rmax; ++r)
			{
				if (r->sm_rfree != NULL)
					r->sm_rfree(r->sm_rcontext);
			}
			rnext = rl->sm_rpl_next;
			sm_free(rl);
			if (rnext == NULL)
				break;
			rl = rnext;
			rmax = &rl->sm_rpl_vec[SM_RLIST_MAX];
		}
	}

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

	for (pp = rpool->sm_rp_pools; pp != NULL; pp = pnext)
	{
		pnext = pp->sm_rp_pnext;
		sm_free(pp);
	}

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

	if (rpool->sm_rp_parent != NULL)
		*rpool->sm_rp_parent = 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 = SM_MAGIC_NULL;
	rpool->sm_rp_avail = 0;
	rpool->sm_rp_ravail = 0;

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

/*
**  SM_RPOOL_ATTACH -- 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.
*/

sm_rpool_attach_T
sm_rpool_attach(sm_rpool_P rpool, sm_rp_rsrcfree_T rfree, void *rcontext)
{
	sm_rp_rlist_T *rl;
	sm_rpool_attach_T a;

	SM_REQUIRE_ISA(rpool, SM_RPOOL_MAGIC);

	if (rpool->sm_rp_ravail == 0)
	{
		rl = sm_malloc(sizeof(sm_rp_rlist_T));
		if (rl == NULL)
			return NULL;
		rl->sm_rpl_next = rpool->sm_rp_rlists;
		rpool->sm_rp_rlists = rl;
		rpool->sm_rp_rptr = rl->sm_rpl_vec;
		rpool->sm_rp_ravail = SM_RLIST_MAX;
	}

	a = &rpool->sm_rp_rptr->sm_rfree;
	rpool->sm_rp_rptr->sm_rfree = rfree;
	rpool->sm_rp_rptr->sm_rcontext = rcontext;
	++rpool->sm_rp_rptr;
	--rpool->sm_rp_ravail;
	return a;
}

char *
sm_rpool_strdup(sm_rpool_P rpool, const char *str)
{
	size_t l;
	char *nstr;

	SM_REQUIRE(str != NULL);
	l = strlen(str) + 1;
	nstr = sm_rpool_malloc(rpool, l);
	if (nstr == NULL)
		return NULL;
	return (char *) sm_memcpy(nstr, str, l);
}

void *
sm_rpool_memdup(sm_rpool_P rpool, const void *ptr, size_t l)
{
	void *nptr;

	SM_REQUIRE(ptr != NULL);
	nptr = sm_rpool_malloc(rpool, l);
	if (nptr == NULL)
		return NULL;
	return sm_memcpy(nptr, ptr, l);
}

/*
**  SM_RPOOL_ZALLOC[_TAGGED] -- allocate memory from rpool, initialize to 0
**
**	Parameters:
**		rpool -- rpool from which memory should be allocated;
**			can be NULL, use sm_zalloc() then.
**		size -- size of block.
#if SM_HEAP_CHECK
**		file -- filename.
**		line -- line number in file.
**		group -- heap group for debugging.
#endif * SM_HEAP_CHECK *
**
**	Returns:
**		Pointer to block, NULL on failure.
*/

void *
#if SM_HEAP_CHECK
sm_rpool_zalloc_tagged(sm_rpool_P rpool, size_t size, char *file, int line,
			int group)
#else
sm_rpool_zalloc(sm_rpool_P rpool, size_t size)
#endif
{
	char *ptr;

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

	ptr = sm_rpool_malloc_tagged(rpool, size, file, line, group);
	if (ptr != NULL)
		sm_memzero(ptr, size);
	return ptr;
}



syntax highlighted by Code2HTML, v. 0.9.1