Index: /trunk/Ohana/src/libdvo/include/dvo.h
===================================================================
--- /trunk/Ohana/src/libdvo/include/dvo.h	(revision 31662)
+++ /trunk/Ohana/src/libdvo/include/dvo.h	(revision 31663)
@@ -78,5 +78,10 @@
 # define LOAD_MISS 	0x04
 # define LOAD_SECF 	0x08 
-# define LOAD_MEAS_META 0x10
+# define SKIP_AVES 	0x10
+# define SKIP_MEAS 	0x20
+# define SKIP_MISS 	0x40
+# define SKIP_SECF 	0x80
+
+// # define LOAD_MEAS_META 0x100 -- is this used??
 
 /* photometry code types */
Index: /trunk/Ohana/src/libdvo/src/coordops.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/coordops.c	(revision 31662)
+++ /trunk/Ohana/src/libdvo/src/coordops.c	(revision 31663)
@@ -440,6 +440,6 @@
 int GetCoords (Coords *coords, Header *header) {
   
-  int status, itmp, Polynomial, Polyterm;
-  double Lambda, rotate, scale;
+  int status, status1, status2, itmp, Polynomial, Polyterm;
+  double Lambda, rotate, rotate1, rotate2, scale;
   double equinox;
   char *ctype;
@@ -456,5 +456,5 @@
   mode = COORD_TYPE_NONE;
   {    
-    int haveCTYPE, haveCDELT, haveCROTA, haveCDij, havePCij, haveRAo;
+    int haveCTYPE, haveCDELT, haveCROTA, haveCROTA1, haveCROTA2, haveCDij, havePCij, haveRAo;
     float tmp;
     char stmp[80];
@@ -463,11 +463,14 @@
     // set: (CDELTi + CROTAi), (CDELTi + PCij), (CDij), 
 
-    haveCTYPE = gfits_scan (header, "CTYPE2",   "%s", 1, stmp);
-    haveCDELT = gfits_scan (header, "CDELT1",   "%f", 1, &tmp);
-    haveCROTA = gfits_scan (header, "CROTA1",   "%f", 1, &tmp);
-    haveCDij  = gfits_scan (header, "CD1_1",    "%f", 1, &tmp);
-    havePCij  = gfits_scan (header, "PC001001", "%f", 1, &tmp);
-    haveRAo   = gfits_scan (header, "RA_O",     "%f", 1, &tmp);
+    haveCTYPE  = gfits_scan (header, "CTYPE2",   "%s", 1, stmp);
+    haveCDELT  = gfits_scan (header, "CDELT1",   "%f", 1, &tmp);
+    haveCROTA1 = gfits_scan (header, "CROTA1",   "%f", 1, &tmp);
+    haveCROTA2 = gfits_scan (header, "CROTA2",   "%f", 1, &tmp);
+    haveCDij   = gfits_scan (header, "CD1_1",    "%f", 1, &tmp);
+    havePCij   = gfits_scan (header, "PC001001", "%f", 1, &tmp);
+    haveRAo    = gfits_scan (header, "RA_O",     "%f", 1, &tmp);
     
+    haveCROTA = haveCROTA1 || haveCROTA2;
+
     if (haveCTYPE && havePCij  && haveCDELT) { mode = COORD_TYPE_PC;   goto gotit; }
     if (haveCTYPE && haveCROTA && haveCDELT) { mode = COORD_TYPE_ROT;  goto gotit; }
@@ -540,5 +543,12 @@
       status &= gfits_scan (header, "CDELT2", "%f", 1, &coords[0].cdelt2);
 
-      status &= gfits_scan (header, "CROTA2", "%lf", 1, &rotate);
+      status1 = gfits_scan (header, "CROTA1", "%lf", 1, &rotate1);
+      status2 = gfits_scan (header, "CROTA2", "%lf", 1, &rotate2);
+      status &= status1 || status2;
+
+      rotate = rotate2;
+      if (status1 && !status2) rotate = rotate1;
+      if (!status1 && !status2) rotate = 0.0;
+
       Lambda = coords[0].cdelt2 / coords[0].cdelt1;
       coords[0].pc1_1 =  cos(rotate*RAD_DEG);
Index: /trunk/Ohana/src/libdvo/src/dvo_catalog.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_catalog.c	(revision 31662)
+++ /trunk/Ohana/src/libdvo/src/dvo_catalog.c	(revision 31663)
@@ -90,4 +90,7 @@
   catalog[0].secfilt = NULL;
   
+  catalog[0].averageT = NULL;
+  catalog[0].measureT = NULL; 
+
   catalog[0].objID = 0;
   catalog[0].catID = 0;
Index: /trunk/Ohana/src/libdvo/src/dvo_catalog_split.c
===================================================================
--- /trunk/Ohana/src/libdvo/src/dvo_catalog_split.c	(revision 31662)
+++ /trunk/Ohana/src/libdvo/src/dvo_catalog_split.c	(revision 31663)
@@ -172,4 +172,5 @@
   header.buffer = NULL;
   primary = NULL;
+  status = FALSE;
 
   /* get the components from the header - these duplicate information in the split files (NAXIS2) */
@@ -239,10 +240,13 @@
 
   /*** Measure Table ***/
-  status = dvo_catalog_open_subcat (catalog, &catalog[0].measure_catalog, ftable.header, "MEASURE", VERBOSE);
-  if (status == DVO_CAT_OPEN_FAIL) {
-    return (FALSE);
-  }
-  if ((status == DVO_CAT_OPEN_EMPTY) && (catalog[0].Nmeas_disk > 0)) {
-    return (FALSE);
+  if (!(catalog[0].catflags & SKIP_MEAS)) {
+    // unless we specify 'skip', we still need to load the 
+    status = dvo_catalog_open_subcat (catalog, &catalog[0].measure_catalog, ftable.header, "MEASURE", VERBOSE);
+    if (status == DVO_CAT_OPEN_FAIL) {
+      return (FALSE);
+    }
+    if ((status == DVO_CAT_OPEN_EMPTY) && (catalog[0].Nmeas_disk > 0)) {
+      return (FALSE);
+    }
   }
   if ((status != DVO_CAT_OPEN_EMPTY) && (catalog[0].catflags & LOAD_MEAS)) {
@@ -262,4 +266,11 @@
   } else {
     // XXX is it necessary to generate a template header here?
+    // XXX this is a memory leak, right?
+    if (catalog[0].measure_catalog) {
+      gfits_free_header (&catalog[0].measure_catalog[0].header);
+    } else {
+      ALLOCATE (catalog[0].measure_catalog, Catalog, 1);
+      dvo_catalog_init (catalog[0].measure_catalog, TRUE);
+    }
     gfits_create_header (&catalog[0].measure_catalog[0].header);
     ALLOCATE (catalog[0].measure, Measure, 1);
Index: /trunk/Ohana/src/libfits/Makefile
===================================================================
--- /trunk/Ohana/src/libfits/Makefile	(revision 31662)
+++ /trunk/Ohana/src/libfits/Makefile	(revision 31663)
@@ -54,5 +54,5 @@
 $(TABL)/F_set_column.$(ARCH).o			$(TABL)/F_get_column.$(ARCH).o   \
 $(TABL)/F_table_row.$(ARCH).o			$(TABL)/F_free_T.$(ARCH).o       \
-$(TABL)/F_table_varlength.$(ARCH).o
+$(TABL)/F_table_varlength.$(ARCH).o		$(TABL)/F_copy_T.$(ARCH).o
 
 EXTERN_OBJ = \
Index: /trunk/Ohana/src/libfits/include/gfitsio.h
===================================================================
--- /trunk/Ohana/src/libfits/include/gfitsio.h	(revision 31662)
+++ /trunk/Ohana/src/libfits/include/gfitsio.h	(revision 31663)
@@ -63,6 +63,6 @@
   Header                 *header;
   char                   *buffer;
-  off_t                   datasize;
-  off_t                   validsize;
+  off_t                   datasize; // size of the buffer (including block padding at the end)
+  off_t                   validsize;  // size of the valid portion of the table (< validsize if file is short)
 } FTable;
 
@@ -199,4 +199,6 @@
 int     gfits_vtable_from_ftable       PROTO((FTable *ftable, VTable *vtable, off_t *row, off_t Nrow));
 int     gfits_write_table              PROTO((char *filename, FTable *ftable)); 
+int     gfits_copy_ftable              PROTO((FTable *in, FTable *out));
+int     gfits_copy_vtable              PROTO((VTable *in, VTable *out));
 
 int     gfits_create_Theader           PROTO((Header *header, char *type));
Index: /trunk/Ohana/src/libfits/matrix/F_copy_M.c
===================================================================
--- /trunk/Ohana/src/libfits/matrix/F_copy_M.c	(revision 31662)
+++ /trunk/Ohana/src/libfits/matrix/F_copy_M.c	(revision 31663)
@@ -3,21 +3,21 @@
 
 /*********************** fits copy header ***********************************/
-int gfits_copy_matrix (Matrix *matrix1, Matrix *matrix2) {
+int gfits_copy_matrix (Matrix *in, Matrix *out) {
 
   int i;
 
-  matrix2[0].unsign   = matrix1[0].unsign;
-  matrix2[0].bitpix   = matrix1[0].bitpix;
-  matrix2[0].datasize = matrix1[0].datasize;
-  matrix2[0].bzero    = matrix1[0].bzero;
-  matrix2[0].bscale   = matrix1[0].bscale;
-  matrix2[0].Naxes    = matrix1[0].Naxes;
+  out[0].unsign   = in[0].unsign;
+  out[0].bitpix   = in[0].bitpix;
+  out[0].datasize = in[0].datasize;
+  out[0].bzero    = in[0].bzero;
+  out[0].bscale   = in[0].bscale;
+  out[0].Naxes    = in[0].Naxes;
   for (i = 0; i < FT_MAX_NAXES; i++) 
-    matrix2[0].Naxis[i] = matrix1[0].Naxis[i];
+    out[0].Naxis[i] = in[0].Naxis[i];
 
-  if (matrix2[0].buffer != NULL) free (matrix2[0].buffer);
-  ALLOCATE (matrix2[0].buffer, char, matrix2[0].datasize);
+  if (out[0].buffer != NULL) free (out[0].buffer);
+  ALLOCATE (out[0].buffer, char, out[0].datasize);
   
-  memcpy (matrix2[0].buffer, matrix1[0].buffer, matrix2[0].datasize);
+  memcpy (out[0].buffer, in[0].buffer, out[0].datasize);
 
   return (TRUE);
Index: /trunk/Ohana/src/libfits/table/F_copy_T.c
===================================================================
--- /trunk/Ohana/src/libfits/table/F_copy_T.c	(revision 31663)
+++ /trunk/Ohana/src/libfits/table/F_copy_T.c	(revision 31663)
@@ -0,0 +1,45 @@
+# include <ohana.h>
+# include <gfitsio.h>
+
+/*********************** copy ftable (does not copy the header pointer) ***********************************/
+int gfits_copy_ftable (FTable *in, FTable *out) {
+
+  /* find buffer size */
+  out[0].validsize = in[0].validsize;
+  out[0].datasize  = in[0].datasize;
+
+  ALLOCATE (out[0].buffer, char, out[0].datasize);
+  memcpy (out[0].buffer, in[0].buffer, out[0].datasize);
+  return (TRUE);
+}	
+
+/*********************** copy ftable (does not copy the header pointer) ***********************************/
+int gfits_copy_vtable (VTable *in, VTable *out) {
+
+  off_t i, Nx, Ny, Nrows;
+
+  /* find buffer size */
+  Nx = in[0].header[0].Naxis[0];
+  Ny = in[0].header[0].Naxis[1];
+
+  // validate these two?
+  // table[0].datasize = gfits_data_size (table[0].header);
+  // table[0].pad = table[0].datasize - Nx*Ny;
+
+  /* find buffer size */
+  out[0].datasize = in[0].datasize;
+  out[0].pad      = in[0].pad;
+  out[0].Nrow     = in[0].Nrow;
+
+  Nrows = out[0].Nrow;
+
+  ALLOCATE (out[0].row, off_t, MAX (Nrows, 1));
+  ALLOCATE (out[0].buffer, char *, MAX (Nrows, 1));
+  for (i = 0; i < Nrows; i++) {
+    out[0].row[i] = in[0].row[i];
+    ALLOCATE (out[0].buffer[i], char, MAX (Nx, 1));
+    memcpy (out[0].buffer[i], in[0].buffer[i], Nx);
+  }
+  return (TRUE);
+}	
+
Index: /trunk/Ohana/src/libkapa/src/bDrawFuncs.c
===================================================================
--- /trunk/Ohana/src/libkapa/src/bDrawFuncs.c	(revision 31662)
+++ /trunk/Ohana/src/libkapa/src/bDrawFuncs.c	(revision 31663)
@@ -78,8 +78,10 @@
 
 void bDrawSetStyle (bDrawBuffer *buffer, bDrawColor color, int lw, int lt) {
-  buffer->bColor = color;
-  buffer->bColor_R = buffer->palette[color].red;
-  buffer->bColor_G = buffer->palette[color].green;
-  buffer->bColor_B = buffer->palette[color].blue;
+  if (color >= 0) {
+    buffer->bColor = color;
+    buffer->bColor_R = buffer->palette[color].red;
+    buffer->bColor_G = buffer->palette[color].green;
+    buffer->bColor_B = buffer->palette[color].blue;
+  }
   buffer->bWeight = lw;
   buffer->bType = lt;
Index: /trunk/Ohana/src/libohana/Makefile
===================================================================
--- /trunk/Ohana/src/libohana/Makefile	(revision 31662)
+++ /trunk/Ohana/src/libohana/Makefile	(revision 31663)
@@ -53,9 +53,13 @@
 
 TEST = \
-$(TESTDIR)/string.$(ARCH)
+$(TESTDIR)/memtest.$(ARCH)
+# $(TESTDIR)/string.$(ARCH)
 
 testcode: install $(TEST)
 test:
 	$(MAKE) testcode
+	for i in $(TEST); do $$i; done
+
+quicktest: 
 	for i in $(TEST); do $$i; done
 
Index: /trunk/Ohana/src/libohana/include/ohana.h
===================================================================
--- /trunk/Ohana/src/libohana/include/ohana.h	(revision 31662)
+++ /trunk/Ohana/src/libohana/include/ohana.h	(revision 31663)
@@ -19,4 +19,7 @@
 # include <readline/readline.h>
 
+// comment this out to avoid the internal Ohana memory management code
+// # define OHANA_MEMORY
+
 // XXX I was including these before, but RHL claims they are not needed
 // # include <malloc.h>
@@ -234,4 +237,5 @@
 int     scan_line              PROTO((FILE *f, char *line)); 
 int     dparse                 PROTO((double *X, int NX, char *line));
+int     dparse_csv             PROTO((double *X, int NX, char *line));
 int     fparse                 PROTO((float *X, int NX, char *line));
 int     get_argument           PROTO((int argc, char **argv, char *arg));
Index: /trunk/Ohana/src/libohana/include/ohana_allocate.h
===================================================================
--- /trunk/Ohana/src/libohana/include/ohana_allocate.h	(revision 31662)
+++ /trunk/Ohana/src/libohana/include/ohana_allocate.h	(revision 31663)
@@ -1,38 +1,56 @@
 # ifndef OHANA_ALLOCATE
 # define OHANA_ALLOCATE
+
+typedef struct {
+  int    exists; // has the memory management system been created?
+  size_t Ntotal; // number of blocks currently allocated
+  size_t Nbytes; // number of bytes currently allocated
+  size_t Ngood;  // number of good blocks
+  size_t Nbad;   // number of bad blocks
+} OhanaMemstats;
+
+OhanaMemstats ohana_memstats (int mode);
 
 /* default is to use basic system memory functions */
 # ifdef OHANA_MEMORY
 
-void *ohana_malloc (char *file, int line, int Nelem, size_t esize);
-void *ohana_realloc (char *file, int line, void *in, int Nelem, size_t esize);
-void  ohana_free (char *file, int line, void *in);
-void  ohana_memregister_func (char *file, int line, void *ptr);
+void *ohana_malloc (const char *file, int line, const char *func, size_t Nelem, size_t esize);
+void *ohana_realloc (const char *file, int line, const char *func, void *in, size_t Nelem, size_t esize);
+void  ohana_free (const char *file, int line, const char *func, void *in);
 void  ohana_memdump_func (int mode);
 void  ohana_memcheck_func (int mode);
+void  real_free (void *in);
 
-# define ohana_memregister(X) ohana_memregister_func (__FILE__, __LINE__, (X));
 # define ohana_memcheck(X) ohana_memcheck_func (X);
 # define ohana_memdump(X) ohana_memdump_func (X);
 
-# define ALLOCATE(PTR,TYPE,SIZE) \
-  { PTR = (TYPE *) ohana_malloc (__FILE__, __LINE__, (SIZE), sizeof(TYPE)) }
-# define ALLOCATE_ZERO(PTR,TYPE,SIZE)					\
-  { PTR = (TYPE *) ohana_malloc (__FILE__, __LINE__, (SIZE), sizeof(TYPE)); memset (PTR, 0, (SIZE)*sizeof(TYPE)); }
-# define REALLOCATE(PTR,TYPE,SIZE) \
-  { PTR = (TYPE *) ohana_realloc(__FILE__, __LINE__, PTR, (SIZE), sizeof(TYPE)); }
-# define CHECK_REALLOCATE(PTR,TYPE,SIZE,NCURR,DELTA) {			\
-  if ((NCURR) >= (SIZE)) { SIZE += DELTA;				\
-    PTR = (TYPE *) ohana_realloc(__FILE__, __LINE__, PTR, (SIZE), sizeof(TYPE)); } }
-# define FREE(PTR) { if (PTR != NULL) { ohana_free (__FILE__, __LINE__, PTR); } }
-# define free(PTR) { ohana_free(__FILE__, __LINE__, PTR); }
+# define ALLOCATE(PTR,TYPE,SIZE) { \
+    PTR = (TYPE *) ohana_malloc (__FILE__, __LINE__, __func__, (SIZE), sizeof(TYPE)); \
+  }
 
-# else 
-# define ohana_memregister(X) /* NOP */
+# define ALLOCATE_ZERO(PTR,TYPE,SIZE) { \
+    PTR = (TYPE *) ohana_malloc (__FILE__, __LINE__, __func__, (SIZE), sizeof(TYPE)); \
+    memset (PTR, 0, (SIZE)*sizeof(TYPE)); \
+  }
+
+# define REALLOCATE(PTR,TYPE,SIZE) { \
+    PTR = (TYPE *) ohana_realloc(__FILE__, __LINE__, __func__, PTR, (SIZE), sizeof(TYPE)); \
+  }
+
+# define CHECK_REALLOCATE(PTR,TYPE,SIZE,NCURR,DELTA) { \
+  if ((NCURR) >= (SIZE)) { \
+    SIZE += DELTA; \
+    PTR = (TYPE *) ohana_realloc(__FILE__, __LINE__, __func__, PTR, (SIZE), sizeof(TYPE)); \
+  } }
+
+# define FREE(PTR) { if (PTR != NULL) { ohana_free (__FILE__, __LINE__, __func__, PTR); } }
+# define free(PTR) { ohana_free(__FILE__, __LINE__, __func__, PTR); }
+
+# else  /* below: not OHANA_MEMORY */
+
 # define ohana_memcheck(X) /* NOP */
 # define ohana_memdump(X) /* NOP */
-# endif /* OHANA_MEMORY */
+void  real_free (void *in);
 
-# ifndef ALLOCATE
 # define ALLOCATE(PTR,TYPE,SIZE) {					\
   PTR = (TYPE *) malloc ((size_t)(MAX(((SIZE)*((int)sizeof(TYPE))),1))); \
@@ -63,5 +81,5 @@
 
 # define FREE(PTR) { if (PTR != NULL) { free (PTR); } }
-# endif /* ALLOCATE */
+# endif /* OHANA_MEMORY */
 
 # endif /* OHANA_ALLOCATE */
Index: /trunk/Ohana/src/libohana/src/ohana_allocate.c
===================================================================
--- /trunk/Ohana/src/libohana/src/ohana_allocate.c	(revision 31662)
+++ /trunk/Ohana/src/libohana/src/ohana_allocate.c	(revision 31663)
@@ -2,4 +2,9 @@
 # include <stdlib.h>
 # include <stdarg.h>
+# include <stdint.h>
+# include <pthread.h>
+
+# undef OHANA_MEMORY
+# include "ohana_allocate.h"
 
 /* need an internal version that does not use ohana_memory functions */
@@ -7,51 +12,24 @@
 # define TRUE 1
 # define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
-# define OHANA_ALLOCATE(X,T,S)  \
-  X = (T *) malloc ((unsigned)(MAX(((S)*((int)sizeof(T))),1))); \
-  if (X == NULL) { \
-    fprintf(stderr,"failed malloc at %d in %s\n", __LINE__, __FILE__);\
-    abort(); } 
-# define OHANA_REALLOCATE(X,T,S) \
-  X = (T *) realloc(X,(unsigned)(MAX(((S)*((int)sizeof(T))),1))); \
-  if (X == NULL) { \
-    fprintf(stderr,"failed realloc at %d in %s\n", __LINE__, __FILE__);\
-    abort(); }
-# define OHANA_CHECK_REALLOCATE(X,T,S,N,D) \
-  if ((N) >= (S)) { \
-    S += D; \
-    X = (T *) realloc(X,(unsigned)(MAX(((S)*((int)sizeof(T))),1))); \
-    if (X == NULL) { \
-      fprintf(stderr,"failed realloc increment at %d in %s\n", __LINE__, __FILE__);\
-      abort(); } }
-# define OHANA_FREE(X) free(X)
-
-# define STATE_ALLOC    0
-# define STATE_REALLOC  1
-# define STATE_FREE     2
-# define STATE_EXTERNAL 3
-
-typedef struct {
-  char *file;
-  int   line;
-  void *ptr;   // location of externally visible entry
-  int   size;
-  int   state;
-} Memlist;
-
-// XXX consider fixing the memory tracking model (list?)
-// static long *memsort;
-// static long *memhash;
-static Memlist *memlist = NULL;
-int Nmemlist = 0;
-int NMEMLIST = 0;
-int NMEMBYTE = 0;
+
+# define OHANA_MEMMAGIC (uint32_t) 0xdeadbeef
+
+typedef struct Memblock {
+  uint32_t startblock;	      // endpost marker
+  struct Memblock *prevBlock; // previously allocated memory
+  struct Memblock *nextBlock; // next allocated memory
+  size_t size;		      // size of memory
+  const char *file;	      // file (re)allocated 
+  const char *func;	      // func (re)allocated 
+  int line;		      // line (re)allocated
+  uint32_t endblock;	      // endpost marker
+} Memblock;
+
+static Memblock *lastBlock = NULL;
+
+static pthread_mutex_t memBlockListMutex = PTHREAD_MUTEX_INITIALIZER;
 
 void ohana_meminit () {
-  Nmemlist = 0;
-  NMEMLIST = 1000;
-  OHANA_ALLOCATE (memlist, Memlist, NMEMLIST);
-  // OHANA_ALLOCATE (memsort, long, NMEMLIST);
-  // OHANA_ALLOCATE (memhash, long, NMEMLIST);
-  NMEMBYTE = sizeof(size_t);
+  return;
 }
 
@@ -65,134 +43,157 @@
 }
 
-void *ohana_malloc (char *file, int line, int Nelem, size_t esize) {
-
-  void *ptr, *new;
-  int size;
-  size_t *marker;
-
-  if (memlist == NULL) ohana_meminit ();
+void *ohana_malloc (const char *file, int line, const char *func, size_t Nelem, size_t esize) {
+
+  void *ptr;		      // actual user memory allocated
+  Memblock *new;	      // new memblock created to track the user memory
+  size_t size;		      // total number of bytes requested (not items)
 
   Nelem = MAX (1, Nelem);
   size = Nelem * esize;
 
-  new = malloc (size + 2*NMEMBYTE); /* 2 extra locations to save endposts */
-  if (new == NULL) ohana_memabort ("failed to allocate memory (%s, %d)\n", file, line);
-  ptr = new + NMEMBYTE;
-
-  marker = (size_t *) new;
-  *marker = 0xdeadbeef;
-  marker = (size_t *)(new + size + NMEMBYTE);
-  *marker = 0xdeadbeef;
-
-  /* new memory, add to stack */
-  memlist[Nmemlist].ptr  = ptr;
-  memlist[Nmemlist].file = file;
-  memlist[Nmemlist].line = line;
-  memlist[Nmemlist].size = size;
-  memlist[Nmemlist].state = STATE_ALLOC;
-
-  // before each free, we sort this pair to speed up searching
-  // memsort[Nmemlist] = ptr;
-  // memhash[Nmemlist] = Nmemlist;
-
-  Nmemlist ++;
-  if (Nmemlist == NMEMLIST) {
-    NMEMLIST += 1000;
-    OHANA_REALLOCATE (memlist, Memlist, NMEMLIST);
-  }
+  // total size is : memblock + data + endpost
+  new = (Memblock *) malloc (sizeof(Memblock) + size + sizeof(void *)); 
+  if (new == NULL) ohana_memabort ("failed to allocate memory (%s, %d, %s)\n", file, line, func);
+
+  // pointer to the start of the user memory
+  ptr = (char *)(new + 1);
+
+  // set the end-post values
+  new->startblock = OHANA_MEMMAGIC;
+  new->endblock = OHANA_MEMMAGIC;
+  *(uint32_t *)(ptr + size) = OHANA_MEMMAGIC;
+
+  // set the memory metadata
+  new->size = size;
+  new->file = file;
+  new->line = line;
+  new->func = func;
+
+  // new memblock becomes the 'lastBlock':
+  // lastBlock = new
+  // new->prev = (new - 1)
+  // (new - 1)->next = new
+
+  // for memory allocated in the order m0, m1, m2
+  // m0->next = m1, m1->next = m2, m2->next = NULL
+  // m2->prev = m1, m1->prev = m0, m0->prev = NULL
+  // lastBlock = m2
+
+  // set up the pointers
+  new->nextBlock = NULL;
+
+  // protect the list during this operation
+  pthread_mutex_lock(&memBlockListMutex);
+
+  if (lastBlock) {
+    lastBlock->nextBlock = new;
+  }
+  new->prevBlock = lastBlock;
+  lastBlock = new;
+
+  pthread_mutex_unlock(&memBlockListMutex);
+
   return (ptr);
 }
 
-void *ohana_realloc (char *file, int line, void *in, int Nelem, size_t esize) {
-
-  int i, size;
-  void *ptr, *ref, *new;
-  size_t *marker;
-
-  if (memlist == NULL) ohana_memabort ("REALLOCATE before ALLOCATE");
+void *ohana_realloc (const char *file, int line, const char *func, void *in, size_t Nelem, size_t esize) {
+
+  void *ptr;		      // actual user memory allocated
+  Memblock *old;	      // original memblock associated with user memory
+  Memblock *new;	      // new memblock associated with user memory
+  size_t size;
+
+  // just allocate if not previously allocated
+  if (!in) {
+    ptr = ohana_malloc (file, line, func, Nelem, esize);
+    return ptr;
+  }
+
+  // memblock of supplied pointer
+  old = (Memblock *) in - 1;
 
   Nelem = MAX (1, Nelem);
   size = Nelem * esize;
 
-  ref = in - NMEMBYTE;
-
-  /* find old entry, update ptr, file, line */
-  for (i = 0; i < Nmemlist; i++) {
-    if (memlist[i].state == STATE_FREE) continue;
-    if (memlist[i].ptr == in) {
-
-      if (memlist[i].state == STATE_EXTERNAL) ohana_memabort ("ERROR: realloc of external memory");
-
-      /* ask for new memory */
-      new = realloc (ref, size + 2*NMEMBYTE); /* 2 extra slots to save endposts */
-      if (new == NULL) ohana_memabort ("failed to reallocate memory (%s, %d)\n", file, line);
-      ptr = new + NMEMBYTE;
-      
-      /* set the marker */
-      marker = (size_t *) new;
-      *marker = 0xdeadbeef;
-      marker = (size_t *)(ptr + size);
-      *marker = 0xdeadbeef;
-
-      /* otherwise, update memory new location */
-      memlist[i].ptr = ptr;
-      memlist[i].size = size;
-      memlist[i].state = STATE_REALLOC;
-
-      /* if new memory in new location, update references */
-      if (ptr != in) {
-	memlist[i].file = file;
-	memlist[i].line = line;
-      }
-
-      return (ptr);
-    }
-  }
-  ohana_memabort ("allocated memory not found for realloc (%s, %d)\n", file, line);
-  return (NULL);
+  // requested same size as current allocation
+  if (size == old->size) {
+    return in;
+  }
+
+  pthread_mutex_lock(&memBlockListMutex);
+
+  Memblock *nextBlock = old->nextBlock;
+  Memblock *prevBlock = old->prevBlock;
+
+  int isLast = (old == lastBlock);
+
+  // ask for new memory
+  // total size is : memblock + data + endpost
+  new = (Memblock *) realloc (old, sizeof(Memblock) + size + sizeof(void *));
+  if (new == NULL) ohana_memabort ("failed to reallocate memory (%s, %d, %s)\n", file, line, func);
+  ptr = (char *) (new + 1);
+  
+  // give the realloc location:
+  new->size = size; 
+  new->file = file;
+  new->line = line;
+  new->func = func;
+
+  // update the endpost (the others were set originally
+  *(uint32_t *)(ptr + size) = OHANA_MEMMAGIC;
+
+  // need to reset lastBlock in case we moved
+  if (isLast) {
+    lastBlock = new;
+  }
+
+  // need to adjust the neighbors' pointers:
+  if (nextBlock) {
+    nextBlock->prevBlock = new;
+  }
+  if (prevBlock) {
+    prevBlock->nextBlock = new;
+  }
+  
+  pthread_mutex_unlock(&memBlockListMutex);
+
+  return (ptr);
+}
+
+void real_free (void *in) {
+
+  if (!in) return;
+  free (in);
+  return;
 }
 
 // this is very slow.  should we speed this up by indexing on the ptr?
-void ohana_free (char *file, int line, void *in) {
-
-  int i;
-
-  if (memlist == NULL) ohana_memabort ("FREE before ALLOCATE");
-
-  /* find old entry, set state */
-  for (i = 0; i < Nmemlist; i++) {
-    if (memlist[i].state == STATE_FREE) continue;
-    if (memlist[i].ptr == in) {
-      memlist[i].state = STATE_FREE;
-      return;
-    }
-  }
-
-  /* find already freed examples */
-  for (i = 0; i < Nmemlist; i++) {
-    if (memlist[i].ptr == in) {
-      fprintf (stderr, "used here: %4d %s %d\n", i, memlist[i].file, memlist[i].line);
-    }
-  }
-
-  ohana_memabort ("allocated memory not found for free (%s, %d : %x)\n", file, line, in);
-  return;
-}
-
-/* register externally allocated memory with ohana memory manager */
-void ohana_memregister_func (char *file, int line, void *ptr) {
-
-  if (memlist == NULL) ohana_meminit ();
-
-  /* add to stack */
-  memlist[Nmemlist].ptr  = ptr;
-  memlist[Nmemlist].file = file;
-  memlist[Nmemlist].line = line;
-  memlist[Nmemlist].state = STATE_EXTERNAL;
-  Nmemlist ++;
-  if (Nmemlist == NMEMLIST) {
-    NMEMLIST += 1000;
-    OHANA_REALLOCATE (memlist, Memlist, NMEMLIST);
-  }
+void ohana_free (const char *file, int line, const char *func, void *in) {
+
+  Memblock *ref;
+
+  if (!in) return;
+
+  ref = (Memblock *) in - 1;
+
+  pthread_mutex_lock(&memBlockListMutex);
+  
+  Memblock *nextBlock = ref->nextBlock;
+  Memblock *prevBlock = ref->prevBlock;
+
+  if (nextBlock) {
+    nextBlock->prevBlock = prevBlock;
+  }
+  if (prevBlock) {
+    prevBlock->nextBlock = nextBlock;
+  }
+  if (lastBlock == ref) {
+    lastBlock = prevBlock;
+  }
+  
+  pthread_mutex_unlock(&memBlockListMutex);
+  
+  free (ref);
+
   return;
 }
@@ -200,53 +201,48 @@
 void ohana_memcheck_func (int allmemory) {
 
-  int i, j, next, prev, header;
-  size_t *marker;
-  char top, bottom;
-
-  if (Nmemlist == 0) {
+  if (!lastBlock) {
     fprintf (stderr, "no memory allocated\n");
     return;
   }
 
-  header = FALSE;
-  fprintf (stderr, "checking %d memory blocks\n", Nmemlist);
-  for (i = 0; i < Nmemlist; i++) {
-    if (memlist[i].state == STATE_EXTERNAL) continue;
-    if ((allmemory == 0) && (memlist[i].state == STATE_FREE)) continue;
-
-    top = bottom = 'Y';
-    marker = (size_t *)(memlist[i].ptr - NMEMBYTE);
-    if (*marker == 0xdeadbeef) bottom = 'N';
-    marker = (size_t *)(memlist[i].ptr + memlist[i].size);
-    if (*marker == 0xdeadbeef) top = 'N';
+  Memblock *thisBlock = lastBlock;
+
+  size_t Ngood  = 0;
+  size_t Nbad   = 0;
+  size_t Ntotal = 0;
+  size_t Nbytes = 0;
+
+  while (thisBlock) {
+
+    int good = TRUE;
     
-    if ((top == 'N') && (bottom == 'N')) continue;
-    if (!header) {
-      fprintf (stderr, "         Nmem start end file            line\n");
-      header = TRUE;
+    if (thisBlock->startblock != OHANA_MEMMAGIC) good = FALSE;
+    if (thisBlock->endblock != OHANA_MEMMAGIC) good = FALSE;
+
+    // pointer to the start of the user memory
+    char *ptr = (char *)(thisBlock + 1) + thisBlock->size;
+    uint32_t endpost = *(uint32_t *)ptr;
+    if (endpost != OHANA_MEMMAGIC) good = FALSE;
+
+    // XXX keep checking even if memory is corrupted?
+    if (!good) {
+      if (Nbad < 1) {
+	fprintf (stderr, "memory corruption\n");
+      }
+      if (Nbad < 100) {
+	fprintf (stderr, "  file: %s, line: %d, func: %s\n", thisBlock->file, thisBlock->line, thisBlock->func);
+      }
+      Nbad ++;
+    } else {
+      Ngood ++;
     }
-    fprintf (stderr, "corrupt:  %3d   %c    %c  %-15s %3d\n", i, bottom, top, memlist[i].file, memlist[i].line);
-    prev = next = -1;
-    for (j = 0; j < Nmemlist; j++) {
-      if (memlist[j].ptr < memlist[i].ptr) {
-	if (prev == -1) prev = j;
-	if (memlist[j].ptr > memlist[prev].ptr) prev = j;
-      }
-      if (memlist[j].ptr > memlist[i].ptr) {
-	if (next == -1) next = j;
-	if (memlist[j].ptr < memlist[next].ptr) next = j;
-      }
-    }	  
-    if (prev == -1) {
-      fprintf (stderr, "no previous memory block\n");
-    } else {
-      fprintf (stderr, "prev:     %3d           %-15s %3d\n", prev, memlist[prev].file, memlist[prev].line);
-    }
-    if (next == -1) {
-      fprintf (stderr, "no next memory block\n");
-    } else {
-      fprintf (stderr, "next:     %3d           %-15s %3d\n", next, memlist[next].file, memlist[next].line);
-    }
-  }
+    Ntotal ++;
+    Nbytes += thisBlock->size;
+
+    thisBlock = thisBlock->prevBlock;
+  }
+ 
+  fprintf (stderr, "%zd memory blocks allocated (%zd bytes total), %zd good, %zd bad\n", Ntotal, Nbytes, Ngood, Nbad);
+
   return;
 }
@@ -254,39 +250,85 @@
 void ohana_memdump_func (int allmemory) {
 
-  int i, Ns[4], N;
-  char S[4];
-
-  if (Nmemlist == 0) {
+  if (!lastBlock) {
     fprintf (stderr, "no memory allocated\n");
     return;
   }
 
-  S[0] = 'A';
-  S[1] = 'R';
-  S[2] = 'F';
-  S[3] = 'X';
-  Ns[0] = Ns[1] = Ns[2] = Ns[3] = 0;
-  N = 0;
-
-  for (i = 0; i < Nmemlist; i++) {
-    if (memlist[i].state == STATE_ALLOC)    N = 0;
-    if (memlist[i].state == STATE_REALLOC)  N = 1;
-    if (memlist[i].state == STATE_FREE)     N = 2;
-    if (memlist[i].state == STATE_EXTERNAL) N = 2;
-    Ns[N] ++;
-    if ((allmemory == 0) && (memlist[i].state == STATE_FREE)) continue;
-    if (memlist[i].state == STATE_EXTERNAL) {
-      fprintf (stderr, "%3d %4d %lx : %lx - extern (extern     extern) %c %s %d\n", 
-	       Ns[N], i, (long)memlist[i].ptr, (long)memlist[i].ptr, 
-	       S[N], memlist[i].file, memlist[i].line);
+  Memblock *thisBlock = lastBlock;
+
+  size_t Ntotal = 0;
+  size_t Nbytes = 0;
+
+  fprintf (stderr, " entry | bytes | cumulative | STATUS | file | line | function\n");
+
+  while (thisBlock) {
+
+    int good = TRUE;
+    
+    if (thisBlock->startblock != OHANA_MEMMAGIC) good = FALSE;
+    if (thisBlock->endblock != OHANA_MEMMAGIC) good = FALSE;
+
+    // pointer to the start of the user memory
+    char *ptr = (char *)(thisBlock + 1) + thisBlock->size;
+    uint32_t endpost = *(uint32_t *)ptr;
+    if (endpost != OHANA_MEMMAGIC) good = FALSE;
+
+    // XXX keep checking even if memory is corrupted?
+    Ntotal ++;
+    Nbytes += thisBlock->size;
+
+    if (Ntotal < 100) {
+      if (good) {
+	fprintf (stderr, "  %zd  %zd  %zd  GOOD  %s %d, func: %s\n", Ntotal, thisBlock->size, Nbytes, thisBlock->file, thisBlock->line, thisBlock->func);
+      } else {
+	fprintf (stderr, "  %zd  %zd  %zd  BAD   %s %d, func: %s\n", Ntotal, thisBlock->size, Nbytes, thisBlock->file, thisBlock->line, thisBlock->func);
+      }
+    }
+
+    thisBlock = thisBlock->prevBlock;
+  }
+  fprintf (stderr, "%zd memory blocks allocated (%zd bytes total)\n", Ntotal, Nbytes);
+
+  return;
+}
+
+OhanaMemstats ohana_memstats (int allmemory) {
+
+  OhanaMemstats memstats;
+
+  memstats.exists = 0;
+  memstats.Ntotal = 0;
+  memstats.Nbytes = 0;
+  memstats.Ngood = 0;
+  memstats.Nbad = 0;
+  
+  Memblock *thisBlock = lastBlock;
+
+  while (thisBlock) {
+
+    memstats.exists = TRUE;
+
+    int good = TRUE;
+    
+    if (thisBlock->startblock != OHANA_MEMMAGIC) good = FALSE;
+    if (thisBlock->endblock != OHANA_MEMMAGIC) good = FALSE;
+
+    // pointer to the start of the user memory
+    char *ptr = (char *)(thisBlock + 1) + thisBlock->size;
+    uint32_t endpost = *(uint32_t *)ptr;
+    if (endpost != OHANA_MEMMAGIC) good = FALSE;
+
+    memstats.Ntotal ++;
+    memstats.Nbytes += thisBlock->size;
+
+    if (good) {
+      memstats.Ngood ++;
     } else {
-      fprintf (stderr, "%3d %4d %lx : %lx - %lx (%lx %lx) %c %s %d\n", 
-	       Ns[N], i, (long)memlist[i].ptr, (long)(memlist[i].ptr - NMEMBYTE), (long)(memlist[i].ptr + memlist[i].size + NMEMBYTE), 
-	       *(long *)(memlist[i].ptr - NMEMBYTE), *(long *)(memlist[i].ptr + memlist[i].size), 
-	       S[N], memlist[i].file, memlist[i].line);
+      memstats.Nbad ++;
     }
-  }
-  fprintf (stderr, "Nused: %d (Nalloc: %d, Nrealloc: %d), Nfree: %d\n", 
-	   Ns[0]+Ns[1], Ns[0], Ns[1], Ns[2]);
-  return;
-}
+
+    thisBlock = thisBlock->prevBlock;
+  }
+
+  return memstats;
+}
Index: /trunk/Ohana/src/libohana/src/string.c
===================================================================
--- /trunk/Ohana/src/libohana/src/string.c	(revision 31662)
+++ /trunk/Ohana/src/libohana/src/string.c	(revision 31663)
@@ -153,4 +153,18 @@
 }
 
+// advance to the next word, separated by commas:
+// AA,BB,CC : go from AA to BB to CC to 0
+// AA,,CC : go from AA to , to CC to 0
+// ,,, : go from , to , to , to 0
+
+char *_parse_nextword_csv (char *string) {
+
+  if (string == (char *) NULL) return ((char *) NULL);
+
+  for (; (*string != 0) && (*string != ','); string++);
+  if (*string == ',') string ++;
+  return (string);
+}
+
 int dparse (double *X, int NX, char *line) {
 
@@ -162,4 +176,26 @@
   for (i = 0; i < NX - 1; i++)
     word = _parse_nextword (word);
+
+  *X = strtod (word, &ptr);
+  if (ptr == word) return (FALSE);
+  if (word[0] == '-') return (-1);
+  return (1);
+}
+
+int dparse_csv (double *X, int NX, char *line) {
+
+  int i;
+  char *word;
+  char *ptr;
+
+  word = line;
+  for (i = 0; i < NX - 1; i++)
+    word = _parse_nextword_csv (word);
+  
+  if (word[0] == '"') word[0] = ' ';
+  if (word[0] == ',') {
+      *X = NAN;
+      return 1;
+  }
 
   *X = strtod (word, &ptr);
Index: /trunk/Ohana/src/libohana/test/memtest.c
===================================================================
--- /trunk/Ohana/src/libohana/test/memtest.c	(revision 31663)
+++ /trunk/Ohana/src/libohana/test/memtest.c	(revision 31663)
@@ -0,0 +1,188 @@
+# include "ohana.h"
+# include "tap_ohana.h"
+
+int main (void) {
+
+  plan_tests (64);
+
+  diag ("libohana memtest.c tests");
+
+  {
+    // int status;
+    char *data;
+    OhanaMemstats memstats;
+
+    // does ALLOCATE work?
+    ALLOCATE (data, char, 100);
+    memstats = ohana_memstats(0);
+
+    skip_start (!memstats.exists, 16, "skipping all tests : memory management not enabled");
+
+    ok (memstats.Ntotal ==   1, "allocated a block");
+    ok (memstats.Nbytes == 100, "allocated correct amount");
+    ok (memstats.Ngood  ==   1, "block is good");
+    ok (memstats.Nbad   ==   0, "no blocks are bad");
+    
+    // does REALLOCATE work?
+    REALLOCATE (data, char, 1000);
+    memstats = ohana_memstats(0);
+
+    ok (memstats.Ntotal ==    1, "allocated a block");
+    ok (memstats.Nbytes == 1000, "allocated correct amount");
+    ok (memstats.Ngood  ==    1, "block is good");
+    ok (memstats.Nbad   ==    0, "no blocks are bad");
+    
+    // does FREE work?
+    free (data);
+    memstats = ohana_memstats(0);
+
+    ok (memstats.Ntotal ==   0, "freed block");
+    ok (memstats.Nbytes ==   0, "kept correct amount");
+    ok (memstats.Ngood  ==   0, "block is good");
+    ok (memstats.Nbad   ==   0, "no blocks are bad");
+    
+    skip_end();
+  }
+
+  {
+    // int status;
+    char *data1, *data2, *data3, *data4;
+    OhanaMemstats memstats;
+
+    // does ALLOCATE work?
+    ALLOCATE (data1, char, 100);
+    ALLOCATE (data2, char, 100);
+    ALLOCATE (data3, char, 100);
+    ALLOCATE (data4, char, 100);
+    memstats = ohana_memstats(0);
+
+    skip_start (!memstats.exists, 16, "skipping all tests : memory management not enabled");
+
+    ok (memstats.Ntotal ==   4, "allocated a block");
+    ok (memstats.Nbytes == 400, "allocated correct amount");
+    ok (memstats.Ngood  ==   4, "block is good");
+    ok (memstats.Nbad   ==   0, "no blocks are bad");
+    
+    // does REALLOCATE work?
+    REALLOCATE (data1, char, 1000);
+    REALLOCATE (data2, char, 1000);
+    REALLOCATE (data3, char, 1000);
+    REALLOCATE (data4, char, 1000);
+    memstats = ohana_memstats(0);
+
+    ok (memstats.Ntotal ==    4, "allocated a block");
+    ok (memstats.Nbytes == 4000, "allocated correct amount");
+    ok (memstats.Ngood  ==    4, "block is good");
+    ok (memstats.Nbad   ==    0, "no blocks are bad");
+    
+    // does FREE work?
+    free (data1);
+    memstats = ohana_memstats(0);
+
+    ok (memstats.Ntotal ==    3, "freed block");
+    ok (memstats.Nbytes == 3000, "kept correct amount");
+    ok (memstats.Ngood  ==    3, "block is good");
+    ok (memstats.Nbad   ==    0, "no blocks are bad");
+    
+    free (data2);
+    memstats = ohana_memstats(0);
+
+    ok (memstats.Ntotal ==    2, "freed block");
+    ok (memstats.Nbytes == 2000, "kept correct amount");
+    ok (memstats.Ngood  ==    2, "block is good");
+    ok (memstats.Nbad   ==    0, "no blocks are bad");
+    
+    free (data3);
+    memstats = ohana_memstats(0);
+
+    ok (memstats.Ntotal ==    1, "freed block");
+    ok (memstats.Nbytes == 1000, "kept correct amount");
+    ok (memstats.Ngood  ==    1, "block is good");
+    ok (memstats.Nbad   ==    0, "no blocks are bad");
+    
+    free (data4);
+    memstats = ohana_memstats(0);
+
+    ok (memstats.Ntotal ==    0, "freed block");
+    ok (memstats.Nbytes ==    0, "kept correct amount");
+    ok (memstats.Ngood  ==    0, "block is good");
+    ok (memstats.Nbad   ==    0, "no blocks are bad");
+    
+    skip_end();
+  }
+
+  {
+    // int status;
+    char *data1, *data2, *data3, *data4;
+    OhanaMemstats memstats;
+
+    // does ALLOCATE work?
+    ALLOCATE (data1, char, 100);
+    ALLOCATE (data2, char, 100);
+    ALLOCATE (data3, char, 100);
+    ALLOCATE (data4, char, 100);
+    memstats = ohana_memstats(0);
+
+    skip_start (!memstats.exists, 16, "skipping all tests : memory management not enabled");
+
+    ok (memstats.Ntotal ==   4, "allocated a block");
+    ok (memstats.Nbytes == 400, "allocated correct amount");
+    ok (memstats.Ngood  ==   4, "block is good");
+    ok (memstats.Nbad   ==   0, "no blocks are bad");
+    
+    // damage some data
+    data1[101] = 111;
+    memstats = ohana_memstats(0);
+
+    ok (memstats.Ntotal ==    4, "allocated a block");
+    ok (memstats.Nbytes ==  400, "allocated correct amount");
+    ok (memstats.Ngood  ==    3, "3 blocks are good");
+    ok (memstats.Nbad   ==    1, "one block is bad");
+    
+    // damage some other data
+    data2[-2] = 111;
+    memstats = ohana_memstats(0);
+
+    ok (memstats.Ntotal ==    4, "allocated a block");
+    ok (memstats.Nbytes ==  400, "allocated correct amount");
+    ok (memstats.Ngood  ==    2, "3 blocks are good");
+    ok (memstats.Nbad   ==    2, "one block is bad");
+    
+    // does FREE work?
+    free (data1);
+    memstats = ohana_memstats(0);
+
+    ok (memstats.Ntotal ==    3, "freed block");
+    ok (memstats.Nbytes ==  300, "kept correct amount");
+    ok (memstats.Ngood  ==    2, "block is good");
+    ok (memstats.Nbad   ==    1, "no blocks are bad");
+    
+    free (data2);
+    memstats = ohana_memstats(0);
+
+    ok (memstats.Ntotal ==    2, "freed block");
+    ok (memstats.Nbytes ==  200, "kept correct amount");
+    ok (memstats.Ngood  ==    2, "block is good");
+    ok (memstats.Nbad   ==    0, "no blocks are bad");
+    
+    free (data3);
+    memstats = ohana_memstats(0);
+
+    ok (memstats.Ntotal ==    1, "freed block");
+    ok (memstats.Nbytes ==  100, "kept correct amount");
+    ok (memstats.Ngood  ==    1, "block is good");
+    ok (memstats.Nbad   ==    0, "no blocks are bad");
+    
+    free (data4);
+    memstats = ohana_memstats(0);
+
+    ok (memstats.Ntotal ==    0, "freed block");
+    ok (memstats.Nbytes ==    0, "kept correct amount");
+    ok (memstats.Ngood  ==    0, "block is good");
+    ok (memstats.Nbad   ==    0, "no blocks are bad");
+    
+    skip_end();
+  }
+
+  return exit_status();
+}
Index: /trunk/Ohana/src/libohana/test/typetest.c
===================================================================
--- /trunk/Ohana/src/libohana/test/typetest.c	(revision 31662)
+++ /trunk/Ohana/src/libohana/test/typetest.c	(revision 31663)
@@ -74,5 +74,10 @@
 
     big_value = 10;
-    fprintf (stderr, "this is a bit int: "OFF_T_FMT" n'est pas?\n", big_value);
+    fprintf (stderr, "this is a big int: "OFF_T_FMT" n'est pas?\n", big_value);
+
+    // we could move the % out of the FMT and allow constructions like this:
+    // # define OFF_T_FMT "jd"
+    // fprintf (stderr, "this is a big int: %06"OFF_T_FMT_ALT" n'est pas?\n", big_value);
+    // still kind of ugly...
 
     exit (status);
