Skip to content

bannertopdf: close output stream so the generated PDF is not truncated - #249

Merged
tillkamppeter merged 1 commit into
OpenPrinting:masterfrom
mtaplits:bannertopdf-close-output-stream
Sep 14, 2026
Merged

tillkamppeter merged 1 commit into
OpenPrinting:masterfrom
mtaplits:bannertopdf-close-output-stream

Conversation

@mtaplits

Copy link
Copy Markdown
Contributor

Fixes #212.

Problem

cfFilterBannerToPDF() opens a stream on the output file descriptor with fdopen(outputfd, "w"), but on the success path it never closes it — fclose(outputfp) only appears on the error paths. The stream therefore keeps its last buffer, and cfFilterChain() closes the raw output fd and exits immediately after the filter function returns, so that tail is discarded.

The lost bytes are the cross-reference table and the %%EOF marker. With the universal filter the next filter in the chain, pdftopdf (backed by PDFio), cannot parse the file and exits with status 1, which kills the chain:

cfFilterChain: pdftopdf completed with status 1.
cfFilterChain: pdftopdf (PID …) stopped with status 1
ERROR: universal filter failed.

This breaks printing the CUPS test page (application/vnd.cups-pdf-banner) and banner pages (job-sheets=standard). Ordinary documents are unaffected because their chains do not start with bannertopdf.

It is a regression from 1.x: there bannertopdf was a standalone filter binary writing to stdout, so normal process exit flushed the stream. The 2.x rewrite made it an in-process library function invoked by cfFilterChain()/cfFilterUniversal(), where the caller closes the fd and exits without giving the filter a chance to flush.

Verification on master

Running the universal filter on the test page template, before and after this change (builds of this branch, same configuration):

env CONTENT_TYPE=application/vnd.cups-pdf-banner CUPS_DATADIR=/usr/share/cups \
    CUPS_SERVERBIN=/usr/lib/cups CUPS_SERVERROOT=/etc/cups PPD=<queue>.ppd \
    CHARSET=utf-8 FINAL_CONTENT_TYPE=application/vnd.cups-raster \
    /usr/lib/cups/filter/universal 1 "$USER" testprint 1 "" \
    /usr/share/cups/data/testprint > out.raster
exit bytes chain
before 1 4 pdftopdf stopped with status 1
after 0 485766 all filters status 0

bannertopdf run standalone produces 49736 bytes for the same input; in the chain pdftopdf was receiving 49152 = 12 × 4096, i.e. truncated exactly on a stdio buffer boundary, with the missing 584 bytes being the xref table and %%EOF.

Content after the fix is intact: the page renders with the same ink density as the template scaled to the same canvas (0.64% vs 2.55% for default-testpage.pdf, ratio 0.252 against a 4×6-to-A4 area ratio of 0.248).

Notes

  • make check: 7 pass, 4 fail — testpdf2, test-analyze, test-pdf, test-ps, all with Font no was not loaded, i.e. the missing ttf-dejavu checkdep. These fail identically without this change and are unrelated to it.
  • The test suite does not currently exercise cfFilterBannerToPDF() at all, which is why this regression was not caught. I have not added a test here since it would need a new fixture (a PDF-io-based assertion on the generated stream); happy to add one if you would like it in this PR.
  • The related but separate truncation in cfPDFPagesFP() (cupsfilters/pdf.c, missing fflush() before pdfioFileOpen()) is already fixed upstream by e14bad4061 / Fix printing error: pdfio output "Missing Root object." #167 and is not touched here.

cfFilterBannerToPDF() never closed the FILE * it fdopen()s on outputfd
on the success path, so any still buffered tail of the generated PDF was
lost whenever the caller closed the raw output file descriptor and exited
right after the filter function returned. cfFilterChain() does exactly
that, so with the "universal" filter the following pdftopdf received a
truncated PDF (missing the cross-reference table and the %%EOF marker),
failed to parse it and exited with status 1.

This broke printing the CUPS test page (application/vnd.cups-pdf-banner)
and banner pages (job-sheets). It is a regression from 1.x, where
bannertopdf was a standalone filter writing to stdout and normal process
exit flushed the stream; the 2.x rewrite turned it into an in-process
library function called by cfFilterChain().

Verified on master: the universal filter chain exits 1 with 4 bytes of
output before the change and 0 with a complete page after it.

Fixes OpenPrinting#212
@tillkamppeter
tillkamppeter merged commit 1e66af6 into OpenPrinting:master Sep 14, 2026
15 checks passed
castrojo added a commit to projectbluefin/fsdk-containers that referenced this pull request Sep 25, 2026
)

libcupsfilters 2.2.1's cfFilterBannerToPDF() never fcloses its
fdopen(outputfd) stream. When cfFilterChain() runs it in-process, the
parent closes outfd and exits right after the function returns, so the
buffered tail of the banner/test-page PDF is lost. pdftopdf then gets
empty or truncated input and the job aborts with 0 bytes.

Backport the upstream fix (OpenPrinting/libcupsfilters#249, commit
1e66af6, on master after 2.2.1) into patches/printing/libcupsfilters/.
The existing libcupsfilters patch_queue picks it up through the junction's
local source staging. Only libcupsfilters and its printing reverse deps
change keys (cups-filters, libppd, pappl-retrofit, base); every other
element keeps its key on both arches.

Signed-off-by: castrojo <castrojo@users.noreply.github.com>
Co-authored-by: castrojo <castrojo@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cfFilterBannerToPDF() output is truncated (missing xref and %%EOF) when run inside cfFilterChain() — breaks CUPS test page printing

2 participants