Skip to content

Commit 0693f57

Browse files
abrignoniclaude
andcommitted
chore: re-vendor qnxprobe 1.34, which says why a zstd SquashFS lists nothing
On a Python without zstd, qnxprobe 1.34 carries the reason as the volume's note, and this seeker prints a volume's note on its line in the run log. The raw-image doc said the log gave no reason; it now says the log names it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent a6191a3 commit 0693f57

3 files changed

Lines changed: 59 additions & 9 deletions

File tree

‎admin/docs/raw_image_input.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ for it, so no report field carries a zone the evidence never had.
8585
- zstd-compressed SquashFS and UBIFS need Python 3.14 or later
8686
(`compression.zstd`), which the builds made by `test_builds.yml` use. Run from
8787
source on an older Python, a zstd SquashFS is listed as a volume with no files,
88-
and the run log does not say why; a zstd-compressed UBIFS file is listed and
89-
not staged, and the log names the reason.
88+
and the run log gives the reason on that volume's line; a zstd-compressed UBIFS
89+
file is listed and not staged, and the log names the reason.
9090
- An encrypted volume (Android file-based encryption, iOS data protection,
9191
FileVault, BitLocker) reads, but its names or contents are ciphertext.
9292

‎scripts/vendor/qnxprobe.py‎

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
except ImportError:
4444
ewfprobe = None
4545

46-
QNXPROBE_VERSION = "1.33"
46+
QNXPROBE_VERSION = "1.34"
4747

4848
QNX6_MAGIC = 0x68191122
4949
BOOTBLOCK_SIZE = 0x2000
@@ -5818,6 +5818,13 @@ def lz4_block_decompress(src, limit=None):
58185818
return bytes(out)
58195819

58205820

5821+
# What a zstd SquashFS says on a Python with no zstd. identify_fs() reports it
5822+
# among its lines and the walker carries it as its note, which volumes() passes
5823+
# on, so a listing that comes back empty says why wherever the volume is shown.
5824+
SQUASHFS_NO_ZSTD_NOTE = ("zstd compressed; this Python has no zstd (3.14 adds it), so "
5825+
"the listing may be short and no file can be read")
5826+
5827+
58215828
def _zstd_module():
58225829
try:
58235830
from compression import zstd # Python 3.14 and later
@@ -6078,6 +6085,15 @@ def readlink(self, ref):
60786085
ino = self.inode(ref)
60796086
return ino.get("target", b"").decode("utf-8", "surrogateescape")
60806087

6088+
@property
6089+
def note(self):
6090+
"""Why this volume cannot be read on this Python, or None. listdir()
6091+
answers an empty list when its directory table cannot be decompressed,
6092+
so volumes() carries this beside the volume to say why."""
6093+
if self.comp == 6 and _zstd_module() is None:
6094+
return SQUASHFS_NO_ZSTD_NOTE
6095+
return None
6096+
60816097
def listdir(self, ref):
60826098
try:
60836099
ino = self.inode(ref)
@@ -6201,8 +6217,7 @@ def identify_squashfs(fh, base, size=None):
62016217
lines.append(f"note the image records {human(w.bytes_used)} but the region "
62026218
f"holds {human(size)}, so its end is missing")
62036219
if w.comp == 6 and _zstd_module() is None:
6204-
lines.append("note zstd compressed; this Python has no zstd (3.14 adds "
6205-
"it), so the listing may be short and no file can be read")
6220+
lines.append("note " + SQUASHFS_NO_ZSTD_NOTE)
62066221
if not root_ok:
62076222
if w.comp == 6 and _zstd_module() is None:
62086223
return "squashfs", lines
@@ -9343,6 +9358,12 @@ def volumes(fh, size=None):
93439358
vol["note"] = f"contents not read: {exc}"
93449359
except Exception as exc:
93459360
vol["note"] = f"could not walk this filesystem: {exc}"
9361+
# A walker that is built but cannot read its volume here says why, for
9362+
# a zstd SquashFS on a Python without zstd, whose listing comes back
9363+
# empty rather than raising.
9364+
wnote = getattr(vol.get("walker"), "note", None)
9365+
if wnote and "note" not in vol:
9366+
vol["note"] = wnote
93469367
out.append(vol)
93479368
return out
93489369

@@ -13039,6 +13060,35 @@ def _vol_view(path):
1303913060
"--oob while mounted, against the kernel's own read-back"),
1304013061
("ubi-nand-history", "ubi", "ubi-nand.history.kernel.sha256", None, "stat", "",
1304113062
(), "a static UBI volume the kernel wrote with ubiupdatevol, from the same image")]
13063+
# A zstd SquashFS on a Python with no zstd lists nothing, since its
13064+
# directory tables are compressed too, and the walker answers an empty
13065+
# listing rather than raising. volumes() has to say why beside the
13066+
# volume, or it reads as an empty filesystem. The missing module is
13067+
# simulated, so this runs on every Python.
13068+
zst_img = os.path.join(fx, "squashfs-zstd.img.gz")
13069+
if not os.path.isfile(zst_img):
13070+
print(" [SKIP] squashfs-zstd is not beside this script, so the note a "
13071+
"Python without zstd gives was not checked")
13072+
else:
13073+
with gzip.open(zst_img, "rb") as gz:
13074+
zst_bytes = gz.read()
13075+
zst_globals = globals()
13076+
zst_real = zst_globals["_zstd_module"]
13077+
try:
13078+
zst_globals["_zstd_module"] = lambda: None
13079+
zst_hidden = volumes(io.BytesIO(zst_bytes), len(zst_bytes))
13080+
finally:
13081+
zst_globals["_zstd_module"] = zst_real
13082+
zst_shown = volumes(io.BytesIO(zst_bytes), len(zst_bytes))
13083+
zst_cond = (len(zst_hidden) == 1 and zst_hidden[0]["kind"] == "squashfs"
13084+
and zst_hidden[0].get("note") == SQUASHFS_NO_ZSTD_NOTE
13085+
and len(zst_shown) == 1
13086+
and (zst_shown[0].get("note") is None) == (zst_real() is not None))
13087+
if not zst_cond:
13088+
ok = False
13089+
print(f" [{'PASS' if zst_cond else 'FAIL'}] a zstd SquashFS on a Python "
13090+
f"without zstd carries a note saying why it lists nothing, and none "
13091+
f"where zstd is present")
1304213092
for stem, want_kind, hashes, listing, style, prefix, loose, label in sq + jf + ub + ya + kh:
1304313093
img = os.path.join(fx, stem + ".img.gz")
1304413094
if not (os.path.isfile(img) and os.path.isfile(os.path.join(fx, hashes))):

‎scripts/vendor/vendored.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
{
44
"path": "scripts/vendor/qnxprobe.py",
55
"name": "qnxprobe",
6-
"version": "1.33",
6+
"version": "1.34",
77
"upstream": "https://github.com/abrignoni/qnxprobe",
88
"upstream_file": "qnxprobe.py",
9-
"commit": "e9bfb69e2d1556df67d5f1f8301dcf6f9a351749",
10-
"commit_date": "2026-09-25T15:29:51-04:00",
11-
"sha256": "15924af48c61fffb3fbbb1a7a8547f102341263d27970c58f71fba98df7bcdc8",
9+
"commit": "fc74ca829c2773879243b326f2262c29f462df3a",
10+
"commit_date": "2026-09-25T16:01:32-04:00",
11+
"sha256": "ab0f681313bc305f3662d4421199f45da357da3becf90b28696a158fbaae2710",
1212
"licence": "MIT",
1313
"licence_file": "scripts/vendor/LICENSE-qnxprobe",
1414
"note": "Copied verbatim. Fix upstream and re-vendor; edits here are reverted by the next sync."

0 commit comments

Comments
 (0)