diff options
Diffstat (limited to '')
-rw-r--r-- | lib/isc/mem.c | 3015 |
1 files changed, 3015 insertions, 0 deletions
diff --git a/lib/isc/mem.c b/lib/isc/mem.c new file mode 100644 index 0000000..3fec795 --- /dev/null +++ b/lib/isc/mem.c @@ -0,0 +1,3015 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include <config.h> + +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <limits.h> + +#include <isc/bind9.h> +#include <isc/json.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/msgs.h> +#include <isc/once.h> +#include <isc/ondestroy.h> +#include <isc/string.h> +#include <isc/mutex.h> +#include <isc/print.h> +#include <isc/util.h> +#include <isc/xml.h> + +#define MCTXLOCK(m, l) if (((m)->flags & ISC_MEMFLAG_NOLOCK) == 0) LOCK(l) +#define MCTXUNLOCK(m, l) if (((m)->flags & ISC_MEMFLAG_NOLOCK) == 0) UNLOCK(l) + +#ifndef ISC_MEM_DEBUGGING +#define ISC_MEM_DEBUGGING 0 +#endif +LIBISC_EXTERNAL_DATA unsigned int isc_mem_debugging = ISC_MEM_DEBUGGING; +LIBISC_EXTERNAL_DATA unsigned int isc_mem_defaultflags = ISC_MEMFLAG_DEFAULT; + +/* + * Constants. + */ + +#define DEF_MAX_SIZE 1100 +#define DEF_MEM_TARGET 4096 +#define ALIGNMENT_SIZE 8U /*%< must be a power of 2 */ +#define NUM_BASIC_BLOCKS 64 /*%< must be > 1 */ +#define TABLE_INCREMENT 1024 +#define DEBUGLIST_COUNT 1024 + +/* + * Types. + */ +typedef struct isc__mem isc__mem_t; +typedef struct isc__mempool isc__mempool_t; + +#if ISC_MEM_TRACKLINES +typedef struct debuglink debuglink_t; +struct debuglink { + ISC_LINK(debuglink_t) link; + const void *ptr[DEBUGLIST_COUNT]; + size_t size[DEBUGLIST_COUNT]; + const char *file[DEBUGLIST_COUNT]; + unsigned int line[DEBUGLIST_COUNT]; + unsigned int count; +}; + +#define FLARG_PASS , file, line +#define FLARG , const char *file, unsigned int line +#else +#define FLARG_PASS +#define FLARG +#endif + +typedef struct element element; +struct element { + element * next; +}; + +typedef struct { + /*! + * This structure must be ALIGNMENT_SIZE bytes. + */ + union { + size_t size; + isc__mem_t *ctx; + char bytes[ALIGNMENT_SIZE]; + } u; +} size_info; + +struct stats { + unsigned long gets; + unsigned long totalgets; + unsigned long blocks; + unsigned long freefrags; +}; + +#define MEM_MAGIC ISC_MAGIC('M', 'e', 'm', 'C') +#define VALID_CONTEXT(c) ISC_MAGIC_VALID(c, MEM_MAGIC) + +#if ISC_MEM_TRACKLINES +typedef ISC_LIST(debuglink_t) debuglist_t; +#endif + +/* List of all active memory contexts. */ + +static ISC_LIST(isc__mem_t) contexts; + +static isc_once_t once = ISC_ONCE_INIT; +static isc_mutex_t contextslock; +static isc_mutex_t createlock; + +/*% + * Total size of lost memory due to a bug of external library. + * Locked by the global lock. + */ +static uint64_t totallost; + +struct isc__mem { + isc_mem_t common; + isc_ondestroy_t ondestroy; + unsigned int flags; + isc_mutex_t lock; + isc_memalloc_t memalloc; + isc_memfree_t memfree; + void * arg; + size_t max_size; + bool checkfree; + struct stats * stats; + unsigned int references; + char name[16]; + void * tag; + size_t quota; + size_t total; + size_t inuse; + size_t maxinuse; + size_t hi_water; + size_t lo_water; + bool hi_called; + bool is_overmem; + isc_mem_water_t water; + void * water_arg; + ISC_LIST(isc__mempool_t) pools; + unsigned int poolcnt; + + /* ISC_MEMFLAG_INTERNAL */ + size_t mem_target; + element ** freelists; + element * basic_blocks; + unsigned char ** basic_table; + unsigned int basic_table_count; + unsigned int basic_table_size; + unsigned char * lowest; + unsigned char * highest; + +#if ISC_MEM_TRACKLINES + debuglist_t * debuglist; + unsigned int debuglistcnt; +#endif + + unsigned int memalloc_failures; + ISC_LINK(isc__mem_t) link; +}; + +#define MEMPOOL_MAGIC ISC_MAGIC('M', 'E', 'M', 'p') +#define VALID_MEMPOOL(c) ISC_MAGIC_VALID(c, MEMPOOL_MAGIC) + +struct isc__mempool { + /* always unlocked */ + isc_mempool_t common; /*%< common header of mempool's */ + isc_mutex_t *lock; /*%< optional lock */ + isc__mem_t *mctx; /*%< our memory context */ + /*%< locked via the memory context's lock */ + ISC_LINK(isc__mempool_t) link; /*%< next pool in this mem context */ + /*%< optionally locked from here down */ + element *items; /*%< low water item list */ + size_t size; /*%< size of each item on this pool */ + unsigned int maxalloc; /*%< max number of items allowed */ + unsigned int allocated; /*%< # of items currently given out */ + unsigned int freecount; /*%< # of items on reserved list */ + unsigned int freemax; /*%< # of items allowed on free list */ + unsigned int fillcount; /*%< # of items to fetch on each fill */ + /*%< Stats only. */ + unsigned int gets; /*%< # of requests to this pool */ + /*%< Debugging only. */ +#if ISC_MEMPOOL_NAMES + char name[16]; /*%< printed name in stats reports */ +#endif +}; + +/* + * Private Inline-able. + */ + +#if ! ISC_MEM_TRACKLINES +#define ADD_TRACE(a, b, c, d, e) +#define DELETE_TRACE(a, b, c, d, e) +#define ISC_MEMFUNC_SCOPE +#else +#define TRACE_OR_RECORD (ISC_MEM_DEBUGTRACE|ISC_MEM_DEBUGRECORD) +#define ADD_TRACE(a, b, c, d, e) \ + do { \ + if ((isc_mem_debugging & TRACE_OR_RECORD) != 0 && \ + b != NULL) \ + add_trace_entry(a, b, c, d, e); \ + } while (0) +#define DELETE_TRACE(a, b, c, d, e) \ + do { \ + if ((isc_mem_debugging & TRACE_OR_RECORD) != 0 && \ + b != NULL) \ + delete_trace_entry(a, b, c, d, e); \ + } while(0) + +static void +print_active(isc__mem_t *ctx, FILE *out); + +#endif /* ISC_MEM_TRACKLINES */ + +/*% + * The following are intended for internal use (indicated by "isc__" + * prefix) but are not declared as static, allowing direct access + * from unit tests, etc. + */ + +isc_result_t +isc__mem_create2(size_t init_max_size, size_t target_size, + isc_mem_t **ctxp, unsigned int flags); +void +isc__mem_attach(isc_mem_t *source, isc_mem_t **targetp); +void +isc__mem_detach(isc_mem_t **ctxp); +void +isc___mem_putanddetach(isc_mem_t **ctxp, void *ptr, size_t size FLARG); +void +isc__mem_destroy(isc_mem_t **ctxp); +isc_result_t +isc__mem_ondestroy(isc_mem_t *ctx, isc_task_t *task, isc_event_t **event); +void * +isc___mem_get(isc_mem_t *ctx, size_t size FLARG); +void +isc___mem_put(isc_mem_t *ctx, void *ptr, size_t size FLARG); +void +isc__mem_stats(isc_mem_t *ctx, FILE *out); +void * +isc___mem_allocate(isc_mem_t *ctx, size_t size FLARG); +void * +isc___mem_reallocate(isc_mem_t *ctx, void *ptr, size_t size FLARG); +void +isc___mem_free(isc_mem_t *ctx, void *ptr FLARG); +char * +isc___mem_strdup(isc_mem_t *mctx, const char *s FLARG); +void +isc__mem_setdestroycheck(isc_mem_t *ctx, bool flag); +void +isc__mem_setquota(isc_mem_t *ctx, size_t quota); +size_t +isc__mem_getquota(isc_mem_t *ctx); +size_t +isc__mem_inuse(isc_mem_t *ctx); +size_t +isc__mem_maxinuse(isc_mem_t *ctx); +size_t +isc__mem_total(isc_mem_t *ctx); +bool +isc__mem_isovermem(isc_mem_t *ctx); +void +isc__mem_setwater(isc_mem_t *ctx, isc_mem_water_t water, void *water_arg, + size_t hiwater, size_t lowater); +void +isc__mem_waterack(isc_mem_t *ctx0, int flag); +void +isc__mem_setname(isc_mem_t *ctx, const char *name, void *tag); +const char * +isc__mem_getname(isc_mem_t *ctx); +void * +isc__mem_gettag(isc_mem_t *ctx); +isc_result_t +isc__mempool_create(isc_mem_t *mctx, size_t size, isc_mempool_t **mpctxp); +void +isc__mempool_setname(isc_mempool_t *mpctx, const char *name); +void +isc__mempool_destroy(isc_mempool_t **mpctxp); +void +isc__mempool_associatelock(isc_mempool_t *mpctx, isc_mutex_t *lock); +void * +isc___mempool_get(isc_mempool_t *mpctx FLARG); +void +isc___mempool_put(isc_mempool_t *mpctx, void *mem FLARG); +void +isc__mempool_setfreemax(isc_mempool_t *mpctx, unsigned int limit); +unsigned int +isc__mempool_getfreemax(isc_mempool_t *mpctx); +unsigned int +isc__mempool_getfreecount(isc_mempool_t *mpctx); +void +isc__mempool_setmaxalloc(isc_mempool_t *mpctx, unsigned int limit); +unsigned int +isc__mempool_getmaxalloc(isc_mempool_t *mpctx); +unsigned int +isc__mempool_getallocated(isc_mempool_t *mpctx); +void +isc__mempool_setfillcount(isc_mempool_t *mpctx, unsigned int limit); +unsigned int +isc__mempool_getfillcount(isc_mempool_t *mpctx); +void +isc__mem_printactive(isc_mem_t *ctx0, FILE *file); +void +isc__mem_printallactive(FILE *file); +unsigned int +isc__mem_references(isc_mem_t *ctx0); + +static struct isc__memmethods { + isc_memmethods_t methods; + + /*% + * The following are defined just for avoiding unused static functions. + */ + void *createx, *create, *create2, *ondestroy, *stats, + *setquota, *getquota, *setname, *getname, *gettag; +} memmethods = { + { + isc__mem_attach, + isc__mem_detach, + isc__mem_destroy, + isc___mem_get, + isc___mem_put, + isc___mem_putanddetach, + isc___mem_allocate, + isc___mem_reallocate, + isc___mem_strdup, + isc___mem_free, + isc__mem_setdestroycheck, + isc__mem_setwater, + isc__mem_waterack, + isc__mem_inuse, + isc__mem_maxinuse, + isc__mem_total, + isc__mem_isovermem, + isc__mempool_create + }, + (void *)isc_mem_createx, + (void *)isc_mem_create, + (void *)isc_mem_create2, + (void *)isc_mem_ondestroy, + (void *)isc_mem_stats, + (void *)isc_mem_setquota, + (void *)isc_mem_getquota, + (void *)isc_mem_setname, + (void *)isc_mem_getname, + (void *)isc_mem_gettag +}; + +static struct isc__mempoolmethods { + isc_mempoolmethods_t methods; + + /*% + * The following are defined just for avoiding unused static functions. + */ + void *getfreemax, *getfreecount, *getmaxalloc, *getfillcount; +} mempoolmethods = { + { + isc__mempool_destroy, + isc___mempool_get, + isc___mempool_put, + isc__mempool_getallocated, + isc__mempool_setmaxalloc, + isc__mempool_setfreemax, + isc__mempool_setname, + isc__mempool_associatelock, + isc__mempool_setfillcount + }, + (void *)isc_mempool_getfreemax, + (void *)isc_mempool_getfreecount, + (void *)isc_mempool_getmaxalloc, + (void *)isc_mempool_getfillcount +}; + +#if ISC_MEM_TRACKLINES +/*! + * mctx must be locked. + */ +static inline void +add_trace_entry(isc__mem_t *mctx, const void *ptr, size_t size FLARG) { + debuglink_t *dl; + unsigned int i; + size_t mysize = size; + + if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) + fprintf(stderr, isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, + ISC_MSG_ADDTRACE, + "add %p size %u " + "file %s line %u mctx %p\n"), + ptr, size, file, line, mctx); + + if (mctx->debuglist == NULL) + return; + + if (mysize > mctx->max_size) + mysize = mctx->max_size; + + dl = ISC_LIST_HEAD(mctx->debuglist[mysize]); + while (dl != NULL) { + if (dl->count == DEBUGLIST_COUNT) + goto next; + for (i = 0; i < DEBUGLIST_COUNT; i++) { + if (dl->ptr[i] == NULL) { + dl->ptr[i] = ptr; + dl->size[i] = size; + dl->file[i] = file; + dl->line[i] = line; + dl->count++; + return; + } + } + next: + dl = ISC_LIST_NEXT(dl, link); + } + + dl = malloc(sizeof(debuglink_t)); + INSIST(dl != NULL); + + ISC_LINK_INIT(dl, link); + for (i = 1; i < DEBUGLIST_COUNT; i++) { + dl->ptr[i] = NULL; + dl->size[i] = 0; + dl->file[i] = NULL; + dl->line[i] = 0; + } + + dl->ptr[0] = ptr; + dl->size[0] = size; + dl->file[0] = file; + dl->line[0] = line; + dl->count = 1; + + ISC_LIST_PREPEND(mctx->debuglist[mysize], dl, link); + mctx->debuglistcnt++; +} + +static inline void +delete_trace_entry(isc__mem_t *mctx, const void *ptr, size_t size, + const char *file, unsigned int line) +{ + debuglink_t *dl; + unsigned int i; + + if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) + fprintf(stderr, isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, + ISC_MSG_DELTRACE, + "del %p size %u " + "file %s line %u mctx %p\n"), + ptr, size, file, line, mctx); + + if (mctx->debuglist == NULL) + return; + + if (size > mctx->max_size) + size = mctx->max_size; + + dl = ISC_LIST_HEAD(mctx->debuglist[size]); + while (dl != NULL) { + for (i = 0; i < DEBUGLIST_COUNT; i++) { + if (dl->ptr[i] == ptr) { + dl->ptr[i] = NULL; + dl->size[i] = 0; + dl->file[i] = NULL; + dl->line[i] = 0; + + INSIST(dl->count > 0); + dl->count--; + if (dl->count == 0) { + ISC_LIST_UNLINK(mctx->debuglist[size], + dl, link); + free(dl); + } + return; + } + } + dl = ISC_LIST_NEXT(dl, link); + } + + /* + * If we get here, we didn't find the item on the list. We're + * screwed. + */ + INSIST(dl != NULL); +} +#endif /* ISC_MEM_TRACKLINES */ + +static inline size_t +rmsize(size_t size) { + /* + * round down to ALIGNMENT_SIZE + */ + return (size & (~(ALIGNMENT_SIZE - 1))); +} + +static inline size_t +quantize(size_t size) { + /*! + * Round up the result in order to get a size big + * enough to satisfy the request and be aligned on ALIGNMENT_SIZE + * byte boundaries. + */ + + if (size == 0U) + return (ALIGNMENT_SIZE); + return ((size + ALIGNMENT_SIZE - 1) & (~(ALIGNMENT_SIZE - 1))); +} + +static inline bool +more_basic_blocks(isc__mem_t *ctx) { + void *tmp; + unsigned char *curr, *next; + unsigned char *first, *last; + unsigned char **table; + unsigned int table_size; + size_t increment; + int i; + + /* Require: we hold the context lock. */ + + /* + * Did we hit the quota for this context? + */ + increment = NUM_BASIC_BLOCKS * ctx->mem_target; + if (ctx->quota != 0U && ctx->total + increment > ctx->quota) + return (false); + + INSIST(ctx->basic_table_count <= ctx->basic_table_size); + if (ctx->basic_table_count == ctx->basic_table_size) { + table_size = ctx->basic_table_size + TABLE_INCREMENT; + table = (ctx->memalloc)(ctx->arg, + table_size * sizeof(unsigned char *)); + if (table == NULL) { + ctx->memalloc_failures++; + return (false); + } + if (ctx->basic_table_size != 0) { + memmove(table, ctx->basic_table, + ctx->basic_table_size * + sizeof(unsigned char *)); + (ctx->memfree)(ctx->arg, ctx->basic_table); + } + ctx->basic_table = table; + ctx->basic_table_size = table_size; + } + + tmp = (ctx->memalloc)(ctx->arg, NUM_BASIC_BLOCKS * ctx->mem_target); + if (tmp == NULL) { + ctx->memalloc_failures++; + return (false); + } + ctx->total += increment; + ctx->basic_table[ctx->basic_table_count] = tmp; + ctx->basic_table_count++; + + curr = tmp; + next = curr + ctx->mem_target; + for (i = 0; i < (NUM_BASIC_BLOCKS - 1); i++) { + ((element *)curr)->next = (element *)next; + curr = next; + next += ctx->mem_target; + } + /* + * curr is now pointing at the last block in the + * array. + */ + ((element *)curr)->next = NULL; + first = tmp; + last = first + NUM_BASIC_BLOCKS * ctx->mem_target - 1; + if (first < ctx->lowest || ctx->lowest == NULL) + ctx->lowest = first; + if (last > ctx->highest) + ctx->highest = last; + ctx->basic_blocks = tmp; + + return (true); +} + +static inline bool +more_frags(isc__mem_t *ctx, size_t new_size) { + int i, frags; + size_t total_size; + void *tmp; + unsigned char *curr, *next; + + /*! + * Try to get more fragments by chopping up a basic block. + */ + + if (ctx->basic_blocks == NULL) { + if (!more_basic_blocks(ctx)) { + /* + * We can't get more memory from the OS, or we've + * hit the quota for this context. + */ + /* + * XXXRTH "At quota" notification here. + */ + return (false); + } + } + + total_size = ctx->mem_target; + tmp = ctx->basic_blocks; + ctx->basic_blocks = ctx->basic_blocks->next; + frags = (int)(total_size / new_size); + ctx->stats[new_size].blocks++; + ctx->stats[new_size].freefrags += frags; + /* + * Set up a linked-list of blocks of size + * "new_size". + */ + curr = tmp; + next = curr + new_size; + total_size -= new_size; + for (i = 0; i < (frags - 1); i++) { + ((element *)curr)->next = (element *)next; + curr = next; + next += new_size; + total_size -= new_size; + } + /* + * Add the remaining fragment of the basic block to a free list. + */ + total_size = rmsize(total_size); + if (total_size > 0U) { + ((element *)next)->next = ctx->freelists[total_size]; + ctx->freelists[total_size] = (element *)next; + ctx->stats[total_size].freefrags++; + } + /* + * curr is now pointing at the last block in the + * array. + */ + ((element *)curr)->next = NULL; + ctx->freelists[new_size] = tmp; + + return (true); +} + +static inline void * +mem_getunlocked(isc__mem_t *ctx, size_t size) { + size_t new_size = quantize(size); + void *ret; + + if (new_size >= ctx->max_size) { + /* + * memget() was called on something beyond our upper limit. + */ + if (ctx->quota != 0U && ctx->total + size > ctx->quota) { + ret = NULL; + goto done; + } + ret = (ctx->memalloc)(ctx->arg, size); + if (ret == NULL) { + ctx->memalloc_failures++; + goto done; + } + ctx->total += size; + ctx->inuse += size; + ctx->stats[ctx->max_size].gets++; + ctx->stats[ctx->max_size].totalgets++; + /* + * If we don't set new_size to size, then the + * ISC_MEM_FILL code might write over bytes we + * don't own. + */ + new_size = size; + goto done; + } + /* + * If there are no blocks in the free list for this size, get a chunk + * of memory and then break it up into "new_size"-sized blocks, adding + * them to the free list. + */ + if (ctx->freelists[new_size] == NULL && !more_frags(ctx, new_size)) + return (NULL); + + /* + * The free list uses the "rounded-up" size "new_size". + */ + + ret = ctx->freelists[new_size]; + ctx->freelists[new_size] = ctx->freelists[new_size]->next; + + + /* + * The stats[] uses the _actual_ "size" requested by the + * caller, with the caveat (in the code above) that "size" >= the + * max. size (max_size) ends up getting recorded as a call to + * max_size. + */ + ctx->stats[size].gets++; + ctx->stats[size].totalgets++; + ctx->stats[new_size].freefrags--; + ctx->inuse += new_size; + + done: + +#if ISC_MEM_FILL + if (ret != NULL) + memset(ret, 0xbe, new_size); /* Mnemonic for "beef". */ +#endif + + return (ret); +} + +#if ISC_MEM_FILL && ISC_MEM_CHECKOVERRUN +static inline void +check_overrun(void *mem, size_t size, size_t new_size) { + unsigned char *cp; + + cp = (unsigned char *)mem; + cp += size; + while (size < new_size) { + INSIST(*cp == 0xbe); + cp++; + size++; + } +} +#endif + +/* coverity[+free : arg-1] */ +static inline void +mem_putunlocked(isc__mem_t *ctx, void *mem, size_t size) { + size_t new_size = quantize(size); + + if (new_size >= ctx->max_size) { + /* + * memput() called on something beyond our upper limit. + */ +#if ISC_MEM_FILL + memset(mem, 0xde, size); /* Mnemonic for "dead". */ +#endif + (ctx->memfree)(ctx->arg, mem); + INSIST(ctx->stats[ctx->max_size].gets != 0U); + ctx->stats[ctx->max_size].gets--; + INSIST(size <= ctx->inuse); + ctx->inuse -= size; + return; + } + +#if ISC_MEM_FILL +#if ISC_MEM_CHECKOVERRUN + check_overrun(mem, size, new_size); +#endif + memset(mem, 0xde, new_size); /* Mnemonic for "dead". */ +#endif + + /* + * The free list uses the "rounded-up" size "new_size". + */ + ((element *)mem)->next = ctx->freelists[new_size]; + ctx->freelists[new_size] = (element *)mem; + + /* + * The stats[] uses the _actual_ "size" requested by the + * caller, with the caveat (in the code above) that "size" >= the + * max. size (max_size) ends up getting recorded as a call to + * max_size. + */ + INSIST(ctx->stats[size].gets != 0U); + ctx->stats[size].gets--; + ctx->stats[new_size].freefrags++; + ctx->inuse -= new_size; +} + +/*! + * Perform a malloc, doing memory filling and overrun detection as necessary. + */ +static inline void * +mem_get(isc__mem_t *ctx, size_t size) { + char *ret; + +#if ISC_MEM_CHECKOVERRUN + size += 1; +#endif + + ret = (ctx->memalloc)(ctx->arg, size); + if (ret == NULL) + ctx->memalloc_failures++; + +#if ISC_MEM_FILL + if (ret != NULL) + memset(ret, 0xbe, size); /* Mnemonic for "beef". */ +#else +# if ISC_MEM_CHECKOVERRUN + if (ret != NULL) + ret[size-1] = 0xbe; +# endif +#endif + + return (ret); +} + +/*! + * Perform a free, doing memory filling and overrun detection as necessary. + */ +/* coverity[+free : arg-1] */ +static inline void +mem_put(isc__mem_t *ctx, void *mem, size_t size) { +#if ISC_MEM_CHECKOVERRUN + INSIST(((unsigned char *)mem)[size] == 0xbe); +#endif +#if ISC_MEM_FILL + memset(mem, 0xde, size); /* Mnemonic for "dead". */ +#else + UNUSED(size); +#endif + (ctx->memfree)(ctx->arg, mem); +} + +/*! + * Update internal counters after a memory get. + */ +static inline void +mem_getstats(isc__mem_t *ctx, size_t size) { + ctx->total += size; + ctx->inuse += size; + + if (size > ctx->max_size) { + ctx->stats[ctx->max_size].gets++; + ctx->stats[ctx->max_size].totalgets++; + } else { + ctx->stats[size].gets++; + ctx->stats[size].totalgets++; + } +} + +/*! + * Update internal counters after a memory put. + */ +static inline void +mem_putstats(isc__mem_t *ctx, void *ptr, size_t size) { + UNUSED(ptr); + + INSIST(ctx->inuse >= size); + ctx->inuse -= size; + + if (size > ctx->max_size) { + INSIST(ctx->stats[ctx->max_size].gets > 0U); + ctx->stats[ctx->max_size].gets--; + } else { + INSIST(ctx->stats[size].gets > 0U); + ctx->stats[size].gets--; + } +} + +/* + * Private. + */ + +static void * +default_memalloc(void *arg, size_t size) { + UNUSED(arg); + if (size == 0U) + size = 1; + return (malloc(size)); +} + +static void +default_memfree(void *arg, void *ptr) { + UNUSED(arg); + free(ptr); +} + +static void +initialize_action(void) { + RUNTIME_CHECK(isc_mutex_init(&createlock) == ISC_R_SUCCESS); + RUNTIME_CHECK(isc_mutex_init(&contextslock) == ISC_R_SUCCESS); + ISC_LIST_INIT(contexts); + totallost = 0; +} + +/* + * Public. + */ + +isc_result_t +isc_mem_createx(size_t init_max_size, size_t target_size, + isc_memalloc_t memalloc, isc_memfree_t memfree, void *arg, + isc_mem_t **ctxp) +{ + return (isc_mem_createx2(init_max_size, target_size, memalloc, memfree, + arg, ctxp, isc_mem_defaultflags)); + +} + +isc_result_t +isc_mem_createx2(size_t init_max_size, size_t target_size, + isc_memalloc_t memalloc, isc_memfree_t memfree, void *arg, + isc_mem_t **ctxp, unsigned int flags) +{ + isc__mem_t *ctx; + isc_result_t result; + + REQUIRE(ctxp != NULL && *ctxp == NULL); + REQUIRE(memalloc != NULL); + REQUIRE(memfree != NULL); + + INSIST((ALIGNMENT_SIZE & (ALIGNMENT_SIZE - 1)) == 0); + + RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); + + ctx = (memalloc)(arg, sizeof(*ctx)); + if (ctx == NULL) + return (ISC_R_NOMEMORY); + + if ((flags & ISC_MEMFLAG_NOLOCK) == 0) { + result = isc_mutex_init(&ctx->lock); + if (result != ISC_R_SUCCESS) { + (memfree)(arg, ctx); + return (result); + } + } + + if (init_max_size == 0U) + ctx->max_size = DEF_MAX_SIZE; + else + ctx->max_size = init_max_size; + ctx->flags = flags; + ctx->references = 1; + memset(ctx->name, 0, sizeof(ctx->name)); + ctx->tag = NULL; + ctx->quota = 0; + ctx->total = 0; + ctx->inuse = 0; + ctx->maxinuse = 0; + ctx->hi_water = 0; + ctx->lo_water = 0; + ctx->hi_called = false; + ctx->is_overmem = false; + ctx->water = NULL; + ctx->water_arg = NULL; + ctx->common.impmagic = MEM_MAGIC; + ctx->common.magic = ISCAPI_MCTX_MAGIC; + ctx->common.methods = (isc_memmethods_t *)&memmethods; + isc_ondestroy_init(&ctx->ondestroy); + ctx->memalloc = memalloc; + ctx->memfree = memfree; + ctx->arg = arg; + ctx->stats = NULL; + ctx->checkfree = true; +#if ISC_MEM_TRACKLINES + ctx->debuglist = NULL; + ctx->debuglistcnt = 0; +#endif + ISC_LIST_INIT(ctx->pools); + ctx->poolcnt = 0; + ctx->freelists = NULL; + ctx->basic_blocks = NULL; + ctx->basic_table = NULL; + ctx->basic_table_count = 0; + ctx->basic_table_size = 0; + ctx->lowest = NULL; + ctx->highest = NULL; + + ctx->stats = (memalloc)(arg, + (ctx->max_size+1) * sizeof(struct stats)); + if (ctx->stats == NULL) { + result = ISC_R_NOMEMORY; + goto error; + } + memset(ctx->stats, 0, (ctx->max_size + 1) * sizeof(struct stats)); + + if ((flags & ISC_MEMFLAG_INTERNAL) != 0) { + if (target_size == 0U) + ctx->mem_target = DEF_MEM_TARGET; + else + ctx->mem_target = target_size; + ctx->freelists = (memalloc)(arg, ctx->max_size * + sizeof(element *)); + if (ctx->freelists == NULL) { + result = ISC_R_NOMEMORY; + goto error; + } + memset(ctx->freelists, 0, + ctx->max_size * sizeof(element *)); + } + +#if ISC_MEM_TRACKLINES + if ((isc_mem_debugging & ISC_MEM_DEBUGRECORD) != 0) { + unsigned int i; + + ctx->debuglist = (memalloc)(arg, + (ctx->max_size+1) * sizeof(debuglist_t)); + if (ctx->debuglist == NULL) { + result = ISC_R_NOMEMORY; + goto error; + } + for (i = 0; i <= ctx->max_size; i++) + ISC_LIST_INIT(ctx->debuglist[i]); + } +#endif + + ctx->memalloc_failures = 0; + + LOCK(&contextslock); + ISC_LIST_INITANDAPPEND(contexts, ctx, link); + UNLOCK(&contextslock); + + *ctxp = (isc_mem_t *)ctx; + return (ISC_R_SUCCESS); + + error: + if (ctx != NULL) { + if (ctx->stats != NULL) + (memfree)(arg, ctx->stats); + if (ctx->freelists != NULL) + (memfree)(arg, ctx->freelists); +#if ISC_MEM_TRACKLINES + if (ctx->debuglist != NULL) + (ctx->memfree)(ctx->arg, ctx->debuglist); +#endif /* ISC_MEM_TRACKLINES */ + if ((ctx->flags & ISC_MEMFLAG_NOLOCK) == 0) + DESTROYLOCK(&ctx->lock); + (memfree)(arg, ctx); + } + + return (result); +} + +static void +destroy(isc__mem_t *ctx) { + unsigned int i; + isc_ondestroy_t ondest; + + LOCK(&contextslock); + ISC_LIST_UNLINK(contexts, ctx, link); + totallost += ctx->inuse; + UNLOCK(&contextslock); + + ctx->common.impmagic = 0; + ctx->common.magic = 0; + + INSIST(ISC_LIST_EMPTY(ctx->pools)); + +#if ISC_MEM_TRACKLINES + if (ctx->debuglist != NULL) { + if (ctx->checkfree) { + for (i = 0; i <= ctx->max_size; i++) { + if (!ISC_LIST_EMPTY(ctx->debuglist[i])) + print_active(ctx, stderr); + INSIST(ISC_LIST_EMPTY(ctx->debuglist[i])); + } + } else { + debuglink_t *dl; + + for (i = 0; i <= ctx->max_size; i++) + for (dl = ISC_LIST_HEAD(ctx->debuglist[i]); + dl != NULL; + dl = ISC_LIST_HEAD(ctx->debuglist[i])) { + ISC_LIST_UNLINK(ctx->debuglist[i], + dl, link); + free(dl); + } + } + (ctx->memfree)(ctx->arg, ctx->debuglist); + } +#endif + INSIST(ctx->references == 0); + + if (ctx->checkfree) { + for (i = 0; i <= ctx->max_size; i++) { + if (ctx->stats[i].gets != 0U) { + fprintf(stderr, + "Failing assertion due to probable " + "leaked memory in context %p (\"%s\") " + "(stats[%u].gets == %lu).\n", + ctx, ctx->name, i, ctx->stats[i].gets); +#if ISC_MEM_TRACKLINES + print_active(ctx, stderr); +#endif + INSIST(ctx->stats[i].gets == 0U); + } + } + } + + (ctx->memfree)(ctx->arg, ctx->stats); + + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + for (i = 0; i < ctx->basic_table_count; i++) + (ctx->memfree)(ctx->arg, ctx->basic_table[i]); + (ctx->memfree)(ctx->arg, ctx->freelists); + if (ctx->basic_table != NULL) + (ctx->memfree)(ctx->arg, ctx->basic_table); + } + + ondest = ctx->ondestroy; + + if ((ctx->flags & ISC_MEMFLAG_NOLOCK) == 0) + DESTROYLOCK(&ctx->lock); + (ctx->memfree)(ctx->arg, ctx); + + isc_ondestroy_notify(&ondest, ctx); +} + +void +isc__mem_attach(isc_mem_t *source0, isc_mem_t **targetp) { + isc__mem_t *source = (isc__mem_t *)source0; + + REQUIRE(VALID_CONTEXT(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + MCTXLOCK(source, &source->lock); + source->references++; + MCTXUNLOCK(source, &source->lock); + + *targetp = (isc_mem_t *)source; +} + +void +isc__mem_detach(isc_mem_t **ctxp) { + isc__mem_t *ctx; + bool want_destroy = false; + + REQUIRE(ctxp != NULL); + ctx = (isc__mem_t *)*ctxp; + REQUIRE(VALID_CONTEXT(ctx)); + + MCTXLOCK(ctx, &ctx->lock); + INSIST(ctx->references > 0); + ctx->references--; + if (ctx->references == 0) + want_destroy = true; + MCTXUNLOCK(ctx, &ctx->lock); + + if (want_destroy) + destroy(ctx); + + *ctxp = NULL; +} + +/* + * isc_mem_putanddetach() is the equivalent of: + * + * mctx = NULL; + * isc_mem_attach(ptr->mctx, &mctx); + * isc_mem_detach(&ptr->mctx); + * isc_mem_put(mctx, ptr, sizeof(*ptr); + * isc_mem_detach(&mctx); + */ + +void +isc___mem_putanddetach(isc_mem_t **ctxp, void *ptr, size_t size FLARG) { + isc__mem_t *ctx; + bool want_destroy = false; + size_info *si; + size_t oldsize; + + REQUIRE(ctxp != NULL); + ctx = (isc__mem_t *)*ctxp; + REQUIRE(VALID_CONTEXT(ctx)); + REQUIRE(ptr != NULL); + + /* + * Must be before mem_putunlocked() as ctxp is usually within + * [ptr..ptr+size). + */ + *ctxp = NULL; + + if ((isc_mem_debugging & (ISC_MEM_DEBUGSIZE|ISC_MEM_DEBUGCTX)) != 0) { + if ((isc_mem_debugging & ISC_MEM_DEBUGSIZE) != 0) { + si = &(((size_info *)ptr)[-1]); + oldsize = si->u.size - ALIGNMENT_SIZE; + if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) + oldsize -= ALIGNMENT_SIZE; + INSIST(oldsize == size); + } + isc__mem_free((isc_mem_t *)ctx, ptr FLARG_PASS); + + MCTXLOCK(ctx, &ctx->lock); + ctx->references--; + if (ctx->references == 0) + want_destroy = true; + MCTXUNLOCK(ctx, &ctx->lock); + if (want_destroy) + destroy(ctx); + + return; + } + + MCTXLOCK(ctx, &ctx->lock); + + DELETE_TRACE(ctx, ptr, size, file, line); + + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + mem_putunlocked(ctx, ptr, size); + } else { + mem_putstats(ctx, ptr, size); + mem_put(ctx, ptr, size); + } + + INSIST(ctx->references > 0); + ctx->references--; + if (ctx->references == 0) + want_destroy = true; + + MCTXUNLOCK(ctx, &ctx->lock); + + if (want_destroy) + destroy(ctx); +} + +void +isc__mem_destroy(isc_mem_t **ctxp) { + isc__mem_t *ctx; + + /* + * This routine provides legacy support for callers who use mctxs + * without attaching/detaching. + */ + + REQUIRE(ctxp != NULL); + ctx = (isc__mem_t *)*ctxp; + REQUIRE(VALID_CONTEXT(ctx)); + + MCTXLOCK(ctx, &ctx->lock); +#if ISC_MEM_TRACKLINES + if (ctx->references != 1) + print_active(ctx, stderr); +#endif + REQUIRE(ctx->references == 1); + ctx->references--; + MCTXUNLOCK(ctx, &ctx->lock); + + destroy(ctx); + + *ctxp = NULL; +} + +isc_result_t +isc_mem_ondestroy(isc_mem_t *ctx0, isc_task_t *task, isc_event_t **event) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + isc_result_t res; + + MCTXLOCK(ctx, &ctx->lock); + res = isc_ondestroy_register(&ctx->ondestroy, task, event); + MCTXUNLOCK(ctx, &ctx->lock); + + return (res); +} + +void * +isc___mem_get(isc_mem_t *ctx0, size_t size FLARG) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + void *ptr; + bool call_water = false; + + REQUIRE(VALID_CONTEXT(ctx)); + + if ((isc_mem_debugging & (ISC_MEM_DEBUGSIZE|ISC_MEM_DEBUGCTX)) != 0) + return (isc__mem_allocate(ctx0, size FLARG_PASS)); + + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + MCTXLOCK(ctx, &ctx->lock); + ptr = mem_getunlocked(ctx, size); + } else { + ptr = mem_get(ctx, size); + MCTXLOCK(ctx, &ctx->lock); + if (ptr != NULL) + mem_getstats(ctx, size); + } + + ADD_TRACE(ctx, ptr, size, file, line); + if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water) { + ctx->is_overmem = true; + if (!ctx->hi_called) + call_water = true; + } + if (ctx->inuse > ctx->maxinuse) { + ctx->maxinuse = ctx->inuse; + if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water && + (isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0) + fprintf(stderr, "maxinuse = %lu\n", + (unsigned long)ctx->inuse); + } + MCTXUNLOCK(ctx, &ctx->lock); + + if (call_water && (ctx->water != NULL)) + (ctx->water)(ctx->water_arg, ISC_MEM_HIWATER); + + return (ptr); +} + +void +isc___mem_put(isc_mem_t *ctx0, void *ptr, size_t size FLARG) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + bool call_water = false; + size_info *si; + size_t oldsize; + + REQUIRE(VALID_CONTEXT(ctx)); + REQUIRE(ptr != NULL); + + if ((isc_mem_debugging & (ISC_MEM_DEBUGSIZE|ISC_MEM_DEBUGCTX)) != 0) { + if ((isc_mem_debugging & ISC_MEM_DEBUGSIZE) != 0) { + si = &(((size_info *)ptr)[-1]); + oldsize = si->u.size - ALIGNMENT_SIZE; + if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) + oldsize -= ALIGNMENT_SIZE; + INSIST(oldsize == size); + } + isc__mem_free((isc_mem_t *)ctx, ptr FLARG_PASS); + return; + } + + MCTXLOCK(ctx, &ctx->lock); + + DELETE_TRACE(ctx, ptr, size, file, line); + + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + mem_putunlocked(ctx, ptr, size); + } else { + mem_putstats(ctx, ptr, size); + mem_put(ctx, ptr, size); + } + + /* + * The check against ctx->lo_water == 0 is for the condition + * when the context was pushed over hi_water but then had + * isc_mem_setwater() called with 0 for hi_water and lo_water. + */ + if ((ctx->inuse < ctx->lo_water) || (ctx->lo_water == 0U)) { + ctx->is_overmem = false; + if (ctx->hi_called) + call_water = true; + } + + MCTXUNLOCK(ctx, &ctx->lock); + + if (call_water && (ctx->water != NULL)) + (ctx->water)(ctx->water_arg, ISC_MEM_LOWATER); +} + +void +isc__mem_waterack(isc_mem_t *ctx0, int flag) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + REQUIRE(VALID_CONTEXT(ctx)); + + MCTXLOCK(ctx, &ctx->lock); + if (flag == ISC_MEM_LOWATER) + ctx->hi_called = false; + else if (flag == ISC_MEM_HIWATER) + ctx->hi_called = true; + MCTXUNLOCK(ctx, &ctx->lock); +} + +#if ISC_MEM_TRACKLINES +static void +print_active(isc__mem_t *mctx, FILE *out) { + if (mctx->debuglist != NULL) { + debuglink_t *dl; + unsigned int i, j; + const char *format; + bool found; + + fprintf(out, "%s", isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, + ISC_MSG_DUMPALLOC, + "Dump of all outstanding " + "memory allocations:\n")); + found = false; + format = isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, + ISC_MSG_PTRFILELINE, + "\tptr %p size %u file %s line %u\n"); + for (i = 0; i <= mctx->max_size; i++) { + dl = ISC_LIST_HEAD(mctx->debuglist[i]); + + if (dl != NULL) + found = true; + + while (dl != NULL) { + for (j = 0; j < DEBUGLIST_COUNT; j++) + if (dl->ptr[j] != NULL) + fprintf(out, format, + dl->ptr[j], + dl->size[j], + dl->file[j], + dl->line[j]); + dl = ISC_LIST_NEXT(dl, link); + } + } + if (!found) + fputs(isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, + ISC_MSG_NONE, "\tNone.\n"), out); + } +} +#endif + +/* + * Print the stats[] on the stream "out" with suitable formatting. + */ +void +isc_mem_stats(isc_mem_t *ctx0, FILE *out) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_t i; + const struct stats *s; + const isc__mempool_t *pool; + + REQUIRE(VALID_CONTEXT(ctx)); + MCTXLOCK(ctx, &ctx->lock); + + for (i = 0; i <= ctx->max_size; i++) { + s = &ctx->stats[i]; + + if (s->totalgets == 0U && s->gets == 0U) + continue; + fprintf(out, "%s%5lu: %11lu gets, %11lu rem", + (i == ctx->max_size) ? ">=" : " ", + (unsigned long) i, s->totalgets, s->gets); + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0 && + (s->blocks != 0U || s->freefrags != 0U)) + fprintf(out, " (%lu bl, %lu ff)", + s->blocks, s->freefrags); + fputc('\n', out); + } + + /* + * Note that since a pool can be locked now, these stats might be + * somewhat off if the pool is in active use at the time the stats + * are dumped. The link fields are protected by the isc_mem_t's + * lock, however, so walking this list and extracting integers from + * stats fields is always safe. + */ + pool = ISC_LIST_HEAD(ctx->pools); + if (pool != NULL) { + fprintf(out, "%s", isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, + ISC_MSG_POOLSTATS, + "[Pool statistics]\n")); + fprintf(out, "%15s %10s %10s %10s %10s %10s %10s %10s %1s\n", + isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, + ISC_MSG_POOLNAME, "name"), + isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, + ISC_MSG_POOLSIZE, "size"), + isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, + ISC_MSG_POOLMAXALLOC, "maxalloc"), + isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, + ISC_MSG_POOLALLOCATED, "allocated"), + isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, + ISC_MSG_POOLFREECOUNT, "freecount"), + isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, + ISC_MSG_POOLFREEMAX, "freemax"), + isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, + ISC_MSG_POOLFILLCOUNT, "fillcount"), + isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, + ISC_MSG_POOLGETS, "gets"), + "L"); + } + while (pool != NULL) { + fprintf(out, "%15s %10lu %10u %10u %10u %10u %10u %10u %s\n", +#if ISC_MEMPOOL_NAMES + pool->name, +#else + "(not tracked)", +#endif + (unsigned long) pool->size, pool->maxalloc, + pool->allocated, pool->freecount, pool->freemax, + pool->fillcount, pool->gets, + (pool->lock == NULL ? "N" : "Y")); + pool = ISC_LIST_NEXT(pool, link); + } + +#if ISC_MEM_TRACKLINES + print_active(ctx, out); +#endif + + MCTXUNLOCK(ctx, &ctx->lock); +} + +/* + * Replacements for malloc() and free() -- they implicitly remember the + * size of the object allocated (with some additional overhead). + */ + +static void * +mem_allocateunlocked(isc_mem_t *ctx0, size_t size) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_info *si; + + size += ALIGNMENT_SIZE; + if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) + size += ALIGNMENT_SIZE; + + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) + si = mem_getunlocked(ctx, size); + else + si = mem_get(ctx, size); + + if (si == NULL) + return (NULL); + if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) { + si->u.ctx = ctx; + si++; + } + si->u.size = size; + return (&si[1]); +} + +void * +isc___mem_allocate(isc_mem_t *ctx0, size_t size FLARG) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_info *si; + bool call_water = false; + + REQUIRE(VALID_CONTEXT(ctx)); + + MCTXLOCK(ctx, &ctx->lock); + si = mem_allocateunlocked((isc_mem_t *)ctx, size); + if (((ctx->flags & ISC_MEMFLAG_INTERNAL) == 0) && (si != NULL)) + mem_getstats(ctx, si[-1].u.size); + + ADD_TRACE(ctx, si, si[-1].u.size, file, line); + if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water && + !ctx->is_overmem) { + ctx->is_overmem = true; + } + + if (ctx->hi_water != 0U && !ctx->hi_called && + ctx->inuse > ctx->hi_water) { + ctx->hi_called = true; + call_water = true; + } + if (ctx->inuse > ctx->maxinuse) { + ctx->maxinuse = ctx->inuse; + if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water && + (isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0) + fprintf(stderr, "maxinuse = %lu\n", + (unsigned long)ctx->inuse); + } + MCTXUNLOCK(ctx, &ctx->lock); + + if (call_water) + (ctx->water)(ctx->water_arg, ISC_MEM_HIWATER); + + return (si); +} + +void * +isc___mem_reallocate(isc_mem_t *ctx0, void *ptr, size_t size FLARG) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + void *new_ptr = NULL; + size_t oldsize, copysize; + + REQUIRE(VALID_CONTEXT(ctx)); + + /* + * This function emulates the realloc(3) standard library function: + * - if size > 0, allocate new memory; and if ptr is non NULL, copy + * as much of the old contents to the new buffer and free the old one. + * Note that when allocation fails the original pointer is intact; + * the caller must free it. + * - if size is 0 and ptr is non NULL, simply free the given ptr. + * - this function returns: + * pointer to the newly allocated memory, or + * NULL if allocation fails or doesn't happen. + */ + if (size > 0U) { + new_ptr = isc__mem_allocate(ctx0, size FLARG_PASS); + if (new_ptr != NULL && ptr != NULL) { + oldsize = (((size_info *)ptr)[-1]).u.size; + INSIST(oldsize >= ALIGNMENT_SIZE); + oldsize -= ALIGNMENT_SIZE; + if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) { + INSIST(oldsize >= ALIGNMENT_SIZE); + oldsize -= ALIGNMENT_SIZE; + } + copysize = (oldsize > size) ? size : oldsize; + memmove(new_ptr, ptr, copysize); + isc__mem_free(ctx0, ptr FLARG_PASS); + } + } else if (ptr != NULL) + isc__mem_free(ctx0, ptr FLARG_PASS); + + return (new_ptr); +} + +void +isc___mem_free(isc_mem_t *ctx0, void *ptr FLARG) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_info *si; + size_t size; + bool call_water= false; + + REQUIRE(VALID_CONTEXT(ctx)); + REQUIRE(ptr != NULL); + + if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) { + si = &(((size_info *)ptr)[-2]); + REQUIRE(si->u.ctx == ctx); + size = si[1].u.size; + } else { + si = &(((size_info *)ptr)[-1]); + size = si->u.size; + } + + MCTXLOCK(ctx, &ctx->lock); + + DELETE_TRACE(ctx, ptr, size, file, line); + + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + mem_putunlocked(ctx, si, size); + } else { + mem_putstats(ctx, si, size); + mem_put(ctx, si, size); + } + + /* + * The check against ctx->lo_water == 0 is for the condition + * when the context was pushed over hi_water but then had + * isc_mem_setwater() called with 0 for hi_water and lo_water. + */ + if (ctx->is_overmem && + (ctx->inuse < ctx->lo_water || ctx->lo_water == 0U)) { + ctx->is_overmem = false; + } + + if (ctx->hi_called && + (ctx->inuse < ctx->lo_water || ctx->lo_water == 0U)) { + ctx->hi_called = false; + + if (ctx->water != NULL) + call_water = true; + } + MCTXUNLOCK(ctx, &ctx->lock); + + if (call_water) + (ctx->water)(ctx->water_arg, ISC_MEM_LOWATER); +} + + +/* + * Other useful things. + */ + +char * +isc___mem_strdup(isc_mem_t *mctx0, const char *s FLARG) { + isc__mem_t *mctx = (isc__mem_t *)mctx0; + size_t len; + char *ns; + + REQUIRE(VALID_CONTEXT(mctx)); + REQUIRE(s != NULL); + + len = strlen(s) + 1; + + ns = isc__mem_allocate((isc_mem_t *)mctx, len FLARG_PASS); + + if (ns != NULL) + strlcpy(ns, s, len); + + return (ns); +} + +void +isc__mem_setdestroycheck(isc_mem_t *ctx0, bool flag) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + REQUIRE(VALID_CONTEXT(ctx)); + MCTXLOCK(ctx, &ctx->lock); + + ctx->checkfree = flag; + + MCTXUNLOCK(ctx, &ctx->lock); +} + +/* + * Quotas + */ + +void +isc_mem_setquota(isc_mem_t *ctx0, size_t quota) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + REQUIRE(VALID_CONTEXT(ctx)); + MCTXLOCK(ctx, &ctx->lock); + + ctx->quota = quota; + + MCTXUNLOCK(ctx, &ctx->lock); +} + +size_t +isc_mem_getquota(isc_mem_t *ctx0) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_t quota; + + REQUIRE(VALID_CONTEXT(ctx)); + MCTXLOCK(ctx, &ctx->lock); + + quota = ctx->quota; + + MCTXUNLOCK(ctx, &ctx->lock); + + return (quota); +} + +size_t +isc__mem_inuse(isc_mem_t *ctx0) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_t inuse; + + REQUIRE(VALID_CONTEXT(ctx)); + MCTXLOCK(ctx, &ctx->lock); + + inuse = ctx->inuse; + + MCTXUNLOCK(ctx, &ctx->lock); + + return (inuse); +} + +size_t +isc__mem_maxinuse(isc_mem_t *ctx0) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_t maxinuse; + + REQUIRE(VALID_CONTEXT(ctx)); + MCTXLOCK(ctx, &ctx->lock); + + maxinuse = ctx->maxinuse; + + MCTXUNLOCK(ctx, &ctx->lock); + + return (maxinuse); +} + +size_t +isc__mem_total(isc_mem_t *ctx0) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_t total; + + REQUIRE(VALID_CONTEXT(ctx)); + MCTXLOCK(ctx, &ctx->lock); + + total = ctx->total; + + MCTXUNLOCK(ctx, &ctx->lock); + + return (total); +} + +void +isc__mem_setwater(isc_mem_t *ctx0, isc_mem_water_t water, void *water_arg, + size_t hiwater, size_t lowater) +{ + isc__mem_t *ctx = (isc__mem_t *)ctx0; + bool callwater = false; + isc_mem_water_t oldwater; + void *oldwater_arg; + + REQUIRE(VALID_CONTEXT(ctx)); + REQUIRE(hiwater >= lowater); + + MCTXLOCK(ctx, &ctx->lock); + oldwater = ctx->water; + oldwater_arg = ctx->water_arg; + if (water == NULL) { + callwater = ctx->hi_called; + ctx->water = NULL; + ctx->water_arg = NULL; + ctx->hi_water = 0; + ctx->lo_water = 0; + } else { + if (ctx->hi_called && + (ctx->water != water || ctx->water_arg != water_arg || + ctx->inuse < lowater || lowater == 0U)) + callwater = true; + ctx->water = water; + ctx->water_arg = water_arg; + ctx->hi_water = hiwater; + ctx->lo_water = lowater; + } + MCTXUNLOCK(ctx, &ctx->lock); + + if (callwater && oldwater != NULL) + (oldwater)(oldwater_arg, ISC_MEM_LOWATER); +} + +bool +isc__mem_isovermem(isc_mem_t *ctx0) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + REQUIRE(VALID_CONTEXT(ctx)); + + /* + * We don't bother to lock the context because 100% accuracy isn't + * necessary (and even if we locked the context the returned value + * could be different from the actual state when it's used anyway) + */ + return (ctx->is_overmem); +} + +void +isc_mem_setname(isc_mem_t *ctx0, const char *name, void *tag) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + REQUIRE(VALID_CONTEXT(ctx)); + + LOCK(&ctx->lock); + strlcpy(ctx->name, name, sizeof(ctx->name)); + ctx->tag = tag; + UNLOCK(&ctx->lock); +} + +const char * +isc_mem_getname(isc_mem_t *ctx0) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + REQUIRE(VALID_CONTEXT(ctx)); + + if (ctx->name[0] == 0) + return (""); + + return (ctx->name); +} + +void * +isc_mem_gettag(isc_mem_t *ctx0) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + REQUIRE(VALID_CONTEXT(ctx)); + + return (ctx->tag); +} + +/* + * Memory pool stuff + */ + +isc_result_t +isc__mempool_create(isc_mem_t *mctx0, size_t size, isc_mempool_t **mpctxp) { + isc__mem_t *mctx = (isc__mem_t *)mctx0; + isc__mempool_t *mpctx; + + REQUIRE(VALID_CONTEXT(mctx)); + REQUIRE(size > 0U); + REQUIRE(mpctxp != NULL && *mpctxp == NULL); + + /* + * Allocate space for this pool, initialize values, and if all works + * well, attach to the memory context. + */ + mpctx = isc_mem_get((isc_mem_t *)mctx, sizeof(isc__mempool_t)); + if (mpctx == NULL) + return (ISC_R_NOMEMORY); + + mpctx->common.methods = (isc_mempoolmethods_t *)&mempoolmethods; + mpctx->common.impmagic = MEMPOOL_MAGIC; + mpctx->common.magic = ISCAPI_MPOOL_MAGIC; + mpctx->lock = NULL; + mpctx->mctx = mctx; + /* + * Mempools are stored as a linked list of element. + */ + if (size < sizeof(element)) { + size = sizeof(element); + } + mpctx->size = size; + mpctx->maxalloc = UINT_MAX; + mpctx->allocated = 0; + mpctx->freecount = 0; + mpctx->freemax = 1; + mpctx->fillcount = 1; + mpctx->gets = 0; +#if ISC_MEMPOOL_NAMES + mpctx->name[0] = 0; +#endif + mpctx->items = NULL; + + *mpctxp = (isc_mempool_t *)mpctx; + + MCTXLOCK(mctx, &mctx->lock); + ISC_LIST_INITANDAPPEND(mctx->pools, mpctx, link); + mctx->poolcnt++; + MCTXUNLOCK(mctx, &mctx->lock); + + return (ISC_R_SUCCESS); +} + +void +isc__mempool_setname(isc_mempool_t *mpctx0, const char *name) { + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + + REQUIRE(name != NULL); + REQUIRE(VALID_MEMPOOL(mpctx)); + +#if ISC_MEMPOOL_NAMES + if (mpctx->lock != NULL) + LOCK(mpctx->lock); + + strlcpy(mpctx->name, name, sizeof(mpctx->name)); + + if (mpctx->lock != NULL) + UNLOCK(mpctx->lock); +#else + UNUSED(mpctx); + UNUSED(name); +#endif +} + +void +isc__mempool_destroy(isc_mempool_t **mpctxp) { + isc__mempool_t *mpctx; + isc__mem_t *mctx; + isc_mutex_t *lock; + element *item; + + REQUIRE(mpctxp != NULL); + mpctx = (isc__mempool_t *)*mpctxp; + REQUIRE(VALID_MEMPOOL(mpctx)); +#if ISC_MEMPOOL_NAMES + if (mpctx->allocated > 0) + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc__mempool_destroy(): mempool %s " + "leaked memory", + mpctx->name); +#endif + REQUIRE(mpctx->allocated == 0); + + mctx = mpctx->mctx; + + lock = mpctx->lock; + + if (lock != NULL) + LOCK(lock); + + /* + * Return any items on the free list + */ + MCTXLOCK(mctx, &mctx->lock); + while (mpctx->items != NULL) { + INSIST(mpctx->freecount > 0); + mpctx->freecount--; + item = mpctx->items; + mpctx->items = item->next; + + if ((mctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + mem_putunlocked(mctx, item, mpctx->size); + } else { + mem_putstats(mctx, item, mpctx->size); + mem_put(mctx, item, mpctx->size); + } + } + MCTXUNLOCK(mctx, &mctx->lock); + + /* + * Remove our linked list entry from the memory context. + */ + MCTXLOCK(mctx, &mctx->lock); + ISC_LIST_UNLINK(mctx->pools, mpctx, link); + mctx->poolcnt--; + MCTXUNLOCK(mctx, &mctx->lock); + + mpctx->common.impmagic = 0; + mpctx->common.magic = 0; + + isc_mem_put((isc_mem_t *)mpctx->mctx, mpctx, sizeof(isc__mempool_t)); + + if (lock != NULL) + UNLOCK(lock); + + *mpctxp = NULL; +} + +void +isc__mempool_associatelock(isc_mempool_t *mpctx0, isc_mutex_t *lock) { + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + + REQUIRE(VALID_MEMPOOL(mpctx)); + REQUIRE(mpctx->lock == NULL); + REQUIRE(lock != NULL); + + mpctx->lock = lock; +} + +void * +isc___mempool_get(isc_mempool_t *mpctx0 FLARG) { + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + element *item; + isc__mem_t *mctx; + unsigned int i; + + REQUIRE(VALID_MEMPOOL(mpctx)); + + mctx = mpctx->mctx; + + if (mpctx->lock != NULL) + LOCK(mpctx->lock); + + /* + * Don't let the caller go over quota + */ + if (ISC_UNLIKELY(mpctx->allocated >= mpctx->maxalloc)) { + item = NULL; + goto out; + } + + if (ISC_UNLIKELY(mpctx->items == NULL)) { + /* + * We need to dip into the well. Lock the memory context + * here and fill up our free list. + */ + MCTXLOCK(mctx, &mctx->lock); + for (i = 0; i < mpctx->fillcount; i++) { + if ((mctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + item = mem_getunlocked(mctx, mpctx->size); + } else { + item = mem_get(mctx, mpctx->size); + if (item != NULL) + mem_getstats(mctx, mpctx->size); + } + if (ISC_UNLIKELY(item == NULL)) + break; + item->next = mpctx->items; + mpctx->items = item; + mpctx->freecount++; + } + MCTXUNLOCK(mctx, &mctx->lock); + } + + /* + * If we didn't get any items, return NULL. + */ + item = mpctx->items; + if (ISC_UNLIKELY(item == NULL)) + goto out; + + mpctx->items = item->next; + INSIST(mpctx->freecount > 0); + mpctx->freecount--; + mpctx->gets++; + mpctx->allocated++; + + out: + if (mpctx->lock != NULL) + UNLOCK(mpctx->lock); + +#if ISC_MEM_TRACKLINES + if (((isc_mem_debugging & TRACE_OR_RECORD) != 0) && item != NULL) { + MCTXLOCK(mctx, &mctx->lock); + ADD_TRACE(mctx, item, mpctx->size, file, line); + MCTXUNLOCK(mctx, &mctx->lock); + } +#endif /* ISC_MEM_TRACKLINES */ + + return (item); +} + +/* coverity[+free : arg-1] */ +void +isc___mempool_put(isc_mempool_t *mpctx0, void *mem FLARG) { + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + isc__mem_t *mctx; + element *item; + + REQUIRE(VALID_MEMPOOL(mpctx)); + REQUIRE(mem != NULL); + + mctx = mpctx->mctx; + + if (mpctx->lock != NULL) + LOCK(mpctx->lock); + + INSIST(mpctx->allocated > 0); + mpctx->allocated--; + +#if ISC_MEM_TRACKLINES + if ((isc_mem_debugging & TRACE_OR_RECORD) != 0) { + MCTXLOCK(mctx, &mctx->lock); + DELETE_TRACE(mctx, mem, mpctx->size, file, line); + MCTXUNLOCK(mctx, &mctx->lock); + } +#endif /* ISC_MEM_TRACKLINES */ + + /* + * If our free list is full, return this to the mctx directly. + */ + if (mpctx->freecount >= mpctx->freemax) { + MCTXLOCK(mctx, &mctx->lock); + if ((mctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + mem_putunlocked(mctx, mem, mpctx->size); + } else { + mem_putstats(mctx, mem, mpctx->size); + mem_put(mctx, mem, mpctx->size); + } + MCTXUNLOCK(mctx, &mctx->lock); + if (mpctx->lock != NULL) + UNLOCK(mpctx->lock); + return; + } + + /* + * Otherwise, attach it to our free list and bump the counter. + */ + mpctx->freecount++; + item = (element *)mem; + item->next = mpctx->items; + mpctx->items = item; + + if (mpctx->lock != NULL) + UNLOCK(mpctx->lock); +} + +/* + * Quotas + */ + +void +isc__mempool_setfreemax(isc_mempool_t *mpctx0, unsigned int limit) { + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + + REQUIRE(VALID_MEMPOOL(mpctx)); + + if (mpctx->lock != NULL) + LOCK(mpctx->lock); + + mpctx->freemax = limit; + + if (mpctx->lock != NULL) + UNLOCK(mpctx->lock); +} + +unsigned int +isc_mempool_getfreemax(isc_mempool_t *mpctx0) { + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + unsigned int freemax; + + REQUIRE(VALID_MEMPOOL(mpctx)); + + if (mpctx->lock != NULL) + LOCK(mpctx->lock); + + freemax = mpctx->freemax; + + if (mpctx->lock != NULL) + UNLOCK(mpctx->lock); + + return (freemax); +} + +unsigned int +isc_mempool_getfreecount(isc_mempool_t *mpctx0) { + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + unsigned int freecount; + + REQUIRE(VALID_MEMPOOL(mpctx)); + + if (mpctx->lock != NULL) + LOCK(mpctx->lock); + + freecount = mpctx->freecount; + + if (mpctx->lock != NULL) + UNLOCK(mpctx->lock); + + return (freecount); +} + +void +isc__mempool_setmaxalloc(isc_mempool_t *mpctx0, unsigned int limit) { + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + + REQUIRE(limit > 0); + + REQUIRE(VALID_MEMPOOL(mpctx)); + + if (mpctx->lock != NULL) + LOCK(mpctx->lock); + + mpctx->maxalloc = limit; + + if (mpctx->lock != NULL) + UNLOCK(mpctx->lock); +} + +unsigned int +isc_mempool_getmaxalloc(isc_mempool_t *mpctx0) { + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + unsigned int maxalloc; + + REQUIRE(VALID_MEMPOOL(mpctx)); + + if (mpctx->lock != NULL) + LOCK(mpctx->lock); + + maxalloc = mpctx->maxalloc; + + if (mpctx->lock != NULL) + UNLOCK(mpctx->lock); + + return (maxalloc); +} + +unsigned int +isc__mempool_getallocated(isc_mempool_t *mpctx0) { + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + unsigned int allocated; + + REQUIRE(VALID_MEMPOOL(mpctx)); + + if (mpctx->lock != NULL) + LOCK(mpctx->lock); + + allocated = mpctx->allocated; + + if (mpctx->lock != NULL) + UNLOCK(mpctx->lock); + + return (allocated); +} + +void +isc__mempool_setfillcount(isc_mempool_t *mpctx0, unsigned int limit) { + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + + REQUIRE(limit > 0); + REQUIRE(VALID_MEMPOOL(mpctx)); + + if (mpctx->lock != NULL) + LOCK(mpctx->lock); + + mpctx->fillcount = limit; + + if (mpctx->lock != NULL) + UNLOCK(mpctx->lock); +} + +unsigned int +isc_mempool_getfillcount(isc_mempool_t *mpctx0) { + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + + unsigned int fillcount; + + REQUIRE(VALID_MEMPOOL(mpctx)); + + if (mpctx->lock != NULL) + LOCK(mpctx->lock); + + fillcount = mpctx->fillcount; + + if (mpctx->lock != NULL) + UNLOCK(mpctx->lock); + + return (fillcount); +} + +isc_result_t +isc__mem_register(void) { + return (isc_mem_register(isc_mem_create2)); +} + +void +isc__mem_printactive(isc_mem_t *ctx0, FILE *file) { +#if ISC_MEM_TRACKLINES + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + REQUIRE(VALID_CONTEXT(ctx)); + REQUIRE(file != NULL); + + print_active(ctx, file); +#else + UNUSED(ctx0); + UNUSED(file); +#endif +} + +void +isc_mem_printallactive(FILE *file) { +#if !ISC_MEM_TRACKLINES + UNUSED(file); +#else + isc__mem_t *ctx; + + RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); + + LOCK(&contextslock); + for (ctx = ISC_LIST_HEAD(contexts); + ctx != NULL; + ctx = ISC_LIST_NEXT(ctx, link)) { + fprintf(file, "context: %p\n", ctx); + print_active(ctx, file); + } + UNLOCK(&contextslock); +#endif +} + +void +isc_mem_checkdestroyed(FILE *file) { +#if !ISC_MEM_TRACKLINES + UNUSED(file); +#endif + + RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); + + LOCK(&contextslock); + if (!ISC_LIST_EMPTY(contexts)) { +#if ISC_MEM_TRACKLINES + if ((isc_mem_debugging & TRACE_OR_RECORD) != 0) { + isc__mem_t *ctx; + + for (ctx = ISC_LIST_HEAD(contexts); + ctx != NULL; + ctx = ISC_LIST_NEXT(ctx, link)) { + fprintf(file, "context: %p\n", ctx); + print_active(ctx, file); + } + fflush(file); + } +#endif + INSIST(0); + } + UNLOCK(&contextslock); +} + +unsigned int +isc_mem_references(isc_mem_t *ctx0) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + unsigned int references; + + REQUIRE(VALID_CONTEXT(ctx)); + + MCTXLOCK(ctx, &ctx->lock); + references = ctx->references; + MCTXUNLOCK(ctx, &ctx->lock); + + return (references); +} + +typedef struct summarystat { + uint64_t total; + uint64_t inuse; + uint64_t blocksize; + uint64_t contextsize; +} summarystat_t; + +#ifdef HAVE_LIBXML2 +#define TRY0(a) do { xmlrc = (a); if (xmlrc < 0) goto error; } while(0) +static int +xml_renderctx(isc__mem_t *ctx, summarystat_t *summary, + xmlTextWriterPtr writer) +{ + int xmlrc; + + REQUIRE(VALID_CONTEXT(ctx)); + + MCTXLOCK(ctx, &ctx->lock); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "context")); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "id")); + TRY0(xmlTextWriterWriteFormatString(writer, "%p", ctx)); + TRY0(xmlTextWriterEndElement(writer)); /* id */ + + if (ctx->name[0] != 0) { + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "name")); + TRY0(xmlTextWriterWriteFormatString(writer, "%s", ctx->name)); + TRY0(xmlTextWriterEndElement(writer)); /* name */ + } + + summary->contextsize += sizeof(*ctx) + + (ctx->max_size + 1) * sizeof(struct stats) + + ctx->max_size * sizeof(element *) + + ctx->basic_table_count * sizeof(char *); +#if ISC_MEM_TRACKLINES + if (ctx->debuglist != NULL) { + summary->contextsize += + (ctx->max_size + 1) * sizeof(debuglist_t) + + ctx->debuglistcnt * sizeof(debuglink_t); + } +#endif + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "references")); + TRY0(xmlTextWriterWriteFormatString(writer, "%d", ctx->references)); + TRY0(xmlTextWriterEndElement(writer)); /* references */ + + summary->total += ctx->total; + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "total")); + TRY0(xmlTextWriterWriteFormatString(writer, + "%" PRIu64, + (uint64_t)ctx->total)); + TRY0(xmlTextWriterEndElement(writer)); /* total */ + + summary->inuse += ctx->inuse; + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "inuse")); + TRY0(xmlTextWriterWriteFormatString(writer, + "%" PRIu64, + (uint64_t)ctx->inuse)); + TRY0(xmlTextWriterEndElement(writer)); /* inuse */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "maxinuse")); + TRY0(xmlTextWriterWriteFormatString(writer, + "%" PRIu64, + (uint64_t)ctx->maxinuse)); + TRY0(xmlTextWriterEndElement(writer)); /* maxinuse */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "blocksize")); + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + summary->blocksize += ctx->basic_table_count * + NUM_BASIC_BLOCKS * ctx->mem_target; + TRY0(xmlTextWriterWriteFormatString(writer, + "%" PRIu64, + (uint64_t) + ctx->basic_table_count * + NUM_BASIC_BLOCKS * + ctx->mem_target)); + } else + TRY0(xmlTextWriterWriteFormatString(writer, "%s", "-")); + TRY0(xmlTextWriterEndElement(writer)); /* blocksize */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "pools")); + TRY0(xmlTextWriterWriteFormatString(writer, "%u", ctx->poolcnt)); + TRY0(xmlTextWriterEndElement(writer)); /* pools */ + summary->contextsize += ctx->poolcnt * sizeof(isc_mempool_t); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "hiwater")); + TRY0(xmlTextWriterWriteFormatString(writer, + "%" PRIu64, + (uint64_t)ctx->hi_water)); + TRY0(xmlTextWriterEndElement(writer)); /* hiwater */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "lowater")); + TRY0(xmlTextWriterWriteFormatString(writer, + "%" PRIu64 "", + (uint64_t)ctx->lo_water)); + TRY0(xmlTextWriterEndElement(writer)); /* lowater */ + + TRY0(xmlTextWriterEndElement(writer)); /* context */ + + error: + MCTXUNLOCK(ctx, &ctx->lock); + + return (xmlrc); +} + +int +isc_mem_renderxml(xmlTextWriterPtr writer) { + isc__mem_t *ctx; + summarystat_t summary; + uint64_t lost; + int xmlrc; + + memset(&summary, 0, sizeof(summary)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "contexts")); + + RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); + + LOCK(&contextslock); + lost = totallost; + for (ctx = ISC_LIST_HEAD(contexts); + ctx != NULL; + ctx = ISC_LIST_NEXT(ctx, link)) { + xmlrc = xml_renderctx(ctx, &summary, writer); + if (xmlrc < 0) { + UNLOCK(&contextslock); + goto error; + } + } + UNLOCK(&contextslock); + + TRY0(xmlTextWriterEndElement(writer)); /* contexts */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "summary")); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "TotalUse")); + TRY0(xmlTextWriterWriteFormatString(writer, + "%" PRIu64, + summary.total)); + TRY0(xmlTextWriterEndElement(writer)); /* TotalUse */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "InUse")); + TRY0(xmlTextWriterWriteFormatString(writer, + "%" PRIu64, + summary.inuse)); + TRY0(xmlTextWriterEndElement(writer)); /* InUse */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "BlockSize")); + TRY0(xmlTextWriterWriteFormatString(writer, + "%" PRIu64, + summary.blocksize)); + TRY0(xmlTextWriterEndElement(writer)); /* BlockSize */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "ContextSize")); + TRY0(xmlTextWriterWriteFormatString(writer, + "%" PRIu64, + summary.contextsize)); + TRY0(xmlTextWriterEndElement(writer)); /* ContextSize */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "Lost")); + TRY0(xmlTextWriterWriteFormatString(writer, + "%" PRIu64, + lost)); + TRY0(xmlTextWriterEndElement(writer)); /* Lost */ + + TRY0(xmlTextWriterEndElement(writer)); /* summary */ + error: + return (xmlrc); +} + +#endif /* HAVE_LIBXML2 */ + +#ifdef HAVE_JSON +#define CHECKMEM(m) do { \ + if (m == NULL) { \ + result = ISC_R_NOMEMORY;\ + goto error;\ + } \ +} while(0) + +static isc_result_t +json_renderctx(isc__mem_t *ctx, summarystat_t *summary, json_object *array) { + isc_result_t result = ISC_R_FAILURE; + json_object *ctxobj, *obj; + char buf[1024]; + + REQUIRE(VALID_CONTEXT(ctx)); + REQUIRE(summary != NULL); + REQUIRE(array != NULL); + + MCTXLOCK(ctx, &ctx->lock); + + summary->contextsize += sizeof(*ctx) + + (ctx->max_size + 1) * sizeof(struct stats) + + ctx->max_size * sizeof(element *) + + ctx->basic_table_count * sizeof(char *); + summary->total += ctx->total; + summary->inuse += ctx->inuse; + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) + summary->blocksize += ctx->basic_table_count * + NUM_BASIC_BLOCKS * ctx->mem_target; +#if ISC_MEM_TRACKLINES + if (ctx->debuglist != NULL) { + summary->contextsize += + (ctx->max_size + 1) * sizeof(debuglist_t) + + ctx->debuglistcnt * sizeof(debuglink_t); + } +#endif + + ctxobj = json_object_new_object(); + CHECKMEM(ctxobj); + + snprintf(buf, sizeof(buf), "%p", ctx); + obj = json_object_new_string(buf); + CHECKMEM(obj); + json_object_object_add(ctxobj, "id", obj); + + if (ctx->name[0] != 0) { + obj = json_object_new_string(ctx->name); + CHECKMEM(obj); + json_object_object_add(ctxobj, "name", obj); + } + + obj = json_object_new_int64(ctx->references); + CHECKMEM(obj); + json_object_object_add(ctxobj, "references", obj); + + obj = json_object_new_int64(ctx->total); + CHECKMEM(obj); + json_object_object_add(ctxobj, "total", obj); + + obj = json_object_new_int64(ctx->inuse); + CHECKMEM(obj); + json_object_object_add(ctxobj, "inuse", obj); + + obj = json_object_new_int64(ctx->maxinuse); + CHECKMEM(obj); + json_object_object_add(ctxobj, "maxinuse", obj); + + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + uint64_t blocksize; + blocksize = ctx->basic_table_count * NUM_BASIC_BLOCKS * + ctx->mem_target; + obj = json_object_new_int64(blocksize); + CHECKMEM(obj); + json_object_object_add(ctxobj, "blocksize", obj); + } + + obj = json_object_new_int64(ctx->poolcnt); + CHECKMEM(obj); + json_object_object_add(ctxobj, "pools", obj); + + summary->contextsize += ctx->poolcnt * sizeof(isc_mempool_t); + + obj = json_object_new_int64(ctx->hi_water); + CHECKMEM(obj); + json_object_object_add(ctxobj, "hiwater", obj); + + obj = json_object_new_int64(ctx->lo_water); + CHECKMEM(obj); + json_object_object_add(ctxobj, "lowater", obj); + + MCTXUNLOCK(ctx, &ctx->lock); + json_object_array_add(array, ctxobj); + return (ISC_R_SUCCESS); + + error: + MCTXUNLOCK(ctx, &ctx->lock); + if (ctxobj != NULL) + json_object_put(ctxobj); + return (result); +} + +isc_result_t +isc_mem_renderjson(json_object *memobj) { + isc_result_t result = ISC_R_SUCCESS; + isc__mem_t *ctx; + summarystat_t summary; + uint64_t lost; + json_object *ctxarray, *obj; + + memset(&summary, 0, sizeof(summary)); + RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); + + ctxarray = json_object_new_array(); + CHECKMEM(ctxarray); + + LOCK(&contextslock); + lost = totallost; + for (ctx = ISC_LIST_HEAD(contexts); + ctx != NULL; + ctx = ISC_LIST_NEXT(ctx, link)) { + result = json_renderctx(ctx, &summary, ctxarray); + if (result != ISC_R_SUCCESS) { + UNLOCK(&contextslock); + goto error; + } + } + UNLOCK(&contextslock); + + obj = json_object_new_int64(summary.total); + CHECKMEM(obj); + json_object_object_add(memobj, "TotalUse", obj); + + obj = json_object_new_int64(summary.inuse); + CHECKMEM(obj); + json_object_object_add(memobj, "InUse", obj); + + obj = json_object_new_int64(summary.blocksize); + CHECKMEM(obj); + json_object_object_add(memobj, "BlockSize", obj); + + obj = json_object_new_int64(summary.contextsize); + CHECKMEM(obj); + json_object_object_add(memobj, "ContextSize", obj); + + obj = json_object_new_int64(lost); + CHECKMEM(obj); + json_object_object_add(memobj, "Lost", obj); + + json_object_object_add(memobj, "contexts", ctxarray); + return (ISC_R_SUCCESS); + + error: + if (ctxarray != NULL) + json_object_put(ctxarray); + return (result); +} +#endif /* HAVE_JSON */ + +static isc_memcreatefunc_t mem_createfunc = NULL; + +isc_result_t +isc_mem_register(isc_memcreatefunc_t createfunc) { + isc_result_t result = ISC_R_SUCCESS; + + RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); + + LOCK(&createlock); + if (mem_createfunc == NULL) + mem_createfunc = createfunc; + else + result = ISC_R_EXISTS; + UNLOCK(&createlock); + + return (result); +} + + +isc_result_t +isc__mem_create2(size_t init_max_size, size_t target_size, isc_mem_t **mctxp, + unsigned int flags) +{ + isc_result_t result; + + LOCK(&createlock); + + REQUIRE(mem_createfunc != NULL); + result = (*mem_createfunc)(init_max_size, target_size, mctxp, flags); + + UNLOCK(&createlock); + + return (result); +} + +isc_result_t +isc_mem_create(size_t init_max_size, size_t target_size, isc_mem_t **mctxp) { + isc_result_t result; + + if (isc_bind9) + return (isc_mem_createx2(init_max_size, target_size, + default_memalloc, default_memfree, + NULL, mctxp, isc_mem_defaultflags)); + LOCK(&createlock); + + REQUIRE(mem_createfunc != NULL); + result = (*mem_createfunc)(init_max_size, target_size, mctxp, + isc_mem_defaultflags); + + UNLOCK(&createlock); + + return (result); +} + +isc_result_t +isc_mem_create2(size_t init_max_size, size_t target_size, isc_mem_t **mctxp, + unsigned int flags) +{ + if (isc_bind9) + return (isc_mem_createx2(init_max_size, target_size, + default_memalloc, default_memfree, + NULL, mctxp, flags)); + + return (isc_mem_createx2(init_max_size, target_size, + default_memalloc, default_memfree, + NULL, mctxp, flags)); +} + +void +isc_mem_attach(isc_mem_t *source, isc_mem_t **targetp) { + REQUIRE(ISCAPI_MCTX_VALID(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + if (isc_bind9) + isc__mem_attach(source, targetp); + else + source->methods->attach(source, targetp); + + ENSURE(*targetp == source); +} + +void +isc_mem_detach(isc_mem_t **mctxp) { + REQUIRE(mctxp != NULL && ISCAPI_MCTX_VALID(*mctxp)); + + if (isc_bind9) + isc__mem_detach(mctxp); + else + (*mctxp)->methods->detach(mctxp); + + ENSURE(*mctxp == NULL); +} + +void +isc_mem_destroy(isc_mem_t **mctxp) { + REQUIRE(mctxp != NULL && ISCAPI_MCTX_VALID(*mctxp)); + + if (isc_bind9) + isc__mem_destroy(mctxp); + else + (*mctxp)->methods->destroy(mctxp); + + ENSURE(*mctxp == NULL); +} + +void +isc_mem_setdestroycheck(isc_mem_t *mctx, bool flag) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + mctx->methods->setdestroycheck(mctx, flag); +} + +void +isc_mem_setwater(isc_mem_t *ctx, isc_mem_water_t water, void *water_arg, + size_t hiwater, size_t lowater) +{ + REQUIRE(ISCAPI_MCTX_VALID(ctx)); + + if (isc_bind9) + isc__mem_setwater(ctx, water, water_arg, hiwater, lowater); + else + ctx->methods->setwater(ctx, water, water_arg, hiwater, lowater); +} + +void +isc_mem_waterack(isc_mem_t *ctx, int flag) { + REQUIRE(ISCAPI_MCTX_VALID(ctx)); + + if (isc_bind9) + isc__mem_waterack(ctx, flag); + else + ctx->methods->waterack(ctx, flag); +} + +size_t +isc_mem_inuse(isc_mem_t *mctx) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + if (isc_bind9) + return (isc__mem_inuse(mctx)); + + return (mctx->methods->inuse(mctx)); +} + +size_t +isc_mem_maxinuse(isc_mem_t *mctx) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + if (isc_bind9) + return (isc__mem_maxinuse(mctx)); + + return (mctx->methods->maxinuse(mctx)); +} + +size_t +isc_mem_total(isc_mem_t *mctx) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + if (isc_bind9) + return (isc__mem_total(mctx)); + + return (mctx->methods->total(mctx)); +} + +bool +isc_mem_isovermem(isc_mem_t *mctx) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + if (isc_bind9) + return (isc__mem_isovermem(mctx)); + + return (mctx->methods->isovermem(mctx)); +} + + +isc_result_t +isc_mempool_create(isc_mem_t *mctx, size_t size, isc_mempool_t **mpctxp) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + return (mctx->methods->mpcreate(mctx, size, mpctxp)); +} + +void +isc_mempool_destroy(isc_mempool_t **mpctxp) { + REQUIRE(mpctxp != NULL && ISCAPI_MPOOL_VALID(*mpctxp)); + + if (isc_bind9) + isc__mempool_destroy(mpctxp); + else + (*mpctxp)->methods->destroy(mpctxp); + + ENSURE(*mpctxp == NULL); +} + +unsigned int +isc_mempool_getallocated(isc_mempool_t *mpctx) { + REQUIRE(ISCAPI_MPOOL_VALID(mpctx)); + + if (isc_bind9) + return (isc__mempool_getallocated(mpctx)); + + return (mpctx->methods->getallocated(mpctx)); +} + +void +isc_mempool_setmaxalloc(isc_mempool_t *mpctx, unsigned int limit) { + REQUIRE(ISCAPI_MPOOL_VALID(mpctx)); + + if (isc_bind9) + isc__mempool_setmaxalloc(mpctx, limit); + else + mpctx->methods->setmaxalloc(mpctx, limit); +} + +void +isc_mempool_setfreemax(isc_mempool_t *mpctx, unsigned int limit) { + REQUIRE(ISCAPI_MPOOL_VALID(mpctx)); + + if (isc_bind9) + isc__mempool_setfreemax(mpctx, limit); + else + mpctx->methods->setfreemax(mpctx, limit); +} + +void +isc_mempool_setname(isc_mempool_t *mpctx, const char *name) { + REQUIRE(ISCAPI_MPOOL_VALID(mpctx)); + + if (isc_bind9) + isc__mempool_setname(mpctx, name); + else + mpctx->methods->setname(mpctx, name); +} + +void +isc_mempool_associatelock(isc_mempool_t *mpctx, isc_mutex_t *lock) { + REQUIRE(ISCAPI_MPOOL_VALID(mpctx)); + + if (isc_bind9) + isc__mempool_associatelock(mpctx, lock); + else + mpctx->methods->associatelock(mpctx, lock); +} + +void +isc_mempool_setfillcount(isc_mempool_t *mpctx, unsigned int limit) { + REQUIRE(ISCAPI_MPOOL_VALID(mpctx)); + + if (isc_bind9) + isc__mempool_setfillcount(mpctx, limit); + else + mpctx->methods->setfillcount(mpctx, limit); +} + +void * +isc__mem_get(isc_mem_t *mctx, size_t size FLARG) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + if (isc_bind9) + return (isc___mem_get(mctx, size FLARG_PASS)); + + return (mctx->methods->memget(mctx, size FLARG_PASS)); + +} + +void +isc__mem_put(isc_mem_t *mctx, void *ptr, size_t size FLARG) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + if (isc_bind9) + isc___mem_put(mctx, ptr, size FLARG_PASS); + else + mctx->methods->memput(mctx, ptr, size FLARG_PASS); +} + +void +isc__mem_putanddetach(isc_mem_t **mctxp, void *ptr, size_t size FLARG) { + REQUIRE(mctxp != NULL && ISCAPI_MCTX_VALID(*mctxp)); + + if (isc_bind9) + isc___mem_putanddetach(mctxp, ptr, size FLARG_PASS); + else + (*mctxp)->methods->memputanddetach(mctxp, ptr, size FLARG_PASS); + + /* + * XXX: We cannot always ensure *mctxp == NULL here + * (see lib/isc/mem.c). + */ +} + +void * +isc__mem_allocate(isc_mem_t *mctx, size_t size FLARG) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + if (isc_bind9) + return (isc___mem_allocate(mctx, size FLARG_PASS)); + + return (mctx->methods->memallocate(mctx, size FLARG_PASS)); +} + +void * +isc__mem_reallocate(isc_mem_t *mctx, void *ptr, size_t size FLARG) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + if (isc_bind9) + return (isc___mem_reallocate(mctx, ptr, size FLARG_PASS)); + + return (mctx->methods->memreallocate(mctx, ptr, size FLARG_PASS)); +} + +char * +isc__mem_strdup(isc_mem_t *mctx, const char *s FLARG) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + if (isc_bind9) + return (isc___mem_strdup(mctx, s FLARG_PASS)); + + return (mctx->methods->memstrdup(mctx, s FLARG_PASS)); +} + +void +isc__mem_free(isc_mem_t *mctx, void *ptr FLARG) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + if (isc_bind9) + isc___mem_free(mctx, ptr FLARG_PASS); + else + mctx->methods->memfree(mctx, ptr FLARG_PASS); +} + +void * +isc__mempool_get(isc_mempool_t *mpctx FLARG) { + REQUIRE(ISCAPI_MPOOL_VALID(mpctx)); + + if (isc_bind9) + return (isc___mempool_get(mpctx FLARG_PASS)); + + return (mpctx->methods->get(mpctx FLARG_PASS)); +} + +void +isc__mempool_put(isc_mempool_t *mpctx, void *mem FLARG) { + REQUIRE(ISCAPI_MPOOL_VALID(mpctx)); + + if (isc_bind9) + isc___mempool_put(mpctx, mem FLARG_PASS); + else + mpctx->methods->put(mpctx, mem FLARG_PASS); +} |