diff --git a/CHANGELOG.md b/CHANGELOG.md index 27742774c..256eb022a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - `MpService::startup_all_aps` and `MpService::startup_this_ap` now accept an optional `event` parameter to allow non-blocking operation. - Added `core::error::Error` implementations to all error types. +- Added the `ShellParams` protocol ### Removed - `BootServices::memmove` and `BootServices::set_mem` have been removed, use diff --git a/uefi-test-runner/examples/shell_params.rs b/uefi-test-runner/examples/shell_params.rs new file mode 100644 index 000000000..a0f03c6ca --- /dev/null +++ b/uefi-test-runner/examples/shell_params.rs @@ -0,0 +1,57 @@ +// ANCHOR: all +// ANCHOR: features +#![no_main] +#![no_std] +// ANCHOR_END: features + +use log::error; +// ANCHOR: use +//use log::info; +use uefi::CStr16; +use uefi::{prelude::*, proto::shell_params::ShellParameters}; +use uefi_services::println; + +extern crate alloc; +use alloc::string::String; +use alloc::vec::Vec; +// ANCHOR_END: use + +// ANCHOR: entry +#[entry] +fn main(image_handle: Handle, mut system_table: SystemTable) -> Status { + // ANCHOR_END: entry + // ANCHOR: services + uefi_services::init(&mut system_table).unwrap(); + let boot_services = system_table.boot_services(); + // ANCHOR_END: services + + // ANCHOR: params + let shell_params = + boot_services.open_protocol_exclusive::(image_handle); + let shell_params = match shell_params { + Ok(s) => s, + Err(e) => { + error!("Failed to get ShellParameters protocol"); + return e.status(); + } + }; + + // Get as Vec of String, only with alloc feature + let args: Vec = shell_params.get_args().collect(); + println!("Args: {:?}", args); + + // Or without allocating, get a slice of the pointers + let args = shell_params.get_args_slice(); + println!("Num args: {}", args.len()); + if args.len() > 1 { + unsafe { + println!("First real arg: '{}'", CStr16::from_ptr(args[1])); + } + } + // ANCHOR_END: params + + // ANCHOR: return + Status::SUCCESS +} +// ANCHOR_END: return +// ANCHOR_END: all diff --git a/uefi-test-runner/src/main.rs b/uefi-test-runner/src/main.rs index 97d79d37e..c203557e8 100644 --- a/uefi-test-runner/src/main.rs +++ b/uefi-test-runner/src/main.rs @@ -6,10 +6,11 @@ extern crate log; #[macro_use] extern crate alloc; -use alloc::string::ToString; -use uefi::prelude::*; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; use uefi::proto::console::serial::Serial; use uefi::Result; +use uefi::{prelude::*, proto::shell_params::ShellParameters}; use uefi_services::{print, println}; mod boot; @@ -17,6 +18,27 @@ mod fs; mod proto; mod runtime; +fn test_subshell(image: Handle, bt: &BootServices) -> Result { + let shell_params = unsafe { + bt + //.open_protocol_exclusive::(image).unwrap(); + .open_protocol::( + uefi::table::boot::OpenProtocolParams { + handle: image, + agent: bt.image_handle(), + controller: None, + }, + uefi::table::boot::OpenProtocolAttributes::GetProtocol, + ) + .unwrap() + }; + + println!("Subshell argc={}", shell_params.argc); + let args: Vec = shell_params.get_args().collect(); + println!("Args: {:?}", args); + Ok(shell_params.argc > 1) +} + #[entry] fn efi_main(image: Handle, mut st: SystemTable) -> Status { // Initialize utilities (logging, memory allocation...) @@ -45,6 +67,17 @@ fn efi_main(image: Handle, mut st: SystemTable) -> Status { // Test all the boot services. let bt = st.boot_services(); + // If ShellParametes protocol present and run with arguments, + // we're running from a subshell started by a test. Thus run the + // ShellParams test. + if let Ok(true) = test_subshell(image, bt) { + println!("Running subshell tests"); + proto::shell_params::test_subshell(image, bt); + shutdown(st); + } else { + println!("Couldn't find shell params"); + } + // Try retrieving a handle to the file system the image was booted from. bt.get_image_file_system(image) .expect("Failed to retrieve boot file system"); diff --git a/uefi-test-runner/src/proto/mod.rs b/uefi-test-runner/src/proto/mod.rs index ce7575e55..ac14dc1be 100644 --- a/uefi-test-runner/src/proto/mod.rs +++ b/uefi-test-runner/src/proto/mod.rs @@ -20,6 +20,7 @@ pub fn test(image: Handle, st: &mut SystemTable) { network::test(bt); pi::test(bt); rng::test(bt); + shell_params::test(image, bt); string::test(bt); #[cfg(any( @@ -63,6 +64,7 @@ mod media; mod network; mod pi; mod rng; +pub mod shell_params; #[cfg(any( target_arch = "x86", target_arch = "x86_64", diff --git a/uefi-test-runner/src/proto/shell_params.rs b/uefi-test-runner/src/proto/shell_params.rs new file mode 100644 index 000000000..7ebafe1d0 --- /dev/null +++ b/uefi-test-runner/src/proto/shell_params.rs @@ -0,0 +1,114 @@ +use alloc::string::String; +use alloc::vec::Vec; +use log::info; +use uefi::prelude::*; +use uefi::proto::device_path::build::{self, DevicePathBuilder}; +use uefi::proto::device_path::{DevicePath, DeviceSubType, DeviceType, LoadedImageDevicePath}; +use uefi::proto::loaded_image::LoadedImage; +use uefi::proto::shell_params::ShellParameters; +use uefi::table::boot::{BootServices, LoadImageSource}; +use uefi::CStr16; + +pub fn test(image: Handle, bt: &BootServices) { + info!("Running loaded image protocol test"); + + let shell_params = bt + .open_protocol_exclusive::(image) + .expect("Failed to open ShellParameters protocol"); + + info!("Argc: {}", shell_params.argc); + info!("Args:"); + for arg in shell_params.get_args_slice() { + let arg_str = unsafe { CStr16::from_ptr(*arg) }; + info!(" '{}'", arg_str); + } + + assert_eq!(shell_params.argc, shell_params.get_args_slice().len()); + + // By default a single argument, the executable's path + assert_eq!(shell_params.argc, 1); + + subshell_runner(image, bt); +} + +/// Get the device path of the shell app. This is the same as the +/// currently-loaded image's device path, but with the file path part changed. +fn get_shell_app_device_path<'a>( + boot_services: &BootServices, + storage: &'a mut Vec, +) -> &'a DevicePath { + let loaded_image_device_path = boot_services + .open_protocol_exclusive::(boot_services.image_handle()) + .expect("failed to open LoadedImageDevicePath protocol"); + + let mut builder = DevicePathBuilder::with_vec(storage); + for node in loaded_image_device_path.node_iter() { + if node.full_type() == (DeviceType::MEDIA, DeviceSubType::MEDIA_FILE_PATH) { + break; + } + builder = builder.push(&node).unwrap(); + } + builder = builder + .push(&build::media::FilePath { + path_name: cstr16!(r"efi\boot\shell.efi"), + }) + .unwrap(); + builder.finalize().unwrap() +} + +fn subshell_runner(image: Handle, boot_services: &BootServices) { + let mut storage = Vec::new(); + let shell_image_path = get_shell_app_device_path(boot_services, &mut storage); + + // Load the shell app. + let shell_image_handle = boot_services + .load_image( + image, + LoadImageSource::FromDevicePath { + device_path: shell_image_path, + from_boot_manager: false, + }, + ) + .expect("failed to load shell app"); + + // Set the command line passed to the shell app so that it will run the + // test-runner app. This automatically turns off the five-second delay. + let mut shell_loaded_image = boot_services + .open_protocol_exclusive::(shell_image_handle) + .expect("failed to open LoadedImage protocol"); + let load_options = cstr16!(r"shell.efi test_runner.efi arg1 arg2"); + unsafe { + shell_loaded_image.set_load_options( + load_options.as_ptr().cast(), + load_options.num_bytes() as u32, + ); + } + + info!("launching the sub shell app"); + boot_services + .start_image(shell_image_handle) + .expect("failed to launch the shell app"); +} + +pub fn test_subshell(image: Handle, boot_services: &BootServices) { + info!("Running test from subshell"); + + let shell_params = boot_services + .open_protocol_exclusive::(image) + .expect("Failed to open ShellParameters protocol"); + + info!("Argc: {}", shell_params.argc); + info!("Args:"); + for arg in shell_params.get_args_slice() { + let arg_str = unsafe { CStr16::from_ptr(*arg) }; + info!(" '{}'", arg_str); + } + + assert_eq!(shell_params.argc, shell_params.get_args_slice().len()); + + let args: Vec = shell_params.get_args().collect(); + assert_eq!(args, vec![r"FS0:\efi\boot\test_runner.efi", "arg1", "arg2"]); + + // test_runner.efi arg1 arg2 + assert_eq!(shell_params.argc, 3); +} diff --git a/uefi/src/proto/mod.rs b/uefi/src/proto/mod.rs index 71d7530dd..6f2663944 100644 --- a/uefi/src/proto/mod.rs +++ b/uefi/src/proto/mod.rs @@ -76,6 +76,7 @@ pub mod network; pub mod pi; pub mod rng; pub mod security; +pub mod shell_params; pub mod shim; pub mod string; pub mod tcg; diff --git a/uefi/src/proto/shell_params.rs b/uefi/src/proto/shell_params.rs new file mode 100644 index 000000000..411a495e8 --- /dev/null +++ b/uefi/src/proto/shell_params.rs @@ -0,0 +1,49 @@ +//! `ShellParams` protocol + +use crate::proto::unsafe_protocol; +use crate::Char16; +use core::ffi::c_void; +use core::slice::from_raw_parts; + +#[cfg(feature = "alloc")] +use crate::CStr16; +#[cfg(feature = "alloc")] +use alloc::string::String; +#[cfg(feature = "alloc")] +use alloc::string::ToString; + +type ShellFileHandle = *const c_void; + +/// The ShellParameters protocol. +#[repr(C)] +#[unsafe_protocol("752f3136-4e16-4fdc-a22a-e5f46812f4ca")] +pub struct ShellParameters { + /// Pointer to a list of arguments + pub argv: *const *const Char16, + /// Number of arguments + pub argc: usize, + /// Handle of the standard input + std_in: ShellFileHandle, + /// Handle of the standard output + std_out: ShellFileHandle, + /// Handle of the standard error output + std_err: ShellFileHandle, +} + +impl ShellParameters { + /// Get an iterator of the shell parameter arguments + #[cfg(feature = "alloc")] + pub fn get_args(&self) -> impl Iterator { + unsafe { + from_raw_parts(self.argv, self.argc) + .iter() + .map(|x| CStr16::from_ptr(*x).to_string()) + } + } + + /// Get a slice of the args, as Char16 pointers + #[must_use] + pub fn get_args_slice(&self) -> &[*const Char16] { + unsafe { from_raw_parts(self.argv, self.argc) } + } +}