/* vim: set shiftwidth=4 tabstop=8 softtabstop=4: */
/* $Id: cfuncs.c,v 1.6 2003/01/19 09:50:59 shinra Exp $ */
#include "sptprivate.h"
#include <signal.h>

#if !defined(HAVE_STRDUP) && defined(NEED_STRDUP)
char *
ALTSYM(strdup) (const char *str)
{
    char *res;
    res = malloc(strlen(str) + 1);
    if (res == NULL)
	return NULL;
    strcpy(res, str);
    return res;
}
#endif

#if !defined(HAVE_STRNDUP) && defined(NEED_STRNDUP)
char *
ALTSYM(strndup) (const char *str, size_t len)
{
    size_t i;
    char *res;
    for (i = 0; i < len && str[i] != '\0'; i++);
    res = malloc(i + 1);
    if (res == NULL)
	return NULL;
    memcpy(res, str, i);
    res[i] = '\0';
    return res;
}
#endif

#ifndef HAVE_MALLOC
void *
ALTSYM(malloc) (size_t size)
{
    return malloc(size ? size : 1);
}
void *
ALTSYM(calloc) (size_t number, size_t size)
{
    return ALTSYM(calloc) (number ? number : 1, size ? size : 1);
}
#endif /* !HAVE_MALLOC */

#ifndef HAVE_REALLOCF
void *
ALTSYM(reallocf) (void *ptr, size_t size)
{
    void *tmp;
    tmp = realloc(ptr, size);
    if (tmp == NULL)
	free(ptr);
    return tmp;
}
#endif /* !HAVE_REALLOCF */

#ifdef USE_MEMORY_DEBUG
#define MEMORY_MAGIC 0x8e70142f
#define FREED_MAGIC 0x209ac17e
#include <stdio.h>
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#ifndef UTMP_MAX
#define UTMP_MAX ((unsigned int)-1)
#endif

struct memlist {
    struct memlist *next;
    struct memlist *prev;
    int magic;
    const char *file;	/* assume that __FILE__ is valid forever */
    unsigned int line;
};

static int n_alloc = 0;
static int n_free = 0;
static int n_realloc = 0;
static int n_nomem = 0;
static struct memlist root = { NULL, NULL, MEMORY_MAGIC, __FILE__, __LINE__ };

#define THROUGH_ARGS file, line
static void
link_memlist(struct memlist *plist, DEBUG_ARGS)
{
#ifdef MEMORY_VERBOSE
    struct memlist *pp;
    fprintf(stderr, "link_memlist: %s line %d\n", file, line);
    if (root.next) {
	for (pp = root.next; pp != &root; pp = pp->next)
	    fprintf(stderr, "pre link: file %s line %d\n",
		    pp->file, pp->line);
    }
#endif
    if (root.next) {
	assert(root.prev);
	plist->next = &root;
	plist->prev = root.prev;
	root.prev->next = plist;
	root.prev = plist;
    } else {
	assert(!root.prev);
	plist->next = &root;
	plist->prev = &root;
	root.next = plist;
	root.prev = plist;
    }
    plist->magic = MEMORY_MAGIC;
    plist->file = file;
    plist->line = line;
#ifdef MEMORY_VERBOSE
    assert(root.next);
    for (pp = root.next; pp != &root; pp = pp->next)
	fprintf(stderr, "post link: file %s line %d\n",
		pp->file, pp->line);
#endif
}

static int
validate_mem(struct memlist *plist, DEBUG_ARGS)
{
#ifdef MEMORY_VERBOSE
    fprintf(stderr, "validate_mem: %s line %d\n", file, line);
    fprintf(stderr, "validate_mem: plist: %s line %d\n",
	    plist->file, plist->line);
#endif
    if (plist->magic == MEMORY_MAGIC
	    && plist->next
	    && plist->next->magic == MEMORY_MAGIC
	    && plist->prev
	    && plist->prev->magic == MEMORY_MAGIC)
	return 0;
#ifdef NDEBUG
    if (plist->magic == FREED_MAGIC)
	fprintf(stderr, "MEMORY_DEBUG: waning: already freed: %s line %d\n",
		file, line);
    else
	fprintf(stderr, "MEMORY_DEBUG: waning: unknown pointer: %s line %d\n",
		file, line);
#else
    if (plist->magic == FREED_MAGIC)
	fprintf(stderr, "MEMORY_DEBUG: fatal: already freed: %s line %d\n",
		file, line);
    else
	fprintf(stderr, "MEMORY_DEBUG: fatal: unknown pointer: %s line %d\n",
		file, line);
    abort();
#endif
    return -1;
}

static void
unlink_memlist(struct memlist *plist)
{
#ifdef MEMORY_VERBOSE
    struct memlist *pp;
#endif
    assert(root.next && root.prev);
#ifdef MEMORY_VERBOSE
    for (pp = root.next; pp != &root; pp = pp->next)
	fprintf(stderr, "pre unlink: file %s line %d\n",
		pp->file, pp->line);
#endif
#ifdef MEMORY_VERBOSE
    fprintf(stderr, "unlink_memlist: plist: %s line %d\n",
	    plist->file, plist->line);
#endif
    if (plist->next == &root && plist->prev == &root) {
	assert(root.next == plist && root.prev == plist);
	root.next = root.prev = NULL;
    } else {
	assert(plist->next != plist->prev);
	plist->next->prev = plist->prev;
	plist->prev->next = plist->next;
    }
#ifdef MEMORY_VERBOSE
    if (root.next) {
	for (pp = root.next; pp != &root; pp = pp->next)
	    fprintf(stderr, "post unlink: file %s line %d\n",
		    pp->file, pp->line);
    }
#endif
    plist->magic = FREED_MAGIC;
}

void *
DEBUGSYM(malloc) (size_t size, DEBUG_ARGS)
{
    struct memlist *ptr;
    if (UINT_MAX - sizeof(struct memlist) < size) {
	n_nomem++;
	return NULL;
    }
    ptr = malloc(sizeof(struct memlist) + size);
    if (ptr == NULL) {
	n_nomem++;
	return NULL;
    }
    n_alloc++;
    link_memlist(ptr, THROUGH_ARGS);
    return (void *)(ptr + 1);
}

void *
DEBUGSYM(calloc) (size_t number, size_t size, DEBUG_ARGS)
{
    struct memlist *ptr;
    if ((UINT_MAX - sizeof(struct memlist)) / number < size) {
	n_nomem++;
	return NULL;
    }
    size *= number;
    ptr = malloc(sizeof(struct memlist) + size);
    if (ptr == NULL) {
	n_nomem++;
	return NULL;
    }
    n_alloc++;
    link_memlist(ptr, THROUGH_ARGS);
    BZERO(ptr + 1, size);
    return (void *)(ptr + 1);
}

void *
DEBUGSYM(realloc) (void *ptr, size_t size, DEBUG_ARGS)
{
    struct memlist *tmp, *pp = (struct memlist *)ptr - 1;
    if (ptr == NULL)
	return DEBUGSYM(malloc) (size, THROUGH_ARGS);
    if (UINT_MAX - sizeof(struct memlist) < size) {
	n_nomem++;
	return NULL;
    }
    if (validate_mem(pp, THROUGH_ARGS))
	return NULL;
    tmp = realloc(pp, sizeof(struct memlist) + size);
    if (tmp == NULL) {
	n_nomem++;
	return NULL;
    }
    n_realloc++;
    tmp->prev->next = tmp;
    tmp->next->prev = tmp;
    return (void *)(tmp + 1);
}

void *
DEBUGSYM(reallocf) (void *ptr, size_t size, DEBUG_ARGS)
{
    void *tmp;
    tmp = DEBUGSYM(realloc) (ptr, size, THROUGH_ARGS);
    if (tmp == NULL)
	DEBUGSYM(safe_free) (ptr, THROUGH_ARGS);
    return tmp;
}

void
DEBUGSYM(free) (void *ptr, DEBUG_ARGS)
{
    struct memlist *pp = (struct memlist *)ptr - 1;
    assert(ptr != NULL);
    if (validate_mem(pp, THROUGH_ARGS))
	return;
    n_free++;
    unlink_memlist(pp);
    free(pp);
}

void
DEBUGSYM(safe_free) (void *ptr, DEBUG_ARGS)
{
    if (ptr == NULL)
	return;
    DEBUGSYM(free) (ptr, THROUGH_ARGS);
}

char *
DEBUGSYM(strdup) (const char *str, DEBUG_ARGS)
{
    char *ptr;
    size_t size = strlen(str) + 1;
    ptr = DEBUGSYM(malloc) (size, THROUGH_ARGS);
    if (ptr != NULL)
	MEMCPY(ptr, str, size);
    return ptr;
}

#if 0 /* unused */
char *
DEBUGSYM(strndup) (const char *str, size_t len, DEBUG_ARGS)
{
    char *ptr;
    size_t i;
    size_t size;
    for (i = 0; i < len && str[i] != '\0'; i++);
    i++;
    ptr = DEBUGSYM(malloc) (i, THROUGH_ARGS);
    if (ptr != NULL)
	MEMCPY(ptr, str, i);
    return ptr;
}
#endif /* unused */

void
DEBUGSYM(memory_report) (void)
{
    fprintf(stderr, "n_alloc = %d\n", n_alloc);
    fprintf(stderr, "n_free = %d\n", n_free);
    fprintf(stderr, "n_realloc = %d\n", n_realloc);
    fprintf(stderr, "n_nomem = %d\n", n_nomem);
    if (n_alloc > n_free) {
	struct memlist *plist;
	unsigned int n_list = 0;
	assert(root.next);
	for (plist = root.next; plist != &root; plist = plist->next, n_list++)
	    fprintf(stderr, "file %s line %d\n",
		    plist->file, plist->line);
	fprintf(stderr, "%d unfreed memory blocks.\n", n_list);
	assert(n_list == n_alloc - n_free);
    }
}
#endif /* !USE_MEMORY_DEBUG */

#ifndef HAVE_STRCSPN
#ifdef NEED_STRSPN
size_t
ALTSYM(strspn) (const char *s, const char *cs)
{
    const char *p, *csp;
    for (p = s; *p; ++p) {
	for (csp = cs; *csp; ++csp)
	    if (*csp == *p)
		goto next;
	break;
next:
    }
    return p - s;
}
#endif

#ifdef NEED_STRCSPN
size_t
ALTSYM(strcspn) (const char *s, const char *cs)
{
    const char *p, *csp;
    for (p = s; *p; ++p) {
	for (csp = cs; *csp; ++csp)
	    if (*csp == *p)
		goto finish;
    }
finish:
    return p - s;
}
#endif

#endif /* !HAVE_STRCSPN */

#ifdef HAVE_SIGACTION
void
PRIVATESYM(trap_signal) (
	int sig, RETSIGTYPE (*func) (int), sighandler_type *old)
{
    struct sigaction new;
    new.sa_handler = func;
    sigemptyset(&new.sa_mask);
    new.sa_flags = 0;
    sigaction(sig, &new, old);
}
#endif /* HAVE_SIGACTION */

