os_update: write bootloader artifacts to an eMMC hardware boot partition - #25
os_update: write bootloader artifacts to an eMMC hardware boot partition#25mobileoverlord wants to merge 6 commits into
Conversation
…ly without one A bundle's layout (devpath + computed offsets) was preferred over the partition's PARTLABEL whenever the bundle carried one, so a GPT artifact was written by raw offset to the manifest's devpath. On i.MX that device name is kernel-dependent (/dev/mmcblk1 in the manifest, /dev/mmcblk2 under 6.18), and the computed offsets need not match where the flasher placed the partitions: Writing rootfs_hash -> /dev/mmcblk1@618659840 (partition: rootfs-b-hash) Failed to open device /dev/mmcblk1 for rootfs_hash: No such file or directory Resolve /dev/disk/by-partlabel/<name> first and fall back to the layout only when udev has no label for the partition (MBR layouts). Both the staged and the streaming write paths go through the same locate_target.
…ue test label A label that exists but fails to resolve (dangling symlink, EACCES, I/O error) is surfaced as the error it is rather than treated as absent, so a transient failure can never turn into a computed-offset write. The tests derive their nonexistent label from the pid so attached media cannot collide with it.
Path::exists() answers false for a dangling symlink or a metadata error, so both the label check in locate_target and the neighbour scan in disk_has_any_label could misclassify a labeled disk as unlabeled and enable raw offset writes. One helper, label_absent, now decides via symlink_metadata and only a clean NotFound counts; every other outcome is surfaced by resolve_partition or treated as evidence the disk is labeled.
test_mode_redirects_os_releases_to_tmpdir set TMPDIR=/scratch under ENV_VAR_MUTEX, but tests in other modules call TempDir::new() without that lock and read TMPDIR while it is set; whichever one raced into the window failed on NotFound (seen as config::tests::test_load_invalid_toml in CI). Use a real, kept directory so a racing TempDir still has somewhere to go.
A slot target named `emmc-boot:<n>` addresses the n-th hardware boot partition (/dev/mmcblkXbootN) of the disk that holds the var partition, resolved at run time because the kernel's mmcblk numbering is not stable. The write lifts force_ro, writes, fsyncs, reads back and compares, and restores force_ro whatever happened; a disk without boot partitions (an SD card) is refused with a message rather than written somewhere else. This lets an i.MX stone manifest carry imx-boot as an OS artifact (slot a -> boot0, slot b -> boot1) so a bootloader - and the FIT key it enforces - ships with the OS update it belongs to; activation flips PARTITION_CONFIG through the manifest's existing command action.
…iled A manifest describes every medium a machine can be provisioned on. An update applied while running from an SD card meets an emmc-boot:<n> target for hardware that is not there; write everything else and say what was skipped, so the OS update keeps working on every medium and only the pieces that exist on this one are written. Pure helper + test for the eMMC/SD split.
|
Follow-up in f5c5332: a target the boot medium does not have ( |
|
Board result (imx8mp-evk, eMMC): OS update with a changed rootfs wrote the bundle's |
| let Some(index) = spec.strip_prefix("emmc-boot:") else { | ||
| return Ok(None); | ||
| }; | ||
| let n: u8 = index.parse().map_err(|_| { |
There was a problem hiding this comment.
error: n is parsed as u8, so emmc-boot:2 is accepted despite the message promising <0|1>. emmc_boot_target then finds /dev/mmcblk2boot2 missing and returns NotOnThisMedium, reporting "has no eMMC hardware boot partitions ... running from an SD card" on a board that does have them. The update then proceeds to activation, and avocado-imx-bootpart's only guard is the IVT magic check on the target partition, which a stale image from a previous update passes. A one-character manifest typo therefore activates a new rootfs against the old bootloader, with no error anywhere in the chain, which is exactly the bootloader/FIT-key coupling this PR exists to establish. Everywhere else in this file a bad target name is a hard error; emmc-boot: is the only target that degrades into a silent skip.
Reject n > 1 here, and gate the skip in emmc_boot_target on boot0 being absent (the disk genuinely has no hardware boot partitions) rather than on boot<n> being absent.
| })?; | ||
| drop(f); | ||
| let mut back = vec![0u8; data.len()]; | ||
| fs::File::open(dev) |
There was a problem hiding this comment.
problem: this read-back is served from the block device page cache, so it compares memory to memory and never reaches the eMMC. Buffered block-device I/O goes through the bdev inode's page cache; sync_all() writes those pages back but leaves them resident and clean, so the File::open(dev) + read_exact immediately after reads the same pages that were just written. The comparison cannot fail.
Nothing regresses relative to having no read-back at all, since a genuine write failure still surfaces as EROFS/ENOSPC/EIO out of write_all. But the check does not provide the property the doc comment and the PR description claim, and this is the one write path where a real read-back is worth having. Invalidate before reading to make it real: BLKFLSBUF on the fd, posix_fadvise(POSIX_FADV_DONTNEED), or reopen with O_DIRECT. Doing the compare in chunks while you are there would also drop the current peak of two full-size buffers resident (fs::read plus vec![0u8; data.len()]), where every other write path in this file streams with 4 MiB buffers.
|
|
||
| /// Name of the whole disk holding the `var` partition, e.g. `mmcblk2`. | ||
| fn root_disk_name() -> Result<String, OsUpdateError> { | ||
| let part = fs::canonicalize("/dev/disk/by-partlabel/var").map_err(|e| { |
There was a problem hiding this comment.
problem: this hard-errors when /dev/disk/by-partlabel/var is absent, which contradicts the rule the second commit establishes. An unlabeled or MBR medium, exactly the case #24 added offset writes for, carries no by-partlabel entries at all, so an emmc-boot:* artifact fails the entire OS update there instead of being skipped as a target this medium does not have. The NotOnThisMedium path only covers media where var is labeled but the boot device is missing.
Stacked on #24 (uses
locate_target).What
A slot target named
emmc-boot:<n>(in the manifest'sslot_partitions) addresses the n-th eMMC hardware boot partition (/dev/mmcblkXbootN) of the disk holding thevarpartition.How
/dev/disk/by-partlabel/var's parent disk, since mmcblk numbering is kernel-dependent.force_ro, writes + fsyncs, reads back and compares, restoresforce_roon every path. A disk without boot partitions (SD card) is refused with a clear message instead of writing elsewhere.Lets an i.MX stone manifest carry
imx_bootas an OS artifact (slot a → boot0, slot b → boot1), so the bootloader — and the FIT key it enforces — ships with the OS update it belongs to. Activation/rollback flipPARTITION_CONFIGwith the existingcommandaction (avocado-imx-bootpart <slot>, meta-avocado). No stone changes.