/*
 * 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: mem.c,v 1.3 2006/10/05 04:27:37 ca Exp $")

/*
**  Debugging memory allocation package with memory context.
**
**  NOTES:
**  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.
**
**  This is a split of heap.c, it would be nice to merge these
**  together again.
*/

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

struct sm_mctx_S
{
	sm_magic_T	 sm_magic;
	size_t		 sm_mctx_cur;	/* current memory usage */
	size_t		 sm_mctx_max;	/* maximum memory usage */
	size_t		 sm_mctx_lim;	/* upper limit for memory usage */
	/* not yet checked */

	uint		 sm_mctx_flags;

	sm_mctx_oom_F	*sm_mctx_oom;	/* callback if out of memory */
	void		*sm_mctx_cbctx;	/* application context to pass to cb */
#if MTA_USE_PTHREADS
	pthread_mutex_t	 sm_mctx_mutex;
#endif
};

#define SM_MCTX_SET_FLAG(sm_mctx, fl)	(sm_mctx)->sm_mctx_flags |= (fl)
#define SM_MCTX_CLR_FLAG(sm_mctx, fl)	(sm_mctx)->sm_mctx_flags &= ~(fl)
#define SM_MCTX_IS_FLAG(sm_mctx, fl)	(((sm_mctx)->sm_mctx_flags & (fl)) != 0)

/* undef all macro versions of the "functions" so they can be specified here */
#if !SM_HEAP_CHECK
# undef sm_ctxmalloc
# undef sm_ctxzalloc
# undef sm_ctxrealloc
#endif /* !SM_HEAP_CHECK */
#undef sm_ctxalloc_tagged
#undef sm_ctxfree
#undef sm_ctxfree_tagged
#if SM_HEAP_CHECK
# undef sm_ctxheap_register
# undef sm_ctxheap_checkptr
# undef sm_ctxheap_report
sm_heap_item_T *SmMemTable[SM_HEAP_TABLE_SIZE];
#endif /* SM_HEAP_CHECK */

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

/*
**  SM_MCTX_NEW -- create new mctx
**
**	Parameters:
**		limit -- limit for memory usage (0: none).
**		oomcb -- callback for out of memory
**		cbctx -- context for callback
**		psm_mctx -- (pointer to) memory context (output)
**
**	Returns:
**		usual error code.
*/

sm_ret_T
sm_mctx_new(size_t limit, sm_mctx_oom_F oomcb, void *cbctx, sm_mctx_P *psm_mctx)
{
	sm_mctx_P sm_mctx;
#if MTA_USE_PTHREADS
	int r;
#endif

	SM_REQUIRE(psm_mctx != NULL);
	sm_mctx = (sm_mctx_P) malloc(sizeof(*sm_mctx));
	if (sm_mctx == NULL)
		return sm_err_temp(ENOMEM);
#if MTA_USE_PTHREADS
	r = pthread_mutex_init(&(sm_mctx->sm_mctx_mutex), SM_PTHREAD_MUTEXATTR);
	if (r != 0)
	{
		free(sm_mctx);
		return sm_err_temp(r);
	}
#endif
	sm_mctx->sm_mctx_cur = 0;
	sm_mctx->sm_mctx_max = 0;
	sm_mctx->sm_mctx_lim = limit;
	sm_mctx->sm_mctx_oom = oomcb;
	sm_mctx->sm_mctx_cbctx = cbctx;
	sm_mctx->sm_magic = SM_MCTX_MAGIC;
	*psm_mctx = sm_mctx;
	return SM_SUCCESS;
}

/*
**  SM_MCTX_FLAGS -- set flags for mctx
**
**	Parameters:
**		sm_mctx -- memory context
**		flags -- flags to set
**
**	Returns:
**		usual error code.
*/

sm_ret_T
sm_mctx_flags(sm_mctx_P sm_mctx, uint flags)
{
#if MTA_USE_PTHREADS
	int r;
#endif

	SM_IS_MCTX(sm_mctx);
#if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex));
	SM_ASSERT(r == 0);
#endif
	sm_mctx->sm_mctx_flags = flags;
#if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex));
	SM_ASSERT(r == 0);
#endif
	return SM_SUCCESS;
}

/*
**  SM_MCTX_DESTROY -- destroy new mctx
**
**	Parameters:
**		sm_mctx -- memory context to destroy
**
**	Returns:
**		usual error code.
*/

sm_ret_T
sm_mctx_destroy(sm_mctx_P sm_mctx)
{
	if (sm_mctx == NULL)
		return SM_SUCCESS;
	SM_IS_MCTX(sm_mctx);
#if MTA_USE_PTHREADS
	(void) pthread_mutex_destroy(&(sm_mctx->sm_mctx_mutex));
#endif
	sm_mctx->sm_magic = SM_MAGIC_NULL;
	free(sm_mctx);
	return SM_SUCCESS;
}

/*
**  SM_CTXMALLOC -- wrapper around malloc()
**
**	Parameters:
**		sm_mctx -- memory context
**		flags -- some flags
**		size -- size of requested memory.
**		tag -- tag for debugging.
**		num -- additional value for debugging.
**		group -- heap group for debugging.
**
**	Returns:
**		Pointer to memory region, NULL on error.
*/

void *
#if !SM_HEAP_CHECK
sm_ctxalloc(sm_mctx_P sm_mctx, uint flags, size_t size)
#else
sm_ctxalloc_tagged(sm_mctx_P sm_mctx, uint flags, size_t size, char *tag, int num, int group)
#endif
{
#if MTA_USE_PTHREADS
	int r;
#endif
	void *ptr;

	ptr = malloc(MALLOC_SIZE(size));
	if (sm_mctx == NULL)
		return ptr;
	if (ptr == NULL && sm_mctx->sm_mctx_oom != NULL)
	{
		sm_ret_T ret;

		ret = sm_mctx->sm_mctx_oom(size, sm_mctx->sm_mctx_cur,
				sm_mctx->sm_mctx_lim, sm_mctx->sm_mctx_cbctx);
		if (ret == SM_SUCCESS)
		{
			ptr = malloc(MALLOC_SIZE(size));
			if (ptr == NULL)
				return NULL;
		}
	}

	if (SM_IS_FLAG(flags, SM_FL_ZALLOC) && ptr != NULL)
		sm_memzero(ptr, size);

#if SM_HEAP_CHECK
	if (SM_MCTX_IS_FLAG(sm_mctx, SM_MCTX_FL_CHK))
	{
		if (ptr != NULL &&
		    !sm_ctxheap_register(sm_mctx, ptr, size, tag, num, group))
		{
			free(ptr);
			ptr = NULL;
			size = 0;
		}
	}
#endif

	if (size > 0)
	{
#if MTA_USE_PTHREADS
		r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex));
		SM_ASSERT(r == 0);
#endif
		sm_mctx->sm_mctx_cur += size;
		if (sm_mctx->sm_mctx_max < sm_mctx->sm_mctx_cur)
			sm_mctx->sm_mctx_max = sm_mctx->sm_mctx_cur;
#if MTA_USE_PTHREADS
		r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex));
		SM_ASSERT(r == 0);
#endif
	}

	return ptr;
}

/*
**  SM_CTXREALLOCSIZE -- wrapper for realloc()
**
**	Parameters:
**		sm_mctx -- memory context
**		ptr -- pointer to old memory area.
**		oldsize -- size of memory currently used.
**		newsize -- size of requested memory.
**
**	Returns:
**		Pointer to new memory area, NULL on failure.
*/

void *
sm_ctxreallocsize(sm_mctx_P sm_mctx, void *ptr, size_t oldsize, size_t newsize)
{
#if MTA_USE_PTHREADS
	int r;
#endif
	void *newptr;

	newptr = realloc(ptr, MALLOC_SIZE(newsize));
	if (sm_mctx == NULL)
		return newptr;
	if (newptr == NULL && sm_mctx->sm_mctx_oom != NULL)
	{
		sm_ret_T ret;

		ret = sm_mctx->sm_mctx_oom(newsize, sm_mctx->sm_mctx_cur,
				sm_mctx->sm_mctx_lim, sm_mctx->sm_mctx_cbctx);
		if (ret == SM_SUCCESS)
		{
			newptr = realloc(ptr, MALLOC_SIZE(newsize));
			if (newptr == NULL)
				return NULL;
		}
	}
#if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex));
	SM_ASSERT(r == 0);
#endif
	if (sm_mctx->sm_mctx_cur <= oldsize)
		sm_mctx->sm_mctx_cur = 0;
	else
		sm_mctx->sm_mctx_cur -= oldsize;
	sm_mctx->sm_mctx_cur += newsize;
	if (sm_mctx->sm_mctx_max < sm_mctx->sm_mctx_cur)
		sm_mctx->sm_mctx_max = sm_mctx->sm_mctx_cur;
#if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex));
	SM_ASSERT(r == 0);
#endif
	return ptr;
}

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

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

	newptr = realloc(ptr, MALLOC_SIZE(size));
	if (sm_mctx == NULL)
		return newptr;
	if (newptr == NULL && sm_mctx->sm_mctx_oom != NULL)
	{
		sm_ret_T ret;

		ret = sm_mctx->sm_mctx_oom(size, sm_mctx->sm_mctx_cur,
				sm_mctx->sm_mctx_lim, sm_mctx->sm_mctx_cbctx);
		if (ret == SM_SUCCESS)
		{
			newptr = realloc(ptr, MALLOC_SIZE(size));
			if (newptr == NULL)
				return NULL;
		}
	}
	/* keeping track of size requires old size, see sm_ctxreallocsize */

#if SM_HEAP_CHECK
	if (SM_MCTX_IS_FLAG(sm_mctx, SM_MCTX_FL_CHK))
	{
# if MTA_USE_PTHREADS
		r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex));
		if (r != 0)
			return NULL;
# endif

		for (hp = &SmMemTable[ptrhash(ptr)]; *hp != NULL;
		     hp = &(**hp).hi_next)
		{
			if ((**hp).hi_ptr == ptr)
			{
				hi = *hp;
				newptr = realloc(ptr, size);
				if (newptr == NULL)
				{
# if MTA_USE_PTHREADS
					r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex));
					SM_ASSERT(r == 0);
# endif
					return NULL;
				}

				if (sm_mctx->sm_mctx_cur <= hi->hi_size)
					sm_mctx->sm_mctx_cur = 0;
				else
					sm_mctx->sm_mctx_cur -= hi->hi_size;
				sm_mctx->sm_mctx_cur += size;
				if (sm_mctx->sm_mctx_max < sm_mctx->sm_mctx_cur)
					sm_mctx->sm_mctx_max =
						sm_mctx->sm_mctx_cur;

				*hp = hi->hi_next;
				hi->hi_ptr = newptr;
				hi->hi_size = size;
				hp = &SmMemTable[ptrhash(newptr)];
				hi->hi_next = *hp;
				*hp = hi;
# if MTA_USE_PTHREADS
				r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex));
				SM_ASSERT(r == 0);
# endif
				return newptr;
			}
		}
		sm_abort("sm_realloc: bad argument (%p)", ptr);
		/* NOTREACHED */
		return NULL;	/* keep Irix compiler happy */
	}
#endif

	return newptr;
}

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

void
#if !SM_HEAP_CHECK
sm_ctxfree(sm_mctx_P sm_mctx, void *ptr)
#else
sm_ctxfree_tagged(sm_mctx_P sm_mctx, void *ptr, char *tag, int num)
#endif
{
#if SM_HEAP_CHECK
# if MTA_USE_PTHREADS
	int r;
# endif
	sm_heap_item_T **hp;
#endif

	if (ptr == NULL)
		return;
#if !SM_HEAP_CHECK
	free(ptr);
	return;
#else
	if (sm_mctx == NULL || !SM_MCTX_IS_FLAG(sm_mctx, SM_MCTX_FL_CHK))
	{
		free(ptr);
		return;
	}
# if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex));
	SM_ASSERT(r == 0);
	if (r != 0)
		return;
# endif /* MTA_USE_PTHREADS */
	for (hp = &SmMemTable[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);
			if (sm_mctx->sm_mctx_cur <= hi->hi_size)
				sm_mctx->sm_mctx_cur = 0;
			else
				sm_mctx->sm_mctx_cur -= hi->hi_size;
			free(ptr);
			free(hi);
# if MTA_USE_PTHREADS
			r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex));
			SM_ASSERT(r == 0);
# endif
			return;
		}
	}
	sm_abort("sm_free: bad argument (%p) (%s:%d)", ptr, tag, num);
#endif
}

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

void
#if !SM_HEAP_CHECK
sm_ctxfree_size(sm_mctx_P sm_mctx, void *ptr, size_t size)
#else
sm_ctxfree_size_tagged(sm_mctx_P sm_mctx, void *ptr, size_t size, char *tag, int num)
#endif
{
#if SM_HEAP_CHECK
	sm_heap_item_T **hp;
#endif
#if MTA_USE_PTHREADS
	int r;
#endif

	if (ptr == NULL)
		return;
#if SM_ZERO_FREE
	/* or other pattern? */
	sm_memzero(ptr, size);
#endif
	if (sm_mctx != NULL)
	{
		SM_IS_MCTX(sm_mctx);
#if MTA_USE_PTHREADS
		r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex));
		SM_ASSERT(r == 0);
#endif
		if (sm_mctx->sm_mctx_cur <= size)
			sm_mctx->sm_mctx_cur = 0;
		else
			sm_mctx->sm_mctx_cur -= size;
#if MTA_USE_PTHREADS
		r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex));
		SM_ASSERT(r == 0);
#endif
	}
#if !SM_HEAP_CHECK
	free(ptr);
	return;
#else
	if (sm_mctx == NULL || !SM_MCTX_IS_FLAG(sm_mctx, SM_MCTX_FL_CHK))
	{
		sm_memzero(ptr, size);
		free(ptr);
		return;
	}
# if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex));
	SM_ASSERT(r == 0);
	if (r != 0)
		return;
# endif /* MTA_USE_PTHREADS */
	for (hp = &SmMemTable[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);

			SM_ASSERT(size == hi->hi_size);

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

#if SM_HEAP_CHECK

/*
**  SM_CTXHEAP_REGISTER -- register a pointer into the heap for debugging.
**
**	Parameters:
**		sm_mctx -- memory context
**		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_ctxheap_register(sm_mctx_P sm_mctx, 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;

	SM_IS_MCTX(sm_mctx);
	if (!SM_MCTX_IS_FLAG(sm_mctx, SM_MCTX_FL_CHK))
		return true;
	SM_REQUIRE(ptr != NULL);
	i = ptrhash(ptr);
# if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex));
	if (r != 0)
		return false;
# endif
# if SM_CHECK_REQUIRE

	/* We require that ptr is not already in SmMemTable */
	for (hi = SmMemTable[i]; hi != NULL; hi = hi->hi_next)
	{
		if (hi->hi_ptr == ptr)
			sm_abort("sm_ctxheap_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(&(sm_mctx->sm_mctx_mutex));
		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 = SmMemTable[i];
	SmMemTable[i] = hi;
# if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex));
	SM_ASSERT(r == 0);
# endif
	return true;
}


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

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

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

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

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

	SM_IS_MCTX(sm_mctx);
	if (!SM_MCTX_IS_FLAG(sm_mctx, SM_MCTX_FL_CHK) || 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(&(sm_mctx->sm_mctx_mutex));
	if (r != 0)
		return;
# endif /* MTA_USE_PTHREADS */
	for (i = 0; i < sizeof(SmMemTable) / sizeof(SmMemTable[0]); ++i)
	{
		sm_heap_item_T *hi = SmMemTable[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) sm_mctx->sm_mctx_max, grandtotal,
		group0total, group1total, otherstotal, lin);
	if (grandtotal != sm_mctx->sm_mctx_cur)
	{
		sm_io_fprintf(stream,
			"BUG => SmMemTotal: got %lu, expected %lu\n",
			(ulong) sm_mctx->sm_mctx_cur, grandtotal);
	}
	sm_io_flush(stream);
# if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex));
	SM_ASSERT(r == 0);
# endif
}
#endif /* !SM_HEAP_CHECK */


syntax highlighted by Code2HTML, v. 0.9.1