Skip to content

Round2 newfilesystems - #83

Merged
danifunker merged 22 commits into
mainfrom
round2-newfilesystems
Sep 12, 2026
Merged

danifunker merged 22 commits into
mainfrom
round2-newfilesystems

Conversation

@danifunker

Copy link
Copy Markdown
Owner

No description provided.

danifunker and others added 22 commits September 10, 2026 11:58
A Mac OS X Server 1.x disk is an APM disk, but its UFS slice is not a bare
filesystem: Rhapsody kept NeXT's disk label and writes a `dlV3` copy into the
slice's own first sectors, so the filesystem starts past that label's front
porch. Addressing the slice's first byte found no superblock, and browse fell
all the way through to the carve view — a 2 GiB `whole-disk.img` plus 1200
`carved-blk*.txt` entries where /System, /usr and mach_kernel should be.

Probe for the label on open and address the filesystem where it says. The
label records `p_base` from the disk origin even when nested, so its own
answer is already absolute; a whole-disk NeXTSTEP label counts from its own
block 0 with a zero container offset, and one expression covers both.

Verified on a Mac OS X Server 1.2 PowerPC install: browse, extract and fsck
(21135 files / 3394 dirs, clean).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A NeXT MO image is the raw cartridge, not the drive's user data, so nothing
in the tree could open one. Three layers stand between the file and a NeXT
disk label:

- Each 1296-byte physical sector is a 36 x 36 array: 32 rows of 32 data bytes,
  with a cross-interleaved Reed-Solomon(36,32) code in the last four columns
  and the last four rows.
- The image starts at the drive's track 4096 while the kernel's logical block 0
  is at track 4149 — 848 sectors of erased media first.
- The `od` driver, not the drive, does the bad-block sparing, from the label's
  own d_ag_size / d_ag_alts / d_ag_off.

The sparing is the one that bites. Skip it and the label, the primary
superblock and the root directory are all still right, so the volume lists and
looks fine; only the cylinder groups past the first drift by 8 sectors, and
fsck then reports 58727 orphan inodes. Decoding and de-sparing here keeps that
out of the NeXT label parser and the UFS reader, which see an ordinary image.

Writing regenerates both ECC passes. Rather than transcribe Previous's 256-entry
t_rem table, the four generator coefficients of (x-1)(x-2)(x-4)(x-8) build it in
a const fn, pinned by three entries of the reference table; the encoder then
reproduces real media byte for byte (473 of 473 ECC-bearing sectors sampled from
a NeXTSTEP 0.8 disk).

Verified on that disk: both 4.3BSD partitions browse and extract, fsck is clean
on the root volume and reports one pre-existing orphan inode on the second.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SunOS 4.x wrote the slice table but not the VTOC, so `sanity`, `version` and
`nparts` all read zero — which our parser already accepts (the kernel's own
`use_vtoc` gate does too) but which leaves every tag at 0. The tag-5 test for
the whole-disk "backup" alias then never fires, so a SunOS 4.1.3 disk listed
slice 2 alongside the five real ones: a 2 GiB entry at LBA 0 overlapping
everything, which browses as a duplicate of the root slice and rules out any
whole-disk backup layout that needs non-overlapping partitions.

Fall back to the geometry when there are no tags: a slice starting at cylinder
0 that spans the label's entire data area is the alias. And stop printing
`unassigned` for those slices — that is a VTOC tag value, and on a label with
no VTOC we never read one; `untagged` says what we actually know.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both schemes bailed out of `backup` entirely — the sidecar JSON was written
and then the run aborted with "browse only". That left two whole families of
vintage disk (SPARC SunOS/Solaris, NeXTSTEP/OPENSTEP) inspectable and
browsable but not backup-able by a backup tool.

The single-file-CHD layout needs none of what was missing. It writes a real
disk image — table at sector 0, partitions at their declared offsets — and
restore is an `io::copy` of the CHD's logical bytes, so nothing on the restore
side has to understand the label at all. What it does need is a head region,
and for these two the honest answer is "verbatim": we have no writer that
could patch a Sun or NeXT label for a resize, so the bytes before the first
partition are copied through unchanged and a resize is refused with that
reason. On a SunOS disk the head region is empty by construction — slice 0
starts at cylinder 0 and carries the label in its own first sector.

The per-partition layouts stay refused, now with a message that says which
layout to use instead of just "not supported".

Verified end to end on both Batch 2 fixtures: a SunOS 4.1.3 SPARC disk
(2.0 GiB -> 81 MB CHD, five slices) and a NeXTSTEP 0.8 magneto-optical
cartridge (242.8 MiB -> 62 MB), each restored and re-fscked to the same
result as the source, with extracted files byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Apple_Rhapsody_UFS` had no compact-reader arm, so the content probe ran at
the slice's first byte, found a NeXT disk label rather than a superblock, and
fell back to raw passthrough — a 2 GiB Mac OS X Server 1.2 slice was read and
staged in full when barely half of it holds data.

Pointing the UFS reader at the filesystem alone would drop the label, which is
what makes the slice bootable. So the compact stream is the label's head region
verbatim followed by the layout-preserving UFS stream: both halves keep every
byte at its original offset, so the concatenation is the slice as it was with
free blocks zeroed, and the sizes add up to the slice exactly.

On the fixture the staged read drops from 2.0 GiB to 1.0 GiB, and a restore
still comes back with the nested label byte-identical and fsck clean
(21135 files / 3394 dirs).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The reader already re-encodes both ECC passes on write, but `open_container_rw`
had no arm for the format, so every edit was turned away with the generic
"convert it to a raw image first". Wire it up: `put`, `rm`, `mkdir` and
`fsck --repair` now work directly on a `.od`.

Checked against the reference algorithm rather than just against ourselves —
after writing a file into the NeXTSTEP 0.8 fixture, all seven sectors the edit
touched carry ECC that an independent implementation of Previous's
cross-interleaved RS(36,32) reproduces byte for byte, and fsck stays clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eleven cases across tiers 2, 3 and 5 for the three fixtures that landed today.
Each one is here because it broke a different assumption, and every one of
those failures was quiet: the volume opened, listed a plausible directory tree,
and was wrong. So the assertions are the ones that would have caught it —
fsck's file count (a lost spare-sector map turns 5708 files into 58727 orphan
inodes), the absence of `whole-disk.img` from a root listing (that entry is the
carve view's, and seeing it means a filesystem was not found), and `untagged`
rather than `unassigned` for slices on a label that has no VTOC to read.

Two of the volumes are damaged and the cases pin the exact damage rather than
looking away: the MO's second partition carries orphan inode 232 in the
pristine image, and the SunOS swap slice holds a destroyed installer miniroot —
a valid superblock over a zeroed root directory — which must report the failure
rather than invent a listing.

Also fills in the five format IDs `rb-regress validate` had been naming as
missing (`fs.bfs`, `fs.ofs`, `part.next`, `part.solaris-x86`, plus the new
`fmt.od`), so the registry validates clean.

Fixtures and catalogue rows are gitignored per FIXTURES.md; the three images
are on this host under `fixtures-large/` and still need pushing to the NAS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`last_data_byte` answers in the filesystem's own coordinates, and a Rhapsody
UFS starts 160 KiB into its slice — so the minimum size reported for that slice
came out 163840 bytes short of the truth. Shrinking a partition to a minimum
that starts where the filesystem does, on a disk where something else stands in
front of it, cuts that much off the far end: the fixture's last fragment is
allocated, so a restore at Minimum would have truncated live data.

Three call sites answered with the filesystem's number — the minimum
dispatcher, `effective_partition_size_reported` (which is what `backup` uses)
and `defragmented_partition_size` — so the head length is now resolved once and
added in all three. Also defers the Rhapsody minimum like the other UFS slices,
since computing it is a bitmap walk.

On the Mac OS X Server 1.2 fixture the reported minimum goes from 2137606144 to
the slice's full 2137769984, which is the right answer for a volume whose last
fragment is in use.

While in the same area: guard `assemble_from_staging` against a disk-label
source arriving with no head region (that would drop the label and still write
a plausible CHD), and bisect the media clamp in the MO geometry rather than
walking back a sector at a time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The last two schemes that bailed out of `backup` with "browse only". Both take
the same verbatim-head treatment Sun and NeXT got: the region ahead of the
first slot — for SGI that is the volume header and the standalone binaries the
PROM boots (`sash`, `ide`), for the IRIS label the boot area — is copied byte
for byte, each slot lands at its declared offset, and restore is a byte copy.
A resize is refused, because nothing here can rebuild either header.

One thing was genuinely new. An `fx` disk describes several *alternative*
layouts at once, so its slots overlap by design: the RISC/os fixture has
fourteen browsable slots of which most claim the same sectors. There is no
per-slot split to make from that, and the assembler would have rejected it as
out-of-order segments. So when the reported partitions overlap, image the drive
as one body and keep the slot table in its sidecar — the shape `backup` already
uses for Solaris x86. The test is on the partitions, not on the scheme, so an
ordinary `fx` root drive still gets per-slot compaction and a plain MBR disk
whose entries happen to overlap stops producing a confusing segment error.

Verified on both fixtures: the RISC/os volume-header disk (1005 MiB -> 171 MB
CHD) and the byte-swapped IRIS 3130 disk label (57 MiB, three slots) each
restore **byte-identical** to the source, and a synthesised `fx` data disk
round-trips through the per-slot path with its volume header intact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three more cases in the tier-5 label file. The IRIS 3130 one asserts
byte-for-byte equality — its slots run contiguously from the head to the end of
the last, so there is no gap for the assembler to zero-fill, and a word-swapped
image is the sharpest test that the copy never interprets those bytes. The
RISC/os one asserts the slot table comes back instead, since that disk is
imaged as one body and the volume header rides inside it.

Also makes the Solaris x86 arm of the backup dispatch say what is true. It
carried a "not yet supported (browse only)" bail that has been dead for as long
as the rewrite-to-MBR above it has existed — Solaris x86 disks have backed up
fine all along, and the next person to read that arm would have concluded
otherwise.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ot restore

An RDB disk did something worse than the schemes that refused outright: it
backed up cleanly and then `restore` turned the folder away. The reason was
recorded in the refusal itself — the sidecar keeps a *parsed* `rdb.json`, and
re-serializing it would lose the FSHD/LSEG filesystem-driver chain and the BADB
list, which needs an RDB encoder nobody has written.

The verbatim-head layout makes that encoder unnecessary. RDSK, the PART chain
and the FSHD/LSEG blocks all live in the reserved area ahead of the first
partition, so copying those bytes through is exactly the fidelity the parsed
form could not give. RDB joins the other four schemes: single-file CHD, head
verbatim, resize refused — and the per-partition layouts are now refused at
backup time rather than producing a folder that only fails at restore.

One guard is RDB-specific. `rdb_blk_hi` says how far the reserved area reaches,
and a compacted partition body zero-fills free blocks, so an RDB claiming
blocks past its first partition would lose part of its own driver chain
silently. That is refused with the reason. On both fixtures the reserved area
ends exactly where the first partition begins (blocks 0..2019 then LBA 2020;
0..251 then LBA 252), which is the layout the check assumes.

Verified: the Workbench 1.3 AFFS disk and the CD32-saves PFS3 disk each restore
byte-identical from a sector-by-sector backup, a compacted backup of the AFFS
one still fscks clean at 337 files, and a synthesised two-partition RDB
round-trips with both bodies and the reserved area intact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found while checking RDB: AHDI had the same defect and the same cause. A
backup succeeded, wrote `ahdi.json`, and then `restore` failed with "no MBR
data available for disk reconstruction" — the sidecar is a parsed table and
nothing turns it back into a root sector.

Same fix as the others: the root sector at LBA 0 rides verbatim ahead of the
first partition, restore is a byte copy, and the per-partition layouts are
refused up front instead of at restore time.

One AHDI-specific case needed care. Its XGM extended chain is structurally
MBR's EBR chain, and MBR only survives that shape because `build_ebr_chain`
rebuilds the EBRs on restore; nothing rebuilds an XGM chain, and those
follow-up sectors sit in the gaps between logicals where the assembler
zero-fills. So a disk with logicals is imaged as one body, which is the same
route an SGI `fx` disk takes and now shares a predicate with it.

Verified: the two-primary AHDI fixture restores byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`CLAUDE.md` says CHD output is only ever single-file. It is not: every source
`single_file_chd::is_supported` rejects falls through to the per-partition loop
and writes `partition-N.chd` — a superfloppy, an X68k disk, and a double-sided
Acorn DFS, which manages two CHDs from one floppy.

Worse, the superfloppy one is not a disk image. The compact reader packs the
volume to its used extent and the CHD records that as the disk's size, so a
1.44 MB floppy becomes an 8.5 KB "hard disk" that `chdman info` and MAME would
both believe. The backup folder still restores — metadata carries both sizes —
but the `.chd` on its own fails the promise the docs make for it.

Writes the investigation down rather than leaving it in a session: the measured
numbers, the fallback at `backup/mod.rs:1487` that describes behaviour nobody
implemented, why no partition table needs inferring, and why "superfloppy" is
not "floppy" (a 300 MB HFV and a 498 MB Amiga HDF are both `PartitionTable::None`,
and CHD is exactly what they want).

Splits the work three ways because the pieces are not equally ready: the
superfloppy fix is contained, X68k needs a padding fix plus an unsolved
resize-on-restore regression and a fixture we do not have, and DSD is a question
for the user rather than a fix. Records which findings are verified and which
are only symptoms, so the next agent knows where to start rather than where to
trust.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A CHD holds a whole disk. `backup --format chd` on a floppy, an HFV or a
bare HDF wrote `partition-0.chd` instead: the compact reader's packed FAT
body wearing a `.chd`, which `chdman info` reported as an 8.5 KB hard disk
for a 1.44 MB floppy. The folder restored fine; only the container was not
a disk image, which is the one promise CLAUDE.md makes for CHD output.

The gate excluded `PartitionTable::None` because the single-file assembler
had no arm for it. A partitionless volume is the case where the partition
and the disk coincide, so the arm is trivial: no table to patch, no head
region, the one body starts at byte 0. The padded packed reader already
sizes that body to the source, so the CHD's logical size is the source
size and the zero tail costs nothing.

One consequence needed handling. A packed FAT/NTFS/exFAT body sits shrunk
inside its full extent in every single-file CHD, and the as-is restore is
a byte copy, so a compacted floppy would have come back as a 1.44 MB image
whose BPB says 205 KB. The per-partition path grows such a volume back on
restore; the as-is restore now does the same for a compacted partitionless
volume, and only for one. MBR/GPT/APM restores are unchanged.

The comment above the split-size check described a raw-forcing fallback
that 5f6378c implemented and bfbc736 removed; it now says what the code
does, which is that X68k and DSD still fall through to per-partition CHDs.
That, and the restore-side resize of label-scheme CHD backups, are written
up in docs/RESUME-chd-single-file-invariant.md with the plan for both.

Pinned by a run_via_staging unit test, an integration test asserting the
container shape and a byte-identical restore, and three tier-5 regression
cases (synthetic floppy, the 300 MB HFV fixture, the 500 MB SFS fixture).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nd grow packed bodies back on restore

A CHD holds a whole disk. Two tables the single-file layout cannot
assemble, X68k and double-sided DFS, still fell through to the per-partition
loop and came out as partition-N.chd: a CHD of one partition is not a disk
image, and a .dsd produced two of them. Both are refused before the backup
folder is created. X68k names --format zstd, which backs the disk up
per-partition and still shrinks the Human68k partition on restore. A .dsd
is refused in every format: its two sides are track-interleaved, no restore
has ever put them back (the MBR fallback bails with "no MBR data
available"), and copying the file is its backup.

Two restore-side changes ride along, both from the same investigation.

The as-is restore of a single-file CHD was a byte copy, so a packed
FAT/NTFS/exFAT body came back shrunk inside its full partition: a 15 MB
partition holding an 8 MB FAT16. The per-partition layout grows such a
volume back; the as-is restore now does too, for every table, so the two
layouts restore the same disk. The previous commit did this for
partitionless volumes only; the user chose to extend it.

That grow exposed a bug both layouts already had for NTFS. The packed
stream stops at the VBR's sector count, one sector before the backup boot
sector, and resize_ntfs_in_place returns early when that count is
unchanged, so the last sector stayed zero and fsck flagged every
compacted NTFS restore. ensure_backup_boot_sector rewrites it when it no
longer matches the VBR, on all three restore paths.

clear_gpt_structures zeroed LBAs 1-33 on every non-GPT restore, which on
a resize restore of a label-scheme CHD wipes an RDB's PART chain, one of
the NeXT label copies, or an X68k table. It is skipped for the disk-label
schemes; none of them can leave a GPT behind.

Pinned by a restore round trip of a packed FAT16 MBR partition, NTFS
unit and integration tests, CLI tests for both refusals, and four tier-5
regression cases in chd-whole-disk-rule.toml. The remaining label-scheme
work is planned in docs/RESUME-chd-single-file-invariant.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…a whole-disk CHD

The disk-label schemes (Sun, NeXT, SGI volume header, SGI disk label,
Amiga RDB, Atari AHDI) are backed up whole, with their head region riding
verbatim in the single-file CHD. Restoring one at any size but Original
went down the MBR arm of the single-file resize path, which patched
nothing it recognised, wrote sector 0 back as it was, and moved the
bodies anyway: exit 0, and a label that points at the old offsets.

partition::restore_patch closes that. One patcher per scheme rewrites the
start/size fields and checksums in the verbatim head bytes, so boot code,
driver chains and IPLs survive, and returns the layout it actually wrote,
because each scheme counts in its own unit (cylinders on Sun and RDB,
1024-byte sectors past a porch on NeXT, the logical sector on X68k) and a
partition may round up and shift the ones after it. The resize restore
reads the head out of the CHD, patches it, copies the bodies where the
label now says, and writes the head after the bodies so a SunOS label
that lives inside slice 0 still lands. A filesystem the in-place
resizers cannot shrink is refused before anything is written. Resizing
at backup time stays refused: the backup is the faithful image, and the
resize happens on the way out.

X68k joins the single-file layout. Its per-partition restore rebuilt the
table from x68k.json and zero-filled the IPL region, which on a real
SCSI disk holds the X68SCSI1 signature that selects the sector size, so
a faithful copy needed the whole-disk shape. An as-is restore is now
byte-identical and --size minimum rewrites the table in place. A SASI
disk with a partition off a 512-byte boundary is refused for CHD only.

Three loose ends ride along. The padded packed reader keyed on how a
partition was dispatched rather than on what its reader reported, so a
FAT behind MBR type 0x83 and a Human68k partition packed without padding
and the CHD assembler fell back to a raw copy. The HFS blank-formatter
equality test stamped the clock twice and failed across a second
boundary; both formatters now take the timestamp from one place. The
orphaned floppy-sizes comment on read_first_sector is gone, and an MBR
partition type that is not a hex byte is an error instead of silently
becoming Linux.

Pinned by patcher unit tests over every scheme, an X68k CLI round trip,
and seven tier-5 cases that shrink the first partition of each scheme on
restore. docs/backup_partition_schemes.md is the design note the code
has referred to since AHDI landed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… too

The restore-time label patcher landed for the disk-label schemes; the
other two places a size can change still did not use it. A CHD backup or
export with a resize refused a Sun, NeXT, SGI, RDB, AHDI or X68k source
at assembly, and the raw and VHD whole-disk exports silently streamed
such a disk verbatim with the overrides dropped, or worse, patched its
label as if it were an MBR when sector 0 happened to end in 0x55AA.

The CHD path now rewrites the head before staging, so the bodies are
staged to the layout the label will carry, and keeps the drive's size:
a label describes the whole drive, and trimming the image to the last
partition would leave it describing a disk that is not there. The raw
and VHD exports gain a generic reconstruction next to the RDB and APM
ones: bodies copied to the new layout, the rewritten head written after
them, the filesystem resizers and hidden-sector patches run on the
result. Both check the in-place resizer can handle the filesystem before
writing anything. The Inspect tab says when a table cannot be resized on
export instead of quietly exporting the disk as-is.

Pinned by an export round trip over six schemes through the CHD path and
a raw reconstruction over three.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
chd_rejects_split_size_up_front asserted that an unsplit CHD backup of a
FAT superfloppy records exactly one compressed member, which was the
per-partition partition-0.chd shape the whole-disk rule retired. The
backup now lands in the single-file layout with the body inside ok.chd
and no member files, which is what "one container" always meant; the
assertion says so directly. Caught by CI, since only the lib suite and
the touched integration tests were run before the push.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… proof builds

`scripts/build-ppc.sh hostc` transpiles the engine for the machine it runs
on, as a cheap proof that the sources still go through mrustc without
needing the PowerPC Mac. On a Linux host it has never worked:

    src/os/linux.rs:8:5-37 error:0: Cannot find component 2 of
      crate::os::linux::nix::mount::umount2

`rb-cli-ppc/Cargo.toml` deliberately declares no `nix` -- lockfiles are
target-agnostic, so nix 0.31's libc floor would drag the whole graph onto
a libc mrustc cannot lower. linux.rs is cfg'd to `target_os = "linux"`, so
the PowerPC target (macOS) never compiles it and the omission is free --
but `hostc` targets *this* machine, where that cfg is true and the extern
crate is absent, so mrustc resolves `use nix::` as a local path and stops.
The stage was only ever run on an Apple Silicon host, where linux.rs is
not compiled, which is why nobody hit it.

nix was doing seven things here: `umount2`, and `geteuid` / `getuid` /
`getgid` / `umask` in the elevation path. Each is a one-for-one libc
wrapper -- nix's `umount2` is `libc::umount2` plus errno handling, and
`MntFlags` takes its bits from `libc` -- so they now go through `libc`
directly in a private `sys` module. The desktop keeps the same syscalls
and the same unmount-then-lazy-detach fallback.

Gating just the failing import would have moved the error to the next of
the seven; a stub module would have meant a second copy of the elevation
logic to keep in step. Going through libc leaves one implementation, no
feature gate, and an engine that no longer names nix at all -- nix stays
only in src/main.rs, the GUI binary, which is not part of the mrustc
crate. The manifest note and docs/build-ppc-mrustc.md record that a
`use nix::` anywhere under src/ breaks this stage again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…try can work

minicargo can schedule one crate's build script twice at once, and the
second worker execs the binary while the first is still linking it --
`Unable to run process ... Permission denied` partway through the graph.
The job already retried the stage once for this, relying on the build
being incremental.

The retry could not succeed, though: the losing worker leaves the
build-script marker behind with an empty OUT_DIR, so the second attempt
trusted the marker, skipped re-running the script, and died on output
that was never written:

    MACRO<::"serde_core-...">: Unable to open file '.../private.rs'

which is a different error than the one being retried -- the giveaway
that the retry was inheriting poisoned state rather than resuming. It now
removes `$SOL9_OUT/{,host/}build_*` first, so every build script genuinely
re-runs; they are seconds each, and the expensive crate compiles are
untouched.

This is still a mitigation. The scheduling fix is upstream, in the two
minicargo commits on the `ppc-build-2026-09` branch, and CI builds from a
prebuilt seed that predates them -- docs/solaris9-open-items.md records
that a reseeded toolchain is what actually retires the item.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…wnloads it

Both AppImage jobs have failed on every run since 2026-09-11, on the
bundling step:

     * Building anylinux.so...
     Download failed! Trying again...   (x5)
     ERROR: Failed to download 5 times!

Not a network blip, though it reads like one. `quick-sharun.sh` is pinned
to commit 2affcd6, but the script fetches its own `anylinux.c` from
`refs/heads/main` -- a floating ref -- and upstream has since moved the
file. That URL now 404s, so the script's five retries just repeat a
request that can never succeed. The same path at our pinned commit still
returns 200; we pinned the script and left its data floating.

The step now derives ANYLINUX_LIB_SOURCE from ANYLINUX_REF, so the two
pins cannot drift when the ref is next bumped (one `env:` entry cannot
reference another, which is why it is not up there beside
APPIMAGETOOL_LINK). A `curl -fsI` preflight fails the job with a clear
message if the pinned URL ever stops resolving, rather than handing it to
a downloader whose only symptom is five identical retries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Amends the previous commit's fix. Pinning anylinux.c to the old repo at
our quick-sharun ref did unbreak the build, but it froze the helper lib
at the pre-move revision, which is not where upstream is any more.

The file moved to pkgforge-dev/Anylinux-sharun on 2026-09-10 (commit
a84aa4b, "move and build helper libraries to this repository") -- the day
before our AppImage jobs started failing. quick-sharun.sh at our pinned
ref still defaults to the old `refs/heads/main` path, which 404s.

ANYLINUX_LIB_SOURCE now points at the new repo at release tag 3.2.1, so
we track the current lib (633 lines, vs 406 at the old pin) while staying
on a fixed ref rather than a branch -- a branch is what broke this in the
first place. Verified that 3.2.1's anylinux.c is byte-identical to the
new repo's main, is self-contained (system headers only) and compiles
with the exact invocation the pinned script uses:
`cc -shared -fPIC -O2 anylinux.c -o anylinux.so`.

Worth knowing for the next bump: upstream's current quick-sharun.sh has
dropped ANYLINUX_LIB_SOURCE altogether and pulls prebuilt helper libs
from a sharun release tarball instead. Moving to it means taking a
5047-line script in place of our 2990-line pinned one, so it is a
deliberate upgrade to test on its own, not a drive-by.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@danifunker
danifunker merged commit 0dce414 into main Sep 12, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant