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