about summary refs log tree commit diff
path: root/lib/libpm.c
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
commit1fd361a1ea06e44286c213ca1f814f49306fdc43 (patch)
tree64c8c96cf54d8718847339a403e5e67b922e8c3f /lib/libpm.c
downloadnetpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.gz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.xz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.zip
Create Subversion repository
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'lib/libpm.c')
-rw-r--r--lib/libpm.c1494
1 files changed, 1494 insertions, 0 deletions
diff --git a/lib/libpm.c b/lib/libpm.c
new file mode 100644
index 00000000..2e563a09
--- /dev/null
+++ b/lib/libpm.c
@@ -0,0 +1,1494 @@
+/**************************************************************************
+                                  libpm.c
+***************************************************************************
+  This is the most fundamental Netpbm library.  It contains routines
+  not specific to any particular Netpbm format.
+
+  Some of the subroutines in this library are intended and documented
+  for use by Netpbm users, but most of them are just used by other
+  Netpbm library subroutines.
+
+  Before May 2001, this function was served by the libpbm library
+  (in addition to being the library for handling the PBM format).
+
+**************************************************************************/
+#define _SVID_SOURCE
+    /* Make sure P_tmpdir is defined in GNU libc 2.0.7 (_XOPEN_SOURCE 500
+       does it in other libc's).  pm_config.h defines TMPDIR as P_tmpdir
+       in some environments.
+    */
+#define _XOPEN_SOURCE 500    /* Make sure ftello, fseeko are defined */
+#define _LARGEFILE_SOURCE 1  /* Make sure ftello, fseeko are defined */
+#define _LARGEFILE64_SOURCE 1 
+#define _FILE_OFFSET_BITS 64
+    /* This means ftello() is really ftello64() and returns a 64 bit file
+       position.  Unless the C library doesn't have ftello64(), in which 
+       case ftello() is still just ftello().
+
+       Likewise for all the other C library file functions.
+
+       And off_t and fpos_t are 64 bit types instead of 32.  Consequently,
+       pm_filepos_t might be 64 bits instead of 32.
+    */
+#define _LARGE_FILES  
+    /* This does for AIX what _FILE_OFFSET_BITS=64 does for GNU */
+#define _LARGE_FILE_API
+    /* This makes the the x64() functions available on AIX */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <setjmp.h>
+#ifdef __DJGPP__
+  #include <io.h>
+#endif
+
+#include "pm_c_util.h"
+#include "version.h"
+#include "compile.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "pm.h"
+
+/* The following are set by pm_init(), then used by subsequent calls to other
+   pm_xxx() functions.
+   */
+static const char* pm_progname;
+static bool pm_showmessages;  
+    /* Programs should display informational messages (because the user didn't
+       specify the --quiet option).
+    */
+
+int pm_plain_output;
+    /* Boolean: programs should produce output in plain format */
+
+static jmp_buf * pm_jmpbufP = NULL;
+    /* A description of the point to which the program should hyperjump
+       if a libnetpbm function encounters an error (libnetpbm functions
+       don't normally return in that case).
+
+       User sets this to something in his own extra-library context.
+       Libnetpbm routines that have something that needs to be cleaned up
+       preempt it.
+
+       NULL, which is the default value, means when a libnetpbm function
+       encounters an error, it causes the process to exit.
+    */
+
+
+
+void
+pm_setjmpbuf(jmp_buf * const jmpbufP) {
+    pm_jmpbufP = jmpbufP;
+}
+
+
+
+void
+pm_setjmpbufsave(jmp_buf *  const jmpbufP,
+                 jmp_buf ** const oldJmpbufPP) {
+
+    *oldJmpbufPP = pm_jmpbufP;
+    pm_jmpbufP = jmpbufP;
+}
+
+
+
+void
+pm_longjmp(void) {
+
+    if (pm_jmpbufP)
+        longjmp(*pm_jmpbufP, 1);
+    else
+        exit(1);
+}
+
+
+
+void
+pm_usage(const char usage[]) {
+    pm_error("usage:  %s %s", pm_progname, usage);
+}
+
+
+
+void
+pm_perror(const char reason[] ) {
+
+    if (reason != NULL && strlen(reason) != 0)
+        pm_error("%s - errno=%d (%s)", reason, errno, strerror(errno));
+    else
+        pm_error("Something failed with errno=%d (%s)", 
+                 errno, strerror(errno));
+}
+
+
+
+void PM_GNU_PRINTF_ATTR(1,2)
+pm_message(const char format[], ...) {
+
+    va_list args;
+
+    va_start(args, format);
+
+    if (pm_showmessages) {
+        fprintf(stderr, "%s: ", pm_progname);
+        vfprintf(stderr, format, args);
+        fputc('\n', stderr);
+    }
+    va_end(args);
+}
+
+
+
+void PM_GNU_PRINTF_ATTR(1,2)
+pm_error(const char format[], ...) {
+    va_list args;
+
+    va_start(args, format);
+
+    fprintf(stderr, "%s: ", pm_progname);
+    vfprintf(stderr, format, args);
+    fputc('\n', stderr);
+    va_end(args);
+
+    pm_longjmp();
+}
+
+
+/* Variable-sized arrays. */
+
+char *
+pm_allocrow(unsigned int const cols,
+            unsigned int const size) {
+
+    char * itrow;
+
+    if (UINT_MAX / cols < size)
+        pm_error("Arithmetic overflow multiplying %u by %u to get the "
+                 "size of a row to allocate.", cols, size);
+
+    itrow = malloc(cols * size);
+    if (itrow == NULL)
+        pm_error("out of memory allocating a row");
+
+    return itrow;
+}
+
+
+
+void
+pm_freerow(char * const itrow) {
+    free(itrow);
+}
+
+
+
+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.
+
+   We use a special format where we tack on an extra element to the row
+   index to indicate the format of the array.
+
+   We have two ways of allocating the space: fragmented and
+   unfragmented.  In both, the row index (plus the extra element) is
+   in one block of memory.  In the fragmented format, each row is
+   also in an independent memory block, and the extra row pointer is
+   NULL.  In the unfragmented format, all the rows are in a single
+   block of memory called the row heap and the extra row pointer is
+   the address of that block.
+
+   We use unfragmented format if possible, but if the allocation of the
+   row heap fails, we fall back to fragmented.
+-----------------------------------------------------------------------------*/
+    char** rowIndex;
+    char * rowheap;
+
+    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 */
+
+        for (row = 0; row < rows; ++row)
+            rowIndex[row] = &(rowheap[row * cols * size]);
+    }
+    return rowIndex;
+}
+
+
+
+void
+pm_freearray(char ** const rowIndex, 
+             int     const rows) {
+
+    void * const rowheap = rowIndex[rows];
+
+    if (rowheap != NULL)
+        free(rowheap);
+    else {
+        unsigned int row;
+        for (row = 0; row < rows; ++row)
+            pm_freerow(rowIndex[row]);
+    }
+    free(rowIndex);
+}
+
+
+
+/* Case-insensitive keyword matcher. */
+
+int
+pm_keymatch(char *       const strarg, 
+            const char * const keywordarg, 
+            int          const minchars) {
+    int len;
+    const char *keyword;
+    char *str;
+
+    str = strarg;
+    keyword = keywordarg;
+
+    len = strlen( str );
+    if ( len < minchars )
+        return 0;
+    while ( --len >= 0 )
+        {
+        register char c1, c2;
+
+        c1 = *str++;
+        c2 = *keyword++;
+        if ( c2 == '\0' )
+            return 0;
+        if ( ISUPPER( c1 ) )
+            c1 = tolower( c1 );
+        if ( ISUPPER( c2 ) )
+            c2 = tolower( c2 );
+        if ( c1 != c2 )
+            return 0;
+        }
+    return 1;
+}
+
+
+/* Log base two hacks. */
+
+int
+pm_maxvaltobits(int const maxval) {
+    if ( maxval <= 1 )
+        return 1;
+    else if ( maxval <= 3 )
+        return 2;
+    else if ( maxval <= 7 )
+        return 3;
+    else if ( maxval <= 15 )
+        return 4;
+    else if ( maxval <= 31 )
+        return 5;
+    else if ( maxval <= 63 )
+        return 6;
+    else if ( maxval <= 127 )
+        return 7;
+    else if ( maxval <= 255 )
+        return 8;
+    else if ( maxval <= 511 )
+        return 9;
+    else if ( maxval <= 1023 )
+        return 10;
+    else if ( maxval <= 2047 )
+        return 11;
+    else if ( maxval <= 4095 )
+        return 12;
+    else if ( maxval <= 8191 )
+        return 13;
+    else if ( maxval <= 16383 )
+        return 14;
+    else if ( maxval <= 32767 )
+        return 15;
+    else if ( (long) maxval <= 65535L )
+        return 16;
+    else
+        pm_error( "maxval of %d is too large!", maxval );
+        return -1;  /* Should never come here */
+}
+
+int
+pm_bitstomaxval(int const bits) {
+    return ( 1 << bits ) - 1;
+}
+
+
+unsigned int PURE_FN_ATTR
+pm_lcm(unsigned int const x, 
+       unsigned int const y,
+       unsigned int const z,
+       unsigned int const limit) {
+/*----------------------------------------------------------------------------
+  Compute the least common multiple of 'x', 'y', and 'z'.  If it's bigger than
+  'limit', though, just return 'limit'.
+-----------------------------------------------------------------------------*/
+    unsigned int biggest;
+    unsigned int candidate;
+
+    if (x == 0 || y == 0 || z == 0)
+        pm_error("pm_lcm(): Least common multiple of zero taken.");
+
+    biggest = MAX(x, MAX(y,z));
+
+    candidate = biggest;
+    while (((candidate % x) != 0 ||       /* not a multiple of x */
+            (candidate % y) != 0 ||       /* not a multiple of y */
+            (candidate % z) != 0 ) &&     /* not a multiple of z */
+           candidate <= limit)
+        candidate += biggest;
+
+    if (candidate > limit) 
+        candidate = limit;
+
+    return candidate;
+}
+
+
+/* Initialization. */
+
+
+#ifdef VMS
+static const char *
+vmsProgname(int * const argcP, char * argv[]) {   
+    char **temp_argv = argv;
+    int old_argc = *argcP;
+    int i;
+    const char * retval;
+    
+    getredirection( argcP, &temp_argv );
+    if (*argcP > old_argc) {
+        /* Number of command line arguments has increased */
+        fprintf( stderr, "Sorry!! getredirection() for VMS has "
+                 "changed the argument list!!!\n");
+        fprintf( stderr, "This is intolerable at the present time, "
+                 "so we must stop!!!\n");
+        exit(1);
+    }
+    for (i=0; i<*argcP; i++)
+        argv[i] = temp_argv[i];
+    retval = strrchr( argv[0], '/');
+    if ( retval == NULL ) retval = rindex( argv[0], ']');
+    if ( retval == NULL ) retval = rindex( argv[0], '>');
+
+    return retval;
+}
+#endif
+
+
+
+void
+pm_init(const char * const progname, unsigned int const flags) {
+/*----------------------------------------------------------------------------
+   Initialize static variables that Netpbm library routines use.
+
+   Any user of Netpbm library routines is expected to call this at the
+   beginning of this program, before any other Netpbm library routines.
+
+   A program may call this via pm_proginit() instead, though.
+-----------------------------------------------------------------------------*/
+    pm_setMessage(FALSE, NULL);
+
+    pm_progname = progname;
+
+#ifdef O_BINARY
+#ifdef HAVE_SETMODE
+    /* Set the stdin and stdout mode to binary.  This means nothing on Unix,
+       but matters on Windows.
+       
+       Note that stdin and stdout aren't necessarily image files.  In
+       particular, stdout is sometimes text for human consumption,
+       typically printed on the terminal.  Binary mode isn't really
+       appropriate for that case.  We do this setting here without
+       any knowledge of how stdin and stdout are being used because it is
+       easy.  But we do make an exception for the case that we know the
+       file is a terminal, to get a little closer to doing the right
+       thing.  
+    */
+    if (!isatty(0)) setmode(0,O_BINARY);  /* Standard Input */
+    if (!isatty(1)) setmode(1,O_BINARY);  /* Standard Output */
+#endif /* HAVE_SETMODE */
+#endif /* O_BINARY */
+}
+
+
+
+static void
+showVersion(void) {
+    pm_message( "Using libnetpbm from Netpbm Version: %s", NETPBM_VERSION );
+#if defined(COMPILE_TIME) && defined(COMPILED_BY)
+    pm_message( "Compiled %s by user \"%s\"",
+                COMPILE_TIME, COMPILED_BY );
+#endif
+#ifdef BSD
+    pm_message( "BSD defined" );
+#endif /*BSD*/
+#ifdef SYSV
+#ifdef VMS
+    pm_message( "VMS & SYSV defined" );
+#else
+    pm_message( "SYSV defined" );
+#endif
+#endif /*SYSV*/
+#ifdef MSDOS
+    pm_message( "MSDOS defined" );
+#endif /*MSDOS*/
+#ifdef AMIGA
+    pm_message( "AMIGA defined" );
+#endif /* AMIGA */
+    {
+        const char * rgbdef;
+        pm_message( "RGB_ENV='%s'", RGBENV );
+        rgbdef = getenv(RGBENV);
+        if( rgbdef )
+            pm_message( "RGBENV= '%s' (env vbl set to '%s')", 
+                        RGBENV, rgbdef );
+        else
+            pm_message( "RGBENV= '%s' (env vbl is unset)", RGBENV);
+    }
+}
+
+
+
+static void
+showNetpbmHelp(const char progname[]) {
+/*----------------------------------------------------------------------------
+  Tell the user where to get help for this program, assuming it is a Netpbm
+  program (a program that comes with the Netpbm package, as opposed to a 
+  program that just uses the Netpbm libraries).
+
+  Tell him to go to the URL listed in the Netpbm configuration file.
+  The Netpbm configuration file is the file named by the NETPBM_CONF
+  environment variable, or /etc/netpbm if there is no such environment
+  variable.
+
+  If the configuration file doesn't exist or can't be read, or doesn't
+  contain a DOCURL value, tell him to go to a hardcoded source for
+  documentation.
+-----------------------------------------------------------------------------*/
+    const char * netpbmConfigFileName;
+    FILE * netpbmConfigFile;
+    char * docurl;
+
+    if (getenv("NETPBM_CONF"))
+        netpbmConfigFileName = getenv("NETPBM_CONF");
+    else 
+        netpbmConfigFileName = "/etc/netpbm";
+    
+    netpbmConfigFile = fopen(netpbmConfigFileName, "r");
+    if (netpbmConfigFile == NULL) {
+        pm_message("Unable to open Netpbm configuration file '%s'.  "
+                   "Errno = %d (%s).  "
+                   "Use the NETPBM_CONF environment variable "
+                   "to control the identity of the Netpbm configuration file.",
+                   netpbmConfigFileName,errno, strerror(errno));
+        docurl = NULL;
+    } else {
+        docurl = NULL;  /* default */
+        while (!feof(netpbmConfigFile) && !ferror(netpbmConfigFile)) {
+            char line[80+1];
+            fgets(line, sizeof(line), netpbmConfigFile);
+            if (line[0] != '#') {
+                sscanf(line, "docurl=%s", docurl);
+            }
+        }
+        if (docurl == NULL)
+            pm_message("No 'docurl=' line in Netpbm configuration file '%s'.",
+                       netpbmConfigFileName);
+    }
+    if (docurl == NULL)
+        pm_message("We have no reliable indication of where the Netpbm "
+                   "documentation is, but try "
+                   "http://netpbm.sourceforge.net or email "
+                   "Bryan Henderson (bryanh@giraffe-data.com) for help.");
+    else
+        pm_message("This program is part of the Netpbm package.  Find "
+                   "documentation for it at %s/%s\n", docurl, progname);
+}
+
+
+
+void
+pm_proginit(int * const argcP, char * argv[]) {
+/*----------------------------------------------------------------------------
+   Do various initialization things that all programs in the Netpbm package,
+   and programs that emulate such programs, should do.
+
+   This includes processing global options.
+
+   This includes calling pm_init() to initialize the Netpbm libraries.
+-----------------------------------------------------------------------------*/
+    int argn, i;
+    const char * progname;
+    bool showmessages;
+    bool show_version;
+        /* We're supposed to just show the version information, then exit the
+           program.
+        */
+    bool show_help;
+        /* We're supposed to just tell user where to get help, then exit the
+           program.
+        */
+    
+    /* Extract program name. */
+#ifdef VMS
+    progname = vmsProgname(argcP, argv);
+#else
+    progname = strrchr( argv[0], '/');
+#endif
+    if (progname == NULL)
+        progname = argv[0];
+    else
+        ++progname;
+
+    pm_init(progname, 0);
+
+    /* Check for any global args. */
+    showmessages = TRUE;
+    show_version = FALSE;
+    show_help = FALSE;
+    pm_plain_output = FALSE;
+    for (argn = 1; argn < *argcP; ++argn) {
+        if (pm_keymatch(argv[argn], "-quiet", 6) ||
+            pm_keymatch(argv[argn], "--quiet", 7)) 
+            showmessages = FALSE;
+        else if (pm_keymatch(argv[argn], "-version", 8) ||
+                   pm_keymatch(argv[argn], "--version", 9)) 
+            show_version = TRUE;
+        else if (pm_keymatch(argv[argn], "-help", 5) ||
+                 pm_keymatch(argv[argn], "--help", 6) ||
+                 pm_keymatch(argv[argn], "-?", 2)) 
+            show_help = TRUE;
+        else if (pm_keymatch(argv[argn], "-plain", 6) ||
+                 pm_keymatch(argv[argn], "--plain", 7))
+            pm_plain_output = TRUE;
+        else
+            continue;
+        for (i = argn + 1; i <= *argcP; ++i)
+            argv[i - 1] = argv[i];
+        --(*argcP);
+    }
+
+    pm_setMessage((unsigned int) showmessages, NULL);
+
+    if (show_version) {
+        showVersion();
+        exit( 0 );
+    } else if (show_help) {
+        pm_error("Use 'man %s' for help.", progname);
+        /* If we can figure out a way to distinguish Netpbm programs from 
+           other programs using the Netpbm libraries, we can do better here.
+        */
+        if (0)
+            showNetpbmHelp(progname);
+        exit(0);
+    }
+}
+
+
+void
+pm_setMessage(int const newState, int * const oldStateP) {
+    
+    if (oldStateP)
+        *oldStateP = pm_showmessages;
+
+    pm_showmessages = !!newState;
+}
+
+
+char *
+pm_arg0toprogname(const char arg0[]) {
+/*----------------------------------------------------------------------------
+   Given a value for argv[0] (a command name or file name passed to a 
+   program in the standard C calling sequence), return the name of the
+   Netpbm program to which is refers.
+
+   In the most ordinary case, this is simply the argument itself.
+
+   But if the argument contains a slash, it is the part of the argument 
+   after the last slash, and if there is a .exe on it (as there is for
+   DJGPP), that is removed.
+
+   The return value is in static storage within.  It is null-terminated,
+   but truncated at 64 characters.
+-----------------------------------------------------------------------------*/
+    static char retval[64+1];
+    char *slash_pos;
+
+    /* Chop any directories off the left end */
+    slash_pos = strrchr(arg0, '/');
+
+    if (slash_pos == NULL) {
+        strncpy(retval, arg0, sizeof(retval));
+        retval[sizeof(retval)-1] = '\0';
+    } else {
+        strncpy(retval, slash_pos +1, sizeof(retval));
+        retval[sizeof(retval)-1] = '\0';
+    }
+
+    /* Chop any .exe off the right end */
+    if (strlen(retval) >= 4 && strcmp(retval+strlen(retval)-4, ".exe") == 0)
+        retval[strlen(retval)-4] = 0;
+
+    return(retval);
+}
+
+
+
+/* File open/close that handles "-" as stdin/stdout and checks errors. */
+
+FILE*
+pm_openr(const char * const name) {
+    FILE* f;
+
+    if (strcmp(name, "-") == 0)
+        f = stdin;
+    else {
+#ifndef VMS
+        f = fopen(name, "rb");
+#else
+        f = fopen(name, "r", "ctx=stm");
+#endif
+        if (f == NULL) 
+            pm_error("Unable to open file '%s' for reading.  "
+                     "fopen() returns errno %d (%s)", 
+                     name, errno, strerror(errno));
+    }
+    return f;
+}
+
+
+
+FILE*
+pm_openw(const char * const name) {
+    FILE* f;
+
+    if (strcmp(name, "-") == 0)
+        f = stdout;
+    else {
+#ifndef VMS
+        f = fopen(name, "wb");
+#else
+        f = fopen(name, "w", "mbc=32", "mbf=2");  /* set buffer factors */
+#endif
+        if (f == NULL) 
+            pm_error("Unable to open file '%s' for writing.  "
+                     "fopen() returns errno %d (%s)", 
+                     name, errno, strerror(errno));
+    }
+    return f;
+}
+
+
+
+static const char *
+tmpDir(void) {
+/*----------------------------------------------------------------------------
+   Return the name of the directory in which we should create temporary
+   files.
+
+   The name is a constant in static storage.
+-----------------------------------------------------------------------------*/
+    const char * tmpdir;
+        /* running approximation of the result */
+
+    tmpdir = getenv("TMPDIR");   /* Unix convention */
+
+    if (!tmpdir || strlen(tmpdir) == 0)
+        tmpdir = getenv("TMP");  /* Windows convention */
+
+    if (!tmpdir || strlen(tmpdir) == 0)
+        tmpdir = getenv("TEMP"); /* Windows convention */
+
+    if (!tmpdir || strlen(tmpdir) == 0)
+        tmpdir = TMPDIR;
+
+    return tmpdir;
+}
+
+
+
+static int
+mkstempx(char * const filenameBuffer) {
+/*----------------------------------------------------------------------------
+  This is meant to be equivalent to POSIX mkstemp().
+
+  On some old systems, mktemp() is a security hazard that allows a hacker
+  to read or write our temporary file or cause us to read or write some
+  unintended file.  On other systems, mkstemp() does not exist.
+
+  A Windows/mingw environment is one which doesn't have mkstemp()
+  (2006.06.15).
+
+  We assume that if a system doesn't have mkstemp() that its mktemp()
+  is safe, or that the total situation is such that the problems of
+  mktemp() are not a problem for the user.
+-----------------------------------------------------------------------------*/
+    int retval;
+    int fd;
+    unsigned int attempts;
+    bool gotFile;
+    bool error;
+
+    for (attempts = 0, gotFile = FALSE, error = FALSE;
+         !gotFile && !error && attempts < 100;
+         ++attempts) {
+
+        char * rc;
+        rc = mktemp(filenameBuffer);
+
+        if (rc == NULL)
+            error = TRUE;
+        else {
+            int rc;
+
+            rc = open(filenameBuffer, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+
+            if (rc == 0) {
+                fd = rc;
+                gotFile = TRUE;
+            } else {
+                if (errno == EEXIST) {
+                    /* We'll just have to keep trying */
+                } else 
+                    error = TRUE;
+            }
+        }
+    }    
+    if (gotFile)
+        retval = fd;
+    else
+        retval = -1;
+
+    return retval;
+}
+
+
+
+static int
+mkstemp2(char * const filenameBuffer) {
+
+#if HAVE_MKSTEMP
+    if (0)
+        mkstempx(NULL);  /* defeat compiler unused function warning */
+    return mkstemp(filenameBuffer);
+#else
+    return mkstempx(filenameBuffer);
+#endif
+}
+
+
+
+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;
+
+    fnamelen = strlen (pm_progname) + 10; /* "/" + "_XXXXXX\0" */
+
+    tmpdir = tmpDir();
+
+    if (tmpdir[strlen(tmpdir) - 1] == '/')
+        dirseparator = "";
+    else
+        dirseparator = "/";
+    
+    asprintfN(&filenameTemplate, "%s%s%s%s", 
+              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));
+    else {
+        fileP = fdopen(fd, "w+b");
+
+        if (fileP == NULL)
+            pm_error("Unable to create temporary file.  fdopen() failed "
+                     "with errno %d (%s)", errno, strerror(errno));
+    }
+    strfree(filenameTemplate);
+
+    *filenameP = filenameBuffer;
+    *filePP = fileP;
+}
+
+
+
+FILE * 
+pm_tmpfile(void) {
+
+    FILE * fileP;
+    const char * tmpfile;
+
+    pm_make_tmpfile(&fileP, &tmpfile);
+
+    unlink(tmpfile);
+
+    strfree(tmpfile);
+
+    return fileP;
+}
+
+
+
+FILE *
+pm_openr_seekable(const char name[]) {
+/*----------------------------------------------------------------------------
+  Open the file named by name[] such that it is seekable (i.e. it can be
+  rewound and read in multiple passes with fseek()).
+
+  If the file is actually seekable, this reduces to the same as
+  pm_openr().  If not, we copy the named file to a temporary file
+  and return that file's stream descriptor.
+
+  We use a file that the operating system recognizes as temporary, so
+  it picks the filename and deletes the file when Caller closes it.
+-----------------------------------------------------------------------------*/
+    int stat_rc;
+    int seekable;  /* logical: file is seekable */
+    struct stat statbuf;
+    FILE * original_file;
+    FILE * seekable_file;
+
+    original_file = pm_openr((char *) name);
+
+    /* I would use fseek() to determine if the file is seekable and 
+       be a little more general than checking the type of file, but I
+       don't have reliable information on how to do that.  I have seen
+       streams be partially seekable -- you can, for example seek to
+       0 if the file is positioned at 0 but you can't actually back up
+       to 0.  I have seen documentation that says the errno for an
+       unseekable stream is EBADF and in practice seen ESPIPE.
+
+       On the other hand, regular files are always seekable and even if
+       some other file is, it doesn't hurt much to assume it isn't.
+    */
+
+    stat_rc = fstat(fileno(original_file), &statbuf);
+    if (stat_rc == 0 && S_ISREG(statbuf.st_mode))
+        seekable = TRUE;
+    else 
+        seekable = FALSE;
+
+    if (seekable) {
+        seekable_file = original_file;
+    } else {
+        seekable_file = pm_tmpfile();
+        
+        /* Copy the input into the temporary seekable file */
+        while (!feof(original_file) && !ferror(original_file) 
+               && !ferror(seekable_file)) {
+            char buffer[4096];
+            int bytes_read;
+            bytes_read = fread(buffer, 1, sizeof(buffer), original_file);
+            fwrite(buffer, 1, bytes_read, seekable_file);
+        }
+        if (ferror(original_file))
+            pm_error("Error reading input file into temporary file.  "
+                     "Errno = %s (%d)", strerror(errno), errno);
+        if (ferror(seekable_file))
+            pm_error("Error writing input into temporary file.  "
+                     "Errno = %s (%d)", strerror(errno), errno);
+        pm_close(original_file);
+        {
+            int seek_rc;
+            seek_rc = fseek(seekable_file, 0, SEEK_SET);
+            if (seek_rc != 0)
+                pm_error("fseek() failed to rewind temporary file.  "
+                         "Errno = %s (%d)", strerror(errno), errno);
+        }
+    }
+    return seekable_file;
+}
+
+
+
+void
+pm_close(FILE * const f) {
+    fflush(f);
+    if (ferror(f))
+        pm_message("A file read or write error occurred at some point");
+    if (f != stdin)
+        if (fclose(f) != 0)
+            pm_error("close of file failed with errno %d (%s)",
+                     errno, strerror(errno));
+}
+
+
+
+/* The pnmtopng package uses pm_closer() and pm_closew() instead of 
+   pm_close(), apparently because the 1999 Pbmplus package has them.
+   I don't know what the difference is supposed to be.
+*/
+
+void
+pm_closer(FILE * const f) {
+    pm_close(f);
+}
+
+
+
+void
+pm_closew(FILE * const f) {
+    pm_close(f);
+}
+
+
+
+/* Endian I/O.
+
+   Before Netpbm 10.27 (March 2005), these would return failure on EOF
+   or I/O failure.  For backward compatibility, they still have the return
+   code, but it is always zero and the routines abort the program in case
+   of EOF or I/O failure.  A program that wants to handle failure differently
+   must use lower level (C library) interfaces.  But that level of detail
+   is uncharacteristic of a Netpbm program; the ease of programming that
+   comes with not checking a return code is more Netpbm.
+
+   It is also for historical reasons that these return signed values,
+   when clearly unsigned would make more sense.
+*/
+
+
+
+static void
+abortWithReadError(FILE * const ifP) {
+
+    if (feof(ifP))
+        pm_error("Unexpected end of input file");
+    else
+        pm_error("Error (not EOF) reading file.");
+}
+
+
+
+void
+pm_readchar(FILE * const ifP,
+            char * const cP) {
+    
+    int c;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+
+    *cP = c;
+}
+
+
+
+void
+pm_writechar(FILE * const ofP,
+             char   const c) {
+
+    putc(c, ofP);
+}
+
+
+
+int
+pm_readbigshort(FILE *  const ifP, 
+                short * const sP) {
+    int c;
+
+    unsigned short s;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    s = (c & 0xff) << 8;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    s |= c & 0xff;
+
+    *sP = s;
+
+    return 0;
+}
+
+
+
+int
+pm_writebigshort(FILE * const ofP, 
+                 short  const s) {
+
+    putc((s >> 8) & 0xff, ofP);
+    putc(s & 0xff, ofP);
+
+    return 0;
+}
+
+
+
+int
+pm_readbiglong(FILE * const ifP, 
+               long * const lP) {
+
+    int c;
+    unsigned long l;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l = c << 24;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 16;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 8;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c;
+
+    *lP = l;
+
+    return 0;
+}
+
+
+
+int
+pm_writebiglong(FILE * const ofP, 
+                long   const l) {
+
+    putc((l >> 24) & 0xff, ofP);
+    putc((l >> 16) & 0xff, ofP);
+    putc((l >>  8) & 0xff, ofP);
+    putc((l >>  0) & 0xff, ofP);
+
+    return 0;
+}
+
+
+
+int
+pm_readlittleshort(FILE *  const ifP, 
+                   short * const sP) {
+    int c;
+    unsigned short s;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    s = c & 0xff;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    s |= (c & 0xff) << 8;
+
+    *sP = s;
+
+    return 0;
+}
+
+
+
+int
+pm_writelittleshort(FILE * const ofP, 
+                    short  const s) {
+
+    putc((s >> 0) & 0xff, ofP);
+    putc((s >> 8) & 0xff, ofP);
+
+    return 0;
+}
+
+
+
+int
+pm_readlittlelong(FILE * const ifP, 
+                  long * const lP) {
+    int c;
+    unsigned long l;
+
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l = c;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 8;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 16;
+    c = getc(ifP);
+    if (c == EOF)
+        abortWithReadError(ifP);
+    l |= c << 24;
+
+    *lP = l;
+
+    return 0;
+}
+
+
+
+int
+pm_writelittlelong(FILE * const ofP, 
+                   long   const l) {
+
+    putc((l >>  0) & 0xff, ofP);
+    putc((l >>  8) & 0xff, ofP);
+    putc((l >> 16) & 0xff, ofP);
+    putc((l >> 24) & 0xff, ofP);
+
+    return 0;
+}
+
+
+
+int 
+pm_readmagicnumber(FILE * const ifP) {
+
+    int ich1, ich2;
+
+    ich1 = getc(ifP);
+    ich2 = getc(ifP);
+    if (ich1 == EOF || ich2 == EOF)
+        pm_error( "Error reading magic number from Netpbm image stream.  "
+                  "Most often, this "
+                  "means your input file is empty." );
+
+    return ich1 * 256 + ich2;
+}
+
+
+
+/* Read a file of unknown size to a buffer. Return the number of bytes
+   read. Allocate more memory as we need it. The calling routine has
+   to free() the buffer.
+
+   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 */
+#define PM_MAX_BUF_INC 65536   /* Don't allocate more memory in larger blocks
+                                   than this. */
+
+char *
+pm_read_unknown_size(FILE * const file, 
+                     long * const nread) {
+    long nalloc;
+    char * buf;
+    bool eof;
+
+    *nread = 0;
+    nalloc = PM_BUF_SIZE;
+    MALLOCARRAY(buf, nalloc);
+
+    eof = FALSE;  /* initial value */
+
+    while(!eof) {
+        int val;
+
+        if (*nread >= nalloc) { /* We need a larger buffer */
+            if (nalloc > PM_MAX_BUF_INC)
+                nalloc += PM_MAX_BUF_INC;
+            else
+                nalloc += nalloc;
+            REALLOCARRAY_NOFAIL(buf, nalloc);
+        }
+
+        val = getc(file);
+        if (val == EOF)
+            eof = TRUE;
+        else 
+            buf[(*nread)++] = val;
+    }
+    return buf;
+}
+
+
+
+union cheat {
+    uint32n l;
+    short s;
+    unsigned char c[4];
+};
+
+
+
+short
+pm_bs_short(short const s) {
+    union cheat u;
+    unsigned char t;
+
+    u.s = s;
+    t = u.c[0];
+    u.c[0] = u.c[1];
+    u.c[1] = t;
+    return u.s;
+}
+
+
+
+long
+pm_bs_long(long const l) {
+    union cheat u;
+    unsigned char t;
+
+    u.l = l;
+    t = u.c[0];
+    u.c[0] = u.c[3];
+    u.c[3] = t;
+    t = u.c[1];
+    u.c[1] = u.c[2];
+    u.c[2] = t;
+    return u.l;
+}
+
+
+
+void
+pm_tell2(FILE *       const fileP, 
+         void *       const fileposP,
+         unsigned int const fileposSize) {
+/*----------------------------------------------------------------------------
+   Return the current file position as *filePosP, which is a buffer
+   'fileposSize' bytes long.  Abort the program if error, including if
+   *fileP isn't a file that has a position.
+-----------------------------------------------------------------------------*/
+    /* Note: FTELLO() is either ftello() or ftell(), depending on the
+       capabilities of the underlying C library.  It is defined in
+       pm_config.h.  ftello(), in turn, may be either ftell() or
+       ftello64(), as implemented by the C library.
+    */
+    pm_filepos const filepos = FTELLO(fileP);
+    if (filepos < 0)
+        pm_error("ftello() to get current file position failed.  "
+                 "Errno = %s (%d)\n", strerror(errno), errno);
+
+    if (fileposSize == sizeof(pm_filepos)) {
+        pm_filepos * const fileposP_filepos = fileposP;
+        *fileposP_filepos = filepos;
+    } else if (fileposSize == sizeof(long)) {
+        if (sizeof(pm_filepos) > sizeof(long) &&
+            filepos >= (pm_filepos) 1 << (sizeof(long)*8))
+            pm_error("File size is too large to represent in the %u bytes "
+                     "that were provided to pm_tell2()", fileposSize);
+        else {
+            long * const fileposP_long = fileposP;
+            *fileposP_long = (long)filepos;
+        }
+    } else
+        pm_error("File position size passed to pm_tell() is invalid: %u.  "
+                 "Valid sizes are %u and %u", 
+                 fileposSize, sizeof(pm_filepos), sizeof(long));
+}
+
+
+
+unsigned int
+pm_tell(FILE * const fileP) {
+    
+    long filepos;
+
+    pm_tell2(fileP, &filepos, sizeof(filepos));
+
+    return filepos;
+}
+
+
+
+void
+pm_seek2(FILE *             const fileP, 
+         const pm_filepos * const fileposP,
+         unsigned int       const fileposSize) {
+/*----------------------------------------------------------------------------
+   Position file *fileP to position *fileposP.  Abort if error, including
+   if *fileP isn't a seekable file.
+-----------------------------------------------------------------------------*/
+    if (fileposSize == sizeof(pm_filepos)) 
+        /* Note: FSEEKO() is either fseeko() or fseek(), depending on the
+           capabilities of the underlying C library.  It is defined in
+           pm_config.h.  fseeko(), in turn, may be either fseek() or
+           fseeko64(), as implemented by the C library.
+        */
+        FSEEKO(fileP, *fileposP, SEEK_SET);
+    else if (fileposSize == sizeof(long)) {
+        long const fileposLong = *(long *)fileposP;
+        fseek(fileP, fileposLong, SEEK_SET);
+    } else
+        pm_error("File position size passed to pm_seek() is invalid: %u.  "
+                 "Valid sizes are %u and %u", 
+                 fileposSize, sizeof(pm_filepos), sizeof(long));
+}
+
+
+
+void
+pm_seek(FILE * const fileP, unsigned long filepos) {
+/*----------------------------------------------------------------------------
+-----------------------------------------------------------------------------*/
+
+    pm_filepos fileposBuff;
+
+    fileposBuff = filepos;
+
+    pm_seek2(fileP, &fileposBuff, sizeof(fileposBuff));
+}
+
+
+
+void
+pm_nextimage(FILE * const file, int * const eofP) {
+/*----------------------------------------------------------------------------
+   Position the file 'file' to the next image in the stream, assuming it is
+   now positioned just after the current image.  I.e. read off any white
+   space at the end of the current image's raster.  Note that the raw formats
+   don't permit such white space, but this routine tolerates it anyway, 
+   because the plain formats do permit white space after the raster.
+
+   Iff there is no next image, return *eofP == TRUE.
+
+   Note that in practice, we will not normally see white space here in
+   a plain PPM or plain PGM stream because the routine to read a
+   sample from the image reads one character of white space after the
+   sample in order to know where the sample ends.  There is not
+   normally more than one character of white space (a newline) after
+   the last sample in the raster.  But plain PBM is another story.  No white
+   space is required between samples of a plain PBM image.  But the raster
+   normally ends with a newline nonetheless.  Since the sample reading code
+   will not have read that newline, it is there for us to read now.
+-----------------------------------------------------------------------------*/
+    bool eof;
+    bool nonWhitespaceFound;
+
+    eof = FALSE;
+    nonWhitespaceFound = FALSE;
+
+    while (!eof && !nonWhitespaceFound) {
+        int c;
+        c = getc(file);
+        if (c == EOF) {
+            if (feof(file)) 
+                eof = TRUE;
+            else
+                pm_error("File error on getc() to position to image");
+        } else {
+            if (!isspace(c)) {
+                int rc;
+
+                nonWhitespaceFound = TRUE;
+
+                /* Have to put the non-whitespace character back in
+                   the stream -- it's part of the next image.  
+                */
+                rc = ungetc(c, file);
+                if (rc == EOF) 
+                    pm_error("File error doing ungetc() "
+                             "to position to image.");
+            }
+        }
+    }
+    *eofP = eof;
+}
+
+
+
+void
+pm_check(FILE *               const file, 
+         enum pm_check_type   const check_type, 
+         pm_filepos           const need_raster_size,
+         enum pm_check_code * const retval_p) {
+/*----------------------------------------------------------------------------
+   This is not defined for use outside of libnetpbm.
+-----------------------------------------------------------------------------*/
+    struct stat statbuf;
+    pm_filepos curpos;  /* Current position of file; -1 if none */
+    int rc;
+
+#ifdef LARGEFILEDEBUG
+    pm_message("pm_filepos received by pm_check() is %u bytes.",
+               sizeof(pm_filepos));
+#endif
+    /* Note: FTELLO() is either ftello() or ftell(), depending on the
+       capabilities of the underlying C library.  It is defined in
+       pm_config.h.  ftello(), in turn, may be either ftell() or
+       ftello64(), as implemented by the C library.
+    */
+    curpos = FTELLO(file);
+    if (curpos >= 0) {
+        /* This type of file has a current position */
+            
+        rc = fstat(fileno(file), &statbuf);
+        if (rc != 0) 
+            pm_error("fstat() failed to get size of file, though ftello() "
+                     "successfully identified\n"
+                     "the current position.  Errno=%s (%d)",
+                     strerror(errno), errno);
+        else if (!S_ISREG(statbuf.st_mode)) {
+            /* Not a regular file; we can't know its size */
+            if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+        } else {
+            pm_filepos const have_raster_size = statbuf.st_size - curpos;
+            
+            if (have_raster_size < need_raster_size)
+                pm_error("File has invalid format.  The raster should "
+                         "contain %u bytes, but\n"
+                         "the file ends after only %u bytes.",
+                         (unsigned int) need_raster_size, 
+                         (unsigned int) have_raster_size);
+            else if (have_raster_size > need_raster_size) {
+                if (retval_p) *retval_p = PM_CHECK_TOO_LONG;
+            } else {
+                if (retval_p) *retval_p = PM_CHECK_OK;
+            }
+        }
+    } else
+        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
+}
+
+
+