summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYecheng Zhao <zyecheng@google.com>2024-03-19 21:14:27 +0000
committerYecheng Zhao <zyecheng@google.com>2024-03-21 17:51:42 +0000
commit7bda36f0c88cb817a70106331e8070f3f34b4897 (patch)
tree0c4e9b98cea5b76c38d76e931a3299fa880c46c8
parentcf51f287c340c75a15b070440718032643006a5f (diff)
downloadlibbootloader-7bda36f0c88cb817a70106331e8070f3f34b4897.tar.gz
Implement GBL fastboot backend
Implements fastboot backends using generic GBL interfaces. Initially support `fastboot getvar [partition|block-device]`. Bug: 328784766 Change-Id: Ic236bb8cefc841e13185fcacccb4b42796fa6ae8
-rw-r--r--gbl/efi/src/fastboot.rs30
-rw-r--r--gbl/efi/src/main.rs2
-rw-r--r--gbl/libfastboot/src/lib.rs197
-rw-r--r--gbl/libgbl/BUILD2
-rw-r--r--gbl/libgbl/src/fastboot/mod.rs207
-rw-r--r--gbl/libgbl/src/fastboot/vars.rs181
-rw-r--r--gbl/libgbl/src/lib.rs1
-rw-r--r--gbl/libstorage/BUILD9
-rw-r--r--gbl/libstorage/src/gpt.rs10
-rw-r--r--gbl/libstorage/src/lib.rs2
-rw-r--r--gbl/libstorage/src/multi_blocks.rs6
-rw-r--r--gbl/libstorage/test/BUILD16
12 files changed, 536 insertions, 127 deletions
diff --git a/gbl/efi/src/fastboot.rs b/gbl/efi/src/fastboot.rs
index 8789e04..e2359af 100644
--- a/gbl/efi/src/fastboot.rs
+++ b/gbl/efi/src/fastboot.rs
@@ -19,10 +19,11 @@
use crate::error::Result;
use crate::net::{with_efi_network, EfiTcpSocket};
+use crate::utils::find_gpt_devices;
use core::fmt::Write;
-use core::str::Split;
use efi::{efi_print, efi_println, EfiEntry};
-use fastboot::{CommandError, Fastboot, FastbootImplementation, TcpStream, TransportError};
+use fastboot::{Fastboot, TcpStream, TransportError};
+use libgbl::fastboot::GblFastboot;
const DEFAULT_TIMEOUT_MS: u64 = 5_000;
const FASTBOOT_TCP_PORT: u16 = 5554;
@@ -48,35 +49,14 @@ impl TcpStream for EfiFastbootTcpTransport<'_, '_, '_> {
}
}
-/// TODO(b/328786603): Placeholder only. It'll be replaced by a generic GBL Fastboot implementation
-/// in a separate library.
-pub struct GblFastboot {}
-
-impl FastbootImplementation for GblFastboot {
- fn get_var(
- &mut self,
- _: &str,
- _: Split<char>,
- _: &mut [u8],
- ) -> core::result::Result<usize, CommandError> {
- Err("Not found".into())
- }
-
- fn get_var_all<F>(&mut self, _: F) -> core::result::Result<(), CommandError>
- where
- F: FnMut(&str, &[&str], &str),
- {
- Ok(())
- }
-}
-
/// Internal helper for performing Fastboot over TCP.
fn fastboot_tcp_usb(
socket: &mut EfiTcpSocket,
efi_entry: &EfiEntry,
download_buffer: &mut [u8],
) -> Result<()> {
- let mut gbl_fastboot = GblFastboot {};
+ let mut gpt_devices = find_gpt_devices(efi_entry)?;
+ let mut gbl_fastboot = GblFastboot::new(&mut gpt_devices);
efi_println!(efi_entry, "Listening for Fastboot over TCP...");
efi_println!(efi_entry, "IP addresses:");
socket.interface().ip_addrs().iter().for_each(|v| {
diff --git a/gbl/efi/src/main.rs b/gbl/efi/src/main.rs
index aad07fb..b1c3247 100644
--- a/gbl/efi/src/main.rs
+++ b/gbl/efi/src/main.rs
@@ -52,7 +52,7 @@ fn main(image_handle: *mut core::ffi::c_void, systab_ptr: *mut EfiSystemTable) -
efi_println!(entry, "Image path: {}", v);
}
- efi_println!(entry, "Press 'f' to enter fastboot. TODO(b/328786603)");
+ efi_println!(entry, "Press 'f' to enter fastboot.");
match wait_key_stroke(&entry, 'f', 2000) {
Ok(true) => {
efi_println!(entry, "'f' pressed.");
diff --git a/gbl/libfastboot/src/lib.rs b/gbl/libfastboot/src/lib.rs
index 0584f42..4e471a2 100644
--- a/gbl/libfastboot/src/lib.rs
+++ b/gbl/libfastboot/src/lib.rs
@@ -66,15 +66,15 @@
#![cfg_attr(not(test), no_std)]
-use core::fmt::{Display, Write};
-use core::str::Split;
+use core::fmt::{Display, Error, Formatter, Write};
+use core::str::{from_utf8, Split};
-const MAX_COMMAND_SIZE: usize = 4096;
-const MAX_RESPONSE_SIZE: usize = 256;
+pub const MAX_COMMAND_SIZE: usize = 4096;
+pub const MAX_RESPONSE_SIZE: usize = 256;
const OKAY: &'static str = "OKAY";
/// Transport errors.
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum TransportError {
InvalidHanshake,
PacketSizeOverflow,
@@ -82,6 +82,12 @@ pub enum TransportError {
Others(&'static str),
}
+impl Display for TransportError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
+ write!(f, "{:?}", self)
+ }
+}
+
/// Implementation for Fastboot transport interfaces.
pub trait Transport {
/// Fetches the next fastboot packet into `out`.
@@ -155,8 +161,8 @@ pub struct CommandError(FormattedBytes<[u8; COMMAND_ERROR_LENGTH]>);
impl CommandError {
/// Converts to string.
- fn to_str(&self) -> &str {
- core::str::from_utf8(&self.0 .0[..self.0 .1]).unwrap_or("")
+ pub fn to_str(&self) -> &str {
+ from_utf8(&self.0 .0[..self.0 .1]).unwrap_or("")
}
}
@@ -184,6 +190,18 @@ pub trait FastbootImplementation {
out: &mut [u8],
) -> Result<usize, CommandError>;
+ /// A helper API for getting the value of a fastboot variable and decoding it into string.
+ fn get_var_as_str<'s>(
+ &mut self,
+ var: &str,
+ args: Split<char>,
+ out: &'s mut [u8],
+ ) -> Result<&'s str, CommandError> {
+ let size = self.get_var(var, args, out)?;
+ Ok(from_utf8(out.get(..size).ok_or("Invalid variable size")?)
+ .map_err(|_| "Value is not string")?)
+ }
+
/// Backend for `fastboot getvar all`.
///
/// Iterates all combinations of fastboot variable, configurations and values that need to be
@@ -191,33 +209,35 @@ pub trait FastbootImplementation {
///
/// # Args
///
- /// * `f`: A closure that takes 3 arguments: 1. variable name, 2. an array of string
- /// arguments and 3. the corresponding variable value. Implementation should call this for
- /// all combinations that need to be returned for `fastboot getvar all`. For example the
- /// following implementation
+ /// * `f`: A closure that takes 3 arguments: 1. variable name, 2. an array of string
+ /// arguments and 3. the corresponding variable value. Implementation should call this for
+ /// all combinations that need to be returned for `fastboot getvar all`. If `f` returns
+ /// error, the implementation should return it immediately. For example the following
+ /// implementation:
///
- /// fn get_var_all<F>(&mut self, f: F) -> Result<(), CommandError> {
- /// f("partition-size", &["boot_a"], /* size string of boot_a */);
- /// f("partition-size", &["boot_b"], /* size string of boot_b */);
- /// f("partition-size", &["init_boot_a"], /* size string of init_boot_a */);
- /// f("partition-size", &["init_boot_b"], /* size string of init_boot_b */);
- /// Ok(())
- /// }
+ /// fn get_var_all(&mut self, f: F) -> Result<(), CommandError> {
+ /// f("partition-size", &["boot_a"], /* size string of boot_a */)?;
+ /// f("partition-size", &["boot_b"], /* size string of boot_b */)?;
+ /// f("partition-size", &["init_boot_a"], /* size string of init_boot_a */)?;
+ /// f("partition-size", &["init_boot_b"], /* size string of init_boot_b */)?;
+ /// Ok(())
+ /// }
///
- /// will generates the following outputs for `fastboot getvar all`:
+ /// will generates the following outputs for `fastboot getvar all`:
///
- /// ...
- /// (bootloader) partition-size:boot_a: <size of boot_a>
- /// (bootloader) partition-size:boot_b: <size of boot_b>
- /// (bootloader) partition-size:init_boot_a: <size of init_boot_a>
- /// (bootloader) partition-size:init_boot_b: <size of init_boot_b>
- /// ...
+ /// ...
+ /// (bootloader) partition-size:boot_a: <size of boot_a>
+ /// (bootloader) partition-size:boot_b: <size of boot_b>
+ /// (bootloader) partition-size:init_boot_a: <size of init_boot_a>
+ /// (bootloader) partition-size:init_boot_b: <size of init_boot_b>
+ /// ...
///
/// TODO(b/322540167): This and `get_var()` contain duplicated logic. Investigate if there can
/// be better solutions for doing the combination traversal.
- fn get_var_all<F>(&mut self, f: F) -> Result<(), CommandError>
- where
- F: FnMut(&str, &[&str], &str);
+ fn get_var_all(
+ &mut self,
+ f: &mut dyn FnMut(&str, &[&str], &str) -> Result<(), CommandError>,
+ ) -> Result<(), CommandError>;
// TODO(b/322540167): Add methods for other commands.
}
@@ -245,6 +265,8 @@ macro_rules! fastboot_fail {
( $arr:expr, $( $x:expr ),* ) => { fastboot_msg!($arr, "FAIL", $($x,)*) };
}
+const MAX_DOWNLOAD_SIZE_NAME: &'static str = "max-download-size";
+
/// State of the fastboot protocol.
enum ProtocolState {
Command,
@@ -296,7 +318,7 @@ impl<'a> Fastboot<'a> {
}
let mut res = [0u8; MAX_RESPONSE_SIZE];
- let cmd = match core::str::from_utf8(&packet[..cmd_size]) {
+ let cmd = match from_utf8(&packet[..cmd_size]) {
Ok(s) => s,
_ => {
transport.send_packet(fastboot_fail!(res, "Invalid Command"))?;
@@ -322,10 +344,9 @@ impl<'a> Fastboot<'a> {
match transport.receive_packet(remains) {
Ok(size) if size > remains.len() => {
let mut res = [0u8; MAX_RESPONSE_SIZE];
- transport.send_packet(snprintf!(
- res,
- "FAILMore data received then expected"
- ))?;
+ transport.send_packet(
+ snprintf!(res, "FAILMore data received then expected").as_bytes(),
+ )?;
self.total_download_size = 0;
self.downloaded_size = 0;
self.state = ProtocolState::Command;
@@ -395,15 +416,31 @@ impl<'a> Fastboot<'a> {
out: &'s mut [u8],
fb_impl: &mut impl FastbootImplementation,
) -> Result<&'s str, CommandError> {
- if var == "max-download-size" {
- Ok(core::str::from_utf8(snprintf!(out, "{:#x}", self.download_buffer.len())).unwrap())
+ if var == MAX_DOWNLOAD_SIZE_NAME {
+ Ok(snprintf!(out, "{:#x}", self.download_buffer.len()))
} else {
- let size = fb_impl.get_var(var, args, out)?;
- Ok(core::str::from_utf8(out.get(..size).ok_or("Invalid variable size")?)
- .map_err(|_| "Value is not string")?)
+ fb_impl.get_var_as_str(var, args, out)
}
}
+ /// A wrapper of `get_var_all()` that first iterates reserved variables.
+ fn get_var_all_with_native(
+ &mut self,
+ fb_impl: &mut impl FastbootImplementation,
+ f: &mut dyn FnMut(&str, &[&str], &str) -> Result<(), CommandError>,
+ ) -> Result<(), CommandError> {
+ let mut size_str = [0u8; 32];
+
+ // Process the built-in MAX_DOWNLOAD_SIZE_NAME variable.
+ f(
+ MAX_DOWNLOAD_SIZE_NAME,
+ &[],
+ self.get_var_str(MAX_DOWNLOAD_SIZE_NAME, "".split(':'), &mut size_str[..], fb_impl)?,
+ )?;
+
+ fb_impl.get_var_all(f)
+ }
+
/// Method for handling "fastboot getvar all"
fn get_var_all(
&mut self,
@@ -412,35 +449,18 @@ impl<'a> Fastboot<'a> {
) -> Result<(), TransportError> {
let mut res = [0u8; MAX_RESPONSE_SIZE];
let mut transport_error = Ok(());
- // A closure for constructing a string of format `INFO<var>:<args>: <value>`
- let mut process_var = |name: &str, args: &[&str], val: &str| {
- // If we run into transport errors in previous call, don't process.
- if transport_error.is_ok() {
- let mut formatted_bytes = FormattedBytes::new(&mut res);
- write!(formatted_bytes, "INFO{}", name).unwrap();
- args.iter().for_each(|arg| write!(formatted_bytes, ":{}", arg).unwrap());
- write!(formatted_bytes, ": {}", val).unwrap();
- let size = formatted_bytes.size();
- transport_error = transport.send_packet(&res[..size]);
- }
- };
-
- // Process the built-in "max-download-size" variable.
- let mut var_val = [0u8; MAX_RESPONSE_SIZE];
- let val = self
- .get_var_str(
- "max-download-size",
- "".split(':'), /* don't care */
- &mut var_val[..],
- fb_impl,
- )
- .unwrap();
- process_var("max-download-size", &[], val);
- match fb_impl.get_var_all(|name, args, val| process_var(name, args, val)) {
- Ok(()) => {
- transport_error?;
- transport.send_packet(fastboot_okay!(res, ""))
- }
+ let get_res = self.get_var_all_with_native(fb_impl, &mut |name, args, val| {
+ let mut formatted_bytes = FormattedBytes::new(&mut res);
+ write!(formatted_bytes, "INFO{}", name).unwrap();
+ args.iter().for_each(|arg| write!(formatted_bytes, ":{}", arg).unwrap());
+ write!(formatted_bytes, ": {}", val).unwrap();
+ let size = formatted_bytes.size();
+ transport_error = transport.send_packet(&res[..size]);
+ Ok(transport_error?)
+ });
+ transport_error?;
+ match get_res {
+ Ok(()) => transport.send_packet(fastboot_okay!(res, "")),
Err(e) => transport.send_packet(fastboot_fail!(res, "{}", e.to_str())),
}
}
@@ -475,7 +495,7 @@ impl<'a> Fastboot<'a> {
return transport.send_packet(fastboot_fail!(res, "Zero download size"));
}
- transport.send_packet(snprintf!(res, "DATA{:#x}", total_download_size))?;
+ transport.send_packet(snprintf!(res, "DATA{:#x}", total_download_size).as_bytes())?;
self.total_download_size = total_download_size;
self.downloaded_size = 0;
self.state = ProtocolState::Download;
@@ -524,7 +544,7 @@ macro_rules! snprintf {
let mut formatted_bytes = FormattedBytes::new(&mut $arr[..]);
write!(formatted_bytes, $($x,)*).unwrap();
let size = formatted_bytes.size();
- &mut $arr[..size]
+ from_utf8(&$arr[..size]).unwrap()
}
};
}
@@ -547,6 +567,7 @@ mod test {
use super::*;
use std::collections::{BTreeMap, VecDeque};
+ #[derive(Default)]
struct FastbootTest {
// A mapping from (variable name, argument) to variable value.
vars: BTreeMap<(&'static str, &'static [&'static str]), &'static str>,
@@ -569,12 +590,12 @@ mod test {
}
}
- fn get_var_all<F>(&mut self, mut f: F) -> Result<(), CommandError>
- where
- F: FnMut(&str, &[&str], &str),
- {
+ fn get_var_all(
+ &mut self,
+ f: &mut dyn FnMut(&str, &[&str], &str) -> Result<(), CommandError>,
+ ) -> Result<(), CommandError> {
for ((var, config), value) in &self.vars {
- f(var, config, value);
+ f(var, config, value)?;
}
Ok(())
}
@@ -649,7 +670,7 @@ mod test {
#[test]
fn test_non_exist_command() {
- let mut fastboot_impl = FastbootTest { vars: BTreeMap::new() };
+ let mut fastboot_impl: FastbootTest = Default::default();
let mut download_buffer = vec![0u8; 1024];
let mut fastboot = Fastboot::new(&mut download_buffer[..]);
let mut transport = TestTransport::new();
@@ -660,7 +681,7 @@ mod test {
#[test]
fn test_non_ascii_command_string() {
- let mut fastboot_impl = FastbootTest { vars: BTreeMap::new() };
+ let mut fastboot_impl: FastbootTest = Default::default();
let mut download_buffer = vec![0u8; 1024];
let mut fastboot = Fastboot::new(&mut download_buffer[..]);
let mut transport = TestTransport::new();
@@ -671,7 +692,7 @@ mod test {
#[test]
fn test_get_var_max_download_size() {
- let mut fastboot_impl = FastbootTest { vars: BTreeMap::new() };
+ let mut fastboot_impl: FastbootTest = Default::default();
let mut download_buffer = vec![0u8; 1024];
let mut fastboot = Fastboot::new(&mut download_buffer[..]);
let mut transport = TestTransport::new();
@@ -682,13 +703,14 @@ mod test {
#[test]
fn test_get_var() {
+ let mut fastboot_impl: FastbootTest = Default::default();
let vars: [((&str, &[&str]), &str); 4] = [
(("var_0", &[]), "val_0"),
(("var_1", &["a", "b"]), "val_1_a_b"),
(("var_1", &["c", "d"]), "val_1_c_d"),
(("var_2", &["e", "f"]), "val_2_e_f"),
];
- let mut fastboot_impl = FastbootTest { vars: BTreeMap::from(vars) };
+ fastboot_impl.vars = BTreeMap::from(vars);
let mut download_buffer = vec![0u8; 1024];
let mut fastboot = Fastboot::new(&mut download_buffer[..]);
@@ -718,13 +740,14 @@ mod test {
#[test]
fn test_get_var_all() {
+ let mut fastboot_impl: FastbootTest = Default::default();
let vars: [((&str, &[&str]), &str); 4] = [
(("var_0", &[]), "val_0"),
(("var_1", &["a", "b"]), "val_1_a_b"),
(("var_1", &["c", "d"]), "val_1_c_d"),
(("var_2", &["e", "f"]), "val_2_e_f"),
];
- let mut fastboot_impl = FastbootTest { vars: BTreeMap::from(vars) };
+ fastboot_impl.vars = BTreeMap::from(vars);
let mut download_buffer = vec![0u8; 1024];
let mut fastboot = Fastboot::new(&mut download_buffer[..]);
@@ -746,7 +769,7 @@ mod test {
#[test]
fn test_download() {
- let mut fastboot_impl = FastbootTest { vars: BTreeMap::new() };
+ let mut fastboot_impl: FastbootTest = Default::default();
let mut download_buffer = vec![0u8; 1024];
let download_content: Vec<u8> =
(0..download_buffer.len()).into_iter().map(|v| v as u8).collect();
@@ -767,7 +790,7 @@ mod test {
#[test]
fn test_download_not_enough_args() {
- let mut fastboot_impl = FastbootTest { vars: BTreeMap::new() };
+ let mut fastboot_impl: FastbootTest = Default::default();
let mut download_buffer = vec![0u8; 1024];
let mut fastboot = Fastboot::new(&mut download_buffer[..]);
let mut transport = TestTransport::new();
@@ -778,7 +801,7 @@ mod test {
#[test]
fn test_download_invalid_hex_string() {
- let mut fastboot_impl = FastbootTest { vars: BTreeMap::new() };
+ let mut fastboot_impl: FastbootTest = Default::default();
let mut download_buffer = vec![0u8; 1024];
let mut fastboot = Fastboot::new(&mut download_buffer[..]);
let mut transport = TestTransport::new();
@@ -789,7 +812,7 @@ mod test {
}
fn test_download_size(download_buffer_size: usize, download_size: usize, msg: &str) {
- let mut fastboot_impl = FastbootTest { vars: BTreeMap::new() };
+ let mut fastboot_impl: FastbootTest = Default::default();
let mut download_buffer = vec![0u8; download_buffer_size];
let mut transport = TestTransport::new();
transport.add_input(format!("download:{:#x}", download_size).as_bytes());
@@ -810,7 +833,7 @@ mod test {
#[test]
fn test_download_more_than_expected() {
- let mut fastboot_impl = FastbootTest { vars: BTreeMap::new() };
+ let mut fastboot_impl: FastbootTest = Default::default();
let mut download_buffer = vec![0u8; 1024];
let download_content: Vec<u8> = vec![0u8; download_buffer.len()];
let mut fastboot = Fastboot::new(&mut download_buffer[..]);
@@ -832,7 +855,7 @@ mod test {
#[test]
fn test_fastboot_tcp() {
- let mut fastboot_impl = FastbootTest { vars: BTreeMap::new() };
+ let mut fastboot_impl: FastbootTest = Default::default();
let mut download_buffer = vec![0u8; 1024];
let download_content: Vec<u8> =
(0..download_buffer.len()).into_iter().map(|v| v as u8).collect();
@@ -858,7 +881,7 @@ mod test {
#[test]
fn test_fastboot_tcp_invalid_handshake() {
- let mut fastboot_impl = FastbootTest { vars: BTreeMap::new() };
+ let mut fastboot_impl: FastbootTest = Default::default();
let mut download_buffer = vec![0u8; 1024];
let mut fastboot = Fastboot::new(&mut download_buffer[..]);
let mut tcp_stream: TestTcpStream = Default::default();
@@ -871,7 +894,7 @@ mod test {
#[test]
fn test_fastboot_tcp_packet_size_exceeds_maximum() {
- let mut fastboot_impl = FastbootTest { vars: BTreeMap::new() };
+ let mut fastboot_impl: FastbootTest = Default::default();
let mut download_buffer = vec![0u8; 1024];
let mut fastboot = Fastboot::new(&mut download_buffer[..]);
let mut tcp_stream: TestTcpStream = Default::default();
diff --git a/gbl/libgbl/BUILD b/gbl/libgbl/BUILD
index 0ef63fb..20cef67 100644
--- a/gbl/libgbl/BUILD
+++ b/gbl/libgbl/BUILD
@@ -27,6 +27,7 @@ rust_library(
"@bitflags",
"@crc32fast",
"@cstr",
+ "@gbl//libfastboot",
"@gbl//libstorage",
"@gbl//third_party/libzbi",
"@spin",
@@ -36,6 +37,7 @@ rust_library(
rust_test(
name = "libgbl_test",
+ compile_data = ["@gbl//libstorage/test:test_data"],
crate = ":libgbl",
crate_features = ["uuid"],
data = [
diff --git a/gbl/libgbl/src/fastboot/mod.rs b/gbl/libgbl/src/fastboot/mod.rs
new file mode 100644
index 0000000..b779130
--- /dev/null
+++ b/gbl/libgbl/src/fastboot/mod.rs
@@ -0,0 +1,207 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use core::str::Split;
+use fastboot::{CommandError, FastbootImplementation};
+use gbl_storage::AsMultiBlockDevices;
+
+mod vars;
+use vars::{BlockDevice, Partition, Variable};
+
+/// `GblFastboot` implements fastboot commands in the GBL context.
+pub struct GblFastboot<'a> {
+ pub storage: &'a mut dyn AsMultiBlockDevices,
+}
+
+impl<'a> GblFastboot<'a> {
+ /// Native GBL fastboot variables.
+ const NATIVE_VARS: &'static [&'static dyn Variable] = &[
+ &("version-bootloader", "1.0"), // Placeholder for now.
+ &BlockDevice {},
+ &Partition {},
+ ];
+
+ /// Creates a new instance.
+ pub fn new(storage: &'a mut dyn AsMultiBlockDevices) -> Self {
+ Self { storage: storage }
+ }
+
+ /// Returns the storage object.
+ ///
+ /// `AsMultiBlockDevices` has methods with `Self: Sized` constraint. Thus we return a
+ /// `&mut &mut dyn AsMultiBlockDevices` which also implements `AsMultiBlockDevices` but meets
+ /// the `Sized` bound.
+ pub fn storage(&mut self) -> &mut &'a mut dyn AsMultiBlockDevices {
+ &mut self.storage
+ }
+}
+
+impl FastbootImplementation for GblFastboot<'_> {
+ fn get_var(
+ &mut self,
+ var: &str,
+ args: Split<char>,
+ out: &mut [u8],
+ ) -> Result<usize, CommandError> {
+ Self::NATIVE_VARS
+ .iter()
+ .find_map(|v| v.get(self, var, args.clone(), out).transpose())
+ .ok_or::<CommandError>("No such variable".into())?
+ }
+
+ fn get_var_all(
+ &mut self,
+ f: &mut dyn FnMut(&str, &[&str], &str) -> Result<(), CommandError>,
+ ) -> Result<(), CommandError> {
+ Self::NATIVE_VARS.iter().find_map(|v| v.get_all(self, f).err()).map_or(Ok(()), |e| Err(e))
+ }
+}
+
+/// 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)?)
+}
+
+/// 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())
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use gbl_storage::{required_scratch_size, AsBlockDevice, BlockIo};
+ use Vec;
+
+ const BLOCK_SIZE: u64 = 512;
+ const MAX_GPT_ENTRIES: u64 = 128;
+
+ // TODO(b/329138620): Migrate to common storage test library once available.
+ struct TestBlockIo(Vec<u8>);
+
+ impl BlockIo for TestBlockIo {
+ fn block_size(&mut self) -> u64 {
+ BLOCK_SIZE
+ }
+
+ fn num_blocks(&mut self) -> u64 {
+ u64::try_from(self.0.len()).unwrap() / BLOCK_SIZE
+ }
+
+ fn alignment(&mut self) -> u64 {
+ BLOCK_SIZE
+ }
+
+ fn read_blocks(&mut self, blk_offset: u64, out: &mut [u8]) -> bool {
+ out.clone_from_slice(
+ &self.0[usize::try_from(blk_offset * BLOCK_SIZE).unwrap()..][..out.len()],
+ );
+ true
+ }
+
+ fn write_blocks(&mut self, blk_offset: u64, data: &[u8]) -> bool {
+ self.0[usize::try_from(blk_offset * BLOCK_SIZE).unwrap()..][..data.len()]
+ .clone_from_slice(data);
+ true
+ }
+ }
+
+ struct TestBlockDevice((TestBlockIo, Vec<u8>));
+
+ impl AsBlockDevice for TestBlockDevice {
+ fn with(&mut self, f: &mut dyn FnMut(&mut dyn BlockIo, &mut [u8], u64)) {
+ f(&mut self.0 .0, &mut self.0 .1[..], MAX_GPT_ENTRIES)
+ }
+ }
+
+ fn test_block_device(storage: &[u8]) -> TestBlockDevice {
+ let mut io = TestBlockIo(Vec::from(storage));
+ let scratch_size = required_scratch_size(&mut io, MAX_GPT_ENTRIES).unwrap();
+ TestBlockDevice((io, vec![0u8; scratch_size]))
+ }
+
+ struct TestMultiBlockDevices(Vec<TestBlockDevice>);
+
+ impl AsMultiBlockDevices for TestMultiBlockDevices {
+ fn for_each_until(&mut self, f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64) -> bool) {
+ let _ = self
+ .0
+ .iter_mut()
+ .enumerate()
+ .find_map(|(idx, ele)| f(ele, u64::try_from(idx).unwrap()).then_some(()));
+ }
+ }
+
+ /// Helper to test fastboot variable value.
+ fn check_var(gbl_fb: &mut GblFastboot, var: &str, args: &str, expected: &str) {
+ let mut out = vec![0u8; fastboot::MAX_RESPONSE_SIZE];
+ assert_eq!(gbl_fb.get_var_as_str(var, args.split(':'), &mut out[..]).unwrap(), expected);
+ }
+
+ #[test]
+ fn test_get_var_partition_info() {
+ 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);
+ check_var(&mut gbl_fb, "partition-size", "boot_a:0", "0x2000");
+ 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 out = vec![0u8; fastboot::MAX_RESPONSE_SIZE];
+ assert!(gbl_fb
+ .get_var_as_str("partition", "non-existent".split(':'), &mut out[..])
+ .is_err());
+ }
+
+ #[test]
+ fn test_get_var_all() {
+ 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 mut out: Vec<String> = Default::default();
+ gbl_fb
+ .get_var_all(&mut |name, args, val| {
+ out.push(format!("{}:{}: {}", name, args.join(":"), val));
+ Ok(())
+ })
+ .unwrap();
+ assert_eq!(
+ out,
+ [
+ "version-bootloader:: 1.0",
+ "block-device:0:total-blocks: 0x80",
+ "block-device:0:block-size: 0x200",
+ "block-device:1:total-blocks: 0x100",
+ "block-device:1:block-size: 0x200",
+ "partition-size:boot_a:0: 0x2000",
+ "partition-type:boot_a:0: raw",
+ "partition-size:boot_b:0: 0x3000",
+ "partition-type:boot_b:0: raw",
+ "partition-size:vendor_boot_a:1: 0x1000",
+ "partition-type:vendor_boot_a:1: raw",
+ "partition-size:vendor_boot_b:1: 0x1800",
+ "partition-type:vendor_boot_b:1: raw"
+ ]
+ );
+ }
+}
diff --git a/gbl/libgbl/src/fastboot/vars.rs b/gbl/libgbl/src/fastboot/vars.rs
new file mode 100644
index 0000000..ac0eb56
--- /dev/null
+++ b/gbl/libgbl/src/fastboot/vars.rs
@@ -0,0 +1,181 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::fastboot::{hex_str_to_u64, next_arg, GblFastboot};
+use core::fmt::Write;
+use core::str::{from_utf8, Split};
+use fastboot::{snprintf, CommandError, FormattedBytes};
+use gbl_storage::{AsBlockDevice, AsMultiBlockDevices, GPT_NAME_LEN_U16};
+
+/// Internal trait that provides methods for getting and enumerating values for one or multiple
+/// related fastboot variables.
+pub(crate) trait Variable {
+ /// Get the variable value given variable name and arguments.
+ ///
+ /// Return Ok(Some(`size`)) where `size` is the number of bytes written to `out`. Return
+ /// `Ok(None)` if the variable is not supported.
+ fn get(
+ &self,
+ gbl_fb: &mut GblFastboot,
+ name: &str,
+ args: Split<char>,
+ out: &mut [u8],
+ ) -> Result<Option<usize>, CommandError>;
+
+ /// Iterates and calls `f` on all values/arguments combinations.
+ fn get_all(
+ &self,
+ gbl_fb: &mut GblFastboot,
+ f: &mut dyn FnMut(&str, &[&str], &str) -> Result<(), CommandError>,
+ ) -> Result<(), CommandError>;
+}
+
+// Constant fastboot variable
+impl Variable for (&'static str, &'static str) {
+ fn get(
+ &self,
+ _: &mut GblFastboot,
+ name: &str,
+ _: Split<char>,
+ out: &mut [u8],
+ ) -> Result<Option<usize>, CommandError> {
+ Ok((name == self.0).then_some(snprintf!(out, "{}", self.1).len()))
+ }
+
+ fn get_all(
+ &self,
+ _: &mut GblFastboot,
+ f: &mut dyn FnMut(&str, &[&str], &str) -> Result<(), CommandError>,
+ ) -> Result<(), CommandError> {
+ f(self.0, &[], self.1)
+ }
+}
+
+/// `Partition` variable provides information of GPT partitions
+///
+/// `fastboot getvar partition-size:<partition name>[:<block-device>]`
+/// `fastboot getvar partition-type:<partition name>[:<block-device>]`
+pub(crate) struct Partition {}
+
+const PARTITION_SIZE: &str = "partition-size";
+const PARTITION_TYPE: &str = "partition-type";
+impl Variable for Partition {
+ fn get(
+ &self,
+ gbl_fb: &mut GblFastboot,
+ name: &str,
+ mut 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()?;
+ 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_TYPE => Some(snprintf!(out, "raw").len()), // Image type not supported yet.
+ _ => None,
+ })
+ }
+
+ fn get_all(
+ &self,
+ gbl_fb: &mut GblFastboot,
+ f: &mut dyn FnMut(&str, &[&str], &str) -> Result<(), CommandError>,
+ ) -> Result<(), CommandError> {
+ let mut res: Result<(), CommandError> = Ok(());
+ let part_name = &mut [0u8; GPT_NAME_LEN_U16 * 2][..];
+ 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.
+ 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)?;
+ f(PARTITION_SIZE, &[part, id], snprintf!(size_str, "{:#x}", sz))?;
+ // Image type not supported yet.
+ f(PARTITION_TYPE, &[part, id], snprintf!(size_str, "raw"))?;
+ }
+ Ok(())
+ })();
+ res.is_err()
+ });
+ res
+ }
+}
+
+/// `BlockDevice` variable provides information of block devices.
+///
+/// `fastboot getvar block-device:<id>:total-blocks`
+/// `fastboot getvar block-device:<id>:block-size`
+pub(crate) struct BlockDevice {}
+
+const BLOCK_DEVICE: &str = "block-device";
+const TOTAL_BLOCKS: &str = "total-blocks";
+const BLOCK_SIZE: &str = "block-size";
+impl Variable for BlockDevice {
+ fn get(
+ &self,
+ gbl_fb: &mut GblFastboot,
+ name: &str,
+ mut args: Split<char>,
+ out: &mut [u8],
+ ) -> 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 val = match val_type {
+ TOTAL_BLOCKS => gbl_fb.storage().get(id)?.num_blocks()?,
+ BLOCK_SIZE => gbl_fb.storage().get(id)?.block_size()?,
+ _ => return Err("Invalid type".into()),
+ };
+ Some(snprintf!(out, "{:#x}", val).len())
+ }
+ _ => None,
+ })
+ }
+
+ fn get_all(
+ &self,
+ gbl_fb: &mut GblFastboot,
+ f: &mut dyn FnMut(&str, &[&str], &str) -> Result<(), CommandError>,
+ ) -> Result<(), CommandError> {
+ let mut val = [0u8; 32];
+ let mut res: Result<(), CommandError> = Ok(());
+ gbl_fb.storage().for_each_until(&mut |blk, id| {
+ let mut id_str = [0u8; 32];
+ let id = snprintf!(id_str, "{:x}", id);
+ res = (|| {
+ f(BLOCK_DEVICE, &[id, "total-blocks"], snprintf!(val, "{:#x}", blk.num_blocks()?))?;
+ f(BLOCK_DEVICE, &[id, "block-size"], snprintf!(val, "{:#x}", blk.block_size()?))
+ })();
+ res.is_err()
+ });
+ res
+ }
+}
diff --git a/gbl/libgbl/src/lib.rs b/gbl/libgbl/src/lib.rs
index 93c7a97..6a5491c 100644
--- a/gbl/libgbl/src/lib.rs
+++ b/gbl/libgbl/src/lib.rs
@@ -46,6 +46,7 @@ pub mod boot_mode;
pub mod boot_reason;
pub mod digest;
pub mod error;
+pub mod fastboot;
pub mod ops;
/// The 'slots' module, containing types and traits for
diff --git a/gbl/libstorage/BUILD b/gbl/libstorage/BUILD
index 3ea904d..63cd8d1 100644
--- a/gbl/libstorage/BUILD
+++ b/gbl/libstorage/BUILD
@@ -35,14 +35,7 @@ rust_library(
rust_test(
name = "libstorage_test",
- compile_data = [
- "@gbl//libstorage/test:gpt_test_1.bin",
- "@gbl//libstorage/test:gpt_test_2.bin",
- "@gbl//libstorage/test:boot_a.bin",
- "@gbl//libstorage/test:boot_b.bin",
- "@gbl//libstorage/test:vendor_boot_a.bin",
- "@gbl//libstorage/test:vendor_boot_b.bin",
- ],
+ compile_data = ["@gbl//libstorage/test:test_data"],
crate = ":libstorage",
)
diff --git a/gbl/libstorage/src/gpt.rs b/gbl/libstorage/src/gpt.rs
index d1c0f44..44b5876 100644
--- a/gbl/libstorage/src/gpt.rs
+++ b/gbl/libstorage/src/gpt.rs
@@ -23,7 +23,7 @@ use crc32fast::Hasher;
use zerocopy::{AsBytes, FromBytes, FromZeroes, Ref};
const GPT_GUID_LEN: usize = 16;
-pub(crate) const GPT_NAME_LEN: usize = 36;
+pub const GPT_NAME_LEN_U16: usize = 36;
#[repr(C, packed)]
#[derive(Debug, Default, Copy, Clone, AsBytes, FromBytes, FromZeroes)]
@@ -66,7 +66,7 @@ pub struct GptEntry {
pub first: u64,
pub last: u64,
pub flags: u64,
- pub name: [u16; GPT_NAME_LEN],
+ pub name: [u16; GPT_NAME_LEN_U16],
}
impl GptEntry {
@@ -82,7 +82,7 @@ impl GptEntry {
}
/// Decode the partition name into a string. A length N utf16 string can be at most 2N utf8
- /// bytes. Therefore, a safe size of `buffer` is 2*GPT_NAME_LEN = 72.
+ /// bytes. Therefore, a safe size of `buffer` is 2*GPT_NAME_LEN_U16 = 72.
pub fn name_to_str<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a str> {
let mut index = 0;
for c in char::decode_utf16(self.name) {
@@ -104,7 +104,7 @@ impl GptEntry {
impl core::fmt::Display for GptEntry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
// Format: partition name: "abc", [first, last]: [123, 456]
- let mut name_conversion_buffer = [0u8; GPT_NAME_LEN * 2];
+ let mut name_conversion_buffer = [0u8; GPT_NAME_LEN_U16 * 2];
let name = self.name_to_str(&mut name_conversion_buffer).map_err(|_| core::fmt::Error)?;
write!(f, "partition name: \"{}\", [first, last]: [{}, {}]", name, self.first, self.last)
}
@@ -230,7 +230,7 @@ impl<'a> Gpt<'a> {
/// If the object does not contain a valid GPT, the method returns Error.
pub(crate) fn find_partition(&self, part: &str) -> Result<&GptEntry> {
for entry in self.entries()? {
- let mut name_conversion_buffer = [0u8; GPT_NAME_LEN * 2];
+ let mut name_conversion_buffer = [0u8; GPT_NAME_LEN_U16 * 2];
if entry.name_to_str(&mut name_conversion_buffer)? != part {
continue;
}
diff --git a/gbl/libstorage/src/lib.rs b/gbl/libstorage/src/lib.rs
index 408b86f..1097555 100644
--- a/gbl/libstorage/src/lib.rs
+++ b/gbl/libstorage/src/lib.rs
@@ -111,7 +111,7 @@ use core::cmp::min;
// Selective export of submodule types.
mod gpt;
use gpt::Gpt;
-pub use gpt::GptEntry;
+pub use gpt::{GptEntry, GPT_NAME_LEN_U16};
mod multi_blocks;
pub use multi_blocks::{with_id, AsMultiBlockDevices};
diff --git a/gbl/libstorage/src/multi_blocks.rs b/gbl/libstorage/src/multi_blocks.rs
index 7df42a4..9f6686c 100644
--- a/gbl/libstorage/src/multi_blocks.rs
+++ b/gbl/libstorage/src/multi_blocks.rs
@@ -107,6 +107,12 @@ pub trait AsMultiBlockDevices {
}
}
+impl<T: ?Sized + AsMultiBlockDevices> AsMultiBlockDevices for &mut T {
+ fn for_each_until(&mut self, f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64) -> bool) {
+ (*self).for_each_until(&mut |io, id| f(io, id))
+ }
+}
+
/// Iterates and runs a closure on each block device until `Ok(R)` is returned.
fn until_ok<F, R>(devs: &mut (impl AsMultiBlockDevices + ?Sized), mut f: F) -> Result<R>
where
diff --git a/gbl/libstorage/test/BUILD b/gbl/libstorage/test/BUILD
index 6325cad..9a74579 100644
--- a/gbl/libstorage/test/BUILD
+++ b/gbl/libstorage/test/BUILD
@@ -12,4 +12,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+package(
+ default_visibility = ["//visibility:public"],
+)
+
exports_files(glob(["**/*"]))
+
+filegroup(
+ name = "test_data",
+ srcs = [
+ ":boot_a.bin",
+ ":boot_b.bin",
+ ":gpt_test_1.bin",
+ ":gpt_test_2.bin",
+ ":vendor_boot_a.bin",
+ ":vendor_boot_b.bin",
+ ],
+)