summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYecheng Zhao <zyecheng@google.com>2024-04-10 20:51:55 +0000
committerYecheng Zhao <zyecheng@google.com>2024-04-17 18:44:25 +0000
commite20b165fe58862b50163cacbff64da8f4cbe42ae (patch)
tree44a8186dabf7b8f1329e4318f532b2acf8cb99af
parente1061624d04c39a420f180fbefee5931b64dd69c (diff)
downloadlibbootloader-e20b165fe58862b50163cacbff64da8f4cbe42ae.tar.gz
Add GBL interfaces for block device, console, boot
Adds interfaces in GblOps for acessing platform block devices, reading and writing platform console and platform specific boot. Also sets up basic integration tests for libgbl. Bug: b/334962166 Change-Id: I2aa1974599af47a619ca3b7e905f6b8055f5951a
-rw-r--r--gbl/efi/src/error.rs69
-rw-r--r--gbl/efi/src/utils.rs10
-rw-r--r--gbl/libgbl/BUILD19
-rw-r--r--gbl/libgbl/src/error.rs144
-rw-r--r--gbl/libgbl/src/fastboot/vars.rs4
-rw-r--r--gbl/libgbl/src/lib.rs60
-rw-r--r--gbl/libgbl/src/ops.rs143
-rw-r--r--gbl/libgbl/testdata/gen_test_data.py (renamed from gbl/libgbl/testdata/gen_sparse_test_bin.py)39
-rwxr-xr-xgbl/libgbl/testdata/gen_writeback_test_bin.sh24
-rw-r--r--gbl/libgbl/testdata/sparse_test.binbin24704 -> 24704 bytes
-rw-r--r--gbl/libgbl/testdata/sparse_test_blk1024.binbin24704 -> 24704 bytes
-rw-r--r--gbl/libgbl/testdata/sparse_test_raw.binbin57344 -> 57344 bytes
-rw-r--r--gbl/libgbl/testdata/writeback_test_disk.binbin65536 -> 65536 bytes
-rw-r--r--gbl/libgbl/testdata/zircon_a.binbin0 -> 16384 bytes
-rw-r--r--gbl/libgbl/testdata/zircon_b.binbin0 -> 16384 bytes
-rw-r--r--gbl/libgbl/testdata/zircon_gpt.binbin0 -> 131072 bytes
-rw-r--r--gbl/libgbl/testdata/zircon_r.binbin0 -> 16384 bytes
-rw-r--r--gbl/libgbl/tests/integration_tests.rs170
-rw-r--r--gbl/libstorage/src/lib.rs40
-rw-r--r--gbl/libstorage/src/multi_blocks.rs40
-rw-r--r--gbl/libstorage/src/testlib.rs26
-rw-r--r--gbl/tests/BUILD1
-rwxr-xr-x[-rw-r--r--]gbl/tools/gen_gpt_disk.py0
23 files changed, 568 insertions, 221 deletions
diff --git a/gbl/efi/src/error.rs b/gbl/efi/src/error.rs
index 601bf04..0d56949 100644
--- a/gbl/efi/src/error.rs
+++ b/gbl/efi/src/error.rs
@@ -20,6 +20,7 @@ use efi::EfiError;
use fastboot::TransportError;
use fdt::FdtError;
use gbl_storage::StorageError;
+use libgbl::composite_enum;
use misc::BcbError;
use smoltcp::socket::tcp::{ListenError, RecvError, SendError};
use zbi::ZbiError;
@@ -40,74 +41,6 @@ pub enum EfiAppError {
Unsupported,
}
-/// A convenient macro for declaring a composite enum type that simply wraps other types as
-/// entries. It auto-generate `From<...>` implementation for each entry type. i.e.:
-///
-/// ```rust
-/// composite_enum! {
-/// pub enum MyEnum {
-/// Usize(usize),
-/// I64(i64),
-/// }
-/// }
-/// ```
-///
-/// expands to
-///
-/// ```rust
-/// pub enum MyEnum {
-/// Usize(usize),
-/// I64(i64),
-/// }
-///
-/// impl From<usize> for MyEnum {
-/// fn from(entry: usize) -> MyEnum {
-/// MyEnum::Usize(entry)
-/// }
-/// }
-///
-/// impl From<i64> for MyEnum {
-/// fn from(entry: i64) -> MyEnum {
-/// MyEnum::I64(entry)
-/// }
-/// }
-/// ```
-///
-/// The macro assumes that each entry is a different type.
-macro_rules! composite_enum {
- // Top level macro entry. Match enum declaration code and call recursively for `From<>`
- // generation.
- (
- $(#[$outer:meta])*
- $vis:vis enum $EnumName:ident {
- $($entry:ident($entry_type:ty)),*
- $(,)*
- }
- ) => {
- // Copy over enum declaration as it is.
- $(#[$outer])*
- $vis enum $EnumName {
- $($entry($entry_type)),*
- }
-
- // Generate `From<...>` implementation.
- composite_enum!{$EnumName, $($entry($entry_type)),*}
- };
- // `From<>` implementation generation. Base case.
- ($EnumName:ident, $entry:ident($entry_type:ty)) => {
- impl From<$entry_type> for $EnumName {
- fn from(entry: $entry_type) -> $EnumName {
- $EnumName::$entry(entry)
- }
- }
- };
- // `From<>` implementation generation. Recursive case.
- ($EnumName:ident, $entry:ident($entry_type:ty), $($entry_next:ident($entry_type_next:ty)),+) => {
- composite_enum!{$EnumName, $entry($entry_type)}
- composite_enum!{$EnumName, $($entry_next($entry_type_next)),*}
- };
-}
-
composite_enum! {
/// A top level error type that consolidates errors from different libraries.
#[derive(Debug)]
diff --git a/gbl/efi/src/utils.rs b/gbl/efi/src/utils.rs
index e98aec4..fca175b 100644
--- a/gbl/efi/src/utils.rs
+++ b/gbl/efi/src/utils.rs
@@ -109,12 +109,14 @@ impl AsBlockDevice for EfiGptDevice<'_> {
pub struct EfiMultiBlockDevices<'a>(pub alloc::vec::Vec<EfiGptDevice<'a>>);
impl AsMultiBlockDevices for EfiMultiBlockDevices<'_> {
- fn for_each_until(&mut self, f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64) -> bool) {
+ fn for_each(
+ &mut self,
+ f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64),
+ ) -> core::result::Result<(), Option<&'static str>> {
for (idx, ele) in self.0.iter_mut().enumerate() {
- if f(ele, u64::try_from(idx).unwrap()) {
- return;
- }
+ f(ele, u64::try_from(idx).unwrap());
}
+ Ok(())
}
}
diff --git a/gbl/libgbl/BUILD b/gbl/libgbl/BUILD
index 9f86e2b..70da4df 100644
--- a/gbl/libgbl/BUILD
+++ b/gbl/libgbl/BUILD
@@ -52,8 +52,27 @@ rust_test(
],
deps = [
"@avb//:avb_test",
+ "@gbl//libavb:sysdeps",
"@gbl//libstorage:libstorage_testlib",
"@static_assertions",
"@uuid",
],
)
+
+rust_test(
+ name = "integration_test",
+ srcs = ["tests/integration_tests.rs"],
+ compile_data = [
+ "@gbl//libgbl/testdata:zircon_a.bin",
+ "@gbl//libgbl/testdata:zircon_b.bin",
+ "@gbl//libgbl/testdata:zircon_r.bin",
+ "@gbl//libgbl/testdata:zircon_gpt.bin",
+ ],
+ deps = [
+ ":libgbl",
+ "@avb//:avb_crypto_ops_sha_impl_staticlib",
+ "@gbl//libavb:sysdeps",
+ "@gbl//libstorage",
+ "@gbl//libstorage:libstorage_testlib",
+ ],
+)
diff --git a/gbl/libgbl/src/error.rs b/gbl/libgbl/src/error.rs
index 5855d50..b4aa1f6 100644
--- a/gbl/libgbl/src/error.rs
+++ b/gbl/libgbl/src/error.rs
@@ -14,16 +14,21 @@
//! Error types used in libgbl.
+use crate::GblOpsError;
use avb::{DescriptorError, SlotVerifyError};
use core::ffi::{FromBytesUntilNulError, FromBytesWithNulError};
use core::fmt::{Debug, Display, Formatter};
+use gbl_storage::StorageError;
/// Helper type GBL functions will return.
-pub type Result<T> = core::result::Result<T, Error>;
+pub type Result<T> = core::result::Result<T, IntegrationError>;
#[derive(Debug, PartialEq)]
-/// Error values that can be returned by function in this library
+/// Errors originating from GBL native logic.
pub enum Error {
+ ArithmeticOverflow,
+ /// Fail to hand off to kernel.
+ BootFailed,
/// Generic error
Error,
/// Missing all images required to boot system
@@ -37,73 +42,114 @@ pub enum Error {
/// AvbOps were already borrowed. This would happen on second `load_and_verify_image()` call
/// unless `reuse()` is called before.
AvbOpsBusy,
- /// Failed to get descriptor from AvbMeta
- AvbDescriptorError(DescriptorError),
- /// Avb slot verification failed.
- /// SlotVerifyError is used without verify data.
- AvbSlotVerifyError(SlotVerifyError<'static>),
}
-// Unfortunately thiserror is not available in `no_std` world.
-// Thus `Display` implementation is required.
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
+ Error::ArithmeticOverflow => write!(f, "Arithmetic Overflow"),
+ Error::BootFailed => write!(f, "Failed to boot"),
Error::Error => write!(f, "Generic error"),
Error::MissingImage => write!(f, "Missing image required to boot system"),
Error::NotImplemented => write!(f, "Functionality is not implemented"),
Error::OperationProhibited => write!(f, "Operation is prohibited"),
Error::Internal => write!(f, "Internal error"),
Error::AvbOpsBusy => write!(f, "AvbOps were already borrowed"),
- Error::AvbDescriptorError(error) => {
- write!(f, "Failed to get descriptor from AvbMeta: {:?}", error)
- }
- Error::AvbSlotVerifyError(error) => {
- write!(f, "Avb slot verification failed: {}", error)
- }
}
}
}
-impl From<DescriptorError> for Error {
- fn from(value: DescriptorError) -> Self {
- Error::AvbDescriptorError(value)
- }
-}
-
-impl<'a> From<SlotVerifyError<'a>> for Error {
- fn from(value: SlotVerifyError<'a>) -> Self {
- Error::AvbSlotVerifyError(value.without_verify_data())
- }
-}
+/// A helper macro for declaring a composite enum type that simply wraps other types as entries.
+/// It auto-generate `From<...>` implementation for each entry type. The type for each entry must
+/// be different from each other. i.e.:
+///
+/// ```rust
+/// composite_enum! {
+/// pub enum MyEnum {
+/// Usize(usize),
+/// I64(i64),
+/// }
+/// }
+/// ```
+///
+/// expands to
+///
+/// ```rust
+/// pub enum MyEnum {
+/// Usize(usize),
+/// I64(i64),
+/// }
+///
+/// impl From<usize> for MyEnum {
+/// fn from(ent: usize) -> MyEnum {
+/// MyEnum::Usize(ent)
+/// }
+/// }
+///
+/// impl From<i64> for MyEnum {
+/// fn from(ent: i64) -> MyEnum {
+/// MyEnum::I64(ent)
+/// }
+/// }
+/// ```
+#[macro_export]
+macro_rules! composite_enum {
+ (
+ $(#[$outer:meta])*
+ $vis:vis enum $name:ident {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ $ent:ident($ent_t:ty)
+ ),*
+ $(,)*
+ }
+ ) => {
+ // Copy over enum declaration as it is.
+ $(#[$outer])*
+ $vis enum $name {
+ $(
+ $(#[$inner $($args)*])*
+ $ent($ent_t)
+ ),*
+ }
-impl From<FromBytesUntilNulError> for Error {
- fn from(e: FromBytesUntilNulError) -> Self {
- Error::Internal
- }
+ // Generate `From<...>` implementation.
+ composite_enum!{$name, $($ent($ent_t)),*}
+ };
+ // `From<>` implementation generation. Base case.
+ ($name:ident, $ent:ident($ent_t:ty)) => {
+ impl From<$ent_t> for $name {
+ fn from(ent: $ent_t) -> $name {
+ $name::$ent(ent)
+ }
+ }
+ };
+ // `From<>` implementation generation. Recursive case.
+ ($name:ident, $ent:ident($ent_t:ty), $($next:ident($next_t:ty)),+) => {
+ composite_enum!{$name, $ent($ent_t)}
+ composite_enum!{$name, $($next($next_t)),*}
+ };
}
-impl From<FromBytesWithNulError> for Error {
- fn from(e: FromBytesWithNulError) -> Self {
- Error::Internal
+composite_enum! {
+ /// Top level error type that integrates errors from various dependency libraries.
+ #[derive(Debug)]
+ pub enum IntegrationError {
+ /// Failed to get descriptor from AvbMeta
+ AvbDescriptorError(DescriptorError),
+ /// Avb slot verification failed.
+ /// SlotVerifyError is used without verify data.
+ AvbSlotVerifyError(SlotVerifyError<'static>),
+ GblNativeError(Error),
+ GblOpsError(GblOpsError),
+ FromBytesUntilNulError(FromBytesUntilNulError),
+ FromBytesWithNulError(FromBytesWithNulError),
+ StorageError(StorageError),
}
}
-#[cfg(test)]
-mod tests {
- use crate::*;
- use avb::{DescriptorError, SlotVerifyError};
-
- #[test]
- fn test_error_output_formats() {
- assert_eq!("Generic error", format!("{}", Error::Error));
- assert_eq!(
- format!("Avb slot verification failed: {}", SlotVerifyError::Io),
- format!("{}", Error::AvbSlotVerifyError(SlotVerifyError::Io))
- );
- assert_eq!(
- format!("Failed to get descriptor from AvbMeta: {:?}", DescriptorError::InvalidValue),
- format!("{}", Error::AvbDescriptorError(DescriptorError::InvalidValue))
- );
+impl Display for IntegrationError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ write!(f, "{:?}", self)
}
}
diff --git a/gbl/libgbl/src/fastboot/vars.rs b/gbl/libgbl/src/fastboot/vars.rs
index 5ff0fe7..39dcb12 100644
--- a/gbl/libgbl/src/fastboot/vars.rs
+++ b/gbl/libgbl/src/fastboot/vars.rs
@@ -113,7 +113,7 @@ impl Variable for Partition {
Ok(())
})();
res.is_err()
- });
+ })?;
res
}
}
@@ -165,7 +165,7 @@ impl Variable for BlockDevice {
f(BLOCK_DEVICE, &[id, "block-size"], snprintf!(val, "{:#x}", blk.block_size()?))
})();
res.is_err()
- });
+ })?;
res
}
}
diff --git a/gbl/libgbl/src/lib.rs b/gbl/libgbl/src/lib.rs
index e4912f6..a244607 100644
--- a/gbl/libgbl/src/lib.rs
+++ b/gbl/libgbl/src/lib.rs
@@ -40,6 +40,7 @@ use avb::{HashtreeErrorMode, SlotVerifyData, SlotVerifyError, SlotVerifyFlags, S
use core::ffi::CStr;
use core::fmt::Debug;
use cstr::cstr;
+use gbl_storage::AsMultiBlockDevices;
use spin::Mutex;
pub mod boot_mode;
@@ -62,11 +63,15 @@ pub use avb::Descriptor;
pub use boot_mode::BootMode;
pub use boot_reason::KnownBootReason;
pub use digest::{Context, Digest};
-pub use error::{Error, Result};
-pub use ops::{DefaultGblOps, GblOps};
+pub use error::{Error, IntegrationError, Result};
+pub use ops::{
+ AndroidBootImages, BootImages, DefaultGblOps, FuchsiaBootImages, GblOps, GblOpsError,
+};
#[cfg(feature = "sw_digest")]
pub use sw_digest::{SwContext, SwDigest};
+use ops::GblUtils;
+
// TODO: b/312607649 - Replace placeholders with actual structures: https://r.android.com/2721974, etc
/// TODO: b/312607649 - placeholder type
pub struct Partition {}
@@ -184,7 +189,6 @@ type AvbVerifySlot = for<'b> fn(
hashtree_error_mode: HashtreeErrorMode,
) -> SlotVerifyResult<'b, SlotVerifyData<'b>>;
-#[derive(Debug)]
/// GBL object that provides implementation of helpers for boot process.
///
/// To create this object use [GblBuilder].
@@ -230,13 +234,16 @@ where
let requested_partitions = [cstr!("")];
let avb_suffix = CStr::from_bytes_until_nul(&bytes)?;
- let verified_data = VerifiedData((self.verify_slot)(
- avb_ops,
- &requested_partitions,
- Some(avb_suffix),
- SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE,
- HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO,
- )?);
+ let verified_data = VerifiedData(
+ (self.verify_slot)(
+ avb_ops,
+ &requested_partitions,
+ Some(avb_suffix),
+ SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE,
+ HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO,
+ )
+ .map_err(|v| v.without_verify_data())?,
+ );
Ok(verified_data)
}
@@ -250,14 +257,14 @@ where
///
/// * `Ok(Cursor)` - Cursor object that manages a Manager
/// * `Err(Error)` - on failure
- pub fn load_slot_interface<B: gbl_storage::AsBlockDevice, M: Manager>(
+ pub fn load_slot_interface<'b, B: gbl_storage::AsBlockDevice, M: Manager>(
&mut self,
- block_device: B,
- ) -> Result<Cursor<B, M>> {
+ block_device: &'b mut B,
+ ) -> Result<Cursor<'b, B, M>> {
let boot_token = BOOT_TOKEN.lock().take().ok_or(Error::OperationProhibited)?;
self.ops
.load_slot_interface::<B, M>(block_device, boot_token)
- .map_err(|_| Error::OperationProhibited)
+ .map_err(|_| Error::OperationProhibited.into())
}
/// Info Load
@@ -431,10 +438,10 @@ where
self.kernel_jump(kernel_image, ramdisk, dtb, token)
}
- fn is_unrecoverable_error(error: &Error) -> bool {
+ fn is_unrecoverable_error(error: &IntegrationError) -> bool {
// Note: these ifs are nested instead of chained because multiple
// expressions in an if-let is an unstable features
- if let Error::AvbSlotVerifyError(ref avb_error) = error {
+ if let IntegrationError::AvbSlotVerifyError(ref avb_error) = error {
// These are the AVB errors that are not recoverable on a subsequent attempt.
// If necessary in the future, this helper function can be moved to the GblOps trait
// and customized for platform specific behavior.
@@ -465,7 +472,7 @@ where
if oneshot_status == Some(OneShot::Bootloader) {
match self.ops.do_fastboot(&mut slot_cursor) {
Ok(_) => oneshot_status = slot_cursor.ctx.get_oneshot_status(),
- Err(Error::NotImplemented) => (),
+ Err(IntegrationError::GblNativeError(Error::NotImplemented)) => (),
Err(e) => return Err(e),
}
}
@@ -482,7 +489,7 @@ where
AvbVerificationFlags(0),
Some(boot_target),
)
- .map_err(|e: Error| {
+ .map_err(|e: IntegrationError| {
if let BootTarget::NormalBoot(slot) = boot_target {
if Self::is_unrecoverable_error(&e) {
let _ = slot_cursor.ctx.set_slot_unbootable(
@@ -528,6 +535,23 @@ where
Ok((kernel_image, token))
}
+
+ /// Loads and boots a Zircon kernel according to ABR + AVB.
+ pub fn zircon_load_and_boot(&mut self, load_buffer: &mut [u8]) -> Result<()> {
+ let (mut block_devices, load_buffer) = GblUtils::new(self.ops, load_buffer)?;
+ block_devices.sync_gpt_all(&mut |_, _, _| {});
+ // TODO(b/334962583): Implement zircon ABR + AVB.
+ // The following are place holder for test of invocation in the integration test only.
+ let ptn_size = block_devices.find_partition("zircon_a")?.size()?;
+ let (kernel, remains) =
+ load_buffer.split_at_mut(ptn_size.try_into().map_err(|_| Error::ArithmeticOverflow)?);
+ block_devices.read_gpt_partition("zircon_a", 0, kernel)?;
+ self.ops.boot(BootImages::Fuchsia(FuchsiaBootImages {
+ zbi_kernel: kernel,
+ zbi_items: &mut [],
+ }))?;
+ Err(Error::BootFailed.into())
+ }
}
#[cfg(feature = "sw_digest")]
diff --git a/gbl/libgbl/src/ops.rs b/gbl/libgbl/src/ops.rs
index 2054c3a..901f63b 100644
--- a/gbl/libgbl/src/ops.rs
+++ b/gbl/libgbl/src/ops.rs
@@ -20,15 +20,45 @@ extern crate alloc;
extern crate static_assertions;
use crate::digest::{Algorithm, Context};
-use crate::error::{Error, Result};
+use crate::error::{Error, Result as GblResult};
#[cfg(feature = "sw_digest")]
use crate::sw_digest::SwContext;
#[cfg(feature = "alloc")]
use alloc::ffi::CString;
-use core::{fmt::Debug, ptr::NonNull};
+use core::{
+ fmt::{Debug, Write},
+ ptr::NonNull,
+ result::Result,
+};
+use gbl_storage::{
+ required_scratch_size, AsBlockDevice, AsMultiBlockDevices, BlockDevice, BlockIo,
+};
use super::slots;
+/// `AndroidBootImages` contains references to loaded images for booting Android.
+pub struct AndroidBootImages<'a> {
+ pub kernel: &'a mut [u8],
+ pub ramdisk: &'a mut [u8],
+ pub fdt: &'a mut [u8],
+}
+
+/// `FuchsiaBootImages` contains references to loaded images for booting Zircon.
+pub struct FuchsiaBootImages<'a> {
+ pub zbi_kernel: &'a mut [u8],
+ pub zbi_items: &'a mut [u8],
+}
+
+/// `BootImages` contains images for booting Android/Zircon kernel.
+pub enum BootImages<'a> {
+ Android(AndroidBootImages<'a>),
+ Fuchsia(FuchsiaBootImages<'a>),
+}
+
+/// `GblOpsError` is the error type returned by required methods in `GblOps`.
+#[derive(Default, Debug)]
+pub struct GblOpsError(Option<&'static str>);
+
// https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust
// should we use traits for this? or optional/box FnMut?
//
@@ -38,10 +68,36 @@ missing:
- key management => atx extension in callback => atx_ops: ptr::null_mut(), // support optional ATX.
*/
/// Trait that defines callbacks that can be provided to Gbl.
-pub trait GblOps: Debug {
+pub trait GblOps {
/// Digest context type
type Context: Context;
+ /// Iterates block devices on the platform.
+ ///
+ /// For each block device, implementation should call `f` with its 1) `BlockIo` trait
+ /// implementation, 2) a unique u64 ID and 3) maximum number of gpt entries. If the maximum
+ /// entries is 0, it is considered that the block should not use GPT.
+ ///
+ /// The list of block devices and visit order should remain the same for the life time of the
+ /// object that implements this trait. If this can not be met due to media change, error should
+ /// be returned. Dynamic media change is not supported for now.
+ fn visit_block_devices(
+ &mut self,
+ f: &mut dyn FnMut(&mut dyn BlockIo, u64, u64),
+ ) -> Result<(), GblOpsError>;
+
+ /// Prints a ASCII character to the platform console.
+ fn console_put_char(&mut self, ch: u8) -> Result<(), GblOpsError>;
+
+ /// This method can be used to implement platform specific mechanism for deciding whether boot
+ /// should abort and enter Fastboot mode.
+ fn should_stop_in_fastboot(&mut self) -> Result<bool, GblOpsError>;
+
+ /// Platform specific kernel boot implementation.
+ ///
+ /// Implementation is not expected to return on success.
+ fn boot(&mut self, boot_images: BootImages) -> Result<(), GblOpsError>;
+
/// Create digest object to use for hash computations.
///
/// Context interface allows to update value adding more data to process.
@@ -65,24 +121,89 @@ pub trait GblOps: Debug {
fn do_fastboot<B: gbl_storage::AsBlockDevice>(
&self,
cursor: &mut slots::Cursor<B, impl slots::Manager>,
- ) -> Result<()> {
- Err(Error::NotImplemented)
+ ) -> GblResult<()> {
+ Err(Error::NotImplemented.into())
}
/// TODO: b/312607649 - placeholder interface for Gbl specific callbacks that uses alloc.
#[cfg(feature = "alloc")]
- fn gbl_alloc_extra_action(&mut self, s: &str) -> Result<()> {
+ fn gbl_alloc_extra_action(&mut self, s: &str) -> GblResult<()> {
let _c_string = CString::new(s);
- Err(Error::Error)
+ Err(Error::NotImplemented.into())
}
/// Load and initialize a slot manager and return a cursor over the manager on success.
- fn load_slot_interface<B: gbl_storage::AsBlockDevice, M: slots::Manager>(
+ fn load_slot_interface<'b, B: gbl_storage::AsBlockDevice, M: slots::Manager>(
&mut self,
- block_device: B,
+ block_device: &'b mut B,
boot_token: slots::BootToken,
- ) -> Result<slots::Cursor<B, M>> {
- Err(Error::OperationProhibited)
+ ) -> GblResult<slots::Cursor<'b, B, M>> {
+ Err(Error::OperationProhibited.into())
+ }
+
+ /// Computes the sum of required scratch size for all block devices.
+ fn required_scratch_size(&mut self) -> GblResult<usize> {
+ let mut total = 0usize;
+ let mut res = Ok(());
+ self.visit_block_devices(&mut |io, id, max_gpt_entries| {
+ res = (|| {
+ let scratch_size = required_scratch_size(io, max_gpt_entries)?;
+ total = total.checked_add(scratch_size).ok_or(Error::ArithmeticOverflow)?;
+ Ok(())
+ })();
+ })?;
+ res.map(|_| total)
+ }
+}
+
+/// `GblUtils` takes a reference to `GblOps` and implements various traits.
+pub(crate) struct GblUtils<'a, 'b, T: GblOps> {
+ ops: &'a mut T,
+ scratch: &'b mut [u8],
+}
+
+impl<'a, 'b, T: GblOps> GblUtils<'a, 'b, T> {
+ /// Create a new instance with user provided scratch buffer.
+ ///
+ /// # Args
+ ///
+ /// * `ops`: A reference to a `GblOps`,
+ /// * `scratch`: A scratch buffer.
+ ///
+ /// # Returns
+ ///
+ /// Returns a new instance and the trailing unused part of the input scratch buffer.
+ pub fn new(ops: &'a mut T, scratch: &'b mut [u8]) -> GblResult<(Self, &'b mut [u8])> {
+ let total_scratch_size = ops.required_scratch_size()?;
+ let (scratch, remaining) = scratch.split_at_mut(total_scratch_size);
+ Ok((Self { ops: ops, scratch: scratch }, remaining))
+ }
+}
+
+impl<T: GblOps> AsMultiBlockDevices for GblUtils<'_, '_, T> {
+ fn for_each(
+ &mut self,
+ f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64),
+ ) -> core::result::Result<(), Option<&'static str>> {
+ let mut scratch_offset = 0;
+ self.ops
+ .visit_block_devices(&mut |io, id, max_gpt_entries| {
+ // Not expected to fail as `Self::new()` should have checked any overflow.
+ let scratch_size = required_scratch_size(io, max_gpt_entries).unwrap();
+ let scratch = &mut self.scratch[scratch_offset..][..scratch_size];
+ scratch_offset = scratch_offset.checked_add(scratch_size).unwrap();
+ f(&mut BlockDevice::new(io, scratch, max_gpt_entries), id);
+ })
+ .map_err(|v| v.0)
+ }
+}
+
+impl<T: GblOps> Write for GblUtils<'_, '_, T> {
+ fn write_str(&mut self, s: &str) -> core::fmt::Result {
+ for ch in s.as_bytes() {
+ self.ops.console_put_char(*ch).map_err(|_| core::fmt::Error {})?;
+ }
+ Ok(())
}
}
diff --git a/gbl/libgbl/testdata/gen_sparse_test_bin.py b/gbl/libgbl/testdata/gen_test_data.py
index 8283a94..a968917 100644
--- a/gbl/libgbl/testdata/gen_sparse_test_bin.py
+++ b/gbl/libgbl/testdata/gen_test_data.py
@@ -13,21 +13,25 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Generate test files for sparse image flash test"""
+"""Generate test data files for libgbl tests"""
import os
import pathlib
import subprocess
SCRIPT_DIR = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
+GPT_TOOL = pathlib.Path(SCRIPT_DIR.parents[1]) / "tools" / "gen_gpt_disk.py"
+SZ_KB = 1024
-# Writes bytes to a file at a given offset.
+
+# A helper for writing bytes to a file at a given offset.
def write_file(file, offset, data):
file.seek(offset, 0)
file.write(data)
-if __name__ == '__main__':
+# Generates sparse image for flashing test
+def gen_sparse_test_file():
sz_kb = 1024
out_file_raw = SCRIPT_DIR / "sparse_test_raw.bin"
with open(out_file_raw, "wb") as f:
@@ -54,3 +58,32 @@ if __name__ == '__main__':
SCRIPT_DIR / "sparse_test_blk1024.bin",
"1024",
])
+
+
+# Generates GPT disk, kernel data for Zircon tests
+def gen_zircon_gpt():
+ gen_gpt_args = []
+ for suffix in ["a", "b", "r"]:
+ zircon = os.urandom(16 * SZ_KB)
+ out_file = SCRIPT_DIR / f"zircon_{suffix}.bin"
+ out_file.write_bytes(zircon)
+ gen_gpt_args.append(f"--partition=zircon_{suffix},16K,{str(out_file)}")
+
+ subprocess.run([GPT_TOOL, SCRIPT_DIR / "zircon_gpt.bin", "128K"] +
+ gen_gpt_args,
+ check=True)
+
+
+# Generates test data for A/B slot Manager writeback test
+def gen_writeback_test_bin():
+ subprocess.run([
+ GPT_TOOL, SCRIPT_DIR / "writeback_test_disk.bin", "64K",
+ "--partition=test_partition,4k,/dev/zero"
+ ],
+ check=True)
+
+
+if __name__ == '__main__':
+ gen_writeback_test_bin()
+ gen_sparse_test_file()
+ gen_zircon_gpt()
diff --git a/gbl/libgbl/testdata/gen_writeback_test_bin.sh b/gbl/libgbl/testdata/gen_writeback_test_bin.sh
deleted file mode 100755
index 7eba93e..0000000
--- a/gbl/libgbl/testdata/gen_writeback_test_bin.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#! /usr/bin/env bash
-# Copyright (C) 2024 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -e
-
-readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
-
-PARTITION_NAME="test_partition"
-PARTITION_SIZE="4k"
-
-python3 ${SCRIPT_DIR}/../../tools/gen_gpt_disk.py ${SCRIPT_DIR}/writeback_test_disk.bin 64K \
- --partition "${PARTITION_NAME},${PARTITION_SIZE},/dev/zero"
diff --git a/gbl/libgbl/testdata/sparse_test.bin b/gbl/libgbl/testdata/sparse_test.bin
index 1721841..009689e 100644
--- a/gbl/libgbl/testdata/sparse_test.bin
+++ b/gbl/libgbl/testdata/sparse_test.bin
Binary files differ
diff --git a/gbl/libgbl/testdata/sparse_test_blk1024.bin b/gbl/libgbl/testdata/sparse_test_blk1024.bin
index bf79c68..e273137 100644
--- a/gbl/libgbl/testdata/sparse_test_blk1024.bin
+++ b/gbl/libgbl/testdata/sparse_test_blk1024.bin
Binary files differ
diff --git a/gbl/libgbl/testdata/sparse_test_raw.bin b/gbl/libgbl/testdata/sparse_test_raw.bin
index 1272534..ab13637 100644
--- a/gbl/libgbl/testdata/sparse_test_raw.bin
+++ b/gbl/libgbl/testdata/sparse_test_raw.bin
Binary files differ
diff --git a/gbl/libgbl/testdata/writeback_test_disk.bin b/gbl/libgbl/testdata/writeback_test_disk.bin
index 12380d5..ebb428d 100644
--- a/gbl/libgbl/testdata/writeback_test_disk.bin
+++ b/gbl/libgbl/testdata/writeback_test_disk.bin
Binary files differ
diff --git a/gbl/libgbl/testdata/zircon_a.bin b/gbl/libgbl/testdata/zircon_a.bin
new file mode 100644
index 0000000..bd30d49
--- /dev/null
+++ b/gbl/libgbl/testdata/zircon_a.bin
Binary files differ
diff --git a/gbl/libgbl/testdata/zircon_b.bin b/gbl/libgbl/testdata/zircon_b.bin
new file mode 100644
index 0000000..aa9faa9
--- /dev/null
+++ b/gbl/libgbl/testdata/zircon_b.bin
Binary files differ
diff --git a/gbl/libgbl/testdata/zircon_gpt.bin b/gbl/libgbl/testdata/zircon_gpt.bin
new file mode 100644
index 0000000..54624ff
--- /dev/null
+++ b/gbl/libgbl/testdata/zircon_gpt.bin
Binary files differ
diff --git a/gbl/libgbl/testdata/zircon_r.bin b/gbl/libgbl/testdata/zircon_r.bin
new file mode 100644
index 0000000..653837e
--- /dev/null
+++ b/gbl/libgbl/testdata/zircon_r.bin
Binary files differ
diff --git a/gbl/libgbl/tests/integration_tests.rs b/gbl/libgbl/tests/integration_tests.rs
new file mode 100644
index 0000000..082ac44
--- /dev/null
+++ b/gbl/libgbl/tests/integration_tests.rs
@@ -0,0 +1,170 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use gbl_storage::BlockIo;
+use gbl_storage_testlib::TestBlockIo;
+use libgbl::{
+ digest::Algorithm, BootImages, Context, Digest, FuchsiaBootImages, GblBuilder, GblOps,
+ GblOpsError,
+};
+use std::{collections::VecDeque, vec::Vec};
+
+extern crate avb_sysdeps;
+
+struct GblTestBlockIo {
+ io: TestBlockIo,
+ max_gpt_entries: u64,
+}
+
+/// `TestGblOps` provides mock implementation of GblOps for integration test.
+#[derive(Default)]
+struct TestGblOps<'a> {
+ block_io: Vec<GblTestBlockIo>,
+ console_out: VecDeque<u8>,
+ boot_cb: Option<MustUse<&'a mut dyn FnMut(BootImages)>>,
+}
+
+impl TestGblOps<'_> {
+ /// Adds a new block device.
+ pub(crate) fn add_block_device<T: AsRef<[u8]>>(
+ &mut self,
+ alignment: u64,
+ block_size: u64,
+ max_gpt_entries: u64,
+ data: T,
+ ) {
+ self.block_io.push(GblTestBlockIo {
+ io: TestBlockIo::new(alignment, block_size, data),
+ max_gpt_entries: max_gpt_entries,
+ })
+ }
+}
+
+impl GblOps for TestGblOps<'_> {
+ type Context = TestDigestContext;
+
+ fn visit_block_devices(
+ &mut self,
+ f: &mut dyn FnMut(&mut dyn BlockIo, u64, u64),
+ ) -> Result<(), GblOpsError> {
+ for (idx, ele) in self.block_io.iter_mut().enumerate() {
+ f(&mut ele.io, idx.try_into().unwrap(), ele.max_gpt_entries);
+ }
+ Ok(())
+ }
+
+ fn console_put_char(&mut self, ch: u8) -> Result<(), GblOpsError> {
+ Ok(self.console_out.push_back(ch))
+ }
+
+ fn should_stop_in_fastboot(&mut self) -> Result<bool, GblOpsError> {
+ Ok(false)
+ }
+
+ fn boot(&mut self, boot_images: BootImages) -> Result<(), GblOpsError> {
+ Ok((self.boot_cb.as_mut().unwrap().get())(boot_images))
+ }
+}
+
+/// Placeholder.
+struct DigestBytes(Vec<u8>);
+
+impl AsRef<[u8]> for DigestBytes {
+ fn as_ref(&self) -> &[u8] {
+ self.0.as_ref()
+ }
+}
+
+impl Digest for DigestBytes {
+ fn algorithm(&self) -> &Algorithm {
+ unimplemented!();
+ }
+}
+
+/// Placeholder.
+struct TestDigestContext {}
+
+impl Context for TestDigestContext {
+ type Digest = DigestBytes;
+
+ fn new(_: Algorithm) -> Self {
+ unimplemented!();
+ }
+
+ fn update(&mut self, _: &[u8]) {
+ unimplemented!();
+ }
+
+ fn finish(self) -> Self::Digest {
+ unimplemented!();
+ }
+
+ fn algorithm(&self) -> &Algorithm {
+ unimplemented!();
+ }
+}
+
+/// `MustUse` wraps an object and checks that it is accessed at least once before it's dropped.
+/// In this integration test, it is mainly used to check that test provided ops callbacks are run.
+struct MustUse<T: ?Sized> {
+ used: bool,
+ val: T,
+}
+
+impl<T: ?Sized> MustUse<T> {
+ /// Create a new instance.
+ fn new(val: T) -> Self
+ where
+ T: Sized,
+ {
+ Self { used: false, val: val }
+ }
+
+ /// Returns a mutable reference to the object.
+ fn get(&mut self) -> &mut T {
+ self.used = true;
+ &mut self.val
+ }
+}
+
+impl<T: ?Sized> Drop for MustUse<T> {
+ fn drop(&mut self) {
+ assert!(self.used)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_zircon_load_and_boot() {
+ // TODO(b/334962583): Invocation test only. Update this test once
+ // `Gbl::zircon_load_and_boot()` is implemented.
+ let mut boot_cb = |boot_images: BootImages| {
+ let BootImages::Fuchsia(FuchsiaBootImages { zbi_kernel, zbi_items }) = boot_images
+ else {
+ panic!("Wrong image type");
+ };
+ assert_eq!(zbi_kernel, include_bytes!("../testdata/zircon_a.bin"));
+ assert_eq!(zbi_items, []);
+ };
+ let mut ops: TestGblOps = Default::default();
+ ops.add_block_device(512, 512, 128, include_bytes!("../testdata/zircon_gpt.bin"));
+ ops.boot_cb = Some(MustUse::new(&mut boot_cb));
+ let mut gbl = GblBuilder::new(&mut ops).build();
+ let mut load_buffer = vec![0u8; 64 * 1024];
+ let _ = gbl.zircon_load_and_boot(&mut load_buffer[..]);
+ }
+}
diff --git a/gbl/libstorage/src/lib.rs b/gbl/libstorage/src/lib.rs
index 46276d7..5a10aa2 100644
--- a/gbl/libstorage/src/lib.rs
+++ b/gbl/libstorage/src/lib.rs
@@ -114,7 +114,7 @@ use gpt::Gpt;
pub use gpt::{GptEntry, GPT_NAME_LEN_U16};
mod multi_blocks;
-pub use multi_blocks::{with_id, AsMultiBlockDevices};
+pub use multi_blocks::AsMultiBlockDevices;
/// The type of Result used in this library.
pub type Result<T> = core::result::Result<T, StorageError>;
@@ -128,6 +128,7 @@ pub enum StorageError {
BlockDeviceNotFound,
BlockIoError,
BlockIoNotProvided,
+ FailedGettingBlockDevices(Option<&'static str>),
InvalidInput,
NoValidGpt,
NotExist,
@@ -135,27 +136,26 @@ pub enum StorageError {
U64toUSizeOverflow,
}
-impl Into<&'static str> for StorageError {
- fn into(self) -> &'static str {
- match self {
- StorageError::ArithmeticOverflow => "Arithmetic overflow",
- StorageError::OutOfRange => "Out of range",
- StorageError::ScratchTooSmall => "Not enough scratch buffer",
- StorageError::BlockDeviceNotFound => "Block device not found",
- StorageError::BlockIoError => "Block IO error",
- StorageError::BlockIoNotProvided => "Block IO is not provided",
- StorageError::InvalidInput => "Invalid input",
- StorageError::NoValidGpt => "GPT not found",
- StorageError::NotExist => "The specified partition could not be found",
- StorageError::PartitionNotUnique => "Partition is found on multiple block devices",
- StorageError::U64toUSizeOverflow => "u64 to usize fails",
- }
- }
-}
-
impl core::fmt::Display for StorageError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- write!(f, "{}", Into::<&'static str>::into(*self))
+ match self {
+ StorageError::ArithmeticOverflow => write!(f, "Arithmetic overflow"),
+ StorageError::OutOfRange => write!(f, "Out of range"),
+ StorageError::ScratchTooSmall => write!(f, "Not enough scratch buffer"),
+ StorageError::BlockDeviceNotFound => write!(f, "Block device not found"),
+ StorageError::BlockIoError => write!(f, "Block IO error"),
+ StorageError::BlockIoNotProvided => write!(f, "Block IO is not provided"),
+ StorageError::FailedGettingBlockDevices(v) => {
+ write!(f, "Failed to iterate all block devices {:?}", v)
+ }
+ StorageError::InvalidInput => write!(f, "Invalid input"),
+ StorageError::NoValidGpt => write!(f, "GPT not found"),
+ StorageError::NotExist => write!(f, "The specified partition could not be found"),
+ StorageError::PartitionNotUnique => {
+ write!(f, "Partition is found on multiple block devices")
+ }
+ StorageError::U64toUSizeOverflow => write!(f, "u64 to usize fails"),
+ }
}
}
diff --git a/gbl/libstorage/src/multi_blocks.rs b/gbl/libstorage/src/multi_blocks.rs
index 3f92472..0d63eb3 100644
--- a/gbl/libstorage/src/multi_blocks.rs
+++ b/gbl/libstorage/src/multi_blocks.rs
@@ -17,9 +17,24 @@ use crate::{AsBlockDevice, BlockIo, Partition, Result, StorageError};
/// `AsMultiBlockDevices` provides APIs for finding/reading/writing raw data or GPT partitions from
/// multiple block devices.
pub trait AsMultiBlockDevices {
- /// Calls closure `f` for each `AsBlockDevice` object and its unique `id` until reaching end or
- /// `f` returns true.
- fn for_each_until(&mut self, f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64) -> bool);
+ /// Calls closure `f` for each `AsBlockDevice` object and its unique `id` until reaching end.
+ fn for_each(
+ &mut self,
+ f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64),
+ ) -> core::result::Result<(), Option<&'static str>>;
+
+ /// Calls closure `f` for each `AsBlockDevice` object and its unique `id` until reaching end of
+ /// returnning true.
+ fn for_each_until(
+ &mut self,
+ f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64) -> bool,
+ ) -> Result<()> {
+ let mut stop = false;
+ self.for_each(&mut |io, id| {
+ stop = stop || f(io, id);
+ })
+ .map_err(|v| StorageError::FailedGettingBlockDevices(v))
+ }
/// Gets the block device with the given id.
fn get(&mut self, id: u64) -> Result<SelectedBlockDevice>
@@ -33,7 +48,7 @@ pub trait AsMultiBlockDevices {
/// Syncs gpt for all block devices. Caller provides a callback for handling sync error for
/// each block device.
fn sync_gpt_all(&mut self, f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64, StorageError)) {
- self.for_each_until(&mut |v, id| {
+ let _ = self.for_each_until(&mut |v, id| {
match v.sync_gpt() {
Err(e) => f(v, id, e),
_ => {}
@@ -53,7 +68,7 @@ pub trait AsMultiBlockDevices {
v => v.or(res),
};
res.err() == Some(StorageError::PartitionNotUnique)
- });
+ })?;
res
}
@@ -97,8 +112,11 @@ pub trait AsMultiBlockDevices {
}
impl<T: ?Sized + AsMultiBlockDevices> AsMultiBlockDevices for &mut T {
- fn for_each_until(&mut self, f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64) -> bool) {
- (*self).for_each_until(&mut |io, id| f(io, id))
+ fn for_each(
+ &mut self,
+ f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64),
+ ) -> core::result::Result<(), Option<&'static str>> {
+ (*self).for_each(&mut |io, id| f(io, id))
}
}
@@ -111,16 +129,12 @@ where
devs.for_each_until(&mut |v, id| {
res = f(v, id);
res.is_ok()
- });
+ })?;
res
}
/// Finds the first block device with the given ID and runs a closure with it.
-pub fn with_id<F, R>(
- devs: &mut (impl AsMultiBlockDevices + ?Sized),
- dev_id: u64,
- mut f: F,
-) -> Result<R>
+fn with_id<F, R>(devs: &mut (impl AsMultiBlockDevices + ?Sized), dev_id: u64, mut f: F) -> Result<R>
where
F: FnMut(&mut dyn AsBlockDevice) -> R,
{
diff --git a/gbl/libstorage/src/testlib.rs b/gbl/libstorage/src/testlib.rs
index d06a410..85a8225 100644
--- a/gbl/libstorage/src/testlib.rs
+++ b/gbl/libstorage/src/testlib.rs
@@ -31,6 +31,16 @@ pub struct TestBlockIo {
}
impl TestBlockIo {
+ pub fn new<T: AsRef<[u8]>>(block_size: u64, alignment: u64, data: T) -> Self {
+ Self {
+ block_size: block_size,
+ alignment: alignment,
+ storage: Vec::from(data.as_ref()),
+ num_writes: 0,
+ num_reads: 0,
+ }
+ }
+
fn check_alignment(&mut self, buffer: &[u8]) -> bool {
matches!(is_buffer_aligned(buffer, self.alignment()), Ok(true))
&& matches!(is_aligned(buffer.len() as u64, self.block_size()), Ok(true))
@@ -217,13 +227,7 @@ impl<'a> TestBlockDeviceBuilder<'a> {
BackingStore::Size(size) => vec![0u8; size],
};
assert!(storage.len() % (self.block_size as usize) == 0);
- let mut io = TestBlockIo {
- block_size: self.block_size,
- alignment: self.alignment,
- storage,
- num_reads: 0,
- num_writes: 0,
- };
+ let mut io = TestBlockIo::new(self.block_size, self.alignment, storage);
let scratch_size = match self.scratch_size {
Some(s) => s,
None => required_scratch_size(&mut io, self.max_gpt_entries).unwrap(),
@@ -240,11 +244,15 @@ impl<'a> TestBlockDeviceBuilder<'a> {
pub struct TestMultiBlockDevices(pub Vec<TestBlockDevice>);
impl AsMultiBlockDevices for TestMultiBlockDevices {
- fn for_each_until(&mut self, f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64) -> bool) {
+ fn for_each(
+ &mut self,
+ f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64),
+ ) -> core::result::Result<(), Option<&'static str>> {
let _ = self
.0
.iter_mut()
.enumerate()
- .find_map(|(idx, ele)| f(ele, u64::try_from(idx).unwrap()).then_some(()));
+ .for_each(|(idx, ele)| f(ele, u64::try_from(idx).unwrap()));
+ Ok(())
}
}
diff --git a/gbl/tests/BUILD b/gbl/tests/BUILD
index 72d1ad6..6afe555 100644
--- a/gbl/tests/BUILD
+++ b/gbl/tests/BUILD
@@ -20,6 +20,7 @@ test_suite(
"@gbl//libefi:libefi_test",
"@gbl//libfastboot:libfastboot_test",
"@gbl//libfdt:libfdt_test",
+ "@gbl//libgbl:integration_test",
"@gbl//libgbl:libgbl_test",
"@gbl//libstorage:libstorage_doc_test",
"@gbl//libstorage:libstorage_test",
diff --git a/gbl/tools/gen_gpt_disk.py b/gbl/tools/gen_gpt_disk.py
index 09428e7..09428e7 100644..100755
--- a/gbl/tools/gen_gpt_disk.py
+++ b/gbl/tools/gen_gpt_disk.py