diff options
Diffstat (limited to 'editor/specialty/pnmindex.c')
-rw-r--r-- | editor/specialty/pnmindex.c | 494 |
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; } + + |