gbl: efi: add "fastboot boot" command

Add fastboot boot implementation.

Change-Id: I2625ee349f3d4addced105985e7c38f7335b3c10
Signed-off-by: Ying-Chun Liu (PaulLiu) <liupa@google.com>
diff --git a/gbl/efi/src/android_boot.rs b/gbl/efi/src/android_boot.rs
index 8c48ac6..5a02b95 100644
--- a/gbl/efi/src/android_boot.rs
+++ b/gbl/efi/src/android_boot.rs
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::{efi_blocks::find_block_devices, fastboot::fastboot, ops::Ops};
+use crate::{efi_blocks::find_block_devices, fastboot::fastboot, ops::Ops, ops::RambootOps};
 use efi::{exit_boot_services, EfiEntry};
 use libgbl::{android_boot::load_android_simple, gbl_print, gbl_println, GblOps, Os, Result};
 
@@ -32,16 +32,15 @@
 pub fn android_boot_demo(entry: EfiEntry) -> Result<()> {
     let blks = find_block_devices(&entry)?;
     let mut ops = Ops::new(&entry, &blks[..], Some(Os::Android));
+    let mut bootimg_buffer = &mut vec![0u8; 128 * 1024 * 1024][..]; // 128 MB
 
     match ops.should_stop_in_fastboot() {
-        Ok(true) => {
-            fastboot(&mut ops)?;
-        }
-        Ok(false) => {}
+        Ok(true) => fastboot(&mut ops, &mut bootimg_buffer)?,
         Err(e) => {
             gbl_println!(ops, "Warning: error while checking fastboot trigger ({:?})", e);
             gbl_println!(ops, "Ignoring error and continuing with normal boot");
         }
+        _ => {}
     }
 
     gbl_println!(ops, "Try booting as Android");
@@ -49,7 +48,12 @@
     // Allocate buffer for load.
     let mut load_buffer = vec![0u8; 128 * 1024 * 1024]; // 128MB
 
-    let (ramdisk, fdt, kernel, remains) = load_android_simple(&mut ops, &mut load_buffer[..])?;
+    let (ramdisk, fdt, kernel, remains) = if bootimg_buffer.starts_with(b"ANDROID!") {
+        let mut ramboot_ops = RambootOps { ops: &mut ops, bootimg_buffer };
+        load_android_simple(&mut ramboot_ops, &mut load_buffer[..])?
+    } else {
+        load_android_simple(&mut ops, &mut load_buffer[..])?
+    };
 
     gbl_println!(ops, "");
     gbl_println!(
diff --git a/gbl/efi/src/fastboot.rs b/gbl/efi/src/fastboot.rs
index b9a70b0..485e3ab 100644
--- a/gbl/efi/src/fastboot.rs
+++ b/gbl/efi/src/fastboot.rs
@@ -199,7 +199,7 @@
     }
 }
 
-pub fn fastboot(efi_gbl_ops: &mut Ops) -> Result<()> {
+pub fn fastboot(efi_gbl_ops: &mut Ops, bootimg_buf: &mut [u8]) -> Result<()> {
     let efi_entry = efi_gbl_ops.efi_entry;
     efi_println!(efi_entry, "Entering fastboot mode...");
 
@@ -224,6 +224,16 @@
     let tcp = tcp.as_mut().map(|v| EfiFastbootTcpTransport::new(v));
 
     let download_buffers = vec![vec![0u8; 512 * 1024 * 1024]; 2].into();
-    block_on(run_gbl_fastboot(efi_gbl_ops, &download_buffers, VecPinFut::default(), usb, tcp));
+    block_on(run_gbl_fastboot(
+        efi_gbl_ops,
+        &download_buffers,
+        VecPinFut::default(),
+        usb,
+        tcp,
+        bootimg_buf,
+    ));
+
+    efi_println!(efi_entry, "Leaving fastboot mode...");
+
     Ok(())
 }
diff --git a/gbl/efi/src/fuchsia_boot.rs b/gbl/efi/src/fuchsia_boot.rs
index 3b10f89..44254e1 100644
--- a/gbl/efi/src/fuchsia_boot.rs
+++ b/gbl/efi/src/fuchsia_boot.rs
@@ -67,7 +67,7 @@
         let mut ops = Ops::new(&efi_entry, &blks[..], Some(Os::Fuchsia));
         // Checks whether to enter fastboot mode.
         if zircon_check_enter_fastboot(&mut ops) {
-            fastboot(&mut ops)?;
+            fastboot(&mut ops, &mut [])?;
         }
         zircon_load_verify_abr(&mut ops)?
     };
diff --git a/gbl/efi/src/ops.rs b/gbl/efi/src/ops.rs
index 6c0d957..112f3ab 100644
--- a/gbl/efi/src/ops.rs
+++ b/gbl/efi/src/ops.rs
@@ -25,7 +25,7 @@
 };
 use arrayvec::ArrayVec;
 use core::{
-    ffi::CStr, fmt::Write, mem::MaybeUninit, num::NonZeroUsize, ops::DerefMut, ptr::null,
+    cmp::min, ffi::CStr, fmt::Write, mem::MaybeUninit, num::NonZeroUsize, ops::DerefMut, ptr::null,
     slice::from_raw_parts_mut, str::Split,
 };
 use efi::{
@@ -42,7 +42,7 @@
     GblEfiImageInfo, GblEfiVerifiedDeviceTree, PARTITION_NAME_LEN_U16,
 };
 use fdt::Fdt;
-use gbl_storage::{BlockIo, Disk, Gpt};
+use gbl_storage::{BlockIo, Disk, Gpt, SliceMaybeUninit};
 use liberror::{Error, Result};
 use libgbl::{
     constants::{ImageName, BOOTCMD_SIZE},
@@ -563,6 +563,180 @@
     }
 }
 
+/// Inherits everything from `ops` but override a few such as read boot_a from
+/// bootimg_buffer,
+/// avb_write_rollback_index(), slot operation etc
+pub struct RambootOps<'b, T> {
+    pub ops: &'b mut T,
+    pub bootimg_buffer: &'b mut [u8],
+}
+
+impl<'a, 'd, T: GblOps<'a, 'd>> GblOps<'a, 'd> for RambootOps<'_, T> {
+    fn console_out(&mut self) -> Option<&mut dyn Write> {
+        self.ops.console_out()
+    }
+
+    fn should_stop_in_fastboot(&mut self) -> Result<bool> {
+        self.ops.should_stop_in_fastboot()
+    }
+
+    fn reboot(&mut self) {
+        self.ops.reboot()
+    }
+
+    fn disks(
+        &self,
+    ) -> &'a [GblDisk<
+        Disk<impl BlockIo + 'a, impl DerefMut<Target = [u8]> + 'a>,
+        Gpt<impl DerefMut<Target = [u8]> + 'a>,
+    >] {
+        self.ops.disks()
+    }
+
+    fn expected_os(&mut self) -> Result<Option<Os>> {
+        self.ops.expected_os()
+    }
+
+    fn zircon_add_device_zbi_items(
+        &mut self,
+        container: &mut ZbiContainer<&mut [u8]>,
+    ) -> Result<()> {
+        self.ops.zircon_add_device_zbi_items(container)
+    }
+
+    fn get_zbi_bootloader_files_buffer(&mut self) -> Option<&mut [u8]> {
+        self.ops.get_zbi_bootloader_files_buffer()
+    }
+
+    fn load_slot_interface<'c>(
+        &'c mut self,
+        _fnmut: &'c mut dyn FnMut(&mut [u8]) -> Result<()>,
+        _boot_token: BootToken,
+    ) -> GblResult<Cursor<'c>> {
+        self.ops.load_slot_interface(_fnmut, _boot_token)
+    }
+
+    fn avb_read_is_device_unlocked(&mut self) -> AvbIoResult<bool> {
+        self.ops.avb_read_is_device_unlocked()
+    }
+
+    fn avb_read_rollback_index(&mut self, _rollback_index_location: usize) -> AvbIoResult<u64> {
+        self.ops.avb_read_rollback_index(_rollback_index_location)
+    }
+
+    fn avb_write_rollback_index(&mut self, _: usize, _: u64) -> AvbIoResult<()> {
+        // We don't want to persist AVB related data such as updating antirollback indices.
+        Ok(())
+    }
+
+    fn avb_cert_read_permanent_attributes(
+        &mut self,
+        attributes: &mut CertPermanentAttributes,
+    ) -> AvbIoResult<()> {
+        self.ops.avb_cert_read_permanent_attributes(attributes)
+    }
+
+    fn avb_cert_read_permanent_attributes_hash(&mut self) -> AvbIoResult<[u8; SHA256_DIGEST_SIZE]> {
+        self.ops.avb_cert_read_permanent_attributes_hash()
+    }
+
+    fn get_image_buffer(
+        &mut self,
+        image_name: &str,
+        size: NonZeroUsize,
+    ) -> GblResult<ImageBuffer<'d>> {
+        self.ops.get_image_buffer(image_name, size)
+    }
+
+    fn get_custom_device_tree(&mut self) -> Option<&'a [u8]> {
+        self.ops.get_custom_device_tree()
+    }
+
+    fn fixup_os_commandline<'c>(
+        &mut self,
+        commandline: &CStr,
+        fixup_buffer: &'c mut [u8],
+    ) -> Result<Option<&'c str>> {
+        self.ops.fixup_os_commandline(commandline, fixup_buffer)
+    }
+
+    fn fixup_bootconfig<'c>(
+        &mut self,
+        bootconfig: &[u8],
+        fixup_buffer: &'c mut [u8],
+    ) -> Result<Option<&'c [u8]>> {
+        self.ops.fixup_bootconfig(bootconfig, fixup_buffer)
+    }
+
+    fn fixup_device_tree(&mut self, device_tree: &mut [u8]) -> Result<()> {
+        self.ops.fixup_device_tree(device_tree)
+    }
+
+    fn select_device_trees(
+        &mut self,
+        components_registry: &mut DeviceTreeComponentsRegistry,
+    ) -> Result<()> {
+        self.ops.select_device_trees(components_registry)
+    }
+
+    fn read_from_partition_sync(
+        &mut self,
+        part: &str,
+        off: u64,
+        out: &mut (impl SliceMaybeUninit + ?Sized),
+    ) -> Result<()> {
+        if part == "boot_a" {
+            let len = min(self.bootimg_buffer.len() - off as usize, out.len());
+            out.clone_from_slice(&self.bootimg_buffer[off as usize..off as usize + len]);
+            Ok(())
+        } else {
+            self.ops.read_from_partition_sync(part, off, out)
+        }
+    }
+
+    fn avb_handle_verification_result(
+        &mut self,
+        color: BootStateColor,
+        boot_os_version: Option<&[u8]>,
+        boot_security_patch: Option<&[u8]>,
+        system_os_version: Option<&[u8]>,
+        system_security_patch: Option<&[u8]>,
+        vendor_os_version: Option<&[u8]>,
+        vendor_security_patch: Option<&[u8]>,
+    ) -> AvbIoResult<()> {
+        self.ops.avb_handle_verification_result(
+            color,
+            boot_os_version,
+            boot_security_patch,
+            system_os_version,
+            system_security_patch,
+            vendor_os_version,
+            vendor_security_patch,
+        )
+    }
+
+    fn fastboot_variable(
+        &mut self,
+        name: &str,
+        args: Split<'_, char>,
+        out: &mut [u8],
+    ) -> Result<usize> {
+        self.ops.fastboot_variable(name, args, out)
+    }
+
+    async fn fastboot_send_all_variables(&mut self, sender: &mut impl VarInfoSender) -> Result<()> {
+        self.ops.fastboot_send_all_variables(sender).await
+    }
+
+    fn avb_validate_vbmeta_public_key(
+        &self,
+        public_key: &[u8],
+        public_key_metadata: Option<&[u8]>,
+    ) -> AvbIoResult<KeyValidationStatus> {
+        self.ops.avb_validate_vbmeta_public_key(public_key, public_key_metadata)
+    }
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/gbl/libfastboot/src/lib.rs b/gbl/libfastboot/src/lib.rs
index ebe575f..70b54fa 100644
--- a/gbl/libfastboot/src/lib.rs
+++ b/gbl/libfastboot/src/lib.rs
@@ -386,6 +386,20 @@
     /// Backend for `fastboot set_active`.
     async fn set_active(&mut self, slot: &str, responder: impl InfoSender) -> CommandResult<()>;
 
+    /// Backend for `fastboot boot`
+    ///
+    /// # Args
+    ///
+    /// * `responder`: An instance of `InfoSender + OkaySender`. Implementation should call
+    ///   `responder.send_okay("")` right before boot to notify the remote host that the
+    ///   operation is successful.
+    ///
+    /// # Returns
+    ///
+    /// * The method is always return OK to let fastboot continue.
+    /// * Returns `Err(e)` on error.
+    async fn boot(&mut self, responder: impl InfoSender + OkaySender) -> CommandResult<()>;
+
     /// Backend for `fastboot oem ...`.
     ///
     /// # Args
@@ -854,6 +868,19 @@
     reply_fail!(resp, "{}", e.to_str())
 }
 
+// Handles `fastboot boot`
+async fn boot(
+    transport: &mut impl Transport,
+    fb_impl: &mut impl FastbootImplementation,
+) -> Result<()> {
+    let mut resp = Responder::new(transport);
+    let boot_res = async { fb_impl.boot(&mut resp).await }.await;
+    match boot_res {
+        Err(e) => reply_fail!(resp, "{}", e.to_str()),
+        _ => reply_okay!(resp, "boot_command"),
+    }
+}
+
 // Handles `fastboot continue`
 async fn r#continue(
     transport: &mut impl Transport,
@@ -926,6 +953,10 @@
         return transport.send_packet(fastboot_fail!(packet, "No command")).await.map(|_| false);
     };
     match cmd {
+        "boot" => {
+            boot(transport, fb_impl).await?;
+            return Ok(true);
+        }
         "continue" => {
             r#continue(transport, fb_impl).await?;
             return Ok(true);
@@ -1154,6 +1185,10 @@
                 .await?)
         }
 
+        async fn boot(&mut self, mut responder: impl InfoSender + OkaySender) -> CommandResult<()> {
+            Ok(responder.send_info("Boot to boot.img...").await?)
+        }
+
         async fn reboot(
             &mut self,
             mode: RebootMode,
@@ -1256,6 +1291,19 @@
     }
 
     #[test]
+    fn test_boot() {
+        let mut fastboot_impl: FastbootTest = Default::default();
+        fastboot_impl.download_buffer = vec![0u8; 1024];
+        let mut transport = TestTransport::new();
+        transport.add_input(b"boot");
+        let _ = block_on(run(&mut transport, &mut fastboot_impl));
+        assert_eq!(
+            transport.out_queue,
+            VecDeque::<Vec<u8>>::from([b"INFOBoot to boot.img...".into()])
+        );
+    }
+
+    #[test]
     fn test_non_exist_command() {
         let mut fastboot_impl: FastbootTest = Default::default();
         fastboot_impl.download_buffer = vec![0u8; 1024];
diff --git a/gbl/libgbl/src/fastboot/mod.rs b/gbl/libgbl/src/fastboot/mod.rs
index 85bd189..81b15df 100644
--- a/gbl/libgbl/src/fastboot/mod.rs
+++ b/gbl/libgbl/src/fastboot/mod.rs
@@ -135,6 +135,7 @@
     current_download_size: usize,
     enable_async_task: bool,
     default_block: Option<usize>,
+    bootimg_buf: &'b mut [u8],
     // Introduces marker type so that we can enforce constraint 'd <= min('b, 'c).
     // The constraint is expressed in the implementation block for the `FastbootImplementation`
     // trait.
@@ -179,6 +180,7 @@
         task_mapper: fn(Task<'a, 'b, B, P>) -> F,
         tasks: &'d Shared<C>,
         buffer_pool: &'b Shared<P>,
+        bootimg_buf: &'b mut [u8],
     ) -> Self {
         Self {
             gbl_ops,
@@ -190,6 +192,7 @@
             current_download_size: 0,
             enable_async_task: false,
             default_block: None,
+            bootimg_buf,
             _tasks_context_lifetime: PhantomData,
             _get_image_buffer_lifetime: PhantomData,
         }
@@ -685,6 +688,16 @@
             _ => Err("Unknown oem command".into()),
         }
     }
+
+    async fn boot(&mut self, mut resp: impl InfoSender + OkaySender) -> CommandResult<()> {
+        let len = core::cmp::min(self.bootimg_buf.len(), self.current_download_size);
+        let data = self.current_download_buffer.as_mut().ok_or("No file staged")?;
+        let data = &mut data[..self.current_download_size];
+
+        self.bootimg_buf[..len].copy_from_slice(&data[..len]);
+        resp.send_info("Boot into boot.img").await?;
+        Ok(())
+    }
 }
 
 /// `GblUsbTransport` defines transport interfaces for running GBL fastboot over USB.
@@ -723,10 +736,13 @@
     tasks: impl PinFutContainer<'c> + 'c,
     usb: Option<impl GblUsbTransport>,
     tcp: Option<impl GblTcpStream>,
+    bootimg_buf: &'b mut [u8],
 ) {
     let tasks = tasks.into();
     let disks = gbl_ops.disks();
-    GblFastboot::new(gbl_ops, disks, Task::run, &tasks, buffer_pool).run(usb, tcp).await;
+    GblFastboot::new(gbl_ops, disks, Task::run, &tasks, buffer_pool, bootimg_buf)
+        .run(usb, tcp)
+        .await;
 }
 
 /// Runs GBL fastboot on the given USB/TCP channels with N stack allocated worker tasks.
@@ -750,6 +766,7 @@
     buffer_pool: impl BufferPool,
     usb: Option<impl GblUsbTransport>,
     tcp: Option<impl GblTcpStream>,
+    bootimg_buf: &mut [u8],
 ) {
     let buffer_pool = buffer_pool.into();
     // Creates N worker tasks.
@@ -771,7 +788,9 @@
     let mut tasks: [_; N] = tasks.each_mut().map(|v| unsafe { Pin::new_unchecked(v) });
     let tasks = PinFutSlice::new(&mut tasks[..]).into();
     let disks = gbl_ops.disks();
-    GblFastboot::new(gbl_ops, disks, Task::run, &tasks, &buffer_pool).run(usb, tcp).await;
+    GblFastboot::new(gbl_ops, disks, Task::run, &tasks, &buffer_pool, bootimg_buf)
+        .run(usb, tcp)
+        .await;
 }
 
 /// Pre-generates a Fuchsia Fastboot MDNS service broadcast packet.
@@ -910,7 +929,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
         check_var(
             &mut gbl_fb,
             FakeGblOps::GBL_TEST_VAR,
@@ -930,7 +950,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
 
         // Check different semantics
         check_var(&mut gbl_fb, "partition-size", "boot_a", "0x2000");
@@ -988,7 +1009,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
 
         let mut logger = TestVarSender(vec![]);
         block_on(gbl_fb.get_var_all(&mut logger)).unwrap();
@@ -1055,7 +1077,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
 
         // Missing mandatory block device ID for raw block partition.
         assert!(fetch(&mut gbl_fb, "//0/0".into(), 0, 0).is_err());
@@ -1104,7 +1127,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
 
         let off = 512;
         let size = 512;
@@ -1145,7 +1169,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
 
         let expect_boot_a = include_bytes!("../../../libstorage/test/boot_a.bin");
         let expect_boot_b = include_bytes!("../../../libstorage/test/boot_b.bin");
@@ -1207,7 +1232,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
 
         let expect_boot_a = include_bytes!("../../../libstorage/test/boot_a.bin");
         let expect_boot_b = include_bytes!("../../../libstorage/test/boot_b.bin");
@@ -1237,7 +1263,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
 
         let download = sparse.to_vec();
         let resp: TestResponder = Default::default();
@@ -1274,7 +1301,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
         let tasks = gbl_fb.tasks();
         let resp: TestResponder = Default::default();
 
@@ -1325,7 +1353,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
         let tasks = gbl_fb.tasks();
         let resp: TestResponder = Default::default();
 
@@ -1378,7 +1407,8 @@
             liberror::Error::Other(None).into();
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
         let tasks = gbl_fb.tasks();
         let resp: TestResponder = Default::default();
 
@@ -1406,7 +1436,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
         let tasks = gbl_fb.tasks();
         let resp: TestResponder = Default::default();
 
@@ -1457,7 +1488,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
         let resp: TestResponder = Default::default();
 
         let boot_a = include_bytes!("../../../libstorage/test/boot_a.bin");
@@ -1513,7 +1545,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
         let resp: TestResponder = Default::default();
         // Missing block device ID.
         assert!(block_on(oem(&mut gbl_fb, "gbl-set-default-block ", &resp)).is_err());
@@ -1531,7 +1564,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
         let tasks = gbl_fb.tasks();
         let resp: TestResponder = Default::default();
 
@@ -1565,7 +1599,8 @@
         let mut gbl_ops = FakeGblOps::new(&storage);
         let tasks = vec![].into();
         let parts = gbl_ops.disks();
-        let mut gbl_fb = GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers);
+        let mut gbl_fb =
+            GblFastboot::new(&mut gbl_ops, parts, Task::run, &tasks, &dl_buffers, &mut []);
         let tasks = gbl_fb.tasks();
         let resp: TestResponder = Default::default();
 
@@ -1726,7 +1761,7 @@
         listener.add_tcp_input(b"FB01");
         listener.add_tcp_length_prefixed_input(b"getvar:max-download-size");
         listener.add_tcp_length_prefixed_input(b"continue");
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -1753,7 +1788,7 @@
         let listener: SharedTestListener = Default::default();
         let (usb, tcp) = (&listener, &listener);
         let mut fb_fut =
-            pin!(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+            pin!(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         listener.add_usb_input(b"oem gbl-enable-async-task");
         listener.add_usb_input(format!("download:{:#x}", 4 * 1024).as_bytes());
@@ -1817,7 +1852,7 @@
         listener.add_usb_input(b"oem add-staged-bootloader-file file_2");
         listener.add_usb_input(b"continue");
 
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         let buffer = gbl_ops.get_zbi_bootloader_files_buffer_aligned().unwrap();
         let container = ZbiContainer::parse(&buffer[..]).unwrap();
@@ -1840,7 +1875,7 @@
         listener.add_usb_input(b"oem add-staged-bootloader-file");
         listener.add_usb_input(b"continue");
 
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -1867,7 +1902,7 @@
         listener.add_usb_input(b"oem add-staged-bootloader-file file1");
         listener.add_usb_input(b"continue");
 
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -1945,7 +1980,7 @@
 
         listener.add_usb_input(b"continue");
 
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -2036,7 +2071,7 @@
         listener.add_usb_input(b"getvar:partition-size:boot_b");
         listener.add_usb_input(b"continue");
 
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -2070,7 +2105,7 @@
         listener.add_usb_input(b"flash:gpt/0");
         listener.add_usb_input(b"continue");
 
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -2097,7 +2132,7 @@
         listener.add_usb_input(b"flash:gpt/0");
         listener.add_usb_input(b"continue");
 
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -2135,7 +2170,7 @@
         // Invalid option.
         listener.add_usb_input(b"flash:gpt/0/invalid-arg");
         listener.add_usb_input(b"continue");
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -2168,7 +2203,7 @@
         listener.add_usb_input(gpt);
         listener.add_usb_input(b"flash:gpt/0");
         listener.add_usb_input(b"continue");
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -2205,7 +2240,7 @@
         listener.add_usb_input(b"getvar:partition-size:vendor_boot_b");
         listener.add_usb_input(b"continue");
 
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -2234,7 +2269,7 @@
 
         listener.add_usb_input(b"erase:gpt/0");
         listener.add_usb_input(b"continue");
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -2271,7 +2306,7 @@
         // Issues set_active commands
         listener.add_usb_input(cmd.as_bytes());
         listener.add_usb_input(b"continue");
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -2319,7 +2354,7 @@
 
         listener.add_usb_input(b"set_active:r");
         listener.add_usb_input(b"continue");
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -2345,7 +2380,7 @@
 
         listener.add_usb_input(b"reboot-bootloader");
         listener.add_usb_input(b"continue");
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -2376,7 +2411,7 @@
 
         listener.add_usb_input(b"reboot-recovery");
         listener.add_usb_input(b"continue");
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),
@@ -2411,7 +2446,7 @@
         listener.add_usb_input(&[0xaau8; 4 * 1024]);
         listener.add_usb_input(b"flash:fvm");
         listener.add_usb_input(b"continue");
-        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp)));
+        block_on(run_gbl_fastboot_stack::<2>(&mut gbl_ops, buffers, Some(usb), Some(tcp), &mut []));
 
         assert_eq!(
             listener.usb_out_queue(),