/*
 * Copyright (c) 2003-2011
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*****************************************************************************
 * COPYRIGHT AND PERMISSION NOTICE
 * 
 * Copyright (c) 2001-2003 The Queen in Right of Canada
 * 
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, and/or sell
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, provided that the above copyright notice(s) and this
 * permission notice appear in all copies of the Software and that both the
 * above copyright notice(s) and this permission notice appear in supporting
 * documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE 
 * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
 * SOFTWARE.
 * 
 * Except as contained in this notice, the name of a copyright holder shall not
 * be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization of the
 * copyright holder.
 ***************************************************************************/

/*
 * Frame memory - context-based memory management
 * In short, this package keeps track of every memory allocation so that
 * all of them can conveniently be freed by the application, eliminating
 * leaks and potentially increasing security.
 * It might be viewed as being part way to a garbage collection approach.
 *
 * A variety of different kinds of memory allocations and management modes
 * can be selected.
 * 1. The "source" for an allocation (where the memory comes from) can be
 *    provided or the standard system malloc(3) used.
 *
 * 2. Each allocation can be individually released (akin to free(3))
 *    in "unit_free" mode or release can be restricted to being applied to all
 *    allocations on a particular frame at once.
 *
 * calloc(3), realloc(3), and reallocf(3) functionality is provided, but
 * not valloc(3).
 *
 * NOTE: there are some situations in which you must be careful about
 * when frame memory is freed.  For example, if you push a new frame,
 * do some work, and then free the frame, make sure that none of the
 * allocated memory needs to persist.  If you call a library function
 * written by someone else, for instance, you may not know if the function
 * does some initializations using malloc() that subsequent calls will expect
 * to have already been done; so if you free that memory, there could be
 * trouble.
 *
 * XXX
 * o there's surely room for performance and memory usage improvements
 * o Implement sanity checks against corrupted memory
 * o Do smarter reallocations?  If memory is reallocated, flag the new
 *   allocation and allocate more than is asked for in hopes that there are
 *   more reallocations to come.
 * o Instead of freeing chunks and MemChunk and Frame structures, cache
 *   some of them so they can be reused more efficiently.
 * o optionally use mlock() to prevent allocated memory from being written
 *   to swap space, although this is only available to the super-user.
 *
 * Other malloc packages:
 * http://www.yolinux.com/TUTORIALS/LinuxTutorialSoftwareDevelopment.html
 * memget(3)
 *
 * The basic design ideas originated in the late '80s and early 90's
 * and have been refined and reimplemented a few times since then.  - bjb
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2011\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: frame.c 2528 2011-09-23 21:54:05Z brachman $";
#endif

/*
 * Frames     Memory Chunk Allocation List
 *  ----         -----           -----
 * | F0 | ----> | MC0 |  ---->  | MC1 | ---->
 *  ----         -----           ----- 
 * | F1 |        /   /            \  \   \
 *  ----        /MEM/              \  \   \
 * | F2 |      /   /                \  \   \
 *  ----     MCA MCA               MCA MCA MCA    
 * |    |     /   /                 /   /   /
 *         mem mem               mem mem mem
 */

/*
 * XXX Should lengths be Fm_len instead of Fm_ui32?
 * XXX Should be possible to get/set FM_FRAME_UNIT_FREE
 * per Fm_mem_src instead of per frame
 * XXX should be able to ask for a particular alignment
 */

#include "dacs_config.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include "str.h"
#include "frame.h"

#ifndef MEM_ALIGNMENT_TYPE
typedef union {
  int a;
  short b;
  char c[1];
  long d;
  long long ll;
  float f;
  double g;
} MEM_ALIGNMENT_TYPE;
#endif

enum {
#ifndef PAGE_SIZE
  PAGE_SIZE              = 8192,
#endif
  MEM_CLEAR_VALUE        = 17,
  MEM_MINIMUM_CHUNK_SIZE = PAGE_SIZE,		/* A power of two */
  MEM_ROUND_CHUNK_SIZE   = PAGE_SIZE
};

typedef struct MemChunk MemChunk;
typedef struct MemChunkAlloc MemChunkAlloc;

/*
 * A prototype structure for what ultimately gets passed back after a
 * successful allocation.
 * This is currently only necessary to support fm_realloc() - a convenient
 * way of knowing the size of an allocation and where the allocation came
 * from is needed.
 * The mem_mem_to_mca() function returns a pointer to this structure given
 * a pointer passed back to a user.
 *
 * XXX may want to put a magic number (a checksum?) at the start of this
 * header to help detect if it's been clobbered.  Might also put something
 * just beyond the last byte of user data.
 */
struct MemChunkAlloc {
  size_t size;				/* This must be the first element.  It is the */
  							/* number of bytes allocated for user data */
  MemChunk *mc;				/* Must be last real field. */
  MEM_ALIGNMENT_TYPE bogus;	/* The user is given a pointer to this */
};

/*
 * A MemChunk corresponds to one big memory allocation.  One or more
 * allocations are made from it.
 */
struct MemChunk {
  Fm_mem_src *src;		/* Source for this allocation */
  void *mem;			/* A big block from which allocations are made */
  Fm_ui32 size;			/* The size, in bytes, of this big block */
  Fm_ui32 avail;		/* The amount available, in bytes */
  MemChunk *next;		/* Next allocated block */
};

/*
 * Each Frame represents an "allocation context" of memory, each having
 * zero or more allocations associated with it.  Frames are linked together
 * into a "framestack" (an ordered list).  The current or top Frame is
 * pointed to by top_frame.
 * All memory allocations for a frame come from the same source (malloc,
 * by default)
 */
typedef struct Frame Frame;

struct Frame {
  char *name;               /* Currently, only used for debugging */
  unsigned int popped : 1;  /* Non-zero if the frame isn't on the framestack */
  Fm_mem_src *src;          /* Default allocation source */
  MemChunk *chunks;         /* List of allocations for this frame */
  Frame *next;
};

typedef struct MemHeader {
  Fm_ui32 tail_offset;
  MEM_ALIGNMENT_TYPE bogus;
} MemHeader;

/*
 * Each block (allocated or not) is preceded by this header to form
 * a linked list of all blocks ordered by ascending address.
 */
typedef struct MemBlockHeader {
  size_t dsize;					/* Size of data area following this header. */
  Fm_ui32 prev_offset;
  Fm_ui32 next_offset;
  unsigned int inuse : 1;
  MemChunk *mc;					/* Must be last */
  MEM_ALIGNMENT_TYPE bogus;		/* The user is given a pointer to this. */
} MemBlockHeader;

#if !defined(ENABLE_FRAME_MEMORY) || ENABLE_FRAME_MEMORY == 0
int disable_frame_memory = 1;
#else
int disable_frame_memory = 0;
#endif

/*
 * If you want an entire application to use this package, even precompiled
 * code such as libraries, you'll need
 * to define DEFINE_MALLOC so that this package's malloc/realloc/free/etc. are
 * called rather than the usual ones.
 * Since this package is layered on top of standard malloc functionality,
 * we still need to be able to invoke the standard functions.  But we need to
 * avoid multiply-defined names, of course.
 * The current solution is to compile a malloc package, renaming its
 * public interfaces so as not to conflict with the standard names (that
 * we're going to export from this package).
 */

#ifdef DACS_OS_MACOSX
/* XXX unsure what the problem is, but we need to use the native functions */
#undef DEFINE_MALLOC
#endif

#ifdef DEFINE_MALLOC
#define MALLOC		bsd_malloc
#define FREE		bsd_free
#define REALLOC		bsd_realloc

extern void *MALLOC(size_t);
extern void *REALLOC(void *, size_t);
extern void FREE(void *);

static Frame_stats stats;

Frame_stats *
fm_get_stats(void)
{

  return(&stats);
}

/*
 * These functions should be defined if we want other code to use the
 * frame-based functions instead of the usual C library routines.
 * Then within this package, we need to refer to the "real" memory
 * allocation functions by different names.
 */
void *
malloc(size_t size)
{

  if (disable_frame_memory)
	return(MALLOC(size));

  stats.malloc_count++;
  stats.allocated += size;
  return(fm_malloc(size));
}

void *
realloc(void *ptr, size_t size)
{

  if (disable_frame_memory)
	return(REALLOC(ptr, size));

  stats.realloc_count++;
  stats.allocated += size;

  return(fm_realloc(ptr, size));
}

/* This appears to be a FreeBSD specific interface. */
void *
reallocf(void *ptr, size_t size)
{
  void *nptr;

  nptr = realloc(ptr, size);
  if (nptr == NULL && ptr != NULL)
	free(ptr);
  return(nptr);
}

void *
calloc(size_t nmemb, size_t memb_size)
{

  if (disable_frame_memory) {
	size_t need;
	void *p;

	need = nmemb * memb_size;
	if ((p = malloc(need)) != NULL)
	  memset(p, 0, need);

	return(p);
  }

  stats.calloc_count++;
  stats.allocated += (nmemb * memb_size);
  return(fm_calloc(nmemb, memb_size));
}

/*
 * Necessary in case the caller called our free() with a
 * pointer that came from the system's valloc().
 */
void *
valloc(size_t size)
{

  return(malloc(size));
}

void
free(void *ptr)
{

  if (disable_frame_memory)
	FREE(ptr);
  else
	stats.free_count++;
}

/*
 * An obsolete counterpart to calloc(), equivalent to free(3).
 */
void
cfree(void *ptr)
{

  if (disable_frame_memory)
	free(ptr);
  else
	stats.free_count++;
}

#else

#define MALLOC		malloc
#define REALLOC		realloc
#define FREE		free

extern void *MALLOC(size_t);
extern void *REALLOC(void *, size_t);
extern void FREE(void *);

Frame_stats *
fm_get_stats(void)
{

  return(NULL);
}
#endif

char *frame_errmsg;

/* A pointer to the top of the framestack. */
static Frame *top_frame = NULL;
static Frame *popped_frames = NULL;
static unsigned int default_frame_flags = FM_FRAME_MEM_ZAP;
static int trace_flag = 0;

static inline void *
src_malloc(Fm_mem_src *src, size_t size)
{

  if (src == NULL || src->type == MEM_SRC_DEFAULT)
	return(MALLOC(size));
  if (src->type == MEM_SRC_STANDARD)
	return(src->u.mem_funcs_standard->malloc(size));
  if (src->type == MEM_SRC_DATAPTR)
	return(src->u.mem_funcs_dataptr->malloc(src->u.mem_funcs_dataptr->dataptr,
										   size));
  abort();
}

static inline void
src_free(Fm_mem_src *src, void *ptr)
{

  if (src == NULL || src->type == MEM_SRC_DEFAULT)
	FREE(ptr);
  else if (src->type == MEM_SRC_STANDARD)
	src->u.mem_funcs_standard->free(ptr);
  else if (src->type == MEM_SRC_DATAPTR)
	src->u.mem_funcs_dataptr->free(src->u.mem_funcs_dataptr->dataptr, ptr);
  else
	abort();
}

static inline int
src_is_unit_free(Fm_mem_src *src)
{

  if (src == NULL)
	abort();
  return((src->flags & FM_FRAME_UNIT_FREE) != 0);
}

static inline int
src_is_mem_zap(Fm_mem_src *src)
{

  if (src == NULL)
	abort();
  return((src->flags & FM_FRAME_MEM_ZAP) != 0);
}

static inline Fm_ui32
MemHeaderSize(void)
{
  static MemHeader mh;

  return((Fm_ui32) ((char *) &mh.bogus - (char *) &mh));
}

static inline Fm_ui32
MemBlockHeaderSize(void)
{
  static MemBlockHeader mbh;

  return((Fm_ui32) ((char *) &mbh.bogus - (char *) &mbh));
}

/*
 * Compute the number of bytes needed for an object of SIZE bytes and space
 * following it sufficient to result in an ending address that will be
 * aligned to an address evenly divisible by ALIGNMENT.
 * ALIGNMENT is a power of 2.
 */
static inline size_t
mem_align(size_t size, size_t alignment)
{
  size_t aligned_size;

  aligned_size = (size + alignment - 1) & (~ (alignment - 1));
  return(aligned_size);
}

/* The number of full and partial pages needed for LEN bytes. */
static inline Fm_ui32
NBYTES_TO_NPAGES(void *db, Fm_ui32 len)
{
  Fm_ui32 val;

  val = (len + (PAGE_SIZE - 1)) / PAGE_SIZE;
  return(val);
}

/* The rounded-up segment length needed for LEN bytes. */
static inline Fm_ui32
NBYTES_TO_PAGE_BYTES(void *db, Fm_ui32 len)
{
  Fm_ui32 val;

  val = NBYTES_TO_NPAGES(db, len) * PAGE_SIZE;
  return(val);
}

/*
 * If the user requests REQ_SIZE bytes, compute the total amount needed,
 * including bookkeeping overhead and alignment constraints.
 * XXX May be based on constraints and actual chunk.mem value one day so
 * it is currently called from places where that pointer is accessible.
 */
static inline size_t
mem_need(size_t req_size, size_t sizeof_mem_alignment_type, int unit_free)
{
  size_t need;

  if (unit_free)
	need = mem_align(sizeof(MemBlockHeader) - sizeof_mem_alignment_type
					 + req_size, sizeof_mem_alignment_type);
  else
	need = mem_align(sizeof(MemChunkAlloc) - sizeof_mem_alignment_type
					 + req_size, sizeof_mem_alignment_type);
  return(need);
}

/*
 * Given a pointer to allocated memory that was previously returned to the
 * user, return a pointer to that memory's header.
 * XXX Might guard against a clobbered header using a magic number or
 * simple checksum.
 */
static inline MemChunkAlloc *
mem_mem_to_mca(void *ptr)
{
  size_t diff;
  MemChunkAlloc *mca;

  diff = (size_t) ((Fm_ui8 *) &mca->bogus - (Fm_ui8 *) &mca->size);
  mca = (MemChunkAlloc *) ((Fm_ui8 *) ptr - diff);

  return(mca);
}

static inline Fm_ui32
mem_mca_data_length(size_t total_size)
{
  size_t diff;
  MemChunkAlloc *mca;

  diff = (size_t) ((Fm_ui8 *) &mca->bogus - (Fm_ui8 *) &mca->size);
  return(total_size - diff);
}

static inline MemBlockHeader *
mem_mem_to_mbh(void *ptr)
{
  MemBlockHeader *mbh;

  mbh = (MemBlockHeader *) ((char *) ptr - MemBlockHeaderSize());
  return(mbh);
}

static inline int
mem_mem_is_unit_free(void *ptr)
{
  MemChunkAlloc *mca;

  mca = mem_mem_to_mca(ptr);
  return(src_is_unit_free(mca->mc->src));
}

/*
 * A new MemChunk is about to be allocated in response to a user request
 * for SIZE bytes.  Determine how big a MemChunk to actually allocate.
 */
static inline size_t
mem_compute_chunk_size(Frame *f, size_t size)
{
  size_t actual_size;

  if (f == NULL || f->src == NULL) {
	if (size < MEM_MINIMUM_CHUNK_SIZE)
	  return(MEM_MINIMUM_CHUNK_SIZE);

	actual_size = (size + MEM_ROUND_CHUNK_SIZE - 1)
	  & (~ (MEM_ROUND_CHUNK_SIZE - 1));
  }
  else {
	actual_size = NBYTES_TO_PAGE_BYTES(f->src, size);
  }
  return(actual_size);
}

/*
 * Create a new MemChunk on frame F of NEED bytes and make it the head
 * of the list of chunks for frame F.
 * The start of this new chunk must be maximally aligned.
 */
static inline MemChunk *
mem_new_chunk(Frame *f, Fm_mem_src *src, size_t need)
{
  void *ptr;
  MemChunk *mc;

  mc = (MemChunk *) MALLOC(sizeof(MemChunk));
  if (mc == NULL)
	return(NULL);

  if (src == NULL)
	mc->src = f->src;
  else
	mc->src = src;

  ptr = (void *) src_malloc(mc->src, need);
  if (ptr == NULL) {
	FREE(mc);
	frame_errmsg = "malloc failed";
	return(NULL);
  }

  mc->mem = ptr;
  mc->size = need;
  mc->avail = need;
  mc->next = f->chunks;
  mc->src->flags = f->src->flags;

  f->chunks = mc;

  if (trace_flag)
	fprintf(stderr, "mem_new_chunk: %u bytes\n", (unsigned int) need);
  return(mc);
}

static inline char *
xstrdup(const char *str)
{
  char *p;
  size_t len;

  len = strlen(str);
  p = (char *) MALLOC(len + 1);
  memcpy(p, str, len + 1);

  return(p);
}


/****************************************************************************/


/*
 * Convert a byte offset (index) within the memory block starting at address H
 * into an equivalent pointer within that block.
 * An offset of zero represents a NULL pointer.
 */
static MemBlockHeader *
MBH_PTR(void *h, Fm_ui32 offset)
{

  if (offset == 0)
    return(NULL);

  return((MemBlockHeader *) ((char *) h + offset));
}

/*
 * Convert a pointer within the memory block starting at address H into
 * an equivalent byte offset (index) within that block.
 * An offset of zero represents a NULL pointer.
 */
static Fm_ui32
MBH_OFFSET(void *h, MemBlockHeader *mbh)
{

  if (mbh == NULL)
    return(0);

  return((Fm_ui32) ((char *) mbh - (char *) h));
}

/*
 * Try to allocate a block of NEED bytes from MemChunk.
 * If successful, return a pointer to the block, otherwise return NULL.
 */
void *
mem_malloc_block(MemChunk *mc, size_t need)
{
  MemHeader *mh;
  MemBlockHeader *head_mbh, *mbh, *new_mbh;

  mh = (MemHeader *) mc->mem;
  head_mbh = (MemBlockHeader *) &mh->bogus;

  /* Search for a free block - first fit.  */
  for (mbh = head_mbh; mbh != NULL; mbh = MBH_PTR(mh, mbh->next_offset)) {
	if (mbh->inuse || (mbh->dsize + MemBlockHeaderSize()) < need)
	  continue;
      
	if ((mbh->dsize + MemBlockHeaderSize()) == need) {
	  mbh->inuse = 1;
	  mc->avail -= need;
	  return((void *) &mbh->bogus);
	}

	/*
	 * We are trying to split the block.  The remainder must be big enough
	 * to create another block.
	 */
	if (mbh->dsize < need)
	  continue;

	/* Split this block.  */
	new_mbh = (MemBlockHeader *) ((char *) mbh + need);
	new_mbh->dsize = mbh->dsize - need;
	new_mbh->next_offset = mbh->next_offset;
	new_mbh->prev_offset = MBH_OFFSET(mh, mbh);
	mbh->dsize = need - MemBlockHeaderSize();
	mbh->next_offset = MBH_OFFSET(mh, new_mbh);
	if (new_mbh->next_offset) {
	  MemBlockHeader *next_mbh;
        
	  next_mbh = MBH_PTR(mh, new_mbh->next_offset);
	  next_mbh->prev_offset = MBH_OFFSET(mh, new_mbh);
	}
	else
	  mh->tail_offset = MBH_OFFSET(mh, new_mbh);
	new_mbh->inuse = 0;
	mbh->mc = mc;
	mbh->inuse = 1;
	mc->avail -= need;
	return((void *) ((char *) mbh + MemBlockHeaderSize()));
  }

  /* No room left in this MemChunk. */
  return(NULL);
}

/*
 * Allocate NEED bytes which may later be released.
 * Equivalent to malloc(3).
 * Search through the MemChunks belonging to the top frame for NEED
 * available bytes.  If there are none available, create a new MemChunk
 * and allocate from it.
 */
static inline void *
mem_malloc(Frame *f, Fm_mem_src *src, size_t need)
{
  void *ptr;
  size_t mc_need, orig_size;
  MemChunk *mc;
  MemHeader *mh;
  MemBlockHeader *mbh;

  if (need == 0)
    return(NULL);

  /*
   * Search for a MemChunk with potential, then try to allocate from it.
   */
  for (mc = f->chunks; mc != NULL; mc = mc->next) {
	if (mc->src == src && mc->avail >= need) {
	  if ((ptr = mem_malloc_block(mc, need)) != NULL)
		return(ptr);
	}
  }

  /*
   * No existing MemChunk was suitable, so create a new one and allocate
   * from it.
   */
  mc_need = mem_compute_chunk_size(f, need);

#ifdef NOTDEF
  if (need <= MEM_MINIMUM_CHUNK_SIZE)
	mc_need = MEM_MINIMUM_CHUNK_SIZE;
  else {
	for (mc_need = MEM_MINIMUM_CHUNK_SIZE;
		 mc_need < need && mc_need != 0;
		 mc_need <<= 1)
	  ;
	if (mc_need == 0) {
	  if ((mc->size + need) > MEM_MAX_CHUNK_SIZE) {
		/* XXX Too hot to handle. */
		return(NULL);
	  }
	  mc_need = MEM_MAX_CHUNK_SIZE;
	}
  }
#endif

  /* Make one big, available chunk of memory and initialize. */
  orig_size = 0;

  mc = mem_new_chunk(f, src, mc_need);
  if (mc == NULL)
	return(NULL);

  mh = (MemHeader *) mc->mem;
  mh->tail_offset = 0;

  mbh = (MemBlockHeader *) &mh->bogus;
  mbh->mc = mc;
  mbh->inuse = 0;
  mbh->prev_offset = mbh->next_offset = 0;
  mbh->dsize = mc_need - MemHeaderSize() - MemBlockHeaderSize();
  mc->avail -= MemHeaderSize();

  return(mem_malloc_block(mc, need));
}

static inline int
mem_free(void *ptr)
{
  MemHeader *mh;
  MemBlockHeader *mbh, *next_mbh, *prev_mbh;
  MemChunk *mc;

  if (ptr == NULL)
	return(0);

  /* Compute the address of the header from ptr.  */
  mbh = mem_mem_to_mbh(ptr);

  mc = mbh->mc;
  if (!src_is_unit_free(mc->src)) {
    frame_errmsg = "bad argument";
	return(-1);
  }

  if (src_is_mem_zap(mc->src))
	memzapb(ptr, MEM_CLEAR_VALUE, mbh->dsize);

  mc->avail += mbh->dsize;

  mh = (MemHeader *) mc->mem;

  next_mbh = MBH_PTR(mh, mbh->next_offset);
  /* Coalesce this block with the next, if it's not in use.  */
  if (next_mbh != NULL && next_mbh->inuse == 0) {
	mbh->dsize += next_mbh->dsize + MemBlockHeaderSize();
	mc->avail += MemBlockHeaderSize();
	mbh->next_offset = next_mbh->next_offset;
	if (next_mbh->next_offset) {
	  MemBlockHeader *next;

	  next = MBH_PTR(mh, next_mbh->next_offset);
	  next->prev_offset = MBH_OFFSET(mh, mbh);
	}
	else {
	  if (mh->tail_offset != MBH_OFFSET(mh, next_mbh)) {
		fprintf(stderr, "XXX\n");
		exit(1);
	  }
      mh->tail_offset = MBH_OFFSET(mh, mbh);
	}
  }
 
  /* Coalesce this block with its predecessor, if it's not in use.  */
  prev_mbh = MBH_PTR(mh, mbh->prev_offset);
  if (prev_mbh != NULL && prev_mbh->inuse == 0) {
	prev_mbh->dsize += mbh->dsize + MemBlockHeaderSize();
	mc->avail += MemBlockHeaderSize();
	prev_mbh->next_offset = mbh->next_offset;
	if (mbh->next_offset) {
	  MemBlockHeader *tmp;

	  tmp = MBH_PTR(mh, mbh->next_offset);
	  tmp->prev_offset = MBH_OFFSET(mh, prev_mbh);
	}
	else {
	  if (mh->tail_offset != MBH_OFFSET(mh, mbh)) {
		fprintf(stderr, "YYY\n");
		exit(1);
	  }
	  mh->tail_offset = MBH_OFFSET(mh, prev_mbh);
	}
	mbh = prev_mbh;
  }

#ifdef NOTDEF
  if (mh->tail_offset == MBH_OFFSET(mh, mbh)) {
	mh->tail_offset = mbh->prev_offset;
	if (mh->tail_offset) {
	  MemBlockHeader *tmp;

	  tmp = MBH_PTR(mh, mh->tail_offset);
	  tmp->next_offset = 0;
	}
  }
#endif

  mbh->inuse = 0;

  return(0);
}

void
mem_show_mh(MemChunk *mc)
{
  int i;
  size_t total_avail, total_used;
  MemHeader *mh;
  MemBlockHeader *mbh, *mbh_head;

  mh = (MemHeader *) mc->mem;
  fprintf(stderr, " (tail %lu)\n", (unsigned long) mh->tail_offset);
  fprintf(stderr, "          ind     size  prev/next inuse\n");
  mbh_head = (MemBlockHeader *) &mh->bogus;

  i = 1;
  total_avail = total_used = 0;
  for (mbh = mbh_head; mbh != NULL; mbh = MBH_PTR(mh, mbh->next_offset)) {
	fprintf(stderr, "%5d  %6lu %7lu %6lu/%-6lu %3d\n",
			i++, (unsigned long) MBH_OFFSET(mh, mbh),
			(unsigned long) mbh->dsize + MemBlockHeaderSize(),
			(unsigned long) mbh->prev_offset,
			(unsigned long) mbh->next_offset, mbh->inuse);
	if (mbh->inuse)
	  total_used += mbh->dsize;
	else
	  total_avail += mbh->dsize;
  }

  fprintf(stderr, "\nTotal: %u avail, %u used\n",
		  (unsigned int) total_avail, (unsigned int) total_used);
}
  
void
mem_show_frame(Frame *f)
{
  int i;
  MemChunk *mc;

  i = 0;
  for (mc = f->chunks; mc != NULL; mc = mc->next) {
	fprintf(stderr, " Chunk %d: (%lu avail, %lu total)",
			i++, (unsigned long) mc->avail, (unsigned long) mc->size);
	if (src_is_unit_free(mc->src))
	  mem_show_mh(mc);
	else
	  fprintf(stderr, "\n");
  }
}

void
mem_show(void)
{
  Frame *f;

  for (f = top_frame; f != NULL; f = f->next) {
	if (f->name != NULL)
	  fprintf(stderr, "Frame '%s'\n", f->name);
	mem_show_frame(f);
  }
}

/****************************************************************************/

/*
 * Search for an existing chunk with sufficient space.
 * First fit.
 */
static inline MemChunk *
mem_find_chunk(Frame *f, Fm_mem_src *src, size_t need)
{
  MemChunk *mc;

  for (mc = f->chunks; mc != NULL; mc = mc->next) {
	if (mc->src == src && mc->avail >= need)
	  return(mc);
  }
  return(NULL);
}

static inline void *
mem_pointer(MemChunkAlloc *mca)
{

  return((void *) &mca->bogus);
}

/*
 * Return a new allocation of NEED bytes from MC, which the caller guarantees
 * to be big enough.
 */
static inline void *
mem_alloc_from_chunk(Frame *f, MemChunk *mc, size_t need)
{
  MemChunkAlloc *mca;
  void *mem, *ptr;

  ptr = (void *) ((Fm_ui8 *) mc->mem + (mc->size - mc->avail));
  mc->avail -= need;

  mca = (MemChunkAlloc *) ptr;
  mca->size = mem_mca_data_length(need);
  mca->mc = mc;
  mem = mem_pointer(mca);
  if (trace_flag)
	fprintf(stderr, "mem_alloc_from_chunk: %u bytes from %lx, %u avail\n",
			(unsigned int) need, (unsigned long) mc, mc->avail);
  return(mem);
}

/*
 * Make an allocation of REQ_SIZE bytes on the given frame, using
 * memory obtained from SRC.
 */
static inline void *
mem_alloc(Frame *f, Fm_mem_src *src, size_t req_size)
{
  void *ptr;
  size_t need;
  MemChunk *mc;

  /* Round up the size to satisfy alignment constraints. */
  need = mem_need(req_size, sizeof(MEM_ALIGNMENT_TYPE),
				  src_is_unit_free(f->src));

  if (src_is_unit_free(f->src))
	return(mem_malloc(f, src, need));

  /* Look for something already available. */
  mc = mem_find_chunk(f, src, need);
  if (mc == NULL) {
	mc = mem_new_chunk(f, src, mem_compute_chunk_size(f, need));
	if (mc == NULL)
	  return(NULL);
  }

  ptr = mem_alloc_from_chunk(f, mc, need);

  return(ptr);
}

static inline void *
mem_calloc(Frame *f, Fm_mem_src *src, size_t nmemb, size_t req_memb_size)
{
  void *ptr;
  size_t req_size;

  req_size = nmemb * req_memb_size;		/* XXX overflow */
  ptr = mem_alloc(f, src, req_size);
  if (ptr != NULL)
	memset(ptr, 0, req_size);
  return(ptr);
}

static inline int
push(Frame *f)
{
  Frame *ff;

  f->popped = 0;

  /* Remove the frame from the popped list. */
  ff = popped_frames;
  if (ff != NULL) {
	if (ff == f)
	  popped_frames = popped_frames->next;
	else {
	  while (ff->next != f && ff->next != NULL) {
		ff = ff->next;
	  }
	  if (ff != NULL)
		ff->next = (ff->next)->next;
	}
  }

  if (ff == NULL) {
    frame_errmsg = "internal error";
	return(-1);
  }

  f->next = top_frame;
  top_frame = f;
  return(0);
}

/*
 * Create a new, empty frame that is not on the framestack.
 * The source of allocations for the frame is src; if src is NULL, the
 * malloc(3) functions are used.
 * FLAGS specifies some behaviours of the frame.
 */
static inline Frame *
new_frame(Fm_mem_src *src, char *name, unsigned int flags)
{
  Frame *f;

  f = (Frame *) MALLOC(sizeof(Frame));
  if (f == NULL) {
    frame_errmsg = "malloc failed";
	return(NULL);
  }

  if (name == NULL || *name == '\0')
	f->name = NULL;
  else
	f->name = xstrdup(name);

  if (src == NULL) {
	static Fm_mem_src default_mem_src;
	static Fm_mem_funcs_standard default_mem_funcs = { MALLOC, FREE };

	default_mem_src.type = MEM_SRC_DEFAULT;
	default_mem_src.u.mem_funcs_standard = &default_mem_funcs;
	f->src = &default_mem_src;
  }
  else
	f->src = src;

  f->popped = 1;
  f->src->flags = flags & FM_FRAME_FLAG_MASK;
  f->chunks = NULL;
  f->next = popped_frames;
  popped_frames = f;

  return(f);
}

/*
 * Make an allocation of REQ_SIZE bytes on FRAME.
 * If FRAME is NULL, use the top frame, creating it if necessary.
 * The source for the allocation is obtained from FRAME.
 */
static inline void *
alloc_on_frame(Frame *f, Fm_mem_src *src, size_t req_size)
{
  Frame *ff;
  void *p;

  if (f == NULL) {
	/* Use the top frame. */
	if (top_frame == NULL) {
	  ff = new_frame(NULL, NULL, default_frame_flags);
	  if (ff == NULL)
		return(NULL);
	  if (push(ff) == -1)
		return(NULL);		/* XXX leak */
	}
	else
	  ff = top_frame;
  }
  else
	ff = f;

  p = mem_alloc(ff, src, req_size);

  return(p);
}

static inline void *
calloc_on_frame(void *frame, Fm_mem_src *src, size_t nmemb,
					  size_t req_memb_size)
{
  void *ptr;
  size_t req_size;
  Frame *f;

  f = (Frame *) frame;
  req_size = nmemb * req_memb_size;		/* XXX overflow */
  ptr = alloc_on_frame(f, src, req_size);
  if (ptr != NULL)
	memset(ptr, 0, req_size);
  return(ptr);
}

/*
 * Reallocation is currently just making a new allocation and copying the
 * old allocation.
 * XXX Trying to grow the old allocation is a possibility.
 */
static inline void *
mem_realloc(Frame *f, Fm_mem_src *src, void *ptr, size_t req_new_size)
{
  int clear_flag;
  void *new_ptr;
  size_t current_size;

  /* Which type of allocation is this? */
  if (mem_mem_is_unit_free(ptr)) {
	MemBlockHeader *mbh;

	mbh = mem_mem_to_mbh(ptr);
	current_size = mbh->dsize;
	clear_flag = src_is_mem_zap(mbh->mc->src);
  }
  else {
	MemChunkAlloc *mca;

	mca = mem_mem_to_mca(ptr);
	current_size = mca->size;
	clear_flag = src_is_mem_zap(mca->mc->src);
  }

  /* Is the allocation already large enough? */
  if (current_size == req_new_size)
	return(ptr);

  if (current_size > req_new_size) {
	if (clear_flag)
	  memzapb((char *) ptr + req_new_size, MEM_CLEAR_VALUE,
			  current_size - req_new_size);
	return(ptr);
  }

  if ((new_ptr = alloc_on_frame(f, src, req_new_size)) == NULL)
	return(NULL);

  /* Safe and faster not to use memmove(). */
  memcpy(new_ptr, ptr, current_size);

  if (mem_mem_is_unit_free(ptr)) {
	mem_free(ptr);
	/* XXX failure check? */
  }

  return(new_ptr);
}

static inline int
close_frame(Frame *f, int do_free)
{
  MemChunk *mc, *mc_next;

  if (f == NULL)
	return(0);

  for (mc = f->chunks; mc != NULL; mc = mc_next) {
	mc_next = mc->next;
	if (do_free) {
	  if (mc->src == NULL) {
		src_free(mc->src, mc->mem);
		mc->mem = NULL;
	  }
	  else {
		Fm_mem_src *src;

		src = mc->src;
		if (src_is_mem_zap(src))
		  memzapb(mc->mem, MEM_CLEAR_VALUE, mc->size);
		src_free(mc->src, mc->mem); /* XXX */
		mc->mem = NULL;
	  }
	}
	FREE(mc);
  }

  if (f->name != NULL) {
	FREE(f->name);
	f->name = NULL;
  }

  FREE(f);
  return(0);
}

/*
 * Free all allocations associated with FRAME and destroy the frame.
 */
static inline int
free_frame(Frame *f)
{

  return(close_frame(f, 1));
}

/*
 * Create a new frame and return it.
 */
void *
fm_new_frame(char *name, Fm_mem_src *src, unsigned int flags)
{
  Frame *f;

  frame_errmsg = NULL;

  if ((f = new_frame(src, name, flags)) == NULL)
	return(NULL);

  return((void *) f);
}

/*
 * Create a new frame and make it the top frame.
 */
int
fm_push_new_frame(char *name, Fm_mem_src *src, unsigned int flags)
{
  int rc;
  Frame *f;

  frame_errmsg = NULL;

  if ((f = new_frame(src, name, flags)) == NULL)
	return(-1);

  rc = push(f);

  return(rc);
}

/*
 * Name (or rename) a frame.
 */
int
fm_set_frame_name(void *frame, char *new_name)
{
  Frame *f;

  if (frame == NULL) {
	if (top_frame == NULL) {
      frame_errmsg = "no frame present";
	  return(-1);
	}
	f = top_frame;
  }
  else
	f = (Frame *) frame;

  if (f->name != NULL)
	FREE(f->name);

  if (new_name == NULL || *new_name == '\0')
	f->name = NULL;
  else
	f->name = xstrdup(new_name);

  return(0);
}

char *
fm_get_frame_name(void *frame)
{
  Frame *f;

  if (frame == NULL) {
	if (top_frame == NULL) {
      frame_errmsg = "no frame present";
	  return(NULL);
	}
	return(top_frame->name);
  }

  f = (Frame *) frame;
  return(f->name);
}

unsigned int
fm_set_default_frame_flags(unsigned int new_flags)
{
  unsigned int old_flags;

  old_flags = default_frame_flags;
  default_frame_flags = new_flags;

  return(old_flags);
}

unsigned int
fm_get_default_frame_flags(void)
{

  return(default_frame_flags);
}

unsigned int
fm_get_frame_flags(void *frame)
{
  Frame *f;
  
  if (frame == NULL) {
	if (top_frame == NULL) {
      frame_errmsg = "no frame present";
	  return(0);
	}
	f = top_frame;
  }
  else
	f = (Frame *) frame;

  if (f->src == NULL)
	return(0);

  return(f->src->flags);
}

/*
 *
 */
unsigned int
fm_set_frame_flags(void *frame, unsigned int new_flags)
{
  unsigned int old_flags;
  Frame *f;
  
  if (frame == NULL) {
	if (top_frame == NULL) {
      frame_errmsg = "no frame present";
	  return(0);
	}
	f = top_frame;
  }
  else
	f = (Frame *) frame;

  if (f->src == NULL)
	return(0);

  old_flags = f->src->flags;

  f->src->flags = new_flags & FM_FRAME_FLAG_MASK;

  return(old_flags);
}

/*
 * Make FRAME, a previously popped frame, the top frame.
 */
int
fm_push_frame(void *frame)
{
  int rc;
  Frame *f;

  frame_errmsg = NULL;
  if (frame == NULL) {
    frame_errmsg = "bad argument";
	return(-1);
  }

  f = (Frame *) frame;
  rc = push(f);

  return(rc);
}

/*
 * Remove the top frame and return it.
 */
void *
fm_pop_frame(void)
{
  Frame *f;
  
  frame_errmsg = NULL;
  if (top_frame == NULL)
	return(NULL);

  f = top_frame;

  top_frame = top_frame->next;

  f->popped = 1;
  f->next = popped_frames;
  popped_frames = f;

  return((void *) f);
}

/*
 * Exchange the top-most two frames.
 */
int
fm_exchange_frames(void)
{
  Frame *new_top, *old_top;

  frame_errmsg = NULL;
  if (top_frame == NULL || top_frame->next == NULL)
	return(-1);

  new_top = top_frame->next;
  old_top = top_frame;

  old_top->next = (top_frame->next)->next;
  new_top->next = old_top;
  top_frame = new_top;
  return(0);
}

/*
 * Combine allocations on FRAME1 with allocations on FRAME2, leaving
 * FRAME1 and destroying FRAME2.
 * If either is NULL, it refers to the top frame.
 */
int
fm_unite_frames(void *frame1, void *frame2)
{
  Frame *f1, *f2;
  MemChunk *mc;

  frame_errmsg = NULL;

  if (frame1 == NULL) {
	if (top_frame == NULL) {
      frame_errmsg = "no frame";
	  return(-1);
	}
	f1 = top_frame;
  }
  else
	f1 = (Frame *) frame1;

  if (frame2 == NULL) {
	if (top_frame == NULL) {
      frame_errmsg = "no frame";
	  return(-1);
	}
	f2 = top_frame;
  }
  else
	f2 = (Frame *) frame2;

  if (frame1 == frame2) {
    frame_errmsg = "no frame";
	return(-1);
  }

  for (mc = f1->chunks; mc != NULL && mc->next != NULL; mc = mc->next)
	;
  if (mc == NULL)
	f1->chunks = f2->chunks;
  else
	mc->next = f2->chunks;

  if (f2->name != NULL) {
	FREE(f2->name);
	f2->name = NULL;
  }

  if (f2 == top_frame)
	top_frame = f2->next;

  FREE(f2);

  return(0);
}

/*
 * If PTR is NULL, free the top frame's allocations and destroy it.
 * Otherwise, the frame must consist of individually freeable allocations
 * so just free the given one.
 */
int
fm_free(void *ptr)
{
  int rc;
  Frame *f_next;

  frame_errmsg = NULL;
  if (top_frame == NULL)
	return(0);

  if (ptr != NULL)
	return(mem_free(ptr));

  f_next = top_frame->next;

  rc = free_frame(top_frame);

  top_frame = f_next;

  return(rc);
}

/*
 * Close FRAME, which must either be popped or be the top frame.
 * If it's NULL, use the top frame.
 */
int
fm_close_frame(void *frame)
{
  int rc;
  Frame *f;

  frame_errmsg = NULL;

  if (frame == NULL)
	f = top_frame;
  else {
	f = (Frame *) frame;
	if (f != top_frame && f->popped == 0) {
      frame_errmsg = "frame not popped";
	  return(-1);
	}
	push(f);
  }

  top_frame = f->next;

  rc = close_frame(f, 0);
  if (rc != 0)
	return(rc);

  return(rc);
}

/*
 * Close all frames on the stack.
 */
int
fm_close_frames(void)
{
  int rc;
  Frame *f, *f_next;

  frame_errmsg = NULL;
  if (top_frame == NULL)
	return(0);

  rc = 0;
  for (f = top_frame; f != NULL; f = f_next) {
	f_next = f->next;
	if (close_frame(f, 0) == -1)
	  rc = -1;
  }

  top_frame = NULL;
  return(rc);
}

/*
 * Close all pushed frames.
 */
int
fm_close_all_frames(void)
{
  int rc;
  Frame *f, *f_next;

  frame_errmsg = NULL;
  rc = 0;

  for (f = top_frame; f != NULL; f = f_next) {
	f_next = f->next;
	if (close_frame(f, 0) == -1)
	  rc = -1;
	FREE(f);
  }
  top_frame = NULL;

  for (f = popped_frames; f != NULL; f = f_next) {
	f_next = f->next;
	if (close_frame(f, 0) == -1)
		rc = -1;
  }
  popped_frames = NULL;
  return(0);
}

/*
 * Free FRAME's allocations and destroy it.
 * FRAME must have already been popped.
 */
int
fm_free_frame(void *frame)
{
  int rc;
  Frame *f;

  frame_errmsg = NULL;

  if (frame == NULL)
	return(fm_free(NULL));

  f = (Frame *) frame;
  if (f->popped == 0) {
    frame_errmsg = "frame not popped";
	return(-1);
  }

  rc = free_frame(f);
  return(rc);
}

/*
 * Free all pushed frames.
 */
int
fm_free_frames(void)
{
  int rc;
  Frame *f, *f_next;

  frame_errmsg = NULL;
  if (top_frame == NULL)
	return(0);

  rc = 0;
  for (f = top_frame; f != NULL; f = f_next) {
	f_next = f->next;
	if (free_frame(f) == -1)
	  rc = -1;
  }

  top_frame = NULL;
  return(rc);
}

/*
 * Free all frames, even popped ones.
 */
int
fm_free_all_frames(void)
{
  int rc;
  Frame *f, *f_next;

  frame_errmsg = NULL;
  rc = 0;

  for (f = top_frame; f != NULL; f = f_next) {
	f_next = f->next;
	if (free_frame(f) == -1)
	  rc = -1;
  }
  top_frame = NULL;

  for (f = popped_frames; f != NULL; f = f_next) {
	f_next = f->next;
	if (free_frame(f) == -1)
		rc = -1;
  }
  popped_frames = NULL;
  return(0);
}

/*
 * Allocate from the current frame, creating a new one if necessary.
 * If there is no top frame or it isn't associated with a db, the
 * system malloc() is used.
 */
void *
fm_malloc(size_t req_size)
{
  Fm_mem_src *src;

  frame_errmsg = NULL;
  if (top_frame == NULL)
	src = NULL;
  else
	src = top_frame->src;

  return(alloc_on_frame(top_frame, src, req_size));
}

void *
fm_malloc_src(Fm_mem_src *src, size_t req_size)
{

  return(alloc_on_frame(NULL, src, req_size));
}

/*
 * Allocate from FRAME.
 * If FRAME is NULL, use (or create) the top frame.
 */
void *
fm_frame_malloc(void *frame, size_t req_size)
{
  Fm_mem_src *src;
  Frame *f;

  frame_errmsg = NULL;
  f = (Frame *) frame;
  if (f == NULL)
	src = NULL;
  else
	src = f->src;

  return(alloc_on_frame(f, src, req_size));
}

void *
fm_frame_malloc_src(void *frame, Fm_mem_src *src, size_t req_size)
{
  Frame *f;

  frame_errmsg = NULL;
  f = (Frame *) frame;

  return(alloc_on_frame(f, src, req_size));
}

/*
 * Allocate memory from the current frame for an array of NMEMB elements,
 * each of SIZE bytes.  Zero the memory.
 */
void *
fm_calloc(size_t nmemb, size_t req_memb_size)
{
  Fm_mem_src *src;

  frame_errmsg = NULL;
  if (top_frame == NULL)
	src = NULL;
  else
	src = top_frame->src;

  return(calloc_on_frame(NULL, src, nmemb, req_memb_size));
}

void *
fm_calloc_src(Fm_mem_src *src, size_t nmemb, size_t req_memb_size)
{

  frame_errmsg = NULL;

  return(calloc_on_frame(NULL, src, nmemb, req_memb_size));
}

/*
 * Allocate, via calloc(), from FRAME.
 * If FRAME is NULL, use (or create) the top frame.
 */
void *
fm_frame_calloc(void *frame, size_t nmemb, size_t req_memb_size)
{
  Fm_mem_src *src;
  Frame *f;

  frame_errmsg = NULL;
  f = (Frame *) frame;
  if (f == NULL)
	src = NULL;
  else
	src = f->src;

  return(calloc_on_frame(f, src, nmemb, req_memb_size));
}

void *
fm_frame_calloc_src(void *frame, Fm_mem_src *src, size_t nmemb,
					size_t req_memb_size)
{
  Frame *f;

  frame_errmsg = NULL;
  f = (Frame *) frame;

  return(calloc_on_frame(f, src, nmemb, req_memb_size));
}

/*
 * Reallocate memory.
 * If there is no current frame or allocation on that frame, this is
 * equivalent to fm_malloc().
 */
void *
fm_realloc(void *ptr, size_t req_size)
{
  Fm_mem_src *src;

  frame_errmsg = NULL;
  if (ptr == NULL)
	return(fm_malloc(req_size));

  /* This is equivalent to free(3). */
  if (req_size == 0)
	return(NULL);

  if (top_frame == NULL)
	src = NULL;
  else
	src = top_frame->src;

  return(mem_realloc(NULL, src, ptr, req_size));
}

void *
fm_realloc_src(Fm_mem_src *src, void *ptr, size_t req_size)
{

  frame_errmsg = NULL;

  if (ptr == NULL)
	return(fm_malloc_src(src, req_size));

  /* This is equivalent to free(3). */
  if (req_size == 0)
	return(NULL);

  return(mem_realloc(NULL, src, ptr, req_size));
}

void *
fm_frame_realloc(void *frame, void *ptr, size_t req_size)
{
  Fm_mem_src *src;
  Frame *f;

  frame_errmsg = NULL;
  if (ptr == NULL)
	return(fm_frame_malloc(frame, req_size));

  /* This is equivalent to free(3). */
  if (req_size == 0)
	return(NULL);

  f = (Frame *) frame;
  if (f == NULL)
	src = NULL;
  else
	src = f->src;

  return(mem_realloc(f, src, ptr, req_size));
}

void *
fm_frame_realloc_src(void *frame, Fm_mem_src *src, void *ptr, size_t req_size)
{
  Frame *f;

  frame_errmsg = NULL;
  if (ptr == NULL)
	return(fm_frame_malloc_src(frame, src, req_size));

  /* This is equivalent to free(3). */
  if (req_size == 0)
	return(NULL);

  f = (Frame *) frame;
  return(mem_realloc(f, src, ptr, req_size));
}

/* XXX */
char *
fm_strdup(const char *str)
{
  char *p;
  size_t len;

  len = strlen(str);
  p = (char *) fm_malloc(len + 1);
  memcpy(p, str, len + 1);

  return(p);
}

#ifdef PROG

#include "mkargv.h"

int
main(int argc, char **argv)
{
  int i, pcount, quiet, n, st;
  unsigned int flags;
  char *p, *str;
  char **a, buf[1024];
  void *ptrs[1000];

  flags = 0;
  pcount = 0;
  quiet = 0;

  for (i = 1; i < argc; i++) {
	if (streq(argv[1], "-q"))
	  quiet = 1;
	else {
	  fprintf(stderr, "?\n");
	  exit(1);
	}
  }

  while (1) {
	if (!quiet)
	  fprintf(stderr, "> ");
	if (fgets(buf, sizeof(buf), stdin) == NULL)
	  break;
	if ((p = strchr(buf, '\n')) == NULL)
	  break;
	*p = '\0';
	for (p = buf; *p != '\0'; p++) {
	  if (*p != ' ' && *p != '\t')
		break;
	}
	if (*p == '#')
	  continue;
	if ((n = mkargv(p, NULL, &a)) < 1)
	  continue;

	st = -1;
	if (streq(a[0], "new")) {
	  st = fm_push_new_frame((a[1] == NULL) ? "" : a[1], NULL, flags);
	}
	else if (streq(a[0], "freef")) {
	  st = fm_free_frame(NULL);
	}
	else if (streq(a[0], "free") && n == 2) {
	  int x;

	  x = atoi(a[1]);
	  if (x >= pcount)
		st = -1;
	  else {
		st = fm_free(ptrs[x]);
		if (st == 0)
		  ptrs[x] = NULL;
	  }
	}
	else if (streq(a[0], "malloc") && n == 2) {
	  if ((ptrs[pcount] = fm_malloc((size_t) atoi(a[1]))) == NULL)
		st = -1;
	  else {
		if (!quiet)
		  fprintf(stderr, "%d\n", pcount);
		pcount++;
		st = 0;
	  }
	}
	else if (streq(a[0], "calloc") && n == 3) {
	  if ((ptrs[pcount]
		   = fm_calloc((size_t) atoi(a[1]), (size_t) atoi(a[2]))) == NULL)
		st = -1;
	  else {
		if (!quiet)
		  fprintf(stderr, "%d\n", pcount);
		pcount++;
		st = 0;
	  }
	}
	else if (streq(a[0], "realloc") && n == 3) {
	  int x;

	  x = atoi(a[1]);
	  if (x >= pcount)
		st = -1;
	  else {
		if ((ptrs[x] = fm_realloc(ptrs[x], atoi(a[2]))) == NULL)
		  st = -1;
		else
		  st = 0;
	  }
	}
	else if (streq(a[0], "memset") && n == 4) {
	  int x;

	  x = atoi(a[1]);
	  if (x >= pcount)
		st = -1;
	  else {
		memset(ptrs[x], a[2][0], atoi(a[3]));
		st = 0;
	  }
	}
	else if (streq(a[0], "set") && n == 2) {
	  st = 0;
	  if (streq(a[1], "unit_free"))
		flags |= FM_FRAME_UNIT_FREE;
	  else if (streq(a[1], "no_unit_free"))
		flags &= (~FM_FRAME_UNIT_FREE);
	  else if (streq(a[1], "zap_flag"))
		flags |= FM_FRAME_MEM_ZAP;
	  else if (streq(a[1], "no_zap_flag"))
		flags &= (~FM_FRAME_MEM_ZAP);
	  else
		st = -1;
	}
	else if (streq(a[0], "reset")) {
	  st = 0;
	  flags = 0;
	  if (n == 1)
		pcount = 0;
	  else if (streq(a[1], "flags"))
		flags = 0;
	  else
		st = -1;
	}
	else if (streq(a[0], "quit"))
	  break;
	else if (streq(a[0], "quiet")) {
	  quiet = 1;
	  st = 0;
	}
	else if (streq(a[0], "show")) {
	  mem_show();
	  st = 0;
	}
	else if (streq(a[0], "echo")) {
	  st = 0;
	  for (i = 1; i < (n - 1); i++)
		fprintf(stderr, "%s ", a[i]);
	  if (n > 1)
		fprintf(stderr, "%s", a[i]);
	  fprintf(stderr, "\n");
	}

	if (st == -1)
	  fprintf(stderr, "?\n");
  }

  exit(0);
}
#endif
