fix(linux/build): byte-patch Rock Ridge TF dates after xorriso (M1.1 iter37)
Run #4284's diagnostic (iter36) confirmed xorriso ignores every date-setting command we throw at it for the node it just -updated: flag=0x0e → CREATION + MODIFICATION + ACCESS (short form) CREATION ✅ (set from source file btime via touch -d): 7e 05 08 00 2c 3a 00 (= SOURCE_DATE_EPOCH) MODIFICATION ❌ (still wall-clock): A=7e 05 08 01 02 2c 00 B=7e 05 08 01 12 33 00 ACCESS ❌ (still wall-clock): A=7e 05 08 01 02 2c 00 B=7e 05 08 01 12 32 00 Tested across iters 34-36: * `-alter_date_r all "=N" /` — only fixed CREATION (b) * `-alter_date all "=N" path` after -update — same * `-volume_date c m x f u s "=N"` — volume-level only * `touch -d "@N" "${new_sqfs}"` before — fixed CREATION via btime * various orderings, with/without `--` terminators None override xorriso's wall-clock stamping of MOD/ACCESS at -commit. Concede that fight and just patch the bytes after xorriso writes the ISO. We KNOW exactly what's wrong — the TF entry for /live/filesystem.squashfs has its CREATION slot correct (= 7-byte ISO9660 short-form encoding of SOURCE_DATE_EPOCH) but MODIFICATION and ACCESS still hold the post-process commit time. So copy the 7 CREATION bytes over the 7 MODIFICATION bytes and 7 ACCESS bytes. The patcher (embedded Python, since silvermetal-builder ships python3): * Finds every TF entry header (`54 46 1a 01 0e`) near the "filesystem.squashfs" NM tag (96-byte window — anchors both ends so we don't touch some other file's TF entry). * Copies CREATION (offset +5..+12) onto MODIFICATION (+12..+19) and ACCESS (+19..+26). * Skips entries already correct (so re-running is a no-op). * Reports how many entries were patched. This is surgical: only the entry we know is broken, and only when its MOD/ACCESS actually differ from the (known-correct) CREATION. If the next run still drifts, the diagnostic byte-offset will tell us where the residual leak is (almost certainly in some volume descriptor field we haven't covered yet — at which point we extend the patcher). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -292,6 +292,62 @@ post_process_for_reproducibility() {
|
||||
sudo --non-interactive mv -f "${new_iso}" "${iso_file}"
|
||||
sudo --non-interactive rm -f "${new_sqfs}"
|
||||
|
||||
# Post-write byte patch: xorriso's -update writes wall-clock
|
||||
# mtime/atime into Rock Ridge TF directory entries at -commit time
|
||||
# regardless of -alter_date / -alter_date_r / -find set_to_mtime /
|
||||
# touching the source file's mtime (verified across iters 34-36).
|
||||
# The leak is contained to the TF entry of the one node we
|
||||
# replaced (/live/filesystem.squashfs) — every other timestamp in
|
||||
# the ISO was already SDE before -update, and -update doesn't touch
|
||||
# them.
|
||||
#
|
||||
# We have the SDE-derived 7-byte ISO9660 short-form date in the
|
||||
# CREATION slot of that TF entry already (because we touched the
|
||||
# source file's btime). We just copy those 7 bytes over the
|
||||
# MODIFICATION (next 7 bytes) and ACCESS (7 after that) slots.
|
||||
#
|
||||
# The TF entry header is `54 46 1a 01 0e` ("TF" length=26 ver=1
|
||||
# flags=0x0e: CREATION+MODIFICATION+ACCESS, short form). The
|
||||
# filename "filesystem.squashfs" follows immediately after the NM
|
||||
# Rock Ridge entry that follows the TF dates. So a unique 5-byte
|
||||
# marker plus the filename anchors the location.
|
||||
echo "post-process: byte-patching Rock Ridge TF dates on /live/filesystem.squashfs"
|
||||
sudo --non-interactive python3 - "${iso_file}" <<'PYEOF'
|
||||
import sys, struct
|
||||
path = sys.argv[1]
|
||||
TF_HDR = b'TF\x1a\x01\x0e' # "TF" length=26 ver=1 flags=0x0e
|
||||
NAME_TAG = b'filesystem.squashfs' # appears in the NM Rock Ridge entry
|
||||
with open(path, 'r+b') as f:
|
||||
data = f.read()
|
||||
# Find TF entries near a "filesystem.squashfs" NM tag. We don't trust
|
||||
# any single anchor; require both within a 96-byte window so we don't
|
||||
# accidentally rewrite some other TF entry that happens to be near
|
||||
# the NM tag of a different file.
|
||||
patched = 0
|
||||
i = 0
|
||||
while True:
|
||||
j = data.find(TF_HDR, i)
|
||||
if j < 0:
|
||||
break
|
||||
# Look for the filename within ~96 bytes after this TF header.
|
||||
window = data[j:j + 96]
|
||||
if NAME_TAG in window:
|
||||
# Date 1 starts at j + 5; copy its 7 bytes onto Date 2 (j+12)
|
||||
# and Date 3 (j+19).
|
||||
creation = data[j + 5:j + 12]
|
||||
if creation[3:7] != b'\x00\x00\x00\x00': # sanity: not all zeroes
|
||||
# only patch if MOD or ACCESS actually differs from CREATION
|
||||
if data[j + 12:j + 19] != creation or data[j + 19:j + 26] != creation:
|
||||
f.seek(j + 12)
|
||||
f.write(creation)
|
||||
f.seek(j + 19)
|
||||
f.write(creation)
|
||||
patched += 1
|
||||
print(f' patched TF at offset {j} (creation={creation.hex()})')
|
||||
i = j + 1
|
||||
print(f'post-process: byte-patcher fixed {patched} TF entr{"y" if patched == 1 else "ies"}')
|
||||
PYEOF
|
||||
|
||||
echo "post-process: ISO rebuilt with reproducible squashfs"
|
||||
sha256sum "${iso_file}"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user