packPNG v1.8b - audit-driven bugfix: exit code on invalid magic
v1.8b - bugfix: exit non-zero when input has invalid magic (audit-driven)
Second bugfix on the v1.8 line, found by an 8-phase Windows 11 audit
harness run against the v1.8a binary on a real Windows 11 box
(DESKTOP-6R6L470, build 26200, PowerShell 5.1.26100). v1.8a passed 7
of 8 phases cleanly - the holdout was Phase 4 (malformed inputs).
The bug
Files with invalid magic - a 0-byte file named empty.png, or random
bytes named garbage.png - were silently skipped with a warning
but the program exited 0, lying to any script checking exit status.
packPNG a empty.png -> "skipped 1 file(s) with invalid magic" exit 0 (wrong)
packPNG a garbage.png -> "skipped 1 file(s) with invalid magic" exit 0 (wrong)
packPNG a missing.png -> "ERROR missing file: missing.png" exit 1 (correct)
Cause
The legacy filter near the end of main() accumulates unprocessable
inputs into three buckets:
skip_missing(file does not exist)skip_bad_magic(file exists but is not a PNG / .ppg / .jng)skip_compress/skip_decompress(wrong direction for the chosen
subcommand)
skip_missing has always bumped g_errors per file - the program's
final return errs ? 1 : 0 then properly exits non-zero. skip_bad_magic
was missing the same line, so when every input had bad magic the
program exited 0 even though nothing was processed.
The fix
One line. After the verbose-skip loop for skip_bad_magic, mirror the
existing skip_missing bookkeeping:
g_errors += (int)skip_bad_magic.size();Mixed batches (some valid + some bad-magic) now also exit non-zero,
matching how Unix cp / tar / gzip behave when any source fails.
This is stricter than v1.8a behaviour. If you have a script that
intentionally feeds packPNG a directory containing non-PNG files
and was relying on exit 0, switch to per-file invocation or pre-filter
the input list.
Audit results (Windows 11, v1.8b)
| Phase | What it covers | v1.8a | v1.8b |
|---|---|---|---|
| 1. PngSuite full corpus roundtrip | 162 valid files (color_type x bit_depth x interlace) | 162/162 | 162/162 |
| 2. Path edge cases | space, paren, &, [], ;, %, #, +, ' | 9/10 (audit-script bug) | 10/10 |
| 3. Mode coverage | mode 0 default + mode 2 (-ldf) |
2/2 | 2/2 |
| 4. Malformed inputs | 0-byte / garbage / truncated / sig-only / corrupt CRC | 3/5 | 5/5 |
5. -r recurse + -fs |
3-deep dir tree, structure preserve | 2/2 | 2/2 |
6. Output collisions + -od |
dup basename rename, auto-mkdir | 2/2 | 2/2 |
| 7. Big-file stress | 3.42 MB random RGB pack+verify | PASS (0.25s) | PASS (0.25s) |
| 8. Multi-thread determinism | -th1 == -th4 == -th0 SHA256 |
PASS | PASS |
The Phase 2 long-path test (>260 char total) is consistently SKIPPED
because the host filesystem rejects creating such a path before
packPNG ever runs - that is a system / LongPathsEnabled group-policy
setting, not a packPNG bug. The manifest already declares
longPathAware=true.
What didn't change
- Wire format unchanged:
.ppgfiles written by v1.8 / v1.8a decode
under v1.8b and vice versa. BothTCIP(PNG/APNG) andTCIJ(JNG)
magics are intact. - All v1.8a behaviours preserved - the wait_and_return helper for
the help/error console-close fix (encode.su feedback) still works. - Manifest
assemblyIdentity@versionbumped1.8.0.1->1.8.0.2.
Acknowledgements
Thanks to the encode.su community for the v1.8a console paper-cut
report and the YCoCg-R / STRATA pointer that prompted the v1.9
prototype work earlier in this cycle (the latter didn't pan out in
this pipeline, but useful negative evidence).
Binaries
packPNG-linux-x86_64-v1.8b- Linux x86-64, dynamically linked.packPNG-windows-x86_64-full-v1.8b.exe- Windows x86-64, statically
linked, mingw-w64 cross-compiled, UTF-8 active-code-page manifest.
🤖 Generated with Claude Code