about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-29 16:19:20 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-29 16:19:20 +0000
commit89973760e357aaf87b834f93e133ffb76b157d4b (patch)
tree2c24ce7e2601af9836726b0d5b91af8ef204c134
parent3821270a4899d4d9915bcc9f105b65b849467a3f (diff)
downloadnetpbm-mirror-89973760e357aaf87b834f93e133ffb76b157d4b.tar.gz
netpbm-mirror-89973760e357aaf87b834f93e133ffb76b157d4b.tar.xz
netpbm-mirror-89973760e357aaf87b834f93e133ffb76b157d4b.zip
Add pm_errormsg(), pm_setusererrormsg(), pm_setusermessage(), release memory before longjmping
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@30 9d0c8265-081b-0410-96cb-a4ca84ce46f8
-rw-r--r--GNUmakefile3
-rw-r--r--doc/HISTORY4
-rw-r--r--lib/Makefile2
-rw-r--r--lib/compile.h2
-rw-r--r--lib/libpammap.c20
-rw-r--r--lib/libpamn.c284
-rw-r--r--lib/libpamread.c104
-rw-r--r--lib/libpamwrite.c29
-rw-r--r--lib/libpbm3.c113
-rw-r--r--lib/libpgm1.c186
-rw-r--r--lib/libpm.c288
-rw-r--r--lib/libpnm1.c125
-rw-r--r--lib/libpnm2.c102
-rw-r--r--lib/libppm1.c290
-rw-r--r--lib/libppmcmap.c313
-rw-r--r--lib/libppmcolor.c239
-rw-r--r--lib/pm.h14
-rw-r--r--lib/util/Makefile2
-rw-r--r--lib/util/nstring.c9
-rw-r--r--lib/util/nstring.h5
20 files changed, 1566 insertions, 568 deletions
diff --git a/GNUmakefile b/GNUmakefile
index 56c6126c..420d558c 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -117,7 +117,8 @@ $(BUILDDIR)/inttypes_netpbm.h: $(TYPEGEN)
 # cross compiling.
 
 $(BUILDDIR)/pm_config.h: \
-  $(SRCDIR)/pm_config.in.h Makefile.config inttypes_netpbm.h $(ENDIANGEN)
+  $(SRCDIR)/pm_config.in.h Makefile.config inttypes_netpbm.h \
+  $(ENDIANGEN)
 	echo '/* pm_config.h GENERATED BY A MAKE RULE */' >$@ || $(DELETEIT)
 	echo '#ifndef PM_CONFIG_H' >>$@ || $(DELETEIT)
 	echo '#define PM_CONFIG_H' >>$@ || $(DELETEIT)
diff --git a/doc/HISTORY b/doc/HISTORY
index 49eb35be..07ada5f3 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -97,6 +97,10 @@ CHANGE HISTORY
 
 not yet  BJH  Release 10.36.0
 
+              Release allocated memory before longjmping from libnetpbm.
+
+              Add pm_errormsg(), pm_setusererrormsg(), pm_setusermessage().
+
               pm_accept_to_pamtuples: fix bug: fill in pam structure.
               Thanks "Christian Schlotter" <schlotter@users.sourceforge.net>.
 
diff --git a/lib/Makefile b/lib/Makefile
index bd8eccae..87ad860a 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -40,7 +40,7 @@ ifneq (${VMS}x,x)
 LIBOBJECTS += libpbmvms.o
 endif
 # Library objects to be linked but not built by Makefile.common:
-LIBOBJECTS_X = util/shhopt.o util/nstring.o util/filename.o
+LIBOBJECTS_X = util/shhopt.o util/nstring.o util/vasprintf.o util/filename.o
 
 MANUALS3 = libnetpbm
 MANUALS5 = pbm pgm ppm pnm pam
diff --git a/lib/compile.h b/lib/compile.h
index 4626f1ea..2fbeb753 100644
--- a/lib/compile.h
+++ b/lib/compile.h
@@ -1,5 +1,5 @@
 /* This file tells the package when it was compiled */
 /* DO NOT EDIT - THIS FILE IS MAINTAINED AUTOMATICALLY */
 /* Created by the program 'stamp-date'  */
-#define COMPILE_TIME "Thu Aug 17 03:34:22 GMT 2006"
+#define COMPILE_TIME "Tue Aug 29 15:40:16 GMT 2006"
 #define COMPILED_BY "bryanh"
diff --git a/lib/libpammap.c b/lib/libpammap.c
index 9e90ade0..98c7f798 100644
--- a/lib/libpammap.c
+++ b/lib/libpammap.c
@@ -24,12 +24,13 @@
 #define HASH_SIZE 20023
 
 unsigned int
-pnm_hashtuple(struct pam * const pamP, tuple const tuple) {
+pnm_hashtuple(struct pam * const pamP,
+              tuple        const tuple) {
 /*----------------------------------------------------------------------------
    Return the hash value of the tuple 'tuple' -- i.e. an index into a hash
    table.
 -----------------------------------------------------------------------------*/
-    int i;
+    unsigned int i;
     unsigned int hash;
     const unsigned int hash_factor[] = {33023, 30013, 27011};
 
@@ -281,7 +282,7 @@ computehashrecoverable(struct pam *   const pamP,
        tuple values. 
     */
     for (row = 0; row < pamP->height && !full; ++row) {
-        int col;
+        unsigned int col;
         const tuple * tuplerow;  /* The row of tuples we are processing */
         
         if (tupleArray)
@@ -354,19 +355,20 @@ computetuplefreqhash(struct pam *   const pamP,
     rowbuffer = NULL;
     color = NULL;
 
-    if (setjmp(jmpbuf) == 0) {
-        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
-        computehashrecoverable(pamP, tupleArray, maxsize, newMaxval, sizeP,
-                               &tuplefreqhash, &rowbuffer, &color);
-        pm_setjmpbuf(origJmpbufP);
-    } else {
+    if (setjmp(jmpbuf) != 0) {
         if (color) 
             pnm_freepamtuple(color);
         if (rowbuffer)
             pnm_freepamrow(rowbuffer);
         if (tuplefreqhash)
             pnm_destroytuplehash(tuplefreqhash);
+        pm_setjmpbuf(origJmpbufP);
         pm_longjmp();
+    } else {
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+        computehashrecoverable(pamP, tupleArray, maxsize, newMaxval, sizeP,
+                               &tuplefreqhash, &rowbuffer, &color);
+        pm_setjmpbuf(origJmpbufP);
     }
     return tuplefreqhash;
 }
diff --git a/lib/libpamn.c b/lib/libpamn.c
index c7610100..2ec50e7e 100644
--- a/lib/libpamn.c
+++ b/lib/libpamn.c
@@ -10,6 +10,7 @@
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include "nstring.h"
 #include "pam.h"
 #include "fileio.h"
 #include "pm_gamma.h"
@@ -18,15 +19,19 @@
 
 
 
-tuplen *
-pnm_allocpamrown(const struct pam * const pamP) {
+static void
+allocpamrown(const struct pam * const pamP,
+             tuplen **          const tuplerownP,
+             const char **      const errorP) {
 /*----------------------------------------------------------------------------
    We assume that the dimensions of the image are such that arithmetic
    overflow will not occur in our calculations.  NOTE: pnm_readpaminit()
    ensures this assumption is valid.
 -----------------------------------------------------------------------------*/
-    const int bytes_per_tuple = pamP->depth * sizeof(samplen);
+    int const bytes_per_tuple = pamP->depth * sizeof(samplen);
+
     tuplen * tuplerown;
+    const char * error;
 
     /* The tuple row data structure starts with 'width' pointers to
        the tuples, immediately followed by the 'width' tuples
@@ -35,64 +40,106 @@ pnm_allocpamrown(const struct pam * const pamP) {
 
     tuplerown = malloc(pamP->width * (sizeof(tuplen *) + bytes_per_tuple));
     if (tuplerown == NULL)
-        pm_error("Out of memory allocating space for a tuple row of\n"
-                 "%d tuples by %d samples per tuple by %d bytes per sample.",
-                 pamP->width, pamP->depth, sizeof(samplen));
-
-    {
+        asprintfN(&error, "Out of memory allocating space for a tuple row of"
+                  "%u tuples by %u samples per tuple by %u bytes per sample.",
+                  pamP->width, pamP->depth, sizeof(samplen));
+    else {
         /* Now we initialize the pointers to the individual tuples to make this
            a regulation C two dimensional array.
         */
         
-        char *p;
-        int i;
+        unsigned char * p;
+        unsigned int i;
         
-        p = (char*) (tuplerown + pamP->width);  /* location of Tuple 0 */
-        for (i = 0; i < pamP->width; i++) {
+        p = (unsigned char*) (tuplerown + pamP->width);
+            /* location of Tuple 0 */
+        for (i = 0; i < pamP->width; ++i) {
             tuplerown[i] = (tuplen) p;
             p += bytes_per_tuple;
         }
+        *errorP = NULL;
+        *tuplerownP = tuplerown;
     }
-    return(tuplerown);
 }
 
 
 
-void 
-pnm_readpamrown(const struct pam * const pamP, 
-                tuplen *           const tuplenrow) {
+tuplen *
+pnm_allocpamrown(const struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   We assume that the dimensions of the image are such that arithmetic
+   overflow will not occur in our calculations.  NOTE: pnm_readpaminit()
+   ensures this assumption is valid.
+-----------------------------------------------------------------------------*/
+    const char * error;
+    tuplen * tuplerown;
 
-    /* For speed, we don't check any of the inputs for consistency 
-       here (unless it's necessary to avoid crashing).  Any consistency
-       checking should have been done by a prior call to 
-       pnm_writepaminit().
-    */
-    assert(pamP->maxval != 0);
+    allocpamrown(pamP, &tuplerown, &error);
 
-    /* Need a special case for raw PBM because it has multiple tuples (8)
-       packed into one byte.
-    */
-    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE) {
-        int col;
-        bit *bitrow;
-        if (pamP->depth != 1)
-            pm_error("Invalid pam structure passed to pnm_readpamrow().  "
-                     "It says PBM format, but 'depth' member is not 1.");
-        bitrow = pbm_allocrow(pamP->width);
-        pbm_readpbmrow(pamP->file, bitrow, pamP->width, pamP->format);
-        for (col = 0; col < pamP->width; col++)
-            tuplenrow[col][0] = 
-                bitrow[col] == PBM_BLACK ? 0.0 : 1.0;
+    if (error) {
+        pm_errormsg("pnm_allocpamrown() failed.  %s", error);
+        strfree(error);
+        pm_longjmp();
+    }
+
+    return tuplerown;
+}
+
+
+
+static void
+readpbmrow(const struct pam * const pamP, 
+           tuplen *           const tuplenrow) {
+
+    bit * bitrow;
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+
+    bitrow = pbm_allocrow(pamP->width);
+    
+    if (setjmp(jmpbuf) != 0) {
         pbm_freerow(bitrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int col;
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        pbm_readpbmrow(pamP->file, bitrow, pamP->width, pamP->format);
+
+        for (col = 0; col < pamP->width; ++col)
+            tuplenrow[col][0] = bitrow[col] == PBM_BLACK ? 0.0 : 1.0;
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pbm_freerow(bitrow);
+}
+
+
+
+static void
+readpamrow(const struct pam * const pamP, 
+           tuplen *           const tuplenrow) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    tuple * tuplerow;
+    
+    tuplerow = pnm_allocpamrow(pamP);
+    
+    if (setjmp(jmpbuf) != 0) {
+        pnm_freepamrow(tuplerow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
     } else {
         float const scaler = 1.0 / pamP->maxval;
             /* Note: multiplication is faster than division, so we divide
                once here so we can multiply many times later.
             */
-        int col;
-        tuple * tuplerow;
 
-        tuplerow = pnm_allocpamrow(pamP);
+        unsigned int col;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
 
         pnm_readpamrow(pamP, tuplerow);
         for (col = 0; col < pamP->width; ++col) {
@@ -100,15 +147,16 @@ pnm_readpamrown(const struct pam * const pamP,
             for (plane = 0; plane < pamP->depth; ++plane)
                 tuplenrow[col][plane] = tuplerow[col][plane] * scaler;
         }
-        pnm_freepamrow(tuplerow);
+        pm_setjmpbuf(origJmpbufP);
     }
+    pnm_freepamrow(tuplerow);
 }
 
 
 
 void 
-pnm_writepamrown(const struct pam * const pamP, 
-                 const tuplen *     const tuplenrow) {
+pnm_readpamrown(const struct pam * const pamP, 
+                tuplen *           const tuplenrow) {
 
     /* For speed, we don't check any of the inputs for consistency 
        here (unless it's necessary to avoid crashing).  Any consistency
@@ -121,20 +169,66 @@ pnm_writepamrown(const struct pam * const pamP,
        packed into one byte.
     */
     if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE) {
-        int col;
-        bit *bitrow;
-        bitrow = pbm_allocrow(pamP->width);
-        for (col = 0; col < pamP->width; col++)
-            bitrow[col] = 
-                tuplenrow[col][0] < 0.5 ? PBM_BLACK : PBM_WHITE;
+        if (pamP->depth != 1)
+            pm_error("Invalid pam structure passed to pnm_readpamrow().  "
+                     "It says PBM format, but 'depth' member is not 1.");
+
+        readpbmrow(pamP, tuplenrow);
+    } else
+        readpamrow(pamP, tuplenrow);
+}
+
+
+
+static void
+writepbmrow(const struct pam * const pamP, 
+            const tuplen *     const tuplenrow) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    bit * bitrow;
+
+    bitrow = pbm_allocrow(pamP->width);
+
+    if (setjmp(jmpbuf) != 0) {
+        pbm_freerow(bitrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int col;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        for (col = 0; col < pamP->width; ++col)
+            bitrow[col] = tuplenrow[col][0] < 0.5 ? PBM_BLACK : PBM_WHITE;
         pbm_writepbmrow(pamP->file, bitrow, pamP->width, 
                         pamP->format == PBM_FORMAT);
-        pbm_freerow(bitrow);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pbm_freerow(bitrow);
+} 
+
+
+
+static void
+writepamrow(const struct pam * const pamP, 
+            const tuplen *     const tuplenrow) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    tuple * tuplerow;
+    
+    tuplerow = pnm_allocpamrow(pamP);
+    
+    if (setjmp(jmpbuf) != 0) {
+        pnm_freepamrow(tuplerow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
     } else {
-        tuple * tuplerow;
-        int col;
+        unsigned int col;
 
-        tuplerow = pnm_allocpamrow(pamP);
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
 
         for (col = 0; col < pamP->width; ++col) {
             unsigned int plane;
@@ -143,8 +237,32 @@ pnm_writepamrown(const struct pam * const pamP,
                     (tuplenrow[col][plane] * pamP->maxval + 0.5);
         }    
         pnm_writepamrow(pamP, tuplerow);
-        pnm_freepamrow(tuplerow);
+
+        pm_setjmpbuf(origJmpbufP);
     }
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+void 
+pnm_writepamrown(const struct pam * const pamP, 
+                 const tuplen *     const tuplenrow) {
+
+    /* For speed, we don't check any of the inputs for consistency 
+       here (unless it's necessary to avoid crashing).  Any consistency
+       checking should have been done by a prior call to 
+       pnm_writepaminit().
+    */
+    assert(pamP->maxval != 0);
+
+    /* Need a special case for raw PBM because it has multiple tuples (8)
+       packed into one byte.
+    */
+    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE)
+        writepbmrow(pamP, tuplenrow);
+    else
+        writepamrow(pamP, tuplenrow);
 }
 
 
@@ -152,8 +270,8 @@ pnm_writepamrown(const struct pam * const pamP,
 tuplen **
 pnm_allocpamarrayn(const struct pam * const pamP) {
     
-    tuplen **tuplenarray;
-    int row;
+    tuplen ** tuplenarray;
+    const char * error;
 
     /* If the speed of this is ever an issue, it might be sped up a little
        by allocating one large chunk.
@@ -161,12 +279,32 @@ pnm_allocpamarrayn(const struct pam * const pamP) {
     
     MALLOCARRAY(tuplenarray, pamP->height);
     if (tuplenarray == NULL) 
-        pm_error("Out of memory allocating the row pointer section of "
-                 "a %u row array", pamP->height);
-
-    for (row = 0; row < pamP->height; row++) {
-        tuplenarray[row] = pnm_allocpamrown(pamP);
+        asprintfN(&error,
+                  "Out of memory allocating the row pointer section of "
+                  "a %u row array", pamP->height);
+    else {
+        unsigned int rowsDone;
+
+        rowsDone = 0;
+
+        while (rowsDone < pamP->height && !error) {
+            allocpamrown(pamP, &tuplenarray[rowsDone], &error);
+            if (!error)
+                ++rowsDone;
+        }
+        if (error) {
+            unsigned int row;
+            for (row = 0; row < rowsDone; ++row)
+                pnm_freepamrown(tuplenarray[rowsDone]);
+            free(tuplenarray);
+        }
+    }
+    if (error) {
+        pm_errormsg("pnm_allocpamarrayn() failed.  %s", error);
+        strfree(error);
+        pm_longjmp();
     }
+
     return(tuplenarray);
 }
 
@@ -191,16 +329,28 @@ pnm_readpamn(FILE *       const file,
              int          const size) {
 
     tuplen **tuplenarray;
-    int row;
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
 
     pnm_readpaminit(file, pamP, size);
     
     tuplenarray = pnm_allocpamarrayn(pamP);
     
-    for (row = 0; row < pamP->height; row++) 
-        pnm_readpamrown(pamP, tuplenarray[row]);
+    if (setjmp(jmpbuf) != 0) {
+        pnm_freepamarrayn(tuplenarray, pamP);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int row;
 
-    return(tuplenarray);
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        for (row = 0; row < pamP->height; ++row) 
+            pnm_readpamrown(pamP, tuplenarray[row]);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    return tuplenarray;
 }
 
 
@@ -209,11 +359,11 @@ void
 pnm_writepamn(struct pam * const pamP, 
               tuplen **    const tuplenarray) {
 
-    int row;
+    unsigned int row;
 
     pnm_writepaminit(pamP);
     
-    for (row = 0; row < pamP->height; row++) 
+    for (row = 0; row < pamP->height; ++row) 
         pnm_writepamrown(pamP, tuplenarray[row]);
 }
 
@@ -473,6 +623,8 @@ createUngammaMapOffset(const struct pam * const pamP,
    can be used in a reverse lookup to convert normalized ungamma'ed
    samplen values to integer sample values.  The 0.5 effectively does
    the rounding.
+
+   This never throws an error.  Return value NULL means failed.
 -----------------------------------------------------------------------------*/
     pnm_transformMap * retval;
     pnm_transformMap ungammaTransformMap;
diff --git a/lib/libpamread.c b/lib/libpamread.c
index 1b65745a..85701a90 100644
--- a/lib/libpamread.c
+++ b/lib/libpamread.c
@@ -18,30 +18,46 @@
 
 #include "pam.h"
 #include "fileio.h"
+#include "nstring.h"
 
 
 static void
 readPbmRow(const struct pam * const pamP,
            tuple *            const tuplerow) {
 
-    unsigned char *bitrow;
     if (pamP->depth != 1)
         pm_error("Invalid pam structure passed to pnm_readpamrow().  "
                  "It says PBM format, but 'depth' member is not 1.");
+    else {
+        jmp_buf jmpbuf;
+        jmp_buf * origJmpbufP;
+        unsigned char * bitrow;
+
+        bitrow = (unsigned char *) pbm_allocrow(pbm_packed_bytes(pamP->width));
+
+        if (setjmp(jmpbuf) != 0) {
+            pbm_freerow(bitrow);
+            pm_setjmpbuf(origJmpbufP);
+            pm_longjmp();
+        } else {
+            pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+            pbm_readpbmrow_packed(pamP->file, bitrow, pamP->width,
+                                  pamP->format);
     
-    bitrow = (unsigned char *) pbm_allocrow(pbm_packed_bytes(pamP->width));
-    pbm_readpbmrow_packed(pamP->file, bitrow, pamP->width, pamP->format);
-    
-    if (tuplerow) {
-        int col;
-        for (col = 0; col < pamP->width; ++col) {
-            tuplerow[col][0] = 
-                ( ((bitrow[col/8] >> (7-col%8)) & 1 ) == PBM_BLACK)
-                ? PAM_PBM_BLACK : PAM_PBM_WHITE
-                ;
+            if (tuplerow) {
+                unsigned int col;
+                for (col = 0; col < pamP->width; ++col) {
+                    tuplerow[col][0] = 
+                        ( ((bitrow[col/8] >> (7-col%8)) & 1 ) == PBM_BLACK)
+                        ? PAM_PBM_BLACK : PAM_PBM_WHITE
+                        ;
+                }
+            }
+            pm_setjmpbuf(origJmpbufP);
         }
-    }   
-    pbm_freerow(bitrow);
+        pbm_freerow(bitrow);
+    }
 }
 
 
@@ -185,6 +201,7 @@ readRawNonPbmRow(const struct pam * const pamP,
 
     unsigned char * inbuf;
     size_t bytesRead;
+    const char * error;
 
     inbuf = pnm_allocrowimage(pamP);
     
@@ -192,25 +209,34 @@ readRawNonPbmRow(const struct pam * const pamP,
 
     if (bytesRead != rowImageSize) {
         if (feof(pamP->file))
-            pm_error("End of file encountered when trying to read a row from "
-                     "input file.");
+            asprintfN(&error,
+                      "End of file encountered when trying to read a row from "
+                      "input file.");
         else 
-            pm_error("Error reading a row from input file.  "
-                     "fread() fails with errno=%d (%s)",
-                     errno, strerror(errno));
-    }
-    if (tuplerow) {
-        switch (pamP->bytes_per_sample) {
-        case 1: parse1BpsRow(pamP, tuplerow, inbuf); break;
-        case 2: parse2BpsRow(pamP, tuplerow, inbuf); break;
-        case 3: parse3BpsRow(pamP, tuplerow, inbuf); break;
-        case 4: parse4BpsRow(pamP, tuplerow, inbuf); break;
-        default:
-            pm_error("invalid bytes per sample passed to "
-                     "pnm_formatpamrow(): %u",  pamP->bytes_per_sample);
+            asprintfN(&error, "Error reading a row from input file.  "
+                      "fread() fails with errno=%d (%s)",
+                      errno, strerror(errno));
+    } else {
+        error = NULL;  /* initial assumption */
+        if (tuplerow) {
+            switch (pamP->bytes_per_sample) {
+            case 1: parse1BpsRow(pamP, tuplerow, inbuf); break;
+            case 2: parse2BpsRow(pamP, tuplerow, inbuf); break;
+            case 3: parse3BpsRow(pamP, tuplerow, inbuf); break;
+            case 4: parse4BpsRow(pamP, tuplerow, inbuf); break;
+            default:
+                asprintfN(&error, "invalid bytes per sample passed to "
+                          "pnm_formatpamrow(): %u",  pamP->bytes_per_sample);
+            }
         }
     }
     pnm_freerowimage(inbuf);
+
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
+        pm_longjmp();
+    }
 }
 
 
@@ -263,15 +289,27 @@ pnm_readpam(FILE *       const fileP,
             struct pam * const pamP, 
             int          const size) {
 
-    tuple **tuplearray;
-    int row;
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    tuple ** tuplearray;
 
     pnm_readpaminit(fileP, pamP, size);
     
     tuplearray = pnm_allocpamarray(pamP);
     
-    for (row = 0; row < pamP->height; row++) 
-        pnm_readpamrow(pamP, tuplearray[row]);
-
+    if (setjmp(jmpbuf) != 0) {
+        pnm_freepamarray(tuplearray, pamP);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int row;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+            
+        for (row = 0; row < pamP->height; ++row) 
+            pnm_readpamrow(pamP, tuplearray[row]);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
     return tuplearray;
 }
diff --git a/lib/libpamwrite.c b/lib/libpamwrite.c
index 9184a4b5..83f0f41b 100644
--- a/lib/libpamwrite.c
+++ b/lib/libpamwrite.c
@@ -308,22 +308,33 @@ writePamRawRow(const struct pam * const pamP,
    Write mutiple ('count') copies of the same row ('tuplerow') to the file,
    in raw (not plain) format.
 -----------------------------------------------------------------------------*/
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
     unsigned int rowImageSize;
-
     unsigned char * outbuf;  /* malloc'ed */
-    unsigned int i;
 
     outbuf = pnm_allocrowimage(pamP);
 
     pnm_formatpamrow(pamP, tuplerow, outbuf, &rowImageSize);
 
-    for (i = 0; i < count; ++i) {
-        size_t bytesWritten;
-
-        bytesWritten = fwrite(outbuf, 1, rowImageSize, pamP->file);
-        if (bytesWritten != rowImageSize)
-            pm_error("fwrite() failed to write an image row to the file.  "
-                     "errno=%d (%s)", errno, strerror(errno));
+    if (setjmp(jmpbuf) != 0) {
+        pnm_freerowimage(outbuf);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int i;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+        
+        for (i = 0; i < count; ++i) {
+            size_t bytesWritten;
+            
+            bytesWritten = fwrite(outbuf, 1, rowImageSize, pamP->file);
+            if (bytesWritten != rowImageSize)
+                pm_error("fwrite() failed to write an image row to the file.  "
+                         "errno=%d (%s)", errno, strerror(errno));
+        }
+        pm_setjmpbuf(origJmpbufP);
     }
     pnm_freerowimage(outbuf);
 }
diff --git a/lib/libpbm3.c b/lib/libpbm3.c
index 2e7b922c..a5d5ea62 100644
--- a/lib/libpbm3.c
+++ b/lib/libpbm3.c
@@ -58,8 +58,8 @@ static void
 packBitsWithMmxSse(FILE *          const fileP,
                    const bit *     const bitrow,
                    unsigned char * const packedBits,
-                   int             const cols,
-                   int *           const nextColP) {
+                   unsigned int    const cols,
+                   unsigned int *  const nextColP) {
 /*----------------------------------------------------------------------------
    Pack the bits of bitrow[] into bytes at 'packedBits'.  Going left to right,
    stop when there aren't enough bits left to fill a whole byte.  Return
@@ -136,8 +136,8 @@ static void
 packBitsGeneric(FILE *          const fileP,
                 const bit *     const bitrow,
                 unsigned char * const packedBits,
-                int             const cols,
-                int *           const nextColP) {
+                unsigned int    const cols,
+                unsigned int *  const nextColP) {
 /*----------------------------------------------------------------------------
    Pack the bits of bitrow[] into byts at 'packedBits'.  Going left to right,
    stop when there aren't enough bits left to fill a whole byte.  Return
@@ -167,41 +167,63 @@ packBitsGeneric(FILE *          const fileP,
 
 
 static void
+packPartialBytes(const bit *     const bitrow,
+                 unsigned int    const cols,
+                 unsigned int    const nextCol,
+                 unsigned char * const packedBits) {
+              
+    /* routine for partial byte at the end of packedBits[]
+       Prior to addition of the above enhancement,
+       this method was used for the entire process
+    */                   
+    
+    unsigned int col;
+    int bitshift;
+    unsigned char item;
+    
+    bitshift = 7;  /* initial value */
+    item = 0;      /* initial value */
+    for (col = nextCol; col < cols; ++col, --bitshift)
+        if (bitrow[col] != 0)
+            item |= 1 << bitshift;
+    
+    packedBits[col/8] = item;
+}
+
+
+
+static void
 writePbmRowRaw(FILE *      const fileP,
                const bit * const bitrow,
                int         const cols) {
 
-    int nextCol;
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    unsigned char * packedBits;
 
-    unsigned char * const packedBits = pbm_allocrow_packed(cols);
+    packedBits = pbm_allocrow_packed(cols);
 
-    if (HAVE_MMX_SSE)
-        packBitsWithMmxSse(fileP, bitrow, packedBits, cols, &nextCol);
-    else 
-        packBitsGeneric(fileP, bitrow, packedBits, cols, &nextCol);
+    if (setjmp(jmpbuf) != 0) {
+        pbm_freerow_packed(packedBits);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int nextCol;
 
-    /* routine for partial byte at the end of packed_bits[]
-       Prior to addition of the above enhancement,
-       this method was used for the entire process
-     */                   
-
-    if (cols % 8 > 0) {
-        int col;
-        int bitshift;
-        unsigned char item;
-
-        bitshift = 7;  /* initial value */
-        item = 0;      /* initial value */
-        for (col = nextCol; col < cols; ++col, --bitshift )
-            if (bitrow[col] !=0)
-                item |= 1 << bitshift
-                ;
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        if (HAVE_MMX_SSE)
+            packBitsWithMmxSse(fileP, bitrow, packedBits, cols, &nextCol);
+        else 
+            packBitsGeneric(fileP, bitrow, packedBits, cols, &nextCol);
+
+        if (cols % 8 > 0)
+            packPartialBytes(bitrow, cols, nextCol, packedBits);
         
-        packedBits[col/8] = item;
+        writePackedRawRow(fileP, packedBits, cols);
+
+        pm_setjmpbuf(origJmpbufP);
     }
-    
-    writePackedRawRow(fileP, packedBits, cols);
-    
     pbm_freerow_packed(packedBits);
 }
 
@@ -244,22 +266,37 @@ pbm_writepbmrow(FILE * const fileP,
 
 void
 pbm_writepbmrow_packed(FILE *                const fileP, 
-                       const unsigned char * const packed_bits,
+                       const unsigned char * const packedBits,
                        int                   const cols, 
                        int                   const forceplain) {
 
     if (!forceplain && !pm_plain_output)
-        writePackedRawRow(fileP, packed_bits, cols);
+        writePackedRawRow(fileP, packedBits, cols);
     else {
-        bit *bitrow;
-        int col;
+        jmp_buf jmpbuf;
+        jmp_buf * origJmpbufP;
+        bit * bitrow;
 
         bitrow = pbm_allocrow(cols);
 
-        for (col = 0; col < cols; ++col) 
-            bitrow[col] = 
-                packed_bits[col/8] & (0x80 >> (col%8)) ? PBM_BLACK : PBM_WHITE;
-        writePbmRowPlain(fileP, bitrow, cols);
+        if (setjmp(jmpbuf) != 0) {
+            pbm_freerow(bitrow);
+            pm_setjmpbuf(origJmpbufP);
+            pm_longjmp();
+        } else {
+            unsigned int col;
+            
+            pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+            for (col = 0; col < cols; ++col) 
+                bitrow[col] = 
+                    packedBits[col/8] & (0x80 >> (col%8)) ?
+                    PBM_BLACK : PBM_WHITE;
+
+            writePbmRowPlain(fileP, bitrow, cols);
+
+            pm_setjmpbuf(origJmpbufP);
+        }
         pbm_freerow(bitrow);
     }
 }
diff --git a/lib/libpgm1.c b/lib/libpgm1.c
index 75fa365b..5b17910a 100644
--- a/lib/libpgm1.c
+++ b/lib/libpgm1.c
@@ -28,6 +28,7 @@
 #include "libpam.h"
 #include "fileio.h"
 #include "mallocvar.h"
+#include "nstring.h"
 
 
 gray *
@@ -190,105 +191,166 @@ pgm_getrawsample(FILE * const file,
 
 
 
-void
-pgm_readpgmrow(FILE * const file,
+static void
+readRpgmRow(FILE * const fileP,
                gray * const grayrow, 
                int    const cols,
                gray   const maxval,
                int    const format) {
 
-    switch (format) {
-    case PGM_FORMAT: {
-        unsigned int col;
-        for (col = 0; col < cols; ++col) {
-            grayrow[col] = pm_getuint(file);
-            if (grayrow[col] > maxval)
-                pm_error("value out of bounds (%u > %u)",
-                         grayrow[col], maxval);
+    unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
+    int          const bytesPerRow    = cols * bytesPerSample;
+    
+    unsigned char * rowBuffer;
+    const char * error;
+    
+    MALLOCARRAY(rowBuffer, bytesPerRow);
+    if (rowBuffer == NULL)
+        asprintfN(&error, "Unable to allocate memory for row buffer "
+                  "for %u columns", cols);
+    else {
+        ssize_t rc;
+        rc = fread(rowBuffer, 1, bytesPerRow, fileP);
+        if (rc == 0)
+            asprintfN(&error, "Error reading row.  fread() errno=%d (%s)",
+                      errno, strerror(errno));
+        else if (rc != bytesPerRow)
+            asprintfN(&error, "Error reading row.  Short read of %u bytes "
+                      "instead of %u", rc, bytesPerRow);
+        else {
+            error = NULL;
+            if (maxval < 256) {
+                unsigned int col;
+                for (col = 0; col < cols; ++col)
+                    grayrow[col] = (gray)rowBuffer[col];
+            } else {
+                unsigned int bufferCursor;
+                unsigned int col;
+                
+                bufferCursor = 0;  /* Start at beginning of rowBuffer[] */
+                
+                for (col = 0; col < cols; ++col) {
+                    gray g;
+                    
+                    g = rowBuffer[bufferCursor++] << 8;
+                    g |= rowBuffer[bufferCursor++];
+                    
+                    grayrow[col] = g;
+                }
+            }
         }
+        free(rowBuffer);
     }
-    break;
-        
-    case RPGM_FORMAT: {
-        unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
-        int          const bytesPerRow    = cols * bytesPerSample;
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
+        pm_longjmp();
+    }
+} 
 
-        unsigned char * rowBuffer;
-        ssize_t rc;
 
-        MALLOCARRAY(rowBuffer, bytesPerRow);
-        if (rowBuffer == NULL)
-            pm_error("Unable to allocate memory for row buffer "
-                     "for %u columns", cols);
 
-        rc = fread(rowBuffer, 1, bytesPerRow, file);
-        if (rc == 0)
-            pm_error("Error reading row.  fread() errno=%d (%s)",
-                     errno, strerror(errno));
-        else if (rc != bytesPerRow)
-            pm_error("Error reading row.  Short read of %u bytes "
-                     "instead of %u", rc, bytesPerRow);
+static void
+readPbmRow(FILE * const fileP,
+           gray * const grayrow, 
+           int    const cols,
+           gray   const maxval,
+           int    const format) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    bit * bitrow;
+    
+    bitrow = pbm_allocrow(cols);
+    if (setjmp(jmpbuf) != 0) {
+        pbm_freerow(bitrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int col;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        pbm_readpbmrow(fileP, bitrow, cols, format);
+        for (col = 0; col < cols; ++col)
+            grayrow[col] = (bitrow[col] == PBM_WHITE ) ? maxval : 0;
 
-        if (maxval < 256) {
-            unsigned int col;
-            for (col = 0; col < cols; ++col)
-                grayrow[col] = (gray)rowBuffer[col];
-        } else {
-            unsigned int bufferCursor;
-            unsigned int col;
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pbm_freerow(bitrow);
+}
 
-            bufferCursor = 0;  /* Start at beginning of rowBuffer[] */
 
-            for (col = 0; col < cols; ++col) {
-                gray g;
 
-                g = rowBuffer[bufferCursor++] << 8;
-                g |= rowBuffer[bufferCursor++];
+void
+pgm_readpgmrow(FILE * const fileP,
+               gray * const grayrow, 
+               int    const cols,
+               gray   const maxval,
+               int    const format) {
 
-                grayrow[col] = g;
-            }
+    switch (format) {
+    case PGM_FORMAT: {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            grayrow[col] = pm_getuint(fileP);
+            if (grayrow[col] > maxval)
+                pm_error("value out of bounds (%u > %u)",
+                         grayrow[col], maxval);
         }
-        free(rowBuffer);
     }
+    break;
+        
+    case RPGM_FORMAT:
+        readRpgmRow(fileP, grayrow, cols, maxval, format);
         break;
     
     case PBM_FORMAT:
-    case RPBM_FORMAT: {
-        bit * bitrow;
-        int col;
-
-        bitrow = pbm_allocrow(cols);
-        pbm_readpbmrow( file, bitrow, cols, format );
-        for (col = 0; col < cols; ++col)
-            grayrow[col] = (bitrow[col] == PBM_WHITE ) ? maxval : 0;
-        pbm_freerow(bitrow);
-    }
+    case RPBM_FORMAT:
+        readPbmRow(fileP, grayrow, cols, maxval, format);
         break;
         
     default:
-        pm_error( "can't happen" );
+        pm_error("can't happen");
     }
 }
 
 
 
 gray **
-pgm_readpgm(FILE * const file,
+pgm_readpgm(FILE * const fileP,
             int *  const colsP,
             int *  const rowsP, 
             gray * const maxvalP) {
 
-    gray** grays;
-    int row;
+    gray ** grays;
+    int rows, cols;
+    gray maxval;
     int format;
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
 
-    pgm_readpgminit( file, colsP, rowsP, maxvalP, &format );
+    pgm_readpgminit(fileP, &cols, &rows, &maxval, &format);
     
-    grays = pgm_allocarray( *colsP, *rowsP );
-    
-    for ( row = 0; row < *rowsP; ++row )
-        pgm_readpgmrow( file, grays[row], *colsP, *maxvalP, format );
+    grays = pgm_allocarray(cols, rows);
+
+    if (setjmp(jmpbuf) != 0) {
+        pgm_freearray(grays, rows);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int row;
     
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        for (row = 0; row < rows; ++row)
+            pgm_readpgmrow(fileP, grays[row], cols, maxval, format);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    *colsP = cols;
+    *rowsP = rows;
+    *maxvalP = maxval;
     return grays;
 }
 
diff --git a/lib/libpm.c b/lib/libpm.c
index 978657a9..df59e6c4 100644
--- a/lib/libpm.c
+++ b/lib/libpm.c
@@ -55,7 +55,7 @@
 /* The following are set by pm_init(), then used by subsequent calls to other
    pm_xxx() functions.
    */
-static const char* pm_progname;
+static const char * pm_progname;
 static bool pm_showmessages;  
     /* Programs should display informational messages (because the user didn't
        specify the --quiet option).
@@ -76,6 +76,17 @@ static jmp_buf * pm_jmpbufP = NULL;
        NULL, which is the default value, means when a libnetpbm function
        encounters an error, it causes the process to exit.
     */
+static pm_usererrormsgfn * userErrorMsgFn = NULL;
+    /* A function to call to issue an error message.
+
+       NULL means use the library default: print to Standard Error
+    */
+
+static pm_usermessagefn * userMessageFn = NULL;
+    /* A function to call to issue an error message.
+
+       NULL means use the library default: print to Standard Error
+    */
 
 
 
@@ -108,6 +119,22 @@ pm_longjmp(void) {
 
 
 void
+pm_setusererrormsgfn(pm_usererrormsgfn * fn) {
+
+    userErrorMsgFn = fn;
+}
+
+
+
+void
+pm_setusermessagefn(pm_usermessagefn * fn) {
+
+    userMessageFn = fn;
+}
+
+
+
+void
 pm_usage(const char usage[]) {
     pm_error("usage:  %s %s", pm_progname, usage);
 }
@@ -122,24 +149,64 @@ pm_message(const char format[], ...) {
     va_start(args, format);
 
     if (pm_showmessages) {
-        fprintf(stderr, "%s: ", pm_progname);
-        vfprintf(stderr, format, args);
-        fputc('\n', stderr);
+        const char * msg;
+        vasprintfN(&msg, format, args);
+
+        if (userMessageFn)
+            userMessageFn(msg);
+        else
+            fprintf(stderr, "%s: %s\n", pm_progname, msg);
+
+        strfree(msg);
     }
     va_end(args);
 }
 
 
 
+static void
+errormsg(const char * const msg) {
+
+    if (userErrorMsgFn)
+        userErrorMsgFn(msg);
+    else
+        fprintf(stderr, "%s: %s\n", pm_progname, msg);
+}
+
+
+
+void PM_GNU_PRINTF_ATTR(1,2)
+pm_errormsg(const char format[], ...) {
+
+    va_list args;
+    const char * msg;
+
+    va_start(args, format);
+
+    vasprintfN(&msg, format, args);
+    
+    errormsg(msg);
+
+    strfree(msg);
+
+    va_end(args);
+}
+
+
+
 void PM_GNU_PRINTF_ATTR(1,2)
 pm_error(const char format[], ...) {
     va_list args;
+    const char * msg;
 
     va_start(args, format);
 
-    fprintf(stderr, "%s: ", pm_progname);
-    vfprintf(stderr, format, args);
-    fputc('\n', stderr);
+    vasprintfN(&msg, format, args);
+    
+    errormsg(msg);
+
+    strfree(msg);
+
     va_end(args);
 
     pm_longjmp();
@@ -174,8 +241,64 @@ pm_freerow(char * const itrow) {
 
 
 
-char**
-pm_allocarray(int const cols, int const rows, int const size )  {
+static void
+allocarrayNoHeap(unsigned char ** const rowIndex,
+                 unsigned int     const cols,
+                 unsigned int     const rows,
+                 unsigned int     const size,
+                 const char **    const errorP) {
+
+    if (UINT_MAX / cols < size)
+        asprintfN(errorP,
+                  "Arithmetic overflow multiplying %u by %u to get the "
+                  "size of a row to allocate.", cols, size);
+    else {
+        unsigned int rowsDone;
+
+        rowsDone = 0;
+        *errorP = NULL;
+
+        while (rowsDone < rows && !*errorP) {
+            unsigned char * const rowSpace = malloc(cols * size);
+            if (rowSpace == NULL)
+                asprintfN(errorP,
+                          "Unable to allocate a %u-column by %u byte row",
+                          cols, size);
+            else
+                rowIndex[rowsDone++] = rowSpace;
+        }
+        if (*errorP) {
+            unsigned int row;
+            for (row = 0; row < rowsDone; ++row)
+                free(rowIndex[row]);
+        }
+    }
+}
+
+
+
+static unsigned char *
+allocRowHeap(unsigned int const cols,
+             unsigned int const rows,
+             unsigned int const size) {
+
+    unsigned char * retval;
+
+    if (UINT_MAX / cols / rows < size)
+        /* Too big even to request the memory ! */
+        retval = NULL;
+    else
+        retval = malloc(rows * cols * size);
+
+    return retval;
+}
+
+
+
+char **
+pm_allocarray(int const cols,
+              int const rows,
+              int const size )  {
 /*----------------------------------------------------------------------------
    Allocate an array of 'rows' rows of 'cols' columns each, with each
    element 'size' bytes.
@@ -194,38 +317,46 @@ pm_allocarray(int const cols, int const rows, int const size )  {
    We use unfragmented format if possible, but if the allocation of the
    row heap fails, we fall back to fragmented.
 -----------------------------------------------------------------------------*/
-    char** rowIndex;
-    char * rowheap;
+    unsigned char ** rowIndex;
+    const char * error;
 
     MALLOCARRAY(rowIndex, rows + 1);
     if (rowIndex == NULL)
-        pm_error("out of memory allocating row index (%u rows) for an array",
-                 rows);
-    rowheap = malloc(rows * cols * size);
-    if (rowheap == NULL) {
-        /* We couldn't get the whole heap in one block, so try fragmented
-           format.
-        */
-        unsigned int row;
-        
-        rowIndex[rows] = NULL;   /* Declare it fragmented format */
-
-        for (row = 0; row < rows; ++row) {
-            rowIndex[row] = pm_allocrow(cols, size);
-            if (rowIndex[row] == NULL)
-                pm_error("out of memory allocating Row %u "
-                         "(%u columns, %u bytes per tuple) "
-                         "of an array", row, cols, size);
-        }
-    } else {
-        /* It's unfragmented format */
-        unsigned int row;
-        rowIndex[rows] = rowheap;  /* Declare it unfragmented format */
+        asprintfN(&error,
+                  "out of memory allocating row index (%u rows) for an array",
+                  rows);
+    else {
+        unsigned char * rowheap;
 
-        for (row = 0; row < rows; ++row)
-            rowIndex[row] = &(rowheap[row * cols * size]);
+        rowheap = allocRowHeap(cols, rows, size);
+
+        if (rowheap) {
+            /* It's unfragmented format */
+
+            rowIndex[rows] = rowheap;  /* Declare it unfragmented format */
+
+            if (rowheap) {
+                unsigned int row;
+                
+                for (row = 0; row < rows; ++row)
+                    rowIndex[row] = &(rowheap[row * cols * size]);
+            }
+            error = NULL;
+        } else {
+            /* We couldn't get the whole heap in one block, so try fragmented
+               format.
+            */
+            rowIndex[rows] = NULL;   /* Declare it fragmented format */
+            
+            allocarrayNoHeap(rowIndex, cols, rows, size, &error);
+        }
+    }
+    if (error) {
+        pm_errormsg("Couldn't allocate %u-row array.  %s", rows, error);
+        strfree(error);
+        pm_longjmp();
     }
-    return rowIndex;
+    return (char **)rowIndex;
 }
 
 
@@ -795,17 +926,65 @@ mkstemp2(char * const filenameBuffer) {
 
 
 
+static void
+makeTmpfileWithTemplate(const char *  const filenameTemplate,
+                        FILE **       const filePP,
+                        const char ** const filenameP,
+                        const char ** const errorP) {
+    
+    char * filenameBuffer;  /* malloc'ed */
+
+    filenameBuffer = strdup(filenameTemplate);
+
+    if (filenameBuffer == NULL)
+        asprintfN(errorP, "Unable to allocate storage for temporary "
+                  "file name");
+    else {
+        int rc;
+        
+        rc = mkstemp2(filenameBuffer);
+        
+        if (rc < 0)
+            asprintfN(errorP,
+                      "Unable to create temporary file according to name "
+                      "pattern '%s'.  mkstemp() failed with errno %d (%s)",
+                      filenameTemplate, errno, strerror(errno));
+        else {
+            int const fd = rc;
+            
+            FILE * fileP;
+            fileP = fdopen(fd, "w+b");
+            
+            if (fileP == NULL)
+                asprintfN(errorP, "Unable to create temporary file.  "
+                          "fdopen() failed with errno %d (%s)",
+                          errno, strerror(errno));
+            else {
+                *errorP = NULL;
+                *filePP = fileP;
+                *filenameP = filenameBuffer;
+            }
+            if (*errorP) {
+                unlink(filenameBuffer);
+                close(fd);
+            }
+        }
+        if (*errorP)
+            strfree(filenameBuffer);
+    }
+}
+
+
+
 void
 pm_make_tmpfile(FILE **       const filePP,
                 const char ** const filenameP) {
 
-    int fd;
-    FILE * fileP;
     const char * filenameTemplate;
-    char * filenameBuffer;  /* malloc'ed */
     unsigned int fnamelen;
     const char * tmpdir;
     const char * dirseparator;
+    const char * error;
 
     fnamelen = strlen (pm_progname) + 10; /* "/" + "_XXXXXX\0" */
 
@@ -820,27 +999,18 @@ pm_make_tmpfile(FILE **       const filePP,
               tmpdir, dirseparator, pm_progname, "_XXXXXX");
 
     if (filenameTemplate == NULL)
-        pm_error("Unable to allocate storage for temporary file name");
-
-    filenameBuffer = strdup(filenameTemplate);
-
-    fd = mkstemp2(filenameBuffer);
-
-    if (fd < 0)
-        pm_error("Unable to create temporary file according to name "
-                 "pattern '%s'.  mkstemp() failed with "
-                 "errno %d (%s)", filenameTemplate, errno, strerror(errno));
+        asprintfN(&error,
+                  "Unable to allocate storage for temporary file name");
     else {
-        fileP = fdopen(fd, "w+b");
+        makeTmpfileWithTemplate(filenameTemplate, filePP, filenameP, &error);
 
-        if (fileP == NULL)
-            pm_error("Unable to create temporary file.  fdopen() failed "
-                     "with errno %d (%s)", errno, strerror(errno));
+        strfree(filenameTemplate);
+    }
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
+        pm_longjmp();
     }
-    strfree(filenameTemplate);
-
-    *filenameP = filenameBuffer;
-    *filePP = fileP;
 }
 
 
@@ -1200,9 +1370,9 @@ pm_readmagicnumber(FILE * const ifP) {
    Oliver Trepte, oliver@fysik4.kth.se, 930613 */
 
 #define PM_BUF_SIZE 16384      /* First try this size of the buffer, then
-                                   double this until we reach PM_MAX_BUF_INC */
+                                  double this until we reach PM_MAX_BUF_INC */
 #define PM_MAX_BUF_INC 65536   /* Don't allocate more memory in larger blocks
-                                   than this. */
+                                  than this. */
 
 char *
 pm_read_unknown_size(FILE * const file, 
diff --git a/lib/libpnm1.c b/lib/libpnm1.c
index 82f99b93..536e5dc4 100644
--- a/lib/libpnm1.c
+++ b/lib/libpnm1.c
@@ -133,6 +133,74 @@ pnm_readpnminit(FILE *   const fileP,
 
 
 
+static void
+readpgmrow(FILE * const fileP,
+           xel *  const xelrow,
+           int    const cols,
+           xelval const maxval,
+           int    const format) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    gray * grayrow;
+
+    grayrow = pgm_allocrow(cols);
+
+    if (setjmp(jmpbuf) != 0) {
+        pgm_freerow(grayrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int col;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        pgm_readpgmrow(fileP, grayrow, cols, (gray) maxval, format);
+
+        for (col = 0; col < cols; ++col)
+            PNM_ASSIGN1(xelrow[col], grayrow[col]);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pgm_freerow(grayrow);
+}
+
+
+
+static void
+readpbmrow(FILE * const fileP,
+               xel *  const xelrow,
+               int    const cols,
+               xelval const maxval,
+               int    const format) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    bit * bitrow;
+
+    bitrow = pbm_allocrow(cols);
+
+    if (setjmp(jmpbuf) != 0) {
+        pbm_freerow(bitrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int col;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        pbm_readpbmrow(fileP, bitrow, cols, format);
+
+        for (col = 0; col < cols; ++col)
+            PNM_ASSIGN1(xelrow[col], bitrow[col] == PBM_BLACK ? 0 : maxval);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pbm_freerow(bitrow);
+}
+
+
+
 void
 pnm_readpnmrow(FILE * const fileP,
                xel *  const xelrow,
@@ -145,28 +213,13 @@ pnm_readpnmrow(FILE * const fileP,
         ppm_readppmrow(fileP, (pixel*) xelrow, cols, (pixval) maxval, format);
         break;
 
-    case PGM_TYPE: {
-        gray * grayrow;
-        unsigned int col;
-
-        grayrow = pgm_allocrow(cols);
-        pgm_readpgmrow(fileP, grayrow, cols, (gray) maxval, format);
-        for (col = 0; col < cols; ++col)
-            PNM_ASSIGN1(xelrow[col], grayrow[col]);
-        pgm_freerow(grayrow);
-    }
-    break;
+    case PGM_TYPE:
+        readpgmrow(fileP, xelrow, cols, maxval, format);
+        break;
         
-    case PBM_TYPE: {
-        bit * bitrow;
-        unsigned int col;
-        bitrow = pbm_allocrow(cols);
-        pbm_readpbmrow(fileP, bitrow, cols, format);
-        for (col = 0; col < cols; ++col)
-            PNM_ASSIGN1(xelrow[col], bitrow[col] == PBM_BLACK ? 0: maxval);
-        pbm_freerow(bitrow);
-    }
-    break;
+    case PBM_TYPE:
+        readpbmrow(fileP, xelrow, cols, maxval, format);
+        break;
 
     default:
         pm_error("INTERNAL ERROR.  Impossible format.");
@@ -182,15 +235,35 @@ pnm_readpnm(FILE *   const fileP,
             xelval * const maxvalP,
             int *    const formatP) {
 
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    int cols, rows;
+    xelval maxval;
+    int format;
     xel ** xels;
-    int row;
 
-    pnm_readpnminit(fileP, colsP, rowsP, maxvalP, formatP);
+    pnm_readpnminit(fileP, &cols, &rows, &maxval, &format);
+
+    xels = pnm_allocarray(cols, rows);
 
-    xels = pnm_allocarray(*colsP, *rowsP);
+    if (setjmp(jmpbuf) != 0) {
+        pnm_freearray(xels, rows);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int row;
 
-    for (row = 0; row < *rowsP; ++row)
-        pnm_readpnmrow(fileP, xels[row], *colsP, *maxvalP, *formatP);
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        for (row = 0; row < rows; ++row)
+            pnm_readpnmrow(fileP, xels[row], cols, maxval, format);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    *colsP = cols;
+    *rowsP = rows;
+    *maxvalP = maxval;
+    *formatP = format;
 
     return xels;
 }
diff --git a/lib/libpnm2.c b/lib/libpnm2.c
index aae78d52..7e4f7e2a 100644
--- a/lib/libpnm2.c
+++ b/lib/libpnm2.c
@@ -55,6 +55,74 @@ pnm_writepnminit(FILE * const fileP,
 
 
 
+static void
+writepgmrow(FILE *       const fileP, 
+            xel *        const xelrow, 
+            unsigned int const cols, 
+            xelval       const maxval, 
+            int          const format, 
+            bool         const plainFormat) {
+    
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    gray * grayrow;
+    
+    grayrow = pgm_allocrow(cols);
+    
+    if (setjmp(jmpbuf) != 0) {
+        pgm_freerow(grayrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int col;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+        
+        for (col = 0; col < cols; ++col)
+            grayrow[col] = PNM_GET1(xelrow[col]);
+    
+        pgm_writepgmrow(fileP, grayrow, cols, (gray) maxval, plainFormat);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pgm_freerow(grayrow);
+}
+
+
+
+static void
+writepbmrow(FILE *       const fileP,
+            xel *        const xelrow,
+            unsigned int const cols,
+            bool         const plainFormat) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    bit * bitrow;
+
+    bitrow = pbm_allocrow(cols);
+    
+    if (setjmp(jmpbuf) != 0) {
+        pbm_freerow(bitrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int col;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        for (col = 0; col < cols; ++col)
+            bitrow[col] = PNM_GET1(xelrow[col]) == 0 ? PBM_BLACK : PBM_WHITE;
+    
+        pbm_writepbmrow(fileP, bitrow, cols, plainFormat);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pbm_freerow(bitrow);
+}    
+
+
+
 void
 pnm_writepnmrow(FILE * const fileP, 
                 xel *  const xelrow, 
@@ -71,35 +139,13 @@ pnm_writepnmrow(FILE * const fileP,
                         plainFormat);
         break;
 
-    case PGM_TYPE: {
-        gray* grayrow;
-        unsigned int col;
-
-        grayrow = pgm_allocrow(cols);
-
-        for (col = 0; col < cols; ++col)
-            grayrow[col] = PNM_GET1(xelrow[col]);
-
-        pgm_writepgmrow(fileP, grayrow, cols, (gray) maxval, plainFormat);
-
-        pgm_freerow( grayrow );
-    }
-    break;
-
-    case PBM_TYPE: {
-        bit* bitrow;
-        unsigned int col;
-
-        bitrow = pbm_allocrow(cols);
-
-        for (col = 0; col < cols; ++col)
-            bitrow[col] = PNM_GET1(xelrow[col]) == 0 ? PBM_BLACK : PBM_WHITE;
-
-        pbm_writepbmrow(fileP, bitrow, cols, plainFormat);
+    case PGM_TYPE:
+        writepgmrow(fileP, xelrow, cols, maxval, format, plainFormat);
+        break;
 
-        pbm_freerow(bitrow);
-    }    
-    break;
+    case PBM_TYPE:
+        writepbmrow(fileP, xelrow, cols, plainFormat);
+        break;
     
     default:
         pm_error("invalid format argument received by pnm_writepnmrow(): %d"
diff --git a/lib/libppm1.c b/lib/libppm1.c
index 57a1db7d..a7ea78cf 100644
--- a/lib/libppm1.c
+++ b/lib/libppm1.c
@@ -19,6 +19,7 @@
 #include <string.h>
 #include <stdio.h>
 #include <errno.h>
+
 #include "ppm.h"
 #include "libppm.h"
 #include "pgm.h"
@@ -29,6 +30,7 @@
 #include "libpam.h"
 #include "fileio.h"
 #include "mallocvar.h"
+#include "nstring.h"
 
 
 pixel *
@@ -150,124 +152,211 @@ ppm_readppminit(FILE *   const fileP,
 
 
 
-void
-ppm_readppmrow(FILE*  const fileP, 
-               pixel* const pixelrow, 
-               int    const cols, 
-               pixval const maxval, 
-               int    const format) {
-
-    switch (format) {
-    case PPM_FORMAT: {
-        unsigned int col;
-        for (col = 0; col < cols; ++col) {
-            pixval const r = pm_getuint(fileP);
-            pixval const g = pm_getuint(fileP);
-            pixval const b = pm_getuint(fileP);
-
-            if (r > maxval)
-                pm_error("Red sample value %u is greater than maxval (%u)",
-                         r, maxval);
-            if (g > maxval)
-                pm_error("Green sample value %u is greater than maxval (%u)",
-                         g, maxval);
-            if (b > maxval)
-                pm_error("Blue sample value %u is greater than maxval (%u)",
-                         b, maxval);
-
-            PPM_ASSIGN(pixelrow[col], r, g, b);
-        }
+static void
+readppm(FILE *       const fileP, 
+        pixel *      const pixelrow, 
+        unsigned int const cols, 
+        pixval       const maxval, 
+        int          const format) {
+
+    unsigned int col;
+
+    for (col = 0; col < cols; ++col) {
+        pixval const r = pm_getuint(fileP);
+        pixval const g = pm_getuint(fileP);
+        pixval const b = pm_getuint(fileP);
+        
+        if (r > maxval)
+            pm_error("Red sample value %u is greater than maxval (%u)",
+                     r, maxval);
+        if (g > maxval)
+            pm_error("Green sample value %u is greater than maxval (%u)",
+                     g, maxval);
+        if (b > maxval)
+            pm_error("Blue sample value %u is greater than maxval (%u)",
+                     b, maxval);
+        
+        PPM_ASSIGN(pixelrow[col], r, g, b);
     }
-    break;
+}
 
-    /* For PAM, we require a depth of 3, which means the raster format
-       is identical to Raw PPM!  How convenient.
-    */
-    case PAM_FORMAT:
-    case RPPM_FORMAT: {
-        unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
-        unsigned int const bytesPerRow    = cols * 3 * bytesPerSample;
-        
-        unsigned int bufferCursor;
-        unsigned char * rowBuffer;
-        ssize_t rc;
 
-        MALLOCARRAY(rowBuffer, bytesPerRow);
+
+static void
+readrppm(FILE *       const fileP, 
+         pixel *      const pixelrow, 
+         unsigned int const cols, 
+         pixval       const maxval, 
+         int          const format) {
+
+    unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
+    unsigned int const bytesPerRow    = cols * 3 * bytesPerSample;
         
-        if (rowBuffer == NULL)
-            pm_error("Unable to allocate memory for row buffer "
-                     "for %u columns", cols);
+    unsigned char * rowBuffer;
+    const char * error;
+    
+    MALLOCARRAY(rowBuffer, bytesPerRow);
+        
+    if (rowBuffer == NULL)
+        asprintfN(&error, "Unable to allocate memory for row buffer "
+                  "for %u columns", cols);
+    else {
+        ssize_t rc;
 
         rc = fread(rowBuffer, 1, bytesPerRow, fileP);
     
         if (feof(fileP))
-            pm_error("Unexpected EOF reading row of PPM image.");
+            asprintfN(&error, "Unexpected EOF reading row of PPM image.");
         else if (ferror(fileP))
-            pm_error("Error reading row.  fread() errno=%d (%s)",
-                     errno, strerror(errno));
+            asprintfN(&error, "Error reading row.  fread() errno=%d (%s)",
+                      errno, strerror(errno));
         else if (rc != bytesPerRow)
-            pm_error("Error reading row.  Short read of %u bytes "
-                     "instead of %u", rc, bytesPerRow);
-    
-        bufferCursor = 0;  /* start at beginning of rowBuffer[] */
+            asprintfN(&error, "Error reading row.  Short read of %u bytes "
+                      "instead of %u", rc, bytesPerRow);
+        else {
+            unsigned int bufferCursor;
+
+            error = NULL;
+
+            bufferCursor = 0;  /* start at beginning of rowBuffer[] */
         
-        if (bytesPerSample == 1) {
-            unsigned int col;
-            for (col = 0; col < cols; ++col) {
-                pixval const r = rowBuffer[bufferCursor++];
-                pixval const g = rowBuffer[bufferCursor++];
-                pixval const b = rowBuffer[bufferCursor++];
-                PPM_ASSIGN(pixelrow[col], r, g, b);
-            }
-        } else  {
-            /* two byte samples */
-            unsigned int col;
-            for (col = 0; col < cols; ++col) {
-                pixval r, g, b;
-
-                r = rowBuffer[bufferCursor++] << 8;
-                r |= rowBuffer[bufferCursor++];
-
-                g = rowBuffer[bufferCursor++] << 8;
-                g |= rowBuffer[bufferCursor++];
-
-                b = rowBuffer[bufferCursor++] << 8;
-                b |= rowBuffer[bufferCursor++];
-                
-                PPM_ASSIGN(pixelrow[col], r, g, b);
+            if (bytesPerSample == 1) {
+                unsigned int col;
+                for (col = 0; col < cols; ++col) {
+                    pixval const r = rowBuffer[bufferCursor++];
+                    pixval const g = rowBuffer[bufferCursor++];
+                    pixval const b = rowBuffer[bufferCursor++];
+                    PPM_ASSIGN(pixelrow[col], r, g, b);
+                }
+            } else  {
+                /* two byte samples */
+                unsigned int col;
+                for (col = 0; col < cols; ++col) {
+                    pixval r, g, b;
+                    
+                    r = rowBuffer[bufferCursor++] << 8;
+                    r |= rowBuffer[bufferCursor++];
+                    
+                    g = rowBuffer[bufferCursor++] << 8;
+                    g |= rowBuffer[bufferCursor++];
+                    
+                    b = rowBuffer[bufferCursor++] << 8;
+                    b |= rowBuffer[bufferCursor++];
+                    
+                    PPM_ASSIGN(pixelrow[col], r, g, b);
+                }
             }
         }
         free(rowBuffer);
     }
-    break;
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
+        pm_longjmp();
+    }
+}
 
-    case PGM_FORMAT:
-    case RPGM_FORMAT: {
-        gray * const grayrow = pgm_allocrow(cols);
+
+
+static void
+readpgm(FILE *       const fileP, 
+        pixel *      const pixelrow, 
+        unsigned int const cols, 
+        pixval       const maxval, 
+        int          const format) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    gray * grayrow;
+
+    grayrow = pgm_allocrow(cols);
+
+    if (setjmp(jmpbuf) != 0) {
+        pgm_freerow(grayrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
         unsigned int col;
+    
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
 
         pgm_readpgmrow(fileP, grayrow, cols, maxval, format);
+
         for (col = 0; col < cols; ++col) {
             pixval const g = grayrow[col];
             PPM_ASSIGN(pixelrow[col], g, g, g);
         }
-        pgm_freerow(grayrow);
+        pm_setjmpbuf(origJmpbufP);
     }
-    break;
+    pgm_freerow(grayrow);
+}
 
-    case PBM_FORMAT:
-    case RPBM_FORMAT: {
-        bit * const bitrow = pbm_allocrow(cols);
+
+
+static void
+readpbm(FILE *       const fileP, 
+        pixel *      const pixelrow, 
+        unsigned int const cols, 
+        pixval       const maxval, 
+        int          const format) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    bit * bitrow;
+
+    bitrow = pbm_allocrow(cols);
+
+    if (setjmp(jmpbuf) != 0) {
+        pbm_freerow(bitrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
         unsigned int col;
 
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
         pbm_readpbmrow(fileP, bitrow, cols, format);
+
         for (col = 0; col < cols; ++col) {
             pixval const g = (bitrow[col] == PBM_WHITE) ? maxval : 0;
             PPM_ASSIGN(pixelrow[col], g, g, g);
         }
-        pbm_freerow(bitrow);
+        pm_setjmpbuf(origJmpbufP);
     }
-    break;
+    pbm_freerow(bitrow);
+}
+
+
+
+void
+ppm_readppmrow(FILE *  const fileP, 
+               pixel * const pixelrow, 
+               int     const cols, 
+               pixval  const maxval, 
+               int     const format) {
+
+    switch (format) {
+    case PPM_FORMAT:
+        readppm(fileP, pixelrow, cols, maxval, format);
+        break;
+
+    /* For PAM, we require a depth of 3, which means the raster format
+       is identical to Raw PPM!  How convenient.
+    */
+    case PAM_FORMAT:
+    case RPPM_FORMAT:
+        readrppm(fileP, pixelrow, cols, maxval, format);
+        break;
+
+    case PGM_FORMAT:
+    case RPGM_FORMAT:
+        readpgm(fileP, pixelrow, cols, maxval, format);
+        break;
+
+    case PBM_FORMAT:
+    case RPBM_FORMAT:
+        readpbm(fileP, pixelrow, cols, maxval, format);
+        break;
 
     default:
         pm_error("Invalid format code");
@@ -281,17 +370,36 @@ ppm_readppm(FILE *   const fileP,
             int *    const colsP, 
             int *    const rowsP, 
             pixval * const maxvalP) {
-    pixel** pixels;
-    int row;
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    pixel ** pixels;
+    int cols, rows;
+    pixval maxval;
     int format;
 
-    ppm_readppminit(fileP, colsP, rowsP, maxvalP, &format);
+    ppm_readppminit(fileP, &cols, &rows, &maxval, &format);
 
-    pixels = ppm_allocarray(*colsP, *rowsP);
+    pixels = ppm_allocarray(cols, rows);
 
-    for (row = 0; row < *rowsP; ++row)
-        ppm_readppmrow(fileP, pixels[row], *colsP, *maxvalP, format);
+    if (setjmp(jmpbuf) != 0) {
+        ppm_freearray(pixels, rows);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int row;
 
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        for (row = 0; row < rows; ++row)
+            ppm_readppmrow(fileP, pixels[row], cols, maxval, format);
+
+        *colsP = cols;
+        *rowsP = rows;
+        *maxvalP = maxval;
+
+        pm_setjmpbuf(origJmpbufP);
+    }
     return pixels;
 }
 
diff --git a/lib/libppmcmap.c b/lib/libppmcmap.c
index a9efccbc..c1243cb6 100644
--- a/lib/libppmcmap.c
+++ b/lib/libppmcmap.c
@@ -12,9 +12,11 @@
 ** implied warranty.
 */
 
-#include "ppm.h"
-#include "libppm.h"
+#include "pm_c_util.h"
+#include "nstring.h"
 #include "mallocvar.h"
+#include "libppm.h"
+#include "ppm.h"
 #include "ppmcmap.h"
 
 #define HASH_SIZE 20023
@@ -110,94 +112,124 @@ ppm_addtocolorhist( colorhist_vector chv,
 
 
 
-colorhash_table
-ppm_alloccolorhash(void)  {
+static colorhash_table
+alloccolorhash(void)  {
     colorhash_table cht;
     int i;
 
     MALLOCARRAY(cht, HASH_SIZE);
+    if (cht) {
+        for (i = 0; i < HASH_SIZE; ++i)
+            cht[i] = NULL;
+    }
+    return cht;
+}
+
+
+
+colorhash_table
+ppm_alloccolorhash(void)  {
+    colorhash_table cht;
+
+    cht = alloccolorhash();
+
     if (cht == NULL)
         pm_error( "out of memory allocating hash table" );
 
-    for (i = 0; i < HASH_SIZE; ++i)
-        cht[i] = NULL;
-
     return cht;
 }
 
 
 
-static colorhash_table
-computecolorhash(pixel ** const pixels, 
-                 const int cols, const int rows, 
-                 const int maxcolors, int * const colorsP,
-                 FILE * const ifp, pixval const maxval, int const format) {
-/*----------------------------------------------------------------------------
-   Compute a color histogram from an image.  The input is one of two types:
+static void
+readppmrow(FILE *        const fileP, 
+           pixel *       const pixelrow, 
+           int           const cols, 
+           pixval        const maxval, 
+           int           const format,
+           const char ** const errorP) {
 
-   1) a two-dimensional array of pixels 'pixels';  In this case, 'pixels'
-      is non-NULL and 'ifp' is NULL.
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    
+    if (setjmp(jmpbuf) != 0) {
+        pm_setjmpbuf(origJmpbufP);
+        asprintfN(errorP, "Failed to read row of image.");
+    } else {
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
 
-   2) an open file, positioned to the image data.  In this case,
-      'pixels' is NULL and 'ifp' is non-NULL.  ifp is the stream
-      descriptor for the input file, and 'maxval' and 'format' are
-      parameters of the image data in it.
-      
-      We return with the file still open and its position undefined.  
+        ppm_readppmrow(fileP, pixelrow, cols, maxval, format);
 
-   In either case, the image is 'cols' by 'rows'.
+        *errorP = NULL; /* Would have longjmped if anything went wrong */
+                
+        pm_setjmpbuf(origJmpbufP);
+    }
+}
 
-   Return the number of colors found as *colorsP.
 
-   However, if 'maxcolors' is nonzero and the number of colors is
-   greater than 'maxcolors', return a null return value and *colorsP
-   undefined.
+
+static void
+buildHashTable(FILE *          const ifP,
+               pixel **        const pixels,
+               unsigned int    const cols,
+               unsigned int    const rows,
+               pixval          const maxval,
+               int             const format,
+               unsigned int    const maxcolors,
+               colorhash_table const cht,
+               pixel *         const rowbuffer,
+               int *           const nColorsP,
+               bool *          const tooManyColorsP,
+               const char **   const errorP) {
+/*----------------------------------------------------------------------------
+  Look at all the colors in the file *ifP or array pixels[][] and add
+  them to the hash table 'cht'.
+
+  Even if we fail, we may add some colors to 'cht'.
+
+  As soon as we've seen more that 'maxcolors' colors, we quit.  In that
+  case, only, we return *tooManyColorsP == true.  That is not a failure.
+  'maxcolors' == 0 means infinity.
 -----------------------------------------------------------------------------*/
-    colorhash_table cht;
-    int row;
-    pixel * rowbuffer;  /* malloc'ed */
-        /* Buffer for a row read from the input file; undefined (but still
-           allocated) if input is not from a file.
-        */
-    
-    cht = ppm_alloccolorhash( );
-    *colorsP = 0;   /* initial value */
+    unsigned int row;
+    unsigned int nColors;
 
-    rowbuffer = ppm_allocrow(cols);
+    nColors = 0;   /* initial value */
+    *tooManyColorsP = FALSE; /* initial value */
+    *errorP = NULL;  /* initial value */
 
     /* Go through the entire image, building a hash table of colors. */
-    for (row = 0; row < rows; ++row) {
-        int col;
+    for (row = 0; row < rows && !*tooManyColorsP && !*errorP; ++row) {
+        unsigned int col;
         pixel * pixelrow;  /* The row of pixels we are processing */
 
-        if (ifp) {
-            ppm_readppmrow(ifp, rowbuffer, cols, maxval, format);
+        if (ifP) {
+            readppmrow(ifP, rowbuffer, cols, maxval, format, errorP);
             pixelrow = rowbuffer;
         } else 
             pixelrow = pixels[row];
 
-        for (col = 0; col < cols; ++col) {
+        for (col = 0; col < cols && !*tooManyColorsP && !*errorP; ++col) {
             const pixel apixel = pixelrow[col];
             const int hash = ppm_hashpixel(apixel);
             colorhist_list chl; 
 
             for (chl = cht[hash]; 
-                 chl != (colorhist_list) 0 && 
-                     !PPM_EQUAL(chl->ch.color, apixel);
+                 chl && !PPM_EQUAL(chl->ch.color, apixel);
                  chl = chl->next);
 
             if (chl)
-                chl->ch.value++;
+                ++chl->ch.value;
             else {
                 /* It's not in the hash yet, so add it (if allowed) */
-                ++(*colorsP);
-                if (maxcolors > 0 && *colorsP > maxcolors) {
-                    ppm_freecolorhash(cht);
-                    return NULL;
-                } else {
+                ++nColors;
+                if (maxcolors > 0 && nColors > maxcolors)
+                    *tooManyColorsP = TRUE;
+                else {
                     MALLOCVAR(chl);
                     if (chl == NULL)
-                        pm_error("out of memory computing hash table");
+                        asprintfN(errorP,
+                                  "out of memory computing hash table");
                     chl->ch.color = apixel;
                     chl->ch.value = 1;
                     chl->next = cht[hash];
@@ -206,31 +238,124 @@ computecolorhash(pixel ** const pixels,
             }
         }
     }
-    ppm_freerow(rowbuffer);
-    return cht;
+    *nColorsP = nColors;
+}
+
+
+
+static void
+computecolorhash(pixel **          const pixels, 
+                 unsigned int      const cols,
+                 unsigned int      const rows, 
+                 unsigned int      const maxcolors,
+                 int *             const nColorsP,
+                 FILE *            const ifP,
+                 pixval            const maxval,
+                 int               const format,
+                 colorhash_table * const chtP,
+                 const char **     const errorP) {
+/*----------------------------------------------------------------------------
+   Compute a color histogram from an image.  The input is one of two types:
+
+   1) a two-dimensional array of pixels 'pixels';  In this case, 'pixels'
+      is non-NULL and 'ifP' is NULL.
+
+   2) an open file, positioned to the image data.  In this case,
+      'pixels' is NULL and 'ifP' is non-NULL.  ifP is the stream
+      descriptor for the input file, and 'maxval' and 'format' are
+      parameters of the image data in it.
+      
+      We return with the file still open and its position undefined.  
+
+   In either case, the image is 'cols' by 'rows'.
+
+   Return the number of colors found as *colorsP.
+
+   However, if 'maxcolors' is nonzero and the number of colors is
+   greater than 'maxcolors', return a null return value and *colorsP
+   undefined.
+-----------------------------------------------------------------------------*/
+    pixel * rowbuffer;  /* malloc'ed */
+        /* Buffer for a row read from the input file; undefined (but still
+           allocated) if input is not from a file.
+        */
+
+    MALLOCARRAY(rowbuffer, cols);
+        
+    if (rowbuffer == NULL)
+        asprintfN(errorP, "Unable to allocate %u-column row buffer.", cols);
+    else {
+        colorhash_table cht;
+        bool tooManyColors;
+
+        cht = alloccolorhash();
+
+        if (cht == NULL)
+            asprintfN(errorP, "Unable to allocate color hash.");
+        else {
+            buildHashTable(ifP, pixels, cols, rows, maxval, format, maxcolors,
+                           cht, rowbuffer,
+                           nColorsP, &tooManyColors, errorP);
+                
+            if (tooManyColors) {
+                ppm_freecolorhash(cht);
+                *chtP = NULL;
+            } else
+                *chtP = cht;
+
+            if (*errorP)
+                ppm_freecolorhash(cht);
+        }
+        free(rowbuffer);
+    }
 }
 
 
 
 colorhash_table
 ppm_computecolorhash(pixel ** const pixels, 
-                     const int cols, const int rows, 
-                     const int maxcolors, int * const colorsP) {
+                     int      const cols,
+                     int      const rows, 
+                     int      const maxcolors,
+                     int *    const colorsP) {
+
+    colorhash_table cht;
+    const char * error;
+
+    computecolorhash(pixels, cols, rows, maxcolors, colorsP, 
+                     NULL, 0, 0, &cht, &error);
 
-    return computecolorhash(pixels, cols, rows, maxcolors, colorsP, 
-                            NULL, 0, 0);
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
+        pm_longjmp();
+    }
+    return cht;
 }
 
 
 
 colorhash_table
-ppm_computecolorhash2(FILE * const ifp,
-                      const int cols, const int rows, 
-                      const pixval maxval, const int format, 
-                      const int maxcolors, int * const colorsP ) {
+ppm_computecolorhash2(FILE * const ifP,
+                      int    const cols,
+                      int    const rows, 
+                      pixval const maxval,
+                      int    const format, 
+                      int    const maxcolors,
+                      int *  const colorsP ) {
+
+    colorhash_table cht;
+    const char * error;
 
-    return computecolorhash(NULL, cols, rows, maxcolors, colorsP,
-                            ifp, maxval, format);
+    computecolorhash(NULL, cols, rows, maxcolors, colorsP,
+                     ifP, maxval, format, &cht, &error);
+
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
+        pm_longjmp();
+    }
+    return cht;
 }
 
 
@@ -353,30 +478,50 @@ ppm_colorhashtocolorhist(colorhash_table const cht, int const maxcolors) {
 colorhash_table
 ppm_colorhisttocolorhash(colorhist_vector const chv, 
                          int              const colors) {
+
+    colorhash_table retval;
     colorhash_table cht;
-    int i, hash;
-    pixel color;
-    colorhist_list chl;
+    const char * error;
 
-    cht = ppm_alloccolorhash( );  /* Initializes to NULLs */
-
-    for (i = 0; i < colors; ++i) {
-        color = chv[i].color;
-        hash = ppm_hashpixel(color);
-        for (chl = cht[hash]; chl != (colorhist_list) 0; chl = chl->next)
-            if (PPM_EQUAL(chl->ch.color, color))
-                pm_error(
-                    "same color found twice - %d %d %d", PPM_GETR(color),
-                    PPM_GETG(color), PPM_GETB(color) );
-        MALLOCVAR(chl);
-        if (chl == NULL)
-            pm_error("out of memory");
-        chl->ch.color = color;
-        chl->ch.value = i;
-        chl->next = cht[hash];
-        cht[hash] = chl;
+    cht = alloccolorhash( );  /* Initializes to NULLs */
+    if (cht == NULL)
+        asprintfN(&error, "Unable to allocate color hash");
+    else {
+        unsigned int i;
+
+        for (i = 0, error = NULL; i < colors && !error; ++i) {
+            pixel const color = chv[i].color;
+            int const hash = ppm_hashpixel(color);
+            
+            colorhist_list chl;
+
+            for (chl = cht[hash]; chl && !error; chl = chl->next)
+                if (PPM_EQUAL(chl->ch.color, color))
+                    asprintfN(&error, "same color found twice: (%u %u %u)",
+                              PPM_GETR(color),
+                              PPM_GETG(color),
+                              PPM_GETB(color));
+            MALLOCVAR(chl);
+            if (chl == NULL)
+                asprintfN(&error, "out of memory");
+            else {
+                chl->ch.color = color;
+                chl->ch.value = i;
+                chl->next = cht[hash];
+                cht[hash] = chl;
+            }
+        }
+        if (error)
+            ppm_freecolorhash(cht);
     }
-    return cht;
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
+        pm_longjmp();
+    } else
+        retval = cht;
+
+    return retval;
 }
 
 
diff --git a/lib/libppmcolor.c b/lib/libppmcolor.c
index 7cfacd29..d5323be3 100644
--- a/lib/libppmcolor.c
+++ b/lib/libppmcolor.c
@@ -19,6 +19,7 @@
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include "nstring.h"
 #include "ppm.h"
 #include "colorname.h"
 
@@ -471,11 +472,12 @@ processColorfileEntry(struct colorfile_entry const ce,
                       colorhash_table        const cht,
                       const char **          const colornames,
                       pixel *                const colors,
-                      unsigned int *         const colornameIndexP) {
+                      unsigned int *         const colornameIndexP,
+                      const char **          const errorP) {
 
     if (*colornameIndexP >= MAXCOLORNAMES)
-        pm_error("Too many colors in colorname dictionary.  "
-                 "Max allowed is %u", MAXCOLORNAMES);
+        asprintfN(errorP, "Too many colors in colorname dictionary.  "
+                  "Max allowed is %u", MAXCOLORNAMES);
     else {
         pixel color;
 
@@ -487,13 +489,17 @@ processColorfileEntry(struct colorfile_entry const ce,
                file gives for each color, so we just ignore the
                current entry.  
             */
+            *errorP = NULL;
         } else {
             ppm_addtocolorhash(cht, &color, *colornameIndexP);
             colornames[*colornameIndexP] = strdup(ce.colorname);
             colors[*colornameIndexP] = color;
             if (colornames[*colornameIndexP] == NULL)
-                pm_error("Unable to allocate space for color name");
-            ++(*colornameIndexP);
+                asprintfN(errorP, "Unable to allocate space for color name");
+            else {
+                *errorP = NULL;
+                ++(*colornameIndexP);
+            }
         }
     }
 }
@@ -501,39 +507,173 @@ processColorfileEntry(struct colorfile_entry const ce,
 
 
 static void
-readcolordict(const char *    const fileName,
+openColornameFile(const char *  const fileName,
+                  bool          const mustOpen,
+                  FILE **       const filePP,
+                  const char ** const errorP) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+
+    if (setjmp(jmpbuf) != 0) {
+        asprintfN(errorP, "Failed to open color name file");
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        *filePP = pm_openColornameFile(fileName, mustOpen);
+
+        *errorP = NULL;  /* Would have longjmped if there were a problem */
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+}
+
+
+
+static void
+readOpenColorFile(FILE *          const colorFileP,
+                  unsigned int *  const nColorsP,
+                  const char **   const colornames,
+                  pixel *         const colors,
+                  colorhash_table const cht,
+                  const char **   const errorP) {
+/*----------------------------------------------------------------------------
+   Read the color dictionary file *colorFileP and add the colors in it
+   to colornames[], colors[], and 'cht'.
+
+   We may add colors to 'cht' even if we fail.
+-----------------------------------------------------------------------------*/
+    unsigned int nColorsDone;
+    bool done;
+
+    nColorsDone = 0;
+    done = FALSE;
+    *errorP = NULL;
+
+    while (!done && !*errorP) {
+        struct colorfile_entry const ce = pm_colorget(colorFileP);
+        
+        if (!ce.colorname)  
+            done = TRUE;
+        else 
+            processColorfileEntry(ce, cht, colornames, colors,
+                                  &nColorsDone, errorP);
+    }
+    if (!*errorP) {
+        *nColorsP = nColorsDone;
+        
+        while (nColorsDone < MAXCOLORNAMES)
+            colornames[nColorsDone++] = NULL;
+    }
+    
+    if (*errorP) {
+        unsigned int colorIndex;
+
+        for (colorIndex = 0; colorIndex < nColorsDone; ++colorIndex)
+            strfree(colornames[colorIndex]);
+    }
+}
+
+
+
+static colorhash_table
+allocColorHash(void) {
+
+    colorhash_table cht;
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+
+    if (setjmp(jmpbuf) != 0)
+        cht = NULL;
+    else {
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+        cht = ppm_alloccolorhash();
+    }
+    pm_setjmpbuf(origJmpbufP);
+
+    return cht;
+}
+
+
+
+static void
+readColorFile(const char *    const fileName,
               bool            const mustOpen,
               unsigned int *  const nColorsP,
               const char **   const colornames,
-              pixel * const   colors,
-              colorhash_table const cht) {
+              pixel *         const colors,
+              colorhash_table const cht,
+              const char **   const errorP) {
 
-    FILE * colorFile;
+    FILE * colorFileP;
 
-    colorFile = pm_openColornameFile(fileName, mustOpen);
+    openColornameFile(fileName, mustOpen, &colorFileP, errorP);
+    if (!*errorP) {
+        if (colorFileP == NULL) {
+            /* Couldn't open it, but Caller says treat same as
+               empty file
+            */
+            *nColorsP = 0;
+            *errorP = NULL;
+        } else {
+            readOpenColorFile(colorFileP, nColorsP, colornames, colors, cht,
+                              errorP);
+            
+            fclose(colorFileP);
+        }
+    }
+}
 
-    if (colorFile != NULL) {
-        unsigned int colornameIndex;
-        bool done;
+    
 
-        colornameIndex = 0;  /* initial value */
-        done = FALSE;
-        while (!done) {
-            struct colorfile_entry const ce = pm_colorget(colorFile);
+static void
+readcolordict(const char *      const fileName,
+              bool              const mustOpen,
+              unsigned int *    const nColorsP,
+              const char ***    const colornamesP,
+              pixel **          const colorsP,
+              colorhash_table * const chtP,
+              const char **     const errorP) {
 
-            if (!ce.colorname)  
-                done = TRUE;
-            else 
-                processColorfileEntry(ce, cht, colornames, colors,
-                                      &colornameIndex);
-        }
+    const char ** colornames;
 
-        *nColorsP = colornameIndex;
+    MALLOCARRAY(colornames, MAXCOLORNAMES);
 
-        while (colornameIndex < MAXCOLORNAMES)
-            colornames[colornameIndex++] = NULL;
+    if (colornames == NULL)
+        asprintfN(errorP, "Unable to allocate space for colorname table.");
+    else {
+        pixel * colors;
 
-        fclose(colorFile);
+        MALLOCARRAY(colors, MAXCOLORNAMES);
+        
+        if (colors == NULL)
+            asprintfN(errorP, "Unable to allocate space for color table.");
+        else {
+            colorhash_table cht;
+
+            cht = allocColorHash();
+            
+            if (cht == NULL)
+                asprintfN(errorP, "Unable to allocate space for color hash");
+            else {
+                readColorFile(fileName, mustOpen,
+                              nColorsP, colornames, colors, cht,
+                              errorP);
+
+                if (*errorP)
+                    ppm_freecolorhash(cht);
+                else
+                    *chtP = cht;
+            }
+            if (*errorP)
+                free(colors);
+            else
+                *colorsP = colors;
+        }
+        if (*errorP)
+            free(colornames);
+        else
+            *colornamesP = colornames;
     }
 }
 
@@ -551,32 +691,31 @@ ppm_readcolordict(const char *      const fileName,
     const char ** colornames;
     pixel * colors;
     unsigned int nColors;
+    const char * error;
 
-    cht = ppm_alloccolorhash();
-
-    MALLOCARRAY(colornames, MAXCOLORNAMES);
-
-    colors = ppm_allocrow(MAXCOLORNAMES);
-
-    if (colornames == NULL)
-        pm_error("Unable to allocate space for colorname table.");
-
-    readcolordict(fileName, mustOpen, &nColors, colornames, colors, cht);
+    readcolordict(fileName, mustOpen, &nColors, &colornames, &colors, &cht,
+                  &error);
 
-    if (chtP)
-        *chtP = cht;
-    else
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
         ppm_freecolorhash(cht);
-    if (colornamesP)
-        *colornamesP = colornames;
-    else
-        ppm_freecolornames(colornames);
-    if (colorsP)
-        *colorsP = colors;
-    else
-        ppm_freerow(colors);
-    if (nColorsP)
-        *nColorsP = nColors;
+    } else {
+        if (chtP)
+            *chtP = cht;
+        else
+            ppm_freecolorhash(cht);
+        if (colornamesP)
+            *colornamesP = colornames;
+        else
+            ppm_freecolornames(colornames);
+        if (colorsP)
+            *colorsP = colors;
+        else
+            ppm_freerow(colors);
+        if (nColorsP)
+            *nColorsP = nColors;
+    }
 }
 
 
diff --git a/lib/pm.h b/lib/pm.h
index 199d47f2..696d763c 100644
--- a/lib/pm.h
+++ b/lib/pm.h
@@ -154,10 +154,24 @@ pm_setjmpbufsave(jmp_buf *  const jmpbufP,
 void
 pm_longjmp(void);
 
+
+typedef void pm_usermessagefn(const char * msg);
+
+void
+pm_setusermessagefn(pm_usermessagefn * fn);
+
+typedef void pm_usererrormsgfn(const char * msg);
+
+void
+pm_setusererrormsgfn(pm_usererrormsgfn * fn);
+
 void PM_GNU_PRINTF_ATTR(1,2)
 pm_message (const char format[], ...);     
 
 void PM_GNU_PRINTF_ATTR(1,2)
+pm_errormsg(const char format[], ...);
+
+void PM_GNU_PRINTF_ATTR(1,2)
 pm_error (const char reason[], ...);       
 
 /* Obsolete - use shhopt and user's manual instead */
diff --git a/lib/util/Makefile b/lib/util/Makefile
index 8f461f28..4ba60ba2 100644
--- a/lib/util/Makefile
+++ b/lib/util/Makefile
@@ -11,7 +11,7 @@ INCLUDES = -I $(BUILDDIR) -I $(SRCDIR)/$(SUBDIR)/..
 
 # nstring is required for asprintf(), etc.  Also some systems don't have
 # snprintf(), e.g. Solaris 2.5.1.  2002.03.29.
-UTILOBJECTS = shhopt.o nstring.o filename.o
+UTILOBJECTS = shhopt.o nstring.o vasprintf.o filename.o
 
 MERGE_OBJECTS =
 
diff --git a/lib/util/nstring.c b/lib/util/nstring.c
index 702a3c44..749e9060 100644
--- a/lib/util/nstring.c
+++ b/lib/util/nstring.c
@@ -740,15 +740,6 @@ const char * const strsol = "NO MEMORY TO CREATE STRING!";
 
 
 
-/* We would like to have vasprintfN(), but it is difficult because you
-   can't run through a va_list twice, which we would want to do: once
-   to measure the length; once actually to build the string.  On some
-   machines, you can simply make two copies of the va_list variable in
-   normal C fashion, but on others you need va_copy, which is a
-   relatively recent invention.  In particular, the simple va_list copy
-   failed on an AMD64 Gcc Linux system in March 2006.
-*/
-
 void PM_GNU_PRINTF_ATTR(2,3)
 asprintfN(const char ** const resultP,
           const char *  const fmt, 
diff --git a/lib/util/nstring.h b/lib/util/nstring.h
index 9ed20051..d976e859 100644
--- a/lib/util/nstring.h
+++ b/lib/util/nstring.h
@@ -134,6 +134,11 @@ asprintfN(const char ** const resultP,
           const char *  const fmt,
           ...) PM_GNU_PRINTF_ATTR(2,3);
 
+void
+vasprintfN(const char ** const resultP,
+           const char *  const format,
+           va_list             args);
+
 void 
 strfree(const char * const string);