/*
 * Copyright (c) 1999-2006 Christophe Fillot.
 * E-mail: cf@utc.fr
 *
 * mempool.c: Simple Memory Pools.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <time.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>

#include "utils.h"
#include "mempool.h"

/* 
 * Internal function used to allocate a memory block, and do basic operations
 * on it. It does not manipulate pools, so no mutex is needed.
 */
static inline memblock_t *memblock_alloc (size_t size, int zeroed)
{
    memblock_t *block;
    size_t total_size;

    total_size = size + sizeof (memblock_t);
    if (!(block = malloc (total_size)))
        return NULL;

    if (zeroed)
        memset (block, 0, total_size);

    block->tag = MEMBLOCK_TAG;
    block->block_size = size;
    block->prev = block->next = NULL;
    return block;
}

/* Insert block in linked list */
static inline void memblock_insert (mempool_t * pool, memblock_t * block)
{
    MEMPOOL_LOCK (pool);

    pool->nr_blocks++;
    pool->total_size += block->block_size;

    block->prev = NULL;
    block->next = pool->block_list;

    if (block->next)
        block->next->prev = block;

    pool->block_list = block;

    MEMPOOL_UNLOCK (pool);
}

/* Remove block from linked list */
static inline void memblock_delete (mempool_t * pool, memblock_t * block)
{
    MEMPOOL_LOCK (pool);

    pool->nr_blocks--;
    pool->total_size -= block->block_size;

    if (!block->prev)
        pool->block_list = block->next;
    else
        block->prev->next = block->next;

    if (block->next)
        block->next->prev = block->prev;

    block->next = block->prev = NULL;
    MEMPOOL_UNLOCK (pool);
}

/* Allocate a new block in specified pool (internal function) */
static inline void *mp_alloc_inline (mempool_t * pool, size_t size,
    int zeroed)
{
    memblock_t *block;

    if (!(block = memblock_alloc (size, zeroed)))
        return NULL;

    block->pool = pool;
    memblock_insert (pool, block);
    return (block->data);
}

/* Allocate a new block in specified pool */
void *mp_alloc (mempool_t * pool, size_t size)
{
    return (mp_alloc_inline (pool, size, TRUE));
}

/* Allocate a new block which will not be zeroed */
void *mp_alloc_n0 (mempool_t * pool, size_t size)
{
    return (mp_alloc_inline (pool, size, FALSE));
}

/* Reallocate a block */
void *mp_realloc (void *addr, size_t new_size)
{
    memblock_t *ptr, *block = (memblock_t *) addr - 1;
    mempool_t *pool;
    size_t total_size;

    assert (block->tag == MEMBLOCK_TAG);
    pool = block->pool;

    /* remove this block from list */
    memblock_delete (pool, block);

    /* reallocate block with specified size */
    total_size = new_size + sizeof (memblock_t);

    if (!(ptr = realloc (block, total_size))) {
        memblock_insert (pool, block);
        return NULL;
    }

    ptr->block_size = new_size;
    memblock_insert (pool, ptr);
    return ptr->data;
}

/* Allocate a new memory block and copy data into it */
void *mp_dup (mempool_t * pool, void *data, size_t size)
{
    void *p;

    if ((p = mp_alloc_n0 (pool, size)))
        memcpy (p, data, size);

    return p;
}

/* Duplicate specified string and insert it in a memory pool */
char *mp_strdup (mempool_t * pool, char *str)
{
    char *new_str;

    if ((new_str = mp_alloc (pool, strlen (str) + 1)) == NULL)
        return NULL;

    strcpy (new_str, str);
    return new_str;
}

/* Free block at specified address */
int mp_free (void *addr)
{
    memblock_t *block = (memblock_t *) addr - 1;
    mempool_t *pool;

    if (addr != NULL) {
        assert (block->tag == MEMBLOCK_TAG);
        pool = block->pool;

        memblock_delete (pool, block);
        memset (block, 0, sizeof (memblock_t));
        free (block);
    }

    return (0);
}

/* Free block at specified address and clean pointer */
int mp_free_ptr (void *addr)
{
    void *p;

    assert (addr != NULL);
    p = *(void **) addr;
    *(void **) addr = NULL;
    mp_free (p);
    return (0);
}

/* Free all blocks of specified pool */
void mp_free_all_blocks (mempool_t * pool)
{
    memblock_t *block, *next;

    MEMPOOL_LOCK (pool);

    for (block = pool->block_list; block; block = next) {
        next = block->next;
        free (block);
    }

    pool->block_list = NULL;
    pool->nr_blocks = 0;
    pool->total_size = 0;

    MEMPOOL_UNLOCK (pool);
}

/* Free specified memory pool */
void mp_free_pool (mempool_t * pool)
{
    mp_free_all_blocks (pool);

    if (!(pool->flags & MEMPOOL_FIXED))
        free (pool);
}

/* Create a new pool in a fixed memory area */
mempool_t *mp_create_fixed_pool (mempool_t * mp, char *name)
{
    memset (mp, 0, sizeof (*mp));

    if (pthread_mutex_init (&mp->lock, NULL) != 0)
        return NULL;

    mp->name = name;
    mp->block_list = NULL;
    mp->flags = MEMPOOL_FIXED;
    return mp;
}

/* Create a new pool */
mempool_t *mp_create_pool (char *name)
{
    mempool_t *mp = malloc (sizeof (*mp));

    if (!mp || !mp_create_fixed_pool (mp, name)) {
        free (mp);
        return NULL;
    }

    mp->flags = 0;              /* clear "FIXED" flag */
    return mp;
}
