Skip to content

Conversation

@bennyz
Copy link
Member

@bennyz bennyz commented Feb 2, 2026

Use the fastboot CLI and add support for flashing from OCI.

This extracts the OCI artifact and proceeds to flash with the existing fastboot CLI tool

Summary by CodeRabbit

  • New Features

    • Fastboot flashing command for OCI images with device detection, partition mapping, timeouts, credentials, and progress controls.
    • New OCI extraction capabilities to pull files by annotations or patterns into memory or to disk.
    • Automotive image metadata accessors for partition, target, arch, and decompressed-size.
  • Tests

    • End-to-end OCI extraction tests covering tar layers, direct files, and partial results.
  • Documentation

    • Added OCI Images usage and fastboot examples.

@coderabbitai
Copy link

coderabbitai bot commented Feb 2, 2026

Warning

Rate limit exceeded

@bennyz has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 20 minutes and 55 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Adds an automotive OCI-annotation module, a fastboot-based flashing workflow and options, expanded OCI extraction APIs (in-memory, to-dir, and annotation-driven), CLI Fastboot command wiring, OCI extraction tests, and README docs; updates module re-exports to expose the new APIs.

Changes

Cohort / File(s) Summary
Automotive annotations
src/fls/automotive.rs
New public module with OCI annotation key constants, target constants, and helpers to extract partition name, decompressed size, and target from layer annotations.
Fastboot flow
src/fls/fastboot.rs
New internal module implementing fastboot flashing: public flash_from_fastboot, FastbootError, TempDirGuard, device detection, timed CLI command execution, partition flashing orchestration, OCI extraction integration, and tests.
FLS module & exports
src/fls/mod.rs
Registers automotive and internal fastboot modules; re-exports flash_from_fastboot and adds FastbootOptions to published options.
Fastboot options
src/fls/options.rs
Adds public FastbootOptions with Default and From<&FastbootOptions> for HttpClientOptions implementations.
OCI extraction APIs
src/fls/oci/from_oci.rs
Large additions: streaming/decompression/tar helpers and four new public async APIs for extracting files from OCI images (in-memory and to-dir) and annotation-driven extraction.
OCI re-exports
src/fls/oci/mod.rs
Expanded public re-exports to expose the new OCI extraction functions alongside flash_from_oci.
CLI integration
src/main.rs
Adds parse_target_mapping, a new Commands::Fastboot variant, CLI parsing/wiring to build FastbootOptions, and invocation/exit handling for fls::flash_from_fastboot.
Tests & helpers
tests/oci_extract.rs, tests/common/mod.rs
New OCI extraction tests with TLS/mock registry and tar/blob helpers; added #[allow(dead_code)] to a test helper.
Docs
README.md
Adds OCI Images subsection with examples for flashing from registries and via fastboot, and credential usage examples.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as "main.rs"
    participant Fastboot as "src/fls/fastboot.rs"
    participant Oci as "src/fls/oci/from_oci.rs"
    participant Registry as "OCI Registry"
    participant Device as "Fastboot Device"

    User->>CLI: invoke fastboot command (image_ref + options)
    CLI->>Fastboot: flash_from_fastboot(image_ref, options)
    Fastboot->>Fastboot: detect device (serial or auto)
    Fastboot->>Device: fastboot probe / id
    Device-->>Fastboot: device id/status

    alt annotations auto-detect
        Fastboot->>Oci: extract_files_by_annotations(image)
    else explicit mappings
        Fastboot->>Oci: extract_files_from_oci_image(image, targets)
    end

    Oci->>Registry: fetch manifest & blobs
    Registry-->>Oci: blobs/layers
    Oci->>Oci: stream, decompress, tar-extract files
    Oci-->>Fastboot: partition -> file mappings

    Fastboot->>Device: fastboot flash <partition> <file> (loop)
    Device-->>Fastboot: command result
    Fastboot->>Device: fastboot continue/reboot
    Device-->>Fastboot: ack
    Fastboot-->>CLI: success/failure
    CLI->>User: exit code / message
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • mangelajo

Poem

🐰
I hopped through layers, sniffed each tar and gzip,
found partitions tucked beneath OCI skies.
Fastboot hummed, I nudged each file in place,
devices blinked — firmware snug in its space.
carrots approved, I bounced off to trace. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding fastboot support for OCI images, which is the primary objective of this pull request.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@bennyz bennyz force-pushed the fastboot branch 2 times, most recently from 21bb739 to 322e9da Compare February 2, 2026 11:13
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@src/fls/automotive.rs`:
- Around line 5-21: Remove the unused public constant PARTITION_PREFIX from the
annotations module: delete the line defining pub const PARTITION_PREFIX so the
module only exposes the actually used constants (e.g., PARTITION_ANNOTATION).
Verify there are no remaining references to PARTITION_PREFIX (functions like
extract_partition_name use PARTITION_ANNOTATION) and run the build to ensure no
missing usages after removal.

In `@src/fls/fastboot.rs`:
- Around line 269-305: extract_files_by_patterns_to_dir currently calls
find_partition_file_in_oci_to_dir per mapping which re-downloads/extracts the
OCI image for each partition; instead perform a single extraction pass and reuse
results: create or call a helper (e.g., extract_all_partition_files_from_oci or
modify find_partition_file_in_oci_to_dir to accept all patterns) that takes
image_ref, options, output_dir and the full set of options.partition_mappings
and returns a HashMap<String, PathBuf> of all found files; then populate
partition_files from that returned map, check for missing mappings and return
the same error messages when entries are absent, and only perform one OCI
extraction to avoid O(N) downloads.
- Around line 240-246: Fastboot's OCI extraction is not passing registry
credentials — add username: Option<String> and password: Option<String> to the
FastbootOptions struct, ensure they are populated from the CLI parsing path, and
thread them into the OciOptions constructed where
extract_files_from_oci_image_to_dir and extract_files_by_annotations_to_dir are
called (replace username: None/password: None with
FastbootOptions.username/FastbootOptions.password). Update any
constructors/creators of FastbootOptions and usages so RegistryClient
(src/fls/oci/registry.rs:33) receives the credentials via OciOptions.
- Around line 114-146: The run_fastboot_command function currently builds a
std::process::Command which leaves the fastboot child running if the tokio
timeout fires; replace usage of std::process::Command with
tokio::process::Command in run_fastboot_command and call .kill_on_drop(true) on
the Command builder (the cmd variable) before calling .output(), keeping
stdout(Stdio::piped()) and stderr(Stdio::piped()) as-is so the spawned child is
automatically killed if the async timeout cancels the future.

In `@src/fls/sparse_write.rs`:
- Around line 1-56: The pattern match in execute_write_commands uses the
non-existent struct form WriteCommand::Seek { offset, skipped } — change it to
the tuple form WriteCommand::Seek(offset) (matching the enum in simg.rs and
from_oci.rs) and remove the use of the now-missing skipped variable and its
addition to total_skipped; if you still need to track skipped bytes compute
skipped as the difference between the target offset and the writer's current
position (e.g., skipped = offset.saturating_sub(current_pos)) by querying an
AsyncBlockWriter position method (or change AsyncBlockWriter::seek to return the
previous offset) before calling writer.seek(offset). Ensure references are
updated in execute_write_commands and any debug output uses only offset (and
computed skipped if you implement the position query).
🧹 Nitpick comments (7)
src/fls/sparse_write.rs (1)

89-94: Consider removing println! from library helper code.

Using println! directly in a shared helper module couples it to stdout output. Callers using this as a library may prefer to control logging/output themselves. Consider either:

  • Removing the println and relying on the debug eprintln
  • Accepting a callback or using a logging framework
  • Returning format detection info and letting callers handle output
src/main.rs (2)

85-131: Consider adding buffer size arguments for consistency.

The Fastboot command lacks buffer_size and write_buffer_size arguments that exist in FromUrl. While hardcoded defaults (128 MB each, set in the handler) may work, exposing these as CLI options would provide consistency and allow users to tune performance for their environment.


346-365: Address TODO and clarify hardcoded values.

  1. Line 350: The device field is set to "fastboot" which is a placeholder. Consider documenting this is intentional (fastboot doesn't use block devices) or removing it from FlashOptions for this use case.

  2. Line 360: There's a TODO: Add target CLI argument - this should be tracked or implemented. The target field in FastbootOptions appears unused.

  3. Lines 351-352: Buffer sizes are hardcoded to 128 MB without CLI arguments, unlike FromUrl.

Would you like me to open an issue to track adding the --target CLI argument?

src/fls/automotive.rs (1)

37-44: Consider logging parse failures for decompressed size.

The extract_decompressed_size function silently discards parse errors via .parse().ok(). For debugging purposes, it might be helpful to log when a decompressed-size annotation exists but contains an invalid value.

 pub fn extract_decompressed_size(
     layer_annotations: &std::collections::HashMap<String, String>,
 ) -> Option<u64> {
     layer_annotations
         .get(annotations::DECOMPRESSED_SIZE)
-        .and_then(|s| s.parse().ok())
+        .and_then(|s| {
+            s.parse().map_err(|e| {
+                eprintln!("[WARN] Invalid decompressed-size annotation '{}': {}", s, e);
+                e
+            }).ok()
+        })
 }
src/fls/oci/from_oci.rs (2)

458-542: Use automotive module constants instead of hardcoded annotation strings.

The annotation key "automotive.sdv.cloud.redhat.com/partition" is hardcoded at lines 481 and 568, but the automotive::annotations module defines PARTITION_ANNOTATION for this purpose. Using the constant improves maintainability.

♻️ Proposed refactor

Add the import at the top of the file:

use crate::fls::automotive::annotations;

Then update the hardcoded strings:

-            if let Some(partition) = annotations.get("automotive.sdv.cloud.redhat.com/partition") {
+            if let Some(partition) = annotations.get(crate::fls::automotive::annotations::PARTITION_ANNOTATION) {

Similarly for "org.opencontainers.image.title" - consider adding this to the automotive annotations module if it's automotive-specific, or to a shared OCI constants module.

Also applies to: 544-596


598-757: Code duplication with sparse_write.rs - consider consolidation.

These functions (execute_write_commands, process_sparse_data, handle_detected_format, process_buffer_with_format_detection, finalize_format_at_eof) duplicate similar functionality in the new src/fls/sparse_write.rs module, but with different signatures:

Function from_oci.rs sparse_write.rs
execute_write_commands Returns io::Result<()> Returns io::Result<u64> (skipped bytes)
WriteCommand::Seek Seek(offset) Seek { offset, skipped }
handle_detected_format Returns Option<SparseParser> Returns (Option<SparseParser>, u64)

The PR description mentions sparse_write.rs is for "both URL-based and OCI-based flash paths," but from_oci.rs maintains its own implementations. Consider migrating from_oci.rs to use the shared helpers to avoid divergence.

src/fls/fastboot.rs (1)

179-215: Verify flashing order isn’t device-sensitive.
Alphabetical sorting can violate required flashing sequences on some devices. If order matters, consider accepting an explicit order (e.g., Vec of tuples) or preserving annotation order.

@bennyz bennyz force-pushed the fastboot branch 2 times, most recently from 7752491 to cc15252 Compare February 2, 2026 12:07
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/fls/oci/from_oci.rs`:
- Around line 86-224: The current branch in extract_files_from_oci_image relies
on expect_tar_archive(target_files) (which checks target filename extensions) to
decide tar vs direct-file, causing mis-detection; instead detect tar from the
layer content after downloading/decompressing: in extract_files_from_oci_image,
after producing decompressed_data, replace the expect_tar_archive check with a
content-based check (e.g., check manifest layer media type if available via
manifest.get_single_layer().media_type(), or inspect decompressed_data for tar
magic by verifying decompressed_data.len() >= 262 and
&decompressed_data[257..262] == b"ustar" or attempting
tar::Archive::new(Cursor::new(&decompressed_data)).entries() and treating it as
tar only if that succeeds), and only fall back to the direct-file code path when
the content is not a valid tar; keep expect_tar_archive but stop using it for
runtime detection in extract_files_from_oci_image.
- Around line 150-167: The in-memory extraction paths (functions
extract_files_from_oci_image and extract_files_by_annotations) only detect gzip
via hardcoded magic bytes and therefore miss XZ-compressed layers; update both
functions to use the existing helpers is_gzip_prefix() and is_xz_prefix() to
detect format, and add an XZ decompression branch that mirrors the gzip branch
(use the project's XZ decoder utility or xz2::read::XzDecoder to
read-from-memory and call read_to_end into a Vec, returning the decompressed
bytes), keeping the gzip branch but replacing the raw magic-byte check with
is_gzip_prefix(); ensure errors are mapped consistently as done for gzip
decompression.

In `@src/main.rs`:
- Around line 128-130: The CLI's file_pattern is parsed but never passed into
the fastboot extraction logic; add a file_pattern: Option<String> field to
FastbootOptions, set it from the CLI arg (the existing file_pattern variable),
and propagate that value into the OciOptions used by the fastboot extraction
path (where you build the extraction options/struct for OCI handling). Update
all places that construct FastbootOptions (including the other occurrence around
lines 346-367) to include the new field, and ensure the fastboot extraction
function consumes FastbootOptions and forwards file_pattern into the OciOptions
so the pattern is actually used during OCI layer extraction.

@bennyz bennyz force-pushed the fastboot branch 2 times, most recently from 765dd9a to a43fb8e Compare February 2, 2026 12:31
@bennyz bennyz requested a review from mangelajo February 2, 2026 13:02
Copy link
Member Author

Choose a reason for hiding this comment

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

probably better suited for the jumpstarter driver...

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/fls/fastboot.rs`:
- Line 61: FastbootOptions.force_sparse is accepted and only printed (println!("
Force sparse: {}", options.force_sparse)), but
flash_partitions_with_fastboot_cli does not act on it; either implement sparse
conversion when force_sparse is true or remove the unused option. Update
flash_partitions_with_fastboot_cli to check FastbootOptions::force_sparse and,
when true, convert each partition image to a sparse format before invoking
fastboot (e.g., call a helper like convert_image_to_sparse(image_path) or use
the project's sparse conversion utility), ensure converted files are used for
flashing and cleaned up afterwards, and add a TODO/comment if you choose to
defer implementation; also ensure the same change/handling is applied where the
option is printed around lines 96–97.

In `@src/fls/oci/from_oci.rs`:
- Around line 525-527: The code directly slices layer.digest with
&layer.digest[0..12], which can panic for short digests; update the usage in
from_oci.rs to perform a safe substring retrieval (e.g., use String::get(0..12)
and fall back to the entire layer.digest when the slice is None) wherever the
short digest preview is produced so layer.digest is never indexed out-of-bounds.
🧹 Nitpick comments (5)
src/fls/oci/from_oci.rs (2)

96-115: Good content-based tar detection implementation.

The dual-approach detection (ustar magic bytes at offset 257 + fallback to archive parsing) is robust. However, there's a minor inconsistency with the existing is_tar_archive in src/fls/magic_bytes.rs (which only checks ustar magic at offset 257-262). Consider consolidating these implementations to avoid divergence.


520-527: Use annotation constants from the automotive module.

The annotation key "automotive.sdv.cloud.redhat.com/partition" is hardcoded here and at line 610, but automotive::annotations::PARTITION_ANNOTATION was added for this purpose. Using the constant improves maintainability.

Suggested fix
+use crate::fls::automotive::annotations::PARTITION_ANNOTATION;
+
 // In extract_files_by_annotations (line 520):
-            if let Some(partition) = annotations.get("automotive.sdv.cloud.redhat.com/partition") {
+            if let Some(partition) = annotations.get(PARTITION_ANNOTATION) {

 // In extract_files_by_annotations_to_dir (line 610):
-            if let Some(partition) = annotations.get("automotive.sdv.cloud.redhat.com/partition") {
+            if let Some(partition) = annotations.get(PARTITION_ANNOTATION) {
src/fls/fastboot.rs (1)

186-186: Consider making the fastboot path configurable.

The fastboot binary path is hardcoded as "fastboot". For environments where fastboot is not in PATH or a specific version is needed, consider adding an option to FastbootOptions to override this.

src/main.rs (2)

343-343: Hardcoded placeholder device value.

The device field is set to "fastboot" with a comment noting it's not used. Since FlashOptions::device is required but irrelevant for fastboot operations, consider either:

  1. Making device optional in FlashOptions, or
  2. Using an empty string or a more explicit placeholder

This is minor since the value is indeed unused in the fastboot path.


353-353: Address or remove the TODO comment.

The target: None with // TODO: Add target CLI argument suggests incomplete functionality. Based on the code, target in FastbootOptions appears intended for something different from partition_mappings. Either implement the feature, remove the field from FastbootOptions, or clarify what target represents vs partition_mappings.

Would you like me to help clarify the purpose of the target field or open an issue to track this?

println!(" Device serial: {}", serial);
}
println!(" Partition mappings: {:?}", options.partition_mappings);
println!(" Force sparse: {}", options.force_sparse);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

force_sparse option is accepted but not implemented.

The force_sparse field is printed at line 61 and accepted in FastbootOptions, but the actual flashing logic in flash_partitions_with_fastboot_cli doesn't use it. If sparse image conversion is needed, it should be implemented; otherwise, consider removing the option or adding a TODO comment.

Also applies to: 96-97

🤖 Prompt for AI Agents
In `@src/fls/fastboot.rs` at line 61, FastbootOptions.force_sparse is accepted and
only printed (println!("  Force sparse: {}", options.force_sparse)), but
flash_partitions_with_fastboot_cli does not act on it; either implement sparse
conversion when force_sparse is true or remove the unused option. Update
flash_partitions_with_fastboot_cli to check FastbootOptions::force_sparse and,
when true, convert each partition image to a sparse format before invoking
fastboot (e.g., call a helper like convert_image_to_sparse(image_path) or use
the project's sparse conversion utility), ensure converted files are used for
flashing and cleaned up afterwards, and add a TODO/comment if you choose to
defer implementation; also ensure the same change/handling is applied where the
option is printed around lines 96–97.

Comment on lines 525 to 529
&layer.digest[0..12],
partition
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential panic if layer digest is shorter than 12 characters.

The slice &layer.digest[0..12] will panic if the digest is shorter than 12 characters. While SHA256 digests in practice are always 64+ characters (with sha256: prefix), defensive coding would use .get(0..12) or similar.

Suggested fix
-                        &layer.digest[0..12],
+                        layer.digest.get(0..12).unwrap_or(&layer.digest),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
&layer.digest[0..12],
partition
);
layer.digest.get(0..12).unwrap_or(&layer.digest),
partition
);
🤖 Prompt for AI Agents
In `@src/fls/oci/from_oci.rs` around lines 525 - 527, The code directly slices
layer.digest with &layer.digest[0..12], which can panic for short digests;
update the usage in from_oci.rs to perform a safe substring retrieval (e.g., use
String::get(0..12) and fall back to the entire layer.digest when the slice is
None) wherever the short digest preview is produced so layer.digest is never
indexed out-of-bounds.

@mangelajo
Copy link
Member

Ok, I think it's a good intermediate step until we can really stream it to USB.

In the meanwhile, can you add some tests:

https://github.com/jumpstarter-dev/fls/tree/main/tests

and update the README.md to cover the OCI stuff?

@bennyz
Copy link
Member Author

bennyz commented Feb 2, 2026

Ok, I think it's a good intermediate step until we can really stream it to USB.

In the meanwhile, can you add some tests:

main/tests

and update the README.md to cover the OCI stuff?

Sure, this is a working "draft"

@bennyz bennyz force-pushed the fastboot branch 3 times, most recently from a65b2a0 to 4cac93e Compare February 3, 2026 09:42
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/fls/oci/from_oci.rs`:
- Around line 129-139: The code currently prints layer info but does not reject
Zstd-compressed layers, which can cause in-memory extraction to return
compressed data or break tar detection; add an early guard after obtaining layer
via manifest.get_single_layer() (use the layer.compression() result) that
returns an error if compression is Zstd (or any unsupported algorithm) with a
clear message; apply the same guard in the annotation extractors/to-dir path so
both in-memory extraction and to-dir extraction consistently reject Zstd layers
instead of attempting to treat them as raw/tar data.
- Around line 623-625: partition is derived from OCI annotations and is
untrusted; calling output_dir.join(format!("{}.img", partition)) can lead to
path traversal. Before building output_path, validate or normalize partition
(e.g., reject or sanitize any input containing path separators, "..", or
absolute path markers, and constrain to a safe whitelist like [A-Za-z0-9._-] or
run it through a filename sanitizer) and store the result in a new
sanitized_name variable; then use output_dir.join(format!("{}.img",
sanitized_name)) when calling stream_blob_to_file and inserting into
partition_files. Ensure the validation is performed where partition is used to
create output_path (the code around
output_path/stream_blob_to_file/partition_files) and fail fast with an error if
the annotation yields an invalid name.
🧹 Nitpick comments (1)
src/main.rs (1)

334-354: Resolve the TODO for target CLI argument or remove the unused field.
Line 348 leaves a TODO that suggests incomplete behavior in the CLI surface.

If you want, I can propose an implementation for the target CLI arg and the wiring into FastbootOptions.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/fls/oci/from_oci.rs`:
- Around line 517-571: The current code skips layers that lack
org.opencontainers.image.title; change the logic so extraction runs whenever
annotations contains "automotive.sdv.cloud.redhat.com/partition" even if title
is missing by computing a fallback title (e.g., using the layer.digest prefix or
a generic "unknown" label) and using that fallback for logs and for the key when
inserting into partition_files; keep the same download/decompress flow
(client.get_blob_stream, is_gzip_prefix/is_xz_prefix, GzDecoder/XzDecoder) and
ensure partition_files.insert(partition.clone(), decompressed_data) still
happens whether title was original or fallback.
🧹 Nitpick comments (1)
src/main.rs (1)

334-354: Resolve the TODO for target or remove it.
The comment suggests a missing CLI argument; either wire the option through or drop the TODO to avoid a lingering partial feature.

@bennyz bennyz force-pushed the fastboot branch 3 times, most recently from 73994c7 to 2927037 Compare February 3, 2026 10:46
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/fls/oci/from_oci.rs`:
- Around line 647-658: The function ensure_supported_layer_compression currently
only allows LayerCompression::None and LayerCompression::Gzip and therefore
rejects XZ-compressed layers even though XZ is supported elsewhere; update the
match in ensure_supported_layer_compression to include LayerCompression::Xz (or
the exact enum variant used for XZ) alongside None and Gzip so XZ returns
Ok(()), keeping the existing error message for other variants and referencing
the function name ensure_supported_layer_compression and the LayerCompression
enum to locate the change.
- Around line 263-325: The compression detection in stream_blob_to_file
currently inspects only the first chunk which can be shorter than the gzip/xz
magic and cause false negatives; change the logic to accumulate incoming chunks
until you have at least 6 bytes (the max magic length) before deciding
is_gzip/is_xz. Concretely: in stream_blob_to_file (and the similar reader
function around lines 327-426), replace the single-first-chunk handling by
repeatedly pulling from stream.next() (but not dropping chunks) into a small
Vec<u8> buffer until buffer.len() >= 6 or stream ends; run
is_gzip_prefix/is_xz_prefix against that buffered prefix, then send the buffered
data as the first item(s) into the mpsc tx (preserving original chunk boundaries
or as a single Bytes) before spawning the forward task to continue streaming the
remaining chunks. Ensure error handling and the existing channel/decoder flow
remain unchanged.

@bennyz bennyz force-pushed the fastboot branch 3 times, most recently from ec3973d to 0b00a32 Compare February 3, 2026 11:36
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/fls/oci/from_oci.rs`:
- Around line 556-566: The slices &layer.digest[0..12] can panic if digest is
shorter than 12 bytes; compute a safe preview string first (e.g., let preview =
layer.digest.get(0..12).unwrap_or(&layer.digest) or build a preview by taking up
to 12 chars) and then use that preview in the title fallback and the println
instead of direct slicing; update the code around the title construction and the
println in from_oci.rs (references: layer.digest, annotations.get(...),
ensure_supported_layer_compression, and the println call) to use this safe
preview variable.
🧹 Nitpick comments (1)
src/fls/oci/from_oci.rs (1)

86-105: Consider reusing is_tar_archive from magic_bytes module.

A similar helper is_tar_archive already exists in src/fls/magic_bytes.rs (see relevant snippets). This local implementation adds fallback parsing but creates code duplication. Consider either:

  1. Reusing the existing helper if the magic-byte check suffices, or
  2. Consolidating both into a single shared implementation in magic_bytes.rs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/fls/oci/from_oci.rs`:
- Around line 661-666: The println! uses direct slicing &layer.digest[0..12]
which can panic; update the extraction message in from_oci.rs to use a safe
slice retrieval (e.g., layer.digest.get(..12) with a fallback) before formatting
so it never indexes out of bounds—modify the println! call that references
title, layer.digest and partition to use the safe-preview value instead of
direct slicing.

In `@tests/oci_extract.rs`:
- Around line 326-380: Update the doc comment on the function
extract_files_from_oci_image_to_dir to explicitly document its partial-success
semantics: state that it returns a HashMap containing only files it successfully
extracted, that missing/unspecified files are not treated as errors at this
layer, and that callers are responsible for validating that all required files
are present and returning errors if needed; modify the function-level docstring
immediately above extract_files_from_oci_image_to_dir to include these three
bullet points in concise form.
🧹 Nitpick comments (6)
src/fls/oci/from_oci.rs (1)

87-106: Remove duplicate is_tar_archive function - use existing helper from magic_bytes.rs.

This function duplicates is_tar_archive from src/fls/magic_bytes.rs (lines 34-38 per the relevant code snippets). The existing helper uses safe .get() access for the magic bytes check. Reuse it to avoid code duplication and ensure consistent behavior.

♻️ Suggested refactor
-fn is_tar_archive(data: &[u8]) -> bool {
-    if data.len() >= 262 && &data[257..262] == b"ustar" {
-        return true;
-    }
-
-    if data.len() < 512 {
-        return false;
-    }
-
-    let cursor = std::io::Cursor::new(data);
-    let mut archive = tar::Archive::new(cursor);
-    match archive.entries() {
-        Ok(mut entries) => match entries.next() {
-            Some(Ok(_)) => true,
-            Some(Err(_)) => false,
-            None => true,
-        },
-        Err(_) => false,
-    }
-}
+use crate::fls::magic_bytes::is_tar_archive;
README.md (1)

98-134: Good documentation for OCI functionality.

The new OCI Images section provides clear examples for both block device flashing and fastboot operations. The credential handling documentation (including FLS_REGISTRY_PASSWORD env var) is a good security practice.

Consider adding documentation for the --timeout option in the fastboot section, as it's an important operational parameter (default 30 seconds may be insufficient for large partitions).

📝 Suggested documentation addition
 Registry credentials can be provided with `-u/--username` and `-p/--password`
 (`FLS_REGISTRY_PASSWORD` env var is supported for the password).
+
+Additional options:
+- `--timeout <SECONDS>` - Timeout for fastboot operations (default: 30)
+- `--device-serial <SERIAL>` - Target specific device when multiple are connected
src/fls/automotive.rs (1)

1-49: LGTM - Clean automotive annotations module.

The module provides well-structured OCI annotation accessors with appropriate Option semantics for missing or invalid values. The past review comment about removing PARTITION_PREFIX has been addressed.

Minor observation: The targets submodule constants (RIDESX4, AUTOSD) are unused in the codebase. Consider removing them if they're not needed, or add a TODO comment if they're intended for future use.

src/main.rs (2)

85-125: Missing --skip-reboot CLI flag.

The FastbootOptions struct includes a skip_reboot field that controls post-flash reboot behavior, but this isn't exposed in the CLI. Users cannot prevent the device from rebooting after flashing.

Consider adding:

/// Skip rebooting the device after flashing
#[arg(long)]
skip_reboot: bool,

348-348: Address TODO: Wire target CLI argument.

The target: None with a TODO suggests this feature is intentionally deferred. Consider either:

  1. Adding the CLI argument now for completeness, or
  2. Creating a tracking issue for this work

Would you like me to open an issue to track adding the target CLI argument?

tests/oci_extract.rs (1)

64-65: Consider graceful TLS handshake error handling.

The unwrap() on tls_acceptor.accept(stream).await will panic if a TLS handshake fails, potentially causing confusing test failures. For test robustness, consider:

let Ok(tls_stream) = tls_acceptor.accept(stream).await else {
    return; // Skip malformed connections
};

This is a minor concern for test code, but could help debugging if certificate issues arise.

Use the fastboot CLI and add support for flashing from OCI.
Currently we extract the OCI image on the host, and fastboot CLI will perform the flashing as it already does.

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Assisted-by: claude-opus-4.5
include oci flashing and fastboot subcommand

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
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.

2 participants