|
43 | 43 | except ImportError: |
44 | 44 | ewfprobe = None |
45 | 45 |
|
46 | | -QNXPROBE_VERSION = "1.33" |
| 46 | +QNXPROBE_VERSION = "1.34" |
47 | 47 |
|
48 | 48 | QNX6_MAGIC = 0x68191122 |
49 | 49 | BOOTBLOCK_SIZE = 0x2000 |
@@ -5818,6 +5818,13 @@ def lz4_block_decompress(src, limit=None): |
5818 | 5818 | return bytes(out) |
5819 | 5819 |
|
5820 | 5820 |
|
| 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 | + |
5821 | 5828 | def _zstd_module(): |
5822 | 5829 | try: |
5823 | 5830 | from compression import zstd # Python 3.14 and later |
@@ -6078,6 +6085,15 @@ def readlink(self, ref): |
6078 | 6085 | ino = self.inode(ref) |
6079 | 6086 | return ino.get("target", b"").decode("utf-8", "surrogateescape") |
6080 | 6087 |
|
| 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 | + |
6081 | 6097 | def listdir(self, ref): |
6082 | 6098 | try: |
6083 | 6099 | ino = self.inode(ref) |
@@ -6201,8 +6217,7 @@ def identify_squashfs(fh, base, size=None): |
6201 | 6217 | lines.append(f"note the image records {human(w.bytes_used)} but the region " |
6202 | 6218 | f"holds {human(size)}, so its end is missing") |
6203 | 6219 | 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) |
6206 | 6221 | if not root_ok: |
6207 | 6222 | if w.comp == 6 and _zstd_module() is None: |
6208 | 6223 | return "squashfs", lines |
@@ -9343,6 +9358,12 @@ def volumes(fh, size=None): |
9343 | 9358 | vol["note"] = f"contents not read: {exc}" |
9344 | 9359 | except Exception as exc: |
9345 | 9360 | 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 |
9346 | 9367 | out.append(vol) |
9347 | 9368 | return out |
9348 | 9369 |
|
@@ -13039,6 +13060,35 @@ def _vol_view(path): |
13039 | 13060 | "--oob while mounted, against the kernel's own read-back"), |
13040 | 13061 | ("ubi-nand-history", "ubi", "ubi-nand.history.kernel.sha256", None, "stat", "", |
13041 | 13062 | (), "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") |
13042 | 13092 | for stem, want_kind, hashes, listing, style, prefix, loose, label in sq + jf + ub + ya + kh: |
13043 | 13093 | img = os.path.join(fx, stem + ".img.gz") |
13044 | 13094 | if not (os.path.isfile(img) and os.path.isfile(os.path.join(fx, hashes))): |
|
0 commit comments