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