about summary refs log tree commit diff
path: root/editor/specialty/pnmindex.c
diff options
context:
space:
mode:
Diffstat (limited to 'editor/specialty/pnmindex.c')
-rw-r--r--editor/specialty/pnmindex.c494
1 files changed, 339 insertions, 155 deletions
diff --git a/editor/specialty/pnmindex.c b/editor/specialty/pnmindex.c
index 0a8e35cc..0214ee8b 100644
--- a/editor/specialty/pnmindex.c
+++ b/editor/specialty/pnmindex.c
@@ -1,8 +1,8 @@
 /*============================================================================
-                              pnmindex   
+                                pnmindex
 ==============================================================================
 
-  build a visual index of a bunch of PNM images
+  Build a visual index of a bunch of PNM images.
 
   This used to be a C shell program, and then a BASH program.  Neither
   were portable enough, and the program is too complex for either of
@@ -15,12 +15,14 @@
 ============================================================================*/
 
 #define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
+#define _C99_SOURCE  /* Make sure snprintf() is in stdio.h */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 #define _BSD_SOURCE   /* Make sure strdup is in string.h */
 
 #include <assert.h>
 #include <unistd.h>
 #include <stdarg.h>
+#include <stdio.h>
 #include <errno.h>
 #include <sys/stat.h>
 
@@ -30,9 +32,10 @@
 #include "shhopt.h"
 #include "mallocvar.h"
 #include "nstring.h"
+#include "pm_system.h"
 #include "pnm.h"
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -51,60 +54,74 @@ static bool verbose;
 
 
 
-static void PM_GNU_PRINTF_ATTR(1,2)
-systemf(const char * const fmt,
-        ...) {
+static const char *
+shellQuote(const char * const arg) {
+/*----------------------------------------------------------------------------
+   A string that in a Bourne shell command forms a single token whose value
+   understood by the shell is 'arg'.
 
-    va_list varargs;
-    
-    size_t dryRunLen;
-    
-    va_start(varargs, fmt);
-    
-    pm_vsnprintf(NULL, 0, fmt, varargs, &dryRunLen);
+   For example,
 
-    va_end(varargs);
+     'arg'           result
+     --------------  --------------
 
-    if (dryRunLen + 1 < dryRunLen)
-        /* arithmetic overflow */
-        pm_error("Command way too long");
-    else {
-        size_t const allocSize = dryRunLen + 1;
-        char * shellCommand;
-        shellCommand = malloc(allocSize);
-        if (shellCommand == NULL)
-            pm_error("Can't get storage for %u-character command",
-                     (unsigned)allocSize);
-        else {
-            va_list varargs;
-            size_t realLen;
-            int rc;
-
-            va_start(varargs, fmt);
-
-            pm_vsnprintf(shellCommand, allocSize, fmt, varargs, &realLen);
-                
-            assert(realLen == dryRunLen);
-            va_end(varargs);
-
-            if (verbose)
-                pm_message("shell cmd: %s", shellCommand);
-
-            rc = system(shellCommand);
-            if (rc != 0)
-                pm_error("shell command '%s' failed.  rc %d",
-                         shellCommand, rc);
-            
-            pm_strfree(shellCommand);
+     hello           'hello'
+     two words       'two words'
+     'Bryan's'       \''Bryan'\''s'\'
+
+   Note that in the last example the result is a concatenation of two
+   single-quoted strings and 3 esaped single quotes outside of those quoted
+   strings.
+
+   You can use this safely to insert an arbitrary string from an untrusted
+   source into a shell command without worrying about it changing the nature
+   of the shell command.
+
+   In newly malloced storage.
+-----------------------------------------------------------------------------*/
+    unsigned int const worstCaseResultLen = 3 * strlen(arg) + 1;
+
+    char * buffer;
+    unsigned int i;
+    unsigned int cursor;
+    bool inquotes;
+
+    MALLOCARRAY_NOFAIL(buffer, worstCaseResultLen);
+
+    for (i = 0, cursor=0, inquotes=false; i < strlen(arg); ++i) {
+        if (arg[i] == '\'') {
+            if (inquotes) {
+                buffer[cursor++] = '\'';  /* Close the quotation */
+                inquotes = false;
+            }
+            assert(!inquotes);
+            buffer[cursor++] = '\\';  /* Add escaped */
+            buffer[cursor++] = '\'';  /*    single quote */
+        } else {
+            if (!inquotes) {
+                buffer[cursor++] = '\'';
+                inquotes = true;
+            }
+            assert(inquotes);
+            buffer[cursor++] = arg[i];
         }
     }
+    if (inquotes)
+        buffer[cursor++] = '\'';   /* Close the final quotation */
+
+    buffer[cursor++] = '\0';  /* Terminate string */
+
+    assert(cursor <= worstCaseResultLen);
+
+    return buffer;
 }
-        
+
+
 
 
 static void
-parseCommandLine(int argc, char ** argv, 
-                 struct cmdlineInfo * const cmdlineP) {
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
 
     unsigned int option_def_index;
     optEntry *option_def;
@@ -118,13 +135,13 @@ parseCommandLine(int argc, char ** argv,
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0, "black",       OPT_FLAG,   NULL,                  
+    OPTENT3(0, "black",       OPT_FLAG,   NULL,
             &cmdlineP->black,         0);
-    OPTENT3(0, "noquant",     OPT_FLAG,   NULL,                  
+    OPTENT3(0, "noquant",     OPT_FLAG,   NULL,
             &cmdlineP->noquant,       0);
-    OPTENT3(0, "quant",       OPT_FLAG,   NULL,                  
+    OPTENT3(0, "quant",       OPT_FLAG,   NULL,
             &quant,                   0);
-    OPTENT3(0, "verbose",     OPT_FLAG,   NULL,                  
+    OPTENT3(0, "verbose",     OPT_FLAG,   NULL,
             &cmdlineP->verbose,       0);
     OPTENT3(0, "size",        OPT_UINT,   &cmdlineP->size,
             &sizeSpec,                0);
@@ -137,21 +154,30 @@ parseCommandLine(int argc, char ** argv,
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
-    opt.allowNegNum = FALSE; 
+    opt.allowNegNum = FALSE;
 
-    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdline_p and others. */
 
     if (quant && cmdlineP->noquant)
-        pm_error("You can't specify both -quant and -noquat");
+        pm_error("You can't specify both -quant and -noquant");
 
-    if (!colorsSpec)
+    if (colorsSpec) {
+        if (cmdlineP->colors == 0)
+            pm_error("-colors value must be positive");
+    } else
         cmdlineP->colors = 256;
-    
-    if (!sizeSpec)
+
+    if (sizeSpec) {
+        if (cmdlineP->size == 0)
+            pm_error("-size value must be positive");
+    } else
         cmdlineP->size = 100;
 
-    if (!acrossSpec)
+    if (acrossSpec) {
+        if (cmdlineP->across == 0)
+            pm_error("-across value must be positive");
+    } else
         cmdlineP->across = 6;
 
     if (!titleSpec)
@@ -181,7 +207,7 @@ parseCommandLine(int argc, char ** argv,
 
 
 static void
-freeCmdline(struct cmdlineInfo const cmdline) {
+freeCmdline(struct CmdlineInfo const cmdline) {
 
     unsigned int i;
 
@@ -195,10 +221,38 @@ freeCmdline(struct cmdlineInfo const cmdline) {
 
 
 static void
+validateTmpdir(const char * const dirNm) {
+/*----------------------------------------------------------------------------
+   Abort program if 'dirNm' contains special characters that would cause
+   trouble if included in a shell command.
+
+   The cleanest thing to do would be to allow any characters in the directory
+   name and just quote and escape the properly every time we include the name
+   of a temporary file in a shell command, but we're too lazy for that.
+-----------------------------------------------------------------------------*/
+    if (
+        strchr(dirNm, '\\') ||
+        strchr(dirNm, '\'') ||
+        strchr(dirNm, ' ') ||
+        strchr(dirNm, '"') ||
+        strchr(dirNm, '$') ||
+        strchr(dirNm, '`') ||
+        strchr(dirNm, '!')
+        ) {
+        pm_error("TMPDIR environment variable contains a shell special "
+                 "character; this program cannot use that.");
+    }
+}
+
+
+
+static void
 makeTempDir(const char ** const tempDirP) {
 
     const char * const tmpdir = getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp";
 
+    validateTmpdir(tmpdir);
+
     const char * mytmpdir;
     int rc;
 
@@ -233,9 +287,9 @@ rowFileName(const char * const dirName,
             unsigned int const row) {
 
     const char * fileName;
-    
+
     pm_asprintf(&fileName, "%s/pi.%u", dirName, row);
-    
+
     return fileName;
 }
 
@@ -245,76 +299,116 @@ static void
 makeTitle(const char * const title,
           unsigned int const rowNumber,
           bool         const blackBackground,
-          const char * const tempDir) {
+          const char * const dirNm) {
+/*----------------------------------------------------------------------------
+   Create a PBM file containing the text 'title'.
+
+   If 'blackBackground' is true, make it white on black; otherwise, black
+   on white.
 
+   Name the file like a thumbnail row file for row number 'rowNumber',
+   in directory named 'dirNm'.
+-----------------------------------------------------------------------------*/
     const char * const invertStage = blackBackground ? "| pnminvert " : "";
 
     const char * fileName;
+    FILE * outFP;
+    struct bufferDesc titleBuf;
+    const char * shellCommand;
+    int termStatus;
 
-    fileName = rowFileName(tempDir, rowNumber);
-
-    /* This quoting is not adequate.  We really should do this without
-       a shell at all.
-    */
-    systemf("pbmtext \"%s\" "
-            "%s"
-            "> %s", 
-            title, invertStage, fileName);
+    titleBuf.size              = strlen(title);
+    titleBuf.buffer            = (unsigned char *)title;
+    titleBuf.bytesTransferredP = NULL;
 
+    fileName = rowFileName(dirNm, rowNumber);
+    outFP = pm_openw(fileName);
     pm_strfree(fileName);
+
+    pm_asprintf(&shellCommand, "pbmtext %s", invertStage);
+
+    pm_system2(&pm_feed_from_memory, &titleBuf,
+               &pm_accept_to_filestream, outFP,
+               shellCommand, &termStatus);
+
+    pm_strfree(shellCommand);
+
+    if (termStatus != 0)
+        pm_error("Failed to generate title image");
+
+    pm_close(outFP);
 }
 
 
 
 static void
-copyImage(const char * const inputFileName,
-          const char * const outputFileName) {
+copyImage(const char * const inputFileNm,
+          const char * const outputFileNm) {
+
+    int termStatus;
 
-    systemf("cat %s > %s", inputFileName, outputFileName);
-} 
+    pm_system2_lp("cat",
+                  &pm_feed_from_file, (void*)inputFileNm,
+                  &pm_accept_to_file, (void*)outputFileNm,
+                  &termStatus,
+                  "cat", NULL);
+
+    if (termStatus != 0)
+        pm_error("'cat' to copy image '%s' to '%s' failed, "
+                 "termination status = %d",
+                 inputFileNm, outputFileNm, termStatus);
+}
 
 
 
 static void
-copyScaleQuantImage(const char * const inputFileName,
-                    const char * const outputFileName,
+copyScaleQuantImage(const char * const inputFileNm,
+                    const char * const outputFileNm,
                     int          const format,
                     unsigned int const size,
                     unsigned int const quant,
-                    unsigned int const colors) {
+                    unsigned int const colorCt) {
 
     const char * scaleCommand;
+    int termStatus;
 
     switch (PNM_FORMAT_TYPE(format)) {
     case PBM_TYPE:
-        pm_asprintf(&scaleCommand, 
-                    "pamscale -quiet -xysize %u %u %s "
-                    "| pgmtopbm > %s",
-                    size, size, inputFileName, outputFileName);
+        pm_asprintf(&scaleCommand,
+                    "pamscale -quiet -xysize %u %u "
+                    "| pamditherbw",
+                    size, size);
         break;
-        
+
     case PGM_TYPE:
-        pm_asprintf(&scaleCommand, 
-                    "pamscale -quiet -xysize %u %u %s >%s",
-                    size, size, inputFileName, outputFileName);
+        pm_asprintf(&scaleCommand,
+                    "pamscale -quiet -xysize %u %u",
+                    size, size);
         break;
-        
+
     case PPM_TYPE:
         if (quant)
-            pm_asprintf(&scaleCommand, 
-                        "pamscale -quiet -xysize %u %u %s "
-                        "| pnmquant -quiet %u > %s",
-                        size, size, inputFileName, colors, outputFileName);
+            pm_asprintf(&scaleCommand,
+                        "pamscale -quiet -xysize %u %u "
+                        "| pnmquant -quiet %u ",
+                        size, size, colorCt);
         else
-            pm_asprintf(&scaleCommand, 
-                        "pamscale -quiet -xysize %u %u %s >%s",
-                        size, size, inputFileName, outputFileName);
+            pm_asprintf(&scaleCommand,
+                        "pamscale -quiet -xysize %u %u ",
+                        size, size);
         break;
     default:
         pm_error("Unrecognized Netpbm format: %d", format);
     }
 
-    systemf("%s", scaleCommand);
+    pm_system2(pm_feed_from_file, (void*)inputFileNm,
+               pm_accept_to_file, (void*)outputFileNm,
+               scaleCommand,
+               &termStatus);
+
+    if (termStatus != 0)
+        pm_message("Shell command '%s' failed.  Termination status=%d",
+                   scaleCommand, termStatus);
 
     pm_strfree(scaleCommand);
 }
@@ -326,7 +420,7 @@ formatTypeMax(int const typeA,
               int const typeB) {
 
     if (typeA == PPM_TYPE || typeB == PPM_TYPE)
-        return PPM_TYPE; 
+        return PPM_TYPE;
     else if (typeA == PGM_TYPE || typeB == PGM_TYPE)
         return PGM_TYPE;
     else
@@ -341,9 +435,9 @@ thumbnailFileName(const char * const dirName,
                   unsigned int const col) {
 
     const char * fileName;
-    
+
     pm_asprintf(&fileName, "%s/pi.%u.%u", dirName, row, col);
-    
+
     return fileName;
 }
 
@@ -364,7 +458,7 @@ thumbnailFileList(const char * const dirName,
         pm_error("Unable to allocate %u bytes for file list", maxListSize);
 
     list[0] = '\0';
-    
+
     for (col = 0; col < cols; ++col) {
         const char * const fileName = thumbnailFileName(dirName, row, col);
 
@@ -383,21 +477,51 @@ thumbnailFileList(const char * const dirName,
 
 
 static void
-makeImageFile(const char * const thumbnailFileName,
-              const char * const inputFileName,
+makeImageFile(const char * const thumbnailFileNm,
+              const char * const inputFileNm,
               bool         const blackBackground,
-              const char * const outputFileName) {
+              const char * const outputFileNm) {
+/*----------------------------------------------------------------------------
+   Create one thumbnail image.  It consists of the image in the file named
+   'thumbnailFileName' with text of that name appended to the bottom.
 
-    const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
-    const char * const invertStage = blackBackground ? "| pnminvert " : "";
+   Write the result to the file named 'outputFileName'.
 
-    systemf("pbmtext \"%s\" "
-            "%s"
-            "| pnmcat %s -topbottom %s - "
-            "> %s",
-            inputFileName, invertStage, blackWhiteOpt, 
-            thumbnailFileName, outputFileName);
-}    
+   'blackBackground' means give the image a black background where padding
+   is necessary and make the text white on black.  If false, give the image
+   a white background instead.
+-----------------------------------------------------------------------------*/
+    const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
+    const char * const invertStage   = blackBackground ? "| pnminvert " : "";
+    const char * const thumbnailFileNmToken = shellQuote(thumbnailFileNm);
+
+    struct bufferDesc fileNmBuf;
+    const char * shellCommand;
+    int termStatus;
+
+    fileNmBuf.size              = strlen(inputFileNm);
+    fileNmBuf.buffer            = (unsigned char *)inputFileNm;
+    fileNmBuf.bytesTransferredP = NULL;
+
+    pm_asprintf(&shellCommand,
+                "pbmtext "
+                "%s"
+                "| pamcat %s -topbottom %s - ",
+                invertStage, blackWhiteOpt, thumbnailFileNmToken);
+
+    pm_system2(&pm_feed_from_memory, &fileNmBuf,
+               &pm_accept_to_file, (void*)outputFileNm,
+               shellCommand,
+               &termStatus);
+
+    if (termStatus != 0)
+        pm_error("Shell command '%s' to add file name to thumbnail image "
+                 "of file '%s' failed, termination Status = %d",
+                 shellCommand, inputFileNm, termStatus);
+
+    pm_strfree(thumbnailFileNmToken);
+    pm_strfree(shellCommand);
+}
 
 
 
@@ -406,32 +530,31 @@ makeThumbnail(const char *  const inputFileName,
               unsigned int  const size,
               bool          const black,
               bool          const quant,
-              unsigned int  const colors,
+              unsigned int  const colorCt,
               const char *  const tempDir,
               unsigned int  const row,
               unsigned int  const col,
               int *         const formatP) {
 
+    const char * const fileName = thumbnailFileName(tempDir, row, col);
+
     FILE * ifP;
     int imageCols, imageRows, format;
     xelval maxval;
     const char * tmpfile;
-    const char * fileName;
-        
+
     ifP = pm_openr(inputFileName);
     pnm_readpnminit(ifP, &imageCols, &imageRows, &maxval, &format);
     pm_close(ifP);
-    
+
     pm_asprintf(&tmpfile, "%s/pi.tmp", tempDir);
 
     if (imageCols < size && imageRows < size)
         copyImage(inputFileName, tmpfile);
     else
-        copyScaleQuantImage(inputFileName, tmpfile, format, 
-                            size, quant, colors);
+        copyScaleQuantImage(inputFileName, tmpfile, format,
+                            size, quant, colorCt);
 
-    fileName = thumbnailFileName(tempDir, row, col);
-        
     makeImageFile(tmpfile, inputFileName, black, fileName);
 
     unlink(tmpfile);
@@ -441,7 +564,7 @@ makeThumbnail(const char *  const inputFileName,
 
     *formatP = format;
 }
-        
+
 
 
 static void
@@ -450,7 +573,7 @@ unlinkThumbnailFiles(const char * const dirName,
                      unsigned int const cols) {
 
     unsigned int col;
-    
+
     for (col = 0; col < cols; ++col) {
         const char * const fileName = thumbnailFileName(dirName, row, col);
 
@@ -467,7 +590,7 @@ unlinkRowFiles(const char * const dirName,
                unsigned int const rows) {
 
     unsigned int row;
-    
+
     for (row = 0; row < rows; ++row) {
         const char * const fileName = rowFileName(dirName, row);
 
@@ -481,40 +604,66 @@ unlinkRowFiles(const char * const dirName,
 
 static void
 combineIntoRowAndDelete(unsigned int const row,
-                        unsigned int const cols,
+                        unsigned int const colCt,
                         int          const maxFormatType,
                         bool         const blackBackground,
                         bool         const quant,
-                        unsigned int const colors,
+                        unsigned int const colorCt,
                         const char * const tempDir) {
+/*----------------------------------------------------------------------------
+   Combine the 'colCt' thumbnails for row 'row' into a PNM file in directory
+   'tempDir'.
+
+   Each thumbnail is from a specially named file in 'tempDir' whose name
+   indicates its position in the row, and the output is also specially named
+   with a name indicating it is row 'row'.
+
+   Where the thumnails are different heights, pad with black if
+   'blackBackground' is true; white otherwise.
 
+   If 'quant', color-quantize the result to have no more than 'colorCt'
+   colors, choosing the colors that best represent all the pixels in the
+   result.
+
+   'maxFormatType' is the most expressive format of all the thumbnail files;
+   results are undefined if it is not.
+-----------------------------------------------------------------------------*/
     const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
+    const char * const fileNm        = rowFileName(tempDir, row);
 
-    const char * fileName;
     const char * quantStage;
     const char * fileList;
-    
-    fileName = rowFileName(tempDir, row);
+    const char * shellCommand;
+    int termStatus;
 
-    unlink(fileName);
+    unlink(fileNm);
 
     if (maxFormatType == PPM_TYPE && quant)
-        pm_asprintf(&quantStage, "| pnmquant -quiet %u ", colors);
+        pm_asprintf(&quantStage, "| pnmquant -quiet %u ", colorCt);
     else
         quantStage = strdup("");
 
-    fileList = thumbnailFileList(tempDir, row, cols);
+    fileList = thumbnailFileList(tempDir, row, colCt);
+
+    pm_asprintf(&shellCommand, "pamcat %s -leftright -jbottom %s "
+                "%s",
+                blackWhiteOpt, fileList, quantStage);
 
-    systemf("pnmcat %s -leftright -jbottom %s "
-            "%s"
-            ">%s",
-            blackWhiteOpt, fileList, quantStage, fileName);
+    pm_system2(&pm_feed_null, NULL,
+               &pm_accept_to_file, (void*)fileNm,
+               shellCommand,
+               &termStatus);
+
+    if (termStatus != 0)
+        pm_error("Shell command '%s' to create row of thumbnails failed.  "
+                 "Termination status = %d", shellCommand, termStatus);
 
     pm_strfree(fileList);
     pm_strfree(quantStage);
-    pm_strfree(fileName);
+    pm_strfree(fileNm);
+    pm_strfree(shellCommand);
 
-    unlinkThumbnailFiles(tempDir, row, cols);
+    unlinkThumbnailFiles(tempDir, row, colCt);
 }
 
 
@@ -539,7 +688,7 @@ rowFileList(const char * const dirName,
 
         if (strlen(list) + strlen(fileName) + 1 > maxListSize - 1)
             pm_error("File name list too long for this program to handle.");
-        
+
         else {
             strcat(list, " ");
             strcat(list, fileName);
@@ -553,66 +702,99 @@ rowFileList(const char * const dirName,
 
 
 static void
-writeRowsAndDelete(unsigned int const rows,
+writeRowsAndDelete(unsigned int const rowCt,
                    int          const maxFormatType,
                    bool         const blackBackground,
                    bool         const quant,
-                   unsigned int const colors,
+                   unsigned int const colorCt,
                    const char * const tempDir) {
+/*----------------------------------------------------------------------------
+   Write the PNM image containing the 'rowCt' rows of thumbnails to Standard
+   Output.  Take each row of thumbnails from a specially named PNM file in
+   directory 'tempDir', and unlink it from that directory.
+
+   Where the rows are different widths, pad with black if 'blackBackground'
+   is true; white otherwise.
 
+   If 'quant', color-quantize the result to have no more than 'colorCt'
+   colors, choosing the colors that best represent all the pixels in the
+   result.
+
+   'maxFormatType' is the most expressive format of all the row files; results
+   are undefined if it is not.
+-----------------------------------------------------------------------------*/
     const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
+    const char * const plainOpt      = pm_plain_output ? "-plain" : "";
 
     const char * quantStage;
     const char * fileList;
-    
+    const char * shellCommand;
+    int termStatus;
+
     if (maxFormatType == PPM_TYPE && quant)
-        pm_asprintf(&quantStage, "| pnmquant -quiet %u ", colors);
+        pm_asprintf(&quantStage, "| pnmquant -quiet %u ", colorCt);
     else
         quantStage = strdup("");
 
-    fileList = rowFileList(tempDir, rows);
+    fileList = rowFileList(tempDir, rowCt);
 
-    systemf("pnmcat %s -topbottom %s %s",
-            blackWhiteOpt, fileList, quantStage);
+    pm_asprintf(&shellCommand, "pamcat %s %s -topbottom %s %s",
+                plainOpt, blackWhiteOpt, fileList, quantStage);
 
+    /* Do pamcat/pnmquant command with no Standard Input and writing to
+       our Standard Output
+    */
+    pm_system2(&pm_feed_null, NULL,
+               NULL, NULL,
+               shellCommand,
+               &termStatus);
+
+    if (termStatus != 0)
+        pm_error("Shell command '%s' to assemble %u rows of thumbnails and "
+                 "write them out failed; termination status = %d",
+                 shellCommand, rowCt, termStatus);
+
+    pm_strfree(shellCommand);
     pm_strfree(fileList);
     pm_strfree(quantStage);
 
-    unlinkRowFiles(tempDir, rows);
+    unlinkRowFiles(tempDir, rowCt);
 }
 
 
 
 int
-main(int argc, char *argv[]) {
-    struct cmdlineInfo cmdline;
+main(int argc, const char ** argv) {
+
+    struct CmdlineInfo cmdline;
     const char * tempDir;
     int maxFormatType;
     unsigned int colsInRow;
     unsigned int rowsDone;
     unsigned int i;
 
-    pnm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
     verbose = cmdline.verbose;
-    
+
     makeTempDir(&tempDir);
 
-    maxFormatType = PBM_TYPE;
-    colsInRow = 0;
-    rowsDone = 0;
+    rowsDone = 0;  /* initial value */
 
     if (cmdline.title)
         makeTitle(cmdline.title, rowsDone++, cmdline.black, tempDir);
 
-    for (i = 0; i < cmdline.inputFileCount; ++i) {
+    for (i = 0, colsInRow = 0, maxFormatType = PBM_TYPE;
+         i < cmdline.inputFileCount;
+         ++i) {
+
         const char * const inputFileName = cmdline.inputFileName[i];
 
         int format;
 
-        makeThumbnail(inputFileName, cmdline.size, cmdline.black, 
+        makeThumbnail(inputFileName, cmdline.size, cmdline.black,
                       !cmdline.noquant, cmdline.colors, tempDir,
                       rowsDone, colsInRow, &format);
 
@@ -640,3 +822,5 @@ main(int argc, char *argv[]) {
 
     return 0;
 }
+
+