Blob Blame History Raw
/*****************************************************************************/
/*  dalist: a zero-dependency book-keeping library                           */
/*  Copyright (C) 2013--2021  SysDeer Technologies, LLC                      */
/*  Released under GPLv2 and GPLv3; see COPYING.DALIST.                      */
/*****************************************************************************/

#include <dalist/dalist.h>
#include "dalist_impl.h"

/* private prototypes */
static void * dalist_alloc(struct dalist_ex * dlist);

static int dalist_memfn_internal(
	struct dalist_ex *	dlist,
	void **			addr,
	size_t *		alloc_size);


dalist_api
int dalist_get_free_node(
	struct dalist_ex *	dlist,
	void **			fnode)
{
	int			ret;
	struct dalist_node *	nfree;

	/* in case we fail */
	*fnode = (void *)0;

	if (dlist->free)
		/* allocation is not needed */
		nfree  = (struct dalist_node *)dlist->free;
	else
		nfree = (struct dalist_node *)dalist_alloc(dlist);

	if (nfree) {
		*fnode = nfree;
		dlist->free = nfree->next;

		if (nfree->next) /* new head of dlist->free */
			nfree->next->prev = 0;

		/* bookkeeping */
		dlist->info.free_nodes--;

		/* and for good measure */
		nfree->prev = nfree->next = 0;
		ret = DALIST_OK;
	} else
		ret = DALIST_EMEMFN;

	return ret;
}


dalist_api
int dalist_deposit_free_node(
	struct dalist_ex *	dlist,
	void *			fnode)
{
	int			ret;
	struct dalist_node *	nfree;

	if (fnode) {
		nfree       = (struct dalist_node *)fnode;
		nfree->next = (struct dalist_node *)dlist->free;

		/* fnode is now the head free node */
		nfree->prev = 0;
		dlist->free = fnode;

		/* bookkeeping */
		dlist->info.free_nodes++;
		ret = DALIST_OK;
	} else {
		ret = DALIST_ENODE;
	}

	return ret;
}


dalist_api
int dalist_deposit_memory_block(
	struct dalist_ex *	dlist,
	void *			addr,
	size_t			alloc_size)
{
	struct dalist_node *	fnode;
	struct dalist_node *	fnode_next;
	char *			naddr;
	char *			naddr_next;
	char *			naddr_upper;
	size_t			node_size;

	/* node_size: before alignment */
	if (dlist->dblock_size == 0)
		node_size = sizeof(struct dalist_node);
	else
		node_size = (size_t)&((struct dalist_node_ex *)0)->dblock
				+ dlist->dblock_size;

	/* node_size: after alignment */
	node_size += sizeof(uintptr_t)-1;
	node_size |= sizeof(uintptr_t)-1;
	node_size ^= sizeof(uintptr_t)-1;

	/* validate block size */
	if (alloc_size < node_size)
		return DALIST_EMEMBLOCK;

	/* marks */
	naddr       = (char *)addr;
	naddr_next  = naddr + node_size;
	naddr_upper = naddr + alloc_size;

	/* chain of free nodes */
	while ((naddr_next + node_size) <= naddr_upper) {
		fnode      = (struct dalist_node *)naddr;
		fnode_next = (struct dalist_node *)naddr_next;

		fnode->next      = fnode_next;
		fnode_next->prev = fnode;

		naddr      += node_size;
		naddr_next += node_size;
	}

	/* free nodes block: head and tail */
	fnode       = (struct dalist_node *)addr;
	fnode_next  = (struct dalist_node *)naddr;

	if (dlist->free) {
		/* link with pre-allocated free nodes */
		fnode_next->next = (struct dalist_node *)dlist->free;
		((struct dalist_node *)dlist->free)->prev = fnode_next;
	} else {
		/* no pre-allocated free nodes */
		fnode_next->next = 0;
	}

	/* fnode is now the head free node */
	fnode->prev = 0;
	dlist->free = fnode;

	/* bookkeeping */
	dlist->info.free_nodes += (alloc_size / node_size);

	return DALIST_OK;
}


static void * __cdecl dalist_alloc(struct dalist_ex * dlist)
{
	int			ret;
	void *			addr;
	size_t			alloc_size;
	memfn_custom *		pmemfn;
	struct dalist_node *	fnode;

	/* pmemfn */
	if (dlist->memfn_method == DALIST_MEMFN_CUSTOM)
		pmemfn = (dalist_memfn_custom *)(uintptr_t)dlist->memfn_ptr;
	else
		pmemfn = dalist_memfn_internal;

	/* allocate: try */
	addr  = 0;
	fnode = 0;
	ret   = pmemfn(dlist,&addr,&alloc_size);

	/* allocate: verify */
	if (ret == DALIST_OK) {
		if (dlist->memfn_method == DALIST_MEMFN_MALLOC)
			fnode = (struct dalist_node *)addr;
		else if (dalist_deposit_memory_block(
				dlist,
				addr,
				alloc_size) == DALIST_OK)
			fnode = (struct dalist_node *)dlist->free;
	}

	return fnode;
}


static int __cdecl dalist_memfn_internal(
	struct dalist_ex *	dlist,
	void **			address,
	size_t *		alloc_size)
{
	int		ret;
	void *		addr;
	size_t		size;

	memfn_mmap *	pfn_mmap;
	memfn_malloc *	pfn_malloc;
	memfn_allocvm * pfn_allocvm;

	switch (dlist->memfn_method) {
		case DALIST_MEMFN_MALLOC:
			if (dlist->dblock_size == 0)
				size = sizeof(struct dalist_node);
			else
				size = (size_t)((struct dalist_node_ex *)0)->dblock
					+ dlist->dblock_size;

			pfn_malloc = (memfn_malloc *)(uintptr_t)dlist->memfn_ptr;
			addr = pfn_malloc(size);

			if (addr) {
				*address = addr;
				*alloc_size = size;
				ret = dlist->memfn_status = DALIST_OK;
			} else {
				ret = DALIST_EMEMFN;
				dlist->memfn_status = dalist_errno(ret);
			}
			break;

		case DALIST_MEMFN_MMAP:
			pfn_mmap = (memfn_mmap * )(uintptr_t)dlist->memfn_ptr;

			addr = pfn_mmap(
				(void *)0,
				dlist->lblock_size,
				PROT_READ | PROT_WRITE,
				MAP_ANON | MAP_SHARED,
				0,0);

			if (addr) {
				*address = addr;
				*alloc_size = dlist->lblock_size;
				ret = dlist->memfn_status = DALIST_OK;
			} else {
				ret = DALIST_EMEMFN;
				dlist->memfn_status = dalist_errno(ret);
			}
			break;

		case DALIST_MEMFN_NT_ALLOCATE_VIRTUAL_MEMORY:
			addr       = (void *)0;
			size = dlist->lblock_size;
			pfn_allocvm = (memfn_allocvm *)(uintptr_t)dlist->memfn_ptr;

			dlist->memfn_status = pfn_allocvm(
				NT_CURRENT_PROCESS_HANDLE,
				&addr,
				0,
				&size,
				NT_MEM_COMMIT,
				NT_PAGE_READWRITE);

			if (dlist->memfn_status == NT_STATUS_SUCCESS) {
				*address = addr;
				*alloc_size = size;
				ret = DALIST_OK;
			} else {
				ret = DALIST_EMEMFN;
			}
			break;
		default:
			ret = DALIST_EINTERNAL;
			break;
	}

	return ret;
}