From 5f75bed0b84eed2c715c1fdd97f4aa1322d94847 Mon Sep 17 00:00:00 2001 From: Timothy Roberts <39689890+timrobertsdev@users.noreply.github.com> Date: Mon, 28 Nov 2022 14:13:56 -0500 Subject: [PATCH 1/4] uefi: Begin implementing the EFI Shell Protocol --- uefi-test-runner/src/proto/mod.rs | 2 + uefi-test-runner/src/proto/shell.rs | 28 ++ uefi/src/proto/mod.rs | 1 + uefi/src/proto/shell/mod.rs | 406 ++++++++++++++++++++++++++++ 4 files changed, 437 insertions(+) create mode 100644 uefi-test-runner/src/proto/shell.rs create mode 100644 uefi/src/proto/shell/mod.rs diff --git a/uefi-test-runner/src/proto/mod.rs b/uefi-test-runner/src/proto/mod.rs index 3527e0b29..8d882c20d 100644 --- a/uefi-test-runner/src/proto/mod.rs +++ b/uefi-test-runner/src/proto/mod.rs @@ -42,6 +42,7 @@ pub fn test() { target_arch = "aarch64" ))] shim::test(); + shell::test(); tcg::test(); } @@ -96,6 +97,7 @@ mod shell_params; target_arch = "arm", target_arch = "aarch64" ))] +mod shell; mod shim; mod string; mod tcg; diff --git a/uefi-test-runner/src/proto/shell.rs b/uefi-test-runner/src/proto/shell.rs new file mode 100644 index 000000000..ed01dbe88 --- /dev/null +++ b/uefi-test-runner/src/proto/shell.rs @@ -0,0 +1,28 @@ +use uefi::CStr16; +use uefi::prelude::BootServices; +use uefi::proto::shell::Shell; + +pub fn test(bt: &BootServices) { + info!("Running shell protocol tests"); + + let handle = bt.get_handle_for_protocol::().expect("No Shell handles"); + + let mut shell = bt + .open_protocol_exclusive::(handle) + .expect("Failed to open Shell protocol"); + + // create some files + let mut test_buf = [0u16; 12]; + let test_str = CStr16::from_str_with_buf("test", &mut test_buf).unwrap(); + shell.create_file(test_str, 0); + + // get file tree + let mut str_buf = [0u16; 12]; + let str_str = CStr16::from_str_with_buf(r"fs0:\*", &mut str_buf).unwrap(); + let res = shell.find_files(str_str); + let list = res.unwrap(); + let list = list.unwrap(); + let first = list.first(); + + info!("filetree test successful") +} \ No newline at end of file diff --git a/uefi/src/proto/mod.rs b/uefi/src/proto/mod.rs index defc0e4f9..0a9a5f223 100644 --- a/uefi/src/proto/mod.rs +++ b/uefi/src/proto/mod.rs @@ -101,3 +101,4 @@ where ptr.cast::() } } + diff --git a/uefi/src/proto/shell/mod.rs b/uefi/src/proto/shell/mod.rs new file mode 100644 index 000000000..92548b7d3 --- /dev/null +++ b/uefi/src/proto/shell/mod.rs @@ -0,0 +1,406 @@ +//! EFI Shell Protocol v2.2 + +use core::{ffi::c_void, marker::PhantomData, mem::MaybeUninit, ptr::NonNull}; + +use uefi_macros::unsafe_protocol; + +use crate::{CStr16, Char16, Event, Handle, Result, Status, StatusExt}; + +use super::media::file::FileInfo; + +/// TODO +#[repr(C)] +#[unsafe_protocol("6302d008-7f9b-4f30-87ac-60c9fef5da4e")] +pub struct Shell { + execute: extern "efiapi" fn( + parent_image_handle: *const Handle, + commandline: *const CStr16, + environment: *const *const CStr16, + out_status: *mut Status, + ) -> Status, + get_env: usize, + set_env: usize, + get_alias: usize, + set_alias: usize, + get_help_text: usize, + get_device_path_from_map: usize, + get_map_from_device_path: usize, + get_device_path_from_file_path: usize, + get_file_path_from_device_path: usize, + set_map: usize, + + get_cur_dir: extern "efiapi" fn(file_system_mapping: *const Char16) -> *const CStr16, + set_cur_dir: usize, + open_file_list: usize, + free_file_list: extern "efiapi" fn(file_list: *mut *mut ShellFileInfo), + remove_dup_in_file_list: usize, + + batch_is_active: extern "efiapi" fn() -> bool, + is_root_shell: usize, + enable_page_break: extern "efiapi" fn(), + disable_page_break: extern "efiapi" fn(), + get_page_break: usize, + get_device_name: usize, + + get_file_info: usize, + set_file_info: usize, + open_file_by_name: usize, + close_file: extern "efiapi" fn(file_handle: ShellFileHandle) -> Status, + create_file: extern "efiapi" fn( + file_name: &CStr16, + file_attribs: u64, + out_file_handle: *mut ShellFileHandle, + ) -> Status, + read_file: usize, + write_file: usize, + delete_file: extern "efiapi" fn(file_handle: ShellFileHandle) -> Status, + delete_file_by_name: extern "efiapi" fn(file_name: &CStr16) -> Status, + get_file_position: usize, + set_file_position: usize, + flush_file: extern "efiapi" fn(file_handle: ShellFileHandle) -> Status, + find_files: extern "efiapi" fn( + file_pattern: *const CStr16, + out_file_list: *mut *mut ShellFileInfo, + ) -> Status, + find_files_in_dir: extern "efiapi" fn( + file_dir_handle: ShellFileHandle, + out_file_list: *mut *mut ShellFileInfo, + ) -> Status, + get_file_size: usize, + + open_root: usize, + open_root_by_handle: usize, + + execution_break: Event, + + major_version: u32, + minor_version: u32, + register_guid_name: usize, + get_guid_name: usize, + get_guid_from_name: usize, + get_env_ex: usize, +} + +impl core::fmt::Debug for Shell { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + todo!() + } +} + +impl Shell { + /// TODO + pub fn execute( + &self, + parent_image: Handle, + command_line: &CStr16, + environment: &[&CStr16], + ) -> Result { + let mut out_status: MaybeUninit = MaybeUninit::uninit(); + // We have to do this in two parts, an `as` cast straight to *const *const CStr16 doesn't compile + let environment = environment.as_ptr(); + let environment = environment.cast::<*const CStr16>(); + + (self.execute)( + &parent_image, + command_line, + environment, + out_status.as_mut_ptr(), + ) + .to_result_with_val(|| unsafe { out_status.assume_init() }) + } + + /// TODO + #[must_use] + pub fn get_cur_dir<'a>(&'a self, file_system_mapping: Option<&CStr16>) -> Option<&'a CStr16> { + let mapping_ptr: *const Char16 = + file_system_mapping.map_or(core::ptr::null(), |x| (x as *const CStr16).cast()); + let cur_dir = (self.get_cur_dir)(mapping_ptr); + if cur_dir.is_null() { + None + } else { + unsafe { Some(&*cur_dir) } + } + } + + /// Returns `true` if any script files are currently being processed. + #[must_use] + pub fn batch_is_active(&self) -> bool { + (self.batch_is_active)() + } + + /// Disables the page break output mode. + pub fn disable_page_break(&self) { + (self.disable_page_break)() + } + + /// Enables the page break output mode. + pub fn enable_page_break(&self) { + (self.enable_page_break)() + } + + /// Closes `file_handle`. All data is flushed to the device and the file is closed. + /// + /// Per the UEFI spec, the file handle will be closed in all cases and this function + /// only returns [`Status::SUCCESS`]. + pub fn close_file(&self, file_handle: ShellFileHandle) -> Result<()> { + (self.close_file)(file_handle).to_result() + } + + /// TODO + pub fn create_file( + &self, + file_name: &CStr16, + file_attribs: u64, + ) -> Result> { + // TODO: Find out how we could take a &str instead, or maybe AsRef, though I think it needs `alloc` + // the returned handle can possibly be NULL, so we need to wrap `ShellFileHandle` in an `Option` + let mut out_file_handle: MaybeUninit> = MaybeUninit::zeroed(); + + (self.create_file)(file_name, file_attribs, out_file_handle.as_mut_ptr().cast()) + // Safety: if this call is successful, `out_file_handle` + // will always be initialized and valid. + .to_result_with_val(|| unsafe { out_file_handle.assume_init() }) + } + + /// TODO + pub fn delete_file(&self, file_handle: ShellFileHandle) -> Result<()> { + (self.delete_file)(file_handle).to_result() + } + + /// TODO + pub fn delete_file_by_name(&self, file_name: &CStr16) -> Result<()> { + (self.delete_file_by_name)(file_name).to_result() + } + + /// TODO + pub fn find_files(&self, file_pattern: &CStr16) -> Result> { + let mut out_list: MaybeUninit<*mut ShellFileInfo> = MaybeUninit::uninit(); + let mut out_ptr = out_list.as_mut_ptr(); + if out_ptr.is_null() { + panic!("outptr null"); + } + (self.find_files)(file_pattern, out_ptr).to_result_with_val(|| { + // safety: if we're here, out_list is valid, but maybe null + let out_list = unsafe { out_list.assume_init() }; + if out_list.is_null() { + None + } else { + let file_list = FileList::new(out_list.cast(), self); + Some(file_list) + } + }) + } + + /// TODO, basically the same as `find_files` + pub fn find_files_in_dir(&self, file_dir_handle: ShellFileHandle) -> Result> { + let mut out_list: MaybeUninit<*mut ShellFileInfo> = MaybeUninit::uninit(); + (self.find_files_in_dir)(file_dir_handle, out_list.as_mut_ptr()).to_result_with_val(|| { + // safety: if we're here, out_list is valid, but maybe null + let out_list = unsafe { out_list.assume_init() }; + if out_list.is_null() { + None + } else { + let file_list = FileList::new(out_list.cast(), self); + Some(file_list) + } + }) + } + + /// Flushes all modified data associated with a file to a device. + /// + /// # Returns + /// + /// * `Ok(Some(file_iter))` - if one or more files were found that match the given pattern, + /// where `file_iter` is an iterator over the matching files. + /// * `Ok(None)` - if no files were found that match the given pattern. + /// * `Err(e)` - if an error occurred while searching for files. The specific error variants + /// are described below. + /// + /// # Errors + /// + /// This function returns errors directly from the UEFI function + /// `EFI_SHELL_PROTOCOL.FlushFile()`. + /// + /// See the function definition in the EFI Shell Specification v2.2, Chapter 2.2 + /// for more information on each error type. + /// + /// * [`uefi::Status::NO_MEDIA`] + /// * [`uefi::Status::DEVICE_ERROR`] + /// * [`uefi::Status::VOLUME_CORRUPTED`] + /// * [`uefi::Status::WRITE_PROTECTED`] + /// * [`uefi::Status::ACCESS_DENIED`] + /// * [`uefi::Status::VOLUME_FULL`] + pub fn flush_file(&self, file_handle: ShellFileHandle) -> Result<()> { + (self.flush_file)(file_handle).to_result() + } +} + +/// TODO +#[repr(transparent)] +#[derive(Debug)] +pub struct ShellFileHandle(NonNull); + +/// TODO +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct ShellFileInfo { + link: ListEntry, + status: Status, + full_name: *const CStr16, + file_name: *const CStr16, + shell_file_handle: Handle, + info: *mut FileInfo, +} + +impl ShellFileInfo { + /// TODO + #[must_use] + pub fn file_name(&self) -> &CStr16 { + unsafe { &*self.file_name } + } +} + +/// TODO +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct ListEntry { + flink: *mut ListEntry, + blink: *mut ListEntry, +} + +/// TODO +#[derive(Debug)] +pub struct FileListIter<'list> { + current_node: *const ListEntry, + current_node_back: *const ListEntry, + _marker: PhantomData<&'list ListEntry>, +} + +impl<'l> FileListIter<'l> { + fn new(start: *const ListEntry, end: *const ListEntry, _shell: &'l Shell) -> Self { + assert!(!start.is_null()); + assert!(!end.is_null()); + // Safety: all `ShellFileInfo` pointers are `ListEntry` pointers and vica-versa + Self { + current_node: start, + current_node_back: end, + _marker: PhantomData, + } + } +} + +impl<'l> Iterator for FileListIter<'l> { + type Item = &'l ShellFileInfo; + + fn next(&mut self) -> Option { + // Safety: This is safe as we're dereferencing a pointer that we've already null-checked + unsafe { + if (*self.current_node).flink.is_null() { + None + } else { + self.current_node = (*self.current_node).flink; + let ret = self.current_node.cast::(); + // Safety: all `ShellFileInfo` pointers are `ListEntry` pointers and vica-versa + Some(&*ret) + } + } + } +} + +impl<'l> DoubleEndedIterator for FileListIter<'l> { + fn next_back(&mut self) -> Option { + if self.current_node == self.current_node_back { + None + } else { + let ret = self.current_node_back.cast::(); + // safety: the equality check in the other branch should ensure we're always + // pointing to a valid node + self.current_node_back = unsafe { (*self.current_node_back).blink }; + unsafe { Some(&*ret) } + } + } +} + +/// Safe abstraction over the linked list returned by functions such as `Shell::find_files` or +/// `Shell::find_files_in_dir`. The list and all related structures will be freed when this +/// goes out of scope. +#[derive(Debug)] +pub struct FileList<'a> { + start: *const ListEntry, + end: *const ListEntry, + shell_protocol: &'a Shell, +} + +impl<'a> FileList<'a> { + #[must_use] + #[inline] + fn new(root: *const ListEntry, shell: &'a Shell) -> Self { + assert!(!root.is_null()); + + Self { + start: root, + end: core::ptr::null(), + shell_protocol: shell, + } + } + + /// Returns an iterator over the file list. + #[must_use] + #[inline] + pub fn iter(&'a self) -> FileListIter { + if self.end.is_null() { + // generate `self.end` + let _ = self.last(); + } + + FileListIter::new(self.start, self.end, self.shell_protocol) + } + + /// Returns the first element of the file list + #[must_use] + #[inline] + pub fn first(&'a self) -> &'a ShellFileInfo { + // safety: once `self` is created, start is valid + unsafe { &*self.start.cast() } + } + + /// Returns the element at the specified index or `None` if the index is invalid. + #[must_use] + #[inline] + pub fn get(&'a self, index: usize) -> Option<&'a ShellFileInfo> { + self.iter().nth(index) + } + + /// Returns the last element of the file list. + /// + /// The end position is lazily generated on the first call to this function. + #[must_use] + #[inline] + pub fn last(&'a self) -> &'a ShellFileInfo { + if !self.end.is_null() { + unsafe { &*self.end.cast() } + } else { + // traverse the list, keeping track of the last seen element + // we specifically do not use `self.iter().last()` here to avoid + // looping forever + let mut last = self.start; + + unsafe { + while !(*last).flink.is_null() { + last = (*last).flink; + } + + &*(last.cast()) + } + } + } +} + +impl<'a> Drop for FileList<'a> { + fn drop(&mut self) { + let mut root = self.start as *mut ListEntry; + let file_list_ptr = &mut root as *mut *mut ListEntry; + // Call the firmware's allocator to free + (self.shell_protocol.free_file_list)(file_list_ptr.cast::<*mut ShellFileInfo>()); + } +} From 47724977014db0856f04b8e919c184a9533ce108 Mon Sep 17 00:00:00 2001 From: Ren Trieu Date: Thu, 1 May 2025 08:03:10 -0700 Subject: [PATCH 2/4] uefi-raw: Add EFI Shell Protocol Co-authored-by: Philipp Schuster --- uefi-raw/src/protocol/mod.rs | 1 + uefi-raw/src/protocol/shell.rs | 172 ++++++++++++ uefi-test-runner/src/proto/mod.rs | 2 +- uefi-test-runner/src/proto/shell.rs | 31 +-- uefi/src/proto/mod.rs | 2 +- uefi/src/proto/shell/mod.rs | 409 +--------------------------- 6 files changed, 191 insertions(+), 426 deletions(-) create mode 100644 uefi-raw/src/protocol/shell.rs diff --git a/uefi-raw/src/protocol/mod.rs b/uefi-raw/src/protocol/mod.rs index dd3558126..444262738 100644 --- a/uefi-raw/src/protocol/mod.rs +++ b/uefi-raw/src/protocol/mod.rs @@ -24,6 +24,7 @@ pub mod nvme; pub mod pci; pub mod rng; pub mod scsi; +pub mod shell; pub mod shell_params; pub mod string; pub mod tcg; diff --git a/uefi-raw/src/protocol/shell.rs b/uefi-raw/src/protocol/shell.rs new file mode 100644 index 000000000..2bc29d682 --- /dev/null +++ b/uefi-raw/src/protocol/shell.rs @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! EFI Shell Protocol v2.2 + +use core::ffi::c_void; + +use crate::{Char8, Char16, Event, Guid, Handle, Status, guid}; + +use super::device_path::DevicePathProtocol; +use super::file_system::FileInfo; +use super::shell_params::ShellFileHandle; + +/// List Entry for File Lists +#[derive(Debug)] +#[repr(C)] +pub struct ListEntry<'a> { + pub f_link: *mut ListEntry<'a>, + pub b_link: *mut ListEntry<'a>, +} + +/// ShellFileInfo for File Lists +#[derive(Debug)] +#[repr(C)] +pub struct ShellFileInfo<'a> { + pub link: ListEntry<'a>, + pub status: Status, + pub full_name: *mut Char16, + pub file_name: *mut Char16, + pub shell_file_handle: Handle, + pub file_info: FileInfo, +} + +/// Used to specify where component names should be taken from +pub type ShellDeviceNameFlags = u32; +pub const DEVICE_NAME_USE_COMPONENT_NAME: u32 = 0x0000001; +pub const DEVICE_NAME_USE_DEVICE_PATH: u32 = 0x0000002; + +/// Shell Protocol +#[derive(Debug)] +#[repr(C)] +pub struct ShellProtocol { + pub execute: unsafe extern "efiapi" fn( + parent_image_handle: *const Handle, + command_line: *const Char16, + environment: *const *const Char16, + status_code: *mut Status, + ) -> Status, + pub get_env: unsafe extern "efiapi" fn(name: *const Char16) -> *const Char16, + pub set_env: unsafe extern "efiapi" fn( + name: *const Char16, + value: *const Char16, + volatile: bool, + ) -> Status, + pub get_alias: unsafe extern "efiapi" fn(alias: *const Char16, volatile: bool) -> *const Char16, + pub set_alias: unsafe extern "efiapi" fn( + command: *const Char16, + alias: *const Char16, + replace: bool, + volatile: bool, + ) -> Status, + pub get_help_text: unsafe extern "efiapi" fn( + command: *const Char16, + sections: *const Char16, + help_text: *mut *mut Char16, + ) -> Status, + pub get_device_path_from_map: + unsafe extern "efiapi" fn(mapping: *const Char16) -> DevicePathProtocol, + pub get_map_from_device_path: + unsafe extern "efiapi" fn(device_path: *mut *mut DevicePathProtocol) -> *const Char16, + pub get_device_path_from_file_path: + unsafe extern "efiapi" fn(path: *const Char16) -> DevicePathProtocol, + pub get_file_path_from_device_path: + unsafe extern "efiapi" fn(path: *const DevicePathProtocol) -> *const Char16, + pub set_map: unsafe extern "efiapi" fn( + device_path: DevicePathProtocol, + mapping: *const Char16, + ) -> Status, + + pub get_cur_dir: unsafe extern "efiapi" fn(file_system_mapping: *const Char16) -> *const Char16, + pub set_cur_dir: + unsafe extern "efiapi" fn(file_system: *const Char16, dir: *const Char16) -> Status, + pub open_file_list: unsafe extern "efiapi" fn( + path: Char16, + open_mode: u64, + file_list: *mut *mut ShellFileInfo, + ) -> Status, + pub free_file_list: unsafe extern "efiapi" fn(file_list: *const *const ShellFileInfo) -> Status, + pub remove_dup_in_file_list: + unsafe extern "efiapi" fn(file_list: *const *const ShellFileInfo) -> Status, + + pub batch_is_active: unsafe extern "efiapi" fn() -> bool, + pub is_root_shell: unsafe extern "efiapi" fn() -> bool, + pub enable_page_break: unsafe extern "efiapi" fn(), + pub disable_page_break: unsafe extern "efiapi" fn(), + pub get_page_break: unsafe extern "efiapi" fn() -> bool, + pub get_device_name: unsafe extern "efiapi" fn( + device_handle: Handle, + flags: ShellDeviceNameFlags, + language: *const Char8, + best_device_name: *mut *mut Char16, + ) -> Status, + + pub get_file_info: unsafe extern "efiapi" fn(file_handle: ShellFileHandle) -> FileInfo, + pub set_file_info: unsafe extern "efiapi" fn( + file_handle: ShellFileHandle, + file_info: *const FileInfo, + ) -> Status, + pub open_file_by_name: unsafe extern "efiapi" fn( + file_name: *const Char16, + file_handle: *mut ShellFileHandle, + open_mode: u64, + ) -> Status, + pub close_file: unsafe extern "efiapi" fn(file_handle: ShellFileHandle) -> Status, + pub create_file: unsafe extern "efiapi" fn( + file_name: *const Char16, + file_attribs: u64, + file_handle: *mut ShellFileHandle, + ) -> Status, + pub read_file: unsafe extern "efiapi" fn( + file_handle: ShellFileHandle, + read_size: *mut usize, + buffer: *mut c_void, + ) -> Status, + pub write_file: unsafe extern "efiapi" fn( + file_handle: ShellFileHandle, + buffer_size: *mut usize, + buffer: *mut c_void, + ) -> Status, + pub delete_file: unsafe extern "efiapi" fn(file_name: *const Char16) -> Status, + pub delete_file_by_name: unsafe extern "efiapi" fn(file_name: *const Char16) -> Status, + pub get_file_position: + unsafe extern "efiapi" fn(file_handle: ShellFileHandle, position: *mut u64) -> Status, + pub set_file_position: + unsafe extern "efiapi" fn(file_handle: ShellFileHandle, position: u64) -> Status, + pub flush_file: unsafe extern "efiapi" fn(file_handle: ShellFileHandle) -> Status, + pub find_files: unsafe extern "efiapi" fn( + file_pattern: *const Char16, + file_list: *mut *mut ShellFileInfo, + ) -> Status, + pub find_files_in_dir: unsafe extern "efiapi" fn( + file_dir_handle: ShellFileHandle, + file_list: *mut *mut ShellFileInfo, + ) -> Status, + pub get_file_size: + unsafe extern "efiapi" fn(file_handle: ShellFileHandle, size: *mut u64) -> Status, + + pub open_root: unsafe extern "efiapi" fn( + device_path: *const DevicePathProtocol, + file_handle: *mut ShellFileHandle, + ) -> Status, + pub open_root_by_handle: unsafe extern "efiapi" fn( + device_handle: Handle, + file_handle: *mut ShellFileHandle, + ) -> Status, + + pub execution_break: Event, + + pub major_version: u32, + pub minor_version: u32, + pub register_guid_name: + unsafe extern "efiapi" fn(guid: *const Guid, guid_name: *const Char16) -> Status, + pub get_guid_name: + unsafe extern "efiapi" fn(guid: *const Guid, guid_name: *mut *mut Char16) -> Status, + pub get_guid_from_name: + unsafe extern "efiapi" fn(guid_name: *const Char16, guid: *mut Guid) -> Status, + pub get_env_ex: + unsafe extern "efiapi" fn(name: *const Char16, attributes: *mut u32) -> *const Char16, +} + +impl ShellProtocol { + pub const GUID: Guid = guid!("6302d008-7f9b-4f30-87ac-60c9fef5da4e"); +} diff --git a/uefi-test-runner/src/proto/mod.rs b/uefi-test-runner/src/proto/mod.rs index 8d882c20d..5d66587c4 100644 --- a/uefi-test-runner/src/proto/mod.rs +++ b/uefi-test-runner/src/proto/mod.rs @@ -90,7 +90,6 @@ mod pci; mod pi; mod rng; mod scsi; -mod shell_params; #[cfg(any( target_arch = "x86", target_arch = "x86_64", @@ -98,6 +97,7 @@ mod shell_params; target_arch = "aarch64" ))] mod shell; +mod shell_params; mod shim; mod string; mod tcg; diff --git a/uefi-test-runner/src/proto/shell.rs b/uefi-test-runner/src/proto/shell.rs index ed01dbe88..493a22370 100644 --- a/uefi-test-runner/src/proto/shell.rs +++ b/uefi-test-runner/src/proto/shell.rs @@ -1,28 +1,13 @@ -use uefi::CStr16; -use uefi::prelude::BootServices; +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use uefi::boot; use uefi::proto::shell::Shell; -pub fn test(bt: &BootServices) { +pub fn test() { info!("Running shell protocol tests"); - let handle = bt.get_handle_for_protocol::().expect("No Shell handles"); - - let mut shell = bt - .open_protocol_exclusive::(handle) - .expect("Failed to open Shell protocol"); - - // create some files - let mut test_buf = [0u16; 12]; - let test_str = CStr16::from_str_with_buf("test", &mut test_buf).unwrap(); - shell.create_file(test_str, 0); - - // get file tree - let mut str_buf = [0u16; 12]; - let str_str = CStr16::from_str_with_buf(r"fs0:\*", &mut str_buf).unwrap(); - let res = shell.find_files(str_str); - let list = res.unwrap(); - let list = list.unwrap(); - let first = list.first(); + let handle = boot::get_handle_for_protocol::().expect("No Shell handles"); - info!("filetree test successful") -} \ No newline at end of file + let mut _shell = + boot::open_protocol_exclusive::(handle).expect("Failed to open Shell protocol"); +} diff --git a/uefi/src/proto/mod.rs b/uefi/src/proto/mod.rs index 0a9a5f223..2ac72c3e8 100644 --- a/uefi/src/proto/mod.rs +++ b/uefi/src/proto/mod.rs @@ -28,6 +28,7 @@ pub mod rng; #[cfg(feature = "alloc")] pub mod scsi; pub mod security; +pub mod shell; pub mod shell_params; pub mod shim; pub mod string; @@ -101,4 +102,3 @@ where ptr.cast::() } } - diff --git a/uefi/src/proto/shell/mod.rs b/uefi/src/proto/shell/mod.rs index 92548b7d3..e7e0dd2f4 100644 --- a/uefi/src/proto/shell/mod.rs +++ b/uefi/src/proto/shell/mod.rs @@ -1,406 +1,13 @@ -//! EFI Shell Protocol v2.2 - -use core::{ffi::c_void, marker::PhantomData, mem::MaybeUninit, ptr::NonNull}; - -use uefi_macros::unsafe_protocol; - -use crate::{CStr16, Char16, Event, Handle, Result, Status, StatusExt}; - -use super::media::file::FileInfo; - -/// TODO -#[repr(C)] -#[unsafe_protocol("6302d008-7f9b-4f30-87ac-60c9fef5da4e")] -pub struct Shell { - execute: extern "efiapi" fn( - parent_image_handle: *const Handle, - commandline: *const CStr16, - environment: *const *const CStr16, - out_status: *mut Status, - ) -> Status, - get_env: usize, - set_env: usize, - get_alias: usize, - set_alias: usize, - get_help_text: usize, - get_device_path_from_map: usize, - get_map_from_device_path: usize, - get_device_path_from_file_path: usize, - get_file_path_from_device_path: usize, - set_map: usize, - - get_cur_dir: extern "efiapi" fn(file_system_mapping: *const Char16) -> *const CStr16, - set_cur_dir: usize, - open_file_list: usize, - free_file_list: extern "efiapi" fn(file_list: *mut *mut ShellFileInfo), - remove_dup_in_file_list: usize, - - batch_is_active: extern "efiapi" fn() -> bool, - is_root_shell: usize, - enable_page_break: extern "efiapi" fn(), - disable_page_break: extern "efiapi" fn(), - get_page_break: usize, - get_device_name: usize, - - get_file_info: usize, - set_file_info: usize, - open_file_by_name: usize, - close_file: extern "efiapi" fn(file_handle: ShellFileHandle) -> Status, - create_file: extern "efiapi" fn( - file_name: &CStr16, - file_attribs: u64, - out_file_handle: *mut ShellFileHandle, - ) -> Status, - read_file: usize, - write_file: usize, - delete_file: extern "efiapi" fn(file_handle: ShellFileHandle) -> Status, - delete_file_by_name: extern "efiapi" fn(file_name: &CStr16) -> Status, - get_file_position: usize, - set_file_position: usize, - flush_file: extern "efiapi" fn(file_handle: ShellFileHandle) -> Status, - find_files: extern "efiapi" fn( - file_pattern: *const CStr16, - out_file_list: *mut *mut ShellFileInfo, - ) -> Status, - find_files_in_dir: extern "efiapi" fn( - file_dir_handle: ShellFileHandle, - out_file_list: *mut *mut ShellFileInfo, - ) -> Status, - get_file_size: usize, - - open_root: usize, - open_root_by_handle: usize, - - execution_break: Event, - - major_version: u32, - minor_version: u32, - register_guid_name: usize, - get_guid_name: usize, - get_guid_from_name: usize, - get_env_ex: usize, -} - -impl core::fmt::Debug for Shell { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - todo!() - } -} - -impl Shell { - /// TODO - pub fn execute( - &self, - parent_image: Handle, - command_line: &CStr16, - environment: &[&CStr16], - ) -> Result { - let mut out_status: MaybeUninit = MaybeUninit::uninit(); - // We have to do this in two parts, an `as` cast straight to *const *const CStr16 doesn't compile - let environment = environment.as_ptr(); - let environment = environment.cast::<*const CStr16>(); - - (self.execute)( - &parent_image, - command_line, - environment, - out_status.as_mut_ptr(), - ) - .to_result_with_val(|| unsafe { out_status.assume_init() }) - } - - /// TODO - #[must_use] - pub fn get_cur_dir<'a>(&'a self, file_system_mapping: Option<&CStr16>) -> Option<&'a CStr16> { - let mapping_ptr: *const Char16 = - file_system_mapping.map_or(core::ptr::null(), |x| (x as *const CStr16).cast()); - let cur_dir = (self.get_cur_dir)(mapping_ptr); - if cur_dir.is_null() { - None - } else { - unsafe { Some(&*cur_dir) } - } - } - - /// Returns `true` if any script files are currently being processed. - #[must_use] - pub fn batch_is_active(&self) -> bool { - (self.batch_is_active)() - } - - /// Disables the page break output mode. - pub fn disable_page_break(&self) { - (self.disable_page_break)() - } - - /// Enables the page break output mode. - pub fn enable_page_break(&self) { - (self.enable_page_break)() - } - - /// Closes `file_handle`. All data is flushed to the device and the file is closed. - /// - /// Per the UEFI spec, the file handle will be closed in all cases and this function - /// only returns [`Status::SUCCESS`]. - pub fn close_file(&self, file_handle: ShellFileHandle) -> Result<()> { - (self.close_file)(file_handle).to_result() - } - - /// TODO - pub fn create_file( - &self, - file_name: &CStr16, - file_attribs: u64, - ) -> Result> { - // TODO: Find out how we could take a &str instead, or maybe AsRef, though I think it needs `alloc` - // the returned handle can possibly be NULL, so we need to wrap `ShellFileHandle` in an `Option` - let mut out_file_handle: MaybeUninit> = MaybeUninit::zeroed(); - - (self.create_file)(file_name, file_attribs, out_file_handle.as_mut_ptr().cast()) - // Safety: if this call is successful, `out_file_handle` - // will always be initialized and valid. - .to_result_with_val(|| unsafe { out_file_handle.assume_init() }) - } - - /// TODO - pub fn delete_file(&self, file_handle: ShellFileHandle) -> Result<()> { - (self.delete_file)(file_handle).to_result() - } - - /// TODO - pub fn delete_file_by_name(&self, file_name: &CStr16) -> Result<()> { - (self.delete_file_by_name)(file_name).to_result() - } +// SPDX-License-Identifier: MIT OR Apache-2.0 - /// TODO - pub fn find_files(&self, file_pattern: &CStr16) -> Result> { - let mut out_list: MaybeUninit<*mut ShellFileInfo> = MaybeUninit::uninit(); - let mut out_ptr = out_list.as_mut_ptr(); - if out_ptr.is_null() { - panic!("outptr null"); - } - (self.find_files)(file_pattern, out_ptr).to_result_with_val(|| { - // safety: if we're here, out_list is valid, but maybe null - let out_list = unsafe { out_list.assume_init() }; - if out_list.is_null() { - None - } else { - let file_list = FileList::new(out_list.cast(), self); - Some(file_list) - } - }) - } - - /// TODO, basically the same as `find_files` - pub fn find_files_in_dir(&self, file_dir_handle: ShellFileHandle) -> Result> { - let mut out_list: MaybeUninit<*mut ShellFileInfo> = MaybeUninit::uninit(); - (self.find_files_in_dir)(file_dir_handle, out_list.as_mut_ptr()).to_result_with_val(|| { - // safety: if we're here, out_list is valid, but maybe null - let out_list = unsafe { out_list.assume_init() }; - if out_list.is_null() { - None - } else { - let file_list = FileList::new(out_list.cast(), self); - Some(file_list) - } - }) - } - - /// Flushes all modified data associated with a file to a device. - /// - /// # Returns - /// - /// * `Ok(Some(file_iter))` - if one or more files were found that match the given pattern, - /// where `file_iter` is an iterator over the matching files. - /// * `Ok(None)` - if no files were found that match the given pattern. - /// * `Err(e)` - if an error occurred while searching for files. The specific error variants - /// are described below. - /// - /// # Errors - /// - /// This function returns errors directly from the UEFI function - /// `EFI_SHELL_PROTOCOL.FlushFile()`. - /// - /// See the function definition in the EFI Shell Specification v2.2, Chapter 2.2 - /// for more information on each error type. - /// - /// * [`uefi::Status::NO_MEDIA`] - /// * [`uefi::Status::DEVICE_ERROR`] - /// * [`uefi::Status::VOLUME_CORRUPTED`] - /// * [`uefi::Status::WRITE_PROTECTED`] - /// * [`uefi::Status::ACCESS_DENIED`] - /// * [`uefi::Status::VOLUME_FULL`] - pub fn flush_file(&self, file_handle: ShellFileHandle) -> Result<()> { - (self.flush_file)(file_handle).to_result() - } -} - -/// TODO -#[repr(transparent)] -#[derive(Debug)] -pub struct ShellFileHandle(NonNull); - -/// TODO -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct ShellFileInfo { - link: ListEntry, - status: Status, - full_name: *const CStr16, - file_name: *const CStr16, - shell_file_handle: Handle, - info: *mut FileInfo, -} - -impl ShellFileInfo { - /// TODO - #[must_use] - pub fn file_name(&self) -> &CStr16 { - unsafe { &*self.file_name } - } -} - -/// TODO -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct ListEntry { - flink: *mut ListEntry, - blink: *mut ListEntry, -} - -/// TODO -#[derive(Debug)] -pub struct FileListIter<'list> { - current_node: *const ListEntry, - current_node_back: *const ListEntry, - _marker: PhantomData<&'list ListEntry>, -} - -impl<'l> FileListIter<'l> { - fn new(start: *const ListEntry, end: *const ListEntry, _shell: &'l Shell) -> Self { - assert!(!start.is_null()); - assert!(!end.is_null()); - // Safety: all `ShellFileInfo` pointers are `ListEntry` pointers and vica-versa - Self { - current_node: start, - current_node_back: end, - _marker: PhantomData, - } - } -} - -impl<'l> Iterator for FileListIter<'l> { - type Item = &'l ShellFileInfo; +//! EFI Shell Protocol v2.2 - fn next(&mut self) -> Option { - // Safety: This is safe as we're dereferencing a pointer that we've already null-checked - unsafe { - if (*self.current_node).flink.is_null() { - None - } else { - self.current_node = (*self.current_node).flink; - let ret = self.current_node.cast::(); - // Safety: all `ShellFileInfo` pointers are `ListEntry` pointers and vica-versa - Some(&*ret) - } - } - } -} +use crate::proto::unsafe_protocol; -impl<'l> DoubleEndedIterator for FileListIter<'l> { - fn next_back(&mut self) -> Option { - if self.current_node == self.current_node_back { - None - } else { - let ret = self.current_node_back.cast::(); - // safety: the equality check in the other branch should ensure we're always - // pointing to a valid node - self.current_node_back = unsafe { (*self.current_node_back).blink }; - unsafe { Some(&*ret) } - } - } -} +pub use uefi_raw::protocol::shell::ShellProtocol; -/// Safe abstraction over the linked list returned by functions such as `Shell::find_files` or -/// `Shell::find_files_in_dir`. The list and all related structures will be freed when this -/// goes out of scope. +/// Shell Protocol #[derive(Debug)] -pub struct FileList<'a> { - start: *const ListEntry, - end: *const ListEntry, - shell_protocol: &'a Shell, -} - -impl<'a> FileList<'a> { - #[must_use] - #[inline] - fn new(root: *const ListEntry, shell: &'a Shell) -> Self { - assert!(!root.is_null()); - - Self { - start: root, - end: core::ptr::null(), - shell_protocol: shell, - } - } - - /// Returns an iterator over the file list. - #[must_use] - #[inline] - pub fn iter(&'a self) -> FileListIter { - if self.end.is_null() { - // generate `self.end` - let _ = self.last(); - } - - FileListIter::new(self.start, self.end, self.shell_protocol) - } - - /// Returns the first element of the file list - #[must_use] - #[inline] - pub fn first(&'a self) -> &'a ShellFileInfo { - // safety: once `self` is created, start is valid - unsafe { &*self.start.cast() } - } - - /// Returns the element at the specified index or `None` if the index is invalid. - #[must_use] - #[inline] - pub fn get(&'a self, index: usize) -> Option<&'a ShellFileInfo> { - self.iter().nth(index) - } - - /// Returns the last element of the file list. - /// - /// The end position is lazily generated on the first call to this function. - #[must_use] - #[inline] - pub fn last(&'a self) -> &'a ShellFileInfo { - if !self.end.is_null() { - unsafe { &*self.end.cast() } - } else { - // traverse the list, keeping track of the last seen element - // we specifically do not use `self.iter().last()` here to avoid - // looping forever - let mut last = self.start; - - unsafe { - while !(*last).flink.is_null() { - last = (*last).flink; - } - - &*(last.cast()) - } - } - } -} - -impl<'a> Drop for FileList<'a> { - fn drop(&mut self) { - let mut root = self.start as *mut ListEntry; - let file_list_ptr = &mut root as *mut *mut ListEntry; - // Call the firmware's allocator to free - (self.shell_protocol.free_file_list)(file_list_ptr.cast::<*mut ShellFileInfo>()); - } -} +#[repr(transparent)] +#[unsafe_protocol(uefi_raw::protocol::shell::ShellProtocol::GUID)] +pub struct Shell(uefi_raw::protocol::shell::ShellProtocol); From 7afa6cea2020e2218174d546d6bb2a675516b78b Mon Sep 17 00:00:00 2001 From: Ren Trieu Date: Sun, 1 Jun 2025 13:54:12 -0700 Subject: [PATCH 3/4] uefi-raw: Revising EFI Shell Protocol definition --- uefi-raw/src/protocol/shell.rs | 42 ++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/uefi-raw/src/protocol/shell.rs b/uefi-raw/src/protocol/shell.rs index 2bc29d682..891f56df9 100644 --- a/uefi-raw/src/protocol/shell.rs +++ b/uefi-raw/src/protocol/shell.rs @@ -10,30 +10,42 @@ use super::device_path::DevicePathProtocol; use super::file_system::FileInfo; use super::shell_params::ShellFileHandle; +use bitflags::bitflags; + /// List Entry for File Lists #[derive(Debug)] #[repr(C)] -pub struct ListEntry<'a> { - pub f_link: *mut ListEntry<'a>, - pub b_link: *mut ListEntry<'a>, +pub struct ListEntry { + pub f_link: *mut ListEntry, + pub b_link: *mut ListEntry, } /// ShellFileInfo for File Lists #[derive(Debug)] #[repr(C)] -pub struct ShellFileInfo<'a> { - pub link: ListEntry<'a>, +pub struct ShellFileInfo { + pub link: ListEntry, pub status: Status, pub full_name: *mut Char16, pub file_name: *mut Char16, - pub shell_file_handle: Handle, - pub file_info: FileInfo, + pub handle: ShellFileHandle, + pub info: FileInfo, } /// Used to specify where component names should be taken from pub type ShellDeviceNameFlags = u32; -pub const DEVICE_NAME_USE_COMPONENT_NAME: u32 = 0x0000001; -pub const DEVICE_NAME_USE_DEVICE_PATH: u32 = 0x0000002; + +bitflags! { + /// Specifies the source of the component name + #[repr(transparent)] + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] + pub struct DeviceName: u32 { + /// Use Component Name + const DEVICE_NAME_USE_COMPONENT_NAME = 0x0000001; + /// Use Device Path + const DEVICE_NAME_USE_DEVICE_PATH = 0x0000002; + } +} /// Shell Protocol #[derive(Debug)] @@ -64,15 +76,15 @@ pub struct ShellProtocol { help_text: *mut *mut Char16, ) -> Status, pub get_device_path_from_map: - unsafe extern "efiapi" fn(mapping: *const Char16) -> DevicePathProtocol, + unsafe extern "efiapi" fn(mapping: *const Char16) -> *const DevicePathProtocol, pub get_map_from_device_path: unsafe extern "efiapi" fn(device_path: *mut *mut DevicePathProtocol) -> *const Char16, pub get_device_path_from_file_path: - unsafe extern "efiapi" fn(path: *const Char16) -> DevicePathProtocol, + unsafe extern "efiapi" fn(path: *const Char16) -> *const DevicePathProtocol, pub get_file_path_from_device_path: unsafe extern "efiapi" fn(path: *const DevicePathProtocol) -> *const Char16, pub set_map: unsafe extern "efiapi" fn( - device_path: DevicePathProtocol, + device_path: *const DevicePathProtocol, mapping: *const Char16, ) -> Status, @@ -80,7 +92,7 @@ pub struct ShellProtocol { pub set_cur_dir: unsafe extern "efiapi" fn(file_system: *const Char16, dir: *const Char16) -> Status, pub open_file_list: unsafe extern "efiapi" fn( - path: Char16, + path: *const Char16, open_mode: u64, file_list: *mut *mut ShellFileInfo, ) -> Status, @@ -100,7 +112,7 @@ pub struct ShellProtocol { best_device_name: *mut *mut Char16, ) -> Status, - pub get_file_info: unsafe extern "efiapi" fn(file_handle: ShellFileHandle) -> FileInfo, + pub get_file_info: unsafe extern "efiapi" fn(file_handle: ShellFileHandle) -> *const FileInfo, pub set_file_info: unsafe extern "efiapi" fn( file_handle: ShellFileHandle, file_info: *const FileInfo, @@ -126,7 +138,7 @@ pub struct ShellProtocol { buffer_size: *mut usize, buffer: *mut c_void, ) -> Status, - pub delete_file: unsafe extern "efiapi" fn(file_name: *const Char16) -> Status, + pub delete_file: unsafe extern "efiapi" fn(file_handle: ShellFileHandle) -> Status, pub delete_file_by_name: unsafe extern "efiapi" fn(file_name: *const Char16) -> Status, pub get_file_position: unsafe extern "efiapi" fn(file_handle: ShellFileHandle, position: *mut u64) -> Status, From a02420e4744b93f594d6037c14ee21a7ac5cd0b2 Mon Sep 17 00:00:00 2001 From: Ren Trieu Date: Mon, 2 Jun 2025 07:16:09 -0700 Subject: [PATCH 4/4] uefi-raw: Revising ShellDeviceNameFlags --- uefi-raw/src/protocol/shell.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/uefi-raw/src/protocol/shell.rs b/uefi-raw/src/protocol/shell.rs index 891f56df9..8e9500221 100644 --- a/uefi-raw/src/protocol/shell.rs +++ b/uefi-raw/src/protocol/shell.rs @@ -32,18 +32,15 @@ pub struct ShellFileInfo { pub info: FileInfo, } -/// Used to specify where component names should be taken from -pub type ShellDeviceNameFlags = u32; - bitflags! { /// Specifies the source of the component name #[repr(transparent)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] - pub struct DeviceName: u32 { + pub struct ShellDeviceNameFlags: u32 { /// Use Component Name - const DEVICE_NAME_USE_COMPONENT_NAME = 0x0000001; + const USE_COMPONENT_NAME = 0x0000001; /// Use Device Path - const DEVICE_NAME_USE_DEVICE_PATH = 0x0000002; + const USE_DEVICE_PATH = 0x0000002; } }