diff options
Diffstat (limited to 'src/kash/shheap.c')
-rw-r--r-- | src/kash/shheap.c | 596 |
1 files changed, 596 insertions, 0 deletions
diff --git a/src/kash/shheap.c b/src/kash/shheap.c new file mode 100644 index 0000000..cc481fb --- /dev/null +++ b/src/kash/shheap.c @@ -0,0 +1,596 @@ +/* $Id: shheap.c 3477 2020-09-17 21:52:16Z bird $ */ +/** @file + * The shell memory heap methods. + */ + +/* + * Copyright (c) 2009-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net> + * + * + * This file is part of kBuild. + * + * kBuild 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. + * + * kBuild 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 kBuild; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "shheap.h" +#include <string.h> +#include <stdlib.h> +#include "shinstance.h" + +#if K_OS == K_OS_WINDOWS && defined(SH_FORKED_MODE) +# define SHHEAP_IN_USE +#endif + +#ifdef SHHEAP_IN_USE +# if K_OS == K_OS_WINDOWS +# include <Windows.h> +# else +# include <unistd.h> +# endif +#endif + + +/******************************************************************************* +* Structures and Typedefs * +*******************************************************************************/ +#ifdef SHHEAP_IN_USE +/** + * heap memory block header. + */ +typedef struct shmemhdr +{ + size_t magic; /**< Magic value */ + size_t size; /**< The block size */ + struct shmemhdr *next; /**< Forward pointer. */ + struct shmemhdr *prev; /**< Backward pointer. */ + struct shmemhdr *next2; /**< Free/Shell list forward. */ + struct shmemhdr *prev2; /**< Free/Shell list backward. */ + struct shinstance *psh; /**< The shell who allocated it. */ + struct shmemchunk *chunk; /**< The chunk who owns this. */ +} shmemhdr; + +/** Free block magic (shmemhdr::magic) */ +#define SHMEMHDR_MAGIC_FREE 0xbeeff00d +/** Used block magic (shmemhdr::magic) */ +#define SHMEMHDR_MAGIC_USED 0xfeedface + +typedef struct shmemchunk +{ + struct shmemhdr *head; /**< Head of the block list. */ + struct shmemhdr *free_head; /**< Head of the free list. */ + struct shmemchunk *next; /**< The next block. */ + struct shmemchunk *prev; /**< The previous block. */ + size_t size; /**< Chunk size. */ + size_t magic; /**< Magic value. */ + size_t padding0; + size_t padding1; +} shmemchunk; + +/** shmemchunk::magic */ +#define SHMEMCHUNK_MAGIC 0x12345678 + +#endif /* K_OS_WINDOWS */ + + +/******************************************************************************* +* Defined Constants And Macros * +*******************************************************************************/ +#define SHHEAP_ALIGN(sz) (((sz) + 31) & ~(size_t)31) +#define SHHEAP_CHUNK_ALIGN(sz) (((sz) + 0xffff) & ~(size_t)0xffff) +#define SHHEAP_MIN_CHUNK 0x80000 //(1024*1024) +#ifdef NDEBUG +# define SHHEAP_CHECK() do { } while (0) +# define SHHEAP_CHECK_2() do { } while (0) +# define SHHEAP_ASSERT(expr) do { } while (0) +# define SHHEAP_POISON_PSH(p,v) (p) +# define SHHEAP_POISON_NULL(v) NULL +#else +# define SHHEAP_CHECK() shheap_check() +# define SHHEAP_CHECK_2() shheap_check() +# define SHHEAP_ASSERT(expr) kHlpAssert(expr) +# define SHHEAP_POISON_PSH(p,v) ((shinstance *)(v)) +# define SHHEAP_POISON_NULL(v) ((void *)(v)) +#endif + + +/******************************************************************************* +* Global Variables * +*******************************************************************************/ +#ifdef SHHEAP_IN_USE +/** The heap lock. */ +static shmtx g_sh_heap_mtx; +/** The heap. + * This is a list of chunks. */ +static shmemchunk *g_sh_heap; +#endif + + +int shheap_init(void *phead) +{ + int rc; +#ifdef SHHEAP_IN_USE + SHHEAP_ASSERT(SHHEAP_ALIGN(sizeof(shmemhdr)) == sizeof(shmemhdr)); + rc = shmtx_init(&g_sh_heap_mtx); + g_sh_heap = (shmemchunk *)phead; /* non-zero on fork() */ +#else + rc = 0; +#endif + return rc; +} + +#ifdef SHHEAP_IN_USE + +# if K_OS == K_OS_WINDOWS + +/** + * Get the head so the child can pass it to shheap_init() after fork(). + * + * @returns g_sh_heap. + */ +void *shheap_get_head(void) +{ + return g_sh_heap; +} + +/** + * Copies the heap into the child process. + * + * @returns 0 on success, -1 and errno on failure. + * @param hChild Handle to the child process. + */ +int shheap_fork_copy_to_child(void *hChild) +{ + shmemchunk *chunk; + shmtxtmp tmp; + int err = 0; + + shmtx_enter(&g_sh_heap_mtx, &tmp); + + for (chunk = g_sh_heap; chunk; chunk = chunk->next) + { + void *chld_chnk; + + chld_chnk = (shmemchunk *)VirtualAllocEx(hChild, chunk, chunk->size, + MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (chld_chnk != chunk) + { + err = GetLastError(); + fprintf(stderr, "shfork: VirtualAllocEx(,%p,%p,) -> %p/%d\n", chunk, chunk->size, chld_chnk, err); + break; + } + + if (!WriteProcessMemory(hChild, chunk, chunk, chunk->size, NULL /* pNumberOfBytesWritten */)) + { + err = GetLastError(); + fprintf(stderr, "shfork: WriteProcessMemory(,%p,,%p,) -> %d\n", chunk, chunk->size, err); + break; + } + } + + shmtx_leave(&g_sh_heap_mtx, &tmp); + + if (!err) + return 0; + errno = EINVAL; + return -1; +} + +# endif /* K_OS == K_OS_WINDOWS */ + +/** + * Checks a heap chunk. + * @param chunk The chunk to check. + */ +static void shheap_check_chunk(shmemchunk *chunk) +{ + size_t free_count; + struct shmemhdr *mem; + struct shmemhdr *prev; + + SHHEAP_ASSERT(chunk->magic == SHMEMCHUNK_MAGIC); + SHHEAP_ASSERT(chunk->head); + SHHEAP_ASSERT(chunk->size == SHHEAP_CHUNK_ALIGN(chunk->size)); + + free_count = 0; + prev = NULL; + for (mem = chunk->head; mem; mem = mem->next) + { + size_t size = (mem->next ? (char *)mem->next : (char *)chunk + chunk->size) - (char *)(mem + 1); + SHHEAP_ASSERT(mem->size == size); + SHHEAP_ASSERT(mem->prev == prev); + if (mem->magic == SHMEMHDR_MAGIC_FREE) + free_count++; + else + SHHEAP_ASSERT(mem->magic == SHMEMHDR_MAGIC_USED); + prev = mem; + } + + prev = NULL; + for (mem = chunk->free_head; mem; mem = mem->next2) + { + size_t size = (mem->next ? (char *)mem->next : (char *)chunk + chunk->size) - (char *)(mem + 1); + SHHEAP_ASSERT(mem->size == size); + SHHEAP_ASSERT(mem->prev2 == prev); + SHHEAP_ASSERT(mem->magic == SHMEMHDR_MAGIC_FREE); + free_count--; + prev = mem; + } + SHHEAP_ASSERT(free_count == 0); +} + +/** + * Checks the heap. + */ +static void shheap_check(void) +{ + shmemchunk *chunk; + for (chunk = g_sh_heap; chunk; chunk = chunk->next) + shheap_check_chunk(chunk); +} + +/** + * Grows the heap with another chunk carving out a block + * + * @returns Pointer to a used entry of size @a size1. NULL + * if we're out of memory + * @param size1 The size of the block to be returned (aligned). + */ +static shmemhdr *shheap_grow(size_t size1) +{ + shmemchunk *chunk; + shmemhdr *used; + shmemhdr *avail; + size_t chunk_size; + + /* Calc the chunk size and allocate it. */ + chunk_size = SHHEAP_ALIGN(size1) + SHHEAP_ALIGN(sizeof(*chunk)) + SHHEAP_ALIGN(sizeof(*used)) * 10; + if (chunk_size < SHHEAP_MIN_CHUNK) + chunk_size = SHHEAP_MIN_CHUNK; + else + chunk_size = SHHEAP_CHUNK_ALIGN(chunk_size); + +# if K_OS == K_OS_WINDOWS + chunk = (shmemchunk *)VirtualAlloc(NULL, chunk_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); +# else + chunk = NULL; +# endif + + if (!chunk) + return NULL; + + used = (shmemhdr *)((char *)chunk + SHHEAP_ALIGN(sizeof(*chunk))); + avail = (shmemhdr *)((char *)(used + 1) + size1); + + used->magic = SHMEMHDR_MAGIC_USED; + used->size = size1; + used->next = avail; + used->prev = NULL; + used->next2 = SHHEAP_POISON_NULL(0x41); + used->prev2 = SHHEAP_POISON_NULL(0x41); + used->psh = NULL; + used->chunk = chunk; + + avail->magic = SHMEMHDR_MAGIC_FREE; + avail->size = (char *)chunk + chunk_size - (char *)(avail + 1); + avail->next = NULL; + avail->prev = used; + avail->next2 = NULL; + avail->prev2 = NULL; + avail->psh = NULL; + avail->chunk = chunk; + + chunk->head = used; + chunk->free_head = avail; + chunk->size = chunk_size; + chunk->magic = SHMEMCHUNK_MAGIC; + chunk->prev = NULL; + chunk->next = g_sh_heap; + if (g_sh_heap) + g_sh_heap->prev = chunk; + g_sh_heap = chunk; + chunk->padding0 = 0; + chunk->padding1 = 0; + + SHHEAP_CHECK_2(); + return used; +} + +/*** + * Splits a big memory block into two smaller, one with the + * size @a size1. + * + * The one with the given size is removed from the free list + * while the other one remains there. + * + * @returns The @a size1 sized block, NULL on failure. + * @param big The block that is too big. + * @param size1 The size of the block to be returned (aligned). + */ +static shmemhdr *shheap_split(shmemhdr *big, size_t size1) +{ + shmemhdr *split; + SHHEAP_ASSERT(SHHEAP_ALIGN(sizeof(*big)) == sizeof(*big)); + SHHEAP_ASSERT(big->magic == SHMEMHDR_MAGIC_FREE); + SHHEAP_ASSERT(!big->next2 || big->next2->magic == SHMEMHDR_MAGIC_FREE); + SHHEAP_ASSERT(!big->prev2 || big->prev2->magic == SHMEMHDR_MAGIC_FREE); + + split = (shmemhdr *)((uint8_t *)(big + 1) + size1); + split->magic = SHMEMHDR_MAGIC_FREE; + split->size = big->size - size1 - sizeof(*split); + split->next = big->next; + split->prev = big; + split->next2 = big->next2; + split->prev2 = big->prev2; + split->psh = SHHEAP_POISON_NULL(0x54); + split->chunk = big->chunk; + + if (big->next2) + big->next2->prev2 = split; + if (big->prev2) + big->prev2->next2 = split; + else + big->chunk->free_head = split; + + big->magic = SHMEMHDR_MAGIC_USED; + big->next2 = big->prev2 = SHHEAP_POISON_NULL(0x41); + + if (big->next) + big->next->prev = split; + big->next = split; + big->size = size1; + + SHHEAP_CHECK_2(); + return big; +} + +/*** + * Unlinks a free memory block. + * @param mem The block to unlink. + */ +static void shheap_unlink_free(shmemhdr *mem) +{ + if (mem->next2) + mem->next2->prev2 = mem->prev2; + if (mem->prev2) + mem->prev2->next2 = mem->next2; + else + mem->chunk->free_head = mem->next2; + mem->magic = SHMEMHDR_MAGIC_USED; + mem->next2 = mem->prev2 = SHHEAP_POISON_NULL(0x42); +} + +#endif /* SHHEAP_IN_USE */ + + +/** free() */ +void sh_free(shinstance *psh, void *ptr) +{ +#ifdef SHHEAP_IN_USE + shmemhdr *mem; + shmemhdr *right; + shmemhdr *left; + shmtxtmp tmp; + + if (ptr) + mem = (shmemhdr *)ptr - 1; + else + return; + + if (mem->magic != SHMEMHDR_MAGIC_USED) + { + SHHEAP_ASSERT(0); + return; + } + + shmtx_enter(&g_sh_heap_mtx, &tmp); + SHHEAP_CHECK(); + + /* join right. */ + right = mem->next; + if ( right + && right->magic == SHMEMHDR_MAGIC_FREE) + { + mem->next = right->next; + if (right->next) + right->next->prev = mem; + + mem->next2 = right->next2; + if (right->next2) + right->next2->prev2 = mem; + mem->prev2 = right->prev2; + if (right->prev2) + mem->prev2->next2 = mem; + else + mem->chunk->free_head = mem; + + mem->size += sizeof(*right) + right->size; + mem->magic = SHMEMHDR_MAGIC_FREE; + right->magic = ~SHMEMHDR_MAGIC_FREE; + mem->psh = SHHEAP_POISON_NULL(0x50); + SHHEAP_CHECK_2(); + } + + /* join left */ + left = mem->prev; + if ( left + && left->magic == SHMEMHDR_MAGIC_FREE) + { + left->next = mem->next; + if (mem->next) + mem->next->prev = left; + + if (mem->magic == SHMEMHDR_MAGIC_FREE) + { + if (mem->next2) + mem->next2->prev2 = mem->prev2; + if (mem->prev2) + mem->prev2->next2 = mem->next2; + else + mem->chunk->free_head = mem->next2; + } + + left->size += sizeof(*mem) + mem->size; + mem->magic = ~SHMEMHDR_MAGIC_USED; + left->psh = SHHEAP_POISON_NULL(0x51); + } + + /* insert as free if necessary */ + else if (mem->magic == SHMEMHDR_MAGIC_USED) + { + mem->prev2 = NULL; + mem->next2 = mem->chunk->free_head; + if (mem->chunk->free_head) + mem->chunk->free_head->prev2 = mem; + mem->chunk->free_head = mem; + mem->magic = SHMEMHDR_MAGIC_FREE; + mem->psh = SHHEAP_POISON_NULL(0x52); + } + + SHHEAP_CHECK(); + shmtx_leave(&g_sh_heap_mtx, &tmp); +#else + if (ptr) + free(ptr); + (void)psh; +#endif +} + +/** malloc() */ +void *sh_malloc(shinstance *psh, size_t size) +{ +#ifdef SHHEAP_IN_USE + shmemchunk *chunk; + shmemhdr *mem; + shmtxtmp tmp; + + size = SHHEAP_ALIGN(size); + SHHEAP_ASSERT(size); + if (!size) + size = SHHEAP_ALIGN(1); + + shmtx_enter(&g_sh_heap_mtx, &tmp); + SHHEAP_CHECK(); + + + /* Search for fitting block */ + mem = NULL; + chunk = g_sh_heap; + while (chunk) + { + mem = chunk->free_head; + while (mem && mem->size < size) + mem = mem->next2; + if (mem) + break; + chunk = chunk->next; + } + if (mem) + { + /* split it, or just unlink it? */ + if (mem->size - size > sizeof(*mem) * 2) + mem = shheap_split(mem, size); + else + shheap_unlink_free(mem); + } + else + { + /* no block found, try grow the heap. */ + mem = shheap_grow(size); + if (!mem) + { + shmtx_leave(&g_sh_heap_mtx, &tmp); + return NULL; + } + } + + SHHEAP_CHECK(); + shmtx_leave(&g_sh_heap_mtx, &tmp); + + mem->psh = SHHEAP_POISON_PSH(psh, 0x53); + + return mem + 1; + +#else + (void)psh; + return malloc(size); +#endif +} + +/** calloc() */ +void *sh_calloc(shinstance *psh, size_t num, size_t item_size) +{ +#ifdef SHHEAP_IN_USE + size_t size = num * item_size; + void *pv = sh_malloc(psh, size); + if (pv) + pv = memset(pv, '\0', size); + return pv; +#else + (void)psh; + return calloc(num, item_size); +#endif +} + +/** realloc() */ +void *sh_realloc(shinstance *psh, void *old, size_t new_size) +{ +#ifdef SHHEAP_IN_USE + void *pv; + if (new_size) + { + if (old) + { + shmemhdr *hdr = (shmemhdr *)old - 1; + if (hdr->size < new_size) + { + pv = sh_malloc(psh, new_size); + if (pv) + { + memcpy(pv, old, hdr->size); + sh_free(psh, old); + } + } + else + pv = old; + } + else + pv = sh_malloc(psh, new_size); + } + else + { + sh_free(psh, old); + pv = NULL; + } + return pv; +#else + return realloc(old, new_size); +#endif +} + +/** strdup() */ +char *sh_strdup(shinstance *psh, const char *string) +{ + size_t len = strlen(string); + char *ret = sh_malloc(psh, len + 1); + if (ret) + memcpy(ret, string, len + 1); + return ret; +} + + |