/*
 * Copyright (c) 2000-2006 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: heap.c,v 1.25 2006/10/05 04:27:37 ca Exp $")

/*
**  Debugging memory allocation package
**
**  If SM_HEAP_CHECK is set, then the variable SmHeapCheck
**  can be used to turn on/off heap checks at runtime.
**  NOTE: Do NOT turn on heap checks AFTER any allocation has been performed
**  because then the pointer isn't registered and hence a free() operation
**  will cause a failure.
*/

#include "sm/assert.h"
#include "sm/debug.h"
#include "sm/string.h"
#include "sm/heap.h"
#include "heapdbg.h"
#include "sm/memops.h"
#if SM_HEAP_CHECK
# include "sm/io.h"
# if MTA_USE_PTHREADS
#  include "sm/pthread.h"
# endif
#endif

/* undef all macro versions of the "functions" so they can be specified here */
#if !SM_HEAP_CHECK
# undef sm_malloc
# undef sm_zalloc
# undef sm_realloc
#endif /* !SM_HEAP_CHECK */
#undef sm_malloc_tagged
#undef sm_zalloc_tagged
#undef sm_free
#undef sm_free_tagged
#if SM_HEAP_CHECK
# undef sm_heap_register
# undef sm_heap_checkptr
# undef sm_heap_report
#endif /* SM_HEAP_CHECK */

#if SM_HEAP_CHECK
#if 0
SM_DEBUG_T SmHeapCheck = SM_DEBUG_INITIALIZER("sm_check_heap",
    "@(#)$Debug: sm_check_heap - check sm_malloc, sm_realloc, sm_free calls $");
#else /* 0 */
SM_DEBUG_T SmHeapCheck = 1;
#endif /* 0 */
# define HEAP_CHECK SmHeapCheck
#endif /* SM_HEAP_CHECK */

#define MALLOC_SIZE(size)	((size) == 0 ? 1 : (size))

#if !SM_HEAP_CHECK

# if !HAVE_MALLOC
/*
**  SM_MALLOC -- wrapper around malloc()
**
**	Parameters:
**		size -- size of requested memory.
**
**	Returns:
**		Pointer to memory region, NULL on error.
*/

void *
sm_malloc(size_t size)
{
	return malloc(MALLOC_SIZE(size));
}

/*
**  SM_REALLOC -- wrapper for realloc()
**
**	Parameters:
**		ptr -- pointer to old memory area.
**		size -- size of requested memory.
**
**	Returns:
**		Pointer to new memory area, NULL on failure.
*/

void *
sm_realloc(void *ptr, size_t size)
{
	return realloc(ptr, MALLOC_SIZE(size));
}
# endif /* !HAVE_MALLOC */

/*
**  SM_ZALLOC -- malloc() and initialize memory to 0
**
**	Parameters:
**		size -- size of requested memory.
**
**	Returns:
**		Pointer to memory region, NULL on error.
*/

void *
sm_zalloc(size_t size)
{
	void *ptr;

	ptr = malloc(MALLOC_SIZE(size));
	if (ptr != NULL)
		sm_memzero(ptr, size);
	return ptr;
}

#if !SM_FREE_NULL
/*
**  SM_FREE -- wrapper around free()
**
**	Parameters:
**		ptr -- pointer to memory region.
**
**	Returns:
**		none.
*/

void
sm_free(void *ptr)
{
	if (ptr == NULL)
		return;
	free(ptr);
	return;
}
#endif /* !SM_FREE_NULL */

/*
**  SM_FREE_SIZE -- wrapper around free()
**
**	Parameters:
**		ptr -- pointer to memory region.
**		size -- size of released memory.
**
**	Returns:
**		none.
*/

void
sm_free_size(void *ptr, size_t size)
{
	if (ptr == NULL)
		return;
# if SM_ZERO_FREE
	/* or other pattern? */
	sm_memzero(ptr, size);
# endif
	free(ptr);
	return;
}

#else /* !SM_HEAP_CHECK */

# if MTA_USE_PTHREADS
pthread_mutex_t	 SmHeapMutex = PTHREAD_MUTEX_INITIALIZER;
# endif

/*
**  Each allocated block is assigned a "group number".
**  By default, all blocks are assigned to group #1.
**  By convention, group #0 is for memory that is never freed.
**  You can use group numbers any way you want, in order to help make
**  sense of sm_heap_report output.
*/

int SmHeapGroup = 1;
int SmHeapMaxGroup = 1;

/*
**  Total number of bytes allocated.
**  This is only maintained if the sm_check_heap debug category is active.
*/

size_t SmHeapTotal = 0;

/*
**  High water mark: the most that SmHeapTotal has ever been.
*/

size_t SmHeapMaxTotal = 0;

/*
**  Maximum number of bytes that may be allocated at any one time.
**  0 means no limit.
**  This is only honoured if sm_check_heap is active.
*/

# if 0
SM_DEBUG_T SmHeapLimit = SM_DEBUG_INITIALIZER("sm_heap_limit",
    "@(#)$Debug: sm_heap_limit - max # of bytes permitted in heap $");
# endif

static sm_heap_item_T *SmHeapTable[SM_HEAP_TABLE_SIZE];

/*
**  SM_MALLOC_TAGGED -- wrapper around malloc(), debugging version.
**
**	Parameters:
**		size -- size of requested memory.
**		tag -- tag for debugging.
**		num -- additional value for debugging.
**		group -- heap group for debugging.
**
**	Returns:
**		Pointer to memory region.
*/

void *
sm_malloc_tagged(size_t size, char *tag, int num, int group)
{
	void *ptr;

	if (!HEAP_CHECK)
	{
		ptr = malloc(MALLOC_SIZE(size));
		return ptr;
	}

#if 0
	if (sm_xtrap_check())
		return NULL;
	if (sm_debug_active(&SmHeapLimit, 1)
	    && sm_debug_level(&SmHeapLimit) < SmHeapTotal + size)
		return NULL;
#endif
	ptr = malloc(MALLOC_SIZE(size));
	if (ptr != NULL && !sm_heap_register(ptr, size, tag, num, group))
	{
		free(ptr);
		ptr = NULL;
	}
	SmHeapTotal += size;
	if (SmHeapTotal > SmHeapMaxTotal)
		SmHeapMaxTotal = SmHeapTotal;
	return ptr;
}

/*
**  SM_ZALLOC_TAGGED -- wrapper around malloc(), debugging version.
**
**	Parameters:
**		size -- size of requested memory.
**		tag -- tag for debugging.
**		num -- additional value for debugging.
**		group -- heap group for debugging.
**
**	Returns:
**		Pointer to memory region.
*/

void *
sm_zalloc_tagged(size_t size, char *tag, int num, int group)
{
	void *ptr;

	if (!HEAP_CHECK)
	{
		ptr = malloc(MALLOC_SIZE(size));
		if (ptr != NULL)
			sm_memzero(ptr, size);
		return ptr;
	}

#if 0
	if (sm_xtrap_check())
		return NULL;
	if (sm_debug_active(&SmHeapLimit, 1)
	    && sm_debug_level(&SmHeapLimit) < SmHeapTotal + size)
		return NULL;
#endif
	ptr = malloc(MALLOC_SIZE(size));
	if (ptr != NULL && !sm_heap_register(ptr, size, tag, num, group))
	{
		free(ptr);
		ptr = NULL;
	}
	SmHeapTotal += size;
	if (SmHeapTotal > SmHeapMaxTotal)
		SmHeapMaxTotal = SmHeapTotal;
	if (ptr != NULL)
		sm_memzero(ptr, size);
	return ptr;
}

/*
**  SM_HEAP_REGISTER -- register a pointer into the heap for debugging.
**
**	Parameters:
**		ptr -- pointer to register.
**		size -- size of requested memory.
**		tag -- tag for debugging.
**		num -- additional value for debugging.
**		group -- heap group for debugging.
**
**	Returns:
**		true iff successfully registered (not yet in table).
*/

bool
sm_heap_register(void *ptr, size_t size, char *tag, int num, int group)
{
	int i;
# if MTA_USE_PTHREADS
	int r;
# endif
	sm_heap_item_T *hi;

	if (!HEAP_CHECK)
		return true;
	SM_REQUIRE(ptr != NULL);
	i = ptrhash(ptr);
# if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&SmHeapMutex);
	SM_LOCK_OK(r);
	if (r != 0)
		return false;
# endif
# if SM_CHECK_REQUIRE

	/* We require that ptr is not already in SmHeapTable */
	for (hi = SmHeapTable[i]; hi != NULL; hi = hi->hi_next)
	{
		if (hi->hi_ptr == ptr)
			sm_abort("sm_heap_register: ptr %p is already registered (%s:%d)",
				 ptr, hi->hi_tag, hi->hi_num);
	}
# endif /* SM_CHECK_REQUIRE */
	hi = (sm_heap_item_T *) malloc(sizeof(sm_heap_item_T));
	if (hi == NULL)
	{
# if MTA_USE_PTHREADS
		r = pthread_mutex_unlock(&SmHeapMutex);
		SM_ASSERT(r == 0);
# endif
		return false;
	}
	hi->hi_ptr = ptr;
	hi->hi_size = size;
	hi->hi_tag = tag;
	hi->hi_num = num;
	hi->hi_group = group;
	hi->hi_next = SmHeapTable[i];
	SmHeapTable[i] = hi;
# if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&SmHeapMutex);
	SM_ASSERT(r == 0);
# endif
	return true;
}

/*
**  SM_REALLOC -- wrapper for realloc(), debugging version.
**
**	Parameters:
**		ptr -- pointer to old memory area.
**		size -- size of requested memory.
**
**	Returns:
**		Pointer to new memory area, NULL on failure.
*/

void *
sm_realloc(void *ptr, size_t size)
{
# if MTA_USE_PTHREADS
	int r;
# endif
	void *newptr;
	sm_heap_item_T *hi, **hp;

	if (!HEAP_CHECK)
	{
		newptr = realloc(ptr, size);
		return newptr;
	}

	if (ptr == NULL)
		return sm_malloc_tagged(size, "realloc", 0, SmHeapGroup);

# if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&SmHeapMutex);
	SM_LOCK_OK(r);
	if (r != 0)
		return NULL;
# endif

	for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next)
	{
		if ((**hp).hi_ptr == ptr)
		{
#if 0
			if (sm_xtrap_check())
				goto errunl;
#endif
			hi = *hp;
#if 0
			if (sm_debug_active(&SmHeapLimit, 1)
			    && sm_debug_level(&SmHeapLimit)
			       < SmHeapTotal - hi->hi_size + size)
			{
				goto errunl;
			}
#endif /* 0 */
			newptr = realloc(ptr, size);
			if (newptr == NULL)
			{
				goto errunl;
			}
			SmHeapTotal = SmHeapTotal - hi->hi_size + size;
			if (SmHeapTotal > SmHeapMaxTotal)
				SmHeapMaxTotal = SmHeapTotal;
			*hp = hi->hi_next;
			hi->hi_ptr = newptr;
			hi->hi_size = size;
			hp = &SmHeapTable[ptrhash(newptr)];
			hi->hi_next = *hp;
			*hp = hi;
# if MTA_USE_PTHREADS
			r = pthread_mutex_unlock(&SmHeapMutex);
			SM_ASSERT(r == 0);
# endif
			return newptr;
		}
	}
	sm_abort("sm_realloc: bad argument (%p)", ptr);
	/* NOTREACHED */

  errunl:
# if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&SmHeapMutex);
	SM_ASSERT(r == 0);
# endif
	return NULL;	/* keep Irix compiler happy */
}

/*
**  SM_FREE_TAGGED -- wrapper around free(), debugging version.
**
**	Parameters:
**		ptr -- pointer to memory region.
**		tag -- tag for debugging.
**		num -- additional value for debugging.
**
**	Returns:
**		none.
*/

void
sm_free_tagged(void *ptr, char *tag, int num)
{
# if MTA_USE_PTHREADS
	int r;
# endif
	sm_heap_item_T **hp;

	if (ptr == NULL)
		return;
	if (!HEAP_CHECK)
	{
		free(ptr);
		return;
	}
# if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&SmHeapMutex);
	SM_ASSERT(r == 0);
	if (r != 0)
		return;
# endif /* MTA_USE_PTHREADS */
	for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next)
	{
		if ((**hp).hi_ptr == ptr)
		{
			sm_heap_item_T *hi = *hp;

			*hp = hi->hi_next;

			/*
			**  Fill the block with zeros before freeing.
			**  This is intended to catch problems with
			**  dangling pointers.  The block is filled with
			**  zeros, not with some non-zero value, because
			**  it is common practice in some C code to store
			**  a zero in a structure member before freeing the
			**  structure, as a defense against dangling pointers.
			*/

			(void) memset(ptr, 0, hi->hi_size);
			SmHeapTotal -= hi->hi_size;
			free(ptr);
			free(hi);
# if MTA_USE_PTHREADS
			r = pthread_mutex_unlock(&SmHeapMutex);
			SM_ASSERT(r == 0);
# endif
			return;
		}
	}
	sm_abort("sm_free: bad argument (%p) (%s:%d)", ptr, tag, num);
}

/*
**  SM_FREE_SIZE_TAGGED -- wrapper around free(), debugging version.
**
**	Parameters:
**		ptr -- pointer to memory region.
**		size -- size of released memory.
**		tag -- tag for debugging.
**		num -- additional value for debugging.
**
**	Returns:
**		none.
*/

void
sm_free_size_tagged(void *ptr, size_t size, char *tag, int num)
{
# if MTA_USE_PTHREADS
	int r;
# endif
	sm_heap_item_T **hp;

	if (ptr == NULL)
		return;
	if (!HEAP_CHECK)
	{
		sm_memzero(ptr, size);
		free(ptr);
		return;
	}
# if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&SmHeapMutex);
	SM_ASSERT(r == 0);
	if (r != 0)
		return;
# endif /* MTA_USE_PTHREADS */
	for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next)
	{
		if ((**hp).hi_ptr == ptr)
		{
			sm_heap_item_T *hi = *hp;

			*hp = hi->hi_next;

			/*
			**  Fill the block with zeros before freeing.
			**  This is intended to catch problems with
			**  dangling pointers.  The block is filled with
			**  zeros, not with some non-zero value, because
			**  it is common practice in some C code to store
			**  a zero in a structure member before freeing the
			**  structure, as a defense against dangling pointers.
			*/

			(void) memset(ptr, 0, hi->hi_size);
			SmHeapTotal -= hi->hi_size;
			sm_memzero(ptr, size);
			free(ptr);
			free(hi);
# if MTA_USE_PTHREADS
			r = pthread_mutex_unlock(&SmHeapMutex);
			SM_ASSERT(r == 0);
# endif
			return;
		}
	}
	sm_abort("sm_free: bad argument (%p) (%s:%d)", ptr, tag, num);
}

/*
**  SM_HEAP_CHECKPTR_TAGGED -- check whether ptr is a valid argument to sm_free
**
**	Parameters:
**		ptr -- pointer to memory region.
**		tag -- tag for debugging.
**		num -- additional value for debugging.
**
**	Returns:
**		none.
**
**	Side Effects:
**		aborts if check fails.
*/

void
sm_heap_checkptr_tagged(void *ptr, char *tag, int num)
{
# if MTA_USE_PTHREADS
	int r;
# endif
	sm_heap_item_T *hp;

	if (!HEAP_CHECK)
		return;
	if (ptr == NULL)
		return;
# if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&SmHeapMutex);
	SM_LOCK_OK(r);
	if (r != 0)
		return;
# endif /* MTA_USE_PTHREADS */
	for (hp = SmHeapTable[ptrhash(ptr)]; hp != NULL; hp = hp->hi_next)
	{
		if (hp->hi_ptr == ptr)
		{
# if MTA_USE_PTHREADS
			r = pthread_mutex_unlock(&SmHeapMutex);
			SM_ASSERT(r == 0);
# endif
			return;
		}
	}
	sm_abort("sm_heap_checkptr(%p): bad ptr (%s:%d)", ptr, tag, num);
}

/*
**  SM_HEAP_REPORT -- output "map" of used heap.
**
**	Parameters:
**		stream -- the file pointer to write to.
**		verbosity -- how much info?
**
**	Returns:
**		none.
*/

void
sm_heap_report(sm_file_T *stream, int verbosity)
{
	uint i;
# if MTA_USE_PTHREADS
	int r;
# endif
	ulong group0total, group1total, otherstotal, grandtotal, lin, l;

	if (!HEAP_CHECK || verbosity <= 0)
		return;
	group0total = group1total = otherstotal = grandtotal = lin = 0;
# if MTA_USE_PTHREADS

	/*
	**  NOTE: This can deadlock iff the caller doesn't have stream open
	**  because SM I/O will allocated a buffer for stream otherwise
	**  on the first call to sm_io_fprintf().
	**  Possible workaround: call sm_io_fprintf() before acquiring the lock.
	*/

	r = pthread_mutex_lock(&SmHeapMutex);
	SM_LOCK_OK(r);
	if (r != 0)
		return;
# endif /* MTA_USE_PTHREADS */
	for (i = 0; i < sizeof(SmHeapTable) / sizeof(SmHeapTable[0]); ++i)
	{
		sm_heap_item_T *hi = SmHeapTable[i];

		l = 0;
		while (hi != NULL)
		{
			if (verbosity > 2
			    || (verbosity > 1 && hi->hi_group != 0))
			{
				sm_io_fprintf(stream,
					"%4d %*lx %7lu bytes",
					hi->hi_group,
					(int) sizeof(void *) * 2,
					(long)hi->hi_ptr,
					(ulong)hi->hi_size);
				if (hi->hi_tag != NULL)
				{
					sm_io_fprintf(stream, "  %s",
						hi->hi_tag);
					if (hi->hi_num)
					{
						sm_io_fprintf(stream,
							":%d",
							hi->hi_num);
					}
				}
				sm_io_fprintf(stream, "\n");
			}
			switch (hi->hi_group)
			{
			  case 0:
				group0total += hi->hi_size;
				break;
			  case 1:
				group1total += hi->hi_size;
				break;
			  default:
				otherstotal += hi->hi_size;
				break;
			}
			grandtotal += hi->hi_size;
			hi = hi->hi_next;
			++l;
		}
		if (lin < l)
			lin = l;
	}
	sm_io_fprintf(stream,
		"heap max=%lu, total=%lu, "
		"group 0=%lu, group 1=%lu, others=%lu\nlinear_length=%lu\n",
		(ulong) SmHeapMaxTotal, grandtotal,
		group0total, group1total, otherstotal, lin);
	if (grandtotal != SmHeapTotal)
	{
		sm_io_fprintf(stream,
			"BUG => SmHeapTotal: got %lu, expected %lu\n",
			(ulong) SmHeapTotal, grandtotal);
	}
	sm_io_flush(stream);
# if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&SmHeapMutex);
	SM_ASSERT(r == 0);
# endif
}
#endif /* !SM_HEAP_CHECK */


syntax highlighted by Code2HTML, v. 0.9.1