/* * 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 #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; }