From 23657c7ce704da457a60a6bc8f53de0fccda0459 Mon Sep 17 00:00:00 2001 From: Till Kamppeter Date: Sun, 1 Aug 2021 15:56:27 +0200 Subject: [PATCH] libcupsfilters, foomatic-rip: Added streaming mode for filters --- NEWS | 15 ++ cupsfilters/ghostscript.c | 226 +++++++++++++++++------------- cupsfilters/pdftopdf/pdftopdf.cc | 91 ++++++++---- filter/foomatic-rip/foomaticrip.c | 45 ++++-- filter/foomatic-rip/postscript.c | 184 ++++++++++++------------ 5 files changed, 331 insertions(+), 230 deletions(-) diff --git a/NEWS b/NEWS index 4a41fe530..13bcf8bf5 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,21 @@ NEWS - OpenPrinting CUPS Filters v1.27.5 - 2020-06-05 CHANGES IN V2.0.0 + - libcupsfilters, foomatic-rip: Added new streaming mode + triggered by the boolean "filter-streaming-mode" option. In + this mode a filter (function) is supposed to avoid + everything which prevents the job data from streaming, as + loading the whole job (or good part of it) into a temporary + file or into memory, interpreting PDF, pre-checking input + file type or zero-page jobs, ... This is mainly to be used + by Printer Applications when they do raster printing in + streaming mode, to run with lowest resources possible. + Currently foomatic-rip, ghostscript(), and pdftopdf() got a + streaming mode. For the former two PostScript (not PDF) is + assumed as input and no zero-page-job check is done, in the + latter all QPDF processing (page management, page size + adjustment, ...) is skipped and only JCL according to the + PPD added. - Sample PPDs: Add borderless page size definitions to Generic PDF Printer, HP Color LaserJet CM3530 MFP PDF, and Ricoh PDF Printer PPD files. diff --git a/cupsfilters/ghostscript.c b/cupsfilters/ghostscript.c index 266505680..fef032ef2 100644 --- a/cupsfilters/ghostscript.c +++ b/cupsfilters/ghostscript.c @@ -883,135 +883,163 @@ ghostscript(int inputfd, /* I - File descriptor input stream */ } /* - * Find out file type ... + * Streaming mode without pre-checking input format or zero-page jobs */ - if (inputseekable) - doc_type = parse_doc_type(fp); - - /* - * Copy input into temporary file if needed ... - * (If the input is not seekable or if it is PostScript, to be able - * to count the pages) - */ - - if (!inputseekable || doc_type == GS_DOC_TYPE_PS) { - if ((fd = cupsTempFd(tempfile, sizeof(tempfile))) < 0) - { - if (log) log(ld, FILTER_LOGLEVEL_ERROR, - "ghostscript: Unable to copy PDF file: %s", strerror(errno)); - return (1); - } - - if (log) log(ld, FILTER_LOGLEVEL_DEBUG, - "ghostscript: Copying input to temp file \"%s\"", - tempfile); - - while ((bytes = fread(buf, 1, sizeof(buf), fp)) > 0) - bytes = write(fd, buf, bytes); + if ((t = cupsGetOption("filter-streaming-mode", num_options, options)) == + NULL || + (!strcasecmp(t, "false") || !strcasecmp(t, "off") || + !strcasecmp(t, "no"))) + { - fclose(fp); - close(fd); + /* + * Find out file type ... + */ - filename = tempfile; + if (inputseekable) + doc_type = parse_doc_type(fp); /* - * Open the temporary file to read it instead of the original input ... + * Copy input into temporary file if needed ... + * (If the input is not seekable or if it is PostScript, to be able + * to count the pages) */ - if ((fp = fopen(filename, "r")) == NULL) - { - if (!iscanceled || !iscanceled(icd)) + if (!inputseekable || doc_type == GS_DOC_TYPE_PS) { + if ((fd = cupsTempFd(tempfile, sizeof(tempfile))) < 0) { - if (log) log(ld, FILTER_LOGLEVEL_DEBUG, - "ghostscript: Unable to open temporary file."); + if (log) log(ld, FILTER_LOGLEVEL_ERROR, + "ghostscript: Unable to copy PDF file: %s", strerror(errno)); + return (1); } - goto out; - } - } else - filename = NULL; + if (log) log(ld, FILTER_LOGLEVEL_DEBUG, + "ghostscript: Copying input to temp file \"%s\"", + tempfile); - if (!inputseekable) - doc_type = parse_doc_type(fp); + while ((bytes = fread(buf, 1, sizeof(buf), fp)) > 0) + bytes = write(fd, buf, bytes); - if (doc_type == GS_DOC_TYPE_EMPTY) { - if (log) log(ld, FILTER_LOGLEVEL_DEBUG, - "ghostscript: Input is empty, outputting empty file."); - status = 0; - if (outformat == OUTPUT_FORMAT_CUPS_RASTER || - outformat == OUTPUT_FORMAT_PWG_RASTER || - outformat == OUTPUT_FORMAT_APPLE_RASTER) - fprintf(stdout, "RaS2"); - goto out; - } if (doc_type == GS_DOC_TYPE_UNKNOWN) { - if (log) log(ld, FILTER_LOGLEVEL_ERROR, - "ghostscript: Can't detect file type"); - goto out; - } + fclose(fp); + close(fd); - if (doc_type == GS_DOC_TYPE_PDF) { - int pages = pdf_pages_fp(fp); + filename = tempfile; - if (pages == 0) { + /* + * Open the temporary file to read it instead of the original input ... + */ + + if ((fp = fopen(filename, "r")) == NULL) + { + if (!iscanceled || !iscanceled(icd)) + { + if (log) log(ld, FILTER_LOGLEVEL_DEBUG, + "ghostscript: Unable to open temporary file."); + } + + goto out; + } + } else + filename = NULL; + + if (!inputseekable) + doc_type = parse_doc_type(fp); + + if (doc_type == GS_DOC_TYPE_EMPTY) { if (log) log(ld, FILTER_LOGLEVEL_DEBUG, - "ghostscript: No pages left, outputting empty file."); + "ghostscript: Input is empty, outputting empty file."); status = 0; if (outformat == OUTPUT_FORMAT_CUPS_RASTER || outformat == OUTPUT_FORMAT_PWG_RASTER || outformat == OUTPUT_FORMAT_APPLE_RASTER) - fprintf(stdout, "RaS2"); + if (write(outputfd, "RaS2", 4)) {}; goto out; - } - if (pages < 0) { + } if (doc_type == GS_DOC_TYPE_UNKNOWN) { if (log) log(ld, FILTER_LOGLEVEL_ERROR, - "ghostscript: Unexpected page count"); + "ghostscript: Can't detect file type"); goto out; } - } else { - char gscommand[65536]; - char output[31] = ""; - int pagecount; - size_t bytes; - // Ghostscript runs too long while converting djvu files to Xerox`s 3210 format - // Using -dDEVICEWIDTHPOINTS -dDEVICEHEIGHTPOINTS params solves the problem - snprintf(gscommand, 65536, "%s -q -dNOPAUSE -dBATCH -sDEVICE=bbox -dDEVICEWIDTHPOINTS=1 -dDEVICEHEIGHTPOINTS=1 %s 2>&1 | grep -c HiResBoundingBox", - CUPS_GHOSTSCRIPT, filename); + + if (doc_type == GS_DOC_TYPE_PDF) { + int pages = pdf_pages_fp(fp); + + if (pages == 0) { + if (log) log(ld, FILTER_LOGLEVEL_DEBUG, + "ghostscript: No pages left, outputting empty file."); + status = 0; + if (outformat == OUTPUT_FORMAT_CUPS_RASTER || + outformat == OUTPUT_FORMAT_PWG_RASTER || + outformat == OUTPUT_FORMAT_APPLE_RASTER) + if (write(outputfd, "RaS2", 4)) {}; + goto out; + } + if (pages < 0) { + if (log) log(ld, FILTER_LOGLEVEL_ERROR, + "ghostscript: Unexpected page count"); + goto out; + } + } else { + char gscommand[65536]; + char output[31] = ""; + int pagecount; + size_t bytes; + /* Ghostscript runs too long on files converted from djvu files */ + /* Using -dDEVICEWIDTHPOINTS -dDEVICEHEIGHTPOINTS params solves the + problem */ + snprintf(gscommand, 65536, "%s -q -dNOPAUSE -dBATCH -sDEVICE=bbox -dDEVICEWIDTHPOINTS=1 -dDEVICEHEIGHTPOINTS=1 %s 2>&1 | grep -c HiResBoundingBox", + CUPS_GHOSTSCRIPT, filename); - FILE *pd = popen(gscommand, "r"); - if (!pd) { - if (log) log(ld, FILTER_LOGLEVEL_ERROR, - "ghostscript: Failed to execute ghostscript to determine " - "number of input pages!"); - goto out; - } + FILE *pd = popen(gscommand, "r"); + if (!pd) { + if (log) log(ld, FILTER_LOGLEVEL_ERROR, + "ghostscript: Failed to execute ghostscript to determine " + "number of input pages!"); + goto out; + } - bytes = fread(output, 1, 31, pd); - pclose(pd); + bytes = fread(output, 1, 31, pd); + pclose(pd); - if (bytes <= 0 || sscanf(output, "%d", &pagecount) < 1) - pagecount = -1; + if (bytes <= 0 || sscanf(output, "%d", &pagecount) < 1) + pagecount = -1; - if (pagecount == 0) { - if (log) log(ld, FILTER_LOGLEVEL_DEBUG, - "ghostscript: No pages left, outputting empty file."); - status = 0; - if (outformat == OUTPUT_FORMAT_CUPS_RASTER || - outformat == OUTPUT_FORMAT_PWG_RASTER || - outformat == OUTPUT_FORMAT_APPLE_RASTER) - fprintf(stdout, "RaS2"); - goto out; + if (pagecount == 0) { + if (log) log(ld, FILTER_LOGLEVEL_DEBUG, + "ghostscript: No pages left, outputting empty file."); + status = 0; + if (outformat == OUTPUT_FORMAT_CUPS_RASTER || + outformat == OUTPUT_FORMAT_PWG_RASTER || + outformat == OUTPUT_FORMAT_APPLE_RASTER) + if (write(outputfd, "RaS2", 4)) {}; + goto out; + } + if (pagecount < 0) { + if (log) log(ld, FILTER_LOGLEVEL_ERROR, + "ghostscript: Unexpected page count"); + goto out; + } } - if (pagecount < 0) { - if (log) log(ld, FILTER_LOGLEVEL_ERROR, - "ghostscript: Unexpected page count"); - goto out; + + if (filename) { + /* Remove name of temp file*/ + unlink(filename); + filename = NULL; } + + if (log) log(ld, FILTER_LOGLEVEL_DEBUG, + "ghostscript: Input format: %s", + (doc_type == GS_DOC_TYPE_PDF ? "PDF" : + (doc_type == GS_DOC_TYPE_PDF ? "PostScript" : + (doc_type == GS_DOC_TYPE_PDF ? "Empty file" : + "Unknown")))); } - if (filename) { - /* Remove name of temp file*/ - unlink(filename); - filename = NULL; + else + { + doc_type = GS_DOC_TYPE_UNKNOWN; + if (log) log(ld, FILTER_LOGLEVEL_DEBUG, + "ghostscript: Input format: Not determined"); + if (log) log(ld, FILTER_LOGLEVEL_DEBUG, + "ghostscript: Streaming mode, no checks for input format, zero-page input, instructions from previous filter"); } /* Check status of color management in CUPS */ @@ -1203,8 +1231,6 @@ ghostscript(int inputfd, /* I - File descriptor input stream */ } } - - /* set PDF-specific options */ if (doc_type == GS_DOC_TYPE_PDF) { parse_pdf_header_options(fp, &h); diff --git a/cupsfilters/pdftopdf/pdftopdf.cc b/cupsfilters/pdftopdf/pdftopdf.cc index 3783486e8..30bc00c22 100644 --- a/cupsfilters/pdftopdf/pdftopdf.cc +++ b/cupsfilters/pdftopdf/pdftopdf.cc @@ -972,6 +972,12 @@ pdftopdf(int inputfd, /* I - File descriptor input stream */ { pdftopdf_doc_t doc; /* Document information */ char *final_content_type = NULL; + FILE *inputfp, + *outputfp; + const char *t; + int streaming = 0; + size_t bytes; + char buf[BUFSIZ]; filter_logfunc_t log = data->logfunc; void *ld = data->logdata; filter_iscanceledfunc_t iscanceled = data->iscanceledfunc; @@ -1004,36 +1010,57 @@ pdftopdf(int inputfd, /* I - File descriptor input stream */ param.dump(&doc); #endif - int empty = 0; + // If we are in streaming mode we only apply JCL and do not run the + // job through QPDL (so no page management, form flattening, + // page size/orientation adjustment, ...) + if ((t = cupsGetOption("filter-streaming-mode", + data->num_options, data->options)) != + NULL && + (strcasecmp(t, "false") && strcasecmp(t, "off") & + strcasecmp(t, "no"))) { + streaming = 1; + if (log) log(ld, FILTER_LOGLEVEL_DEBUG, + "pdftopdf: Streaming mode: No PDF processing, only adding of JCL"); + } std::unique_ptr proc(PDFTOPDF_Factory::processor()); - FILE *f = NULL; - if (inputseekable && inputfd > 0) { - if ((f = fdopen(inputfd, "rb")) == NULL) + if ((inputseekable && inputfd > 0) || streaming) { + if ((inputfp = fdopen(inputfd, "rb")) == NULL) return 1; } else { - if ((f = copy_fd_to_temp(inputfd, &doc)) == NULL) + if ((inputfp = copy_fd_to_temp(inputfd, &doc)) == NULL) return 1; } - if (is_empty(f)) { - fclose(f); - empty = 1; - } else if (!proc->loadFile(f, &doc, WillStayAlive, 1)) { - fclose(f); - return 1; - } - if(empty) - { + if (!streaming) { + if (is_empty(inputfp)) { + fclose(inputfp); + if (log) log(ld, FILTER_LOGLEVEL_DEBUG, + "pdftopdf: Input is empty, outputting empty file."); + return 0; + } + if (log) log(ld, FILTER_LOGLEVEL_DEBUG, - "pdftopdf: Input is empty, outputting empty file."); - return 0; + "pdftopdf: Processing PDF input with QPDF: Page-ranges, page-set, number-up, booklet, size adjustment, ..."); + + // Load the PDF input data into QPDF + if (!proc->loadFile(inputfp, &doc, WillStayAlive, 1)) { + fclose(inputfp); + return 1; + } + + // Process the PDF input data + if (!processPDFTOPDF(*proc, param, &doc)) + return 2; + + // Pass information to subsequent filters via PDF comments + emitComment(*proc, param); } -/* TODO + /* TODO // color management ---- PPD: + --- PPD: copyPPDLine_(fp_dest, fp_src, "*PPD-Adobe: "); copyPPDLine_(fp_dest, fp_src, "*cupsICCProfile "); copyPPDLine_(fp_dest, fp_src, "*Manufacturer:"); @@ -1042,19 +1069,27 @@ pdftopdf(int inputfd, /* I - File descriptor input stream */ if (cupsICCProfile) { proc.addCM(...,...); } -*/ - - if (!processPDFTOPDF(*proc,param,&doc)) - return 2; + */ - FILE *outputfp; outputfp = fdopen(outputfd, "w"); - if(outputfp == NULL) return 1; + if (outputfp == NULL) + return 1; + + emitPreamble(outputfp, data->ppd, param); // ppdEmit, JCL stuff - emitPreamble(outputfp, data->ppd,param); // ppdEmit, JCL stuff - emitComment(*proc,param); // pass information to subsequent filters via PDF comments - proc->emitFile(outputfp, &doc, WillStayAlive); - // proc->emitFilename(NULL); + if (!streaming) { + // Pass on the processed input data + proc->emitFile(outputfp, &doc, WillStayAlive); + // proc->emitFilename(NULL); + } else { + // Pass through the input data + if (log) log(ld, FILTER_LOGLEVEL_DEBUG, + "pdftopdf: Passing on unchanged PDF data from input"); + while ((bytes = fread(buf, 1, sizeof(buf), inputfp)) > 0) + if (fwrite(buf, 1, bytes, outputfp) != bytes) + break; + fclose(inputfp); + } emitPostamble(outputfp, data->ppd,param); fclose(outputfp); diff --git a/filter/foomatic-rip/foomaticrip.c b/filter/foomatic-rip/foomaticrip.c index 9276a3e36..968dd0a2c 100644 --- a/filter/foomatic-rip/foomaticrip.c +++ b/filter/foomatic-rip/foomaticrip.c @@ -140,6 +140,8 @@ int dontparse = 0; int jobhasjcl; int pdfconvertedtops; +/* Streaming mode: Assume PostScript input, no zero-page job check */ +int streaming = 0; /* cm-calibration flag */ int cm_calibrate = 0; @@ -334,6 +336,14 @@ void process_cmdline_options() cm_calibrate = 1; continue; } + /* option to set color calibration mode */ + if (!strcmp(key, "filter-streaming-mode") && + (!value || + (strcasecmp(value, "false") && strcasecmp(value, "off") && + strcasecmp(value, "no")))) { + streaming = 1; + continue; + } /* Solaris options that have no reason to be */ if (!strcmp(key, "nobanner") || !strcmp(key, "dest") || !strcmp(key, "protocol")) continue; @@ -434,6 +444,7 @@ void process_cmdline_options() cm_disabled = 1; } + _log("Streaming Mode: %s\n", streaming ? "Activated" : "Off"); _log("CM Color Calibration Mode in CUPS: %s\n", cm_calibrate ? "Activated" : "Off"); @@ -576,24 +587,30 @@ int print_file(const char *filename, int convert) } } - n = fread_or_die(buf, 1, sizeof(buf) - 1, file); - if (!n){ - _log("Input is empty, outputting empty file.\n"); - return 1; - } - buf[n] = '\0'; - type = guess_file_type(buf, n, &startpos); - /* We do not use any JCL preceeded to the input data, as it is simply - the PJL commands from the PPD file, and these commands we can also - generate, end we even merge them with PJl from the driver */ - /*if (startpos > 0) { + if (streaming == 0 || file != stdin) { + n = fread_or_die(buf, 1, sizeof(buf) - 1, file); + if (!n) { + _log("Input is empty, outputting empty file.\n"); + return 1; + } + buf[n] = '\0'; + type = guess_file_type(buf, n, &startpos); + /* We do not use any JCL preceeded to the input data, as it is simply + the PJL commands from the PPD file, and these commands we can also + generate, end we even merge them with PJl from the driver */ + /*if (startpos > 0) { jobhasjcl = 1; write_output(buf, startpos); - }*/ - if (file != stdin) + }*/ + if (file != stdin) rewind(file); - if (convert) pdfconvertedtops = 0; + if (convert) pdfconvertedtops = 0; + } else { + n = 0; + buf[0] = '\n'; + type = PS_FILE; + } switch (type) { case PDF_FILE: diff --git a/filter/foomatic-rip/postscript.c b/filter/foomatic-rip/postscript.c index 56a8a48de..10f48ead5 100644 --- a/filter/foomatic-rip/postscript.c +++ b/filter/foomatic-rip/postscript.c @@ -191,103 +191,111 @@ int print_ps(FILE *file, const char *alreadyread, size_t len, const char *filena stream.alreadyread = alreadyread; stream.len = len; - /* Check whether the input file is not empty - - We only read the input data until discovering the first page, this - way we can print PostScript files streaming, allowing for very large - or even infinite PostScript jobs, making use of the fact that - PostScript is a streamable data format. - - This also allows Printer Applications to work with foomatic-rip - as they have to support streaming Apple/PWG Raster input data and - infinite jobs in this format. This way we can simply encapsulate the - Raster data in PostScript. - - Ghostscript command line to find out whether the PostScript input - data actually produces pages. The "bbox" output device produces two - lines of output per page on stderr. We suppress any general output - lines ("-q") an redirect the "bbox" output to stdout, where we can - read it. - - "-dDEVICEWIDTHPOINTS=1 -dDEVICEHEIGHTPOINTS=1" saves Ghostscript - from needing to render the pages to find out the numbers in the input - lines, we only need the boolean answer whether there are pages or not */ - snprintf(gscommand, 65536, "%s -q -dNOPAUSE -dBATCH -sDEVICE=bbox -dDEVICEWIDTHPOINTS=1 -dDEVICEHEIGHTPOINTS=1 -_ 2>&1", + /* If a buffer is supplied but with zero length, we are in streaming + mode and do not pre-check for zero-page input, but print right away */ + if (alreadyread && len == 0) + /* Simply print the file, without checking whether it has pages */ + _print_ps(&stream); + else { + /* Check whether the input file is not empty + + We only read the input data until discovering the first page, this + way we can print PostScript files streaming, allowing for very large + or even infinite PostScript jobs, making use of the fact that + PostScript is a streamable data format. + + This also allows Printer Applications to work with foomatic-rip + as they have to support streaming Apple/PWG Raster input data and + infinite jobs in this format. This way we can simply encapsulate the + Raster data in PostScript. + + Ghostscript command line to find out whether the PostScript input + data actually produces pages. The "bbox" output device produces two + lines of output per page on stderr. We suppress any general output + lines ("-q") an redirect the "bbox" output to stdout, where we can + read it. + + "-dDEVICEWIDTHPOINTS=1 -dDEVICEHEIGHTPOINTS=1" saves Ghostscript + from needing to render the pages to find out the numbers in the input + lines, we only need the boolean answer whether there are pages or + not */ + snprintf(gscommand, 65536, "%s -q -dNOPAUSE -dBATCH -sDEVICE=bbox -dDEVICEWIDTHPOINTS=1 -dDEVICEHEIGHTPOINTS=1 -_ 2>&1", CUPS_GHOSTSCRIPT); - /* Launch Ghostscript an return file handles for stdin and stdout of the - Ghostscript process */ - pid = start_system_process("Check PostScript input non-empty", gscommand, &in, &out); - /* We will observe Ghostscript's output with non-blocking poll(), prepare - data structure */ - pfd.fd = fileno(out); - pfd.events = POLLIN; - - /* Read input as long as we do not find a page ("showpage" action in - PostScript, makes the "bbox" device producing output) */ - while ((stream_next_line(line, &stream)) > 0) { - /* Save what we have already read, we need to re-feed it when actually - rendering the job */ - dstrncat(data_read, line->data, line->len); - /* Feed read line into Ghostscript */ - for (bytes = line->len, pos = line->data, bytes_sent = 0; - bytes_sent >= 0 && bytes_sent < bytes; - bytes -= bytes_sent, pos += bytes_sent) - bytes_sent = fwrite_or_die(pos, 1, bytes, in); - if (bytes_sent < 0) - break; - /* Flush to make Ghostscript operate in as close to real-time as - possible */ - fflush(in); - /* Check if Ghostscript produced output, but do not block if not - (timeout = 0) */ - pres = poll(&pfd, 1, 0); - if (pres < 0) { - _log("Error reading Ghostscript output\n"); - break; - } else if (pres && (pfd.revents & POLLIN)) { - /* Ghostscript produced output, meaning that the data read up to now - has produced a page and so the file is not empty, stop reading - further data */ - pagefound = 1; - break; + /* Launch Ghostscript an return file handles for stdin and stdout of the + Ghostscript process */ + pid = start_system_process("Check PostScript input non-empty", gscommand, &in, &out); + /* We will observe Ghostscript's output with non-blocking poll(), prepare + data structure */ + pfd.fd = fileno(out); + pfd.events = POLLIN; + + /* Read input as long as we do not find a page ("showpage" action in + PostScript, makes the "bbox" device producing output) */ + while ((stream_next_line(line, &stream)) > 0) { + /* Save what we have already read, we need to re-feed it when actually + rendering the job */ + dstrncat(data_read, line->data, line->len); + /* Feed read line into Ghostscript */ + for (bytes = line->len, pos = line->data, bytes_sent = 0; + bytes_sent >= 0 && bytes_sent < bytes; + bytes -= bytes_sent, pos += bytes_sent) + bytes_sent = fwrite_or_die(pos, 1, bytes, in); + if (bytes_sent < 0) + break; + /* Flush to make Ghostscript operate in as close to real-time as + possible */ + fflush(in); + /* Check if Ghostscript produced output, but do not block if not + (timeout = 0) */ + pres = poll(&pfd, 1, 0); + if (pres < 0) { + _log("Error reading Ghostscript output\n"); + break; + } else if (pres && (pfd.revents & POLLIN)) { + /* Ghostscript produced output, meaning that the data read up to now + has produced a page and so the file is not empty, stop reading + further data */ + pagefound = 1; + break; + } } - } - /* If the input file has only a single page and the "showpage" is - very close to the end of the file, it cannot have been - discovered after the last bits of input data got - read. Therefore we do an extra check here. */ - if (!pagefound) { - pres = poll(&pfd, 1, 1000); - if (pres < 0) - _log("Error reading Ghostscript output\n"); - else if (pres && (pfd.revents & POLLIN)) - pagefound = 1; - } + /* If the input file has only a single page and the "showpage" is + very close to the end of the file, it cannot have been + discovered after the last bits of input data got + read. Therefore we do an extra check here. */ + if (!pagefound) { + pres = poll(&pfd, 1, 1000); + if (pres < 0) + _log("Error reading Ghostscript output\n"); + else if (pres && (pfd.revents & POLLIN)) + pagefound = 1; + } - /* Clean up and make Ghostscript stop by that */ - fclose(in); - fclose(out); - wait_for_process(pid); + /* Clean up and make Ghostscript stop by that */ + fclose(in); + fclose(out); + wait_for_process(pid); - if (pagefound) { - _log("File not empty, contains at least one page.\n"); + if (pagefound) { + _log("File not empty, contains at least one page.\n"); - /* Redefine stream for what we have read now */ - if (data_read->len < len) - dstrncat(data_read, buf + data_read->len, len - data_read->len); + /* Redefine stream for what we have read now */ + if (data_read->len < len) + dstrncat(data_read, buf + data_read->len, len - data_read->len); - stream.pos = 0; - stream.file = file; - stream.alreadyread = data_read->data; - stream.len = data_read->len; + stream.pos = 0; + stream.file = file; + stream.alreadyread = data_read->data; + stream.len = data_read->len; - /* Print the file */ - _print_ps(&stream); - } else - _log("No pages left, outputting empty file.\n"); + /* Print the file */ + _print_ps(&stream); + } else + _log("No pages left, outputting empty file.\n"); - free_dstr(data_read); + free_dstr(data_read); + } return 1; }