Skip to content

Commit 553fb24

Browse files
authored
Merge pull request #899 from nicholasbishop/bishop-improve-copy
Rework `FileSystem::copy` to operate on 1MiB chunks
2 parents a72a5cd + a6ab883 commit 553fb24

File tree

5 files changed

+209
-20
lines changed

5 files changed

+209
-20
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- `Parity` and `StopBits` are now a newtype-enums instead of Rust enums. Their
1313
members now have upper-case names.
1414
- `FileSystem::try_exists` now returns `FileSystemResult<bool>`.
15+
- `FileSystem::copy` is now more efficient for large files.
1516

1617
## uefi-macros - [Unreleased]
1718

uefi-test-runner/src/fs/mod.rs

Lines changed: 111 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,6 @@ pub fn test(sfs: ScopedProtocol<SimpleFileSystem>) -> Result<(), fs::Error> {
2424
let read = String::from_utf8(read).expect("Should be valid utf8");
2525
assert_eq!(read.as_str(), data_to_write);
2626

27-
// test copy from non-existent file: does the error type work as expected?
28-
let err = fs.copy(cstr16!("not_found"), cstr16!("abc"));
29-
let expected_err = fs::Error::Io(IoError {
30-
path: PathBuf::from(cstr16!("not_found")),
31-
context: IoErrorContext::OpenError,
32-
uefi_error: uefi::Error::new(Status::NOT_FOUND, ()),
33-
});
34-
assert_eq!(err, Err(expected_err));
35-
3627
// test rename file + path buf replaces / with \
3728
fs.rename(
3829
PathBuf::from(cstr16!("/foo_dir/foo_cpy")),
@@ -64,5 +55,116 @@ pub fn test(sfs: ScopedProtocol<SimpleFileSystem>) -> Result<(), fs::Error> {
6455
// file should not be available after remove all
6556
assert!(!fs.try_exists(cstr16!("foo_dir\\1"))?);
6657

58+
test_copy_error(&mut fs)?;
59+
test_copy_success(&mut fs)?;
60+
test_copy_success_chunks(&mut fs)?;
61+
62+
Ok(())
63+
}
64+
65+
fn test_copy_error(fs: &mut FileSystem) -> Result<(), fs::Error> {
66+
let file1_path = cstr16!("file1");
67+
let dir_path = cstr16!("dir");
68+
69+
// Test copy when the destination exists but the source does not. Verify
70+
// that the destination is not deleted or altered.
71+
fs.write(file1_path, "data1")?;
72+
assert_eq!(
73+
fs.copy(cstr16!("src"), file1_path),
74+
Err(fs::Error::Io(IoError {
75+
path: PathBuf::from(cstr16!("src")),
76+
context: IoErrorContext::OpenError,
77+
uefi_error: uefi::Error::new(Status::NOT_FOUND, ()),
78+
}))
79+
);
80+
assert_eq!(fs.read(file1_path)?, b"data1");
81+
82+
// Test copy when the source is a directory. Verify that the destination is
83+
// not deleted or altered.
84+
fs.create_dir(dir_path)?;
85+
assert_eq!(
86+
fs.copy(dir_path, file1_path),
87+
Err(fs::Error::Io(IoError {
88+
path: PathBuf::from(dir_path),
89+
context: IoErrorContext::NotAFile,
90+
uefi_error: uefi::Error::new(Status::INVALID_PARAMETER, ()),
91+
}))
92+
);
93+
assert_eq!(fs.read(file1_path)?, b"data1");
94+
95+
// Test copy when the source is valid but the destination is a
96+
// directory. Verify that the directory is not deleted.
97+
assert_eq!(
98+
fs.copy(file1_path, dir_path),
99+
Err(fs::Error::Io(IoError {
100+
path: PathBuf::from(dir_path),
101+
context: IoErrorContext::OpenError,
102+
uefi_error: uefi::Error::new(Status::INVALID_PARAMETER, ()),
103+
}))
104+
);
105+
assert_eq!(fs.try_exists(dir_path), Ok(true));
106+
107+
// Clean up temporary files.
108+
fs.remove_file(file1_path)?;
109+
fs.remove_dir(dir_path)?;
110+
111+
Ok(())
112+
}
113+
114+
fn test_copy_success(fs: &mut FileSystem) -> Result<(), fs::Error> {
115+
let file1_path = cstr16!("file1");
116+
let file2_path = cstr16!("file2");
117+
118+
// Test a successful copy where the destination does not already exist.
119+
fs.write(file1_path, "data1")?;
120+
assert_eq!(fs.try_exists(file2_path), Ok(false));
121+
fs.copy(file1_path, file2_path)?;
122+
assert_eq!(fs.read(file1_path)?, b"data1");
123+
assert_eq!(fs.read(file2_path)?, b"data1");
124+
125+
// Test that when copying a smaller file over a larger file, the file is
126+
// properly truncated. Also check that the original file is unchanged.
127+
fs.write(file2_path, "some long data")?;
128+
fs.copy(file1_path, file2_path)?;
129+
assert_eq!(fs.read(file1_path)?, b"data1");
130+
assert_eq!(fs.read(file2_path)?, b"data1");
131+
132+
// Clean up temporary files.
133+
fs.remove_file(file1_path)?;
134+
fs.remove_file(file2_path)?;
135+
136+
Ok(())
137+
}
138+
139+
fn test_copy_success_chunks(fs: &mut FileSystem) -> Result<(), fs::Error> {
140+
let file1_path = cstr16!("file1");
141+
let file2_path = cstr16!("file2");
142+
143+
// Test copy of a large file, where the file's size is an even multiple of
144+
// the 1MiB chunk size.
145+
let chunk_size = 1024 * 1024;
146+
let mut big_buf = Vec::with_capacity(5 * chunk_size);
147+
for i in 0..(4 * chunk_size) {
148+
let byte = u8::try_from(i % 255).unwrap();
149+
big_buf.push(byte);
150+
}
151+
fs.write(file1_path, &big_buf)?;
152+
fs.copy(file1_path, file2_path)?;
153+
assert_eq!(fs.read(file1_path)?, big_buf);
154+
assert_eq!(fs.read(file2_path)?, big_buf);
155+
156+
// Test copy of a large file, where the file's size is not an even multiple
157+
// of the 1MiB chunk size.
158+
big_buf.extend(b"some extra data");
159+
assert_ne!(big_buf.len() % chunk_size, 0);
160+
fs.write(file1_path, &big_buf)?;
161+
fs.copy(file1_path, file2_path)?;
162+
assert_eq!(fs.read(file1_path)?, big_buf);
163+
assert_eq!(fs.read(file2_path)?, big_buf);
164+
165+
// Clean up temporary files.
166+
fs.remove_file(file1_path)?;
167+
fs.remove_file(file2_path)?;
168+
67169
Ok(())
68170
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ fn test_existing_file(directory: &mut Directory) {
127127
let mut info_buffer = vec![0; 128];
128128
let info = file.get_info::<FileInfo>(&mut info_buffer).unwrap();
129129
assert_eq!(info.file_size(), 15);
130-
assert_eq!(info.physical_size(), 512);
130+
assert_eq!(info.physical_size(), 1024);
131131
let tp = TimeParams {
132132
year: 2000,
133133
month: 1,
@@ -355,7 +355,7 @@ fn test_partition_info(bt: &BootServices, disk_handle: Handle) {
355355

356356
assert_eq!(mbr.boot_indicator, 0);
357357
assert_eq!({ mbr.starting_lba }, 1);
358-
assert_eq!({ mbr.size_in_lba }, 1233);
358+
assert_eq!({ mbr.size_in_lba }, 20479);
359359
assert_eq!({ mbr.starting_chs }, [0, 0, 0]);
360360
assert_eq!(mbr.ending_chs, [0, 0, 0]);
361361
assert_eq!(mbr.os_type, MbrOsType(6));
@@ -412,9 +412,9 @@ pub fn test(bt: &BootServices) {
412412
.unwrap();
413413

414414
assert!(!fs_info.read_only());
415-
assert_eq!(fs_info.volume_size(), 512 * 1192);
416-
assert_eq!(fs_info.free_space(), 512 * 1190);
417-
assert_eq!(fs_info.block_size(), 512);
415+
assert_eq!(fs_info.volume_size(), 1024 * 10183);
416+
assert_eq!(fs_info.free_space(), 1024 * 10181);
417+
assert_eq!(fs_info.block_size(), 1024);
418418
assert_eq!(fs_info.volume_label().to_string(), "MbrTestDisk");
419419

420420
// Check that `get_boxed_info` returns the same info.

uefi/src/fs/file_system/fs.rs

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,93 @@ impl<'a> FileSystem<'a> {
5252
src_path: impl AsRef<Path>,
5353
dest_path: impl AsRef<Path>,
5454
) -> FileSystemResult<()> {
55-
let read = self.read(src_path)?;
56-
self.write(dest_path, read)
55+
let src_path = src_path.as_ref();
56+
let dest_path = dest_path.as_ref();
57+
58+
// Open the source file for reading.
59+
let mut src = self
60+
.open(src_path, UefiFileMode::Read, false)?
61+
.into_regular_file()
62+
.ok_or(Error::Io(IoError {
63+
path: src_path.to_path_buf(),
64+
context: IoErrorContext::NotAFile,
65+
uefi_error: Status::INVALID_PARAMETER.into(),
66+
}))?;
67+
68+
// Get the source file's size in bytes.
69+
let src_size = {
70+
let src_info = src.get_boxed_info::<UefiFileInfo>().map_err(|err| {
71+
Error::Io(IoError {
72+
path: src_path.to_path_buf(),
73+
context: IoErrorContext::Metadata,
74+
uefi_error: err,
75+
})
76+
})?;
77+
src_info.file_size()
78+
};
79+
80+
// Try to delete the destination file in case it already exists. Allow
81+
// this to fail, since it might not exist. Or it might exist, but be a
82+
// directory, in which case the error will be caught when trying to
83+
// create the file.
84+
let _ = self.remove_file(dest_path);
85+
86+
// Create and open the destination file.
87+
let mut dest = self
88+
.open(dest_path, UefiFileMode::CreateReadWrite, false)?
89+
.into_regular_file()
90+
.ok_or(Error::Io(IoError {
91+
path: dest_path.to_path_buf(),
92+
context: IoErrorContext::OpenError,
93+
uefi_error: Status::INVALID_PARAMETER.into(),
94+
}))?;
95+
96+
// 1 MiB copy buffer.
97+
let mut chunk = vec![0; 1024 * 1024];
98+
99+
// Read chunks from the source file and write to the destination file.
100+
let mut remaining_size = src_size;
101+
while remaining_size > 0 {
102+
// Read one chunk.
103+
let num_bytes_read = src.read(&mut chunk).map_err(|err| {
104+
Error::Io(IoError {
105+
path: src_path.to_path_buf(),
106+
context: IoErrorContext::ReadFailure,
107+
uefi_error: err.to_err_without_payload(),
108+
})
109+
})?;
110+
111+
// If the read returned no bytes, but `remaining_size > 0`, return
112+
// an error.
113+
if num_bytes_read == 0 {
114+
return Err(Error::Io(IoError {
115+
path: src_path.to_path_buf(),
116+
context: IoErrorContext::ReadFailure,
117+
uefi_error: Status::ABORTED.into(),
118+
}));
119+
}
120+
121+
// Copy the bytes read out to the destination file.
122+
dest.write(&chunk[..num_bytes_read]).map_err(|err| {
123+
Error::Io(IoError {
124+
path: dest_path.to_path_buf(),
125+
context: IoErrorContext::WriteFailure,
126+
uefi_error: err.to_err_without_payload(),
127+
})
128+
})?;
129+
130+
remaining_size -= u64::try_from(num_bytes_read).unwrap();
131+
}
132+
133+
dest.flush().map_err(|err| {
134+
Error::Io(IoError {
135+
path: dest_path.to_path_buf(),
136+
context: IoErrorContext::FlushFailure,
137+
uefi_error: err,
138+
})
139+
})?;
140+
141+
Ok(())
57142
}
58143

59144
/// Creates a new, empty directory at the provided path

xtask/src/disk.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ fn get_partition_byte_range(mbr: &MBR) -> Range<usize> {
1414
}
1515

1616
pub fn create_mbr_test_disk(path: &Path) -> Result<()> {
17-
let num_sectors = 1234;
17+
// 10 MiB.
18+
let size_in_bytes = 10 * 1024 * 1024;
1819

1920
let partition_byte_range;
20-
let mut disk = vec![0; num_sectors * SECTOR_SIZE];
21+
let mut disk = vec![0; size_in_bytes];
2122
{
2223
let mut cur = std::io::Cursor::new(&mut disk);
2324

@@ -104,8 +105,8 @@ fn init_fat_test_partition(disk: &mut [u8], partition_byte_range: Range<usize>)
104105
let stats = fs.stats()?;
105106
// Assert these specific numbers here since they are checked by the
106107
// test-runner too.
107-
assert_eq!(stats.total_clusters(), 1192);
108-
assert_eq!(stats.free_clusters(), 1190);
108+
assert_eq!(stats.total_clusters(), 10183);
109+
assert_eq!(stats.free_clusters(), 10181);
109110

110111
Ok(())
111112
}

0 commit comments

Comments
 (0)