Repository navigation
feat(flash): port flash mode 1 to the Rust initramfs - #27
Conversation
Mode 2 overwrites etc and data with random data from /dev/urandom, mode 3 discards all their blocks with the BLKDISCARD ioctl. The wipe runs between the backup and the reformat and never aborts the reset: a failure on one partition does not skip the other, reformat and restore still run, and the result is reported as Error with a note in the status error field. Legacy mode 4 (customer-supplied wipe hook) is not ported; a mode-4 trigger stays rejected as Invalid. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The note is built from the error's Display, which prefixes "Factory reset error" - inside the factory-reset result that only repeats the obvious. Take the inner reason instead. The overwrite test only checked that the last block changed, so a gap in the middle of the range would have passed. Check every block. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
…an ioctl Seeking to the end of a block device reports its size, which drops one raw ioctl request code, its unsafe block and the size_t reasoning that came with it — and it works on a temp file, so the size path is testable. The remaining BLKDISCARD code is assembled from named constants. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Taking the device size from a seek made both testable against a temp file, so the tests now exercise wipe_random itself instead of only its inner loop, including the error a device it cannot open produces, and that the size path rewinds for the caller that writes from the start. The progress interval becomes an argument of the overwrite loop. At its 1 GiB default no test ever crosses it, which left the sync and step arithmetic uncovered. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
A trigger that failed to parse only produced a kmsg warning. The boot continued as Normal, no factory_reset object was written and the trigger kept its value, so the caller waited for a result that never came and the warning repeated on every following boot. The on-device tests catch this: they set broken triggers and expect a status. The trigger is now carried into factory-reset mode either way. An unusable one skips the sequence, is cleared like any other, and is reported with the status the shell implementation used: anything about mode is Invalid, an unusable preserve is ConfigError. preserve becomes mandatory again - an empty array is how a caller asks to keep nothing. The design of the wipe modes describes the reporting as if it already happened, so it carries a correction. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
A file in factory-reset.d without a usable paths array was skipped, and a broken factory-reset.json was reported as an invalid request. Both are configuration errors: the caller asked to keep something and the file does not say what, so skipping it wipes those paths and still reports success. Every problem in a preserve configuration file now reports ConfigError, which is what the shell implementation did and what the on-device tests expect. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Deduplication: the same get/as_array/as_str block existed three times after the trigger parsing was rewritten. It moves into a helper that reports why a value was not a list of strings, so each caller keeps its own status - a missing key in the trigger is a different answer than one in a preserve file. WipeFailed was built at four call sites with the same two fields. Tests: three of them passed even with the behaviour removed. The overwrite tests accepted a constant buffer, so mode 2 now checks that two wipes differ and that a block carries many distinct values; the rejected-trigger tests built the error themselves instead of walking detect; and the integration test accepted any mode instead of the one the trigger named. Mode 3 had no test at all - both of its failure paths work on a regular file. The 32-bit clamp becomes a function so the 4 GiB case is a table test instead of a comment. The two new error variants get a recovery-class test, since classifying either as fatal would block a boot the design says must continue. Docs: the design of the wipe modes still described the size ioctl that the seek replaced, and named a constant that was renamed; the README claimed a value has to be valid json to reach factory-reset mode; the module map had no factory_reset subtree. The two older factory-reset specs carry a dated correction, the form this repo uses for superseded statements. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
init_setup runs the sync on every boot, and its failure suppresses the first-boot marker like a failed resize does. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
…tion Every command in the build chapter omitted the partition table, which build.rs demands, so none of them worked. The overview counted the shell scripts the binary replaced; the count says nothing once the migration is over. The startup flow now also names the extra-bootargs update as a reason to reboot, next to the filesystem check. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The spec was written against d1168de and is brought onto the wipe-modes base it will merge into. The three plans split the port along the implementation order the spec pins, one PR each, and record where the spec meets the changed base: the FactoryResetTrigger enum, the private partition-number constants, and the error type of the shared reformat_ext4. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Five defects found reviewing the plans against the runtime: - adding BootMode::Flash breaks the match in run_init, so the same task now adds the arm instead of leaving the tree red until later; - run_init called detect twice with an unreachable! arm between them. A panic in PID 1 is a kernel panic and a reboot loop, so detect is now called once and dispatched in one exhaustive match; - mode 1's log had nowhere to land: the data partition is mounted inside normal boot, which a flash mode never reaches, and /sysroot is unmounted before the write. Persistence now mounts it itself; - cargo test does not run tests inside build.rs, so the mode 2 helpers move to a file both the build script and the crate include; - a conflicting trigger and a bad URL both reported themselves as an unusable destination device; they get their own variants. The unmount helper moves into the shared layer, where mode 1 needs it too. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
…ry reset Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
…ppy path The placeholder Flash arm in run_init always returned ConflictingTriggers, including for an ordinary flash-mode selection with no factory reset queued. On a release image that halts forever with flash-mode still set, repeating on every power cycle, and the error message was false in that case. Add FlashError::DispatchUnavailable for the real condition, split the trigger clear into a flash-only helper reused from lib.rs, and clear the flash trigger before reporting it so a power cycle recovers to a normal boot. Also make the factory-reset read failure during the conflict check degrade to Normal like every other read error in detect, restore the doc comment's explanation of FactoryResetTrigger::Rejected, and strengthen the flash detect tests to assert the untouched/cleared trigger sets and the fall-through to factory-reset handling. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Move reformat_ext4 out of the factory-reset module into filesystem so flash mode 1 can use it too; that mode ships in images built without factory-reset. The shared helper returns FilesystemError::FormatFailed instead of FactoryResetError::ReformatFailed, so the factory-reset call site wraps the error back to keep its ContinueDegraded recovery class (a factory reset that cannot reformat degrades the boot, it does not stop it). Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
flash-mode-1 has no caller for reformat_ext4 yet, so gating the shared module on any(factory-reset, flash-mode-1) leaves it dead code and fails -D warnings whenever flash-mode-1 is enabled without factory-reset, including the default feature set. Narrow both the module declaration and the re-export to factory-reset until the flash-mode-1 caller lands and needs the wider gate. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The existing regression guard only asserted the recovery_class mapping in error.rs, never calling reformat_ext4, RealReformatOps::reformat, or the map_err wrap this task added, so it would pass unchanged if the wrap were missing or wrong. Add a test that drives the real call path with an unformattable device and checks the error comes back as FactoryResetError::ReformatFailed, not the shared helper's raw FilesystemError. Also bring the reformat_ext4 call site's import in line with the file's own convention (imported alongside the other filesystem:: symbols, not fully-qualified inline), and note reformatting in filesystem/mod.rs's module doc now that the helper lives there. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
…scale Guard the last-lba and extended-container size computations with checked add/sub so a malformed dump errors instead of wrapping into a near-u64::MAX partition size. Require sector-size: 512 alongside unit: sectors, since the KB-to-sector conversion silently mis-scales on any other sector size. Fix three size assertions that would still pass with a wrong scale factor by anchoring them on the field boundary, and remove a panicking expect() from the non-test parse path. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
A write that returns Ok(0) with bytes still outstanding looped forever instead of failing, unlike the mirrored read-side check. Since this runs as PID 1, a returned error reaches the existing fatal-error path; a silent hang does not. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Add MACHINE_FEATURES to Config, read from the initramfs-local /etc/os-release, since flash modes unmount /sysroot before EFI handling needs it. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
JoergZeidler
left a comment
There was a problem hiding this comment.
Ran fmt, clippy for eight feature combinations (grub,gpt and uboot,dos with test-utils, with factory-reset, with factory-reset,resize-data,release-image, and --no-default-features for both), tests for ten combinations (229 to 429 tests each, all green), cargo audit, and the armv7 clippy from the README: all green. --no-default-features --features grub,gpt,flash-mode,test-utils fails -D warnings, which the README already calls unsupported.
Checked against the legacy flash-mode-1 and common-sh in the meta-omnect history: the three bug claims in section 9 hold, the dump rewrite formulas and the dd replacements match, and the KB units of all five build constants match the .wks files and omnect_conv_size_param. uboot-env.bin is exactly UBOOT_ENV_SIZE KB (mkenvimage -s), so the Some(size) copy length is right.
Labels on every comment: High, Medium, Low; "blocks merge" where I would not merge without it. Nothing is Critical: no finding writes the source disk.
Blocks merge:
clone.rs:229a partition of another disk is accepted, andsfdiskwrites into it before the check fails.rawio.rs:102nofsync; a write-back error on the destination is dropped and the clone reported good.efi.rs:16efivarfsis never mounted; the EFI path needs a hardware run or an explicit mount before this ships (spec 10.2 already says so for the entry count).clone.rs:469the clone sequence has no ordering test and no ops trait; same gap as #25 finding 7.
Points without a diff line:
- The PR text says
/sysroot; the rootfs is at/rootfs. See the comment onconfig/mod.rs:90. - meta-omnect#697 exports the five constants, installs
e2fsprogs,util-linux-sfdiskandefibootmgronefi, and keepsflash-mode-1in the default set. Its recipe hardcodesgpt, so every DOS path here is host-tested only, as the PR text says. ItsSRC_URIstill points at the fork branch. - Spec, top: No
**Status:**line; every other spec in the directory has one. - Spec, section 7: "logs progress while waiting": the wait logs once now.
- Spec, section 6: One more deviation for section 9: the loader is passed as
\EFI\BOOT\bootx64.efi; the legacy script's unquoted\\\\EFI\\\\BOOTreachedefibootmgras\\EFI\\BOOT\\bootx64.efi. Whether the firmware treats both the same I could not verify. - Not verified:
efivarfson the EFI machines; that the targetsfdiskemitssector-size:(util-linux 2.34 or later; an older one fails every clone atverify_sector_scale); thate2image -preaches the console through inherited PID 1 stdio; where the feature combinations run in CI.
Signed-off-by: Joerg Zeidler 62105035+JoergZeidler@users.noreply.github.com
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
…he old ones Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
…ding Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Validate the build constants once, in bytes, before anything is written. Generate the partition UUIDs before the first destructive step, from /dev/urandom instead of a panicking call. Keep the last lines of the e2image output in its error while still forwarding it to the console. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The log writer is injected, so a test sees that a failed run writes a log carrying its error. The devpath is an enum, the env key is a BootEnvKey, and flash-mode and flash-mode-devpath are both trimmed. The integration test keeps only the case the spec assigns to it. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
… operation Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Also test the factory-reset reformat error mapping without running mkfs. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
…ents Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
…acy does Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
JoergZeidler
left a comment
There was a problem hiding this comment.
Round 2 on the 12 new commits. Every reply checked against the code; all four merge blockers are fixed as described:
- a partition of another disk is refused before
sfdisk(REASON_NOT_A_WHOLE_DISK, pinned bya_partition_of_another_disk_is_refused); copy_rangesyncs the destination and reports the write-back error;efivarfsis mounted beforeefibootmgr, and the new entry is created before the old ones are deleted, pinned bythe_new_entry_is_created_before_the_old_ones_are_deleted;clone_withruns on aCloneOpstrait andthe_clone_runs_its_steps_in_the_spec_orderpins the full sequence for all four bootloader x table combinations, plus the two stop-early cases.
Also verified: the run log is written on failure and carries the error (run_and_persist with a log writer), the kmsg hook is tested through a real KmsgLogger on a temp file, required_constants takes the values, the UUIDs come from /dev/urandom before the rootfs is unmounted, e2image output goes through one pipe to the console and the last lines reach the error, /rootfs in spec, plans, project-context and code comments, the spec status line, and the Devpath enum with BootEnvKey in the error. 56 of 57 threads resolved; the devpath-trim one stays open as Low with a note, and the rebase point is withdrawn (squash-only repo).
Machine checks on the new head: fmt, clippy for eight feature combinations, tests for ten combinations (231 to 443 tests each), cargo audit, armv7 clippy: all green.
Two Low points inline, nothing that holds the merge. From my side merge-ready. The EFI path is still without a hardware run; spec 10.2 keeps that as the shipping condition, now covering the efivarfs mount and the loader-path spelling too.
Signed-off-by: Joerg Zeidler 62105035+JoergZeidler@users.noreply.github.com
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
JoergZeidler
left a comment
There was a problem hiding this comment.
Round 3, head 6a629f0: the devpath is trimmed again with parse_devpath_trims_surrounding_whitespace, the plan no longer names the v4 feature, and the 8.2 row covers the queued factory reset. The second URANDOM_PATH stays, the feature-gate reason holds. All four remaining threads resolved.
fmt, clippy (grub,gpt and uboot,dos with factory-reset,test-utils) and tests for four combinations (431 / 420 / 300 / 443) on the new head: green.
Nothing open from my side. The EFI hardware run stays the shipping condition from spec 10.2.
Signed-off-by: Joerg Zeidler 62105035+JoergZeidler@users.noreply.github.com
The recipe selects grub exactly for machines with efi in MACHINE_FEATURES, so the runtime read of /etc/os-release has no other use and is removed. Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Summary
Ports flash mode 1, cloning the running disk onto another block device, from the scripted initramfs to the Rust initramfs, together with the shared flash layer that modes 2 and 3 will build on.
src/mode/flash/holds the trigger keysflash-modeandflash-mode-devpath, the partition-table dump rewrite, in-process replacements for the legacyddcalls, the rootfs unmount, the EFI boot-entry handling and the clone sequence.BootModegets aFlashvariant, dispatched beforeinit_setup: a flash mode clones the whole disk, so the init-setup work on the running disk would not be carried over.flash-mode-1.logon the source data partition on success and on failure, because the device powers off afterwards.efivarfs, creates the new boot entry and only then deletes the old ones, so a failure in between leaves the machine with a boot entry.reformat_ext4moves tosrc/filesystem/, so images withoutfactory-resetcan use it.Legacy bugs fixed rather than reproduced: the device wait reported a timeout when the device appeared on the last poll; the DOS extended-container size was wrong on every DOS machine; and the partition-UUID refresh also ran on MBR, which has no per-partition UUIDs, so mode 1 failed on DOS. The U-Boot environment size and offsets are in KB; reading them as bytes would have written 64 bytes of a 64 KB environment.
Reason
The scripted initramfs is replaced module by module. Mode 1 comes first because modes 2 and 3 reuse most of what it adds.
flash-mode-1is in the default feature set, like the legacy script that every image installs.e2fsprogs(fore2image) and, on EFI machines,efibootmgr; feat(initramfs): replace the scripted initramfs with omnect-os-init meta-omnect#697 does both. Without the constants, mode 1 fails before the first write.gpt, so CI does not run the DOS paths.flash-modecannot be read, the device boots normally and a queued factory reset waits for the next boot, since a conflict between the two cannot be ruled out.CloneOpstrait, and the EFI order throughEfiOps./dev/urandom, so a failing random source is an error and not a panic in PID 1.efivarfsmount.