/*
#ident	"@(#)smail/src:RELEASE-3_2_0_121:alloc.c,v 1.31 2005/07/20 20:33:45 woods Exp"
 */

/*
 *    Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
 *    Copyright (C) 1992  Ronald S. Karr
 * 
 * See the file COPYING, distributed with smail, for restriction
 * and warranty information.
 */

/*
 * alloc.c:
 *	storage allocation with panics on errors
 *	    The functions in this file may be replaced in the future when
 *	    daemon mode is implemented, to make it easier to reclaim
 *	    storage.
 *
 *	block storage allocation
 *	    This allows a given storage allocation to be associated 
 *	    with a group of other storage allocations.  It is
 *	    possible to free or test for existence of the class.
 *
 *	    A block a pointer to a chain of segments.  Each segment
 *	    refers to one storage allocation.  A block also contains
 *	    the total number of bytes allocated.  If this number is
 *	    zero, then no storage is associated with the block.
 *
 *	external functions: xmalloc, xrealloc, priv_xfree, 
 *			    balloc, brealloc, bfree,
 *			    malloc_block, realloc_block, free_block
 */

#include "defs.h"

#include <sys/types.h>
#include <stdio.h>
#include <errno.h>

#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#endif

#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
#  include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif

#ifdef USE_VMALLOC
# include <vmalloc.h>
#endif

#include "smail.h"
#include "exitcodes.h"
#include "addr.h"			/* for "log.h" */
#include "log.h"
#include "alloc.h"
#include "smailport.h"

/* the master block of blocks */
static struct block master = {(struct bseg *)0, 0};

/* default block for 0 based life time */
static struct block default_block = {(struct bseg *)0, 0};
static struct block *def_block = &default_block;

/* variable exported for the X_CHECK macro */
int x_dont_panic = TRUE;              /* if TRUE, X_CHECK won't call panic */

/* functions imported by this file */
#if !defined(HAVE_STDLIB_H) && !defined(HAVE_MALLOC_H) && !defined(USE_VMALLOC)
extern char *malloc();
extern char *realloc();
extern void free();				/* int on SunOS */
#endif


/*
 * xmalloc, priv_xrealloc, priv_xfree - per message malloc and realloc and free
 *			      with aborts on error
 *
 * The following three routines are an interface to malloc(), realloc(), and
 * free().  The first two detect errors and panic the application, which means
 * calling functions don't having to worry about whether a memory allocation
 * error occurs or not.  There is seldom anything graceful to do when such
 * errors happen anyway, and besides they happen very rarely on machines with a
 * reasonable VM space, so just panic about the problem and don't bother
 * returning.
 *
 * These functions also reserve space for a magic canary number, and then check
 * for its presense to help diagnose any double-free attempts, attempts to free
 * non-allocated storage, etc.  See "alloc.h" for the rest of the details.
 * Because of this xfree() must always be used to release storage that was
 * allocated by xalloc() or xrealloc().
 *
 * Note there are xrealloc() and xfree() macros defined in "alloc.h" which
 * provide wrappers to the priv_*() routines.  These wrappers also call the
 * X_CHECK() macro (and for xfree() check that the region pointer is not NULL)
 * and so can provide the line number of the original call instead of always
 * just pointing into this source file.  The priv_*() functions also call the
 * X_CHECK() macro in order to allow the application to pass pointers to these
 * functions off to other libraries which might want to use the same allocator
 * as the common application, but which are not themselves compiled with
 * "alloc.h".
 *
 * In the future, the x-style allocation routines will be used for
 * allocation of spaces which will be reclaimed after processing one
 * message -- i.e. through the block allocator.
 */
char *
xmalloc(size)
    size_t size;			/* size of region to allocate */
{
    register char *ret;			/* region returned by malloc */

    ret = malloc(size + sizeof(ALIGNED_TYPE));
    if (!ret) {
	panic(EX_SOFTWARE, "malloc(%d) failed: %s.", size, strerror(errno));
	/*NOTREACHED*/
    }
    ((ALIGNED_TYPE *) ret)[0] = X_MAGIC;
    return ret + sizeof(ALIGNED_TYPE);
}

char *
priv_xrealloc(region, size)
    char *region;			/* region to be realloc'd */
    size_t size;			/* realloc size */
{
    register char *ret;			/* region returned by realloc */

    if (X_CHECK(region) == FAIL) {
	/* what to do with realloc that is reasonable to avoid a panic? */
	/* copy the region to an entirely new malloc'd area */
	ret = xmalloc(size);
	memcpy(ret, region, (size_t) size);
	return ret;
    }

    ((ALIGNED_TYPE *) region)[-1] = 0;	/* clear the magic number */
    ret = realloc(region - sizeof(ALIGNED_TYPE), size + sizeof(ALIGNED_TYPE));
    if (!ret) {
	panic(EX_SOFTWARE, "realloc(%p, %u) failed: %s.",
	      (POINTER_TYPE) region, size, strerror(errno));
	/*NOTREACHED*/
    }
    ((ALIGNED_TYPE *) ret)[0] = X_MAGIC; /* put the magic number back */
    return ret + sizeof(ALIGNED_TYPE);
}

void
priv_xfree(region)
    char *region;
{
    if (region == NULL) {
	panic(EX_SOFTWARE, "xfree(NULL) called!  Please trace under a debugger!");
	/*NOTREACHED*/
    }
    if (X_CHECK(region) == FAIL) {
	/*
	 * we must permit double free()'s when x_dont_panic is set and hope the
	 * paniclog reveals the problem soon enough
	 */
	return;
    }
    ((ALIGNED_TYPE *)region)[-1] = 0;
    free(region - sizeof(ALIGNED_TYPE));

    return;
}


/*
 * bmalloc - allocate memory and associate it with a block
 *
 * input:
 *	size - number of bytes to allocate
 *	block - the block to associate it with, 0 ==> master
 *
 * output:
 *	returns a pointer to the allocated storage
 */
char *
bmalloc(size, block)
    size_t size;		/* number of bytes to allocate */
    struct block *block;	/* block that data belongs to */
{
    struct bseg *newseg;	/* the new segment for this alloc */

    /* a zero block implies master block */
    if (block == 0) {
	block = def_block;
    }

    /* add a new segment to the block */
    if ((newseg = (struct bseg *)malloc(sizeof(struct bseg))) == NULL) {
	panic(EX_SOFTWARE, "bmalloc: malloc of bseg [%d] failed: %s.", sizeof(struct bseg), strerror(errno));
	/* NOTREACHED */
    }
    newseg->next = block->next;
    block->next = newseg; 

    /* allocate the storage */
    if ((newseg->data = malloc(size)) == NULL) {
	panic(EX_SOFTWARE, "bmalloc: malloc of %u bytes failed: %s.", size, strerror(errno));
	/* NOTREACHED */
    }
    ++block->cnt;

    /* return the pointer */
    return newseg->data;
}

/*
 * brealloc - reallocate storage associated with a block
 *
 * Resize a storagg from a block, and perform all needed
 * accounting adjustments.
 *
 * input:
 *	data - pointer to data being realloced
 *	size - the new size
 *	block - the block that data belongs
 *
 * output:
 *	returns a pointer to the re-allocated storage
 */
char *
brealloc(data, size, block)
    char *data;			/* data being re-allocated */
    size_t size;		/* number of bytes to re-allocate */
    struct block *block;	/* block that data belongs to */
{
    struct bseg *oldseg;	/* old segment associated with data */

    /* a zero block implies master block */
    if (block == 0) {
	block = def_block;
    }

    /* find the segment */
    for (oldseg=block->next;
	 oldseg != NULL && oldseg->data != data;
	 oldseg=oldseg->next) {
	; /* NO-OP */
    }
    if (oldseg == NULL) {
	panic(EX_SOFTWARE, 
	      "brealloc: seg (at %p) not in block (at %p, cnt:%d)",
	      (POINTER_TYPE) oldseg, (POINTER_TYPE) block, block->cnt);
	/* NOTREACHED */
    }

    /* reallocate */
    if ((oldseg->data = realloc(data, size)) == NULL) {
	panic(EX_SOFTWARE, 
	      "brealloc: realloc to %d bytes (at %p) failed: %s.",
	      size, (POINTER_TYPE) data, strerror(errno));
	/* NOTREACHED */
    }

    /* return the pointer */
    return oldseg->data;
}

/*
 * bfree - free a signle segment from a block
 *
 * Free a storage from a block, and perform all needed accounting adjustments.
 *
 * input:
 *	data - pointer to data being freed
 *	block - the block that data belongs
 */
void
bfree(data, block)
    char *data;			/* data being freed */
    struct block *block;	/* block that data belongs to */
{
    struct bseg *oldseg;	/* old segment associated with data */
    struct bseg *lastseg;	/* the segment before the deleted segment */

    /* firewall */
    if (block == NULL) {
	panic(EX_SOFTWARE, "bfree: block is NULL");
	/* NOTREACHED */
    }

    /*
     * delete the segment from the block
     */
    /* first find the segment */
    for (lastseg = NULL, oldseg = block->next; 
	 oldseg != NULL && oldseg->data != data;
	 lastseg = oldseg, oldseg = oldseg->next) {
	; /* NO-OP */
    }
    if (oldseg == NULL) {
	panic(EX_SOFTWARE, 
	      "bfree: data (at %p) not in block (at %p, cnt: %d)",
	      (POINTER_TYPE) data, (POINTER_TYPE) block, block->cnt);
	/* NOTREACHED */
    }
    /* remove the segment from the block */
    if (lastseg == NULL) {
	block->next = oldseg->next;
    } else {
	lastseg->next = oldseg->next;
    }
    /* do the accounting */
    --block->cnt;

    /* free the data and segment */
    free(oldseg->data);
    free((char *) oldseg);

    return;
}


/*
 * malloc_block - form a new storage block
 *
 * Start a new storage block with no storage allociated with it.
 *
 * NOTE: the block structure becomes a segment in the master block
 *
 * input:
 *	none
 *
 * output:
 *	returns a pointer to the new block
 */
struct block *
malloc_block()
{
    struct block *block;	/* ptr to the new block */

    /* allocate the block */
    block = (struct block *) bmalloc(sizeof(struct block), &master);
    block->cnt = 0;
    block->next = NULL;

    /* return the new block */
    return block;
}

/*
 * realloc_block - move a segment from one block to another
 *
 * input:
 *	data - the data being moved
 *	oldblock - the block where the segment currently resides
 *	newblock - the block that will hold the new segment
 */
void
realloc_block(data, oldblock, newblock)
    char *data;			/* the data to move */
    struct block *oldblock;	/* the old block */
    struct block *newblock;	/* the new block */
{
    struct bseg *oldseg;	/* old segment associated with data */
    struct bseg *lastseg;	/* the segment before the deleted segment */

    /* firewall */
    if (oldblock == NULL && newblock == NULL) {
	panic(EX_SOFTWARE, "realloc_block: oldblock or newblock is NULL");
	/* NOTREACHED */
    }

    /*
     * delete the segment from the oldblock
     */
    /* first find the segment */
    for (lastseg = NULL, oldseg = oldblock->next; 
	 oldseg != NULL && oldseg->data != data;
	 lastseg = oldseg, oldseg = oldseg->next) {
	; /* NO-OP */
    }
    if (oldseg == NULL) {
	panic(EX_SOFTWARE, 
	      "realloc_block: data (at %p) not in block (at %p, cnt: %d)",
	      (POINTER_TYPE) data, (POINTER_TYPE) oldblock, oldblock->cnt);
	/* NOTREACHED */
    }
    /* remove the segment from the old block */
    if (lastseg == NULL) {
	oldblock->next = oldseg->next;
    } else {
	lastseg->next = oldseg->next;
    }
    --oldblock->cnt;

    /* add the segment to the new block */
    oldseg->next = newblock->next;
    newblock->next = oldseg; 
    ++newblock->cnt;

    return;
}

/*
 * free_block - free everything in a block
 *
 * input:
 *	block - the block to free all segments on
 */
void
free_block(block)
    struct block *block;	/* the block to free */
{
    struct bseg *curseg;

    if (block == NULL) {
	panic(EX_SOFTWARE, "free_block: block is NULL");
	/* NOTREACHED */
    }

    /* free the chain */
    curseg = block->next;
    while (curseg != NULL) {
	struct bseg *nextseg;

	/* find the next entry so we can free the current one */
	nextseg = curseg->next;
	/* do the accounting */
	--block->cnt;
	/* free the data */
	free(curseg->data);
	/* free the segemnt */
	free((char *) curseg);
	/* go on to the next */
	curseg = nextseg;
    }

    /* double-check the balance sheet */
    if (block->cnt != 0) {
	panic(EX_SOFTWARE, "free_block: freed block (at %p) has cnt: %d", (POINTER_TYPE) block, block->cnt);
	/* NOTREACHED */
    }

    /* free the block segment from the master block */
    bfree((char *) block, &master);

    return;
}

/* 
 * Local Variables:
 * c-file-style: "smail"
 * End:
 */


syntax highlighted by Code2HTML, v. 0.9.1