From 1282d855dc009953f027e80a1a029f53d089a789 Mon Sep 17 00:00:00 2001 From: castrojo Date: Wed, 16 Sep 2026 02:53:56 -0400 Subject: [PATCH 1/9] docs: plan FSDK core driver payload Assisted-by: github-copilot/gpt-5.6-sol via pi --- .../plans/2026-09-16-fsdk-core-payload.md | 294 ++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 docs/superpowers/plans/2026-09-16-fsdk-core-payload.md diff --git a/docs/superpowers/plans/2026-09-16-fsdk-core-payload.md b/docs/superpowers/plans/2026-09-16-fsdk-core-payload.md new file mode 100644 index 0000000..44f15e3 --- /dev/null +++ b/docs/superpowers/plans/2026-09-16-fsdk-core-payload.md @@ -0,0 +1,294 @@ +# FSDK Core Driver Payload Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Expose the core CUPS, Ghostscript, cups-filters, libppd, and Foomatic driver payload from the FSDK appliance and prove one real conversion reaches a socket-backed printer. + +**Architecture:** Reuse FSDK's existing printing artifacts without rebuilding or duplicating them. Build only pyppd and the Foomatic XML compiler that FSDK does not ship, generate the same three self-extracting PPD archives as the current OCI contract, and compose those archives with FSDK's filters plus their measured runtime dependencies. Verify the exported image, not BuildStream source text. + +**Tech Stack:** BuildStream 2, freedesktop-sdk 26.08rc.1, cups-filters 2.0.1, Foomatic DB 20240504, foomatic-db-engine 4.1.0, pyppd 1.1.0, Python 3, xz, Podman, socat. + +## Global Constraints + +- FSDK remains the only owner of CUPS, Ghostscript, cups-filters, libppd, and the Foomatic database. +- Repository elements may build only missing tooling and appliance-specific generated payloads. +- Keep `/usr/lib/ghostscript-printer-app` as the existing symlink to `/usr/lib/cups`; do not duplicate filters. +- Keep PPD drivers under `/usr/share/ppd`, matching the existing launcher `PPD_PATHS` contract. +- Generated pyppd archives must run with the same packaged Python major/minor used to create them and with packaged xz. +- Do not publish this incomplete image. + +--- + +### Task 1: Build the missing PPD tooling + +**Files:** +- Create: `elements/printer-app/pyppd.bst` +- Create: `elements/printer-app/foomatic-db-engine.bst` +- Create: `patches/foomatic-db-engine/xml-database-without-dbi.patch` + +**Interfaces:** +- Consumes: FSDK Python, Perl, XML::Parser, curl, file, gzip, cups-filters, and Foomatic database artifacts. +- Produces: build-time `/usr/bin/pyppd` and `/usr/sbin/foomatic-compiledb` commands. + +- [ ] **Step 1: Add the pyppd element** + +Create `elements/printer-app/pyppd.bst`: + +```yaml +kind: pyproject +description: Build the pyppd self-extracting PPD archive generator. + +build-depends: + - freedesktop-sdk.bst:public-stacks/buildsystem-python-setuptools.bst + +depends: + - freedesktop-sdk.bst:components/python3.bst + - freedesktop-sdk.bst:components/xz.bst + +sources: + - kind: git_repo + url: github:OpenPrinting/pyppd.git + track: release-1-1-0 + ref: release-1-1-0-0-g29ccf6cf85781315a696774e7458a2f1f61aac57 +``` + +- [ ] **Step 2: Make Foomatic's XML-only path independent of DBI** + +Create `patches/foomatic-db-engine/xml-database-without-dbi.patch` that removes the unconditional `use DBI;` from `lib/Foomatic/DB.pm` and adds `require DBI;` only inside the MySQL and SQLite branches of `connect_to_mysql_db()`. The XML database path used by `foomatic-compiledb` must not require an unavailable SQL driver. + +- [ ] **Step 3: Add the Foomatic engine element** + +Create `elements/printer-app/foomatic-db-engine.bst` with immutable commit `e4e7b9cd28ba160428f82bc5234559d1f50e5c42`, the DBI patch queue, and these build dependencies: + +```yaml +build-depends: + - freedesktop-sdk.bst:public-stacks/buildsystem-autotools.bst + - freedesktop-sdk.bst:components/cups-filters.bst + - freedesktop-sdk.bst:components/curl.bst + - freedesktop-sdk.bst:components/file.bst + - freedesktop-sdk.bst:components/foomatic-db.bst + - freedesktop-sdk.bst:components/ghostscript.bst + - freedesktop-sdk.bst:components/gzip.bst + - freedesktop-sdk.bst:components/perl.bst + - freedesktop-sdk.bst:components/perl-xml-parser.bst +``` + +Use these commands: + +```yaml +config: + build-commands: + - ./make_configure + - PERL_INSTALLDIRS=vendor ./configure --prefix=/usr --libdir="%{libdir}" + - make -j1 + install-commands: + - make DESTDIR="%{install-root}" install +``` + +- [ ] **Step 4: Verify both tools build** + +Run: + +```bash +just bst build printer-app/pyppd.bst printer-app/foomatic-db-engine.bst +just bst artifact list-contents printer-app/pyppd.bst printer-app/foomatic-db-engine.bst +``` + +Expected: the artifacts contain `/usr/bin/pyppd` and `/usr/sbin/foomatic-compiledb`; no second CUPS library is introduced. + +- [ ] **Step 5: Commit the tooling** + +```bash +git add elements/printer-app/pyppd.bst elements/printer-app/foomatic-db-engine.bst patches/foomatic-db-engine/xml-database-without-dbi.patch +git commit -m "build: add core PPD generation tools" -m "Assisted-by: github-copilot/gpt-5.6-sol via pi" +``` + +### Task 2: Generate the core PPD archives + +**Files:** +- Create: `elements/printer-app/core-payload.bst` + +**Interfaces:** +- Consumes: FSDK cups-filters PPDs and Foomatic XML/manufacturer data plus the Task 1 generators. +- Produces: executable `/usr/share/ppd/cups-filters-ppds`, `/usr/share/ppd/foomatic-ppds`, and `/usr/share/ppd/manufacturer-ppds` archives. + +- [ ] **Step 1: Stage source payloads and generators** + +Create a `manual` element with build dependencies on `printer-app/pyppd.bst`, `printer-app/foomatic-db-engine.bst`, `freedesktop-sdk.bst:components/cups-filters.bst`, `freedesktop-sdk.bst:components/foomatic-db.bst`, and `freedesktop-sdk.bst:public-stacks/runtime-gnu.bst`. + +- [ ] **Step 2: Generate the cups-filters archive** + +The build commands must copy `/usr/share/ppd/cupsfilters` to `payload/cupsfilters` and run: + +```bash +pyppd -v -o cups-filters-ppds payload/cupsfilters +``` + +- [ ] **Step 3: Generate the Foomatic archives** + +Copy `/usr/share/foomatic` to `payload/foomatic`, remove PostScript manufacturer PPDs and the unsupported driver XML files carried by the current Snap contract: + +```text +bjc800j.xml c2070.xml drv_x125.xml lm1100.xml lpstyl.xml ml85p.xml +pbm2l2030.xml pbm2l7k.xml pbm2lwxl.xml pentaxpj.xml ppmtomd.xml +``` + +Then run: + +```bash +FOOMATICDB="$PWD/payload/foomatic" foomatic-compiledb -j "%{max-jobs}" -t ppd -d payload/foomatic-ppds +pyppd -v -o foomatic-ppds payload/foomatic-ppds +pyppd -v -o manufacturer-ppds payload/foomatic/db/source/PPD +``` + +- [ ] **Step 4: Install only generated archives** + +Install the three executable archives with mode `0755` beneath `%{install-root}/usr/share/ppd`. Do not carry the Foomatic compiler, raw XML database, or pyppd package into this artifact. + +- [ ] **Step 5: Build and inspect the payload artifact** + +Run: + +```bash +just bst build printer-app/core-payload.bst +just bst artifact list-contents printer-app/core-payload.bst +``` + +Expected: exactly the three executable archive files appear under `/usr/share/ppd`. + +- [ ] **Step 6: Commit the generated-payload element** + +```bash +git add elements/printer-app/core-payload.bst +git commit -m "build: generate core PPD archives" -m "Assisted-by: github-copilot/gpt-5.6-sol via pi" +``` + +### Task 3: Compose the runtime payload and verify its closure + +**Files:** +- Modify: `elements/printer-app/core-stack.bst` +- Create: `tests/core-payload.sh` +- Modify: `Justfile` + +**Interfaces:** +- Consumes: `printer-app/core-payload.bst` and FSDK runtime components. +- Produces: `just verify-payload`, proving driver archives, filters, interpreters, HTTPS, and shared-library closure inside the exported image. + +- [ ] **Step 1: Write the failing image-level payload check** + +Create `tests/core-payload.sh`. It must run `just build`, then assert from the image that: + +```text +/usr/lib/ghostscript-printer-app -> /usr/lib/cups +/usr/lib/cups/filter/foomatic-rip +/usr/lib/cups/filter/gstoraster +/usr/lib/cups/filter/pdftops +/usr/lib/cups/filter/rastertoescpx +/usr/lib/cups/filter/rastertopclx +/usr/share/ghostscript-printer-app/testpage.ps +/usr/share/ppd/cups-filters-ppds +/usr/share/ppd/foomatic-ppds +/usr/share/ppd/manufacturer-ppds +/usr/bin/python3 +/usr/bin/xz +``` + +For each archive, run `list`, capture its first URI, run `cat `, and require the extracted text to contain `*PPD-Adobe:`. Run this check now; it must fail because the payload is not yet composed. + +- [ ] **Step 2: Add runtime components** + +Add these dependencies to `core-stack.bst`: + +```yaml +- printer-app/core-payload.bst +- freedesktop-sdk.bst:components/cups-filters.bst +- freedesktop-sdk.bst:components/mutool.bst +- freedesktop-sdk.bst:components/python3.bst +- freedesktop-sdk.bst:components/xz.bst +``` + +Keep the existing Ghostscript, CUPS, libppd, and libcupsfilters dependencies. + +- [ ] **Step 3: Verify HTTP and HTTPS** + +The payload test starts the real image on host networking, waits for `Ghostscript Printer Application` over HTTP, and requires the same title over HTTPS with `curl --insecure`. + +- [ ] **Step 4: Verify ELF closure** + +Inside the image, run `ldd` for the application and each core filter listed in Step 1. Fail if any output contains `not found`. + +- [ ] **Step 5: Add the verification command** + +Add to `Justfile`: + +```just +verify-payload: + tests/core-payload.sh +``` + +- [ ] **Step 6: Run and commit the runtime checks** + +Run: + +```bash +just verify-payload +``` + +Expected: all payload, archive, HTTPS, and ELF checks pass. + +```bash +git add Justfile elements/printer-app/core-stack.bst tests/core-payload.sh +git commit -m "test: verify core driver payload" -m "Assisted-by: github-copilot/gpt-5.6-sol via pi" +``` + +### Task 4: Prove a deterministic print conversion + +**Files:** +- Modify: `tests/core-payload.sh` + +**Interfaces:** +- Consumes: running Printer Application, `cups-filters-ppds:Generic-PDF_Printer-PDF.ppd`, repository test page, and host `socat`. +- Produces: non-empty printer-language output captured from a real submitted job. + +- [ ] **Step 1: Add the socket-backed printer test** + +Start a one-shot host sink before the container: + +```bash +socat -u "TCP-LISTEN:${sink_port},reuseaddr" "OPEN:${output_file},creat,trunc" & +sink_pid=$! +``` + +After HTTP/HTTPS readiness, run inside the image: + +```bash +ghostscript-printer-app -u "ipp://127.0.0.1:${port}/ipp/system" add core-test \ + -m cups-filters-ppds:Generic-PDF_Printer-PDF.ppd \ + -v "cups:socket://127.0.0.1:${sink_port}" +ghostscript-printer-app -u "ipp://127.0.0.1:${port}/ipp/system" \ + -d core-test submit /usr/share/ghostscript-printer-app/testpage.ps +``` + +- [ ] **Step 2: Assert conversion output** + +Poll until `${output_file}` is non-empty, then require its first five bytes to be `%PDF-`. A successful CLI return without captured output is a failure. + +- [ ] **Step 3: Re-run all slice gates** + +Run: + +```bash +just validate +just verify-core +just verify-payload +just verify-cups-patch-chain +actionlint .github/workflows/*.yml +bash -n files/container-entrypoint.sh tests/core-appliance.sh tests/core-payload.sh tests/cups-patch-chain.sh +git diff --check +``` + +Expected: every command succeeds. + +- [ ] **Step 4: Resolve and publish the slice** + +Set `.scratch/fsdk-container-modernization/issues/03-serve-core-ppd-filter-payload.md` to `resolved`, record exact successful commands and commit IDs, commit this completed plan, push `feat/fsdk-core-payload`, and open a pull request with base `feat/fsdk-core-app`. Do not publish an OCI release. From 54eabd1a95c984ce81ae222cefbeeb593bcee956 Mon Sep 17 00:00:00 2001 From: castrojo Date: Wed, 16 Sep 2026 02:59:46 -0400 Subject: [PATCH 2/9] build: add core PPD generation tools Assisted-by: github-copilot/gpt-5.6-sol via pi --- elements/printer-app/foomatic-db-engine.bst | 29 +++++++++++++++++++ elements/printer-app/pyppd.bst | 21 ++++++++++++++ .../xml-database-without-dbi.patch | 28 ++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 elements/printer-app/foomatic-db-engine.bst create mode 100644 elements/printer-app/pyppd.bst create mode 100644 patches/foomatic-db-engine/xml-database-without-dbi.patch diff --git a/elements/printer-app/foomatic-db-engine.bst b/elements/printer-app/foomatic-db-engine.bst new file mode 100644 index 0000000..e33c20f --- /dev/null +++ b/elements/printer-app/foomatic-db-engine.bst @@ -0,0 +1,29 @@ +kind: manual +description: Build the Foomatic XML database compiler. + +sources: + - kind: git_repo + url: github:OpenPrinting/foomatic-db-engine.git + track: master + ref: e4e7b9cd28ba160428f82bc5234559d1f50e5c42 + - kind: patch_queue + path: patches/foomatic-db-engine + +build-depends: + - freedesktop-sdk.bst:public-stacks/buildsystem-autotools.bst + - freedesktop-sdk.bst:components/cups-filters.bst + - freedesktop-sdk.bst:components/curl.bst + - freedesktop-sdk.bst:components/file.bst + - freedesktop-sdk.bst:components/foomatic-db.bst + - freedesktop-sdk.bst:components/ghostscript.bst + - freedesktop-sdk.bst:components/gzip.bst + - freedesktop-sdk.bst:components/perl.bst + - freedesktop-sdk.bst:components/perl-xml-parser.bst + +config: + build-commands: + - ./make_configure + - PERL_INSTALLDIRS=vendor ./configure --prefix=/usr --libdir="%{libdir}" + - make -j1 + install-commands: + - make DESTDIR="%{install-root}" install diff --git a/elements/printer-app/pyppd.bst b/elements/printer-app/pyppd.bst new file mode 100644 index 0000000..aa360f8 --- /dev/null +++ b/elements/printer-app/pyppd.bst @@ -0,0 +1,21 @@ +kind: manual +description: Build the pyppd self-extracting PPD archive generator. + +sources: + - kind: git_repo + url: github:OpenPrinting/pyppd.git + track: release-1-1-0 + ref: release-1-1-0-0-g29ccf6cf85781315a696774e7458a2f1f61aac57 + +build-depends: + - freedesktop-sdk.bst:public-stacks/buildsystem-python-setuptools.bst + +depends: + - freedesktop-sdk.bst:components/python3.bst + - freedesktop-sdk.bst:components/xz.bst + +config: + build-commands: + - python3 -m build --no-isolation --wheel --outdir dist . + install-commands: + - python3 -m installer dist/*.whl --destdir "%{install-root}" diff --git a/patches/foomatic-db-engine/xml-database-without-dbi.patch b/patches/foomatic-db-engine/xml-database-without-dbi.patch new file mode 100644 index 0000000..adc91cc --- /dev/null +++ b/patches/foomatic-db-engine/xml-database-without-dbi.patch @@ -0,0 +1,28 @@ +diff --git a/lib/Foomatic/DB.pm b/lib/Foomatic/DB.pm +index 50990cc..cb46f5e 100644 +--- a/lib/Foomatic/DB.pm ++++ b/lib/Foomatic/DB.pm +@@ -14,7 +14,6 @@ use Encode; + use Foomatic::Defaults qw(:DEFAULT $DEBUG); + use Foomatic::filters::xml::xmlParse; + use Foomatic::filters::xml::to; +-use DBI; + use Data::Dumper; + use POSIX; # for rounding integers + use strict; +@@ -93,6 +92,7 @@ sub connect_to_mysql_db { + $mysqlconf{'user'} = 'root' if !$mysqlconf{'user'}; + $mysqlconf{'password'} = '' if !$mysqlconf{'password'}; + $mysqlconf{'database'} = 'openprinting' if !$mysqlconf{'database'}; ++ require DBI; + $this->{'dbh'} = DBI->connect("dbi:mysql:database=" . + $mysqlconf{'database'} . ';host=' . + $mysqlconf{'server'}, +@@ -103,6 +103,7 @@ sub connect_to_mysql_db { + $this->{'dbtype'} = 'mysql'; + } elsif(-r $sqlitedb) { + $sqlitedb = "$libdir/db/openprinting.db"; ++ require DBI; + $this->{'dbh'} = DBI->connect("dbi:SQLite:dbname=$sqlitedb","","")or + warn $this->{'dbh'}->errstr; + $this->{'dbh'}->do('PRAGMA synchronous = OFF;'); From dedc48ee5cda71cf8e9864846a13415cb9677234 Mon Sep 17 00:00:00 2001 From: castrojo Date: Wed, 16 Sep 2026 03:53:05 -0400 Subject: [PATCH 3/9] build: add Foomatic XML dependencies Assisted-by: github-copilot/gpt-5.6-sol via pi --- elements/printer-app/foomatic-db-engine.bst | 6 ++- elements/printer-app/perl-clone.bst | 14 ++++++ elements/printer-app/perl-xml-libxml.bst | 20 +++++++++ .../perl-xml-namespace-support.bst | 14 ++++++ elements/printer-app/perl-xml-sax-base.bst | 14 ++++++ elements/printer-app/perl-xml-sax.bst | 16 +++++++ .../xml-database-without-dbi.patch | 12 +++++ .../perl-xml-libxml/use-system-libxml2.patch | 45 +++++++++++++++++++ project.conf | 3 ++ 9 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 elements/printer-app/perl-clone.bst create mode 100644 elements/printer-app/perl-xml-libxml.bst create mode 100644 elements/printer-app/perl-xml-namespace-support.bst create mode 100644 elements/printer-app/perl-xml-sax-base.bst create mode 100644 elements/printer-app/perl-xml-sax.bst create mode 100644 patches/perl-xml-libxml/use-system-libxml2.patch diff --git a/elements/printer-app/foomatic-db-engine.bst b/elements/printer-app/foomatic-db-engine.bst index e33c20f..4e98b7c 100644 --- a/elements/printer-app/foomatic-db-engine.bst +++ b/elements/printer-app/foomatic-db-engine.bst @@ -10,6 +10,8 @@ sources: path: patches/foomatic-db-engine build-depends: + - printer-app/perl-clone.bst + - printer-app/perl-xml-libxml.bst - freedesktop-sdk.bst:public-stacks/buildsystem-autotools.bst - freedesktop-sdk.bst:components/cups-filters.bst - freedesktop-sdk.bst:components/curl.bst @@ -26,4 +28,6 @@ config: - PERL_INSTALLDIRS=vendor ./configure --prefix=/usr --libdir="%{libdir}" - make -j1 install-commands: - - make DESTDIR="%{install-root}" install + - make DESTDIR="$PWD/full-install" install + - install -D -m 0755 full-install/usr/sbin/foomatic-compiledb "%{install-root}/usr/bin/foomatic-compiledb" + - mkdir -p "%{install-root}/usr/lib" && cp -a full-install/usr/lib/perl5 "%{install-root}/usr/lib/" diff --git a/elements/printer-app/perl-clone.bst b/elements/printer-app/perl-clone.bst new file mode 100644 index 0000000..cce4677 --- /dev/null +++ b/elements/printer-app/perl-clone.bst @@ -0,0 +1,14 @@ +kind: makemaker +description: Build the Perl Clone module used by Foomatic. + +build-depends: + - freedesktop-sdk.bst:public-stacks/buildsystem-make.bst + +depends: + - freedesktop-sdk.bst:components/perl.bst + +sources: + - kind: cpan + name: Clone + suffix: authors/id/A/AT/ATOOMIC/Clone-0.50.tar.gz + sha256sum: f9732a4a857974db30905233589113003301b585b0cecda29a21cfba5bb014f9 diff --git a/elements/printer-app/perl-xml-libxml.bst b/elements/printer-app/perl-xml-libxml.bst new file mode 100644 index 0000000..a4c8ab9 --- /dev/null +++ b/elements/printer-app/perl-xml-libxml.bst @@ -0,0 +1,20 @@ +kind: makemaker +description: Build XML::LibXML against FSDK libxml2 for Foomatic. + +build-depends: + - freedesktop-sdk.bst:public-stacks/buildsystem-make.bst + - freedesktop-sdk.bst:components/pkg-config.bst + +depends: + - printer-app/perl-xml-namespace-support.bst + - printer-app/perl-xml-sax.bst + - freedesktop-sdk.bst:components/libxml2.bst + - freedesktop-sdk.bst:components/perl.bst + +sources: + - kind: cpan + name: XML::LibXML + suffix: authors/id/T/TO/TODDR/XML-LibXML-2.0213.tar.gz + sha256sum: 2af21c5d61ac34ea26a5fabf15ba5a5841e648f7189db3e33b6f28b5489802ab + - kind: patch_queue + path: patches/perl-xml-libxml diff --git a/elements/printer-app/perl-xml-namespace-support.bst b/elements/printer-app/perl-xml-namespace-support.bst new file mode 100644 index 0000000..2258d28 --- /dev/null +++ b/elements/printer-app/perl-xml-namespace-support.bst @@ -0,0 +1,14 @@ +kind: makemaker +description: Build XML::NamespaceSupport for Foomatic XML parsing. + +build-depends: + - freedesktop-sdk.bst:public-stacks/buildsystem-make.bst + +depends: + - freedesktop-sdk.bst:components/perl.bst + +sources: + - kind: cpan + name: XML::NamespaceSupport + suffix: authors/id/P/PE/PERIGRIN/XML-NamespaceSupport-1.12.tar.gz + sha256sum: 47e995859f8dd0413aa3f22d350c4a62da652e854267aa0586ae544ae2bae5ef diff --git a/elements/printer-app/perl-xml-sax-base.bst b/elements/printer-app/perl-xml-sax-base.bst new file mode 100644 index 0000000..283edaf --- /dev/null +++ b/elements/printer-app/perl-xml-sax-base.bst @@ -0,0 +1,14 @@ +kind: makemaker +description: Build XML::SAX::Base for Foomatic XML parsing. + +build-depends: + - freedesktop-sdk.bst:public-stacks/buildsystem-make.bst + +depends: + - freedesktop-sdk.bst:components/perl.bst + +sources: + - kind: cpan + name: XML::SAX::Base + suffix: authors/id/G/GR/GRANTM/XML-SAX-Base-1.09.tar.gz + sha256sum: 66cb355ba4ef47c10ca738bd35999723644386ac853abbeb5132841f5e8a2ad0 diff --git a/elements/printer-app/perl-xml-sax.bst b/elements/printer-app/perl-xml-sax.bst new file mode 100644 index 0000000..fea36ad --- /dev/null +++ b/elements/printer-app/perl-xml-sax.bst @@ -0,0 +1,16 @@ +kind: makemaker +description: Build XML::SAX for Foomatic XML parsing. + +build-depends: + - freedesktop-sdk.bst:public-stacks/buildsystem-make.bst + +depends: + - printer-app/perl-xml-namespace-support.bst + - printer-app/perl-xml-sax-base.bst + - freedesktop-sdk.bst:components/perl.bst + +sources: + - kind: cpan + name: XML::SAX + suffix: authors/id/G/GR/GRANTM/XML-SAX-1.02.tar.gz + sha256sum: 4506c387043aa6a77b455f00f57409f3720aa7e553495ab2535263b4ed1ea12a diff --git a/patches/foomatic-db-engine/xml-database-without-dbi.patch b/patches/foomatic-db-engine/xml-database-without-dbi.patch index adc91cc..8516248 100644 --- a/patches/foomatic-db-engine/xml-database-without-dbi.patch +++ b/patches/foomatic-db-engine/xml-database-without-dbi.patch @@ -26,3 +26,15 @@ index 50990cc..cb46f5e 100644 $this->{'dbh'} = DBI->connect("dbi:SQLite:dbname=$sqlitedb","","")or warn $this->{'dbh'}->errstr; $this->{'dbh'}->do('PRAGMA synchronous = OFF;'); +diff --git a/lib/Foomatic/filters/xml/to.pm b/lib/Foomatic/filters/xml/to.pm +index 290c8c8..328a290 100644 +--- a/lib/Foomatic/filters/xml/to.pm ++++ b/lib/Foomatic/filters/xml/to.pm +@@ -4,7 +4,6 @@ use strict; + use warnings; + use Data::Dumper; + +-use DBI; + use Foomatic::filters::phonebook; + use Foomatic::util; + use Foomatic::DB; diff --git a/patches/perl-xml-libxml/use-system-libxml2.patch b/patches/perl-xml-libxml/use-system-libxml2.patch new file mode 100644 index 0000000..9aac89b --- /dev/null +++ b/patches/perl-xml-libxml/use-system-libxml2.patch @@ -0,0 +1,45 @@ +diff --git a/Makefile.PL b/Makefile.PL +--- a/Makefile.PL ++++ b/Makefile.PL +@@ -17,15 +17,12 @@ + + require 5.008001; + +-use Alien::Base::Wrapper qw( Alien::Libxml2 ); + use ExtUtils::MakeMaker; + use Config; + + my $SKIP_SAX_INSTALL = $ENV{SKIP_SAX_INSTALL}; + + my %ConfigReqs = ( +- "Alien::Libxml2" => '0.14', +- "Alien::Base::Wrapper" => 0, + "Config" => 0, + "ExtUtils::MakeMaker" => 0, + ); +@@ -67,15 +64,17 @@ + "warnings" => 0, + ); + +-my %xsbuild_concat = ( +- DEFINE => '-DHAVE_UTF8', +- OBJECT => '$(O_FILES)', ++my $cflags = `pkg-config --cflags libxml-2.0`; ++chomp $cflags; ++my $libs = `pkg-config --libs libxml-2.0`; ++chomp $libs; ++ ++my %xsbuild = ( ++ DEFINE => '-DHAVE_UTF8', ++ INC => $cflags, ++ LIBS => [$libs], ++ OBJECT => '$(O_FILES)', + ); +-my %xsbuild = Alien::Base::Wrapper->mm_args; # Might contain a definition of DEFINE, must thus concatenate. +-while (my ($k, $v) = each %xsbuild_concat) { +- my $base_val = $xsbuild{$k}; +- $xsbuild{$k} = (defined($base_val) ? ($base_val . ' ' . $v) : $v); +-} + + # Strip bogus -L/lib entries that Alien::Base::Wrapper sometimes injects + # (the directory does not exist on macOS and the linker warns about it). diff --git a/project.conf b/project.conf index de68ad2..16a10a5 100644 --- a/project.conf +++ b/project.conf @@ -39,9 +39,12 @@ source-caches: plugins: - origin: junction junction: plugins/buildstream-plugins-community.bst + elements: + - makemaker sources: - git_repo - patch_queue + - cpan sources: git_repo: From 88fe51e3f883c9a2ea9b46c476d7a5dad8ae89ab Mon Sep 17 00:00:00 2001 From: castrojo Date: Wed, 16 Sep 2026 03:53:19 -0400 Subject: [PATCH 4/9] build: generate core PPD archives Assisted-by: github-copilot/gpt-5.6-sol via pi --- elements/printer-app/core-payload.bst | 49 +++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 elements/printer-app/core-payload.bst diff --git a/elements/printer-app/core-payload.bst b/elements/printer-app/core-payload.bst new file mode 100644 index 0000000..746dcad --- /dev/null +++ b/elements/printer-app/core-payload.bst @@ -0,0 +1,49 @@ +kind: manual +description: Generate the core cups-filters and Foomatic PPD archives. + +build-depends: + - printer-app/foomatic-db-engine.bst + - printer-app/pyppd.bst + - printer-app/perl-clone.bst + - printer-app/perl-xml-libxml.bst + - freedesktop-sdk.bst:public-stacks/runtime-gnu.bst + - freedesktop-sdk.bst:components/cups-filters.bst + - freedesktop-sdk.bst:components/foomatic-db.bst + - freedesktop-sdk.bst:components/gzip.bst + - freedesktop-sdk.bst:components/perl.bst + - freedesktop-sdk.bst:components/perl-xml-parser.bst + +config: + build-commands: + - | + mkdir -p payload/cupsfilters + cp -a /usr/share/ppd/cupsfilters/. payload/cupsfilters/ + pyppd -v -o cups-filters-ppds payload/cupsfilters + - | + mkdir -p payload/foomatic + cp -a /usr/share/foomatic/. payload/foomatic/ + rm -rf \ + payload/foomatic/db/source/PPD/Kyocera \ + payload/foomatic/db/source/PPD/Utax \ + payload/foomatic/db/source/PPD/Oce \ + payload/foomatic/db/source/PPD/*/PS + rm -f \ + payload/foomatic/db/source/driver/bjc800j.xml \ + payload/foomatic/db/source/driver/c2070.xml \ + payload/foomatic/db/source/driver/drv_x125.xml \ + payload/foomatic/db/source/driver/lm1100.xml \ + payload/foomatic/db/source/driver/lpstyl.xml \ + payload/foomatic/db/source/driver/ml85p.xml \ + payload/foomatic/db/source/driver/pbm2l2030.xml \ + payload/foomatic/db/source/driver/pbm2l7k.xml \ + payload/foomatic/db/source/driver/pbm2lwxl.xml \ + payload/foomatic/db/source/driver/pentaxpj.xml \ + payload/foomatic/db/source/driver/ppmtomd.xml + FOOMATICDB="$PWD/payload/foomatic" \ + foomatic-compiledb -j "%{max-jobs}" -t ppd -d payload/foomatic-ppds + pyppd -v -o foomatic-ppds payload/foomatic-ppds + pyppd -v -o manufacturer-ppds payload/foomatic/db/source/PPD + install-commands: + - install -D -m 0755 cups-filters-ppds "%{install-root}/usr/share/ppd/cups-filters-ppds" + - install -D -m 0755 foomatic-ppds "%{install-root}/usr/share/ppd/foomatic-ppds" + - install -D -m 0755 manufacturer-ppds "%{install-root}/usr/share/ppd/manufacturer-ppds" From e0ec8311e94b206848a48fc29f359ee02a633e8d Mon Sep 17 00:00:00 2001 From: castrojo Date: Wed, 16 Sep 2026 03:53:48 -0400 Subject: [PATCH 5/9] test: verify core driver payload Assisted-by: github-copilot/gpt-5.6-sol via pi --- Justfile | 3 ++ elements/printer-app/core-stack.bst | 5 +++ tests/core-payload.sh | 70 +++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100755 tests/core-payload.sh diff --git a/Justfile b/Justfile index 2fb01e1..57c3d96 100644 --- a/Justfile +++ b/Justfile @@ -64,5 +64,8 @@ export: verify-core: tests/core-appliance.sh +verify-payload: + tests/core-payload.sh + verify-cups-patch-chain: tests/cups-patch-chain.sh diff --git a/elements/printer-app/core-stack.bst b/elements/printer-app/core-stack.bst index 808f14d..70cce63 100644 --- a/elements/printer-app/core-stack.bst +++ b/elements/printer-app/core-stack.bst @@ -3,14 +3,19 @@ description: Core Ghostscript Printer Application appliance stack. depends: - printer-app/application.bst + - printer-app/core-payload.bst - printer-app/runtime-files.bst - freedesktop-sdk.bst:public-stacks/runtime-gnu.bst - freedesktop-sdk.bst:components/avahi.bst - freedesktop-sdk.bst:components/ca-certificates.bst - freedesktop-sdk.bst:components/catatonit.bst - freedesktop-sdk.bst:components/cups-daemon-only.bst + - freedesktop-sdk.bst:components/cups-filters.bst - freedesktop-sdk.bst:components/dbus.bst - freedesktop-sdk.bst:components/ghostscript.bst - freedesktop-sdk.bst:components/libcupsfilters.bst - freedesktop-sdk.bst:components/libppd.bst + - freedesktop-sdk.bst:components/mutool.bst + - freedesktop-sdk.bst:components/python3.bst + - freedesktop-sdk.bst:components/xz.bst - freedesktop-sdk.bst:components/tzdata.bst diff --git a/tests/core-payload.sh b/tests/core-payload.sh new file mode 100755 index 0000000..3fb2b77 --- /dev/null +++ b/tests/core-payload.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +set -euo pipefail + +image="ghcr.io/projectbluefin/ghostscript-printer-app:build" +name="ghostscript-printer-app-payload" +port="${PORT:-18010}" +state_dir="$(mktemp -d)" + +cleanup() { + podman rm -f "$name" >/dev/null 2>&1 || true + podman unshare rm -rf "$state_dir" +} +trap cleanup EXIT + +just build + +podman run --rm --entrypoint /usr/bin/bash "$image" -c ' + set -euo pipefail + test -L /usr/lib/ghostscript-printer-app + test "$(readlink /usr/lib/ghostscript-printer-app)" = /usr/lib/cups + test -f /usr/share/ghostscript-printer-app/testpage.ps + test -x /usr/bin/python3 + test -x /usr/bin/xz + + filters=(foomatic-rip gstoraster pdftops rastertoescpx rastertopclx) + for filter in "${filters[@]}"; do + path="/usr/lib/cups/filter/$filter" + test -x "$path" + dependencies="$(ldd "$path")" + [[ "$dependencies" != *"not found"* ]] + done + + application_dependencies="$(ldd /usr/bin/ghostscript-printer-app)" + [[ "$application_dependencies" != *"not found"* ]] + + archives=(cups-filters-ppds foomatic-ppds manufacturer-ppds) + for archive_name in "${archives[@]}"; do + archive="/usr/share/ppd/$archive_name" + test -x "$archive" + mapfile -t entries < <("$archive" list) + ((${#entries[@]} > 0)) + uri="${entries[0]%% *}" + uri="${uri#\"}" + uri="${uri%\"}" + ppd="$("$archive" cat "$uri")" + [[ "$ppd" == *"*PPD-Adobe:"* ]] + done +' + +chmod 0777 "$state_dir" +podman run -d \ + --name "$name" \ + --network host \ + -e PORT="$port" \ + -v "$state_dir:/var/lib/ghostscript-printer-app:Z" \ + "$image" >/dev/null + +for _ in $(seq 1 60); do + http="$(curl --fail --silent --show-error "http://127.0.0.1:${port}/" 2>/dev/null || true)" + https="$(curl --insecure --fail --silent --show-error "https://127.0.0.1:${port}/" 2>/dev/null || true)" + if [[ "$http" == *'Ghostscript Printer Application'* && "$https" == *'Ghostscript Printer Application'* ]]; then + printf 'OK: core driver payload and HTTPS are available\n' + exit 0 + fi + sleep 1 +done + +podman logs "$name" >&2 +printf 'FAIL: HTTP/HTTPS readiness was not reached\n' >&2 +exit 1 From d87ef56e6c45e8f91f4f6fe66f7aa9d0e1b133c9 Mon Sep 17 00:00:00 2001 From: castrojo Date: Wed, 16 Sep 2026 05:26:48 -0400 Subject: [PATCH 6/9] fix: complete core FSDK print path Assisted-by: github-copilot/gpt-5.6-sol via pi --- elements/freedesktop-sdk.bst | 3 + elements/oci/ghostscript-printer-app.bst | 1 + ...stomize-cups-for-printer-application.patch | 9 ++ .../avoid-global-option-lock-after-fork.patch | 61 +++++++++++ tests/core-payload.sh | 103 +++++++++++++++--- tests/socket-sink.py | 16 +++ 6 files changed, 180 insertions(+), 13 deletions(-) create mode 100644 patches/libcupsfilters/avoid-global-option-lock-after-fork.patch create mode 100755 tests/socket-sink.py diff --git a/elements/freedesktop-sdk.bst b/elements/freedesktop-sdk.bst index 2d93ec9..930295e 100644 --- a/elements/freedesktop-sdk.bst +++ b/elements/freedesktop-sdk.bst @@ -8,6 +8,9 @@ sources: - kind: local path: patches/cups directory: patches/ghostscript-printer-app/cups + - kind: local + path: patches/libcupsfilters + directory: patches/ghostscript-printer-app/libcupsfilters - kind: patch_queue path: patches/freedesktop-sdk diff --git a/elements/oci/ghostscript-printer-app.bst b/elements/oci/ghostscript-printer-app.bst index f605f93..9cf7a30 100644 --- a/elements/oci/ghostscript-printer-app.bst +++ b/elements/oci/ghostscript-printer-app.bst @@ -23,6 +23,7 @@ config: >> /layer/etc/group rm -rf /layer/run/dbus /layer/run/avahi-daemon /layer/run/ghostscript-printer-app chmod 0777 /layer/run + install -d -m 1777 /layer/tmp install -d -m 0777 \ /layer/var/lib/ghostscript-printer-app \ /layer/var/lib/ghostscript-printer-app/ppd \ diff --git a/patches/freedesktop-sdk/0001-customize-cups-for-printer-application.patch b/patches/freedesktop-sdk/0001-customize-cups-for-printer-application.patch index 9eaa7a7..38b4857 100644 --- a/patches/freedesktop-sdk/0001-customize-cups-for-printer-application.patch +++ b/patches/freedesktop-sdk/0001-customize-cups-for-printer-application.patch @@ -52,3 +52,12 @@ diff --git a/elements/components/_private/avahi-base.bst b/elements/components/_ --with-distro=none --disable-libevent --disable-qt3 +diff --git a/elements/components/libcupsfilters.bst b/elements/components/libcupsfilters.bst +--- a/elements/components/libcupsfilters.bst ++++ b/elements/components/libcupsfilters.bst +@@ -34,3 +34,5 @@ sources: + exclude: + - '*rc*' + ref: 2.2.1-0-g2a00cf6aa4234e2e0ac91b9844ab8990d04c7089 ++- kind: patch_queue ++ path: patches/ghostscript-printer-app/libcupsfilters diff --git a/patches/libcupsfilters/avoid-global-option-lock-after-fork.patch b/patches/libcupsfilters/avoid-global-option-lock-after-fork.patch new file mode 100644 index 0000000..07fb5b1 --- /dev/null +++ b/patches/libcupsfilters/avoid-global-option-lock-after-fork.patch @@ -0,0 +1,61 @@ +diff --git a/cupsfilters/filter.c b/cupsfilters/filter.c +--- a/cupsfilters/filter.c ++++ b/cupsfilters/filter.c +@@ -972,7 +972,7 @@ + void *parameters) // I - Filter-specific parameters + { + cf_filter_external_t *params = (cf_filter_external_t *)parameters; +- int i; ++ int i, j; + int is_backend = 0; // Do we call a CUPS backend? + int pid, // Process ID of filter + stderrpid, // Process ID for stderr logging process +@@ -1107,12 +1107,35 @@ + // value in the filter data has priority + // + ++ // Do not use cupsAddOption() here. cfFilterExternal() can run in a ++ // cfFilterPOpen() child of a multi-threaded process, where libcups' ++ // inherited global string-pool lock is not safe to acquire. The option ++ // strings outlive this function, so a shallow array is sufficient. ++ all_options = (cups_option_t *)calloc(params->num_options + ++ data->num_options, ++ sizeof(cups_option_t)); ++ if ((params->num_options + data->num_options) > 0 && !all_options) ++ { ++ if (log) ++ log(ld, CF_LOGLEVEL_ERROR, ++ "cfFilterExternal: Memory allocation failed for options"); ++ status = 1; ++ goto out; ++ } ++ + for (i = 0, opt = params->options; i < params->num_options; i ++, opt ++) +- num_all_options = cupsAddOption(opt->name, opt->value, num_all_options, +- &all_options); ++ all_options[num_all_options ++] = *opt; + for (i = 0, opt = data->options; i < data->num_options; i ++, opt ++) +- num_all_options = cupsAddOption(opt->name, opt->value, num_all_options, +- &all_options); ++ { ++ for (j = 0; j < num_all_options; j ++) ++ if (strcasecmp(all_options[j].name, opt->name) == 0) ++ { ++ all_options[j] = *opt; ++ break; ++ } ++ if (j == num_all_options) ++ all_options[num_all_options ++] = *opt; ++ } + + // + // Create command line arguments for the CUPS filter +@@ -1589,7 +1612,7 @@ + out: + if (params->exec_mode < 0) + unlink(tmp_name); +- cupsFreeOptions(num_all_options, all_options); ++ free(all_options); + if (options_str) + free(options_str); + free(argv[0]); diff --git a/tests/core-payload.sh b/tests/core-payload.sh index 3fb2b77..1befa6f 100755 --- a/tests/core-payload.sh +++ b/tests/core-payload.sh @@ -5,10 +5,18 @@ image="ghcr.io/projectbluefin/ghostscript-printer-app:build" name="ghostscript-printer-app-payload" port="${PORT:-18010}" state_dir="$(mktemp -d)" +sink_port="$((port + 1000))" +output_file="$(mktemp)" +sink_pid="" cleanup() { podman rm -f "$name" >/dev/null 2>&1 || true + if [[ -n "$sink_pid" ]]; then + kill "$sink_pid" >/dev/null 2>&1 || true + wait "$sink_pid" 2>/dev/null || true + fi podman unshare rm -rf "$state_dir" + rm -f "$output_file" } trap cleanup EXIT @@ -22,16 +30,30 @@ podman run --rm --entrypoint /usr/bin/bash "$image" -c ' test -x /usr/bin/python3 test -x /usr/bin/xz - filters=(foomatic-rip gstoraster pdftops rastertoescpx rastertopclx) - for filter in "${filters[@]}"; do - path="/usr/lib/cups/filter/$filter" - test -x "$path" - dependencies="$(ldd "$path")" + executables=( + /usr/bin/ghostscript-printer-app + /usr/bin/gs + /usr/bin/python3 + /usr/bin/xz + /usr/lib/cups/backend/dnssd + /usr/lib/cups/backend/snmp + /usr/lib/cups/backend/socket + /usr/lib/cups/backend/usb + /usr/lib/cups/filter/foomatic-rip + /usr/lib/cups/filter/gstoraster + /usr/lib/cups/filter/pdftops + /usr/lib/cups/filter/rastertoescpx + /usr/lib/cups/filter/rastertopclx + ) + for executable in "${executables[@]}"; do + test -x "$executable" + dependencies="$(ldd "$executable")" [[ "$dependencies" != *"not found"* ]] done - application_dependencies="$(ldd /usr/bin/ghostscript-printer-app)" - [[ "$application_dependencies" != *"not found"* ]] + devices="$(gs -h 2>&1)" + [[ "$devices" == *"cups"* ]] + [[ "$devices" == *"pxlcolor"* ]] archives=(cups-filters-ppds foomatic-ppds manufacturer-ppds) for archive_name in "${archives[@]}"; do @@ -45,9 +67,24 @@ podman run --rm --entrypoint /usr/bin/bash "$image" -c ' ppd="$("$archive" cat "$uri")" [[ "$ppd" == *"*PPD-Adobe:"* ]] done -' + /usr/share/ppd/foomatic-ppds cat \ + foomatic-ppds:0/Generic-PCL_6_PCL_XL_Printer-pxlcolor.ppd \ + > /tmp/foomatic.ppd + if ! PPD=/tmp/foomatic.ppd /usr/lib/cups/filter/foomatic-rip \ + 1 nonroot core-conversion 1 "" \ + /usr/share/ghostscript-printer-app/testpage.ps \ + > /tmp/foomatic-output.pcl 2>/tmp/foomatic.log; then + cat /tmp/foomatic.log >&2 + exit 1 + fi + test -s /tmp/foomatic-output.pcl +' chmod 0777 "$state_dir" + +python3 tests/socket-sink.py "$sink_port" "$output_file" & +sink_pid=$! + podman run -d \ --name "$name" \ --network host \ @@ -55,16 +92,56 @@ podman run -d \ -v "$state_dir:/var/lib/ghostscript-printer-app:Z" \ "$image" >/dev/null +ready=0 for _ in $(seq 1 60); do http="$(curl --fail --silent --show-error "http://127.0.0.1:${port}/" 2>/dev/null || true)" https="$(curl --insecure --fail --silent --show-error "https://127.0.0.1:${port}/" 2>/dev/null || true)" if [[ "$http" == *'Ghostscript Printer Application'* && "$https" == *'Ghostscript Printer Application'* ]]; then - printf 'OK: core driver payload and HTTPS are available\n' - exit 0 + ready=1 + break fi sleep 1 done -podman logs "$name" >&2 -printf 'FAIL: HTTP/HTTPS readiness was not reached\n' >&2 -exit 1 +if [[ "$ready" -ne 1 ]]; then + podman logs "$name" >&2 + printf 'FAIL: HTTP/HTTPS readiness was not reached\n' >&2 + exit 1 +fi + +system_uri="ipp://127.0.0.1:${port}/ipp/system" +printer_uri="ipp://127.0.0.1:${port}/ipp/print/core-test" +podman exec "$name" ghostscript-printer-app \ + -u "$system_uri" \ + -d core-test \ + -m generic--pcl-6-pcl-xl-printer--pxlcolor-recommended-en \ + -v "cups:socket://127.0.0.1:${sink_port}" \ + add +podman exec "$name" ghostscript-printer-app \ + -u "$printer_uri" \ + -d core-test \ + submit /usr/share/ghostscript-printer-app/testpage.ps >/dev/null + +for _ in $(seq 1 120); do + [[ -s "$output_file" ]] && break + sleep 0.5 +done +if [[ ! -s "$output_file" ]]; then + podman exec "$name" ghostscript-printer-app -u "$printer_uri" jobs >&2 || true + podman exec "$name" cat /var/lib/ghostscript-printer-app/ghostscript-printer-app.log >&2 || true + printf 'FAIL: print job produced no socket output\n' >&2 + exit 1 +fi + +wait "$sink_pid" +sink_pid="" +python3 -c 'import pathlib, sys; assert pathlib.Path(sys.argv[1]).read_bytes().startswith(b"\x1b%-12345X")' "$output_file" + +jobs="" +for _ in $(seq 1 120); do + jobs="$(podman exec "$name" ghostscript-printer-app -u "$printer_uri" jobs)" + [[ "$jobs" == *"completed"* ]] && break + sleep 0.5 +done +[[ "$jobs" == *"completed"* ]] +printf 'OK: core driver payload, HTTPS, and print conversion are available\n' diff --git a/tests/socket-sink.py b/tests/socket-sink.py new file mode 100755 index 0000000..2be83b9 --- /dev/null +++ b/tests/socket-sink.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +import socket +import sys + +port = int(sys.argv[1]) +output_path = sys.argv[2] + +with socket.socket() as listener: + listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + listener.bind(("127.0.0.1", port)) + listener.listen(1) + connection, _ = listener.accept() + connection.settimeout(30) + with connection, open(output_path, "wb") as output: + while data := connection.recv(65536): + output.write(data) From dac1122feab266ccdea3facad8736e470686cb72 Mon Sep 17 00:00:00 2001 From: castrojo Date: Wed, 16 Sep 2026 05:59:07 -0400 Subject: [PATCH 7/9] fix: exercise the installed test page Assisted-by: github-copilot/gpt-5.6-sol via pi --- ghostscript-printer-app.c | 2 +- .../avoid-global-option-lock-after-fork.patch | 69 ++++++++++++++----- tests/core-payload.sh | 24 +++++-- 3 files changed, 70 insertions(+), 25 deletions(-) diff --git a/ghostscript-printer-app.c b/ghostscript-printer-app.c index 4aca074..08fd41e 100644 --- a/ghostscript-printer-app.c +++ b/ghostscript-printer-app.c @@ -43,7 +43,7 @@ // Test page -#define TESTPAGE "testpage.pdf" +#define TESTPAGE "testpage.ps" // diff --git a/patches/libcupsfilters/avoid-global-option-lock-after-fork.patch b/patches/libcupsfilters/avoid-global-option-lock-after-fork.patch index 07fb5b1..9b1caf0 100644 --- a/patches/libcupsfilters/avoid-global-option-lock-after-fork.patch +++ b/patches/libcupsfilters/avoid-global-option-lock-after-fork.patch @@ -6,14 +6,30 @@ diff --git a/cupsfilters/filter.c b/cupsfilters/filter.c { cf_filter_external_t *params = (cf_filter_external_t *)parameters; - int i; -+ int i, j; ++ int i, j, pass; int is_backend = 0; // Do we call a CUPS backend? int pid, // Process ID of filter stderrpid, // Process ID for stderr logging process -@@ -1107,12 +1107,35 @@ +@@ -996,7 +996,8 @@ + char job_id_str[16], + copies_str[16], + *options_str = NULL; +- cups_option_t *opt; ++ cups_option_t *opt, *source_options; ++ int num_source_options; + int status = 65536; + int wstatus; + cf_logfunc_t log = data->logfunc; +@@ -1107,12 +1108,45 @@ // value in the filter data has priority // +- for (i = 0, opt = params->options; i < params->num_options; i ++, opt ++) +- num_all_options = cupsAddOption(opt->name, opt->value, num_all_options, +- &all_options); +- for (i = 0, opt = data->options; i < data->num_options; i ++, opt ++) +- num_all_options = cupsAddOption(opt->name, opt->value, num_all_options, +- &all_options); + // Do not use cupsAddOption() here. cfFilterExternal() can run in a + // cfFilterPOpen() child of a multi-threaded process, where libcups' + // inherited global string-pool lock is not safe to acquire. The option @@ -30,27 +46,33 @@ diff --git a/cupsfilters/filter.c b/cupsfilters/filter.c + goto out; + } + - for (i = 0, opt = params->options; i < params->num_options; i ++, opt ++) -- num_all_options = cupsAddOption(opt->name, opt->value, num_all_options, -- &all_options); -+ all_options[num_all_options ++] = *opt; - for (i = 0, opt = data->options; i < data->num_options; i ++, opt ++) -- num_all_options = cupsAddOption(opt->name, opt->value, num_all_options, -- &all_options); ++ for (pass = 0; pass < 2; pass ++) + { -+ for (j = 0; j < num_all_options; j ++) -+ if (strcasecmp(all_options[j].name, opt->name) == 0) -+ { -+ all_options[j] = *opt; -+ break; -+ } -+ if (j == num_all_options) -+ all_options[num_all_options ++] = *opt; ++ source_options = pass ? data->options : params->options; ++ num_source_options = pass ? data->num_options : params->num_options; ++ ++ for (i = 0, opt = source_options; i < num_source_options; i ++, opt ++) ++ { ++ if (!opt->name || !opt->name[0] || !opt->value) ++ continue; ++ for (j = 0; j < num_all_options; j ++) ++ if (strcasecmp(all_options[j].name, opt->name) == 0 || ++ ((strcasecmp(opt->name, "cupsPrintQuality") == 0 || ++ strcasecmp(opt->name, "print-quality") == 0) && ++ (strcasecmp(all_options[j].name, "cupsPrintQuality") == 0 || ++ strcasecmp(all_options[j].name, "print-quality") == 0))) ++ { ++ all_options[j] = *opt; ++ break; ++ } ++ if (j == num_all_options) ++ all_options[num_all_options ++] = *opt; ++ } + } // // Create command line arguments for the CUPS filter -@@ -1589,7 +1612,7 @@ +@@ -1589,11 +1623,14 @@ out: if (params->exec_mode < 0) unlink(tmp_name); @@ -58,4 +80,13 @@ diff --git a/cupsfilters/filter.c b/cupsfilters/filter.c + free(all_options); if (options_str) free(options_str); - free(argv[0]); +- free(argv[0]); +- free(argv); ++ if (argv) ++ { ++ free(argv[0]); ++ free(argv); ++ } + for (i = 0; envp[i]; i ++) + free(envp[i]); + free(envp); diff --git a/tests/core-payload.sh b/tests/core-payload.sh index 1befa6f..30cf11a 100755 --- a/tests/core-payload.sh +++ b/tests/core-payload.sh @@ -7,6 +7,7 @@ port="${PORT:-18010}" state_dir="$(mktemp -d)" sink_port="$((port + 1000))" output_file="$(mktemp)" +cookie_file="$(mktemp)" sink_pid="" cleanup() { @@ -16,7 +17,7 @@ cleanup() { wait "$sink_pid" 2>/dev/null || true fi podman unshare rm -rf "$state_dir" - rm -f "$output_file" + rm -f "$output_file" "$cookie_file" } trap cleanup EXIT @@ -36,12 +37,18 @@ podman run --rm --entrypoint /usr/bin/bash "$image" -c ' /usr/bin/python3 /usr/bin/xz /usr/lib/cups/backend/dnssd + /usr/lib/cups/backend/ipp + /usr/lib/cups/backend/ipps + /usr/lib/cups/backend/lpd /usr/lib/cups/backend/snmp /usr/lib/cups/backend/socket /usr/lib/cups/backend/usb /usr/lib/cups/filter/foomatic-rip /usr/lib/cups/filter/gstoraster /usr/lib/cups/filter/pdftops + /usr/lib/cups/filter/rastertoepson + /usr/lib/cups/filter/rastertohp + /usr/lib/cups/filter/rastertolabel /usr/lib/cups/filter/rastertoescpx /usr/lib/cups/filter/rastertopclx ) @@ -117,10 +124,17 @@ podman exec "$name" ghostscript-printer-app \ -m generic--pcl-6-pcl-xl-printer--pxlcolor-recommended-en \ -v "cups:socket://127.0.0.1:${sink_port}" \ add -podman exec "$name" ghostscript-printer-app \ - -u "$printer_uri" \ - -d core-test \ - submit /usr/share/ghostscript-printer-app/testpage.ps >/dev/null +printer_page="$(curl --fail --silent --show-error \ + --cookie-jar "$cookie_file" \ + "http://127.0.0.1:${port}/core-test/")" +session="${printer_page#*name=\"session\" value=\"}" +session="${session%%\"*}" +[[ -n "$session" && "$session" != "$printer_page" ]] +curl --fail --silent --show-error \ + --cookie "$cookie_file" \ + --data-urlencode "session=$session" \ + --data 'action=print-test-page' \ + "http://127.0.0.1:${port}/core-test/" >/dev/null for _ in $(seq 1 120); do [[ -s "$output_file" ]] && break From 567c72240943f5368917435c402619af2ebb9623 Mon Sep 17 00:00:00 2001 From: castrojo Date: Wed, 16 Sep 2026 05:59:22 -0400 Subject: [PATCH 8/9] docs: record core payload implementation Assisted-by: github-copilot/gpt-5.6-sol via pi --- .../plans/2026-09-16-fsdk-core-payload.md | 88 ++++++++++++------- 1 file changed, 55 insertions(+), 33 deletions(-) diff --git a/docs/superpowers/plans/2026-09-16-fsdk-core-payload.md b/docs/superpowers/plans/2026-09-16-fsdk-core-payload.md index 44f15e3..da8ea04 100644 --- a/docs/superpowers/plans/2026-09-16-fsdk-core-payload.md +++ b/docs/superpowers/plans/2026-09-16-fsdk-core-payload.md @@ -6,7 +6,7 @@ **Architecture:** Reuse FSDK's existing printing artifacts without rebuilding or duplicating them. Build only pyppd and the Foomatic XML compiler that FSDK does not ship, generate the same three self-extracting PPD archives as the current OCI contract, and compose those archives with FSDK's filters plus their measured runtime dependencies. Verify the exported image, not BuildStream source text. -**Tech Stack:** BuildStream 2, freedesktop-sdk 26.08rc.1, cups-filters 2.0.1, Foomatic DB 20240504, foomatic-db-engine 4.1.0, pyppd 1.1.0, Python 3, xz, Podman, socat. +**Tech Stack:** BuildStream 2, freedesktop-sdk 26.08rc.1, cups-filters 2.0.1, Foomatic DB 20240504, foomatic-db-engine 4.1.0, pyppd 1.1.0, Python 3, xz, Podman. ## Global Constraints @@ -28,9 +28,9 @@ **Interfaces:** - Consumes: FSDK Python, Perl, XML::Parser, curl, file, gzip, cups-filters, and Foomatic database artifacts. -- Produces: build-time `/usr/bin/pyppd` and `/usr/sbin/foomatic-compiledb` commands. +- Produces: build-time `/usr/bin/pyppd` and `/usr/bin/foomatic-compiledb` commands. -- [ ] **Step 1: Add the pyppd element** +- [x] **Step 1: Add the pyppd element** Create `elements/printer-app/pyppd.bst`: @@ -52,11 +52,11 @@ sources: ref: release-1-1-0-0-g29ccf6cf85781315a696774e7458a2f1f61aac57 ``` -- [ ] **Step 2: Make Foomatic's XML-only path independent of DBI** +- [x] **Step 2: Make Foomatic's XML-only path independent of DBI** Create `patches/foomatic-db-engine/xml-database-without-dbi.patch` that removes the unconditional `use DBI;` from `lib/Foomatic/DB.pm` and adds `require DBI;` only inside the MySQL and SQLite branches of `connect_to_mysql_db()`. The XML database path used by `foomatic-compiledb` must not require an unavailable SQL driver. -- [ ] **Step 3: Add the Foomatic engine element** +- [x] **Step 3: Add the Foomatic engine element** Create `elements/printer-app/foomatic-db-engine.bst` with immutable commit `e4e7b9cd28ba160428f82bc5234559d1f50e5c42`, the DBI patch queue, and these build dependencies: @@ -82,10 +82,12 @@ config: - PERL_INSTALLDIRS=vendor ./configure --prefix=/usr --libdir="%{libdir}" - make -j1 install-commands: - - make DESTDIR="%{install-root}" install + - make DESTDIR="$PWD/full-install" install + - install -D -m 0755 full-install/usr/sbin/foomatic-compiledb "%{install-root}/usr/bin/foomatic-compiledb" + - mkdir -p "%{install-root}/usr/lib" && cp -a full-install/usr/lib/perl5 "%{install-root}/usr/lib/" ``` -- [ ] **Step 4: Verify both tools build** +- [x] **Step 4: Verify both tools build** Run: @@ -94,9 +96,9 @@ just bst build printer-app/pyppd.bst printer-app/foomatic-db-engine.bst just bst artifact list-contents printer-app/pyppd.bst printer-app/foomatic-db-engine.bst ``` -Expected: the artifacts contain `/usr/bin/pyppd` and `/usr/sbin/foomatic-compiledb`; no second CUPS library is introduced. +Expected: the artifacts contain `/usr/bin/pyppd` and `/usr/bin/foomatic-compiledb`; no second CUPS library is introduced. -- [ ] **Step 5: Commit the tooling** +- [x] **Step 5: Commit the tooling** ```bash git add elements/printer-app/pyppd.bst elements/printer-app/foomatic-db-engine.bst patches/foomatic-db-engine/xml-database-without-dbi.patch @@ -112,11 +114,11 @@ git commit -m "build: add core PPD generation tools" -m "Assisted-by: github-cop - Consumes: FSDK cups-filters PPDs and Foomatic XML/manufacturer data plus the Task 1 generators. - Produces: executable `/usr/share/ppd/cups-filters-ppds`, `/usr/share/ppd/foomatic-ppds`, and `/usr/share/ppd/manufacturer-ppds` archives. -- [ ] **Step 1: Stage source payloads and generators** +- [x] **Step 1: Stage source payloads and generators** Create a `manual` element with build dependencies on `printer-app/pyppd.bst`, `printer-app/foomatic-db-engine.bst`, `freedesktop-sdk.bst:components/cups-filters.bst`, `freedesktop-sdk.bst:components/foomatic-db.bst`, and `freedesktop-sdk.bst:public-stacks/runtime-gnu.bst`. -- [ ] **Step 2: Generate the cups-filters archive** +- [x] **Step 2: Generate the cups-filters archive** The build commands must copy `/usr/share/ppd/cupsfilters` to `payload/cupsfilters` and run: @@ -124,7 +126,7 @@ The build commands must copy `/usr/share/ppd/cupsfilters` to `payload/cupsfilter pyppd -v -o cups-filters-ppds payload/cupsfilters ``` -- [ ] **Step 3: Generate the Foomatic archives** +- [x] **Step 3: Generate the Foomatic archives** Copy `/usr/share/foomatic` to `payload/foomatic`, remove PostScript manufacturer PPDs and the unsupported driver XML files carried by the current Snap contract: @@ -141,11 +143,11 @@ pyppd -v -o foomatic-ppds payload/foomatic-ppds pyppd -v -o manufacturer-ppds payload/foomatic/db/source/PPD ``` -- [ ] **Step 4: Install only generated archives** +- [x] **Step 4: Install only generated archives** Install the three executable archives with mode `0755` beneath `%{install-root}/usr/share/ppd`. Do not carry the Foomatic compiler, raw XML database, or pyppd package into this artifact. -- [ ] **Step 5: Build and inspect the payload artifact** +- [x] **Step 5: Build and inspect the payload artifact** Run: @@ -156,7 +158,7 @@ just bst artifact list-contents printer-app/core-payload.bst Expected: exactly the three executable archive files appear under `/usr/share/ppd`. -- [ ] **Step 6: Commit the generated-payload element** +- [x] **Step 6: Commit the generated-payload element** ```bash git add elements/printer-app/core-payload.bst @@ -174,17 +176,29 @@ git commit -m "build: generate core PPD archives" -m "Assisted-by: github-copilo - Consumes: `printer-app/core-payload.bst` and FSDK runtime components. - Produces: `just verify-payload`, proving driver archives, filters, interpreters, HTTPS, and shared-library closure inside the exported image. -- [ ] **Step 1: Write the failing image-level payload check** +- [x] **Step 1: Write the failing image-level payload check** Create `tests/core-payload.sh`. It must run `just build`, then assert from the image that: ```text /usr/lib/ghostscript-printer-app -> /usr/lib/cups +/usr/lib/cups/backend/dnssd +/usr/lib/cups/backend/ipp +/usr/lib/cups/backend/ipps +/usr/lib/cups/backend/lpd +/usr/lib/cups/backend/snmp +/usr/lib/cups/backend/socket +/usr/lib/cups/backend/usb /usr/lib/cups/filter/foomatic-rip /usr/lib/cups/filter/gstoraster /usr/lib/cups/filter/pdftops /usr/lib/cups/filter/rastertoescpx /usr/lib/cups/filter/rastertopclx +/usr/lib/cups/filter/rastertoepson +/usr/lib/cups/filter/rastertohp +/usr/lib/cups/filter/rastertolabel +/usr/bin/ghostscript-printer-app +/usr/bin/gs /usr/share/ghostscript-printer-app/testpage.ps /usr/share/ppd/cups-filters-ppds /usr/share/ppd/foomatic-ppds @@ -195,7 +209,7 @@ Create `tests/core-payload.sh`. It must run `just build`, then assert from the i For each archive, run `list`, capture its first URI, run `cat `, and require the extracted text to contain `*PPD-Adobe:`. Run this check now; it must fail because the payload is not yet composed. -- [ ] **Step 2: Add runtime components** +- [x] **Step 2: Add runtime components** Add these dependencies to `core-stack.bst`: @@ -209,15 +223,15 @@ Add these dependencies to `core-stack.bst`: Keep the existing Ghostscript, CUPS, libppd, and libcupsfilters dependencies. -- [ ] **Step 3: Verify HTTP and HTTPS** +- [x] **Step 3: Verify HTTP and HTTPS** The payload test starts the real image on host networking, waits for `Ghostscript Printer Application` over HTTP, and requires the same title over HTTPS with `curl --insecure`. -- [ ] **Step 4: Verify ELF closure** +- [x] **Step 4: Verify ELF closure** Inside the image, run `ldd` for the application and each core filter listed in Step 1. Fail if any output contains `not found`. -- [ ] **Step 5: Add the verification command** +- [x] **Step 5: Add the verification command** Add to `Justfile`: @@ -226,7 +240,7 @@ verify-payload: tests/core-payload.sh ``` -- [ ] **Step 6: Run and commit the runtime checks** +- [x] **Step 6: Run and commit the runtime checks** Run: @@ -245,35 +259,43 @@ git commit -m "test: verify core driver payload" -m "Assisted-by: github-copilot **Files:** - Modify: `tests/core-payload.sh` +- Modify: `ghostscript-printer-app.c` +- Modify: `elements/freedesktop-sdk.bst` +- Modify: `patches/freedesktop-sdk/0001-customize-cups-for-printer-application.patch` +- Create: `patches/libcupsfilters/avoid-global-option-lock-after-fork.patch` +- Create: `tests/socket-sink.py` **Interfaces:** -- Consumes: running Printer Application, `cups-filters-ppds:Generic-PDF_Printer-PDF.ppd`, repository test page, and host `socat`. -- Produces: non-empty printer-language output captured from a real submitted job. +- Consumes: running Printer Application, the generated Generic PCL 6/PCL XL Foomatic driver, repository test page, and a host Python socket sink. +- Produces: non-empty PCL XL output captured from a real submitted job. -- [ ] **Step 1: Add the socket-backed printer test** +- [x] **Step 1: Add the socket-backed printer test** Start a one-shot host sink before the container: ```bash -socat -u "TCP-LISTEN:${sink_port},reuseaddr" "OPEN:${output_file},creat,trunc" & +python3 tests/socket-sink.py "$sink_port" "$output_file" & sink_pid=$! ``` -After HTTP/HTTPS readiness, run inside the image: +After HTTP/HTTPS readiness, add a Generic PCL 6/PCL XL printer and invoke its built-in test-page action: ```bash -ghostscript-printer-app -u "ipp://127.0.0.1:${port}/ipp/system" add core-test \ - -m cups-filters-ppds:Generic-PDF_Printer-PDF.ppd \ - -v "cups:socket://127.0.0.1:${sink_port}" ghostscript-printer-app -u "ipp://127.0.0.1:${port}/ipp/system" \ - -d core-test submit /usr/share/ghostscript-printer-app/testpage.ps + -d core-test \ + -m generic--pcl-6-pcl-xl-printer--pxlcolor-recommended-en \ + -v "cups:socket://127.0.0.1:${sink_port}" add +curl --data 'action=print-test-page' \ + "http://127.0.0.1:${port}/core-test/" ``` -- [ ] **Step 2: Assert conversion output** +The application must request the installed `testpage.ps`. Patch FSDK's libcupsfilters 2.2.1 so `cfFilterExternal()` merges borrowed option records without calling libcups' globally locked string pool after `cfFilterPOpen()` forks. Preserve case-insensitive replacement and the `cupsPrintQuality`/`print-quality` alias rule. -Poll until `${output_file}` is non-empty, then require its first five bytes to be `%PDF-`. A successful CLI return without captured output is a failure. +- [x] **Step 2: Assert conversion output** -- [ ] **Step 3: Re-run all slice gates** +Poll until `${output_file}` is non-empty, wait for the sink and print job to complete, then require the PJL/PCL stream to start with `ESC%-12345X`. A successful CLI return without captured output is a failure. + +- [x] **Step 3: Re-run all slice gates** Run: From eb211e46a3834e965f6d4c6017186f75e27cd045 Mon Sep 17 00:00:00 2001 From: castrojo Date: Wed, 16 Sep 2026 06:00:37 -0400 Subject: [PATCH 9/9] docs: complete core payload plan Assisted-by: github-copilot/gpt-5.6-sol via pi --- docs/superpowers/plans/2026-09-16-fsdk-core-payload.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/superpowers/plans/2026-09-16-fsdk-core-payload.md b/docs/superpowers/plans/2026-09-16-fsdk-core-payload.md index da8ea04..8883bc3 100644 --- a/docs/superpowers/plans/2026-09-16-fsdk-core-payload.md +++ b/docs/superpowers/plans/2026-09-16-fsdk-core-payload.md @@ -311,6 +311,6 @@ git diff --check Expected: every command succeeds. -- [ ] **Step 4: Resolve and publish the slice** +- [x] **Step 4: Resolve and publish the slice** Set `.scratch/fsdk-container-modernization/issues/03-serve-core-ppd-filter-payload.md` to `resolved`, record exact successful commands and commit IDs, commit this completed plan, push `feat/fsdk-core-payload`, and open a pull request with base `feat/fsdk-core-app`. Do not publish an OCI release.