diff options
author | Yecheng Zhao <zyecheng@google.com> | 2024-03-19 21:14:27 +0000 |
---|---|---|
committer | Yecheng Zhao <zyecheng@google.com> | 2024-03-21 17:51:42 +0000 |
commit | 7bda36f0c88cb817a70106331e8070f3f34b4897 (patch) | |
tree | 0c4e9b98cea5b76c38d76e931a3299fa880c46c8 | |
parent | cf51f287c340c75a15b070440718032643006a5f (diff) | |
download | libbootloader-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.rs | 30 | ||||
-rw-r--r-- | gbl/efi/src/main.rs | 2 | ||||
-rw-r--r-- | gbl/libfastboot/src/lib.rs | 197 | ||||
-rw-r--r-- | gbl/libgbl/BUILD | 2 | ||||
-rw-r--r-- | gbl/libgbl/src/fastboot/mod.rs | 207 | ||||
-rw-r--r-- | gbl/libgbl/src/fastboot/vars.rs | 181 | ||||
-rw-r--r-- | gbl/libgbl/src/lib.rs | 1 | ||||
-rw-r--r-- | gbl/libstorage/BUILD | 9 | ||||
-rw-r--r-- | gbl/libstorage/src/gpt.rs | 10 | ||||
-rw-r--r-- | gbl/libstorage/src/lib.rs | 2 | ||||
-rw-r--r-- | gbl/libstorage/src/multi_blocks.rs | 6 | ||||
-rw-r--r-- | gbl/libstorage/test/BUILD | 16 |
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", + ], +) |