From 69606a20ebdedb632a0c3f08a1a74cca3167352d Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Sat, 14 Jan 2023 17:14:17 -0500 Subject: [PATCH 1/5] test-runner: Move FileSystemVolumeLabel test to known_disk.rs --- .../src/proto/media/known_disk.rs | 10 ++++++++- uefi-test-runner/src/proto/media/mod.rs | 21 ------------------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/uefi-test-runner/src/proto/media/known_disk.rs b/uefi-test-runner/src/proto/media/known_disk.rs index 1d85ba284..da8072fc7 100644 --- a/uefi-test-runner/src/proto/media/known_disk.rs +++ b/uefi-test-runner/src/proto/media/known_disk.rs @@ -5,7 +5,7 @@ use uefi::prelude::*; use uefi::proto::media::block::BlockIO; use uefi::proto::media::disk::{DiskIo, DiskIo2, DiskIo2Token}; use uefi::proto::media::file::{ - Directory, File, FileAttribute, FileInfo, FileMode, FileSystemInfo, + Directory, File, FileAttribute, FileInfo, FileMode, FileSystemInfo, FileSystemVolumeLabel, }; use uefi::proto::media::fs::SimpleFileSystem; use uefi::table::boot::{EventType, OpenProtocolAttributes, OpenProtocolParams, Tpl}; @@ -383,6 +383,14 @@ pub fn test_known_disk(bt: &BootServices) { let boxed_fs_info = root_directory.get_boxed_info::().unwrap(); assert_eq!(*fs_info, *boxed_fs_info); + // Check that `FileSystemVolumeLabel` provides the same volume label + // as `FileSystemInfo`. + let mut fs_vol_buf = vec![0; 128]; + let fs_vol = root_directory + .get_info::(&mut fs_vol_buf) + .unwrap(); + assert_eq!(fs_info.volume_label(), fs_vol.volume_label()); + test_existing_dir(&mut root_directory); test_delete_warning(&mut root_directory); test_existing_file(&mut root_directory); diff --git a/uefi-test-runner/src/proto/media/mod.rs b/uefi-test-runner/src/proto/media/mod.rs index 393d0a7d2..138b3e699 100644 --- a/uefi-test-runner/src/proto/media/mod.rs +++ b/uefi-test-runner/src/proto/media/mod.rs @@ -1,28 +1,9 @@ mod known_disk; use uefi::prelude::*; -use uefi::proto::media::file::{Directory, File, FileSystemInfo, FileSystemVolumeLabel}; use uefi::proto::media::fs::SimpleFileSystem; use uefi::proto::media::partition::PartitionInfo; -/// Test `FileSystemInfo` and `FileSystemVolumeLabel`. -fn test_file_system_info(directory: &mut Directory) { - let mut fs_info_buf = vec![0; 128]; - let fs_info = directory - .get_info::(&mut fs_info_buf) - .unwrap(); - info!("File system info: {:?}", fs_info); - - let mut fs_vol_buf = vec![0; 128]; - let fs_vol = directory - .get_info::(&mut fs_vol_buf) - .unwrap(); - info!("File system volume label: {:?}", fs_vol); - - // Both types should provide the same volume label. - assert_eq!(fs_info.volume_label(), fs_vol.volume_label()); -} - /// Tests the following protocols: /// - [`SimpleFileSystem`] /// - [`PartitionInfo`] @@ -56,8 +37,6 @@ pub fn test(bt: &BootServices) { info!("Root directory entry: {:?}", file_info); } directory.reset_entry_readout().unwrap(); - - test_file_system_info(&mut directory); } else { warn!("`SimpleFileSystem` protocol is not available"); } From e328179fb62b94a5eafdd02fcd9020a739400c4a Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Sat, 14 Jan 2023 17:43:10 -0500 Subject: [PATCH 2/5] test-runner: Move PartitionInfo test to known_disk.rs --- .../src/proto/media/known_disk.rs | 21 +++++++++++++++++++ uefi-test-runner/src/proto/media/mod.rs | 19 ----------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/uefi-test-runner/src/proto/media/known_disk.rs b/uefi-test-runner/src/proto/media/known_disk.rs index da8072fc7..2ca99b493 100644 --- a/uefi-test-runner/src/proto/media/known_disk.rs +++ b/uefi-test-runner/src/proto/media/known_disk.rs @@ -8,6 +8,7 @@ use uefi::proto::media::file::{ Directory, File, FileAttribute, FileInfo, FileMode, FileSystemInfo, FileSystemVolumeLabel, }; use uefi::proto::media::fs::SimpleFileSystem; +use uefi::proto::media::partition::{MbrOsType, PartitionInfo}; use uefi::table::boot::{EventType, OpenProtocolAttributes, OpenProtocolParams, Tpl}; use uefi::table::runtime::{Daylight, Time, TimeParams}; @@ -335,6 +336,24 @@ fn test_raw_disk_io2(handle: Handle, bt: &BootServices) { } } +/// Check that `disk_handle` points to the expected MBR partition. +fn test_partition_info(bt: &BootServices, disk_handle: Handle) { + let pi = bt + .open_protocol_exclusive::(disk_handle) + .expect("Failed to get partition info"); + + let mbr = pi.mbr_partition_record().expect("Not an MBR disk"); + + info!("MBR partition: {:?}", mbr); + + assert_eq!(mbr.boot_indicator, 0); + assert_eq!({ mbr.starting_lba }, 1); + assert_eq!({ mbr.size_in_lba }, 1233); + assert_eq!({ mbr.starting_chs }, [0, 0, 0]); + assert_eq!(mbr.ending_chs, [0, 0, 0]); + assert_eq!(mbr.os_type, MbrOsType(6)); +} + /// Run various file-system related tests on a special test disk. The disk is created by /// `xtask/src/disk.rs`. pub fn test_known_disk(bt: &BootServices) { @@ -396,6 +415,8 @@ pub fn test_known_disk(bt: &BootServices) { test_existing_file(&mut root_directory); test_create_file(&mut root_directory); test_create_directory(&mut root_directory); + + test_partition_info(bt, handle); } test_raw_disk_io(handle, bt); diff --git a/uefi-test-runner/src/proto/media/mod.rs b/uefi-test-runner/src/proto/media/mod.rs index 138b3e699..0aef2148c 100644 --- a/uefi-test-runner/src/proto/media/mod.rs +++ b/uefi-test-runner/src/proto/media/mod.rs @@ -2,7 +2,6 @@ mod known_disk; use uefi::prelude::*; use uefi::proto::media::fs::SimpleFileSystem; -use uefi::proto::media::partition::PartitionInfo; /// Tests the following protocols: /// - [`SimpleFileSystem`] @@ -41,23 +40,5 @@ pub fn test(bt: &BootServices) { warn!("`SimpleFileSystem` protocol is not available"); } - let handles = bt - .find_handles::() - .expect("Failed to get handles for `PartitionInfo` protocol"); - - for handle in handles { - let pi = bt - .open_protocol_exclusive::(handle) - .expect("Failed to get partition info"); - - if let Some(mbr) = pi.mbr_partition_record() { - info!("MBR partition: {:?}", mbr); - } else if let Some(gpt) = pi.gpt_partition_entry() { - info!("GPT partition: {:?}", gpt); - } else { - info!("Unknown partition"); - } - } - known_disk::test_known_disk(bt); } From e3acf2c7a9a9f31866e640dafcb2066b8642d61d Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Sat, 14 Jan 2023 17:44:27 -0500 Subject: [PATCH 3/5] test-runner: Simplify disk test Per https://github.com/rust-osdev/uefi-rs/issues/553, we've made the test runner always expect to be running in the special QEMU env, so we don't need to have a separate "works with any env" disk test from the more detailed "known disk" test. So, move `known_disk.rs` -> `mod.rs`. --- .../src/proto/media/known_disk.rs | 429 ----------------- uefi-test-runner/src/proto/media/mod.rs | 451 ++++++++++++++++-- 2 files changed, 418 insertions(+), 462 deletions(-) delete mode 100644 uefi-test-runner/src/proto/media/known_disk.rs diff --git a/uefi-test-runner/src/proto/media/known_disk.rs b/uefi-test-runner/src/proto/media/known_disk.rs deleted file mode 100644 index 2ca99b493..000000000 --- a/uefi-test-runner/src/proto/media/known_disk.rs +++ /dev/null @@ -1,429 +0,0 @@ -use alloc::string::ToString; -use core::cell::RefCell; -use core::ptr::NonNull; -use uefi::prelude::*; -use uefi::proto::media::block::BlockIO; -use uefi::proto::media::disk::{DiskIo, DiskIo2, DiskIo2Token}; -use uefi::proto::media::file::{ - Directory, File, FileAttribute, FileInfo, FileMode, FileSystemInfo, FileSystemVolumeLabel, -}; -use uefi::proto::media::fs::SimpleFileSystem; -use uefi::proto::media::partition::{MbrOsType, PartitionInfo}; -use uefi::table::boot::{EventType, OpenProtocolAttributes, OpenProtocolParams, Tpl}; -use uefi::table::runtime::{Daylight, Time, TimeParams}; - -/// Test directory entry iteration. -fn test_existing_dir(directory: &mut Directory) { - info!("Testing existing directory"); - - let input_dir_path = cstr16!("test_dir"); - let dir = directory - .open(input_dir_path, FileMode::Read, FileAttribute::empty()) - .expect("failed to open directory"); - - assert!(dir.is_directory().unwrap()); - - let dir = dir.into_directory().expect("Should be a directory"); - - let dir = RefCell::new(dir); - - // Backing memory to read the file info data into. - let mut stack_buf = [0; 200]; - - // The file names that the test read from the directory. - let entry_names = RefCell::new(vec![]); - - // Expected file names in the directory. - const EXPECTED: &[&str] = &[".", "..", "test_input.txt"]; - - // Reads the whole directory with provided backing memory. - let mut test_read_dir_stack_mem = || { - let mut dir = dir.borrow_mut(); - let mut entry_names = entry_names.borrow_mut(); - loop { - let entry = dir - .read_entry(&mut stack_buf) - .expect("failed to read directory"); - if let Some(entry) = entry { - entry_names.push(entry.file_name().to_string()); - } else { - break; - } - } - assert_eq!(&*entry_names, EXPECTED); - }; - - // Reads the whole directory but returns owned memory on the heap. - let test_read_dir_heap_mem = || { - let mut dir = dir.borrow_mut(); - let mut entry_names = entry_names.borrow_mut(); - loop { - let entry = dir.read_entry_boxed().expect("failed to read directory"); - if let Some(entry) = entry { - entry_names.push(entry.file_name().to_string()); - } else { - break; - } - } - assert_eq!(&*entry_names, EXPECTED); - }; - - // Tests all read dir test functions three times. - for _ in 0..3 { - entry_names.borrow_mut().clear(); - dir.borrow_mut().reset_entry_readout().unwrap(); - test_read_dir_stack_mem(); - - entry_names.borrow_mut().clear(); - dir.borrow_mut().reset_entry_readout().unwrap(); - test_read_dir_heap_mem(); - } -} - -/// Test that deleting a file opened in read-only mode fails with a -/// warning. This is mostly just an excuse to verify that warnings are -/// properly converted to errors. -fn test_delete_warning(directory: &mut Directory) { - let input_file_path = cstr16!("test_dir\\test_input.txt"); - let file = directory - .open(input_file_path, FileMode::Read, FileAttribute::empty()) - .expect("failed to open file") - .into_regular_file() - .expect("not a regular file"); - - assert_eq!( - file.delete().unwrap_err().status(), - Status::WARN_DELETE_FAILURE - ); -} - -/// Test operations on an existing file. -fn test_existing_file(directory: &mut Directory) { - info!("Testing existing file"); - - // Open an existing file. - let input_file_path = cstr16!("test_dir\\test_input.txt"); - let mut file = directory - .open(input_file_path, FileMode::ReadWrite, FileAttribute::empty()) - .expect("failed to open file") - .into_regular_file() - .expect("not a regular file"); - - // Read the file. - let mut buffer = vec![0; 128]; - let size = file.read(&mut buffer).expect("failed to read file"); - let buffer = &buffer[..size]; - info!("Successfully read {}", input_file_path); - assert_eq!(buffer, b"test input data"); - - // Check file metadata. - let mut info_buffer = vec![0; 128]; - let info = file.get_info::(&mut info_buffer).unwrap(); - assert_eq!(info.file_size(), 15); - assert_eq!(info.physical_size(), 512); - let tp = TimeParams { - year: 2000, - month: 1, - day: 24, - hour: 0, - minute: 0, - second: 0, - nanosecond: 0, - time_zone: None, - daylight: Daylight::empty(), - }; - assert_eq!(*info.create_time(), Time::new(tp).unwrap()); - assert_eq!( - *info.last_access_time(), - Time::new(TimeParams { - year: 2001, - month: 2, - day: 25, - ..tp - }) - .unwrap() - ); - assert_eq!( - *info.modification_time(), - Time::new(TimeParams { - year: 2002, - month: 3, - day: 26, - ..tp - }) - .unwrap() - ); - assert_eq!(info.attribute(), FileAttribute::empty()); - assert_eq!(info.file_name(), cstr16!("test_input.txt")); - - // Check that `get_boxed_info` returns the same info. - let boxed_info = file.get_boxed_info::().unwrap(); - assert_eq!(*info, *boxed_info); - - // Delete the file. - file.delete().unwrap(); - - // Verify the file is gone. - assert!(directory - .open(input_file_path, FileMode::Read, FileAttribute::empty()) - .is_err()); -} - -/// Test file creation. -fn test_create_file(directory: &mut Directory) { - info!("Testing file creation"); - - // Create a new file. - let file = directory - .open( - cstr16!("new_test_file.txt"), - FileMode::CreateReadWrite, - FileAttribute::empty(), - ) - .expect("failed to create file"); - - assert!(file.is_regular_file().unwrap()); - - let mut file = file.into_regular_file().expect("not a regular file"); - file.write(b"test output data").unwrap(); -} - -/// Test directory creation by -/// - creating a new directory -/// - creating a file in that directory -/// - accessing the new directory via a flat path and a deep path -fn test_create_directory(root_dir: &mut Directory) { - info!("Testing directory creation"); - - // Create a new directory. - let new_dir = root_dir - .open( - cstr16!("created_dir"), - FileMode::CreateReadWrite, - FileAttribute::DIRECTORY, - ) - .expect("failed to create directory"); - - let mut new_dir = new_dir.into_directory().expect("Should be a directory"); - - // create new file in new director - let msg = "hello_world"; - let file = new_dir - .open( - cstr16!("foobar"), - FileMode::CreateReadWrite, - FileAttribute::empty(), - ) - .unwrap(); - - let mut file = file.into_regular_file().expect("Should be a file!"); - file.write(msg.as_bytes()).unwrap(); - - // now access the new file with a deep path and read its content - let file = root_dir - .open( - cstr16!("created_dir\\foobar"), - FileMode::Read, - FileAttribute::empty(), - ) - .expect("Must open created file with deep path."); - let mut file = file.into_regular_file().expect("Should be a file!"); - - let mut buf = vec![0; msg.len()]; - let read_bytes = file.read(&mut buf).unwrap(); - let read = &buf[0..read_bytes]; - - assert_eq!(msg.as_bytes(), read); -} - -/// Get the media ID via the BlockIO protocol. -fn get_block_media_id(handle: Handle, bt: &BootServices) -> u32 { - // This cannot be opened in `EXCLUSIVE` mode, as doing so - // unregisters the `DiskIO` protocol from the handle. - unsafe { - let block_io = bt - .open_protocol::( - OpenProtocolParams { - handle, - agent: bt.image_handle(), - controller: None, - }, - OpenProtocolAttributes::GetProtocol, - ) - .expect("Failed to get block I/O protocol"); - block_io.media().media_id() - } -} - -/// Tests raw disk I/O. -fn test_raw_disk_io(handle: Handle, bt: &BootServices) { - info!("Testing raw disk I/O"); - - let media_id = get_block_media_id(handle, bt); - - // Open the disk I/O protocol on the input handle - let disk_io = bt - .open_protocol_exclusive::(handle) - .expect("Failed to get disk I/O protocol"); - - // Read from the first sector of the disk into the buffer - let mut buf = vec![0; 512]; - disk_io - .read_disk(media_id, 0, &mut buf) - .expect("Failed to read from disk"); - - // Verify that the disk's MBR signature is correct - assert_eq!(buf[510], 0x55); - assert_eq!(buf[511], 0xaa); - - info!("Raw disk I/O succeeded"); -} - -/// Asynchronous disk I/O task context -#[repr(C)] -struct DiskIoTask { - /// Token for the transaction - token: DiskIo2Token, - /// Buffer holding the read data - buffer: [u8; 512], -} - -/// Tests raw disk I/O through the DiskIo2 protocol. -fn test_raw_disk_io2(handle: Handle, bt: &BootServices) { - info!("Testing raw disk I/O 2"); - - // Open the disk I/O protocol on the input handle - if let Ok(disk_io2) = bt.open_protocol_exclusive::(handle) { - let media_id = get_block_media_id(handle, bt); - - unsafe { - // Create the completion event - let mut event = bt - .create_event(EventType::empty(), Tpl::NOTIFY, None, None) - .expect("Failed to create disk I/O completion event"); - - // Initialise the task context - let mut task = DiskIoTask { - token: DiskIo2Token { - event: Some(event.unsafe_clone()), - transaction_status: uefi::Status::NOT_READY, - }, - buffer: [0; 512], - }; - - // Initiate the asynchronous read operation - disk_io2 - .read_disk_raw( - media_id, - 0, - NonNull::new(&mut task.token as _), - task.buffer.len(), - task.buffer.as_mut_ptr(), - ) - .expect("Failed to initiate asynchronous disk I/O read"); - - // Wait for the transaction to complete - bt.wait_for_event(core::slice::from_mut(&mut event)) - .expect("Failed to wait on completion event"); - - // Verify that the disk's MBR signature is correct - assert_eq!(task.token.transaction_status, uefi::Status::SUCCESS); - assert_eq!(task.buffer[510], 0x55); - assert_eq!(task.buffer[511], 0xaa); - - info!("Raw disk I/O 2 succeeded"); - } - } -} - -/// Check that `disk_handle` points to the expected MBR partition. -fn test_partition_info(bt: &BootServices, disk_handle: Handle) { - let pi = bt - .open_protocol_exclusive::(disk_handle) - .expect("Failed to get partition info"); - - let mbr = pi.mbr_partition_record().expect("Not an MBR disk"); - - info!("MBR partition: {:?}", mbr); - - assert_eq!(mbr.boot_indicator, 0); - assert_eq!({ mbr.starting_lba }, 1); - assert_eq!({ mbr.size_in_lba }, 1233); - assert_eq!({ mbr.starting_chs }, [0, 0, 0]); - assert_eq!(mbr.ending_chs, [0, 0, 0]); - assert_eq!(mbr.os_type, MbrOsType(6)); -} - -/// Run various file-system related tests on a special test disk. The disk is created by -/// `xtask/src/disk.rs`. -pub fn test_known_disk(bt: &BootServices) { - let handles = bt - .find_handles::() - .expect("Failed to get handles for `SimpleFileSystem` protocol"); - assert_eq!(handles.len(), 2); - - let mut found_test_disk = false; - for handle in handles { - { - let mut sfs = bt - .open_protocol_exclusive::(handle) - .expect("Failed to get simple file system"); - let mut root_directory = sfs.open_volume().unwrap(); - - // test is_directory() and is_regular_file() from the File trait which is the - // base for into_type() used later in the test. - { - // because File is "Sized", we cannot cast it to &dyn - fn test_is_directory(file: &impl File) { - assert_eq!(Ok(true), file.is_directory()); - assert_eq!(Ok(false), file.is_regular_file()); - } - test_is_directory(&root_directory); - } - - let mut fs_info_buf = vec![0; 128]; - let fs_info = root_directory - .get_info::(&mut fs_info_buf) - .unwrap(); - - if fs_info.volume_label().to_string() == "MbrTestDisk" { - info!("Checking MbrTestDisk"); - found_test_disk = true; - } else { - continue; - } - - assert!(!fs_info.read_only()); - assert_eq!(fs_info.volume_size(), 512 * 1192); - assert_eq!(fs_info.free_space(), 512 * 1190); - assert_eq!(fs_info.block_size(), 512); - - // Check that `get_boxed_info` returns the same info. - let boxed_fs_info = root_directory.get_boxed_info::().unwrap(); - assert_eq!(*fs_info, *boxed_fs_info); - - // Check that `FileSystemVolumeLabel` provides the same volume label - // as `FileSystemInfo`. - let mut fs_vol_buf = vec![0; 128]; - let fs_vol = root_directory - .get_info::(&mut fs_vol_buf) - .unwrap(); - assert_eq!(fs_info.volume_label(), fs_vol.volume_label()); - - test_existing_dir(&mut root_directory); - test_delete_warning(&mut root_directory); - test_existing_file(&mut root_directory); - test_create_file(&mut root_directory); - test_create_directory(&mut root_directory); - - test_partition_info(bt, handle); - } - - test_raw_disk_io(handle, bt); - test_raw_disk_io2(handle, bt); - } - - if !found_test_disk { - panic!("MbrTestDisk not found"); - } -} diff --git a/uefi-test-runner/src/proto/media/mod.rs b/uefi-test-runner/src/proto/media/mod.rs index 0aef2148c..2ae30a2dd 100644 --- a/uefi-test-runner/src/proto/media/mod.rs +++ b/uefi-test-runner/src/proto/media/mod.rs @@ -1,44 +1,429 @@ -mod known_disk; - +use alloc::string::ToString; +use core::cell::RefCell; +use core::ptr::NonNull; use uefi::prelude::*; +use uefi::proto::media::block::BlockIO; +use uefi::proto::media::disk::{DiskIo, DiskIo2, DiskIo2Token}; +use uefi::proto::media::file::{ + Directory, File, FileAttribute, FileInfo, FileMode, FileSystemInfo, FileSystemVolumeLabel, +}; use uefi::proto::media::fs::SimpleFileSystem; +use uefi::proto::media::partition::{MbrOsType, PartitionInfo}; +use uefi::table::boot::{EventType, OpenProtocolAttributes, OpenProtocolParams, Tpl}; +use uefi::table::runtime::{Daylight, Time, TimeParams}; -/// Tests the following protocols: -/// - [`SimpleFileSystem`] -/// - [`PartitionInfo`] -pub fn test(bt: &BootServices) { - info!("Testing Media Access protocols"); +/// Test directory entry iteration. +fn test_existing_dir(directory: &mut Directory) { + info!("Testing existing directory"); + + let input_dir_path = cstr16!("test_dir"); + let dir = directory + .open(input_dir_path, FileMode::Read, FileAttribute::empty()) + .expect("failed to open directory"); + + assert!(dir.is_directory().unwrap()); + + let dir = dir.into_directory().expect("Should be a directory"); + + let dir = RefCell::new(dir); - if let Ok(handle) = bt.get_handle_for_protocol::() { - let mut sfs = bt - .open_protocol_exclusive::(handle) - .expect("failed to open SimpleFileSystem protocol"); + // Backing memory to read the file info data into. + let mut stack_buf = [0; 200]; - let mut directory = sfs.open_volume().unwrap(); - let mut buffer = vec![0; 128]; + // The file names that the test read from the directory. + let entry_names = RefCell::new(vec![]); + + // Expected file names in the directory. + const EXPECTED: &[&str] = &[".", "..", "test_input.txt"]; + + // Reads the whole directory with provided backing memory. + let mut test_read_dir_stack_mem = || { + let mut dir = dir.borrow_mut(); + let mut entry_names = entry_names.borrow_mut(); loop { - let file_info = match directory.read_entry(&mut buffer) { - Ok(info) => { - if let Some(info) = info { - info - } else { - // We've reached the end of the directory - break; - } - } - Err(error) => { - // Buffer is not big enough, allocate a bigger one and try again. - let min_size = error.data().unwrap(); - buffer.resize(min_size, 0); - continue; - } + let entry = dir + .read_entry(&mut stack_buf) + .expect("failed to read directory"); + if let Some(entry) = entry { + entry_names.push(entry.file_name().to_string()); + } else { + break; + } + } + assert_eq!(&*entry_names, EXPECTED); + }; + + // Reads the whole directory but returns owned memory on the heap. + let test_read_dir_heap_mem = || { + let mut dir = dir.borrow_mut(); + let mut entry_names = entry_names.borrow_mut(); + loop { + let entry = dir.read_entry_boxed().expect("failed to read directory"); + if let Some(entry) = entry { + entry_names.push(entry.file_name().to_string()); + } else { + break; + } + } + assert_eq!(&*entry_names, EXPECTED); + }; + + // Tests all read dir test functions three times. + for _ in 0..3 { + entry_names.borrow_mut().clear(); + dir.borrow_mut().reset_entry_readout().unwrap(); + test_read_dir_stack_mem(); + + entry_names.borrow_mut().clear(); + dir.borrow_mut().reset_entry_readout().unwrap(); + test_read_dir_heap_mem(); + } +} + +/// Test that deleting a file opened in read-only mode fails with a +/// warning. This is mostly just an excuse to verify that warnings are +/// properly converted to errors. +fn test_delete_warning(directory: &mut Directory) { + let input_file_path = cstr16!("test_dir\\test_input.txt"); + let file = directory + .open(input_file_path, FileMode::Read, FileAttribute::empty()) + .expect("failed to open file") + .into_regular_file() + .expect("not a regular file"); + + assert_eq!( + file.delete().unwrap_err().status(), + Status::WARN_DELETE_FAILURE + ); +} + +/// Test operations on an existing file. +fn test_existing_file(directory: &mut Directory) { + info!("Testing existing file"); + + // Open an existing file. + let input_file_path = cstr16!("test_dir\\test_input.txt"); + let mut file = directory + .open(input_file_path, FileMode::ReadWrite, FileAttribute::empty()) + .expect("failed to open file") + .into_regular_file() + .expect("not a regular file"); + + // Read the file. + let mut buffer = vec![0; 128]; + let size = file.read(&mut buffer).expect("failed to read file"); + let buffer = &buffer[..size]; + info!("Successfully read {}", input_file_path); + assert_eq!(buffer, b"test input data"); + + // Check file metadata. + let mut info_buffer = vec![0; 128]; + let info = file.get_info::(&mut info_buffer).unwrap(); + assert_eq!(info.file_size(), 15); + assert_eq!(info.physical_size(), 512); + let tp = TimeParams { + year: 2000, + month: 1, + day: 24, + hour: 0, + minute: 0, + second: 0, + nanosecond: 0, + time_zone: None, + daylight: Daylight::empty(), + }; + assert_eq!(*info.create_time(), Time::new(tp).unwrap()); + assert_eq!( + *info.last_access_time(), + Time::new(TimeParams { + year: 2001, + month: 2, + day: 25, + ..tp + }) + .unwrap() + ); + assert_eq!( + *info.modification_time(), + Time::new(TimeParams { + year: 2002, + month: 3, + day: 26, + ..tp + }) + .unwrap() + ); + assert_eq!(info.attribute(), FileAttribute::empty()); + assert_eq!(info.file_name(), cstr16!("test_input.txt")); + + // Check that `get_boxed_info` returns the same info. + let boxed_info = file.get_boxed_info::().unwrap(); + assert_eq!(*info, *boxed_info); + + // Delete the file. + file.delete().unwrap(); + + // Verify the file is gone. + assert!(directory + .open(input_file_path, FileMode::Read, FileAttribute::empty()) + .is_err()); +} + +/// Test file creation. +fn test_create_file(directory: &mut Directory) { + info!("Testing file creation"); + + // Create a new file. + let file = directory + .open( + cstr16!("new_test_file.txt"), + FileMode::CreateReadWrite, + FileAttribute::empty(), + ) + .expect("failed to create file"); + + assert!(file.is_regular_file().unwrap()); + + let mut file = file.into_regular_file().expect("not a regular file"); + file.write(b"test output data").unwrap(); +} + +/// Test directory creation by +/// - creating a new directory +/// - creating a file in that directory +/// - accessing the new directory via a flat path and a deep path +fn test_create_directory(root_dir: &mut Directory) { + info!("Testing directory creation"); + + // Create a new directory. + let new_dir = root_dir + .open( + cstr16!("created_dir"), + FileMode::CreateReadWrite, + FileAttribute::DIRECTORY, + ) + .expect("failed to create directory"); + + let mut new_dir = new_dir.into_directory().expect("Should be a directory"); + + // create new file in new director + let msg = "hello_world"; + let file = new_dir + .open( + cstr16!("foobar"), + FileMode::CreateReadWrite, + FileAttribute::empty(), + ) + .unwrap(); + + let mut file = file.into_regular_file().expect("Should be a file!"); + file.write(msg.as_bytes()).unwrap(); + + // now access the new file with a deep path and read its content + let file = root_dir + .open( + cstr16!("created_dir\\foobar"), + FileMode::Read, + FileAttribute::empty(), + ) + .expect("Must open created file with deep path."); + let mut file = file.into_regular_file().expect("Should be a file!"); + + let mut buf = vec![0; msg.len()]; + let read_bytes = file.read(&mut buf).unwrap(); + let read = &buf[0..read_bytes]; + + assert_eq!(msg.as_bytes(), read); +} + +/// Get the media ID via the BlockIO protocol. +fn get_block_media_id(handle: Handle, bt: &BootServices) -> u32 { + // This cannot be opened in `EXCLUSIVE` mode, as doing so + // unregisters the `DiskIO` protocol from the handle. + unsafe { + let block_io = bt + .open_protocol::( + OpenProtocolParams { + handle, + agent: bt.image_handle(), + controller: None, + }, + OpenProtocolAttributes::GetProtocol, + ) + .expect("Failed to get block I/O protocol"); + block_io.media().media_id() + } +} + +/// Tests raw disk I/O. +fn test_raw_disk_io(handle: Handle, bt: &BootServices) { + info!("Testing raw disk I/O"); + + let media_id = get_block_media_id(handle, bt); + + // Open the disk I/O protocol on the input handle + let disk_io = bt + .open_protocol_exclusive::(handle) + .expect("Failed to get disk I/O protocol"); + + // Read from the first sector of the disk into the buffer + let mut buf = vec![0; 512]; + disk_io + .read_disk(media_id, 0, &mut buf) + .expect("Failed to read from disk"); + + // Verify that the disk's MBR signature is correct + assert_eq!(buf[510], 0x55); + assert_eq!(buf[511], 0xaa); + + info!("Raw disk I/O succeeded"); +} + +/// Asynchronous disk I/O task context +#[repr(C)] +struct DiskIoTask { + /// Token for the transaction + token: DiskIo2Token, + /// Buffer holding the read data + buffer: [u8; 512], +} + +/// Tests raw disk I/O through the DiskIo2 protocol. +fn test_raw_disk_io2(handle: Handle, bt: &BootServices) { + info!("Testing raw disk I/O 2"); + + // Open the disk I/O protocol on the input handle + if let Ok(disk_io2) = bt.open_protocol_exclusive::(handle) { + let media_id = get_block_media_id(handle, bt); + + unsafe { + // Create the completion event + let mut event = bt + .create_event(EventType::empty(), Tpl::NOTIFY, None, None) + .expect("Failed to create disk I/O completion event"); + + // Initialise the task context + let mut task = DiskIoTask { + token: DiskIo2Token { + event: Some(event.unsafe_clone()), + transaction_status: uefi::Status::NOT_READY, + }, + buffer: [0; 512], }; - info!("Root directory entry: {:?}", file_info); + + // Initiate the asynchronous read operation + disk_io2 + .read_disk_raw( + media_id, + 0, + NonNull::new(&mut task.token as _), + task.buffer.len(), + task.buffer.as_mut_ptr(), + ) + .expect("Failed to initiate asynchronous disk I/O read"); + + // Wait for the transaction to complete + bt.wait_for_event(core::slice::from_mut(&mut event)) + .expect("Failed to wait on completion event"); + + // Verify that the disk's MBR signature is correct + assert_eq!(task.token.transaction_status, uefi::Status::SUCCESS); + assert_eq!(task.buffer[510], 0x55); + assert_eq!(task.buffer[511], 0xaa); + + info!("Raw disk I/O 2 succeeded"); + } + } +} + +/// Check that `disk_handle` points to the expected MBR partition. +fn test_partition_info(bt: &BootServices, disk_handle: Handle) { + let pi = bt + .open_protocol_exclusive::(disk_handle) + .expect("Failed to get partition info"); + + let mbr = pi.mbr_partition_record().expect("Not an MBR disk"); + + info!("MBR partition: {:?}", mbr); + + assert_eq!(mbr.boot_indicator, 0); + assert_eq!({ mbr.starting_lba }, 1); + assert_eq!({ mbr.size_in_lba }, 1233); + assert_eq!({ mbr.starting_chs }, [0, 0, 0]); + assert_eq!(mbr.ending_chs, [0, 0, 0]); + assert_eq!(mbr.os_type, MbrOsType(6)); +} + +/// Run various file-system related tests on a special test disk. The disk is created by +/// `xtask/src/disk.rs`. +pub fn test(bt: &BootServices) { + let handles = bt + .find_handles::() + .expect("Failed to get handles for `SimpleFileSystem` protocol"); + assert_eq!(handles.len(), 2); + + let mut found_test_disk = false; + for handle in handles { + { + let mut sfs = bt + .open_protocol_exclusive::(handle) + .expect("Failed to get simple file system"); + let mut root_directory = sfs.open_volume().unwrap(); + + // test is_directory() and is_regular_file() from the File trait which is the + // base for into_type() used later in the test. + { + // because File is "Sized", we cannot cast it to &dyn + fn test_is_directory(file: &impl File) { + assert_eq!(Ok(true), file.is_directory()); + assert_eq!(Ok(false), file.is_regular_file()); + } + test_is_directory(&root_directory); + } + + let mut fs_info_buf = vec![0; 128]; + let fs_info = root_directory + .get_info::(&mut fs_info_buf) + .unwrap(); + + if fs_info.volume_label().to_string() == "MbrTestDisk" { + info!("Checking MbrTestDisk"); + found_test_disk = true; + } else { + continue; + } + + assert!(!fs_info.read_only()); + assert_eq!(fs_info.volume_size(), 512 * 1192); + assert_eq!(fs_info.free_space(), 512 * 1190); + assert_eq!(fs_info.block_size(), 512); + + // Check that `get_boxed_info` returns the same info. + let boxed_fs_info = root_directory.get_boxed_info::().unwrap(); + assert_eq!(*fs_info, *boxed_fs_info); + + // Check that `FileSystemVolumeLabel` provides the same volume label + // as `FileSystemInfo`. + let mut fs_vol_buf = vec![0; 128]; + let fs_vol = root_directory + .get_info::(&mut fs_vol_buf) + .unwrap(); + assert_eq!(fs_info.volume_label(), fs_vol.volume_label()); + + test_existing_dir(&mut root_directory); + test_delete_warning(&mut root_directory); + test_existing_file(&mut root_directory); + test_create_file(&mut root_directory); + test_create_directory(&mut root_directory); + + test_partition_info(bt, handle); } - directory.reset_entry_readout().unwrap(); - } else { - warn!("`SimpleFileSystem` protocol is not available"); + + test_raw_disk_io(handle, bt); + test_raw_disk_io2(handle, bt); } - known_disk::test_known_disk(bt); + if !found_test_disk { + panic!("MbrTestDisk not found"); + } } From 0601045b766fdc45d5f12f1d4ce3182ee5e83c74 Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Sat, 14 Jan 2023 18:02:36 -0500 Subject: [PATCH 4/5] test-runner: Refactor disk test for clarity Simplify the big disk test function a little by moving the loop to find the test disk into a new function. --- uefi-test-runner/src/proto/media/mod.rs | 132 +++++++++++++----------- 1 file changed, 72 insertions(+), 60 deletions(-) diff --git a/uefi-test-runner/src/proto/media/mod.rs b/uefi-test-runner/src/proto/media/mod.rs index 2ae30a2dd..0e763feca 100644 --- a/uefi-test-runner/src/proto/media/mod.rs +++ b/uefi-test-runner/src/proto/media/mod.rs @@ -9,7 +9,9 @@ use uefi::proto::media::file::{ }; use uefi::proto::media::fs::SimpleFileSystem; use uefi::proto::media::partition::{MbrOsType, PartitionInfo}; -use uefi::table::boot::{EventType, OpenProtocolAttributes, OpenProtocolParams, Tpl}; +use uefi::table::boot::{ + EventType, OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol, Tpl, +}; use uefi::table::runtime::{Daylight, Time, TimeParams}; /// Test directory entry iteration. @@ -354,76 +356,86 @@ fn test_partition_info(bt: &BootServices, disk_handle: Handle) { assert_eq!(mbr.os_type, MbrOsType(6)); } -/// Run various file-system related tests on a special test disk. The disk is created by -/// `xtask/src/disk.rs`. -pub fn test(bt: &BootServices) { +/// Find the disk with the "MbrTestDisk" label. Return the handle and opened +/// `SimpleFileSystem` protocol for that disk. +fn find_test_disk(bt: &BootServices) -> (Handle, ScopedProtocol) { let handles = bt .find_handles::() .expect("Failed to get handles for `SimpleFileSystem` protocol"); assert_eq!(handles.len(), 2); - let mut found_test_disk = false; for handle in handles { - { - let mut sfs = bt - .open_protocol_exclusive::(handle) - .expect("Failed to get simple file system"); - let mut root_directory = sfs.open_volume().unwrap(); - - // test is_directory() and is_regular_file() from the File trait which is the - // base for into_type() used later in the test. - { - // because File is "Sized", we cannot cast it to &dyn - fn test_is_directory(file: &impl File) { - assert_eq!(Ok(true), file.is_directory()); - assert_eq!(Ok(false), file.is_regular_file()); - } - test_is_directory(&root_directory); - } + let mut sfs = bt + .open_protocol_exclusive::(handle) + .expect("Failed to get simple file system"); + let mut root_directory = sfs.open_volume().unwrap(); - let mut fs_info_buf = vec![0; 128]; - let fs_info = root_directory - .get_info::(&mut fs_info_buf) - .unwrap(); + let vol_info = root_directory + .get_boxed_info::() + .unwrap(); - if fs_info.volume_label().to_string() == "MbrTestDisk" { - info!("Checking MbrTestDisk"); - found_test_disk = true; - } else { - continue; - } + if vol_info.volume_label().to_string() == "MbrTestDisk" { + return (handle, sfs); + } + } - assert!(!fs_info.read_only()); - assert_eq!(fs_info.volume_size(), 512 * 1192); - assert_eq!(fs_info.free_space(), 512 * 1190); - assert_eq!(fs_info.block_size(), 512); - - // Check that `get_boxed_info` returns the same info. - let boxed_fs_info = root_directory.get_boxed_info::().unwrap(); - assert_eq!(*fs_info, *boxed_fs_info); - - // Check that `FileSystemVolumeLabel` provides the same volume label - // as `FileSystemInfo`. - let mut fs_vol_buf = vec![0; 128]; - let fs_vol = root_directory - .get_info::(&mut fs_vol_buf) - .unwrap(); - assert_eq!(fs_info.volume_label(), fs_vol.volume_label()); - - test_existing_dir(&mut root_directory); - test_delete_warning(&mut root_directory); - test_existing_file(&mut root_directory); - test_create_file(&mut root_directory); - test_create_directory(&mut root_directory); - - test_partition_info(bt, handle); + panic!("MbrTestDisk not found"); +} + +/// Run various file-system related tests on a special test disk. The disk is created by +/// `xtask/src/disk.rs`. +pub fn test(bt: &BootServices) { + let (handle, mut sfs) = find_test_disk(bt); + + { + let mut root_directory = sfs.open_volume().unwrap(); + + // test is_directory() and is_regular_file() from the File trait which is the + // base for into_type() used later in the test. + { + // because File is "Sized", we cannot cast it to &dyn + fn test_is_directory(file: &impl File) { + assert_eq!(Ok(true), file.is_directory()); + assert_eq!(Ok(false), file.is_regular_file()); + } + test_is_directory(&root_directory); } - test_raw_disk_io(handle, bt); - test_raw_disk_io2(handle, bt); + let mut fs_info_buf = vec![0; 128]; + let fs_info = root_directory + .get_info::(&mut fs_info_buf) + .unwrap(); + + assert!(!fs_info.read_only()); + assert_eq!(fs_info.volume_size(), 512 * 1192); + assert_eq!(fs_info.free_space(), 512 * 1190); + assert_eq!(fs_info.block_size(), 512); + assert_eq!(fs_info.volume_label().to_string(), "MbrTestDisk"); + + // Check that `get_boxed_info` returns the same info. + let boxed_fs_info = root_directory.get_boxed_info::().unwrap(); + assert_eq!(*fs_info, *boxed_fs_info); + + // Check that `FileSystemVolumeLabel` provides the same volume label + // as `FileSystemInfo`. + let mut fs_vol_buf = vec![0; 128]; + let fs_vol = root_directory + .get_info::(&mut fs_vol_buf) + .unwrap(); + assert_eq!(fs_info.volume_label(), fs_vol.volume_label()); + + test_existing_dir(&mut root_directory); + test_delete_warning(&mut root_directory); + test_existing_file(&mut root_directory); + test_create_file(&mut root_directory); + test_create_directory(&mut root_directory); + + test_partition_info(bt, handle); } - if !found_test_disk { - panic!("MbrTestDisk not found"); - } + // Close the `SimpleFileSystem` protocol so that the raw disk tests work. + drop(sfs); + + test_raw_disk_io(handle, bt); + test_raw_disk_io2(handle, bt); } From 8f579a6519fbc5bf54f9c5806095d6d7b0bc9143 Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Sat, 14 Jan 2023 18:06:08 -0500 Subject: [PATCH 5/5] test-runner: Drop media protocol dir This is just one file now, so move `media/mod.rs` -> `media.rs`. --- uefi-test-runner/src/proto/{media/mod.rs => media.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename uefi-test-runner/src/proto/{media/mod.rs => media.rs} (100%) diff --git a/uefi-test-runner/src/proto/media/mod.rs b/uefi-test-runner/src/proto/media.rs similarity index 100% rename from uefi-test-runner/src/proto/media/mod.rs rename to uefi-test-runner/src/proto/media.rs