diff --git a/CHANGELOG.md b/CHANGELOG.md index a6032a897..cbc8a8004 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - `DevicePathNode::data` ### Changed +- Renamed `LoadImageSource::FromFilePath` to `LoadImageSource::FromDevicePath` ### Removed @@ -44,7 +45,6 @@ `BUFFER_TOO_SMALL` error can only occur when reading a directory, not a file. - `RegularFile::read` now reads in 1 MiB chunks to avoid a bug in some firmware. This fix also applies to `fs::FileSystem::read`. - ## uefi-services - 0.19.0 (2023-06-01) ### Changed diff --git a/uefi-test-runner/src/bin/shell_launcher.rs b/uefi-test-runner/src/bin/shell_launcher.rs index 414f12535..0309f7377 100644 --- a/uefi-test-runner/src/bin/shell_launcher.rs +++ b/uefi-test-runner/src/bin/shell_launcher.rs @@ -57,8 +57,8 @@ fn efi_main(image: Handle, mut st: SystemTable) -> Status { let shell_image_handle = boot_services .load_image( image, - LoadImageSource::FromFilePath { - file_path: shell_image_path, + LoadImageSource::FromDevicePath { + device_path: shell_image_path, from_boot_manager: false, }, ) diff --git a/uefi-test-runner/src/boot/mod.rs b/uefi-test-runner/src/boot/mod.rs index cee4c3635..e8f55fe47 100644 --- a/uefi-test-runner/src/boot/mod.rs +++ b/uefi-test-runner/src/boot/mod.rs @@ -1,7 +1,13 @@ +use alloc::string::ToString; use uefi::proto::console::text::Output; -use uefi::table::boot::{BootServices, SearchType}; +use uefi::proto::device_path::media::FilePath; +use uefi::proto::device_path::{DevicePath, LoadedImageDevicePath}; +use uefi::table::boot::{BootServices, LoadImageSource, SearchType}; use uefi::table::{Boot, SystemTable}; -use uefi::Identify; +use uefi::{CString16, Identify}; + +mod memory; +mod misc; pub fn test(st: &SystemTable) { let bt = st.boot_services(); @@ -9,11 +15,9 @@ pub fn test(st: &SystemTable) { memory::test(bt); misc::test(st); test_locate_handle_buffer(bt); + test_load_image(bt); } -mod memory; -mod misc; - fn test_locate_handle_buffer(bt: &BootServices) { info!("Testing the `locate_handle_buffer` function"); @@ -36,3 +40,67 @@ fn test_locate_handle_buffer(bt: &BootServices) { ); } } + +/// This test loads the "self image" again into memory using the `load_image` +/// boot service function. The image is not started but just loaded into memory. +/// +/// It transitively tests the protocol [`LoadedImageDevicePath`] which is +/// required as helper. +fn test_load_image(bt: &BootServices) { + /// The path of the loaded image executing this integration test. + const LOADED_IMAGE_PATH: &str = r"\EFI\BOOT\TEST_RUNNER.EFI"; + + info!("Testing the `load_image` function"); + + let image_device_path_protocol = bt + .open_protocol_exclusive::(bt.image_handle()) + .expect("should open LoadedImage protocol"); + + // Note: This is the full device path. The LoadedImage protocol would only + // provide us with the file-path portion of the device path. + let image_device_path: &DevicePath = &image_device_path_protocol; + + // Get the file-path portion of the device path which is typically behind + // device path node (0x4, 0x4). The string is in upper case. + + let image_device_path_file_path = image_device_path + .node_iter() + .find_map(|node| { + let node: &FilePath = node.try_into().ok()?; + let path = node.path_name().to_cstring16().ok()?; + Some(path.to_string().to_uppercase()) + }) + .expect("should have file-path portion in device path"); + + assert_eq!(image_device_path_file_path.as_str(), LOADED_IMAGE_PATH); + + // Variant A: FromBuffer + { + let mut fs = bt + .get_image_file_system(bt.image_handle()) + .expect("should open file system"); + let path = CString16::try_from(image_device_path_file_path.as_str()).unwrap(); + let image_data = fs.read(&*path).expect("should read file content"); + let load_source = LoadImageSource::FromBuffer { + buffer: image_data.as_slice(), + file_path: None, + }; + let _ = bt + .load_image(bt.image_handle(), load_source) + .expect("should load image"); + + log::debug!("load_image with FromBuffer strategy works"); + } + // Variant B: FromDevicePath + { + let load_source = LoadImageSource::FromDevicePath { + device_path: image_device_path, + from_boot_manager: false, + }; + let _ = bt + .load_image(bt.image_handle(), load_source) + .expect("should load image"); + + log::debug!("load_image with FromFilePath strategy works"); + } +} diff --git a/uefi/src/data_types/strs.rs b/uefi/src/data_types/strs.rs index 9167afe88..875f1c731 100644 --- a/uefi/src/data_types/strs.rs +++ b/uefi/src/data_types/strs.rs @@ -211,10 +211,8 @@ impl CStr16 { Self::from_u16_with_nul_unchecked(slice::from_raw_parts(ptr, len + 1)) } - /// Creates a C string wrapper from a u16 slice - /// - /// Since not every u16 value is a valid UCS-2 code point, this function - /// must do a bit more validity checking than CStr::from_bytes_with_nul + /// Creates a `&CStr16` from a u16 slice, if the slice contains exactly + /// one terminating null-byte and all chars are valid UCS-2 chars. pub fn from_u16_with_nul(codes: &[u16]) -> Result<&Self, FromSliceWithNulError> { for (pos, &code) in codes.iter().enumerate() { match code.try_into() { @@ -234,7 +232,7 @@ impl CStr16 { Err(FromSliceWithNulError::NotNulTerminated) } - /// Unsafely creates a C string wrapper from a u16 slice. + /// Unsafely creates a `&CStr16` from a u16 slice. /// /// # Safety /// @@ -287,11 +285,13 @@ impl CStr16 { Self::from_u16_with_nul(&buf[..index + 1]).map_err(|err| match err { FromSliceWithNulError::InvalidChar(p) => FromStrWithBufError::InvalidChar(p), FromSliceWithNulError::InteriorNul(p) => FromStrWithBufError::InteriorNul(p), - FromSliceWithNulError::NotNulTerminated => unreachable!(), + FromSliceWithNulError::NotNulTerminated => { + unreachable!() + } }) } - /// Create a [`CStr16`] from an [`UnalignedSlice`] using an aligned + /// Create a `&CStr16` from an [`UnalignedSlice`] using an aligned /// buffer for storage. The lifetime of the output is tied to `buf`, /// not `src`. pub fn from_unaligned_slice<'buf>( diff --git a/uefi/src/proto/loaded_image.rs b/uefi/src/proto/loaded_image.rs index 827acaa8c..de1c41107 100644 --- a/uefi/src/proto/loaded_image.rs +++ b/uefi/src/proto/loaded_image.rs @@ -56,10 +56,15 @@ impl LoadedImage { self.device_handle } - /// Get a reference to the `file_path`. + /// Get a reference to the `file_path` portion of the DeviceHandle that the + /// EFI image was loaded from. /// - /// Return `None` if the pointer to the file path portion specific to - /// DeviceHandle that the EFI Image was loaded from is null. + /// For a full device path, consider using the [`LoadedImageDevicePath`] + /// protocol. + /// + /// Returns `None` if `file_path` is null. + /// + /// [`LoadedImageDevicePath`]: crate::proto::device_path::LoadedImageDevicePath #[must_use] pub fn file_path(&self) -> Option<&DevicePath> { if self.file_path.is_null() { diff --git a/uefi/src/table/boot.rs b/uefi/src/table/boot.rs index 07e1acace..1a1672026 100644 --- a/uefi/src/table/boot.rs +++ b/uefi/src/table/boot.rs @@ -1026,8 +1026,8 @@ impl BootServices { source_buffer = buffer.as_ptr(); source_size = buffer.len(); } - LoadImageSource::FromFilePath { - file_path, + LoadImageSource::FromDevicePath { + device_path: file_path, from_boot_manager, } => { boot_policy = u8::from(from_boot_manager); @@ -1697,11 +1697,21 @@ pub enum LoadImageSource<'a> { /// behavior depends on `from_boot_manager`. If `true`, attempt to /// load via the `LoadFile` protocol. If `false`, attempt to load /// via the `LoadFile2` protocol, then fall back to `LoadFile`. - FromFilePath { - /// Device path from which to load the image. - file_path: &'a DevicePath, - - /// Whether the request originates from the boot manager. + FromDevicePath { + /// The full device path from which to load the image. + /// + /// The provided path should be a full device path and not just the + /// file path portion of it. So for example, it must be (the binary + /// representation) + /// `PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x0,0xFFFF,0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/\\EFI\\BOOT\\BOOTX64.EFI` + /// and not just `\\EFI\\BOOT\\BOOTX64.EFI`. + device_path: &'a DevicePath, + + /// If there is no instance of [`SimpleFileSystem`] protocol associated + /// with the given device path, then this function will attempt to use + /// `LoadFileProtocol` (`from_boot_manager` is `true`) or + /// `LoadFile2Protocol`, and then `LoadFileProtocol` + /// (`from_boot_manager` is `false`). from_boot_manager: bool, }, }