/*
 * fhist - file history and comparison tools
 * Copyright (C) 1991-1994, 1998, 2000-2002, 2008, 2010, 2012 Peter Miller
 *
 * Derived from a work
 * Copyright (C) 1990 David I. Bell.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 *
 * Malloc interludes which allow for releasing of all used memory blocks.
 * Just call cm_reset whenever you want to free all allocated memory.
 * This is convenient when reinitializing a program which allocates memory.
 *
 * Note: You must make sure that old stale pointers are not referenced,
 * such as static pointers which were 0 when the program started but
 * won't be later.  In other words, to be safe you should not assume any
 * variables have been initialized, even for zero values.
 */

#include <common/ac/errno.h>
#include <common/ac/stdlib.h>
#include <common/ac/stddef.h>
#include <common/ac/string.h>
#include <libexplain/malloc.h>
#include <libexplain/output.h>
#include <libexplain/realloc.h>

#include <common/cmalloc.h>
#include <common/error.h>

#undef malloc
#undef realloc
#undef free
#undef calloc


#define MAGIC   54928347L


typedef struct chunk    CHUNK;
struct chunk
{
    CHUNK       *next;          /* next chunk in chain */
    CHUNK       *prev;          /* previous chunk in chain */
    long        magic;          /* magic value */
    double      data[1];        /* data storage (varying size) */
};

#define CHUNKSIZE(size) (sizeof(CHUNK) + (size_t)(size) - sizeof(double))

#define DPTOCP(dp) \
        ((CHUNK *)(void*)(((char *)(dp)) - offsetof(CHUNK, data[0])))


static CHUNK head = { &head, &head, 0, { 0 }};


/*
 * Allocate some memory.
 */

static void *
cm_alloc(size_t size)
{
    CHUNK           *cp;

    cp = (CHUNK *)explain_malloc_or_die(CHUNKSIZE(size));
    cp->magic = MAGIC;
    cp->next = head.next;
    cp->next->prev = cp;
    cp->prev = &head;
    head.next = cp;
    return (void *)cp->data;
}


void *
cm_alloc_and_check(size_t n)
{
    return cm_alloc(n);
}


/*
 * Reallocate memory.
 */

static void *
cm_realloc(void *dp, size_t size)
{
    CHUNK           *cp;

    if (!dp)
    {
        errno = EINVAL;
        return 0;
    }
    cp = DPTOCP(dp);
    if (cp->magic != MAGIC)
    {
        explain_output_error
        (
            "%s: %d: bad realloc %08lX (bug)",
            __FILE__,
            __LINE__,
            (unsigned long)dp
        );
        errno = EINVAL;
        return 0;
    }
    cp = (CHUNK *)explain_realloc_or_die(cp, CHUNKSIZE(size));
    if (!cp)
        return 0;
    cp->next->prev = cp;
    cp->prev->next = cp;
    return (void *)cp->data;
}


void *
cm_realloc_and_check(void *a, size_t b)
{
    return cm_realloc(a, b);
}


/*
 * Allocate zeroed memory.
 */

static void *
cm_calloc(size_t nelem, size_t elsize)
{
    size_t          totalsize;
    CHUNK           *cp;

    totalsize = CHUNKSIZE(nelem * elsize);
    cp = (CHUNK *)explain_malloc_or_die(totalsize);
    memset(cp, 0, totalsize);
    cp->magic = MAGIC;
    cp->next = head.next;
    cp->next->prev = cp;
    cp->prev = &head;
    head.next = cp;
    return (void *)cp->data;
}


void *
cm_calloc_and_check(size_t a, size_t b)
{
    return cm_calloc(a, b);
}


/*
 * Free memory.
 */

void
cm_free(void *dp)
{
    CHUNK           *cp;

    if (!dp)
        return;
    cp = DPTOCP(dp);
    if (cp->magic != MAGIC)
    {
        explain_output_error
        (
            "%s: %d: bad free %08lX (bug)",
            __FILE__,
            __LINE__,
            (unsigned long)dp
        );
        return;
    }
    cp->prev->next = cp->next;
    cp->next->prev = cp->prev;
    cp->next = 0;
    cp->prev = 0;
    cp->magic = 0;
    free(cp);
}


/*
 * Reset the whole memory arena.
 * This frees all memory allocated since the last cm_reset call.
 */

void
cm_reset(void)
{
    CHUNK           *cp;

    while (head.next != &head)
    {
        cp = head.next;
        if (cp->magic != MAGIC)
        {
            explain_output_error
            (
                "%s: %d: bad cm_reset %08lX (bug)",
                __FILE__,
                __LINE__,
                (unsigned long)cp
            );
            break;
        }
        head.next = cp->next;
        cp->next = 0;
        cp->prev = 0;
        cp->magic = 0;
        free(cp);
    }
    head.next = &head;
    head.prev = &head;
}


/* vim: set ts=8 sw=4 et : */
