Skip to main content

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}