uefi/proto/media/file/regular.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use super::{File, FileHandle, FileInternal};
4use crate::{Error, Result, Status, StatusExt};
5
6/// A `FileHandle` that is also a regular (data) file.
7///
8/// Use `FileHandle::into_type` or `RegularFile::new` to create a `RegularFile`.
9/// In addition to supporting the normal `File` operations, `RegularFile`
10/// supports direct reading and writing.
11#[repr(transparent)]
12#[derive(Debug)]
13pub struct RegularFile(FileHandle);
14
15impl RegularFile {
16 /// A special position used to seek to the end of a file with `set_position()`.
17 pub const END_OF_FILE: u64 = u64::MAX;
18
19 /// Coverts a `FileHandle` into a `RegularFile` without checking the file kind.
20 /// # Safety
21 /// This function should only be called on handles which ARE NOT directories,
22 /// doing otherwise is unsafe.
23 #[must_use]
24 pub const unsafe fn new(handle: FileHandle) -> Self {
25 Self(handle)
26 }
27
28 /// Read data from file.
29 ///
30 /// Try to read as much as possible into `buffer`. Returns the number of bytes that were
31 /// actually read.
32 ///
33 /// # Arguments
34 /// * `buffer` The target buffer of the read operation
35 ///
36 /// # Errors
37 ///
38 /// See section `EFI_FILE_PROTOCOL.Read()` in the UEFI Specification for more details.
39 ///
40 /// * [`Status::NO_MEDIA`]
41 /// * [`Status::DEVICE_ERROR`]
42 /// * [`Status::VOLUME_CORRUPTED`]
43 ///
44 /// # Quirks
45 ///
46 /// Some UEFI implementations have a bug where large reads will incorrectly
47 /// return an error. This function avoids that bug by reading in chunks of
48 /// no more than 1 MiB. This is handled internally within the function;
49 /// callers can safely pass in a buffer of any size. See
50 /// <https://github.com/rust-osdev/uefi-rs/issues/825> for more information.
51 pub fn read(&mut self, buffer: &mut [u8]) -> Result<usize> {
52 let chunk_size = 1024 * 1024;
53
54 read_chunked(buffer, chunk_size, |buf, buf_size| unsafe {
55 (self.imp().read)(self.imp(), buf_size, buf.cast())
56 })
57 }
58
59 /// Internal method for reading without chunking. This is used to implement
60 /// `Directory::read_entry`.
61 pub(super) fn read_unchunked(&mut self, buffer: &mut [u8]) -> Result<usize, Option<usize>> {
62 let mut buffer_size = buffer.len();
63 let status =
64 unsafe { (self.imp().read)(self.imp(), &mut buffer_size, buffer.as_mut_ptr().cast()) };
65
66 status.to_result_with(
67 || buffer_size,
68 |s| {
69 if s == Status::BUFFER_TOO_SMALL {
70 // `buffer_size` was updated to the required buffer size by the underlying read
71 // function.
72 Some(buffer_size)
73 } else {
74 None
75 }
76 },
77 )
78 }
79
80 /// Write data to file
81 ///
82 /// Write `buffer` to file, increment the file pointer.
83 ///
84 /// If an error occurs, returns the number of bytes that were actually written. If no error
85 /// occurred, the entire buffer is guaranteed to have been written successfully.
86 ///
87 /// # Arguments
88 /// * `buffer` Buffer to write to file
89 ///
90 /// # Errors
91 ///
92 /// See section `EFI_FILE_PROTOCOL.Write()` in the UEFI Specification for more details.
93 ///
94 /// * [`Status::NO_MEDIA`]
95 /// * [`Status::DEVICE_ERROR`]
96 /// * [`Status::VOLUME_CORRUPTED`]
97 /// * [`Status::WRITE_PROTECTED`]
98 /// * [`Status::ACCESS_DENIED`]
99 /// * [`Status::VOLUME_FULL`]
100 pub fn write(&mut self, buffer: &[u8]) -> Result<(), usize> {
101 let mut buffer_size = buffer.len();
102 unsafe { (self.imp().write)(self.imp(), &mut buffer_size, buffer.as_ptr().cast()) }
103 .to_result_with_err(|_| buffer_size)
104 }
105
106 /// Get the file's current position
107 ///
108 /// # Errors
109 ///
110 /// See section `EFI_FILE_PROTOCOL.GetPosition()` in the UEFI Specification for more details.
111 ///
112 /// * [`Status::DEVICE_ERROR`]
113 pub fn get_position(&mut self) -> Result<u64> {
114 let mut pos = 0u64;
115 unsafe { (self.imp().get_position)(self.imp(), &mut pos) }.to_result_with_val(|| pos)
116 }
117
118 /// Sets the file's current position
119 ///
120 /// Set the position of this file handle to the absolute position specified by `position`.
121 ///
122 /// Seeking past the end of the file is allowed, it will trigger file growth on the next write.
123 /// Using a position of RegularFile::END_OF_FILE will seek to the end of the file.
124 ///
125 /// # Arguments
126 /// * `position` The new absolution position of the file handle
127 ///
128 /// # Errors
129 ///
130 /// See section `EFI_FILE_PROTOCOL.SetPosition()` in the UEFI Specification for more details.
131 ///
132 /// * [`Status::DEVICE_ERROR`]
133 pub fn set_position(&mut self, position: u64) -> Result {
134 unsafe { (self.imp().set_position)(self.imp(), position) }.to_result()
135 }
136}
137
138impl File for RegularFile {
139 #[inline]
140 fn handle(&mut self) -> &mut FileHandle {
141 &mut self.0
142 }
143
144 fn is_regular_file(&self) -> Result<bool> {
145 Ok(true)
146 }
147
148 fn is_directory(&self) -> Result<bool> {
149 Ok(false)
150 }
151}
152
153/// Read data into `buffer` in chunks of `chunk_size`. Reading is done by
154/// calling `read`, which takes a pointer to a byte buffer and the buffer's
155/// size.
156///
157/// See [`RegularFile::read`] for details of why reading in chunks is needed.
158///
159/// This separate function exists for easier unit testing.
160fn read_chunked<F>(buffer: &mut [u8], chunk_size: usize, mut read: F) -> Result<usize>
161where
162 F: FnMut(*mut u8, &mut usize) -> Status,
163{
164 let mut remaining_size = buffer.len();
165 let mut total_read_size = 0;
166 let mut output_ptr = buffer.as_mut_ptr();
167
168 while remaining_size > 0 {
169 let requested_read_size = remaining_size.min(chunk_size);
170
171 let mut read_size = requested_read_size;
172 let status = read(output_ptr, &mut read_size);
173
174 if status.is_success() {
175 total_read_size += read_size;
176 remaining_size -= read_size;
177 output_ptr = unsafe { output_ptr.add(read_size) };
178
179 // Exit the loop if there's nothing left to read.
180 if read_size < requested_read_size {
181 break;
182 }
183 } else {
184 return Err(Error::new(status, ()));
185 }
186 }
187
188 Ok(total_read_size)
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use alloc::rc::Rc;
195 use alloc::vec;
196 use alloc::vec::Vec;
197 use core::cell::RefCell;
198
199 #[derive(Default)]
200 struct TestFile {
201 // Use `Rc<RefCell>` so that we can modify via an immutable ref, makes
202 // the test simpler to implement.
203 data: Rc<RefCell<Vec<u8>>>,
204 offset: Rc<RefCell<usize>>,
205 }
206
207 impl TestFile {
208 fn read(&self, buffer: *mut u8, buffer_size: &mut usize) -> Status {
209 let mut offset = self.offset.borrow_mut();
210 let data = self.data.borrow();
211
212 let remaining_data_size = data.len() - *offset;
213 let size_to_read = remaining_data_size.min(*buffer_size);
214 unsafe { buffer.copy_from(data.as_ptr().add(*offset), size_to_read) };
215 *offset += size_to_read;
216 *buffer_size = size_to_read;
217 Status::SUCCESS
218 }
219
220 fn reset(&self) {
221 *self.data.borrow_mut() = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
222 *self.offset.borrow_mut() = 0;
223 }
224 }
225
226 /// Test reading a regular file.
227 #[test]
228 fn test_file_read_chunked() {
229 let file = TestFile::default();
230 let read = |buf, buf_size: &mut usize| file.read(buf, buf_size);
231
232 // Chunk size equal to the data size.
233 file.reset();
234 let mut buffer = [0; 10];
235 assert_eq!(read_chunked(&mut buffer, 10, read), Ok(10));
236 assert_eq!(buffer.as_slice(), *file.data.borrow());
237
238 // Chunk size smaller than the data size.
239 file.reset();
240 let mut buffer = [0; 10];
241 assert_eq!(read_chunked(&mut buffer, 2, read), Ok(10));
242 assert_eq!(buffer.as_slice(), *file.data.borrow());
243
244 // Chunk size bigger than the data size.
245 file.reset();
246 let mut buffer = [0; 10];
247 assert_eq!(read_chunked(&mut buffer, 20, read), Ok(10));
248 assert_eq!(buffer.as_slice(), *file.data.borrow());
249
250 // Buffer smaller than the full file.
251 file.reset();
252 let mut buffer = [0; 4];
253 assert_eq!(read_chunked(&mut buffer, 10, read), Ok(4));
254 assert_eq!(buffer.as_slice(), [1, 2, 3, 4]);
255
256 // Buffer bigger than the full file.
257 file.reset();
258 let mut buffer = [0; 20];
259 assert_eq!(read_chunked(&mut buffer, 10, read), Ok(10));
260 assert_eq!(
261 buffer,
262 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
263 );
264
265 // Empty buffer.
266 file.reset();
267 let mut buffer = [];
268 assert_eq!(read_chunked(&mut buffer, 10, read), Ok(0));
269 assert_eq!(buffer, []);
270
271 // Empty file.
272 file.reset();
273 file.data.borrow_mut().clear();
274 let mut buffer = [0; 10];
275 assert_eq!(read_chunked(&mut buffer, 10, read), Ok(0));
276 assert_eq!(buffer, [0; 10]);
277 }
278}