Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## uefi - [Unreleased]

### Added

- Added `FileHandle::into_directory` and `FileHandle::into_regular_file`.

## uefi-macros - [Unreleased]

## uefi-services - [Unreleased]
Expand Down
20 changes: 20 additions & 0 deletions src/proto/media/file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,26 @@ impl FileHandle {
s => Err(s.into()),
}
}

/// If the handle represents a directory, convert it into a
/// [`Directory`]. Otherwise returns `None`.
pub fn into_directory(self) -> Option<Directory> {
if let Ok(FileType::Dir(dir)) = self.into_type() {
Some(dir)
} else {
None
}
}

/// If the handle represents a regular file, convert it into a
/// [`RegularFile`]. Otherwise returns `None`.
pub fn into_regular_file(self) -> Option<RegularFile> {
if let Ok(FileType::Regular(regular)) = self.into_type() {
Some(regular)
} else {
None
}
}
}

impl File for FileHandle {
Expand Down
180 changes: 180 additions & 0 deletions uefi-test-runner/src/proto/media/known_disk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use alloc::string::ToString;
use uefi::prelude::*;
use uefi::proto::media::file::{
Directory, File, FileAttribute, FileInfo, FileMode, FileSystemInfo,
};
use uefi::proto::media::fs::SimpleFileSystem;
use uefi::table::boot::{OpenProtocolAttributes, OpenProtocolParams};
use uefi::table::runtime::{Daylight, Time};
use uefi::CString16;

/// Test directory entry iteration.
fn test_existing_dir(directory: &mut Directory) {
info!("Testing existing directory");

let input_dir_path = CString16::try_from("test_dir").unwrap();
let mut dir = directory
.open(&input_dir_path, FileMode::Read, FileAttribute::empty())
.expect("failed to open directory")
.into_directory()
.expect("not a directory");

// Collect and validate the directory entries.
let mut entry_names = vec![];
let mut buf = vec![0; 200];
loop {
let entry = dir.read_entry(&mut 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, [".", "..", "test_input.txt"]);
}

/// 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 = CString16::try_from("test_dir\\test_input.txt").unwrap();
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 = CString16::try_from("test_dir\\test_input.txt").unwrap();
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::<FileInfo>(&mut info_buffer).unwrap();
assert_eq!(info.file_size(), 15);
assert_eq!(info.physical_size(), 512);
assert_eq!(
*info.create_time(),
Time::new(2000, 1, 24, 0, 0, 0, 0, 2047, Daylight::empty())
);
assert_eq!(
*info.last_access_time(),
Time::new(2001, 2, 25, 0, 0, 0, 0, 2047, Daylight::empty())
);
assert_eq!(
*info.modification_time(),
Time::new(2002, 3, 26, 0, 0, 0, 0, 2047, Daylight::empty())
);
assert_eq!(info.attribute(), FileAttribute::empty());
assert_eq!(
info.file_name(),
CString16::try_from("test_input.txt").unwrap()
);

// 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 mut file = directory
.open(
&CString16::try_from("new_test_file.txt").unwrap(),
FileMode::CreateReadWrite,
FileAttribute::empty(),
)
.expect("failed to create file")
.into_regular_file()
.expect("not a regular file");
file.write(b"test output data").unwrap();
}

/// Run various tests on a special test disk. The disk is created by
/// xtask/src/disk.rs.
pub fn test_known_disk(image: Handle, bt: &BootServices) {
// This test is only valid when running in the specially-prepared
// qemu with the test disk.
if !cfg!(feature = "qemu") {
return;
}

let handles = bt
.find_handles::<SimpleFileSystem>()
.expect("Failed to get handles for `SimpleFileSystem` protocol");
assert_eq!(handles.len(), 2);

let mut found_test_disk = false;
for handle in handles {
let sfs = bt
.open_protocol::<SimpleFileSystem>(
OpenProtocolParams {
handle,
agent: image,
controller: None,
},
OpenProtocolAttributes::Exclusive,
)
.expect("Failed to get simple file system");
let sfs = unsafe { &mut *sfs.interface.get() };
let mut directory = sfs.open_volume().unwrap();

let mut fs_info_buf = vec![0; 128];
let fs_info = directory
.get_info::<FileSystemInfo>(&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);

test_existing_dir(&mut directory);
test_delete_warning(&mut directory);
test_existing_file(&mut directory);
test_create_file(&mut directory);
}

if !found_test_disk {
panic!("MbrTestDisk not found");
}
}
49 changes: 5 additions & 44 deletions uefi-test-runner/src/proto/media/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
mod known_disk;

use uefi::prelude::*;
use uefi::proto::media::file::{
Directory, File, FileAttribute, FileMode, FileSystemInfo, FileSystemVolumeLabel, FileType,
};
use uefi::proto::media::file::{Directory, File, FileSystemInfo, FileSystemVolumeLabel};
use uefi::proto::media::fs::SimpleFileSystem;
use uefi::proto::media::partition::PartitionInfo;
use uefi::table::boot::{OpenProtocolAttributes, OpenProtocolParams};
use uefi::CString16;

/// Test `FileSystemInfo` and `FileSystemVolumeLabel`.
fn test_file_system_info(directory: &mut Directory) {
Expand All @@ -23,45 +22,6 @@ fn test_file_system_info(directory: &mut Directory) {

// Both types should provide the same volume label.
assert_eq!(fs_info.volume_label(), fs_vol.volume_label());

// If running under qemu we can verify the exact name.
if cfg!(feature = "qemu") {
assert_eq!(
fs_info.volume_label(),
CString16::try_from("QEMU VVFAT").unwrap()
);
}
}

/// Open and read a test file in the boot directory.
fn test_open_and_read(directory: &mut Directory) {
let test_input_path = CString16::try_from("EFI\\BOOT\\test_input.txt").unwrap();
match directory.open(&test_input_path, FileMode::Read, FileAttribute::empty()) {
Ok(file) => {
let file = file.into_type().unwrap();
if let FileType::Regular(mut file) = file {
let mut buffer = vec![0; 128];
let size = file
.read(&mut buffer)
.unwrap_or_else(|_| panic!("failed to read {}", test_input_path));
let buffer = &buffer[..size];
info!("Successfully read {}", test_input_path);
assert_eq!(buffer, b"test input data");
} else {
panic!("{} is not a regular file", test_input_path);
}
}
Err(err) => {
let msg = format!("Failed to open {}: {:?}", test_input_path, err);
// The file might reasonably not be present when running on real
// hardware, so only panic on failure under qemu.
if cfg!(feature = "qemu") {
panic!("{}", msg);
} else {
warn!("{}", msg);
}
}
}
}

pub fn test(image: Handle, bt: &BootServices) {
Expand Down Expand Up @@ -93,7 +53,6 @@ pub fn test(image: Handle, bt: &BootServices) {
directory.reset_entry_readout().unwrap();

test_file_system_info(&mut directory);
test_open_and_read(&mut directory);
} else {
warn!("`SimpleFileSystem` protocol is not available");
}
Expand Down Expand Up @@ -123,4 +82,6 @@ pub fn test(image: Handle, bt: &BootServices) {
info!("Unknown partition");
}
}

known_disk::test_known_disk(image, bt);
}
3 changes: 3 additions & 0 deletions xtask/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ publish = false
[dependencies]
anyhow = "1.0.51"
clap = { version = "3.0.13", features = ["derive"] }
# The latest fatfs release (0.3.5) is old, use git instead to pick up some fixes.
fatfs = { git = "https://github.com/rafalh/rust-fatfs.git", rev = "87fc1ed5074a32b4e0344fcdde77359ef9e75432" }
fs-err = "2.6.0"
mbrman = "0.4.2"
nix = "0.23.1"
regex = "1.5.4"
serde_json = "1.0.73"
Expand Down
Loading