blob: 65801b264694c213f148dbe6503d97851d64ef46 [file] [log] [blame]
/* libnih
*
* alloc.c - hierarchial allocator
*
* Copyright © 2006 Scott James Remnant <scott@netsplit.com>.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <stdlib.h>
#include <nih/macros.h>
#include <nih/logging.h>
#include <nih/list.h>
#include "alloc.h"
/**
* NihAllocCtx:
* @entry: list header,
* @size: size of requested allocation,
* @parent: parent context, when freed we will be,
* @children: child blocks that will be freed when we are,
* @allocator: function to call to return memory,
* @destructor: function to be called when freed.
*
* This structure is placed before all allocations in memory and is used
* to build up a tree of them. When an allocation is freed, all children
* are also freed and any destructors are called.
**/
typedef struct nih_alloc_ctx {
NihList entry;
size_t size;
struct nih_alloc_ctx *parent;
NihList children;
NihAllocator allocator;
NihDestructor destructor;
} NihAllocCtx;
/**
* NIH_ALLOC_CTX:
* @ptr: pointer to block of memory.
*
* Obtain the location of the #NihAllocCtx structure given a pointer to the
* block of memory beyond it.
*
* Returns: pointer to #NihAllocCtx structure.
**/
#define NIH_ALLOC_CTX(ptr) ((NihAllocCtx *)(ptr) - 1)
/**
* NIH_ALLOC_PTR:
* @ctx: pointer to #NihAllocCtx structure.
*
* Obtain the location of the block of memory given a pointer to the
* #NihAllocCtx structure in front of it.
*
* Returns: pointer to block of memory.
**/
#define NIH_ALLOC_PTR(ctx) ((void *)((NihAllocCtx *)(ctx) + 1))
/**
* allocator:
*
* Function used to allocate and free memory for the majority of blocks.
**/
static NihAllocator allocator = NULL;
/**
* nih_alloc_init:
*
* Initialise the default allocator.
**/
static inline void
nih_alloc_init (void)
{
if (! allocator)
nih_alloc_set_allocator (realloc);
}
/**
* nih_alloc_set_allocator:
* @new_allocator: new default allocator function.
*
* Sets the function that will be used to allocate memory for all further
* blocks requested and return it to the system. The behaviour of the
* function should be the same of that as the standard #realloc function.
*
* This function should generally only be used in the initialisation
* portion of your program, and should not be used to switch allocators
* temporarily. Use #nih_alloc_using to allocate a block with an
* alternate allocator.
**/
void
nih_alloc_set_allocator (NihAllocator new_allocator)
{
nih_assert (new_allocator != NULL);
allocator = new_allocator;
}
/**
* nih_alloc_using:
* @allocator: allocator to use for this block,
* @parent: parent block of allocation,
* @size: size of requested block.
*
* Allocates a block of memory of at least @size bytes with the @allocator
* function and returns a pointer to it.
*
* If @parent is not %NULL, it should be a pointer to another allocated
* block which will be used as the parent for this block. When @parent
* is freed, the returned block will be freed too. If you have clean-up
* that would need to be run, you can assign a destructor function using
* the #nih_alloc_set_destructor function.
*
* Returns: requested memory block or %NULL if allocation fails.
**/
void *
nih_alloc_using (NihAllocator allocator,
const void *parent,
size_t size)
{
NihAllocCtx *ctx;
nih_assert (allocator != NULL);
ctx = allocator (NULL, sizeof (NihAllocCtx) + size);
if (! ctx)
return NULL;
ctx->size = size;
nih_list_init (&ctx->entry);
nih_list_init (&ctx->children);
ctx->allocator = allocator;
ctx->destructor = NULL;
if (parent) {
ctx->parent = NIH_ALLOC_CTX (parent);
nih_list_add (&ctx->parent->children, &ctx->entry);
} else {
ctx->parent = NULL;
}
return NIH_ALLOC_PTR (ctx);
}
/**
* nih_alloc:
* @parent: parent block of allocation,
* @size: size of requested block.
*
* Allocates a block of memory of at least @size bytes and returns
* a pointer to it.
*
* If @parent is not %NULL, it should be a pointer to another allocated
* block which will be used as the parent for this block. When @parent
* is freed, the returned block will be freed too. If you have clean-up
* that would need to be run, you can assign a destructor function using
* the #nih_alloc_set_destructor function.
*
* Returns: requested memory block or %NULL if allocation fails.
**/
void *
nih_alloc (const void *parent,
size_t size)
{
nih_alloc_init ();
return nih_alloc_using (allocator, parent, size);
}
/**
* nih_realloc:
* @ptr: block to reallocate or %NULL,
* @parent: parent block of allocation,
* @size: size of new block.
*
* Adjusts the size of the block of memory allocated for @ptr to be at
* least @size bytes and returns the new pointer. If @ptr is %NULL then
* this is equivalent to %nih_alloc.
*
* If @parent is not %NULL, it must be the same object as the parent to
* @ptr, unless that is also %NULL in which case it should be a pointer to
* another block which will be used as the parent for the newly allocated
* block. When @parent is freed, the returned block will be freed too.
* If you have clean-up that would need to be run, you can assign a
* destructor function using the #nih_alloc_set_destructor function.
*
* Returns: reallocated block or %NULL if reallocation fails.
**/
void *
nih_realloc (void *ptr,
const void *parent,
size_t size)
{
NihAllocCtx *ctx;
NihList *first_child = NULL;
if (! ptr)
return nih_alloc (parent, size);
ctx = NIH_ALLOC_CTX (ptr);
if (parent)
nih_assert (ctx->parent == NIH_ALLOC_CTX (parent));
/* This is somewhat more difficult than alloc or free because we
* have a tree of pointers to worry about. Fortunately the
* properties of the nih_list_* functions we use help us a lot here.
*
* The problem is that references in the parent to this ptr's list
* entry or references in children to this ptr's children list
* entry are invalid once the allocator has been called.
*
* We could strip it all down before the allocator, then rebuild it
* afterwards, but that's expensive and could be error-prone in the
* case where the allocator fails.
*
* The solution is to rely on a property of nih_list_add; the entry
* passed (to be added) is cut out of its containing list without
* deferencing the return pointers, this means we can cut the bad
* pointers out simply by calling nih_list_add to put the new
* entry back in the same position.
*
* Of course, this only works in the non-empty list case as trying
* to cut an entry out of an empty list would deference those
* invalid pointers. Happily all we need to do for the non-empty
* list case is call nih_list_init again.
*
* For the primary list entry this is if we don't have a parent.
* For the children list entry, this is if we don't have children,
* which is difficult to detect after the allocator has been
* called. The easiest thing to do is stash a pointer to the first
* child, if non-NULL then we use it for nih_list_add, if NULL then
* we call nih_list_init.
*/
if (! NIH_LIST_EMPTY (&ctx->children))
first_child = ctx->children.next;
/* Now we do the actual realloc(), if this fails then the original
* structure is still intact in memory so we can just return NULL
*/
ctx = ctx->allocator (ctx, sizeof (NihAllocCtx) + size);
if (! ctx)
return NULL;
ctx->size = size;
/* Either update our entry in our parent's list of children,
* or reinitialise the list entry so it doesn't point to nowhere.
*/
if (ctx->parent) {
nih_list_add (&ctx->parent->children, &ctx->entry);
} else {
nih_list_init (&ctx->entry);
}
/* Likewise update the head entry in our own list of children,
* or reinitialise it so it doesn't point to nowhere.
*/
if (first_child) {
nih_list_add (first_child, &ctx->children);
} else {
nih_list_init (&ctx->children);
}
/* Finally fix up the parent pointer in all of our children so they
* point to our new location
*/
NIH_LIST_FOREACH (&ctx->children, iter) {
NihAllocCtx *child_ctx = (NihAllocCtx *)iter;
child_ctx->parent = ctx;
}
return NIH_ALLOC_PTR (ctx);
}
/**
* nih_free:
* @ptr: pointer to block to free.
*
* Return the block of memory at @ptr to the allocator so it may be
* re-used by something else. Any children of the block are also freed,
* and any destructors called.
*
* Returns: return value from destructor, or 0.
**/
int
nih_free (void *ptr)
{
NihAllocCtx *ctx;
int ret = 0;
nih_assert (ptr != NULL);
ctx = NIH_ALLOC_CTX (ptr);
NIH_LIST_FOREACH_SAFE (&ctx->children, iter) {
void *ptr;
ptr = NIH_ALLOC_PTR (iter);
ret = nih_free (ptr);
}
if (ctx->destructor)
ret = ctx->destructor (ptr);
nih_list_remove (&ctx->entry);
ctx->allocator (ctx, 0);
return ret;
}
/**
* nih_alloc_set_destructor:
* @ptr: pointer to block,
* @destructor: destructor function to set.
*
* Sets the destructor function of the block to @destructor, which may be
* %NULL.
*
* The destructor function will be called when the block is freed, either
* directly or as a result as a parent being freed. The block will be
* passed as a pointer to the destructor, and the destructor may return
* a value which will be the return value of the #nih_free function.
**/
void
nih_alloc_set_destructor (void *ptr,
NihDestructor destructor)
{
NihAllocCtx *ctx;
nih_assert (ptr != NULL);
ctx = NIH_ALLOC_CTX (ptr);
ctx->destructor = destructor;
}
/**
* nih_alloc_size:
* @ptr: pointer to block.
*
* Returns: the size of the allocated block, excluding the context.
**/
size_t
nih_alloc_size (const void *ptr)
{
NihAllocCtx *ctx;
nih_assert (ptr != NULL);
ctx = NIH_ALLOC_CTX (ptr);
return ctx->size;
}
/**
* nih_alloc_parent:
* @ptr: pointer to block.
*
* Returns: the parent block or %NULL if none.
**/
void *
nih_alloc_parent (const void *ptr)
{
NihAllocCtx *ctx;
nih_assert (ptr != NULL);
ctx = NIH_ALLOC_CTX (ptr);
if (ctx->parent) {
return NIH_ALLOC_PTR (ctx->parent);
} else {
return NULL;
}
}