Skip to content

add the apply half: install a bundle onto a FAT volume - #12

Merged
joeferner merged 1 commit into
mainfrom
ota-apply
Sep 3, 2026
Merged

joeferner merged 1 commit into
mainfrom
ota-apply

Conversation

@joeferner

Copy link
Copy Markdown
Owner

The other half of an update, behind the apply feature. It takes a validated bundle and a resident-fat volume and writes every entry where its path says.

The order is the crate's, not the bundle's: ordinary files, then Raspberry Pi firmware, then config.txt, then the kernel. That is the "write the live boot file last" rule stated as an order over roles now that a bundle can replace the firmware and config.txt too, and the crate imposes it for the same reason the kernel became a role rather than a position -- a bundle packed in another order must not be able to change which failures leave a board that still boots. The kernel is last because while there is one boot image that write is the commit; it stops being either once there are two slots and rewriting config.txt takes both jobs over.

Entries the card already holds are read and not rewritten. One function answers both halves of that question -- before a write it decides whether to write at all, and after one it is the verification -- so a skipped entry is checked exactly as strictly as a written one, which is the property that would otherwise rot. On hardware, re-applying an unchanged bundle costs 1055 ms and no card writes at all, against 3724 ms to write the same six entries in full. A bundle carrying the Pi firmware carries 3 MB of it and that changes about once a year.

Timing stays with the caller, through Progress. The water sensor keeps producing the figures it always did -- milliseconds and card commands for the write and the read-back separately -- and this crate never learns that embassy_time or a command counter exist.

The tests build FAT32 volumes with mkfs.vfat and judge them with fsck.vfat, because a check written here could only agree with the code it is checking. Two things about that oracle are worth knowing, since both produce a test that cannot fail: fsck.vfat -n exits 0 even when it finds faults, so the assertion is on its output rather than its status; and an 8 MB image is not really FAT32 at 16,100 clusters against a 65,525 minimum, which resident-fat accepts because it enforces only a maximum. Confirmed the oracle bites by removing the final sync -- five tests fail with "Dirty bit is set" and "Free cluster summary wrong".

tests/ is excluded from the published crate: it needs dosfstools and fails rather than skips without it, so shipping it would ship something that cannot run from the tarball it arrives in. CI installs the tools.

The other half of an update, behind the `apply` feature. It takes a
validated bundle and a resident-fat volume and writes every entry where
its path says.

The order is the crate's, not the bundle's: ordinary files, then
Raspberry Pi firmware, then config.txt, then the kernel. That is the
"write the live boot file last" rule stated as an order over roles now
that a bundle can replace the firmware and config.txt too, and the crate
imposes it for the same reason the kernel became a role rather than a
position -- a bundle packed in another order must not be able to change
which failures leave a board that still boots. The kernel is last because
while there is one boot image that write is the commit; it stops being
either once there are two slots and rewriting config.txt takes both jobs
over.

Entries the card already holds are read and not rewritten. One function
answers both halves of that question -- before a write it decides whether
to write at all, and after one it is the verification -- so a skipped
entry is checked exactly as strictly as a written one, which is the
property that would otherwise rot. On hardware, re-applying an unchanged
bundle costs 1055 ms and no card writes at all, against 3724 ms to write
the same six entries in full. A bundle carrying the Pi firmware carries
3 MB of it and that changes about once a year.

Timing stays with the caller, through Progress. The water sensor keeps
producing the figures it always did -- milliseconds and card commands for
the write and the read-back separately -- and this crate never learns
that embassy_time or a command counter exist.

The tests build FAT32 volumes with mkfs.vfat and judge them with
fsck.vfat, because a check written here could only agree with the code it
is checking. Two things about that oracle are worth knowing, since both
produce a test that cannot fail: fsck.vfat -n exits 0 even when it finds
faults, so the assertion is on its output rather than its status; and an
8 MB image is not really FAT32 at 16,100 clusters against a 65,525
minimum, which resident-fat accepts because it enforces only a maximum.
Confirmed the oracle bites by removing the final sync -- five tests fail
with "Dirty bit is set" and "Free cluster summary wrong".

tests/ is excluded from the published crate: it needs dosfstools and
fails rather than skips without it, so shipping it would ship something
that cannot run from the tarball it arrives in. CI installs the tools.
@joeferner
joeferner merged commit 096a51c into main Sep 3, 2026
5 checks passed
@joeferner
joeferner deleted the ota-apply branch September 3, 2026 01:02
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