Skip to content

feat(resize-data): expand data partition to fill disk on first boot#8

Open
JoergZeidler wants to merge 23 commits into
omnect:mainfrom
JoergZeidler:feat/resize-data
Open

feat(resize-data): expand data partition to fill disk on first boot#8
JoergZeidler wants to merge 23 commits into
omnect:mainfrom
JoergZeidler:feat/resize-data

Conversation

@JoergZeidler
Copy link
Copy Markdown
Contributor

@JoergZeidler JoergZeidler commented May 6, 2026

Summary

  • Adds a compile-time resize-data Cargo feature that expands the data
    partition and its ext4 filesystem to fill available disk space on first
    boot, guarded by the resized-data bootloader environment variable so
    it runs exactly once.
  • Splits mount_partitions into mount_core_partitions (rootfs + boot)
    and mount_remaining_partitions (factory / cert / etc / data) so the
    bootloader can be created after boot is mounted but before the data
    partition is touched.
  • Implements src/mode/resize_data.rs with GPT (sgdisk -e) and DOS
    (extended partition resize via parted) branches selected at
    compile time via #[cfg(feature = "gpt|dos")].
  • Integrates resize as a first-boot step in mode::normal::run, called
    before mount_remaining_partitions.

Reason

When an omnect OS image is flashed to a disk larger than the image itself
the data partition occupies only the space baked into the image; the
remaining disk space is wasted unless the partition and its filesystem are
expanded on first boot. This ports and integrates the existing
resize_data branch implementation into the feat/boot-mode-dispatch
architecture.

Verification

All eight feature combinations pass:

cargo test --features grub,gpt
cargo test --features grub,dos
cargo test --features uboot,gpt
cargo test --features uboot,dos
cargo test --features grub,gpt,resize-data
cargo test --features grub,dos,resize-data
cargo test --features uboot,gpt,resize-data
cargo test --features uboot,dos,resize-data

- cargo clippy -- -D warnings clean across all eight combinations
- cargo fmt -- --check clean
- cargo audit — no vulnerabilities (28 crates scanned)

Spec/Plan

Used Spec:
2026-05-04-resize-data-design.md

Used Plan:
2026-05-04-resize-data.md

The scaffold tests only verify that detect() is reachable with and
without a bootloader. A TODO block documents what the full matrix
must cover once the first non-Normal variant lands, so the tests
do not quietly stay green without expansion.

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
- BootContext doc: trimmed to essential pre-conditions
- BootMode/detect: removed PR-version scaffolding, kept variant hints
- mode test block: removed future-variant TODO
- lib.rs: removed single_match scaffolding comment
- omnect_device_service.rs: removed 'legacy bash' phrasing from doc
- grub.rs: removed 'matches legacy behaviour' from inline comments
- types.rs: removed 'legacy bash script encoding' phrasing
- boot_sequence.rs: removed 'Legacy bash never runs' phrasing
- project-context.md: added planned BootMode variants (§8) + doc standards (§9)

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
…t_remaining

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
- Remove forward-scaffolding sentence from mount_remaining_partitions docstring about resize_data_if_needed
- Rename FSCK_LOG_DIR to FSCK_LOG_SUBDIR and compose path from mount_points::DATA_PARTITION
- Remove what-narrating comments (mount factory, mount data)
- Simplify mount_core_partitions docstring to not enumerate partitions
- Add safety comments to TempDir::new().unwrap() calls in tests

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
- Add src/mode/resize_data.rs with resize_if_needed, partition_number,
  find_extended_partition, ensure_mtab, run_e2fsck, run_cmd
- Register pub mod resize_data in src/mode/mod.rs (cfg resize-data)
- Add BootloaderEnvKey::ResizedData variant to src/bootloader/mod.rs
  (guards omnect_resized_data env var used to run exactly once)
- GPT: moves backup header before resizepart (sgdisk -e)
- DOS: resizes extended container before logical data partition

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
- Add NonUtf8Path error variant to ResizeDataError for non-UTF-8 device paths
- Replace all unwrap_or("") path conversions with proper error propagation
- Move PartitionName import to top-level use block
- Extract parse_extended_partition_nr helper; tests call real parsing logic
- Fix trailing space in run_cmd log when args is empty
- Update ensure_mtab docstring to be more accurate
- Add justification comment to _rootfs parameter

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
…rmal

Call resize_if_needed (feature-gated) before mounting data, then
mount_remaining_partitions with deferred propagation so persist_fsck_results
always runs regardless of mount outcome. Remove #[allow(dead_code)] from
layout field now that it is actively used.

resize_if_needed now takes &mut Option<Box<dyn Bootloader>> rather than
Option<&mut dyn Bootloader> to avoid the NLL drop-check extending the borrow
to the owner's destructor.

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
resize-data is implemented as a first-boot step within Normal, not a
separate boot mode. The placeholder comment was misleading.

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
- Mark resize-data as implemented in README feature table
- Remove data partition auto-resize from "not yet implemented" list
- Add resize-data test combinations to README and project-context
- Add resize_data.rs to module structure in project-context
- Add resize-data to feature flags table in project-context
- Remove Resize BootMode variant from planned features (resize runs
  as a first-boot step within Normal, not as a separate boot mode)

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
@JoergZeidler JoergZeidler requested a review from JanZachmann May 6, 2026 09:37
Copy link
Copy Markdown

@JanZachmann JanZachmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review of the resize-data feature. Functionally sound and well-isolated behind the feature flag. The two items I would block on are the cert/factory mount-order change (intentional? please document) and the redundant .map_err(InitramfsError::ResizeData) calls (cleanup, since #[from] already does the conversion). Other comments are minor style/test-coverage items. See inline.

Comment thread src/filesystem/boot_sequence.rs Outdated
Comment thread src/mode/resize_data.rs Outdated
Comment thread src/mode/resize_data.rs Outdated
Comment thread src/mode/resize_data.rs Outdated
Comment thread src/mode/resize_data.rs Outdated
Comment thread src/mode/resize_data.rs
Comment thread src/mode/resize_data.rs
Comment thread src/filesystem/boot_sequence.rs Outdated
- Restore factory → cert mount order in mount_remaining_partitions
- Remove redundant .map_err(); use ? with #[from] ResizeDataError
- Fix module doc-comment: use actual env key omnect_resized_data
- Remove unused _rootfs parameter from resize_if_needed
- Hoist CLI argument literals to named constants
- Add one-shot guard tests and ensure_mtab branch tests
- Remove duplicated // safe: TempDir::new() comments in tests

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
…ests

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
@JoergZeidler JoergZeidler requested a review from JanZachmann May 6, 2026 12:44
Copy link
Copy Markdown

@JanZachmann JanZachmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 8 review findings addressed. Verified at f50a030:

  • factory → cert mount order restored ✓
  • module doc-comment uses omnect_resized_data
  • _rootfs parameter removed (signature + caller in mode/normal.rs) ✓
  • redundant .map_err(InitramfsError::ResizeData) removed throughout — bare ? via #[from]
  • CLI literals hoisted to SGDISK_MOVE_BACKUP_HEADER, PARTED_RESIZEPART, PARTED_RESIZE_FULL, PARTED_PRINT, E2FSCK_FIX, RESIZE2FS_FORCE
  • one-shot guard tests added with MockBootloader
  • 6× duplicated safe: TempDir comments removed ✓

cargo test clean across all four resize-data feature combinations (grub,gpt,resize-data, grub,dos,resize-data, uboot,gpt,resize-data, uboot,dos,resize-data).

One non-blocking follow-up note left on the ensure_mtab thread — the new tests there don't actually call ensure_mtab(). Approving since that finding was originally optional and the function itself is correct.

Extract ensure_mtab_at(mtab, target) so tests can pass controlled paths.
Thin ensure_mtab() wrapper supplies the production consts.
Update tests to actually invoke ensure_mtab_at() instead of re-implementing
the logic inline.

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
@JoergZeidler JoergZeidler requested a review from mlilien May 6, 2026 13:14
Comment thread src/filesystem/boot_sequence.rs Outdated

/// Mount all required partitions in the correct order.
pub fn mount_partitions(
/// Mount the core partitions required before the bootloader can be created.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that reads funny: the bootloader loaded kernel and the kernel loaded omnect-os-init.
the init process will not create a bootloader. please ask your agent for renaming options.

Copy link
Copy Markdown
Contributor Author

@JoergZeidler JoergZeidler May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI answer to choose the correct method name.


The function constructs a Box<dyn Bootloader> — a Rust object that reads/writes the bootloader
environment (grubenv file or U-Boot fw_printenv/fw_setenv). It does not create a bootloader.

Naming options:

Name Notes
open_bootloader_env() Most precise — it opens the env storage
init_bootloader() Common systems pattern, slightly ambiguous
bootloader_env() Short noun-form factory
platform_bootloader() Emphasizes the build-time selection

Recommendation: open_bootloader_env() — directly describes what happens and aligns with the
existing BootloaderEnvKey / get_env / set_env naming in the codebase. Call site reads naturally:

let bootloader_opt = match open_bootloader_env() { ... }

open_bootloader_env sounds good to me. Documentation will be updated after finalize the method name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to open_bootloader_env...

Comment thread src/mode/resize_data.rs
Comment thread src/mode/resize_data.rs
],
)?;

// resize2fs requires a valid mtab entry; create a symlink to /proc/self/mounts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why wouldn't it exist. what was the reasoning for that? imho very unlikely it isn't there. please ask your agent.
not a change request, though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI answer:
In initramfs we're running before switch_root. The initramfs root is a minimal tmpfs/ramfs — /etc/mtab is not part of it. The real rootfs (where /etc/mtab
lives as a symlink to /proc/self/mounts) hasn't been switched to yet. resize2fs reads /etc/mtab to check whether the device is currently mounted; without
it, the check silently degrades or errors. The guard creates the symlink on first run so resize2fs can do its mount-state check correctly.

Comment thread src/lib.rs Outdated
Ok(bl) => Some(bl),
Err(e) => {
warn!("Bootloader unavailable: {e}; fsck results will not be persisted");
warn!("Bootloader unavailable: {e}; ODS update-validation will be skipped");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imho that warning makes no sense. we don't want to skip update-validation. imho that is not a warning its an unrecoverable error. you probably even don't have a valid root then. if not now, then at least at the next boot.
also the naming "Bootloader" is not good.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in case we have no access to the bootloader environment, we have no idea about performing update-validation or not. Should we stay in init process, or reboot the device, or try to boot the rootfs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning message is changed

Comment thread src/lib.rs Outdated
let mount_result = mount_partitions(&layout, rootfs, &mut ods_status);
mount_core_partitions(&layout, rootfs, &mut ods_status)?;

// Best-effort: a corrupted grubenv is a recoverable degraded-boot condition.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does it comment that for grubenv only? ... this is not the grub path, correct? its for all implementations of bootloader environment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's true, comment changed.

Comment thread src/mode/resize_data.rs
}

#[test]
fn test_ensure_mtab_creates_symlink_when_missing() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 tests for the mtab handling, which is most probably not necessary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mtab currently used. the parameterized helper exists and the tests are real — removing them would decrease coverage with no code benefit.

Comment thread src/mode/resize_data.rs
/// e2fsck exit code 1 means "errors were corrected" — that is a success
/// outcome for our purposes; we just need the filesystem to be consistent
/// before resize2fs runs.
fn run_e2fsck(dev: &Path) -> ResizeResult<()> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels like code duplication. why can't you reuse the available fsck implementation? in case errors where corrected, you have no fsck result.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

crate::filesystem::fsck implementation records CORRECTED as a distinct state in the bootloader
environment and requires a bootloader reference.
Here we need neither, this is a transient pre-resize check with no persistent result.

Comment thread src/mode/normal.rs
} = ctx;

#[cfg(feature = "resize-data")]
crate::mode::resize_data::resize_if_needed(layout, &mut bootloader)?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so modes are not mutual exclusive?
why is the resizing part of the "normal" bootmode? what is a bootmode which is not normal then?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resize-data is a transparent first-boot operation that runs during normal boot. New boot-mode will be present in features factory-reset/flash-mode...

Comment thread src/mode/mod.rs
pub enum BootMode {
Normal,
// FactoryReset(FactoryResetConfig)
// Resize
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so wie only have normal bootmode?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, now we have only normal mode, resize-data is part during first-boot in normal mode. Next feature factory-reset will be added as own mode.

Comment thread src/mode/resize_data.rs
@@ -0,0 +1,417 @@
//! Data partition auto-resize
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is that in the mode dir?
it seems it didn't get a mode, as suggested by the previous PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resize_data.rs is in mode/ because it was designed alongside the boot-mode dispatch architecture. Since resize is not a BootMode variant, a case could be made for moving it to filesystem/ or a standalone resize/ module. However, it uses Bootloader and PartitionLayout the same way mode handlers do — and future modes (factory-reset) follow the same pattern.

boot_sequence.rs: doc comment on mount_core_partitions no longer scopes
the ordering requirement to GRUB only — the function runs on all builds.
lib.rs: grubenv → bootloader environment in degraded-boot comment;
inline comment retains GRUB-specific parenthetical as explanation.

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
The function opens the platform bootloader environment interface; it does
not create a bootloader. The new name aligns with the existing get_env /
set_env / BootloaderEnvKey naming in the codebase.

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
…v rename

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
Rephrase the degraded-boot comment to say 'unavailable' rather than
'corrupted' (the cause may be anything), and remove the inline list of
skipped subsystems from the warning message — the message is already
self-describing via the 'booting in degraded mode' suffix.

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
The stubs for FactoryReset and FlashMode are added by their respective
feature branches. Remove them here to reduce noise.

Signed-off-by: Joerg Zeidler <62105035+JoergZeidler@users.noreply.github.com>
@JoergZeidler JoergZeidler requested a review from mlilien May 8, 2026 13:14
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.

3 participants