Blame lib/pmfileio.c

Packit 78deda
/**************************************************************************
Packit 78deda
                                 pmfileio.c
Packit 78deda
***************************************************************************
Packit 78deda
  This file contains fundamental file I/O stuff for libnetpbm.
Packit 78deda
  These are external functions, unlike 'fileio.c', but are not
Packit 78deda
  particular to any Netpbm format.
Packit 78deda
**************************************************************************/
Packit 78deda
#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
Packit 78deda
#define _SVID_SOURCE
Packit 78deda
    /* Make sure P_tmpdir is defined in GNU libc 2.0.7 (_XOPEN_SOURCE 500
Packit 78deda
       does it in other libc's).  pm_config.h defines TMPDIR as P_tmpdir
Packit 78deda
       in some environments.
Packit 78deda
    */
Packit 78deda
#define _BSD_SOURCE    /* Make sure strdup is defined */
Packit 78deda
#define _XOPEN_SOURCE 500    /* Make sure ftello, fseeko, strdup are defined */
Packit 78deda
#define _LARGEFILE_SOURCE 1  /* Make sure ftello, fseeko are defined */
Packit 78deda
#define _LARGEFILE64_SOURCE 1 
Packit 78deda
#define _FILE_OFFSET_BITS 64
Packit 78deda
    /* This means ftello() is really ftello64() and returns a 64 bit file
Packit 78deda
       position.  Unless the C library doesn't have ftello64(), in which 
Packit 78deda
       case ftello() is still just ftello().
Packit 78deda
Packit 78deda
       Likewise for all the other C library file functions.
Packit 78deda
Packit 78deda
       And off_t and fpos_t are 64 bit types instead of 32.  Consequently,
Packit 78deda
       pm_filepos_t might be 64 bits instead of 32.
Packit 78deda
    */
Packit 78deda
#define _LARGE_FILES  
Packit 78deda
    /* This does for AIX what _FILE_OFFSET_BITS=64 does for GNU */
Packit 78deda
#define _LARGE_FILE_API
Packit 78deda
    /* This makes the the x64() functions available on AIX */
Packit 78deda
Packit 78deda
#include "netpbm/pm_config.h"
Packit 78deda
#include <unistd.h>
Packit 78deda
#include <assert.h>
Packit 78deda
#include <stdio.h>
Packit 78deda
#include <fcntl.h>
Packit 78deda
#include <stdarg.h>
Packit 78deda
#include <string.h>
Packit 78deda
#include <errno.h>
Packit 78deda
#if HAVE_IO_H
Packit 78deda
  #include <io.h>  /* For mktemp */
Packit 78deda
#endif
Packit 78deda
Packit 78deda
#include "netpbm/pm_c_util.h"
Packit 78deda
#include "netpbm/mallocvar.h"
Packit 78deda
#include "netpbm/nstring.h"
Packit 78deda
Packit 78deda
#include "pm.h"
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
/* File open/close that handles "-" as stdin/stdout and checks errors. */
Packit 78deda
Packit 78deda
FILE *
Packit 78deda
pm_openr(const char * const name) {
Packit 78deda
    FILE * f;
Packit 78deda
Packit 78deda
    if (streq(name, "-"))
Packit 78deda
        f = stdin;
Packit 78deda
    else {
Packit 78deda
        f = fopen(name, "rb");
Packit 78deda
        if (f == NULL) 
Packit 78deda
            pm_error("Unable to open file '%s' for reading.  "
Packit 78deda
                     "fopen() returns errno %d (%s)", 
Packit 78deda
                     name, errno, strerror(errno));
Packit 78deda
    }
Packit 78deda
    return f;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
FILE *
Packit 78deda
pm_openw(const char * const name) {
Packit 78deda
    FILE * f;
Packit 78deda
Packit 78deda
    if (streq(name, "-"))
Packit 78deda
        f = stdout;
Packit 78deda
    else {
Packit 78deda
        f = fopen(name, "wb");
Packit 78deda
        if (f == NULL) 
Packit 78deda
            pm_error("Unable to open file '%s' for writing.  "
Packit 78deda
                     "fopen() returns errno %d (%s)", 
Packit 78deda
                     name, errno, strerror(errno));
Packit 78deda
    }
Packit 78deda
    return f;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static const char *
Packit 78deda
tmpDir(void) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Return the name of the directory in which we should create temporary
Packit 78deda
   files.
Packit 78deda
Packit 78deda
   The name is a constant in static storage.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    const char * tmpdir;
Packit 78deda
        /* running approximation of the result */
Packit 78deda
Packit 78deda
    tmpdir = getenv("TMPDIR");   /* Unix convention */
Packit 78deda
Packit 78deda
    if (!tmpdir || strlen(tmpdir) == 0)
Packit 78deda
        tmpdir = getenv("TMP");  /* Windows convention */
Packit 78deda
Packit 78deda
    if (!tmpdir || strlen(tmpdir) == 0)
Packit 78deda
        tmpdir = getenv("TEMP"); /* Windows convention */
Packit 78deda
Packit 78deda
    if (!tmpdir || strlen(tmpdir) == 0)
Packit 78deda
        tmpdir = TMPDIR;
Packit 78deda
Packit 78deda
    return tmpdir;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static int
Packit 78deda
tempFileOpenFlags(void) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
  Open flags (argument to open()) suitable for a new temporary file  
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    int retval;
Packit 78deda
Packit 78deda
    retval = 0
Packit 78deda
        | O_CREAT
Packit 78deda
        | O_RDWR
Packit 78deda
#if !MSVCRT
Packit 78deda
        | O_EXCL
Packit 78deda
#endif
Packit 78deda
#if MSVCRT
Packit 78deda
        | O_BINARY
Packit 78deda
#endif
Packit 78deda
        ;
Packit 78deda
Packit 78deda
    return retval;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static int
Packit 78deda
mkstempx(char * const filenameBuffer) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
  This is meant to be equivalent to POSIX mkstemp().
Packit 78deda
Packit 78deda
  On some old systems, mktemp() is a security hazard that allows a hacker
Packit 78deda
  to read or write our temporary file or cause us to read or write some
Packit 78deda
  unintended file.  On other systems, mkstemp() does not exist.
Packit 78deda
Packit 78deda
  A Windows/mingw environment is one which doesn't have mkstemp()
Packit 78deda
  (2006.06.15).
Packit 78deda
Packit 78deda
  We assume that if a system doesn't have mkstemp() that its mktemp()
Packit 78deda
  is safe, or that the total situation is such that the problems of
Packit 78deda
  mktemp() are not a problem for the user.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    int retval;
Packit 78deda
    int fd;
Packit 78deda
    unsigned int attempts;
Packit 78deda
    bool gotFile;
Packit 78deda
    bool error;
Packit 78deda
Packit 78deda
    for (attempts = 0, gotFile = FALSE, error = FALSE;
Packit 78deda
         !gotFile && !error && attempts < 100;
Packit 78deda
         ++attempts) {
Packit 78deda
Packit 78deda
        char * rc;
Packit 78deda
        rc = mktemp(filenameBuffer);
Packit 78deda
Packit 78deda
        if (rc == NULL)
Packit 78deda
            error = TRUE;
Packit 78deda
        else {
Packit 78deda
            int rc;
Packit 78deda
Packit 78deda
            rc = open(filenameBuffer, tempFileOpenFlags(),
Packit 78deda
                      PM_S_IWUSR | PM_S_IRUSR);
Packit 78deda
Packit 78deda
            if (rc >= 0) {
Packit 78deda
                fd = rc;
Packit 78deda
                gotFile = TRUE;
Packit 78deda
            } else {
Packit 78deda
                if (errno == EEXIST) {
Packit 78deda
                    /* We'll just have to keep trying */
Packit 78deda
                } else 
Packit 78deda
                    error = TRUE;
Packit 78deda
            }
Packit 78deda
        }
Packit 78deda
    }    
Packit 78deda
    if (gotFile)
Packit 78deda
        retval = fd;
Packit 78deda
    else
Packit 78deda
        retval = -1;
Packit 78deda
Packit 78deda
    return retval;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static int
Packit 78deda
mkstemp2(char * const filenameBuffer) {
Packit 78deda
Packit 78deda
#if HAVE_MKSTEMP
Packit 78deda
    if (0)
Packit 78deda
        mkstempx(NULL);  /* defeat compiler unused function warning */
Packit 78deda
    return mkstemp(filenameBuffer);
Packit 78deda
#else
Packit 78deda
    return mkstempx(filenameBuffer);
Packit 78deda
#endif
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
makeTmpfileWithTemplate(const char *  const filenameTemplate,
Packit 78deda
                        int *         const fdP,
Packit 78deda
                        const char ** const filenameP,
Packit 78deda
                        const char ** const errorP) {
Packit 78deda
    
Packit 78deda
    char * filenameBuffer;  /* malloc'ed */
Packit 78deda
Packit 78deda
    filenameBuffer = strdup(filenameTemplate);
Packit 78deda
Packit 78deda
    if (filenameBuffer == NULL)
Packit 78deda
        pm_asprintf(errorP, "Unable to allocate storage for temporary "
Packit 78deda
                    "file name");
Packit 78deda
    else {
Packit 78deda
        int rc;
Packit 78deda
        
Packit 78deda
        rc = mkstemp2(filenameBuffer);
Packit 78deda
        
Packit 78deda
        if (rc < 0)
Packit 78deda
            pm_asprintf(errorP,
Packit 78deda
                        "Unable to create temporary file according to name "
Packit 78deda
                        "pattern '%s'.  mkstemp() failed with errno %d (%s)",
Packit 78deda
                        filenameTemplate, errno, strerror(errno));
Packit 78deda
        else {
Packit 78deda
            *fdP = rc;
Packit 78deda
            *filenameP = filenameBuffer;
Packit 78deda
            *errorP = NULL;
Packit 78deda
        }
Packit 78deda
        if (*errorP)
Packit 78deda
            pm_strfree(filenameBuffer);
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
void
Packit 78deda
pm_make_tmpfile_fd(int *         const fdP,
Packit 78deda
                   const char ** const filenameP) {
Packit 78deda
Packit 78deda
    const char * filenameTemplate;
Packit 78deda
    const char * tmpdir;
Packit 78deda
    const char * dirseparator;
Packit 78deda
    const char * error;
Packit 78deda
Packit 78deda
    tmpdir = tmpDir();
Packit 78deda
Packit 78deda
    if (tmpdir[strlen(tmpdir) - 1] == '/')
Packit 78deda
        dirseparator = "";
Packit 78deda
    else
Packit 78deda
        dirseparator = "/";
Packit 78deda
    
Packit 78deda
    pm_asprintf(&filenameTemplate, "%s%s%s%s", 
Packit 78deda
                tmpdir, dirseparator, pm_progname, "_XXXXXX");
Packit 78deda
Packit 78deda
    if (filenameTemplate == pm_strsol)
Packit 78deda
        pm_asprintf(&error,
Packit 78deda
                    "Unable to allocate storage for temporary file name");
Packit 78deda
    else {
Packit 78deda
        makeTmpfileWithTemplate(filenameTemplate, fdP, filenameP, &error);
Packit 78deda
Packit 78deda
        pm_strfree(filenameTemplate);
Packit 78deda
    }
Packit 78deda
    if (error) {
Packit 78deda
        pm_errormsg("%s", error);
Packit 78deda
        pm_strfree(error);
Packit 78deda
        pm_longjmp();
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
void
Packit 78deda
pm_make_tmpfile(FILE **       const filePP,
Packit 78deda
                const char ** const filenameP) {
Packit 78deda
Packit 78deda
    int fd;
Packit 78deda
Packit 78deda
    pm_make_tmpfile_fd(&fd, filenameP);
Packit 78deda
Packit 78deda
    *filePP = fdopen(fd, "w+b");
Packit 78deda
    
Packit 78deda
    if (*filePP == NULL) {
Packit 78deda
        close(fd);
Packit 78deda
        unlink(*filenameP);
Packit 78deda
        pm_strfree(*filenameP);
Packit 78deda
Packit 78deda
        pm_error("Unable to create temporary file.  "
Packit 78deda
                 "fdopen() failed with errno %d (%s)",
Packit 78deda
                 errno, strerror(errno));
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
bool const canUnlinkOpen = 
Packit 78deda
#if CAN_UNLINK_OPEN
Packit 78deda
    1
Packit 78deda
#else
Packit 78deda
    0
Packit 78deda
#endif
Packit 78deda
    ;
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
typedef struct UnlinkListEntry {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   This is an entry in the linked list of files to close and unlink as the
Packit 78deda
   program exits.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    struct UnlinkListEntry * next;
Packit 78deda
    int                      fd;
Packit 78deda
    char                     fileName[1];  /* Actually variable length */
Packit 78deda
} UnlinkListEntry;
Packit 78deda
Packit 78deda
static UnlinkListEntry * unlinkListP;
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
unlinkTempFiles(void) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
  Close and unlink (so presumably delete) the files in the list
Packit 78deda
  *unlinkListP.
Packit 78deda
Packit 78deda
  This is an atexit function.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    while (unlinkListP) {
Packit 78deda
        UnlinkListEntry * const firstEntryP = unlinkListP;
Packit 78deda
Packit 78deda
        unlinkListP = unlinkListP->next;
Packit 78deda
Packit 78deda
        close(firstEntryP->fd);
Packit 78deda
        unlink(firstEntryP->fileName);
Packit 78deda
Packit 78deda
        free(firstEntryP);
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static UnlinkListEntry *
Packit 78deda
newUnlinkListEntry(const char * const fileName,
Packit 78deda
                   int          const fd) {
Packit 78deda
Packit 78deda
    UnlinkListEntry * const unlinkListEntryP =
Packit 78deda
        malloc(sizeof(*unlinkListEntryP) + strlen(fileName) + 1);
Packit 78deda
Packit 78deda
    if (unlinkListEntryP) {
Packit 78deda
        strcpy(unlinkListEntryP->fileName, fileName);
Packit 78deda
        unlinkListEntryP->fd   = fd;
Packit 78deda
        unlinkListEntryP->next = NULL;
Packit 78deda
    }
Packit 78deda
    return unlinkListEntryP;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
addUnlinkListEntry(const char * const fileName,
Packit 78deda
                   int          const fd) {
Packit 78deda
Packit 78deda
    UnlinkListEntry * const unlinkListEntryP =
Packit 78deda
        newUnlinkListEntry(fileName, fd);
Packit 78deda
Packit 78deda
    if (unlinkListEntryP) {
Packit 78deda
        unlinkListEntryP->next = unlinkListP;
Packit 78deda
        unlinkListP = unlinkListEntryP;
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
scheduleUnlinkAtExit(const char * const fileName,
Packit 78deda
                     int          const fd) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Set things up to have the file unlinked as the program exits.
Packit 78deda
Packit 78deda
   This is messy and probably doesn't work in all situations; it is a hack
Packit 78deda
   to get Unix code essentially working on Windows, without messing up the
Packit 78deda
   code too badly for Unix.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    static bool unlinkListEstablished = false;
Packit 78deda
    
Packit 78deda
    if (!unlinkListEstablished) {
Packit 78deda
        atexit(unlinkTempFiles);
Packit 78deda
        unlinkListP = NULL;
Packit 78deda
        unlinkListEstablished = true;
Packit 78deda
    }
Packit 78deda
Packit 78deda
    addUnlinkListEntry(fileName, fd);
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
arrangeUnlink(const char * const fileName,
Packit 78deda
              int          const fd) {
Packit 78deda
Packit 78deda
    if (canUnlinkOpen)
Packit 78deda
        unlink(fileName);
Packit 78deda
    else
Packit 78deda
        scheduleUnlinkAtExit(fileName, fd);
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
FILE * 
Packit 78deda
pm_tmpfile(void) {
Packit 78deda
Packit 78deda
    FILE * fileP;
Packit 78deda
    const char * tmpfileNm;
Packit 78deda
Packit 78deda
    pm_make_tmpfile(&fileP, &tmpfileNm);
Packit 78deda
Packit 78deda
    arrangeUnlink(tmpfileNm, fileno(fileP));
Packit 78deda
Packit 78deda
    pm_strfree(tmpfileNm);
Packit 78deda
Packit 78deda
    return fileP;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
int
Packit 78deda
pm_tmpfile_fd(void) {
Packit 78deda
Packit 78deda
    int fd;
Packit 78deda
    const char * tmpfileNm;
Packit 78deda
Packit 78deda
    pm_make_tmpfile_fd(&fd, &tmpfileNm);
Packit 78deda
Packit 78deda
    arrangeUnlink(tmpfileNm, fd);
Packit 78deda
Packit 78deda
    pm_strfree(tmpfileNm);
Packit 78deda
Packit 78deda
    return fd;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
FILE *
Packit 78deda
pm_openr_seekable(const char name[]) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
  Open the file named by name[] such that it is seekable (i.e. it can be
Packit 78deda
  rewound and read in multiple passes with fseek()).
Packit 78deda
Packit 78deda
  If the file is actually seekable, this reduces to the same as
Packit 78deda
  pm_openr().  If not, we copy the named file to a temporary file
Packit 78deda
  and return that file's stream descriptor.
Packit 78deda
Packit 78deda
  We use a file that the operating system recognizes as temporary, so
Packit 78deda
  it picks the filename and deletes the file when Caller closes it.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    int stat_rc;
Packit 78deda
    int seekable;  /* logical: file is seekable */
Packit 78deda
    struct stat statbuf;
Packit 78deda
    FILE * original_file;
Packit 78deda
    FILE * seekable_file;
Packit 78deda
Packit 78deda
    original_file = pm_openr((char *) name);
Packit 78deda
Packit 78deda
    /* I would use fseek() to determine if the file is seekable and 
Packit 78deda
       be a little more general than checking the type of file, but I
Packit 78deda
       don't have reliable information on how to do that.  I have seen
Packit 78deda
       streams be partially seekable -- you can, for example seek to
Packit 78deda
       0 if the file is positioned at 0 but you can't actually back up
Packit 78deda
       to 0.  I have seen documentation that says the errno for an
Packit 78deda
       unseekable stream is EBADF and in practice seen ESPIPE.
Packit 78deda
Packit 78deda
       On the other hand, regular files are always seekable and even if
Packit 78deda
       some other file is, it doesn't hurt much to assume it isn't.
Packit 78deda
    */
Packit 78deda
Packit 78deda
    stat_rc = fstat(fileno(original_file), &statbuf);
Packit 78deda
    if (stat_rc == 0 && S_ISREG(statbuf.st_mode))
Packit 78deda
        seekable = TRUE;
Packit 78deda
    else 
Packit 78deda
        seekable = FALSE;
Packit 78deda
Packit 78deda
    if (seekable) {
Packit 78deda
        seekable_file = original_file;
Packit 78deda
    } else {
Packit 78deda
        seekable_file = pm_tmpfile();
Packit 78deda
        
Packit 78deda
        /* Copy the input into the temporary seekable file */
Packit 78deda
        while (!feof(original_file) && !ferror(original_file) 
Packit 78deda
               && !ferror(seekable_file)) {
Packit 78deda
            char buffer[4096];
Packit 78deda
            int bytes_read;
Packit 78deda
            bytes_read = fread(buffer, 1, sizeof(buffer), original_file);
Packit 78deda
            fwrite(buffer, 1, bytes_read, seekable_file);
Packit 78deda
        }
Packit 78deda
        if (ferror(original_file))
Packit 78deda
            pm_error("Error reading input file into temporary file.  "
Packit 78deda
                     "Errno = %s (%d)", strerror(errno), errno);
Packit 78deda
        if (ferror(seekable_file))
Packit 78deda
            pm_error("Error writing input into temporary file.  "
Packit 78deda
                     "Errno = %s (%d)", strerror(errno), errno);
Packit 78deda
        pm_close(original_file);
Packit 78deda
        {
Packit 78deda
            int seek_rc;
Packit 78deda
            seek_rc = fseek(seekable_file, 0, SEEK_SET);
Packit 78deda
            if (seek_rc != 0)
Packit 78deda
                pm_error("fseek() failed to rewind temporary file.  "
Packit 78deda
                         "Errno = %s (%d)", strerror(errno), errno);
Packit 78deda
        }
Packit 78deda
    }
Packit 78deda
    return seekable_file;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
void
Packit 78deda
pm_close(FILE * const f) {
Packit 78deda
    fflush(f);
Packit 78deda
    if (ferror(f))
Packit 78deda
        pm_message("A file read or write error occurred at some point");
Packit 78deda
    if (f != stdin)
Packit 78deda
        if (fclose(f) != 0)
Packit 78deda
            pm_error("close of file failed with errno %d (%s)",
Packit 78deda
                     errno, strerror(errno));
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
/* The pnmtopng package uses pm_closer() and pm_closew() instead of 
Packit 78deda
   pm_close(), apparently because the 1999 Pbmplus package has them.
Packit 78deda
   I don't know what the difference is supposed to be.
Packit 78deda
*/
Packit 78deda
Packit 78deda
void
Packit 78deda
pm_closer(FILE * const f) {
Packit 78deda
    pm_close(f);
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
void
Packit 78deda
pm_closew(FILE * const f) {
Packit 78deda
    pm_close(f);
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
/* Endian I/O.
Packit 78deda
Packit 78deda
   Before Netpbm 10.27 (March 2005), these would return failure on EOF
Packit 78deda
   or I/O failure.  For backward compatibility, they still have the return
Packit 78deda
   code, but it is always zero and the routines abort the program in case
Packit 78deda
   of EOF or I/O failure.  A program that wants to handle failure differently
Packit 78deda
   must use lower level (C library) interfaces.  But that level of detail
Packit 78deda
   is uncharacteristic of a Netpbm program; the ease of programming that
Packit 78deda
   comes with not checking a return code is more Netpbm.
Packit 78deda
Packit 78deda
   It is also for historical reasons that these return signed values,
Packit 78deda
   when clearly unsigned would make more sense.
Packit 78deda
*/
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
abortWithReadError(FILE * const ifP) {
Packit 78deda
Packit 78deda
    if (feof(ifP))
Packit 78deda
        pm_error("Unexpected end of input file");
Packit 78deda
    else
Packit 78deda
        pm_error("Error (not EOF) reading file.");
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static unsigned char
Packit 78deda
getcNofail(FILE * const ifP) {
Packit 78deda
Packit 78deda
    int c;
Packit 78deda
Packit 78deda
    c = getc(ifP);
Packit 78deda
Packit 78deda
    if (c == EOF)
Packit 78deda
        abortWithReadError(ifP);
Packit 78deda
Packit 78deda
    return (unsigned char)c;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
void
Packit 78deda
pm_readchar(FILE * const ifP,
Packit 78deda
            char * const cP) {
Packit 78deda
    
Packit 78deda
    *cP = (char)getcNofail(ifP);
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
void
Packit 78deda
pm_writechar(FILE * const ofP,
Packit 78deda
             char   const c) {
Packit 78deda
Packit 78deda
    putc(c, ofP);
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
int
Packit 78deda
pm_readbigshort(FILE *  const ifP, 
Packit 78deda
                short * const sP) {
Packit 78deda
Packit 78deda
    unsigned short s;
Packit 78deda
Packit 78deda
    s  = getcNofail(ifP) << 8;
Packit 78deda
    s |= getcNofail(ifP) << 0;
Packit 78deda
Packit 78deda
    *sP = s;
Packit 78deda
Packit 78deda
    return 0;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
int
Packit 78deda
pm_writebigshort(FILE * const ofP, 
Packit 78deda
                 short  const s) {
Packit 78deda
Packit 78deda
    putc((s >> 8) & 0xff, ofP);
Packit 78deda
    putc(s & 0xff, ofP);
Packit 78deda
Packit 78deda
    return 0;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
int
Packit 78deda
pm_readbiglong(FILE * const ifP, 
Packit 78deda
               long * const lP) {
Packit 78deda
Packit 78deda
    unsigned long l;
Packit 78deda
Packit 78deda
    l  = getcNofail(ifP) << 24;
Packit 78deda
    l |= getcNofail(ifP) << 16;
Packit 78deda
    l |= getcNofail(ifP) <<  8;
Packit 78deda
    l |= getcNofail(ifP) <<  0;
Packit 78deda
Packit 78deda
    *lP = l;
Packit 78deda
Packit 78deda
    return 0;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
int
Packit 78deda
pm_readbiglong2(FILE *    const ifP,
Packit 78deda
                int32_t * const lP) {
Packit 78deda
    int rc;
Packit 78deda
    long l;
Packit 78deda
Packit 78deda
    rc = pm_readbiglong(ifP, &l);
Packit 78deda
Packit 78deda
    assert((int32_t)l == l);
Packit 78deda
Packit 78deda
    *lP = (int32_t)l;
Packit 78deda
Packit 78deda
    return rc;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
int
Packit 78deda
pm_writebiglong(FILE * const ofP, 
Packit 78deda
                long   const l) {
Packit 78deda
Packit 78deda
    putc((l >> 24) & 0xff, ofP);
Packit 78deda
    putc((l >> 16) & 0xff, ofP);
Packit 78deda
    putc((l >>  8) & 0xff, ofP);
Packit 78deda
    putc((l >>  0) & 0xff, ofP);
Packit 78deda
Packit 78deda
    return 0;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
int
Packit 78deda
pm_readlittleshort(FILE *  const ifP, 
Packit 78deda
                   short * const sP) {
Packit 78deda
    unsigned short s;
Packit 78deda
Packit 78deda
    s  = getcNofail(ifP) << 0;
Packit 78deda
    s |= getcNofail(ifP) << 8;
Packit 78deda
Packit 78deda
    *sP = s;
Packit 78deda
Packit 78deda
    return 0;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
int
Packit 78deda
pm_writelittleshort(FILE * const ofP, 
Packit 78deda
                    short  const s) {
Packit 78deda
Packit 78deda
    putc((s >> 0) & 0xff, ofP);
Packit 78deda
    putc((s >> 8) & 0xff, ofP);
Packit 78deda
Packit 78deda
    return 0;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
int
Packit 78deda
pm_readlittlelong(FILE * const ifP, 
Packit 78deda
                  long * const lP) {
Packit 78deda
    unsigned long l;
Packit 78deda
Packit 78deda
    l  = getcNofail(ifP) <<  0;
Packit 78deda
    l |= getcNofail(ifP) <<  8;
Packit 78deda
    l |= getcNofail(ifP) << 16;
Packit 78deda
    l |= getcNofail(ifP) << 24;
Packit 78deda
Packit 78deda
    *lP = l;
Packit 78deda
Packit 78deda
    return 0;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
int
Packit 78deda
pm_readlittlelong2(FILE *    const ifP,
Packit 78deda
                   int32_t * const lP) {
Packit 78deda
    int rc;
Packit 78deda
    long l;
Packit 78deda
Packit 78deda
    rc = pm_readlittlelong(ifP, &l);
Packit 78deda
Packit 78deda
    assert((int32_t)l == l);
Packit 78deda
Packit 78deda
    *lP = (int32_t)l;
Packit 78deda
Packit 78deda
    return rc;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
int
Packit 78deda
pm_writelittlelong(FILE * const ofP, 
Packit 78deda
                   long   const l) {
Packit 78deda
Packit 78deda
    putc((l >>  0) & 0xff, ofP);
Packit 78deda
    putc((l >>  8) & 0xff, ofP);
Packit 78deda
    putc((l >> 16) & 0xff, ofP);
Packit 78deda
    putc((l >> 24) & 0xff, ofP);
Packit 78deda
Packit 78deda
    return 0;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
int 
Packit 78deda
pm_readmagicnumber(FILE * const ifP) {
Packit 78deda
Packit 78deda
    int ich1, ich2;
Packit 78deda
Packit 78deda
    ich1 = getc(ifP);
Packit 78deda
Packit 78deda
    if (ich1 == EOF)
Packit 78deda
        pm_error("Error reading first byte of what is expected to be "
Packit 78deda
                 "a Netpbm magic number.  "
Packit 78deda
                 "Most often, this means your input file is empty");
Packit 78deda
Packit 78deda
    ich2 = getc(ifP);
Packit 78deda
Packit 78deda
    if (ich2 == EOF)
Packit 78deda
        pm_error("Error reading second byte of what is expected to be "
Packit 78deda
                 "a Netpbm magic number (the first byte was successfully "
Packit 78deda
                 "read as 0x%02x)", ich1);
Packit 78deda
Packit 78deda
    return ich1 * 256 + ich2;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
/* Read a file of unknown size to a buffer. Return the number of bytes
Packit 78deda
   read. Allocate more memory as we need it. The calling routine has
Packit 78deda
   to free() the buffer.
Packit 78deda
Packit 78deda
   Oliver Trepte, oliver@fysik4.kth.se, 930613
Packit 78deda
*/
Packit 78deda
Packit 78deda
#define PM_BUF_SIZE 16384      /* First try this size of the buffer, then
Packit 78deda
                                  double this until we reach PM_MAX_BUF_INC */
Packit 78deda
#define PM_MAX_BUF_INC 65536   /* Don't allocate more memory in larger blocks
Packit 78deda
                                  than this. */
Packit 78deda
Packit 78deda
char *
Packit 78deda
pm_read_unknown_size(FILE * const file, 
Packit 78deda
                     long * const nread) {
Packit 78deda
    long nalloc;
Packit 78deda
    char * buf;
Packit 78deda
    bool eof;
Packit 78deda
Packit 78deda
    *nread = 0;
Packit 78deda
    nalloc = PM_BUF_SIZE;
Packit 78deda
    MALLOCARRAY(buf, nalloc);
Packit 78deda
Packit 78deda
    eof = FALSE;  /* initial value */
Packit 78deda
Packit 78deda
    while(!eof) {
Packit 78deda
        int val;
Packit 78deda
Packit 78deda
        if (*nread >= nalloc) { /* We need a larger buffer */
Packit 78deda
            if (nalloc > PM_MAX_BUF_INC)
Packit 78deda
                nalloc += PM_MAX_BUF_INC;
Packit 78deda
            else
Packit 78deda
                nalloc += nalloc;
Packit 78deda
            REALLOCARRAY_NOFAIL(buf, nalloc);
Packit 78deda
        }
Packit 78deda
Packit 78deda
        val = getc(file);
Packit 78deda
        if (val == EOF)
Packit 78deda
            eof = TRUE;
Packit 78deda
        else 
Packit 78deda
            buf[(*nread)++] = val;
Packit 78deda
    }
Packit 78deda
    return buf;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
union cheat {
Packit 78deda
    uint32_t l;
Packit 78deda
    short s;
Packit 78deda
    unsigned char c[4];
Packit 78deda
};
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
short
Packit 78deda
pm_bs_short(short const s) {
Packit 78deda
    union cheat u;
Packit 78deda
    unsigned char t;
Packit 78deda
Packit 78deda
    u.s = s;
Packit 78deda
    t = u.c[0];
Packit 78deda
    u.c[0] = u.c[1];
Packit 78deda
    u.c[1] = t;
Packit 78deda
    return u.s;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
long
Packit 78deda
pm_bs_long(long const l) {
Packit 78deda
    union cheat u;
Packit 78deda
    unsigned char t;
Packit 78deda
Packit 78deda
    u.l = l;
Packit 78deda
    t = u.c[0];
Packit 78deda
    u.c[0] = u.c[3];
Packit 78deda
    u.c[3] = t;
Packit 78deda
    t = u.c[1];
Packit 78deda
    u.c[1] = u.c[2];
Packit 78deda
    u.c[2] = t;
Packit 78deda
    return u.l;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
void
Packit 78deda
pm_tell2(FILE *       const fileP, 
Packit 78deda
         void *       const fileposP,
Packit 78deda
         unsigned int const fileposSize) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Return the current file position as *filePosP, which is a buffer
Packit 78deda
   'fileposSize' bytes long.  Abort the program if error, including if
Packit 78deda
   *fileP isn't a file that has a position.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    /* Note: FTELLO() is either ftello() or ftell(), depending on the
Packit 78deda
       capabilities of the underlying C library.  It is defined in
Packit 78deda
       pm_config.h.  ftello(), in turn, may be either ftell() or
Packit 78deda
       ftello64(), as implemented by the C library.
Packit 78deda
    */
Packit 78deda
    pm_filepos const filepos = FTELLO(fileP);
Packit 78deda
    if (filepos < 0)
Packit 78deda
        pm_error("ftello() to get current file position failed.  "
Packit 78deda
                 "Errno = %s (%d)\n", strerror(errno), errno);
Packit 78deda
Packit 78deda
    if (fileposSize == sizeof(pm_filepos)) {
Packit 78deda
        pm_filepos * const fileposP_filepos = fileposP;
Packit 78deda
        *fileposP_filepos = filepos;
Packit 78deda
    } else if (fileposSize == sizeof(long)) {
Packit 78deda
        if (sizeof(pm_filepos) > sizeof(long) &&
Packit 78deda
            filepos >= (pm_filepos) 1 << (sizeof(long)*8))
Packit 78deda
            pm_error("File size is too large to represent in the %u bytes "
Packit 78deda
                     "that were provided to pm_tell2()", fileposSize);
Packit 78deda
        else {
Packit 78deda
            long * const fileposP_long = fileposP;
Packit 78deda
            *fileposP_long = (long)filepos;
Packit 78deda
        }
Packit 78deda
    } else
Packit 78deda
        pm_error("File position size passed to pm_tell() is invalid: %u.  "
Packit 78deda
                 "Valid sizes are %u and %u", 
Packit 78deda
                 fileposSize, (unsigned int)sizeof(pm_filepos),
Packit 78deda
                 (unsigned int) sizeof(long));
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
unsigned int
Packit 78deda
pm_tell(FILE * const fileP) {
Packit 78deda
    
Packit 78deda
    long filepos;
Packit 78deda
Packit 78deda
    pm_tell2(fileP, &filepos, sizeof(filepos));
Packit 78deda
Packit 78deda
    return filepos;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
void
Packit 78deda
pm_seek2(FILE *             const fileP, 
Packit 78deda
         const pm_filepos * const fileposP,
Packit 78deda
         unsigned int       const fileposSize) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Position file *fileP to position *fileposP.  Abort if error, including
Packit 78deda
   if *fileP isn't a seekable file.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    if (fileposSize == sizeof(pm_filepos)) 
Packit 78deda
        /* Note: FSEEKO() is either fseeko() or fseek(), depending on the
Packit 78deda
           capabilities of the underlying C library.  It is defined in
Packit 78deda
           pm_config.h.  fseeko(), in turn, may be either fseek() or
Packit 78deda
           fseeko64(), as implemented by the C library.
Packit 78deda
        */
Packit 78deda
        FSEEKO(fileP, *fileposP, SEEK_SET);
Packit 78deda
    else if (fileposSize == sizeof(long)) {
Packit 78deda
        long const fileposLong = *(long *)fileposP;
Packit 78deda
        fseek(fileP, fileposLong, SEEK_SET);
Packit 78deda
    } else
Packit 78deda
        pm_error("File position size passed to pm_seek() is invalid: %u.  "
Packit 78deda
                 "Valid sizes are %u and %u", 
Packit 78deda
                 fileposSize, (unsigned int)sizeof(pm_filepos),
Packit 78deda
                 (unsigned int) sizeof(long));
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
void
Packit 78deda
pm_seek(FILE * const fileP, unsigned long filepos) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
Packit 78deda
    pm_filepos fileposBuff;
Packit 78deda
Packit 78deda
    fileposBuff = filepos;
Packit 78deda
Packit 78deda
    pm_seek2(fileP, &fileposBuff, sizeof(fileposBuff));
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
void
Packit 78deda
pm_nextimage(FILE * const file, int * const eofP) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Position the file 'file' to the next image in the stream, assuming it is
Packit 78deda
   now positioned just after the current image.  I.e. read off any white
Packit 78deda
   space at the end of the current image's raster.  Note that the raw formats
Packit 78deda
   don't permit such white space, but this routine tolerates it anyway, 
Packit 78deda
   because the plain formats do permit white space after the raster.
Packit 78deda
Packit 78deda
   Iff there is no next image, return *eofP == TRUE.
Packit 78deda
Packit 78deda
   Note that in practice, we will not normally see white space here in
Packit 78deda
   a plain PPM or plain PGM stream because the routine to read a
Packit 78deda
   sample from the image reads one character of white space after the
Packit 78deda
   sample in order to know where the sample ends.  There is not
Packit 78deda
   normally more than one character of white space (a newline) after
Packit 78deda
   the last sample in the raster.  But plain PBM is another story.  No white
Packit 78deda
   space is required between samples of a plain PBM image.  But the raster
Packit 78deda
   normally ends with a newline nonetheless.  Since the sample reading code
Packit 78deda
   will not have read that newline, it is there for us to read now.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    bool eof;
Packit 78deda
    bool nonWhitespaceFound;
Packit 78deda
Packit 78deda
    eof = FALSE;
Packit 78deda
    nonWhitespaceFound = FALSE;
Packit 78deda
Packit 78deda
    while (!eof && !nonWhitespaceFound) {
Packit 78deda
        int c;
Packit 78deda
        c = getc(file);
Packit 78deda
        if (c == EOF) {
Packit 78deda
            if (feof(file)) 
Packit 78deda
                eof = TRUE;
Packit 78deda
            else
Packit 78deda
                pm_error("File error on getc() to position to image");
Packit 78deda
        } else {
Packit 78deda
            if (!isspace(c)) {
Packit 78deda
                int rc;
Packit 78deda
Packit 78deda
                nonWhitespaceFound = TRUE;
Packit 78deda
Packit 78deda
                /* Have to put the non-whitespace character back in
Packit 78deda
                   the stream -- it's part of the next image.  
Packit 78deda
                */
Packit 78deda
                rc = ungetc(c, file);
Packit 78deda
                if (rc == EOF) 
Packit 78deda
                    pm_error("File error doing ungetc() "
Packit 78deda
                             "to position to image.");
Packit 78deda
            }
Packit 78deda
        }
Packit 78deda
    }
Packit 78deda
    *eofP = eof;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
void
Packit 78deda
pm_check(FILE *               const file, 
Packit 78deda
         enum pm_check_type   const check_type, 
Packit 78deda
         pm_filepos           const need_raster_size,
Packit 78deda
         enum pm_check_code * const retval_p) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   This is not defined for use outside of libnetpbm.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    struct stat statbuf;
Packit 78deda
    pm_filepos curpos;  /* Current position of file; -1 if none */
Packit 78deda
    int rc;
Packit 78deda
Packit 78deda
    /* Note: FTELLO() is either ftello() or ftell(), depending on the
Packit 78deda
       capabilities of the underlying C library.  It is defined in
Packit 78deda
       pm_config.h.  ftello(), in turn, may be either ftell() or
Packit 78deda
       ftello64(), as implemented by the C library.
Packit 78deda
    */
Packit 78deda
    curpos = FTELLO(file);
Packit 78deda
    if (curpos >= 0) {
Packit 78deda
        /* This type of file has a current position */
Packit 78deda
            
Packit 78deda
        rc = fstat(fileno(file), &statbuf);
Packit 78deda
        if (rc != 0) 
Packit 78deda
            pm_error("fstat() failed to get size of file, though ftello() "
Packit 78deda
                     "successfully identified\n"
Packit 78deda
                     "the current position.  Errno=%s (%d)",
Packit 78deda
                     strerror(errno), errno);
Packit 78deda
        else if (!S_ISREG(statbuf.st_mode)) {
Packit 78deda
            /* Not a regular file; we can't know its size */
Packit 78deda
            if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
Packit 78deda
        } else {
Packit 78deda
            pm_filepos const have_raster_size = statbuf.st_size - curpos;
Packit 78deda
            
Packit 78deda
            if (have_raster_size < need_raster_size)
Packit 78deda
                pm_error("File has invalid format.  The raster should "
Packit 78deda
                         "contain %u bytes, but\n"
Packit 78deda
                         "the file ends after only %u bytes.",
Packit 78deda
                         (unsigned int) need_raster_size, 
Packit 78deda
                         (unsigned int) have_raster_size);
Packit 78deda
            else if (have_raster_size > need_raster_size) {
Packit 78deda
                if (retval_p) *retval_p = PM_CHECK_TOO_LONG;
Packit 78deda
            } else {
Packit 78deda
                if (retval_p) *retval_p = PM_CHECK_OK;
Packit 78deda
            }
Packit 78deda
        }
Packit 78deda
    } else
Packit 78deda
        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
void
Packit 78deda
pm_drain(FILE *         const fileP,
Packit 78deda
         unsigned int   const limit,
Packit 78deda
         unsigned int * const bytesReadP) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
  Read bytes from *fileP until EOF and return as *bytesReadP how many there
Packit 78deda
  were.
Packit 78deda
Packit 78deda
  But don't read any more than 'limit'.
Packit 78deda
Packit 78deda
  This is a good thing to call after reading an input file to be sure you
Packit 78deda
  didn't leave some input behind, which could mean you didn't properly
Packit 78deda
  interpret the file.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    unsigned int bytesRead;
Packit 78deda
    bool eof;
Packit 78deda
Packit 78deda
    for (bytesRead = 0, eof = false; !eof && bytesRead < limit;) {
Packit 78deda
Packit 78deda
        int rc;
Packit 78deda
Packit 78deda
        rc = fgetc(fileP);
Packit 78deda
Packit 78deda
        eof = (rc == EOF);
Packit 78deda
        if (!eof)
Packit 78deda
            ++bytesRead;
Packit 78deda
    }
Packit 78deda
    *bytesReadP = bytesRead;
Packit 78deda
}