Skip to content

Commit 87df393

Browse files
nicholasbishopGabrielMajeri
authored andcommitted
Expand file system tests
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.
1 parent ccc5404 commit 87df393

File tree

6 files changed

+317
-44
lines changed

6 files changed

+317
-44
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
use alloc::string::ToString;
2+
use uefi::prelude::*;
3+
use uefi::proto::media::file::{
4+
Directory, File, FileAttribute, FileInfo, FileMode, FileSystemInfo,
5+
};
6+
use uefi::proto::media::fs::SimpleFileSystem;
7+
use uefi::table::boot::{OpenProtocolAttributes, OpenProtocolParams};
8+
use uefi::table::runtime::{Daylight, Time};
9+
use uefi::CString16;
10+
11+
/// Test directory entry iteration.
12+
fn test_existing_dir(directory: &mut Directory) {
13+
info!("Testing existing directory");
14+
15+
let input_dir_path = CString16::try_from("test_dir").unwrap();
16+
let mut dir = directory
17+
.open(&input_dir_path, FileMode::Read, FileAttribute::empty())
18+
.expect("failed to open directory")
19+
.into_directory()
20+
.expect("not a directory");
21+
22+
// Collect and validate the directory entries.
23+
let mut entry_names = vec![];
24+
let mut buf = vec![0; 200];
25+
loop {
26+
let entry = dir.read_entry(&mut buf).expect("failed to read directory");
27+
if let Some(entry) = entry {
28+
entry_names.push(entry.file_name().to_string());
29+
} else {
30+
break;
31+
}
32+
}
33+
assert_eq!(entry_names, [".", "..", "test_input.txt"]);
34+
}
35+
36+
/// Test that deleting a file opened in read-only mode fails with a
37+
/// warning. This is mostly just an excuse to verify that warnings are
38+
/// properly converted to errors.
39+
fn test_delete_warning(directory: &mut Directory) {
40+
let input_file_path = CString16::try_from("test_dir\\test_input.txt").unwrap();
41+
let file = directory
42+
.open(&input_file_path, FileMode::Read, FileAttribute::empty())
43+
.expect("failed to open file")
44+
.into_regular_file()
45+
.expect("not a regular file");
46+
47+
assert_eq!(
48+
file.delete().unwrap_err().status(),
49+
Status::WARN_DELETE_FAILURE
50+
);
51+
}
52+
53+
/// Test operations on an existing file.
54+
fn test_existing_file(directory: &mut Directory) {
55+
info!("Testing existing file");
56+
57+
// Open an existing file.
58+
let input_file_path = CString16::try_from("test_dir\\test_input.txt").unwrap();
59+
let mut file = directory
60+
.open(
61+
&input_file_path,
62+
FileMode::ReadWrite,
63+
FileAttribute::empty(),
64+
)
65+
.expect("failed to open file")
66+
.into_regular_file()
67+
.expect("not a regular file");
68+
69+
// Read the file.
70+
let mut buffer = vec![0; 128];
71+
let size = file.read(&mut buffer).expect("failed to read file");
72+
let buffer = &buffer[..size];
73+
info!("Successfully read {}", input_file_path);
74+
assert_eq!(buffer, b"test input data");
75+
76+
// Check file metadata.
77+
let mut info_buffer = vec![0; 128];
78+
let info = file.get_info::<FileInfo>(&mut info_buffer).unwrap();
79+
assert_eq!(info.file_size(), 15);
80+
assert_eq!(info.physical_size(), 512);
81+
assert_eq!(
82+
*info.create_time(),
83+
Time::new(2000, 1, 24, 0, 0, 0, 0, 2047, Daylight::empty())
84+
);
85+
assert_eq!(
86+
*info.last_access_time(),
87+
Time::new(2001, 2, 25, 0, 0, 0, 0, 2047, Daylight::empty())
88+
);
89+
assert_eq!(
90+
*info.modification_time(),
91+
Time::new(2002, 3, 26, 0, 0, 0, 0, 2047, Daylight::empty())
92+
);
93+
assert_eq!(info.attribute(), FileAttribute::empty());
94+
assert_eq!(
95+
info.file_name(),
96+
CString16::try_from("test_input.txt").unwrap()
97+
);
98+
99+
// Delete the file.
100+
file.delete().unwrap();
101+
102+
// Verify the file is gone.
103+
assert!(directory
104+
.open(&input_file_path, FileMode::Read, FileAttribute::empty())
105+
.is_err());
106+
}
107+
108+
/// Test file creation.
109+
fn test_create_file(directory: &mut Directory) {
110+
info!("Testing file creation");
111+
112+
// Create a new file.
113+
let mut file = directory
114+
.open(
115+
&CString16::try_from("new_test_file.txt").unwrap(),
116+
FileMode::CreateReadWrite,
117+
FileAttribute::empty(),
118+
)
119+
.expect("failed to create file")
120+
.into_regular_file()
121+
.expect("not a regular file");
122+
file.write(b"test output data").unwrap();
123+
}
124+
125+
/// Run various tests on a special test disk. The disk is created by
126+
/// xtask/src/disk.rs.
127+
pub fn test_known_disk(image: Handle, bt: &BootServices) {
128+
// This test is only valid when running in the specially-prepared
129+
// qemu with the test disk.
130+
if !cfg!(feature = "qemu") {
131+
return;
132+
}
133+
134+
let handles = bt
135+
.find_handles::<SimpleFileSystem>()
136+
.expect("Failed to get handles for `SimpleFileSystem` protocol");
137+
assert_eq!(handles.len(), 2);
138+
139+
let mut found_test_disk = false;
140+
for handle in handles {
141+
let sfs = bt
142+
.open_protocol::<SimpleFileSystem>(
143+
OpenProtocolParams {
144+
handle,
145+
agent: image,
146+
controller: None,
147+
},
148+
OpenProtocolAttributes::Exclusive,
149+
)
150+
.expect("Failed to get simple file system");
151+
let sfs = unsafe { &mut *sfs.interface.get() };
152+
let mut directory = sfs.open_volume().unwrap();
153+
154+
let mut fs_info_buf = vec![0; 128];
155+
let fs_info = directory
156+
.get_info::<FileSystemInfo>(&mut fs_info_buf)
157+
.unwrap();
158+
159+
if fs_info.volume_label().to_string() == "MbrTestDisk" {
160+
info!("Checking MbrTestDisk");
161+
found_test_disk = true;
162+
} else {
163+
continue;
164+
}
165+
166+
assert!(!fs_info.read_only());
167+
assert_eq!(fs_info.volume_size(), 512 * 1192);
168+
assert_eq!(fs_info.free_space(), 512 * 1190);
169+
assert_eq!(fs_info.block_size(), 512);
170+
171+
test_existing_dir(&mut directory);
172+
test_delete_warning(&mut directory);
173+
test_existing_file(&mut directory);
174+
test_create_file(&mut directory);
175+
}
176+
177+
if !found_test_disk {
178+
panic!("MbrTestDisk not found");
179+
}
180+
}

uefi-test-runner/src/proto/media/mod.rs

Lines changed: 5 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1+
mod known_disk;
2+
13
use uefi::prelude::*;
2-
use uefi::proto::media::file::{
3-
Directory, File, FileAttribute, FileMode, FileSystemInfo, FileSystemVolumeLabel, FileType,
4-
};
4+
use uefi::proto::media::file::{Directory, File, FileSystemInfo, FileSystemVolumeLabel};
55
use uefi::proto::media::fs::SimpleFileSystem;
66
use uefi::proto::media::partition::PartitionInfo;
77
use uefi::table::boot::{OpenProtocolAttributes, OpenProtocolParams};
8-
use uefi::CString16;
98

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

2423
// Both types should provide the same volume label.
2524
assert_eq!(fs_info.volume_label(), fs_vol.volume_label());
26-
27-
// If running under qemu we can verify the exact name.
28-
if cfg!(feature = "qemu") {
29-
assert_eq!(
30-
fs_info.volume_label(),
31-
CString16::try_from("QEMU VVFAT").unwrap()
32-
);
33-
}
34-
}
35-
36-
/// Open and read a test file in the boot directory.
37-
fn test_open_and_read(directory: &mut Directory) {
38-
let test_input_path = CString16::try_from("EFI\\BOOT\\test_input.txt").unwrap();
39-
match directory.open(&test_input_path, FileMode::Read, FileAttribute::empty()) {
40-
Ok(file) => {
41-
let file = file.into_type().unwrap();
42-
if let FileType::Regular(mut file) = file {
43-
let mut buffer = vec![0; 128];
44-
let size = file
45-
.read(&mut buffer)
46-
.unwrap_or_else(|_| panic!("failed to read {}", test_input_path));
47-
let buffer = &buffer[..size];
48-
info!("Successfully read {}", test_input_path);
49-
assert_eq!(buffer, b"test input data");
50-
} else {
51-
panic!("{} is not a regular file", test_input_path);
52-
}
53-
}
54-
Err(err) => {
55-
let msg = format!("Failed to open {}: {:?}", test_input_path, err);
56-
// The file might reasonably not be present when running on real
57-
// hardware, so only panic on failure under qemu.
58-
if cfg!(feature = "qemu") {
59-
panic!("{}", msg);
60-
} else {
61-
warn!("{}", msg);
62-
}
63-
}
64-
}
6525
}
6626

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

9555
test_file_system_info(&mut directory);
96-
test_open_and_read(&mut directory);
9756
} else {
9857
warn!("`SimpleFileSystem` protocol is not available");
9958
}
@@ -123,4 +82,6 @@ pub fn test(image: Handle, bt: &BootServices) {
12382
info!("Unknown partition");
12483
}
12584
}
85+
86+
known_disk::test_known_disk(image, bt);
12687
}

xtask/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ publish = false
77
[dependencies]
88
anyhow = "1.0.51"
99
clap = { version = "3.0.13", features = ["derive"] }
10+
# The latest fatfs release (0.3.5) is old, use git instead to pick up some fixes.
11+
fatfs = { git = "https://github.com/rafalh/rust-fatfs.git", rev = "87fc1ed5074a32b4e0344fcdde77359ef9e75432" }
1012
fs-err = "2.6.0"
13+
mbrman = "0.4.2"
1114
nix = "0.23.1"
1215
regex = "1.5.4"
1316
serde_json = "1.0.73"

xtask/src/disk.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use anyhow::Result;
2+
use fatfs::{Date, DateTime, FileSystem, FormatVolumeOptions, FsOptions, StdIoWrapper, Time};
3+
use mbrman::{MBRPartitionEntry, CHS, MBR};
4+
use std::io::{Cursor, Read, Write};
5+
use std::ops::Range;
6+
use std::path::Path;
7+
8+
const SECTOR_SIZE: usize = 512;
9+
10+
fn get_partition_byte_range(mbr: &MBR) -> Range<usize> {
11+
let partition_start_byte = mbr[1].starting_lba as usize * SECTOR_SIZE;
12+
let partition_num_bytes = mbr[1].sectors as usize * SECTOR_SIZE;
13+
partition_start_byte..partition_start_byte + partition_num_bytes
14+
}
15+
16+
pub fn create_mbr_test_disk(path: &Path) -> Result<()> {
17+
let num_sectors = 1234;
18+
19+
let partition_byte_range;
20+
let mut disk = vec![0; num_sectors * SECTOR_SIZE];
21+
{
22+
let mut cur = std::io::Cursor::new(&mut disk);
23+
24+
let mut mbr = MBR::new_from(&mut cur, SECTOR_SIZE as u32, [0xff; 4])?;
25+
mbr[1] = MBRPartitionEntry {
26+
boot: false,
27+
first_chs: CHS::empty(),
28+
sys: 0x06,
29+
last_chs: CHS::empty(),
30+
starting_lba: 1,
31+
sectors: mbr.disk_size - 1,
32+
};
33+
34+
partition_byte_range = get_partition_byte_range(&mbr);
35+
36+
mbr.write_into(&mut cur)?;
37+
}
38+
39+
init_fat_test_partition(&mut disk, partition_byte_range)?;
40+
41+
fs_err::write(path, &disk)?;
42+
43+
Ok(())
44+
}
45+
46+
fn init_fat_test_partition(disk: &mut [u8], partition_byte_range: Range<usize>) -> Result<()> {
47+
{
48+
let mut cursor = StdIoWrapper::from(Cursor::new(&mut disk[partition_byte_range.clone()]));
49+
fatfs::format_volume(
50+
&mut cursor,
51+
FormatVolumeOptions::new().volume_label(*b"MbrTestDisk"),
52+
)?;
53+
}
54+
55+
let cursor = Cursor::new(&mut disk[partition_byte_range]);
56+
let fs = FileSystem::new(cursor, FsOptions::new().update_accessed_date(false))?;
57+
58+
assert_eq!(
59+
fs.read_volume_label_from_root_dir().unwrap(),
60+
Some("MbrTestDisk".to_string())
61+
);
62+
63+
let root_dir = fs.root_dir();
64+
65+
let dir = root_dir.create_dir("test_dir")?;
66+
67+
let mut file = dir.create_file("test_input.txt")?;
68+
file.write_all(b"test input data")?;
69+
70+
// The datetime-setting functions have been deprecated, but are
71+
// useful here to force an exact date that can be checked in the
72+
// test.
73+
#[allow(deprecated)]
74+
{
75+
let time = Time::new(0, 0, 0, 0);
76+
file.set_created(DateTime::new(Date::new(2000, 1, 24), time));
77+
file.set_accessed(Date::new(2001, 2, 25));
78+
file.set_modified(DateTime::new(Date::new(2002, 3, 26), time));
79+
}
80+
81+
let stats = fs.stats()?;
82+
// Assert these specific numbers here since they are checked by the
83+
// test-runner too.
84+
assert_eq!(stats.total_clusters(), 1192);
85+
assert_eq!(stats.free_clusters(), 1190);
86+
87+
Ok(())
88+
}
89+
90+
pub fn check_mbr_test_disk(path: &Path) -> Result<()> {
91+
println!("Verifying test disk has been correctly modified");
92+
let mut disk = fs_err::read(path)?;
93+
94+
let partition_byte_range;
95+
{
96+
let mut cursor = Cursor::new(&disk);
97+
let mbr = MBR::read_from(&mut cursor, SECTOR_SIZE as u32)?;
98+
partition_byte_range = get_partition_byte_range(&mbr);
99+
}
100+
101+
let cursor = Cursor::new(&mut disk[partition_byte_range]);
102+
let fs = FileSystem::new(cursor, FsOptions::new().update_accessed_date(false))?;
103+
let root_dir = fs.root_dir();
104+
105+
// Check that the new file was created.
106+
let mut file = root_dir.open_file("new_test_file.txt")?;
107+
let mut bytes = Vec::new();
108+
file.read_to_end(&mut bytes)?;
109+
assert_eq!(bytes, b"test output data");
110+
111+
// Check that the original input file was deleted.
112+
let dir = root_dir.open_dir("test_dir")?;
113+
let children: Vec<_> = dir.iter().map(|e| e.unwrap().file_name()).collect();
114+
assert_eq!(children, [".", ".."]);
115+
116+
Ok(())
117+
}

xtask/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod arch;
22
mod cargo;
3+
mod disk;
34
mod opt;
45
mod qemu;
56
mod util;

0 commit comments

Comments
 (0)