Keep one generation back, and verify before touching the existing build

Without this, a successful transfer of a BROKEN build stranded you: the only
copy of a working test program had already been overwritten, so there was no
way to attempt another transfer without an SD card reader.

Write order is now temp -> verify -> rotate, and the ordering is the point:

  - The image goes to "<name>.tmp" and is read back and compared BEFORE
    anything existing is touched.  If the transfer, the write or the verify
    fails, the build you are running is still on the card untouched and you
    can simply try again.  Previously a failed write left the destination
    half-overwritten.
  - On success the existing build is rotated to "<name>.prev.dol" and the temp
    file takes its place.  So if the NEW build turns out to be broken, boot
    the previous one from Swiss and transfer again from there.  One generation
    back is enough never to need a card reader.

The two extensions are chosen deliberately:
  - the temp file has NO .dol extension, because Swiss lists .dol files and a
    half-written image Swiss offers to boot is exactly what this avoids;
  - the backup KEEPS .dol, because booting it from Swiss is the whole point of
    keeping it.

remove()/rename() on a file that is not there is ignored -- that is just the
first update on a fresh card.  If the final rename somehow fails, the message
names the .prev.dol path rather than leaving the user to guess where their
working build went.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 11:26:58 +02:00
parent 30563b166d
commit 7c9488b617
3 changed files with 92 additions and 32 deletions
+17 -4
View File
@@ -81,12 +81,25 @@ Press **Z**, then on the host:
sudo ./scripts/send-dol.py <iface> gc_bba_test/gc_bba_test.dol
```
The flow is deliberately **receive → write to SD → boot Swiss**:
The flow is deliberately **receive → temp file → verify → rotate → boot Swiss**:
1. The new build arrives over the adapter into a staging buffer.
2. It is written to the SD card (default `/gc_bba_test.dol`, override with
`--dest`) and **read back and compared**.
3. The app then boots **`/boot.dol`** — your known-good Swiss.
2. It is written to `<name>.tmp` and **read back and compared**.
3. Only then is it rotated in: the existing build becomes
**`<name>.prev.dol`** and the temp file becomes `<name>.dol`
(default `/gc_bba_test.dol`, override with `--dest`).
4. The app then boots **`/boot.dol`** — your known-good Swiss.
**Ordering is the point.** If the transfer, the write or the verify fails, the
build you are currently running is still on the card untouched, so you can
just try again. And once an update succeeds, the previous build is kept as
`<name>.prev.dol` — so if the *new* one turns out to be broken, boot the
previous one from Swiss and transfer again from there. One generation back is
enough to never get stranded needing an SD card reader.
The temp file deliberately has **no `.dol` extension**: Swiss lists `.dol`
files, and a half-written image Swiss offers to boot is exactly what this
avoids. The backup keeps `.dol` precisely because booting it is the point.
**The received image is never launched.** The only thing this ever jumps into
is Swiss, which was already known good. So a broken test build costs nothing: