diff options
author | Yecheng Zhao <zyecheng@google.com> | 2024-03-27 21:06:56 +0000 |
---|---|---|
committer | Yecheng Zhao <zyecheng@google.com> | 2024-03-29 17:19:39 +0000 |
commit | 1a60f01421a6dded0cf1aca2c9dd542c03eea3b5 (patch) | |
tree | 54baa267e7f7cd36c27d9b4188eaa349501a0fe3 | |
parent | 23a26fb23693bb682f666d0fafe8bb56a96fe2f8 (diff) | |
download | libbootloader-1a60f01421a6dded0cf1aca2c9dd542c03eea3b5.tar.gz |
Support "fastboot fetch"
Adds support for the "fastboot fetch" command that fetches partitions
from the device. Introduces a semantic that supports specifying any sub
range of a GPT partition or raw block device as a "partition" in
Fastboot commands.
Bug: 331257795
Change-Id: I06df9fbb3c63e0d941af808be1fb747084b9f6b8
-rw-r--r-- | gbl/docs/gbl_fastboot.md | 59 | ||||
-rw-r--r-- | gbl/efi/src/android_boot.rs | 2 | ||||
-rw-r--r-- | gbl/efi/src/avb.rs | 11 | ||||
-rw-r--r-- | gbl/efi/src/fuchsia_boot.rs | 2 | ||||
-rw-r--r-- | gbl/libfastboot/src/lib.rs | 313 | ||||
-rw-r--r-- | gbl/libgbl/src/fastboot/mod.rs | 321 | ||||
-rw-r--r-- | gbl/libgbl/src/fastboot/vars.rs | 46 | ||||
-rw-r--r-- | gbl/libstorage/src/lib.rs | 91 | ||||
-rw-r--r-- | gbl/libstorage/src/multi_blocks.rs | 74 |
9 files changed, 741 insertions, 178 deletions
diff --git a/gbl/docs/gbl_fastboot.md b/gbl/docs/gbl_fastboot.md new file mode 100644 index 0000000..f2dd277 --- /dev/null +++ b/gbl/docs/gbl_fastboot.md @@ -0,0 +1,59 @@ +# Fastboot in GBL + +This document describes Fastboot in the [GBL UEFI bootloader](../efi/BUILD). + +## Transport + +The GBL UEFI bootloader supports both Fastboot over TCP and USB. To enable +Fastboot over TCP, the UEFI loader needs to implement the +`EFI_SIMPLE_NETWORK_PROTOCOL` protocol. To enable Fastboot over USB, the +[EFI_ANDROID_BOOT_PROTOCOL](./EFI_ANDROID_BOOT_PROTOCOL.md) protocol is needed. +GBL automatically establishes the corresponding transport channel if the needed +protocol is available. + +## Definition of Partition + +Certain fastboot commands such as `fastboot flash`, `fastboot fetch` and +`fastboot getvar partition-size` require to specify a partition. GBL Fastboot +assumes that the platform may have multiple storage devices that may or may not +use GPT partitions. Therefore, in the context of GBL Fastboot, the notion +"partition" is defined to represent both GPT partitions or raw storage of any +sub window and on any storage device. Specifically, the following semantics are +introduced for specifying a partition: + +* GPT partitions + ```sh + <part>[:<block_id>] + <part>:[<block_id>][:<offset>] + <part>:[<block_id>]:[<offset>][:<size>] + ``` + This specifies range `[offset, offset+size)` in GPT partition `part` on the + block device with ID `block id`. `block_id`, `offset` and `size` must be a + 64bit integer hex string. If `block id` is not given, GBL will check and only + accept it if the GPT partition is unique among all devices. `offset` defaults + to 0 if not given. `size` defaults to the rest of the GPT partition after + `offset` if not given. The list of GPT partitions, block devices and IDs are + listed by `fastboot getvar all` + + Examples: + * `fastboot flash boot_a` -- Checks that `boot_a` is a unique GPT partition + among all storage devices and flashes in the entire range of the partition. + * `fastboot flash boot_a:0x0` or `boot_a:0` -- Flashes in the entire range of + GPT partition "boot_a" on block device 0. + * `fastboot flash boot_a:0:200` -- Flashes only in range `[512, end)` of GPT + partition "boot_a" on block device 0. + * `fastboot flash boot_a:0:200:200` -- Flashes only in range `[512, 1024)` of + GPT partition "boot_a" on block device 0. + * `fastboot flash boot_a:::` -- Same as `"boot_a"`. + +* Raw storage + ``` + :<block_id> + :<block_id>[:<offset>] + :<block_id>:[<offset>][:<size>] + ``` + This is similar to the case of GPT partitions except that `part` is an empty + string and `block_id` is mandatory. It specifies range `[offset, offset+size)` + of the raw data on the block device with ID `block_id`. `offset` defaults to + 0 if not given. `size` defaults to the rest of the storage after `offset` if + not given. diff --git a/gbl/efi/src/android_boot.rs b/gbl/efi/src/android_boot.rs index 899eb65..85a25b8 100644 --- a/gbl/efi/src/android_boot.rs +++ b/gbl/efi/src/android_boot.rs @@ -243,7 +243,7 @@ pub fn load_android_simple<'a>( })?; } // Check if there is a device specific bootconfig partition. - match gpt_devices.partition_size("bootconfig") { + match gpt_devices.find_partition("bootconfig").and_then(|v| v.size()) { Ok(sz) => { bootconfig_builder.add_with(|out| { // For proof-of-concept only, we just load as much as possible and figure out the diff --git a/gbl/efi/src/avb.rs b/gbl/efi/src/avb.rs index 838be0e..049f0f0 100644 --- a/gbl/efi/src/avb.rs +++ b/gbl/efi/src/avb.rs @@ -54,7 +54,8 @@ impl<'b> Ops<'b> for GblEfiAvbOps<'_, 'b> { let part_str = cstr_to_str(partition, IoError::NoSuchPartition)?; let partition_size: u64 = self .gpt_dev - .partition_size(part_str) + .find_partition(part_str) + .and_then(|v| v.size()) .map_err(|_| IoError::NoSuchPartition)? .try_into() .map_err(|_| IoError::Oom)?; @@ -110,9 +111,8 @@ impl<'b> Ops<'b> for GblEfiAvbOps<'_, 'b> { fn get_unique_guid_for_partition(&mut self, partition: &CStr) -> IoResult<Uuid> { let part_str = cstr_to_str(partition, IoError::NoSuchPartition)?; - let (_, gpt_entry) = - self.gpt_dev.find_partition(part_str).map_err(|_| IoError::NoSuchPartition)?; - Ok(Uuid::from_bytes(gpt_entry.guid)) + let ptn = self.gpt_dev.find_partition(part_str).map_err(|_| IoError::NoSuchPartition)?; + Ok(Uuid::from_bytes(ptn.gpt_entry().guid)) } fn get_size_of_partition(&mut self, partition: &CStr) -> IoResult<u64> { @@ -122,7 +122,8 @@ impl<'b> Ops<'b> for GblEfiAvbOps<'_, 'b> { let part_str = cstr_to_str(partition, IoError::NoSuchPartition)?; Ok(self .gpt_dev - .partition_size(part_str) + .find_partition(part_str) + .and_then(|v| v.size()) .map_err(|_| IoError::NoSuchPartition)? .try_into() .map_err(|_| IoError::NoSuchPartition)?) diff --git a/gbl/efi/src/fuchsia_boot.rs b/gbl/efi/src/fuchsia_boot.rs index bb0f020..0109241 100644 --- a/gbl/efi/src/fuchsia_boot.rs +++ b/gbl/efi/src/fuchsia_boot.rs @@ -160,7 +160,7 @@ pub fn is_fuchsia_gpt(efi_entry: &EfiEntry) -> Result<()> { &["fvm"], ]; for partition in partitions { - if !partition.iter().any(|v| gpt_devices.partition_size(*v).is_ok()) { + if !partition.iter().any(|v| gpt_devices.find_partition(*v).is_ok()) { return Err(EfiAppError::NotFound.into()); } } diff --git a/gbl/libfastboot/src/lib.rs b/gbl/libfastboot/src/lib.rs index 4316118..eb81e32 100644 --- a/gbl/libfastboot/src/lib.rs +++ b/gbl/libfastboot/src/lib.rs @@ -66,7 +66,7 @@ #![cfg_attr(not(test), no_std)] -use core::fmt::{Display, Error, Formatter, Write}; +use core::fmt::{Debug, Display, Error, Formatter, Write}; use core::str::{from_utf8, Split}; pub const MAX_COMMAND_SIZE: usize = 4096; @@ -79,6 +79,7 @@ pub enum TransportError { InvalidHanshake, PacketSizeOverflow, PacketSizeExceedMaximum, + NotEnoughUpload, Others(&'static str), } @@ -156,7 +157,6 @@ const COMMAND_ERROR_LENGTH: usize = MAX_RESPONSE_SIZE - 4; /// Any type that implements `Display` trait can be converted into it. However, because fastboot /// response message is limited to `MAX_RESPONSE_SIZE`. If the final displayed string length /// exceeds it, the rest of the content is ignored. -#[derive(Debug)] pub struct CommandError(FormattedBytes<[u8; COMMAND_ERROR_LENGTH]>); impl CommandError { @@ -166,6 +166,12 @@ impl CommandError { } } +impl Debug for CommandError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{}", self.to_str()) + } +} + impl<T: Display> From<T> for CommandError { fn from(val: T) -> Self { let mut res = CommandError(FormattedBytes([0u8; COMMAND_ERROR_LENGTH], 0)); @@ -274,8 +280,9 @@ pub trait FastbootImplementation { /// } /// ``` /// - /// If implementation fails to upload enough, or upload more than expected data with - /// `Uploader::upload` a Fastboot error will be sent to the host. + /// If implementation fails to upload enough, or attempts to upload more than expected data + /// with `Uploader::upload()`, an error will be returned. + /// /// * `utils`: A mutable reference to an instance of `FastbootUtils`. fn upload( &mut self, @@ -283,6 +290,24 @@ pub trait FastbootImplementation { utils: &mut FastbootUtils, ) -> Result<(), CommandError>; + /// Backend for `fastboot fetch ...` + /// + /// # Args + /// + /// * `part`: The partition name. + /// * `offset`: The offset into the partition for upload. + /// * `size`: The number of bytes to upload. + /// * `upload_builder`: An instance of `UploadBuilder` for initiating and uploading data. + /// * `utils`: A mutable reference to an instance of `FastbootUtils`. + fn fetch( + &mut self, + part: &str, + offset: u64, + size: u64, + upload_builder: UploadBuilder, + utils: &mut FastbootUtils, + ) -> Result<(), CommandError>; + /// Backend for `fastboot oem ...`. /// /// # Args @@ -347,20 +372,43 @@ pub trait FastbootInfoSend { pub struct FastbootUtils<'a> { // TODO(b/328784766): Consider using arrayvec crate or similar instead of passing download // buffer and size separately. - /// The total download buffer. - pub download_buffer: &'a mut [u8], - /// Current downloaded data size. - pub download_data_size: usize, + // The total download buffer. + download_buffer: &'a mut [u8], + // Current downloaded data size. + download_data_size: &'a mut usize, /// When available, a trait object `FastbootInfoSend` for sending Fastboot INFO messages. - pub fb_info: Option<&'a mut dyn FastbootInfoSend>, + fb_info: Option<&'a mut dyn FastbootInfoSend>, } impl<'a> FastbootUtils<'a> { - fn new(fb: &'a mut Fastboot, fb_info: Option<&'a mut dyn FastbootInfoSend>) -> Self { - Self { - download_buffer: fb.download_buffer, - download_data_size: fb.total_download_size, - fb_info: fb_info, + /// Creates a new instance. + pub fn new( + download_buffer: &'a mut [u8], + download_data_size: &'a mut usize, + fb_info: Option<&'a mut dyn FastbootInfoSend>, + ) -> Self { + Self { download_buffer, download_data_size, fb_info } + } + + /// Returns the current downloaded data. + pub fn download_data(&mut self) -> &mut [u8] { + &mut self.download_buffer[..*self.download_data_size] + } + + /// Takes the download buffer. The downloaded data is invalidated. + pub fn take_download_buffer(&mut self) -> &mut [u8] { + *self.download_data_size = 0; + self.download_buffer + } + + /// Sends a Fastboot INFO message. + /// + /// Returns Ok(true) if successful, Ok(false) if INFO messages are not supported in the context + /// of the current command, error otherwise. + pub fn info_send(&mut self, msg: &str) -> Result<bool, CommandError> { + match self.fb_info { + Some(ref mut send) => send.send(msg).map(|_| true), + _ => Ok(false), } } } @@ -424,12 +472,39 @@ impl<'a> Uploader<'a> { pub fn upload(&mut self, data: &[u8]) -> Result<(), CommandError> { *self.remaining = self .remaining - .checked_sub(data.len().try_into().map_err(|_| "usize -> u64 overflows")?) - .ok_or::<CommandError>("Upload more than expected".into())?; + .checked_sub(data.len().try_into().map_err(|_| "")?) + .ok_or::<CommandError>("".into())?; (self.send)(data) } } +/// A helper function that creates an `UploadBuilder` from a `Transport` and runs a closure with +/// it. The helper internally checks that the closure uploads enough data it specifies. +fn with_upload_builder<F, R>( + transport: &mut impl Transport, + mut f: F, +) -> Result<Result<R, CommandError>, TransportError> +where + F: FnMut(UploadBuilder) -> Result<R, CommandError>, +{ + let mut transport_error = Ok(()); + let mut remaining = 0u64; + let mut send = |data: &[u8]| -> Result<(), CommandError> { + transport_error?; + transport_error = transport.send_packet(data); + Ok(transport_error?) + }; + let upload_builder = UploadBuilder { remaining: &mut remaining, send: &mut send }; + let res = f(upload_builder); + transport_error?; + // Failing to upload enough data should be considered a transport error. Because the remote + // host will hang as long as the connection is still active. + match remaining > 0 { + true => Err(TransportError::NotEnoughUpload), + _ => Ok(res), + } +} + pub mod test_utils { use crate::{CommandError, UploadBuilder}; @@ -534,6 +609,7 @@ impl<'a> Fastboot<'a> { "getvar" => self.get_var(args, transport, fb_impl)?, "download" => self.download(args, transport, fb_impl)?, "upload" => self.upload(transport, fb_impl)?, + "fetch" => self.fetch(&cmd_str, args, transport, fb_impl)?, _ if cmd_str.starts_with("oem ") => { self.oem(&cmd_str[4..], transport, fb_impl)? } @@ -625,7 +701,7 @@ impl<'a> Fastboot<'a> { fb_impl: &mut impl FastbootImplementation, ) -> Result<&'s str, CommandError> { let mut info_sender = FastbootInfoSender::new(transport); - let mut utils = FastbootUtils::new(self, Some(&mut info_sender)); + let mut utils = self.utils(Some(&mut info_sender)); fb_impl.get_var_as_str(var, args, out, &mut utils) } @@ -638,8 +714,9 @@ impl<'a> Fastboot<'a> { // Process the built-in MAX_DOWNLOAD_SIZE_NAME variable. let mut size_str = [0u8; 32]; f(MAX_DOWNLOAD_SIZE_NAME, &[], snprintf!(size_str, "{:#x}", self.download_buffer.len()))?; - - fb_impl.get_var_all(f, &mut FastbootUtils::new(self, None)) + // Don't allow other custom INFO messages because variable values are sent as INFO + // messages. + fb_impl.get_var_all(f, &mut self.utils(None)) } /// Method for handling "fastboot getvar all" @@ -704,41 +781,60 @@ impl<'a> Fastboot<'a> { } /// Method for handling "fastboot get_staged ...". - pub fn upload( + fn upload( &mut self, transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<(), TransportError> { - let mut transport_error = Ok(()); - let mut remaining = 0u64; - let mut send = |data: &[u8]| -> Result<(), CommandError> { - transport_error?; - transport_error = transport.send_packet(data); - Ok(transport_error?) - }; - let mut utils = FastbootUtils::new(self, None); - let upload_res = fb_impl - .upload(UploadBuilder { remaining: &mut remaining, send: &mut send }, &mut utils); - transport_error?; let mut res = [0u8; MAX_RESPONSE_SIZE]; - match upload_res { + match with_upload_builder(transport, |upload_builder| { + // No INFO message should be sent during upload. + let mut utils = self.utils(None); + fb_impl.upload(upload_builder, &mut utils) + })? { Err(e) => transport.send_packet(fastboot_fail!(res, "{}", e.to_str())), - Ok(()) if remaining > 0 => { - transport.send_packet(fastboot_fail!(res, "Failed to upload all data")) + _ => transport.send_packet(fastboot_okay!(res, "")), + } + } + + /// Method for handling "fastboot fetch ...". + pub fn fetch( + &mut self, + cmd: &str, + args: Split<char>, + transport: &mut impl Transport, + fb_impl: &mut impl FastbootImplementation, + ) -> Result<(), TransportError> { + let cmd = cmd.strip_prefix("fetch:").unwrap(); + let mut res = [0u8; MAX_RESPONSE_SIZE]; + match with_upload_builder(transport, |upload_builder| -> Result<(), CommandError> { + if args.clone().count() < 3 { + return Err("Not enough argments".into()); } + // Parses backward. Parses size, offset first and treats the remaining string as + // partition name. This allows ":" in partition name. + let mut rev = args.clone().rev(); + let sz = next_arg(&mut rev, Err("Invalid argument".into()))?; + let off = next_arg(&mut rev, Err("Invalid argument".into()))?; + let part = &cmd[..cmd.len() - (off.len() + sz.len() + 2)]; + // No INFO message should be sent during upload. + let mut utils = self.utils(None); + fb_impl.fetch(part, hex_to_u64(off)?, hex_to_u64(sz)?, upload_builder, &mut utils) + })? { + Err(e) => transport.send_packet(fastboot_fail!(res, "{}", e.to_str())), _ => transport.send_packet(fastboot_okay!(res, "")), } } /// Method for handling "fastboot oem ...". - pub fn oem( + fn oem( &mut self, cmd: &str, transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<(), TransportError> { let mut info_sender = FastbootInfoSender::new(transport); - let mut utils = FastbootUtils::new(self, Some(&mut info_sender)); + let mut utils = self.utils(Some(&mut info_sender)); let mut oem_out = [0u8; MAX_RESPONSE_SIZE - 4]; let oem_res = fb_impl.oem(cmd, &mut utils, &mut oem_out[..]); info_sender.transport_error()?; @@ -751,6 +847,11 @@ impl<'a> Fastboot<'a> { Err(e) => transport.send_packet(fastboot_fail!(res, "{}", e.to_str())), } } + + /// Helper method to create an instance of `FastbootUtils`. + fn utils<'b>(&'b mut self, info: Option<&'b mut dyn FastbootInfoSend>) -> FastbootUtils<'b> { + FastbootUtils::new(self.download_buffer, &mut self.total_download_size, info.map(|v| v)) + } } /// A helper data structure for writing formatted string to fixed size bytes array. @@ -812,6 +913,46 @@ fn split_to_array<const MAX_ARGS: usize>(mut split: Split<char>) -> ([&str; MAX_ (args, num) } +/// A helper to convert a hex string into u64. +pub(crate) fn hex_to_u64(s: &str) -> Result<u64, CommandError> { + Ok(u64::from_str_radix(s.strip_prefix("0x").unwrap_or(s), 16)?) +} + +/// A helper to check and fetch the next argument or fall back to the default if not available. +/// +/// # Args +/// +/// args: A string iterator. +/// default: This will be returned as it is if args doesn't have the next element(). Providing a +/// Ok(str) is equivalent to providing a default value. Providing an Err() is equivalent to +/// requiring that the next argument is mandatory. +pub fn next_arg<'a, T: Iterator<Item = &'a str>>( + args: &mut T, + default: Result<&'a str, CommandError>, +) -> Result<&'a str, CommandError> { + args.next().filter(|v| *v != "").ok_or("").or(default.map_err(|e| e.into())) +} + +/// A helper to check and fetch the next argument as a u64 hex string. +/// +/// # Args +/// +/// args: A string iterator. +/// default: This will be returned as it is if args doesn't have the next element(). Providing a +/// Ok(u64) is equivalent to providing a default value. Providing an Err() is equivalent to +/// requiring that the next argument is mandatory. +/// +/// Returns error if the next argument is not a valid hex string. +pub fn next_arg_u64<'a, T: Iterator<Item = &'a str>>( + args: &mut T, + default: Result<u64, CommandError>, +) -> Result<u64, CommandError> { + match next_arg(args, Err("".into())) { + Ok(v) => hex_to_u64(v), + _ => default.map_err(|e| e.into()), + } +} + #[cfg(test)] mod test { use super::*; @@ -824,6 +965,15 @@ mod test { upload_cb: Option< &'a mut dyn FnMut(UploadBuilder, &mut FastbootUtils) -> Result<(), CommandError>, >, + fetch_cb: Option< + &'a mut dyn FnMut( + &str, + u64, + u64, + UploadBuilder, + &mut FastbootUtils, + ) -> Result<(), CommandError>, + >, oem_cb: Option< &'a mut dyn FnMut(&str, &mut FastbootUtils, &mut [u8]) -> Result<usize, CommandError>, >, @@ -866,6 +1016,17 @@ mod test { (self.upload_cb.as_mut().unwrap())(upload_builder, utils) } + fn fetch( + &mut self, + part: &str, + offset: u64, + size: u64, + upload_builder: UploadBuilder, + utils: &mut FastbootUtils, + ) -> Result<(), CommandError> { + (self.fetch_cb.as_mut().unwrap())(part, offset, size, upload_builder, utils) + } + fn oem<'b>( &mut self, cmd: &str, @@ -1144,12 +1305,9 @@ mod test { let mut oem_cb = |cmd: &str, utils: &mut FastbootUtils, res: &mut [u8]| { assert_eq!(cmd, "oem-command"); assert_eq!(utils.download_buffer.len(), DOWNLOAD_BUFFER_LEN); - assert_eq!( - utils.download_buffer[..utils.download_data_size].to_vec(), - download_content - ); - utils.fb_info.as_mut().unwrap().send("oem-info-1").unwrap(); - utils.fb_info.as_mut().unwrap().send("oem-info-2").unwrap(); + assert_eq!(utils.download_data().to_vec(), download_content); + assert!(utils.info_send("oem-info-1").unwrap()); + assert!(utils.info_send("oem-info-2").unwrap()); Ok(snprintf!(res, "oem-return").len()) }; fastboot_impl.oem_cb = Some(&mut oem_cb); @@ -1180,15 +1338,14 @@ mod test { let mut upload_cb = |upload_builder: UploadBuilder, utils: &mut FastbootUtils| { assert_eq!(utils.download_buffer.len(), DOWNLOAD_BUFFER_LEN); - assert_eq!( - utils.download_buffer[..utils.download_data_size].to_vec(), - download_content - ); - let to_send = &utils.download_buffer[..utils.download_data_size]; + assert_eq!(utils.download_data().to_vec(), download_content); + let download_len = utils.download_data().len(); + let to_send = &mut utils.take_download_buffer()[..download_len]; let mut uploader = upload_builder.start(u64::try_from(to_send.len()).unwrap()).unwrap(); - uploader.upload(&to_send[..to_send.len() / 2]).unwrap(); - uploader.upload(&to_send[to_send.len() / 2..]).unwrap(); - assert!(utils.fb_info.is_none()); + uploader.upload(&to_send[..download_len / 2]).unwrap(); + uploader.upload(&to_send[download_len / 2..]).unwrap(); + assert!(!utils.info_send("").unwrap()); + assert_eq!(utils.download_data().len(), 0); Ok(()) }; fastboot_impl.upload_cb = Some(&mut upload_cb); @@ -1220,15 +1377,7 @@ mod test { Ok(()) }; fastboot_impl.upload_cb = Some(&mut upload_cb); - let _ = fastboot.run(&mut transport, &mut fastboot_impl); - assert_eq!( - transport.out_queue, - VecDeque::<Vec<u8>>::from([ - b"DATA00000400".into(), - [0u8; 0x400 - 1].to_vec(), - b"FAILFailed to upload all data".into(), - ]) - ); + assert!(fastboot.run(&mut transport, &mut fastboot_impl).is_err()); } #[test] @@ -1245,17 +1394,61 @@ mod test { Ok(()) }; fastboot_impl.upload_cb = Some(&mut upload_cb); + assert!(fastboot.run(&mut transport, &mut fastboot_impl).is_err()); + } + + #[test] + fn test_fetch() { + let mut fastboot_impl: FastbootTest = Default::default(); + let mut download_buffer = vec![0u8; 2048]; + let mut fastboot = Fastboot::new(&mut download_buffer[..]); + let mut transport = TestTransport::new(); + transport.add_input(b"fetch:boot_a:0:::200:400"); + + let mut fetch_cb = |part: &str, + offset: u64, + size: u64, + upload_builder: UploadBuilder, + _: &mut FastbootUtils| { + assert_eq!(part, "boot_a:0::"); + assert_eq!(offset, 0x200); + assert_eq!(size, 0x400); + let mut uploader = upload_builder.start(size)?; + uploader.upload(&vec![0u8; size.try_into().unwrap()][..])?; + Ok(()) + }; + fastboot_impl.fetch_cb = Some(&mut fetch_cb); let _ = fastboot.run(&mut transport, &mut fastboot_impl); assert_eq!( transport.out_queue, VecDeque::<Vec<u8>>::from([ b"DATA00000400".into(), - b"FAILUpload more than expected".into(), + [0u8; 0x400].to_vec(), + b"OKAY".into(), ]) ); } #[test] + fn test_fetch_invalid_args() { + let mut fastboot_impl: FastbootTest = Default::default(); + let mut download_buffer = vec![0u8; 2048]; + let mut fastboot = Fastboot::new(&mut download_buffer[..]); + let mut transport = TestTransport::new(); + transport.add_input(b"fetch:boot_a"); + transport.add_input(b"fetch:boot_a:200"); + transport.add_input(b"fetch:boot_a::400"); + transport.add_input(b"fetch:boot_a::"); + transport.add_input(b"fetch:boot_a:xxx:400"); + transport.add_input(b"fetch:boot_a:200:xxx"); + let mut fetch_cb = + |_: &str, _: u64, _: u64, _: UploadBuilder, _: &mut FastbootUtils| Ok(()); + fastboot_impl.fetch_cb = Some(&mut fetch_cb); + let _ = fastboot.run(&mut transport, &mut fastboot_impl); + assert!(transport.out_queue.iter().all(|v| v.starts_with(b"FAIL"))); + } + + #[test] fn test_fastboot_tcp() { let mut fastboot_impl: FastbootTest = Default::default(); let mut download_buffer = vec![0u8; 1024]; diff --git a/gbl/libgbl/src/fastboot/mod.rs b/gbl/libgbl/src/fastboot/mod.rs index 60febfb..ae5ae33 100644 --- a/gbl/libgbl/src/fastboot/mod.rs +++ b/gbl/libgbl/src/fastboot/mod.rs @@ -12,13 +12,80 @@ // See the License for the specific language governing permissions and // limitations under the License. +use core::cmp::min; +use core::ffi::CStr; use core::str::Split; -use fastboot::{CommandError, FastbootImplementation, FastbootUtils, UploadBuilder}; -use gbl_storage::AsMultiBlockDevices; +use fastboot::{ + next_arg, next_arg_u64, CommandError, FastbootImplementation, FastbootUtils, UploadBuilder, +}; +use gbl_storage::{AsBlockDevice, AsMultiBlockDevices, GPT_NAME_LEN_U16}; mod vars; use vars::{BlockDevice, Partition, Variable}; +pub(crate) const GPT_NAME_LEN_U8: usize = GPT_NAME_LEN_U16 * 2; + +/// `GblFbPartition` represents a GBL Fastboot partition, which is defined as any sub window of a +/// GPT partition or raw storage. +#[derive(Debug, Copy, Clone)] +pub(crate) struct GblFbPartition { + // GPT partition if it is a non-null string, raw block otherwise. + part: [u8; GPT_NAME_LEN_U8], + blk_id: u64, + // The offset where the window starts. + window_start: u64, + // The size of the window. + window_size: u64, +} + +impl GblFbPartition { + pub fn part(&self) -> &str { + // The construction is guaranteed to give a valid UTF8 string. + CStr::from_bytes_until_nul(&self.part[..]).unwrap().to_str().unwrap() + } +} + +/// `GblFbPartitionIo` provides read/write/size methods for a GBL Fastboot partition. +pub(crate) struct GblFbPartitionIo<'a> { + part: GblFbPartition, + devs: &'a mut dyn AsMultiBlockDevices, +} + +impl GblFbPartitionIo<'_> { + /// Checks read/write offset/size and returns the absolute offset. + fn check_range(&self, rw_off: u64, rw_size: usize) -> Result<u64, CommandError> { + if add(rw_off, u64::try_from(rw_size)?)? > self.part.window_size { + return Err("Read/Write range overflow".into()); + } + Ok(add(rw_off, self.part.window_start)?) + } + + /// Reads from the GBL Fastboot partition. + pub fn read(&mut self, offset: u64, out: &mut [u8]) -> Result<(), CommandError> { + let offset = self.check_range(offset, out.len())?; + let mut dev = (&mut self.devs).get(self.part.blk_id)?; + Ok(match self.part.part() { + "" => dev.read(offset, out), + part => dev.read_gpt_partition(part, offset, out), + }?) + } + + /// Writes to the GBL Fastboot partition. + pub fn write(&mut self, offset: u64, data: &mut [u8]) -> Result<(), CommandError> { + let offset = self.check_range(offset, data.len())?; + let mut dev = (&mut self.devs).get(self.part.blk_id)?; + Ok(match self.part.part() { + "" => dev.write(offset, data), + part => dev.write_gpt_partition_mut(part, offset, data), + }?) + } + + /// Returns the size of the GBL Fastboot partition. + pub fn size(&mut self) -> u64 { + self.part.window_size + } +} + /// `GblFastboot` implements fastboot commands in the GBL context. pub struct GblFastboot<'a> { pub storage: &'a mut dyn AsMultiBlockDevices, @@ -28,6 +95,9 @@ impl<'a> GblFastboot<'a> { /// Native GBL fastboot variables. const NATIVE_VARS: &'static [&'static dyn Variable] = &[ &("version-bootloader", "1.0"), // Placeholder for now. + // GBL Fastboot can internally handle uploading in batches, thus there is no limit on + // max-fetch-size. + &("max-fetch-size", "0xffffffffffffffff"), &BlockDevice {}, &Partition {}, ]; @@ -45,6 +115,56 @@ impl<'a> GblFastboot<'a> { pub fn storage(&mut self) -> &mut &'a mut dyn AsMultiBlockDevices { &mut self.storage } + + /// Parses and checks partition name, block device ID and offset from the arguments and + /// returns an instance of `GblFbPartition`. + pub(crate) fn parse_partition<'b>( + &mut self, + mut args: Split<'b, char>, + ) -> Result<GblFbPartition, CommandError> { + let devs = self.storage(); + // Copies over partition name string + let part = next_arg(&mut args, Ok(""))?; + let mut part_str = [0u8; GPT_NAME_LEN_U8]; + part_str + .get_mut(..part.len()) + .ok_or("Partition name too long")? + .clone_from_slice(part.as_bytes()); + // Parses block device ID. + let blk_id = next_arg_u64(&mut args, Err("".into())).ok(); + // Parses offset + let window_start = next_arg_u64(&mut args, Ok(0))?; + // Checks blk_id and computes maximum partition size. + let (blk_id, max_size) = match part { + "" => { + let blk_id = blk_id.ok_or("Must provide a block device ID")?; + (blk_id, devs.get(blk_id)?.total_size()?) + } + gpt => match blk_id { + Some(id) => (id, devs.get(id)?.find_partition(gpt)?.size()?), + _ => { + devs.check_part(gpt).map(|(id, p)| Ok::<_, CommandError>((id, p.size()?)))?? + } + }, + }; + let max_size = max_size.checked_sub(window_start).ok_or("Offset overflows")?; + // Parses size or uses `max_size` + let window_size = next_arg_u64(&mut args, Ok(max_size))?; + match window_size > max_size { + true => Err("Size overflows".into()), + _ => Ok(GblFbPartition { + part: part_str, + blk_id: blk_id, + window_start: window_start, + window_size: window_size, + }), + } + } + + /// Creates an instance of `GblFbPartitionIO` + pub(crate) fn partition_io(&mut self, part: GblFbPartition) -> GblFbPartitionIo { + GblFbPartitionIo { part: part, devs: self.storage() } + } } impl FastbootImplementation for GblFastboot<'_> { @@ -77,31 +197,62 @@ impl FastbootImplementation for GblFastboot<'_> { Err("Unimplemented".into()) } + fn fetch( + &mut self, + part: &str, + offset: u64, + size: u64, + upload_builder: UploadBuilder, + utils: &mut FastbootUtils, + ) -> Result<(), CommandError> { + let part = self.parse_partition(part.split(':'))?; + let buffer = utils.take_download_buffer(); + let buffer_len = u64::try_from(buffer.len()) + .map_err::<CommandError, _>(|_| "buffer size overflow".into())?; + let end = add(offset, size)?; + let mut curr = offset; + let mut uploader = upload_builder.start(size)?; + while curr < end { + let to_send = min(end - curr, buffer_len); + self.partition_io(part).read(curr, &mut buffer[..to_usize(to_send)?])?; + uploader.upload(&mut buffer[..to_usize(to_send)?])?; + curr += to_send; + } + Ok(()) + } + fn oem<'a>( &mut self, _cmd: &str, utils: &mut FastbootUtils, _res: &'a mut [u8], ) -> Result<&'a [u8], CommandError> { - utils.fb_info.as_mut().unwrap().send("GBL OEM not implemented yet")?; + let _ = utils.info_send("GBL OEM not implemented yet")?; Err("Unimplemented".into()) } } -/// A helper to convert a hex string into u64. -pub(crate) fn hex_str_to_u64(s: &str) -> Result<u64, CommandError> { - Ok(u64::from_str_radix(s.strip_prefix("0x").unwrap_or(s), 16)?) +/// Check and convert u64 into usize +fn to_usize(val: u64) -> Result<usize, CommandError> { + val.try_into().map_err(|_| "Overflow".into()) } -/// A helper to check and fetch the next argument from a `Split` or fail with given message. -pub(crate) fn next_arg<'a>(args: &mut Split<'a, char>, err: &str) -> Result<&'a str, CommandError> { - args.next().ok_or(err.into()) +/// Add two u64 integers and check overflow +fn add(lhs: u64, rhs: u64) -> Result<u64, CommandError> { + lhs.checked_add(rhs).ok_or("Overflow".into()) +} + +/// Subtracts two u64 integers and check overflow +fn sub(lhs: u64, rhs: u64) -> Result<u64, CommandError> { + lhs.checked_sub(rhs).ok_or("Overflow".into()) } #[cfg(test)] mod test { use super::*; + use fastboot::test_utils::with_mock_upload_builder; use gbl_storage::{required_scratch_size, AsBlockDevice, BlockIo}; + use std::string::String; use Vec; const BLOCK_SIZE: u64 = 512; @@ -165,8 +316,8 @@ mod test { /// Helper to test fastboot variable value. fn check_var(gbl_fb: &mut GblFastboot, var: &str, args: &str, expected: &str) { - let mut utils = - FastbootUtils { download_buffer: &mut [], download_data_size: 0, fb_info: None }; + let mut dl_size = 0; + let mut utils = FastbootUtils::new(&mut [], &mut dl_size, None); let mut out = vec![0u8; fastboot::MAX_RESPONSE_SIZE]; assert_eq!( gbl_fb.get_var_as_str(var, args.split(':'), &mut out[..], &mut utils).unwrap(), @@ -181,15 +332,25 @@ mod test { test_block_device(include_bytes!("../../../libstorage/test/gpt_test_2.bin")), ]); devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed")); - let mut gbl_fb = GblFastboot::new(&mut devs); + + // Check different semantics + check_var(&mut gbl_fb, "partition-size", "boot_a", "0x2000"); + check_var(&mut gbl_fb, "partition-size", "boot_a:", "0x2000"); + check_var(&mut gbl_fb, "partition-size", "boot_a::", "0x2000"); + check_var(&mut gbl_fb, "partition-size", "boot_a:::", "0x2000"); check_var(&mut gbl_fb, "partition-size", "boot_a:0", "0x2000"); + check_var(&mut gbl_fb, "partition-size", "boot_a:0:", "0x2000"); + check_var(&mut gbl_fb, "partition-size", "boot_a::0", "0x2000"); + check_var(&mut gbl_fb, "partition-size", "boot_a:0:0", "0x2000"); + check_var(&mut gbl_fb, "partition-size", "boot_a::0x1000", "0x1000"); + check_var(&mut gbl_fb, "partition-size", "boot_b:0", "0x3000"); check_var(&mut gbl_fb, "partition-size", "vendor_boot_a:1", "0x1000"); check_var(&mut gbl_fb, "partition-size", "vendor_boot_b:1", "0x1800"); - let mut utils = - FastbootUtils { download_buffer: &mut [], download_data_size: 0, fb_info: None }; + let mut dl_size = 0; + let mut utils = FastbootUtils::new(&mut [], &mut dl_size, None); let mut out = vec![0u8; fastboot::MAX_RESPONSE_SIZE]; assert!(gbl_fb .get_var_as_str("partition", "non-existent".split(':'), &mut out[..], &mut utils) @@ -205,8 +366,8 @@ mod test { devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed")); let mut gbl_fb = GblFastboot::new(&mut devs); - let mut utils = - FastbootUtils { download_buffer: &mut [], download_data_size: 0, fb_info: None }; + let mut dl_size = 0; + let mut utils = FastbootUtils::new(&mut [], &mut dl_size, None); let mut out: Vec<String> = Default::default(); gbl_fb .get_var_all( @@ -221,6 +382,7 @@ mod test { out, [ "version-bootloader:: 1.0", + "max-fetch-size:: 0xffffffffffffffff", "block-device:0:total-blocks: 0x80", "block-device:0:block-size: 0x200", "block-device:1:total-blocks: 0x100", @@ -236,4 +398,131 @@ mod test { ] ); } + + /// A helper for fetching partition from a `GblFastboot` + fn fetch( + fb: &mut GblFastboot, + part: String, + off: u64, + size: u64, + ) -> Result<Vec<u8>, CommandError> { + let mut dl_size = 0; + // Forces upload in two batches for testing. + let mut download_buffer = + vec![0u8; core::cmp::max(1, usize::try_from(size).unwrap() / 2usize)]; + let mut utils = FastbootUtils::new(&mut download_buffer[..], &mut dl_size, None); + let mut upload_out = vec![0u8; usize::try_from(size).unwrap()]; + let mut res = Ok(()); + let (uploaded, _) = with_mock_upload_builder(&mut upload_out[..], |upload_builder| { + res = fb.fetch(part.as_str(), off, size, upload_builder, &mut utils) + }); + assert!(res.is_err() || uploaded == usize::try_from(size).unwrap()); + res.map(|_| upload_out) + } + + #[test] + fn test_fetch_invalid_partition_arg() { + let mut devs = TestMultiBlockDevices(vec![ + test_block_device(include_bytes!("../../../libstorage/test/gpt_test_1.bin")), + test_block_device(include_bytes!("../../../libstorage/test/gpt_test_2.bin")), + test_block_device(include_bytes!("../../../libstorage/test/gpt_test_2.bin")), + ]); + devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed")); + let mut fb = GblFastboot::new(&mut devs); + + // Missing mandatory block device ID for raw block partition. + assert!(fetch(&mut fb, "::0:0".into(), 0, 0).is_err()); + + // GPT partition does not exist. + assert!(fetch(&mut fb, "non:::".into(), 0, 0).is_err()); + + // GPT Partition is not unique. + assert!(fetch(&mut fb, "vendor_boot_a:::".into(), 0, 0).is_err()); + + // Offset overflows. + assert!(fetch(&mut fb, "boot_a::0x2001:".into(), 0, 1).is_err()); + assert!(fetch(&mut fb, "boot_a".into(), 0x2000, 1).is_err()); + + // Size overflows. + assert!(fetch(&mut fb, "boot_a:::0x2001".into(), 0, 0).is_err()); + assert!(fetch(&mut fb, "boot_a".into(), 0, 0x2001).is_err()); + } + + /// A helper for testing raw block upload. It verifies that data read from block device + /// `blk_id` in range [`off`, `off`+`size`) is the same as `disk[off..][..size]` + fn check_blk_upload(fb: &mut GblFastboot, blk_id: u64, off: u64, size: u64, disk: &[u8]) { + let expected = disk[off.try_into().unwrap()..][..size.try_into().unwrap()].to_vec(); + // offset/size as part of the partition string. + let part = format!(":{:#x}:{:#x}:{:#x}", blk_id, off, size); + assert_eq!(fetch(fb, part, 0, size).unwrap(), expected); + // offset/size as separate fetch arguments. + let part = format!(":{:#x}", blk_id); + assert_eq!(fetch(fb, part, off, size).unwrap(), expected); + } + + #[test] + fn test_fetch_raw_block() { + let disk_0 = include_bytes!("../../../libstorage/test/gpt_test_1.bin"); + let disk_1 = include_bytes!("../../../libstorage/test/gpt_test_2.bin"); + let mut devs = + TestMultiBlockDevices(vec![test_block_device(disk_0), test_block_device(disk_1)]); + devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed")); + let mut gbl_fb = GblFastboot::new(&mut devs); + + let off = 512; + let size = 512; + check_blk_upload(&mut gbl_fb, 0, off, size, disk_0); + check_blk_upload(&mut gbl_fb, 1, off, size, disk_1); + } + + /// A helper for testing uploading GPT partition. It verifies that data read from GPT partition + /// `part` at disk `blk_id` in range [`off`, `off`+`size`) is the same as + /// `partition_data[off..][..size]`. + fn check_gpt_upload( + fb: &mut GblFastboot, + part: &str, + off: u64, + size: u64, + blk_id: Option<u64>, + partition_data: &[u8], + ) { + let expected = + partition_data[off.try_into().unwrap()..][..size.try_into().unwrap()].to_vec(); + let blk_id = blk_id.map_or("".to_string(), |v| format!("{:#x}", v)); + // offset/size as part of the partition string. + let gpt_part = format!("{}:{}:{:#x}:{:#x}", part, blk_id, off, size); + assert_eq!(fetch(fb, gpt_part, 0, size).unwrap(), expected); + // offset/size as separate fetch arguments. + let gpt_part = format!("{}:{}", part, blk_id); + assert_eq!(fetch(fb, gpt_part, off, size).unwrap(), expected); + } + + #[test] + fn test_fetch_gpt_partition() { + let mut devs = TestMultiBlockDevices(vec![ + test_block_device(include_bytes!("../../../libstorage/test/gpt_test_1.bin")), + test_block_device(include_bytes!("../../../libstorage/test/gpt_test_2.bin")), + ]); + devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed")); + let mut gbl_fb = GblFastboot::new(&mut devs); + + let expect_boot_a = include_bytes!("../../../libstorage/test/boot_a.bin"); + let expect_boot_b = include_bytes!("../../../libstorage/test/boot_b.bin"); + let expect_vendor_boot_a = include_bytes!("../../../libstorage/test/vendor_boot_a.bin"); + let expect_vendor_boot_b = include_bytes!("../../../libstorage/test/vendor_boot_b.bin"); + + let size = 512; + let off = 512; + + check_gpt_upload(&mut gbl_fb, "boot_a", off, size, Some(0), expect_boot_a); + check_gpt_upload(&mut gbl_fb, "boot_b", off, size, Some(0), expect_boot_b); + check_gpt_upload(&mut gbl_fb, "vendor_boot_a", off, size, Some(1), expect_vendor_boot_a); + check_gpt_upload(&mut gbl_fb, "vendor_boot_b", off, size, Some(1), expect_vendor_boot_b); + + // No block device id + check_gpt_upload(&mut gbl_fb, "boot_a", off, size, None, expect_boot_a); + check_gpt_upload(&mut gbl_fb, "boot_b", off, size, None, expect_boot_b); + check_gpt_upload(&mut gbl_fb, "vendor_boot_a", off, size, None, expect_vendor_boot_a); + check_gpt_upload(&mut gbl_fb, "vendor_boot_b", off, size, None, expect_vendor_boot_b); + } } diff --git a/gbl/libgbl/src/fastboot/vars.rs b/gbl/libgbl/src/fastboot/vars.rs index ac0eb56..5ff0fe7 100644 --- a/gbl/libgbl/src/fastboot/vars.rs +++ b/gbl/libgbl/src/fastboot/vars.rs @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::fastboot::{hex_str_to_u64, next_arg, GblFastboot}; +use crate::fastboot::{GblFastboot, GPT_NAME_LEN_U8}; use core::fmt::Write; use core::str::{from_utf8, Split}; -use fastboot::{snprintf, CommandError, FormattedBytes}; -use gbl_storage::{AsBlockDevice, AsMultiBlockDevices, GPT_NAME_LEN_U16}; +use fastboot::{next_arg, next_arg_u64, snprintf, CommandError, FormattedBytes}; +use gbl_storage::{AsBlockDevice, AsMultiBlockDevices}; /// Internal trait that provides methods for getting and enumerating values for one or multiple /// related fastboot variables. @@ -64,8 +64,8 @@ impl Variable for (&'static str, &'static str) { /// `Partition` variable provides information of GPT partitions /// -/// `fastboot getvar partition-size:<partition name>[:<block-device>]` -/// `fastboot getvar partition-type:<partition name>[:<block-device>]` +/// `fastboot getvar partition-size:<GBL Fastboot partition>` +/// `fastboot getvar partition-type:<GBL Fastboot partition>` pub(crate) struct Partition {} const PARTITION_SIZE: &str = "partition-size"; @@ -75,19 +75,12 @@ impl Variable for Partition { &self, gbl_fb: &mut GblFastboot, name: &str, - mut args: Split<char>, + args: Split<char>, out: &mut [u8], ) -> Result<Option<usize>, CommandError> { - let part = next_arg(&mut args, "Missing partition name")?; - let blk_id = args.next().map(|v| hex_str_to_u64(v)).transpose()?; + let part = gbl_fb.parse_partition(args)?; Ok(match name { - PARTITION_SIZE => { - let sz = match blk_id { - Some(id) => gbl_fb.storage().get(id)?.partition_size(part)?, - _ => gbl_fb.storage().partition_size(part)?, - }; - Some(snprintf!(out, "{:#x}", sz).len()) - } + PARTITION_SIZE => Some(snprintf!(out, "{:#x}", gbl_fb.partition_io(part).size()).len()), PARTITION_TYPE => Some(snprintf!(out, "raw").len()), // Image type not supported yet. _ => None, }) @@ -98,25 +91,23 @@ impl Variable for Partition { gbl_fb: &mut GblFastboot, f: &mut dyn FnMut(&str, &[&str], &str) -> Result<(), CommandError>, ) -> Result<(), CommandError> { + // Though any sub range of a GPT partition or raw block counts as a partition in GBL + // Fastboot, for "getvar all" we only enumerate whole range GPT partitions. let mut res: Result<(), CommandError> = Ok(()); - let part_name = &mut [0u8; GPT_NAME_LEN_U16 * 2][..]; + let part_name = &mut [0u8; GPT_NAME_LEN_U8][..]; let mut size_str = [0u8; 32]; gbl_fb.storage().for_each_until(&mut |mut v, id| { - // AsBlockDevice::partition_iter() has `Self:Sized` constraint thus we make it into a - // &mut &mut dyn AsBlockDevice to meet the bound requirement. + // `AsBlockDevice::partition_iter()` has `Self:Sized` constraint thus we make it into a + // `&mut &mut dyn AsBlockDevice` to meet the bound requirement. let v = &mut v; let mut id_str = [0u8; 32]; let id = snprintf!(id_str, "{:x}", id); res = (|| { - let block_size = v.block_size()?; for ptn in v.partition_iter() { - let sz = ptn - .blocks()? - .checked_mul(block_size) - .ok_or::<CommandError>("Partition size overflow".into())?; - let part = ptn.name_to_str(part_name)?; + let sz = ptn.size()?; + let part = ptn.gpt_entry().name_to_str(part_name)?; f(PARTITION_SIZE, &[part, id], snprintf!(size_str, "{:#x}", sz))?; - // Image type not supported yet. + // Image type is not supported yet. f(PARTITION_TYPE, &[part, id], snprintf!(size_str, "raw"))?; } Ok(()) @@ -146,9 +137,8 @@ impl Variable for BlockDevice { ) -> Result<Option<usize>, CommandError> { Ok(match name { BLOCK_DEVICE => { - let id = next_arg(&mut args, "Missing block device ID")?; - let val_type = next_arg(&mut args, "Missing value type")?; - let id = hex_str_to_u64(id)?; + let id = next_arg_u64(&mut args, Err("Missing block device ID".into()))?; + let val_type = next_arg(&mut args, Err("Missing value type".into()))?; let val = match val_type { TOTAL_BLOCKS => gbl_fb.storage().get(id)?.num_blocks()?, BLOCK_SIZE => gbl_fb.storage().get(id)?.block_size()?, diff --git a/gbl/libstorage/src/lib.rs b/gbl/libstorage/src/lib.rs index 1097555..d743087 100644 --- a/gbl/libstorage/src/lib.rs +++ b/gbl/libstorage/src/lib.rs @@ -120,7 +120,7 @@ pub use multi_blocks::{with_id, AsMultiBlockDevices}; pub type Result<T> = core::result::Result<T, StorageError>; /// Error code for this library. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum StorageError { ArithmeticOverflow, OutOfRange, @@ -203,23 +203,57 @@ pub trait BlockIo { fn write_blocks(&mut self, blk_offset: u64, data: &[u8]) -> bool; } -/// `GptEntryIterator` is returned by `AsBlockDevice::partition_iter()` and can be used to iterate +/// `Partition` contains information about a GPT partition. +#[derive(Debug, Copy, Clone)] +pub struct Partition { + entry: GptEntry, + block_size: u64, +} + +impl Partition { + /// Creates a new instance. + fn new(entry: GptEntry, block_size: u64) -> Self { + Self { entry, block_size } + } + + /// Returns the partition size in bytes. + pub fn size(&self) -> Result<u64> { + Ok(mul(self.block_size, self.entry.blocks()?)?) + } + + /// Returns the block size of this partition. + pub fn block_size(&self) -> u64 { + self.block_size + } + + /// Returns the partition entry structure in the GPT header. + pub fn gpt_entry(&self) -> &GptEntry { + &self.entry + } +} + +/// `PartitionIterator` is returned by `AsBlockDevice::partition_iter()` and can be used to iterate /// all GPT partition entries. -pub struct GptEntryIterator<'a> { +pub struct PartitionIterator<'a> { dev: &'a mut dyn AsBlockDevice, idx: usize, } -impl Iterator for GptEntryIterator<'_> { - type Item = GptEntry; +impl Iterator for PartitionIterator<'_> { + type Item = Partition; fn next(&mut self) -> Option<Self::Item> { - let res = - with_partitioned_scratch(self.dev, |_, _, gpt_buffer, _| -> Result<Option<GptEntry>> { - Ok(Gpt::from_existing(gpt_buffer)?.entries()?.get(self.idx).map(|v| *v)) - }) - .ok()? - .ok()??; + let res = with_partitioned_scratch( + self.dev, + |io, _, gpt_buffer, _| -> Result<Option<Partition>> { + Ok(Gpt::from_existing(gpt_buffer)? + .entries()? + .get(self.idx) + .map(|v| Partition::new(*v, io.block_size()))) + }, + ) + .ok()? + .ok()??; self.idx += 1; Some(res) } @@ -260,16 +294,21 @@ pub trait AsBlockDevice { /// smaller bound on it, it is recommended to always return 128. fn with(&mut self, f: &mut dyn FnMut(&mut dyn BlockIo, &mut [u8], u64)); - // Returns the block size of the underlying `BlockIo` + /// Returns the block size of the underlying `BlockIo` fn block_size(&mut self) -> Result<u64> { with_partitioned_scratch(self, |io, _, _, _| io.block_size()) } - // Returns the number of blocks of the underlying `BlockIo` + /// Returns the number of blocks of the underlying `BlockIo` fn num_blocks(&mut self) -> Result<u64> { with_partitioned_scratch(self, |io, _, _, _| io.num_blocks()) } + /// Returns the total size in number of bytes. + fn total_size(&mut self) -> Result<u64> { + mul(self.block_size()?, self.num_blocks()?) + } + /// Read data from the block device. /// /// # Args @@ -339,35 +378,27 @@ pub trait AsBlockDevice { } /// Returns an iterator to GPT partition entries. - fn partition_iter(&mut self) -> GptEntryIterator + fn partition_iter(&mut self) -> PartitionIterator where Self: Sized, { - GptEntryIterator { dev: self, idx: 0 } + PartitionIterator { dev: self, idx: 0 } } - /// Returns the `GptEntry` for a partition. + /// Returns the `Partition` for a partition. /// /// # Args /// /// * `part`: Name of the partition. - fn find_partition(&mut self, part: &str) -> Result<GptEntry> { - with_partitioned_scratch(self, |_, _, gpt_buffer, _| { - Ok(Gpt::from_existing(gpt_buffer)?.find_partition(part).map(|v| *v)?) + fn find_partition(&mut self, part: &str) -> Result<Partition> { + with_partitioned_scratch(self, |io, _, gpt_buffer, _| { + Ok(Partition::new( + *Gpt::from_existing(gpt_buffer)?.find_partition(part)?, + io.block_size(), + )) })? } - /// Returns the size of a partition. - /// - /// # Args - /// - /// * `part`: Name of the partition. - fn partition_size(&mut self, part: &str) -> Result<u64> { - let blk_sz = self.block_size()?; - let entry = self.find_partition(part)?; - mul(blk_sz, entry.blocks()?) - } - /// Read a GPT partition on a block device /// /// # Args diff --git a/gbl/libstorage/src/multi_blocks.rs b/gbl/libstorage/src/multi_blocks.rs index 9f6686c..9437c8a 100644 --- a/gbl/libstorage/src/multi_blocks.rs +++ b/gbl/libstorage/src/multi_blocks.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{mul, AsBlockDevice, BlockIo, GptEntry, Result, StorageError}; +use crate::{AsBlockDevice, BlockIo, Partition, Result, StorageError}; /// `AsMultiBlockDevices` provides APIs for finding/reading/writing raw data or GPT partitions from /// multiple block devices. @@ -43,37 +43,26 @@ pub trait AsMultiBlockDevices { } /// Checks that a partition exists and is unique among all block devices with GPT. - fn check_part(&mut self, part: &str) -> Result<()> { - let mut count = 0usize; - self.for_each_until(&mut |v, _| { - count += (|| -> Result<bool> { Ok(v.find_partition(part).is_ok()) })().unwrap_or(false) - as usize; - count > 1 + /// + /// Returns the block device ID for the partition. + fn check_part(&mut self, part: &str) -> Result<(u64, Partition)> { + let mut res = Err(StorageError::NotExist); + self.for_each_until(&mut |v, id| { + res = match v.find_partition(part).map(|v| (id, v)) { + Ok(_) if res.is_ok() => Err(StorageError::PartitionNotUnique), + v => v.or(res), + }; + res.err() == Some(StorageError::PartitionNotUnique) }); - match count { - 1 => Ok(()), - 0 => Err(StorageError::NotExist), - _ => Err(StorageError::PartitionNotUnique), - } + res } /// Returns the block size and `GptEntry` for a partition. /// /// Returns Ok(()) if the partition is found and unique among all block devices. - fn find_partition(&mut self, part: &str) -> Result<(u64, GptEntry)> { + fn find_partition(&mut self, part: &str) -> Result<Partition> { self.check_part(part)?; - until_ok(self, |dev, _| { - let blk_sz = dev.block_size()?; - dev.find_partition(part).map(|v| (blk_sz, v)) - }) - } - - /// Returns the size of a partition. - /// - /// Returns Ok(()) if the partition is found and unique among all block devices. - fn partition_size(&mut self, part: &str) -> Result<u64> { - let (block_size, entry) = self.find_partition(part)?; - Ok(mul(block_size, entry.blocks()?)?) + until_ok(self, |dev, _| dev.find_partition(part)) } /// Reads a GPT partition. @@ -224,17 +213,29 @@ mod test { ]; devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed")); - assert_eq!(devs.partition_size("boot_a").unwrap(), 8 * 1024); - assert_eq!(devs.get(0).unwrap().partition_size("boot_a").unwrap(), 8 * 1024); - - assert_eq!(devs.partition_size("boot_b").unwrap(), 12 * 1024); - assert_eq!(devs.get(0).unwrap().partition_size("boot_b").unwrap(), 12 * 1024); - - assert_eq!(devs.partition_size("vendor_boot_a").unwrap(), 4 * 1024); - assert_eq!(devs.get(1).unwrap().partition_size("vendor_boot_a").unwrap(), 4 * 1024); - - assert_eq!(devs.partition_size("vendor_boot_b").unwrap(), 6 * 1024); - assert_eq!(devs.get(1).unwrap().partition_size("vendor_boot_b").unwrap(), 6 * 1024); + assert_eq!(devs.find_partition("boot_a").and_then(|v| v.size()).unwrap(), 8 * 1024); + assert_eq!( + devs.get(0).unwrap().find_partition("boot_a").and_then(|v| v.size()).unwrap(), + 8 * 1024 + ); + + assert_eq!(devs.find_partition("boot_b").and_then(|v| v.size()).unwrap(), 12 * 1024); + assert_eq!( + devs.get(0).unwrap().find_partition("boot_b").and_then(|v| v.size()).unwrap(), + 12 * 1024 + ); + + assert_eq!(devs.find_partition("vendor_boot_a").and_then(|v| v.size()).unwrap(), 4 * 1024); + assert_eq!( + devs.get(1).unwrap().find_partition("vendor_boot_a").and_then(|v| v.size()).unwrap(), + 4 * 1024 + ); + + assert_eq!(devs.find_partition("vendor_boot_b").and_then(|v| v.size()).unwrap(), 6 * 1024); + assert_eq!( + devs.get(1).unwrap().find_partition("vendor_boot_b").and_then(|v| v.size()).unwrap(), + 6 * 1024 + ); } /// A test helper for `AsMultiBlockDevices::read_gpt_partition` @@ -334,6 +335,5 @@ mod test { assert!(devs.write_gpt_partition_mut("boot_a", 0, &mut []).is_err()); assert!(devs.write_gpt_partition("boot_a", 0, &mut []).is_err()); assert!(devs.find_partition("boot_a").is_err()); - assert!(devs.partition_size("boot_a").is_err()); } } |