summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYecheng Zhao <zyecheng@google.com>2024-03-27 21:06:56 +0000
committerYecheng Zhao <zyecheng@google.com>2024-03-29 17:19:39 +0000
commit1a60f01421a6dded0cf1aca2c9dd542c03eea3b5 (patch)
tree54baa267e7f7cd36c27d9b4188eaa349501a0fe3
parent23a26fb23693bb682f666d0fafe8bb56a96fe2f8 (diff)
downloadlibbootloader-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.md59
-rw-r--r--gbl/efi/src/android_boot.rs2
-rw-r--r--gbl/efi/src/avb.rs11
-rw-r--r--gbl/efi/src/fuchsia_boot.rs2
-rw-r--r--gbl/libfastboot/src/lib.rs313
-rw-r--r--gbl/libgbl/src/fastboot/mod.rs321
-rw-r--r--gbl/libgbl/src/fastboot/vars.rs46
-rw-r--r--gbl/libstorage/src/lib.rs91
-rw-r--r--gbl/libstorage/src/multi_blocks.rs74
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());
}
}