/*
* Similar core function to LGPL licensed talloc from Samba
*/
#define CHECK_ALLOCATION 0
#include "hieralloc.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#if CHECK_ALLOCATION
#include <set>
#endif
typedef struct hieralloc_header
{
unsigned beginMagic;
struct hieralloc_header * parent;
struct hieralloc_header * nextSibling, * prevSibling;
struct hieralloc_header * child;
const char * name;
unsigned size, childCount, refCount;
int (* destructor)(void *);
unsigned endMagic;
} hieralloc_header_t;
#define BEGIN_MAGIC() (13377331)
#define END_MAGIC(header) ((unsigned)((const hieralloc_header_t *)header + 1) % 0x10000 | 0x13370000)
static hieralloc_header_t hieralloc_global_header = {BEGIN_MAGIC(), 0, 0, 0, 0, "hieralloc_hieralloc_global_header", 0, 0 ,1, 0, 0x13370000};
#if CHECK_ALLOCATION
static std::set<void *> allocations;
#endif
#ifdef __cplusplus
extern "C" {
#endif
// Returns 1 if it's a valid header
static inline int check_header(const hieralloc_header_t * header)
{
if (BEGIN_MAGIC() != header->beginMagic)
printf("*** hieralloc check_header fail: %p ***\n", header + 1);
assert(BEGIN_MAGIC() == header->beginMagic);
if (&hieralloc_global_header == header)
{
assert(0x13370000 == header->endMagic);
return 1;
}
assert(END_MAGIC(header) == header->endMagic);
assert(!header->nextSibling || header->nextSibling->prevSibling == header);
assert(!header->nextSibling || header->nextSibling->parent == header->parent);
assert(!header->prevSibling || header->prevSibling->nextSibling == header);
assert(!header->prevSibling || header->prevSibling->parent == header->parent);
return 1;
}
static inline hieralloc_header_t * get_header(const void *ptr)
{
hieralloc_header_t * header = (hieralloc_header_t *)(ptr) - 1;
check_header(header);
return header;
}
static void check_children(hieralloc_header_t * header)
{
check_header(header);
unsigned childCount = 0;
hieralloc_header_t * child = header->child;
while (child)
{
childCount++;
check_children(child);
child = child->nextSibling;
}
assert(childCount == header->childCount);
}
// attach to parent and siblings
static void add_to_parent(hieralloc_header_t * parent, hieralloc_header_t * header)
{
assert(NULL == header->parent);
assert(NULL == header->prevSibling);
assert(NULL == header->nextSibling);
if (parent->child)
{
// hieralloc_header_t * child = parent->child;
// while (child)
// {
// assert(child != header);
// child = child->nextSibling;
// }
parent->child->prevSibling = header;
}
header->nextSibling = parent->child;
header->prevSibling = NULL;
header->parent = parent;
parent->child = header;
parent->childCount++;
assert(!header->nextSibling || header->nextSibling->prevSibling == header);
assert(!header->nextSibling || header->nextSibling->parent == header->parent);
assert(!header->prevSibling || header->prevSibling->nextSibling == header);
assert(!header->prevSibling || header->prevSibling->parent == header->parent);
}
// detach from parent and siblings
static void remove_from_parent(hieralloc_header_t * header)
{
hieralloc_header_t * parent = header->parent;
hieralloc_header_t * sibling = header->prevSibling;
assert(!header->nextSibling || header->nextSibling->prevSibling == header);
assert(!header->nextSibling || header->nextSibling->parent == header->parent);
assert(!header->prevSibling || header->prevSibling->nextSibling == header);
assert(!header->prevSibling || header->prevSibling->parent == header->parent);
if (sibling)
{
if (sibling->nextSibling != header)
{
printf("&sibling->nextSibling=%p &header=%p \n", &sibling->nextSibling, &header);
printf("sibling->nextSibling=%p header=%p \n", sibling->nextSibling, header);
}
sibling->nextSibling = header->nextSibling;
if (header->nextSibling)
header->nextSibling->prevSibling = sibling;
header->prevSibling = NULL;
header->nextSibling = NULL;
}
else
{
assert(parent->child == header);
parent->child = header->nextSibling;
if (header->nextSibling)
header->nextSibling->prevSibling = NULL;
header->nextSibling = NULL;
}
header->parent = NULL;
parent->childCount--;
}
// allocate memory and attach to parent context and siblings
void * hieralloc_allocate(const void * context, unsigned size, const char * name)
{
hieralloc_header_t * ptr = (hieralloc_header_t *)malloc(size + sizeof(hieralloc_header_t));
assert(ptr);
memset(ptr, 0xcd, sizeof(*ptr));
ptr->beginMagic = BEGIN_MAGIC();
ptr->parent = ptr->child = ptr->prevSibling = ptr->nextSibling = NULL;
ptr->name = name;
ptr->size = size;
ptr->childCount = 0;
ptr->refCount = 1;
ptr->destructor = NULL;
ptr->endMagic = END_MAGIC(ptr);
hieralloc_header_t * parent = NULL;
if (!context)
parent = &hieralloc_global_header;
else
parent = get_header(context);
add_to_parent(parent, ptr);
#if CHECK_ALLOCATION
assert(allocations.find(ptr + 1) == allocations.end());
allocations.insert(ptr + 1);
#endif
return ptr + 1;
}
// (re)allocate memory and attach to parent context and siblings
void * hieralloc_reallocate(const void * context, void * ptr, unsigned size, const char * name)
{
if (NULL == ptr)
return hieralloc_allocate(context, size, name);
#if CHECK_ALLOCATION
assert(allocations.find(ptr) != allocations.end());
#endif
if (NULL == context)
context = &hieralloc_global_header + 1;
hieralloc_header_t * header = get_header(ptr);
hieralloc_header_t * parent = header->parent;
if (get_header(context) != get_header(ptr)->parent)
{
remove_from_parent(header);
parent = get_header(context);
add_to_parent(parent, header);
}
header = (hieralloc_header_t *)realloc(header, size + sizeof(hieralloc_header_t));
assert(header);
header->size = size;
header->name = name;
if (ptr == (header + 1))
return ptr; // realloc didn't move allocation
header->beginMagic = BEGIN_MAGIC();
header->endMagic = END_MAGIC(header);
if (header->nextSibling)
header->nextSibling->prevSibling = header;
if (header->prevSibling)
header->prevSibling->nextSibling = header;
else
parent->child = header;
hieralloc_header_t * child = header->child;
while (child)
{
child->parent = header;
child = child->nextSibling;
}
#if CHECK_ALLOCATION
allocations.erase(ptr);
assert(allocations.find(header + 1) == allocations.end());
allocations.insert(header + 1);
#endif
return header + 1;
}
// calls destructor if set, and frees children.
// if destructor returns -1, then do nothing and return -1.
int hieralloc_free(void * ptr)
{
if (!ptr)
return 0;
hieralloc_header_t * header = get_header(ptr);
header->refCount--;
if (header->refCount > 0)
return -1;
if (header->destructor)
if (header->destructor(ptr))
return -1;
int ret = 0;
//* TODO: implement reference and steal first
hieralloc_header_t * child = header->child;
while (child)
{
hieralloc_header_t * current = child;
assert(!current->prevSibling);
child = child->nextSibling;
if (hieralloc_free(current + 1))
{
ret = -1;
remove_from_parent(current);
add_to_parent(header->parent, current);
assert(0); // shouldn't happen since reference is always 1
}
}
if (ret)
return -1;
assert(0 == header->childCount);
assert(!header->child);
remove_from_parent(header);
memset(header, 0xfe, header->size + sizeof(*header));
#if CHECK_ALLOCATION
assert(allocations.find(ptr) != allocations.end());
allocations.erase(ptr);
// don't free yet to force allocations to new addresses for checking double freeing
#else
free(header);
#endif
return 0;
}
// not implemented from talloc_reference
void * hieralloc_reference(const void * ref_ctx, const void * ptr)
{
return (void *)ptr;
}
// not implemented from talloc_unlink
int hieralloc_unlink(const void * ctx, void *ptr)
{
if (!ptr)
return -1;
//assert(get_header(ptr)->refCount > 0);
//get_header(ptr)->refCount--;
return 0;
}
// moves allocation to new parent context; maintain children but update siblings
// returns ptr on success
void * hieralloc_steal(const void * new_ctx, const void * ptr)
{
hieralloc_header_t * header = get_header(ptr);
remove_from_parent(header);
add_to_parent(get_header(new_ctx), header);
return (void *)ptr;
}
// creates 0 allocation to be used as parent context
void * hieralloc_init(const char * name)
{
return hieralloc_allocate(NULL, 0, name);
}
// returns global context
void * hieralloc_autofree_context()
{
return &hieralloc_global_header + 1;
}
// sets destructor to be called before freeing; dctor return -1 aborts free
void hieralloc_set_destructor(const void * ptr, int (* destructor)(void *))
{
get_header(ptr)->destructor = destructor;
}
// gets parent context of allocated memory
void * hieralloc_parent(const void * ptr)
{
const hieralloc_header_t * header = get_header(ptr);
return header->parent + 1;
}
// allocate and zero memory
void * _hieralloc_zero(const void * ctx, unsigned size, const char * name)
{
void *p = hieralloc_allocate(ctx, size, name);
if (p)
memset(p, 0, size);
return p;
}
// allocate and copy
char * hieralloc_strndup(const void * ctx, const char * str, unsigned len)
{
if (!str)
return NULL;
const char *p = (const char *)memchr(str, '\0', len);
len = (p ? p - str : len);
char * ret = (char *)hieralloc_allocate(ctx, len + 1, str);
if (!ret)
return NULL;
memcpy(ret, str, len);
ret[len] = 0;
get_header(ret)->name = ret;
return ret;
}
// allocate and copy
char * hieralloc_strdup(const void * ctx, const char * str)
{
if (!str)
return NULL;
return hieralloc_strndup(ctx, str, strlen(str));
}
static char * _hieralloc_strlendup_append(char * str, unsigned len,
const char * append, unsigned appendLen)
{
//char * ret = hieralloc_allocate(str, sizeof(char) * (len + appendLen + 1), str);
//memcpy(ret, str, len);
hieralloc_header_t * header = get_header(str);
char * ret = (char *)hieralloc_reallocate(header->parent + 1, str, sizeof(char) * (len + appendLen + 1), str);
if (!ret)
return NULL;
memcpy(ret + len, append, appendLen);
ret[len + appendLen] = 0;
get_header(ret)->name = ret;
return ret;
}
// reallocate and append
char * hieralloc_strdup_append(char * str, const char * append)
{
if (!str)
return hieralloc_strdup(NULL, append);
if (!append)
return str;
return _hieralloc_strlendup_append(str, strlen(str), append, strlen(append));
}
// reallocate and append
char * hieralloc_strndup_append(char * str, const char * append, unsigned len)
{
if (!str)
return hieralloc_strdup(NULL, append);
if (!append)
return str;
const char *p = (const char *)memchr(append, '\0', len);
len = (p ? p - append : len);
return _hieralloc_strlendup_append(str, strlen(str), append, len);
}
// allocate and vsprintf
char * hieralloc_vasprintf(const void * ctx, const char * fmt, va_list va)
{
va_list va2;
va_copy(va2, va);
char c = 0;
int len = vsnprintf(&c, 1, fmt, va2); // count how many chars would be printed
va_end(va2);
assert(len >= 0); // some vsnprintf may return -1
if (len < 0)
return NULL;
char * ret = (char *)hieralloc_allocate(ctx, len + 1, fmt);
if (!ret)
return NULL;
va_copy(va2, va);
vsnprintf(ret, len + 1, fmt, va2);
va_end(va2);
get_header(ret)->name = ret;
return ret;
}
// allocate and sprintf
char * hieralloc_asprintf(const void * ctx, const char * fmt, ...)
{
va_list va;
va_start(va, fmt);
char * ret = hieralloc_vasprintf(ctx, fmt, va);
va_end(va);
return ret;
}
// reallocate and append vsprintf
char * hieralloc_vasprintf_append(char * str, const char * fmt, va_list va)
{
if (!str)
return hieralloc_vasprintf(NULL, fmt, va);
int len = strlen(str);
va_list va2;
va_copy(va2, va);
char c = 0;
int appendLen = vsnprintf(&c, 1, fmt, va2); // count how many chars would be printed
va_end(va2);
assert(appendLen >= 0); // some vsnprintf may return -1
if (appendLen < 0)
return str;
str = (char *)hieralloc_reallocate(NULL, str, sizeof(char) * (len + appendLen + 1), str);
if (!str)
return NULL;
va_copy(va2, va);
vsnprintf(str + len, appendLen + 1, fmt, va2);
va_end(va2);
get_header(str)->name = str;
return str;
}
// reallocate and append sprintf
char * hieralloc_asprintf_append(char * str, const char * fmt, ...)
{
va_list va;
va_start(va, fmt);
str = hieralloc_vasprintf_append(str, fmt, va);
va_end(va);
return str;
}
static void _hieralloc_report(const hieralloc_header_t * header, FILE * file, unsigned tab)
{
unsigned i = 0;
for (i = 0; i < tab; i++)
fputc(' ', file);
check_header(header);
fprintf(file, "%p: child=%d ref=%d size=%d dctor=%p name='%.256s' \n", header + 1,
header->childCount, header->refCount, header->size, header->destructor, header->name);
const hieralloc_header_t * child = header->child;
while (child)
{
_hieralloc_report(child, file, tab + 2);
child = child->nextSibling;
}
}
// report self and child allocations
void hieralloc_report(const void * ptr, FILE * file)
{
if (NULL == ptr)
ptr = &hieralloc_global_header + 1;
fputs("hieralloc_report: \n", file);
_hieralloc_report(get_header(ptr), file, 0);
}
static void _hieralloc_report_brief(const hieralloc_header_t * header, FILE * file, unsigned * data)
{
check_header(header);
data[0]++;
data[1] += header->size;
data[2] += header->childCount;
data[3] += header->refCount;
const hieralloc_header_t * child = header->child;
while (child)
{
_hieralloc_report_brief(child, file, data);
child = child->nextSibling;
}
}
void hieralloc_report_brief(const void * ptr, FILE * file)
{
if (NULL == ptr)
ptr = &hieralloc_global_header + 1;
unsigned data [4] = {0};
_hieralloc_report_brief(get_header(ptr), file, data);
fprintf(file, "hieralloc_report %p total: count=%d size=%d child=%d ref=%d \n",
ptr, data[0], data[1], data[2], data[3]);
}
void hieralloc_report_lineage(const void * ptr, FILE * file, int tab)
{
const hieralloc_header_t * header = get_header(ptr);
if (header->parent)
hieralloc_report_lineage(header->parent + 1, file, tab + 2);
for (tab; tab >=0; tab--)
fputc(' ', file);
fprintf(file, "hieralloc_report_lineage %p: size=%d child=%d ref=%d name='%s' parent=%p \n",
ptr, header->size, header->childCount, header->refCount, header->name, header->parent ? header->parent + 1 : NULL);
}
int hieralloc_find(const void * top, const void * ptr, FILE * file, int tab)
{
int found = 0;
if (NULL == top)
top = &hieralloc_global_header + 1;
if (0 == tab && file)
fputs("hieralloc_find start \n", file);
if (top == ptr)
{
if (file)
fprintf(file, "hieralloc_found %p \n", ptr);
found++;
}
const hieralloc_header_t * start = get_header(top);
const hieralloc_header_t * child = start->child;
while (child)
{
found += hieralloc_find(child + 1, ptr, file, tab + 2);
child = child->nextSibling;
}
if (0 == tab && file)
fprintf(file, "hieralloc_find end found=%d \n", found);
return found;
}
#ifdef __cplusplus
} // extern "C"
#endif