-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add code in xtask to build a disk image with a FAT partition, then run file system tests against that disk (when the test runner is built with the qemu feature). A disk image is used instead of QEMU's VVFAT option so that everything is fully controlled (can test exact FS info values) and isolated from the host. This partially addresses #57. There are some more partition and file system tests that can be added here.
- Loading branch information
1 parent
ccc5404
commit 87df393
Showing
6 changed files
with
317 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
use anyhow::Result; | ||
use fatfs::{Date, DateTime, FileSystem, FormatVolumeOptions, FsOptions, StdIoWrapper, Time}; | ||
use mbrman::{MBRPartitionEntry, CHS, MBR}; | ||
use std::io::{Cursor, Read, Write}; | ||
use std::ops::Range; | ||
use std::path::Path; | ||
|
||
const SECTOR_SIZE: usize = 512; | ||
|
||
fn get_partition_byte_range(mbr: &MBR) -> Range<usize> { | ||
let partition_start_byte = mbr[1].starting_lba as usize * SECTOR_SIZE; | ||
let partition_num_bytes = mbr[1].sectors as usize * SECTOR_SIZE; | ||
partition_start_byte..partition_start_byte + partition_num_bytes | ||
} | ||
|
||
pub fn create_mbr_test_disk(path: &Path) -> Result<()> { | ||
let num_sectors = 1234; | ||
|
||
let partition_byte_range; | ||
let mut disk = vec![0; num_sectors * SECTOR_SIZE]; | ||
{ | ||
let mut cur = std::io::Cursor::new(&mut disk); | ||
|
||
let mut mbr = MBR::new_from(&mut cur, SECTOR_SIZE as u32, [0xff; 4])?; | ||
mbr[1] = MBRPartitionEntry { | ||
boot: false, | ||
first_chs: CHS::empty(), | ||
sys: 0x06, | ||
last_chs: CHS::empty(), | ||
starting_lba: 1, | ||
sectors: mbr.disk_size - 1, | ||
}; | ||
|
||
partition_byte_range = get_partition_byte_range(&mbr); | ||
|
||
mbr.write_into(&mut cur)?; | ||
} | ||
|
||
init_fat_test_partition(&mut disk, partition_byte_range)?; | ||
|
||
fs_err::write(path, &disk)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
fn init_fat_test_partition(disk: &mut [u8], partition_byte_range: Range<usize>) -> Result<()> { | ||
{ | ||
let mut cursor = StdIoWrapper::from(Cursor::new(&mut disk[partition_byte_range.clone()])); | ||
fatfs::format_volume( | ||
&mut cursor, | ||
FormatVolumeOptions::new().volume_label(*b"MbrTestDisk"), | ||
)?; | ||
} | ||
|
||
let cursor = Cursor::new(&mut disk[partition_byte_range]); | ||
let fs = FileSystem::new(cursor, FsOptions::new().update_accessed_date(false))?; | ||
|
||
assert_eq!( | ||
fs.read_volume_label_from_root_dir().unwrap(), | ||
Some("MbrTestDisk".to_string()) | ||
); | ||
|
||
let root_dir = fs.root_dir(); | ||
|
||
let dir = root_dir.create_dir("test_dir")?; | ||
|
||
let mut file = dir.create_file("test_input.txt")?; | ||
file.write_all(b"test input data")?; | ||
|
||
// The datetime-setting functions have been deprecated, but are | ||
// useful here to force an exact date that can be checked in the | ||
// test. | ||
#[allow(deprecated)] | ||
{ | ||
let time = Time::new(0, 0, 0, 0); | ||
file.set_created(DateTime::new(Date::new(2000, 1, 24), time)); | ||
file.set_accessed(Date::new(2001, 2, 25)); | ||
file.set_modified(DateTime::new(Date::new(2002, 3, 26), time)); | ||
} | ||
|
||
let stats = fs.stats()?; | ||
// Assert these specific numbers here since they are checked by the | ||
// test-runner too. | ||
assert_eq!(stats.total_clusters(), 1192); | ||
assert_eq!(stats.free_clusters(), 1190); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn check_mbr_test_disk(path: &Path) -> Result<()> { | ||
println!("Verifying test disk has been correctly modified"); | ||
let mut disk = fs_err::read(path)?; | ||
|
||
let partition_byte_range; | ||
{ | ||
let mut cursor = Cursor::new(&disk); | ||
let mbr = MBR::read_from(&mut cursor, SECTOR_SIZE as u32)?; | ||
partition_byte_range = get_partition_byte_range(&mbr); | ||
} | ||
|
||
let cursor = Cursor::new(&mut disk[partition_byte_range]); | ||
let fs = FileSystem::new(cursor, FsOptions::new().update_accessed_date(false))?; | ||
let root_dir = fs.root_dir(); | ||
|
||
// Check that the new file was created. | ||
let mut file = root_dir.open_file("new_test_file.txt")?; | ||
let mut bytes = Vec::new(); | ||
file.read_to_end(&mut bytes)?; | ||
assert_eq!(bytes, b"test output data"); | ||
|
||
// Check that the original input file was deleted. | ||
let dir = root_dir.open_dir("test_dir")?; | ||
let children: Vec<_> = dir.iter().map(|e| e.unwrap().file_name()).collect(); | ||
assert_eq!(children, [".", ".."]); | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
mod arch; | ||
mod cargo; | ||
mod disk; | ||
mod opt; | ||
mod qemu; | ||
mod util; | ||
|
Oops, something went wrong.