File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / libpdel / util / typed_mem.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 23:25:53 2012 UTC (12 years, 4 months ago) by misho
Branches: libpdel, MAIN
CVS tags: v0_5_3, HEAD
libpdel


/*
 * Copyright (c) 2001-2002 Packet Design, LLC.
 * All rights reserved.
 * 
 * Subject to the following obligations and disclaimer of warranty,
 * use and redistribution of this software, in source or object code
 * forms, with or without modifications are expressly permitted by
 * Packet Design; provided, however, that:
 * 
 *    (i)  Any and all reproductions of the source or object code
 *         must include the copyright notice above and the following
 *         disclaimer of warranties; and
 *    (ii) No rights are granted, in any manner or form, to use
 *         Packet Design trademarks, including the mark "PACKET DESIGN"
 *         on advertising, endorsements, or otherwise except as such
 *         appears in the above copyright notice or in the software.
 * 
 * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
 * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * OR NON-INFRINGEMENT.  PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
 * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
 * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
 * RELIABILITY OR OTHERWISE.  IN NO EVENT SHALL PACKET DESIGN BE
 * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
 * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
 * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
 * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Author: Archie Cobbs <archie@freebsd.org>
 */

#include <sys/types.h>
#include <sys/param.h>

#ifndef _KERNEL
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdarg.h>
#include <syslog.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <pthread.h>
#else
#include <sys/systm.h>
#include <machine/stdarg.h>
#include <malloc.h>
#include "util/kernelglue.h"
#endif

#include "structs/structs.h"
#include "structs/type/array.h"
#include "structs/type/struct.h"
#include "structs/type/string.h"
#include "structs/type/int.h"
#include "util/gtree.h"
#include "util/typed_mem.h"

/* Alignment, with a minimum of 4 bytes */
#if defined(__FreeBSD__)
#include <machine/param.h>
#define ALIGNMENT	(_ALIGNBYTES + 1)
#else
#define ALIGNMENT	8			/* conservative guess */
#endif

/* Summary information about a single type of memory */
struct mem_type {
	char		name[TYPED_MEM_TYPELEN];/* string for this type */
	u_int32_t	count;			/* number of allocated blocks */
	size_t		total;			/* total allocated bytes */
};

/* Information about a single allocated block of memory */
struct mem_info {
	void		*mem;			/* actual memory block */
	size_t		size;			/* size of memory block */
	struct mem_type	*type;			/* pointer to type info */
};

/*
 * Structs type for 'struct typed_mem_stats'
 */
#define TYPED_MEM_STATS_MTYPE		"typed_mem_stats"

static const struct structs_type typebuf_type
	= STRUCTS_FIXEDSTRING_TYPE(TYPED_MEM_TYPELEN);
static const struct structs_field typed_mem_typestats_fields[] = {
	STRUCTS_STRUCT_FIELD(typed_mem_typestats, type, &typebuf_type),
	STRUCTS_STRUCT_FIELD(typed_mem_typestats, allocs, &structs_type_uint),
	STRUCTS_STRUCT_FIELD(typed_mem_typestats, bytes, &structs_type_uint),
	STRUCTS_STRUCT_FIELD_END
};
static const struct structs_type typed_mem_typestats_type
	= STRUCTS_STRUCT_TYPE(typed_mem_typestats, typed_mem_typestats_fields);
const struct structs_type typed_mem_stats_type
	= STRUCTS_ARRAY_TYPE(&typed_mem_typestats_type,
	  TYPED_MEM_STATS_MTYPE, "type");

/* We need to use the real functions in here */
#undef malloc
#undef calloc
#undef realloc
#undef reallocf
#undef free
#undef asprintf
#undef vasprintf

/* How to print out problems */
#define WHINE(fmt, args...)	(void)fprintf(stderr, fmt "\n" , ## args)

/*
 * Kernel glue
 */
#ifdef _KERNEL

#if TYPED_MEM_TRACE
#error TYPED_MEM_TRACE not supported in kernel mode
#endif

#define malloc(x)       kern_malloc(x)
#define realloc(x, y)   kern_realloc(x, y)
#define free(x)         kern_free(x)

#define pthread_mutex_lock()	0
#define pthread_mutex_unlock()	0

#undef WHINE
#define WHINE(fmt, args...)	log(LOG_CRIT, fmt "\n" , ## args)

#endif	/* _KERNEL */

/*
 * Internal variables
 */
static u_char	typed_mem_started;
static u_char	typed_mem_enabled;
static struct	gtree *mem_tree;
static struct	gtree *type_tree;
static u_int	tree_node_size;

#ifndef _KERNEL
static pthread_mutex_t	typed_mem_mutex;
#endif

/* Guard bytes. Should have an "aligned" length. */
static const u_char	typed_mem_guard_data[] = { 0x34, 0x8e, 0x71, 0x9f };
static u_char		typed_mem_guard[ALIGNMENT];

/*
 * Internal functions
 */
static gtree_cmp_t	type_cmp;
static gtree_print_t	type_print;
static gtree_cmp_t	mem_cmp;

static struct		mem_info *typed_mem_find(
#if TYPED_MEM_TRACE
	const char *file, u_int line,
#endif
	void *mem, const char *typename, const char *func);

#ifndef _KERNEL
static gtree_print_t	mem_print;
#else
#define mem_print	NULL
#endif

/*
 * Enable typed memory.
 */
int
typed_mem_enable(void)
{
	pthread_mutexattr_t mattr;
	int i;

	/* Already enabled? */
	if (typed_mem_enabled)
		return (0);

	/* Already started allocating memory? */
	if (typed_mem_started) {
		errno = EALREADY;
		return (-1);
	}

	/* Initialize recursive mutex */
	if ((errno = pthread_mutexattr_init(&mattr)) != 0)
		return (-1);
	if ((errno = pthread_mutexattr_settype(&mattr,
	    PTHREAD_MUTEX_RECURSIVE)) != 0) {
		pthread_mutexattr_destroy(&mattr);
		return (-1);
	}
	if ((errno = pthread_mutex_init(&typed_mem_mutex, &mattr)) != 0) {
		pthread_mutexattr_destroy(&mattr);
		return (-1);
	}
	pthread_mutexattr_destroy(&mattr);

	/* Fill in guard bytes */
	for (i = 0; i < ALIGNMENT; i++)
		typed_mem_guard[i] = typed_mem_guard_data[i % ALIGNMENT];

	/* Done */
	typed_mem_enabled = 1;
	return (0);
}

/*
 * realloc(3) replacement
 */
void *
typed_mem_realloc(
#if TYPED_MEM_TRACE
	const char *file, u_int line,
#endif
	const char *typename, void *mem, size_t size)
{
#if TYPED_MEM_TRACE
	void *const omem = mem;
#endif
	struct mem_info *info;
	void *rtn = NULL;
	int errno_save;
	int removed;
	int r;

	/* Check if typed memory is active */
	typed_mem_started = 1;
	if (!typed_mem_enabled || typename == NULL)
		return (realloc(mem, size));

	/* Lock info */
	r = pthread_mutex_lock(&typed_mem_mutex);
	assert(r == 0);

	/* Initialize first */
	if (mem_tree == NULL) {
		if ((mem_tree = gtree_create(NULL, NULL,
		    mem_cmp, NULL, NULL, mem_print)) == NULL)
			goto done;
		if ((type_tree = gtree_create(NULL, NULL,
		    type_cmp, NULL, NULL, type_print)) == NULL) {
			gtree_destroy(&mem_tree);
			goto done;
		}
		tree_node_size = gtree_node_size();
	}

	/* Find/create the memory descriptor block */
	if (mem == NULL) {
		struct mem_type *type;
		struct mem_type tkey;

		/* Get memory and new descriptor block */
		if ((info = malloc(sizeof(*info))) == NULL)
			goto done;
		if ((info->mem = malloc(size
		    + 2 * sizeof(typed_mem_guard))) == NULL) {
			errno_save = errno;
			free(info);
			errno = errno_save;
			goto done;
		}
		info->size = size;

		/* Find/create type descriptor for this memory type */
		strncpy(tkey.name, typename, sizeof(tkey.name) - 1);
		tkey.name[sizeof(tkey.name) - 1] = '\0';
		if ((type = gtree_get(type_tree, &tkey)) == NULL) {
			if ((type = malloc(sizeof(*type))) == NULL) {
				errno_save = errno;
				free(info->mem);
				free(info);
				errno = errno_save;
				goto done;
			}
			strcpy(type->name, tkey.name);
			type->count = 0;
			type->total = 0;
			if (gtree_put(type_tree, type) == -1) {
				errno_save = errno;
				free(type);
				free(info->mem);
				free(info);
				errno = errno_save;
				goto done;
			}
		}
		info->type = type;

		/* Add block descriptor to mem tree */
		if (gtree_put(mem_tree, info) == -1) {
			errno_save = errno;
			if (type->count == 0) {		/* was a new type */
				removed = gtree_remove(type_tree, type);
				assert(removed);
				free(type);
			}
			free(info->mem);
			free(info);
			errno = errno_save;
			goto done;
		}
	} else {
		void *node;

		/* Find memory descriptor */
#if TYPED_MEM_TRACE
		info = typed_mem_find(file, line, mem, typename, "REALLOC");
#else
		info = typed_mem_find(mem, typename, "REALLOC");
#endif

		/* Pre-allocate mem tree node */
		if ((node = malloc(tree_node_size)) == NULL)
			goto done;

		/* Get reallocated memory block */
		if ((mem = realloc(info->mem,
		    size + 2 * sizeof(typed_mem_guard))) == NULL) {
			errno_save = errno;
			free(node);
			errno = errno_save;
			goto done;
		}

		/* Subtract old block from type stats */
		info->type->total -= info->size;
		info->type->count--;

		/* Update block descriptor */
		info->size = size;
		if (info->mem != mem) {
			removed = gtree_remove(mem_tree, info);
			assert(removed);
			info->mem = mem;
			gtree_put_prealloc(mem_tree, info, node);
		} else
			free(node);		/* we didn't need it */
	}

	/* Add new block to type stats */
	info->type->total += info->size;
	info->type->count++;

	/* Install guard bytes */
	memcpy(info->mem, typed_mem_guard, sizeof(typed_mem_guard));
	memcpy((u_char *)info->mem + sizeof(typed_mem_guard) + info->size,
	    typed_mem_guard, sizeof(typed_mem_guard));

#if TYPED_MEM_TRACE
	/* Tracing */
	{
		const char *basename;

		if ((basename = strrchr(file, '/')) != NULL)
			basename++;
		else
			basename = file;
		fprintf(stderr, "%s:%u:ALLOC %p -> %p \"%s\" %u [%u]\n",
		    basename, line, omem, (u_char *)info->mem
		    + sizeof(typed_mem_guard), typename, size,
		    info->type->count);
	}
#endif

	/* Return memory block */
	rtn = (u_char *)info->mem + sizeof(typed_mem_guard);

done:
	/* Done */
	r = pthread_mutex_unlock(&typed_mem_mutex);
	assert(r == 0);
	return (rtn);
}

/*
 * reallocf(3) replacement
 */
void *
typed_mem_reallocf(
#if TYPED_MEM_TRACE
	const char *file, u_int line,
#endif
	const char *typename, void *mem, size_t size)
{
	void *p;

#if TYPED_MEM_TRACE
	if ((p = typed_mem_realloc(file, line, typename, mem, size)) == NULL)
		typed_mem_free(file, line, typename, mem);
#else
	if ((p = typed_mem_realloc(typename, mem, size)) == NULL)
		typed_mem_free(typename, mem);
#endif
	return (p);
}

/*
 * free(3) replacement
 *
 * Note: it is OK if "typedname" points to memory within the region
 * pointed to by "mem".
 */
void
typed_mem_free(
#if TYPED_MEM_TRACE
	const char *file, u_int line,
#endif
	const char *typename, void *mem)
{
	const int errno_save = errno;
	struct mem_info *info;
	int removed;
	int r;

	/* free(NULL) does nothing */
	if (mem == NULL)
		return;

	/* Check if typed memory is active */
	typed_mem_started = 1;
	if (!typed_mem_enabled || typename == NULL) {
		free(mem);
		return;
	}

	/* Lock info */
	r = pthread_mutex_lock(&typed_mem_mutex);
	assert(r == 0);

	/* Find memory descriptor */
#if TYPED_MEM_TRACE
	info = typed_mem_find(file, line, mem, typename, "FREE");
#else
	info = typed_mem_find(mem, typename, "FREE");
#endif

#if TYPED_MEM_TRACE
	/* Tracing */
	{
		const char *basename;

		if ((basename = strrchr(file, '/')) != NULL)
			basename++;
		else
			basename = file;
		fprintf(stderr, "%s:%u:FREE %p -> %p \"%s\" [%u]\n",
		    basename, line, mem, (void *)0,
		    typename, info->type->count);
	}
#endif

	/* Subtract block from descriptor info */
	info->type->total -= info->size;
	if (--info->type->count == 0) {
		assert(info->type->total == 0);
		removed = gtree_remove(type_tree, info->type);
		assert(removed);
		free(info->type);
	}

	/* Free memory and descriptor block */
	removed = gtree_remove(mem_tree, info);
	assert(removed);
	free(info->mem);
	free(info);

	/* Done */
	r = pthread_mutex_unlock(&typed_mem_mutex);
	assert(r == 0);
	errno = errno_save;
}

/*
 * calloc(3) replacement
 */
void *
typed_mem_calloc(
#if TYPED_MEM_TRACE
	const char *file, u_int line,
#endif
	const char *type, size_t num, size_t size)
{
	void *mem;

	size *= num;
#if TYPED_MEM_TRACE
	if ((mem = typed_mem_realloc(file, line, type, NULL, size)) == NULL)
		return (NULL);
#else
	if ((mem = typed_mem_realloc(type, NULL, size)) == NULL)
		return (NULL);
#endif
	memset(mem, 0, size);
	return (mem);
}

/*
 * strdup(3) replacement
 */
char *
typed_mem_strdup(
#if TYPED_MEM_TRACE
	const char *file, u_int line,
#endif
	const char *type, const char *string)
{
	const int slen = strlen(string) + 1;
	char *result;

#if TYPED_MEM_TRACE
	if ((result = typed_mem_realloc(file, line, type, NULL, slen)) == NULL)
		return (NULL);
#else
	if ((result = typed_mem_realloc(type, NULL, slen)) == NULL)
		return (NULL);
#endif
	memcpy(result, string, slen);
	return (result);
}

/*
 * asprintf(3) replacement
 */
int
typed_mem_asprintf(
#if TYPED_MEM_TRACE
	const char *file, u_int line,
#endif
	const char *type, char **ret, const char *fmt, ...)
{
	va_list args;
	int r;

	va_start(args, fmt);
#if TYPED_MEM_TRACE
	r = typed_mem_vasprintf(file, line, type, ret, fmt, args);
#else
	r = typed_mem_vasprintf(type, ret, fmt, args);
#endif
	va_end(args);
	return (r);
}

/*
 * vasprintf(3) replacement
 */
int
typed_mem_vasprintf(
#if TYPED_MEM_TRACE
	const char *file, u_int line,
#endif
	const char *type, char **ret, const char *fmt, va_list args)
{
	int errno_save;
	char *s;
	int r;

	if ((r = vasprintf(ret, fmt, args)) == -1)
		return (-1);
	s = *ret;
#if TYPED_MEM_TRACE
	*ret = typed_mem_strdup(file, line, type, s);
#else
	*ret = typed_mem_strdup(type, s);
#endif
	errno_save = errno;
	free(s);
	if (*ret == NULL) {
		errno = errno_save;
		return (-1);
	}
	return (r);
}

/*
 * Get type for a memory block.
 */
char *
typed_mem_type(const void *mem, char *typebuf)
{
	struct mem_info *info = NULL;
	struct mem_info ikey;
	int r;

	/* Are we enabled? */
	if (!typed_mem_enabled) {
		errno = ENXIO;
		return (NULL);
	}

	/* Lock info */
	r = pthread_mutex_lock(&typed_mem_mutex);
	assert(r == 0);

	/* Find memory descriptor */
	ikey.mem = (u_char *)mem - sizeof(typed_mem_guard);
	if (mem_tree == NULL || (info = gtree_get(mem_tree, &ikey)) == NULL) {
		errno = ENOENT;
		typebuf = NULL;
		goto done;
	}

	/* Copy type */
	strlcpy(typebuf, info->type->name, TYPED_MEM_TYPELEN);

done:
	/* Unlock info */
	r = pthread_mutex_unlock(&typed_mem_mutex);
	assert(r == 0);

	/* Done */
	return (typebuf);
}

/*
 * Return typed memory usage statistics. The caller must free the
 * array by calling "structs_free(&typed_mem_stats_type, NULL, stats)".
 *
 * Returns zero if successful, -1 (and sets errno) if not.
 * If typed memory is disabled, errno = ENXIO.
 */
int
typed_mem_usage(struct typed_mem_stats *stats)
{
	struct mem_type *type;
	int i;
	int r;

	/* Check if enabled */
	if (!typed_mem_enabled) {
		errno = ENXIO;
		return (-1);
	}

	/* Lock info */
	r = pthread_mutex_lock(&typed_mem_mutex);
	assert(r == 0);

	/* Allocate array */
	memset(stats, 0, sizeof(*stats));
	if ((stats->elems = typed_mem_realloc(
#if TYPED_MEM_TRACE
	    __FILE__, __LINE__,
#endif
	    TYPED_MEM_STATS_MTYPE, NULL, (gtree_size(type_tree) + 1)
	    * sizeof(*stats->elems))) == NULL) {
		r = pthread_mutex_unlock(&typed_mem_mutex);
		assert(r == 0);
		return (-1);
	}

	/* Copy type stats */
	for (i = 0, type = gtree_first(type_tree);
	    type != NULL; i++, type = gtree_next(type_tree, type)) {
		struct typed_mem_typestats *const elem = &stats->elems[i];

		strlcpy(elem->type, type->name, sizeof(elem->type));
		elem->allocs = type->count;
		elem->bytes = type->total;
	}
	stats->length = i;

	/* Unlock info */
	r = pthread_mutex_unlock(&typed_mem_mutex);
	assert(r == 0);

	/* Done */
	return (0);
}

#ifndef _KERNEL

/*
 * Return usage statistics in a malloc'd string of the specified type.
 */
void
typed_mem_dump(FILE *fp)
{
	u_long total_blocks = 0;
	u_long total_alloc = 0;
	struct mem_type *type;
	int r;

	/* Check if enabled */
	if (!typed_mem_enabled) {
		fprintf(fp, "Typed memory is not enabled.\n");
		return;
	}

	/* Print header */
	fprintf(fp, "   %-28s %10s %10s\n", "Type", "Count", "Total");
	fprintf(fp, "   %-28s %10s %10s\n", "----", "-----", "-----");

	/* Lock info */
	r = pthread_mutex_lock(&typed_mem_mutex);
	assert(r == 0);

	/* Print allocation types */
	for (type = gtree_first(type_tree);
	    type != NULL; type = gtree_next(type_tree, type)) {
		fprintf(fp, "   %-28s %10u %10lu\n",
		    type->name, (int)type->count, (u_long)type->total);
		total_blocks += type->count;
		total_alloc += type->total;
	}

	/* Print totals */
	fprintf(fp, "   %-28s %10s %10s\n", "", "-----", "-----");
	fprintf(fp, "   %-28s %10lu %10lu\n",
	    "Totals", total_blocks, total_alloc);

	/* Unlock info */
	r = pthread_mutex_unlock(&typed_mem_mutex);
	assert(r == 0);
}

#endif	/* !_KERNEL */

/*
 * Find memory block info descriptor and verify guard bytes
 *
 * This assumes the mutex is locked.
 */
static struct mem_info *
typed_mem_find(
#if TYPED_MEM_TRACE
	const char *file, u_int line,
#endif
	void *mem, const char *typename, const char *func)
{
	struct mem_info *info = NULL;
	struct mem_info ikey;

	/* Find memory descriptor which must already exist */
	ikey.mem = (u_char *)mem - sizeof(typed_mem_guard);
	if (mem_tree == NULL || (info = gtree_get(mem_tree, &ikey)) == NULL) {
#if TYPED_MEM_TRACE
		WHINE("%s:%u: %s() of unallocated block:"
		    " ptr=%p type=\"%s\"", file, line, func, mem, typename);
#else
		WHINE("%s() of unallocated block:"
		    " ptr=%p type=\"%s\"", func, mem, typename);
#endif
		assert(0);
	}

	/* Check type is the same */
	if (strncmp(info->type->name, typename, TYPED_MEM_TYPELEN - 1) != 0) {
#if TYPED_MEM_TRACE
		WHINE("%s:%u: %s() with wrong type:"
		    " ptr=%p \"%s\" != \"%s\"", file, line,
		    func, mem, typename, info->type->name);
#else
		WHINE("%s() with wrong type:"
		    " ptr=%p \"%s\" != \"%s\"", 
		    func, mem, typename, info->type->name);
#endif
		assert(0);
	}

	/* Check ref count */
	assert(info->type->count > 0);

	/* Check guard bytes */
	if (memcmp(info->mem, typed_mem_guard, sizeof(typed_mem_guard)) != 0) {
#if TYPED_MEM_TRACE
		WHINE("%s:%u: %s(): %s guard violated ptr=%p type=\"%s\"",
		    file, line, func, "head", mem, typename);
#else
		WHINE("%s(): %s guard violated ptr=%p type=\"%s\"",
		    func, "head", mem, typename);
#endif
		assert(0);
	}
	if (memcmp((u_char *)info->mem + sizeof(typed_mem_guard) + info->size,
	    typed_mem_guard, sizeof(typed_mem_guard)) != 0) {
#if TYPED_MEM_TRACE
		WHINE("%s:%u: %s(): %s guard violated ptr=%p type=\"%s\"",
		    file, line, func, "tail", mem, typename);
#else
		WHINE("%s(): %s guard violated ptr=%p type=\"%s\"",
		    func, "tail", mem, typename);
#endif
		assert(0);
	}

	/* Done */
	return (info);
}

/*
 * Sort type descriptors by type string.
 */
static int
type_cmp(struct gtree *g, const void *item1, const void *item2)
{
	const struct mem_type *const type1 = item1;
	const struct mem_type *const type2 = item2;

	return (strcmp(type1->name, type2->name));
}

/*
 * Print a type
 */
static const char *
type_print(struct gtree *g, const void *item)
{
	const struct mem_type *const type = item;

	return (type->name);
}

/*
 * Sort memory block descriptors by memory address.
 */
static int
mem_cmp(struct gtree *g, const void *item1, const void *item2)
{
	const struct mem_info *const info1 = item1;
	const struct mem_info *const info2 = item2;

	if (info1->mem < info2->mem)
		return (-1);
	if (info1->mem > info2->mem)
		return (1);
	return (0);
}

#ifndef _KERNEL

/*
 * Print a type
 */
static const char *
mem_print(struct gtree *g, const void *item)
{
	const struct mem_info *const info = item;
	static char buf[32];

	snprintf(buf, sizeof(buf), "%p", info->mem);
	return (buf);
}

#endif	/* !_KERNEL */


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>