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