1 //! This module provides the `FileHandle` structure as well as the more specific `RegularFile` and
2 //! `Directory` structures. This module also provides the `File` trait for opening, querying,
3 //! creating, reading, and writing files.
4 //!
5 //! Usually a file system implementation will return a "root" directory, representing
6 //! `/` on that volume. With that directory, it is possible to enumerate and open
7 //! all the other files on that volume.
8 
9 mod dir;
10 mod info;
11 mod regular;
12 
13 use crate::{CStr16, Result, Status, StatusExt};
14 use core::ffi::c_void;
15 use core::fmt::Debug;
16 use core::{mem, ptr};
17 use uefi_raw::protocol::file_system::FileProtocolV1;
18 
19 #[cfg(all(feature = "unstable", feature = "alloc"))]
20 use {alloc::alloc::Global, core::alloc::Allocator};
21 
22 #[cfg(feature = "alloc")]
23 use {crate::mem::make_boxed, alloc::boxed::Box};
24 
25 pub use dir::Directory;
26 pub use info::{
27     FileInfo, FileInfoCreationError, FileProtocolInfo, FileSystemInfo, FileSystemVolumeLabel,
28     FromUefi,
29 };
30 pub use regular::RegularFile;
31 pub use uefi_raw::protocol::file_system::FileAttribute;
32 
33 /// Common interface to `FileHandle`, `RegularFile`, and `Directory`.
34 ///
35 /// `File` contains all functionality that is safe to perform on any type of
36 /// file handle.
37 pub trait File: Sized {
38     /// Access the underlying file handle.
39     #[doc(hidden)]
handle(&mut self) -> &mut FileHandle40     fn handle(&mut self) -> &mut FileHandle;
41 
42     /// Try to open a file relative to this file.
43     ///
44     /// # Arguments
45     /// * `filename`    Path of file to open, relative to this file
46     /// * `open_mode`   The mode to open the file with
47     /// * `attributes`  Only valid when `FILE_MODE_CREATE` is used as a mode
48     ///
49     /// # Errors
50     ///
51     /// See section `EFI_FILE_PROTOCOL.Open()` in the UEFI Specification for more details.
52     /// Note that [`INVALID_PARAMETER`] is not listed in the specification as one of the
53     /// errors returned by this function, but some implementations (such as EDK2) perform
54     /// additional validation and may return that status for invalid inputs.
55     ///
56     /// [`INVALID_PARAMETER`]: uefi::Status::INVALID_PARAMETER
57     ///
58     /// * [`uefi::Status::INVALID_PARAMETER`]
59     /// * [`uefi::Status::NOT_FOUND`]
60     /// * [`uefi::Status::NO_MEDIA`]
61     /// * [`uefi::Status::MEDIA_CHANGED`]
62     /// * [`uefi::Status::DEVICE_ERROR`]
63     /// * [`uefi::Status::VOLUME_CORRUPTED`]
64     /// * [`uefi::Status::WRITE_PROTECTED`]
65     /// * [`uefi::Status::ACCESS_DENIED`]
66     /// * [`uefi::Status::OUT_OF_RESOURCES`]
67     /// * [`uefi::Status::VOLUME_FULL`]
open( &mut self, filename: &CStr16, open_mode: FileMode, attributes: FileAttribute, ) -> Result<FileHandle>68     fn open(
69         &mut self,
70         filename: &CStr16,
71         open_mode: FileMode,
72         attributes: FileAttribute,
73     ) -> Result<FileHandle> {
74         let mut ptr = ptr::null_mut();
75 
76         unsafe {
77             (self.imp().open)(
78                 self.imp(),
79                 &mut ptr,
80                 filename.as_ptr().cast(),
81                 uefi_raw::protocol::file_system::FileMode::from_bits_truncate(open_mode as u64),
82                 attributes,
83             )
84         }
85         .to_result_with_val(|| unsafe { FileHandle::new(ptr) })
86     }
87 
88     /// Close this file handle. Same as dropping this structure.
close(self)89     fn close(self) {}
90 
91     /// Closes and deletes this file
92     ///
93     /// # Warnings
94     ///
95     /// See section `EFI_FILE_PROTOCOL.Delete()` in the UEFI Specification for more details.
96     ///
97     /// * [`uefi::Status::WARN_DELETE_FAILURE`]
delete(mut self) -> Result98     fn delete(mut self) -> Result {
99         let result = unsafe { (self.imp().delete)(self.imp()) }.to_result();
100         mem::forget(self);
101         result
102     }
103 
104     /// Queries some information about a file
105     ///
106     /// The information will be written into a user-provided buffer.
107     /// If the buffer is too small, the required buffer size will be returned as part of the error.
108     ///
109     /// The buffer must be aligned on an `<Info as Align>::alignment()` boundary.
110     ///
111     /// # Arguments
112     /// * `buffer`  Buffer that the information should be written into
113     ///
114     /// # Errors
115     ///
116     /// See section `EFI_FILE_PROTOCOL.GetInfo()` in the UEFI Specification for more details.
117     ///
118     /// * [`uefi::Status::UNSUPPORTED`]
119     /// * [`uefi::Status::NO_MEDIA`]
120     /// * [`uefi::Status::DEVICE_ERROR`]
121     /// * [`uefi::Status::VOLUME_CORRUPTED`]
122     /// * [`uefi::Status::BUFFER_TOO_SMALL`]
get_info<'buf, Info: FileProtocolInfo + ?Sized>( &mut self, buffer: &'buf mut [u8], ) -> Result<&'buf mut Info, Option<usize>>123     fn get_info<'buf, Info: FileProtocolInfo + ?Sized>(
124         &mut self,
125         buffer: &'buf mut [u8],
126     ) -> Result<&'buf mut Info, Option<usize>> {
127         let mut buffer_size = buffer.len();
128         Info::assert_aligned(buffer);
129         unsafe {
130             (self.imp().get_info)(
131                 self.imp(),
132                 &Info::GUID,
133                 &mut buffer_size,
134                 buffer.as_mut_ptr().cast(),
135             )
136         }
137         .to_result_with(
138             || unsafe { Info::from_uefi(buffer.as_mut_ptr().cast::<c_void>()) },
139             |s| {
140                 if s == Status::BUFFER_TOO_SMALL {
141                     Some(buffer_size)
142                 } else {
143                     None
144                 }
145             },
146         )
147     }
148 
149     /// Sets some information about a file
150     ///
151     /// There are various restrictions on the information that may be modified using this method.
152     /// The simplest one is that it is usually not possible to call it on read-only media. Further
153     /// restrictions specific to a given information type are described in the corresponding
154     /// `FileProtocolInfo` type documentation.
155     ///
156     /// # Arguments
157     /// * `info`  Info that should be set for the file
158     ///
159     /// # Errors
160     ///
161     /// See section `EFI_FILE_PROTOCOL.SetInfo()` in the UEFI Specification for more details.
162     ///
163     /// * [`uefi::Status::UNSUPPORTED`]
164     /// * [`uefi::Status::NO_MEDIA`]
165     /// * [`uefi::Status::DEVICE_ERROR`]
166     /// * [`uefi::Status::VOLUME_CORRUPTED`]
167     /// * [`uefi::Status::WRITE_PROTECTED`]
168     /// * [`uefi::Status::ACCESS_DENIED`]
169     /// * [`uefi::Status::VOLUME_FULL`]
170     /// * [`uefi::Status::BAD_BUFFER_SIZE`]
set_info<Info: FileProtocolInfo + ?Sized>(&mut self, info: &Info) -> Result171     fn set_info<Info: FileProtocolInfo + ?Sized>(&mut self, info: &Info) -> Result {
172         let info_ptr = (info as *const Info).cast::<c_void>();
173         let info_size = mem::size_of_val(info);
174         unsafe { (self.imp().set_info)(self.imp(), &Info::GUID, info_size, info_ptr).to_result() }
175     }
176 
177     /// Flushes all modified data associated with the file handle to the device
178     ///
179     /// # Errors
180     ///
181     /// See section `EFI_FILE_PROTOCOL.Flush()` in the UEFI Specification for more details.
182     ///
183     /// * [`uefi::Status::NO_MEDIA`]
184     /// * [`uefi::Status::DEVICE_ERROR`]
185     /// * [`uefi::Status::VOLUME_CORRUPTED`]
186     /// * [`uefi::Status::WRITE_PROTECTED`]
187     /// * [`uefi::Status::ACCESS_DENIED`]
188     /// * [`uefi::Status::VOLUME_FULL`]
flush(&mut self) -> Result189     fn flush(&mut self) -> Result {
190         unsafe { (self.imp().flush)(self.imp()) }.to_result()
191     }
192 
193     /// Read the dynamically allocated info for a file.
194     #[cfg(feature = "alloc")]
get_boxed_info<Info: FileProtocolInfo + ?Sized + Debug>(&mut self) -> Result<Box<Info>>195     fn get_boxed_info<Info: FileProtocolInfo + ?Sized + Debug>(&mut self) -> Result<Box<Info>> {
196         let fetch_data_fn = |buf| self.get_info::<Info>(buf);
197         #[cfg(not(feature = "unstable"))]
198         let file_info = make_boxed::<Info, _>(fetch_data_fn)?;
199         #[cfg(feature = "unstable")]
200         let file_info = make_boxed::<Info, _, _>(fetch_data_fn, Global)?;
201         Ok(file_info)
202     }
203 
204     /// Read the dynamically allocated info for a file.
205     #[cfg(all(feature = "unstable", feature = "alloc"))]
get_boxed_info_in<Info: FileProtocolInfo + ?Sized + Debug, A: Allocator>( &mut self, allocator: A, ) -> Result<Box<Info>>206     fn get_boxed_info_in<Info: FileProtocolInfo + ?Sized + Debug, A: Allocator>(
207         &mut self,
208         allocator: A,
209     ) -> Result<Box<Info>> {
210         let fetch_data_fn = |buf| self.get_info::<Info>(buf);
211         let file_info = make_boxed::<Info, _, A>(fetch_data_fn, allocator)?;
212         Ok(file_info)
213     }
214 
215     /// Returns if the underlying file is a regular file.
216     /// The result is an error if the underlying file was already closed or deleted.
217     ///
218     /// UEFI file system protocol only knows "regular files" and "directories".
is_regular_file(&self) -> Result<bool>219     fn is_regular_file(&self) -> Result<bool>;
220 
221     /// Returns if the underlying file is a directory.
222     /// The result is an error if the underlying file was already closed or deleted.
223     ///
224     /// UEFI file system protocol only knows "regular files" and "directories".
is_directory(&self) -> Result<bool>225     fn is_directory(&self) -> Result<bool>;
226 }
227 
228 // Internal File helper methods to access the function pointer table.
229 trait FileInternal: File {
imp(&mut self) -> &mut FileProtocolV1230     fn imp(&mut self) -> &mut FileProtocolV1 {
231         unsafe { &mut *self.handle().0 }
232     }
233 }
234 
235 impl<T: File> FileInternal for T {}
236 
237 /// An opaque handle to some contiguous block of data on a volume.
238 ///
239 /// A `FileHandle` is just a wrapper around a UEFI file handle. Under the hood, it can either be a
240 /// `RegularFile` or a `Directory`; use the `into_type()` or the unsafe
241 /// `{RegularFile, Directory}::new()` methods to perform the conversion.
242 ///
243 /// Dropping this structure will result in the file handle being closed.
244 #[repr(transparent)]
245 #[derive(Debug)]
246 pub struct FileHandle(*mut FileProtocolV1);
247 
248 impl FileHandle {
new(ptr: *mut FileProtocolV1) -> Self249     pub(super) const unsafe fn new(ptr: *mut FileProtocolV1) -> Self {
250         Self(ptr)
251     }
252 
253     /// Converts `File` into a more specific subtype based on if it is a
254     /// directory or not. Wrapper around [Self::is_regular_file].
into_type(self) -> Result<FileType>255     pub fn into_type(self) -> Result<FileType> {
256         use FileType::*;
257 
258         self.is_regular_file().map(|is_file| {
259             if is_file {
260                 unsafe { Regular(RegularFile::new(self)) }
261             } else {
262                 unsafe { Dir(Directory::new(self)) }
263             }
264         })
265     }
266 
267     /// If the handle represents a directory, convert it into a
268     /// [`Directory`]. Otherwise returns `None`.
269     #[must_use]
into_directory(self) -> Option<Directory>270     pub fn into_directory(self) -> Option<Directory> {
271         if let Ok(FileType::Dir(dir)) = self.into_type() {
272             Some(dir)
273         } else {
274             None
275         }
276     }
277 
278     /// If the handle represents a regular file, convert it into a
279     /// [`RegularFile`]. Otherwise returns `None`.
280     #[must_use]
into_regular_file(self) -> Option<RegularFile>281     pub fn into_regular_file(self) -> Option<RegularFile> {
282         if let Ok(FileType::Regular(regular)) = self.into_type() {
283             Some(regular)
284         } else {
285             None
286         }
287     }
288 }
289 
290 impl File for FileHandle {
291     #[inline]
handle(&mut self) -> &mut FileHandle292     fn handle(&mut self) -> &mut FileHandle {
293         self
294     }
295 
is_regular_file(&self) -> Result<bool>296     fn is_regular_file(&self) -> Result<bool> {
297         let this = unsafe { self.0.as_mut().unwrap() };
298 
299         // - get_position fails with EFI_UNSUPPORTED on directories
300         // - result is an error if the underlying file was already closed or deleted.
301         let mut pos = 0;
302         match unsafe { (this.get_position)(this, &mut pos) } {
303             Status::SUCCESS => Ok(true),
304             Status::UNSUPPORTED => Ok(false),
305             s => Err(s.into()),
306         }
307     }
308 
is_directory(&self) -> Result<bool>309     fn is_directory(&self) -> Result<bool> {
310         self.is_regular_file().map(|b| !b)
311     }
312 }
313 
314 impl Drop for FileHandle {
drop(&mut self)315     fn drop(&mut self) {
316         let result: Result = unsafe { (self.imp().close)(self.imp()) }.to_result();
317         // The spec says this always succeeds.
318         result.expect("Failed to close file");
319     }
320 }
321 
322 /// Disambiguate the file type. Returned by `File::into_type()`.
323 #[derive(Debug)]
324 pub enum FileType {
325     /// The file was a regular (data) file.
326     Regular(RegularFile),
327     /// The file was a directory.
328     Dir(Directory),
329 }
330 
331 /// Usage flags describing what is possible to do with the file.
332 ///
333 /// SAFETY: Using a repr(C) enum is safe here because this type is only sent to
334 ///         the UEFI implementation, and never received from it.
335 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
336 #[repr(u64)]
337 pub enum FileMode {
338     /// The file can be read from
339     Read = 1,
340 
341     /// The file can be read from and written to
342     ReadWrite = 2 | 1,
343 
344     /// The file can be read, written, and will be created if it does not exist
345     CreateReadWrite = (1 << 63) | 2 | 1,
346 }
347 
348 #[cfg(test)]
349 mod tests {
350     use super::*;
351     use crate::runtime::Time;
352     use crate::{CString16, Guid, Identify};
353     use ::alloc::vec;
354     use uefi_raw::protocol::file_system::FileProtocolRevision;
355 
356     // Test `get_boxed_info` by setting up a fake file, which is mostly
357     // just function pointers. Most of the functions can be empty, only
358     // get_info is actually implemented to return useful data.
359     #[test]
test_get_boxed_info()360     fn test_get_boxed_info() {
361         let mut file_impl = FileProtocolV1 {
362             revision: FileProtocolRevision::REVISION_1,
363             open: stub_open,
364             close: stub_close,
365             delete: stub_delete,
366             read: stub_read,
367             write: stub_write,
368             get_position: stub_get_position,
369             set_position: stub_set_position,
370             get_info: stub_get_info,
371             set_info: stub_set_info,
372             flush: stub_flush,
373         };
374         let file_handle = FileHandle(&mut file_impl);
375 
376         let mut file = unsafe { RegularFile::new(file_handle) };
377         let info = file.get_boxed_info::<FileInfo>().unwrap();
378         assert_eq!(info.file_size(), 123);
379         assert_eq!(info.file_name(), CString16::try_from("test_file").unwrap());
380     }
381 
stub_get_info( _this: *mut FileProtocolV1, information_type: *const Guid, buffer_size: *mut usize, buffer: *mut c_void, ) -> Status382     unsafe extern "efiapi" fn stub_get_info(
383         _this: *mut FileProtocolV1,
384         information_type: *const Guid,
385         buffer_size: *mut usize,
386         buffer: *mut c_void,
387     ) -> Status {
388         assert_eq!(unsafe { *information_type }, FileInfo::GUID);
389 
390         // Use a temporary buffer to get some file info, then copy that
391         // data to the output buffer.
392         let mut tmp = vec![0; 128];
393         let file_size = 123;
394         let physical_size = 456;
395         let time = Time::invalid();
396         let info = FileInfo::new(
397             &mut tmp,
398             file_size,
399             physical_size,
400             time,
401             time,
402             time,
403             FileAttribute::empty(),
404             &CString16::try_from("test_file").unwrap(),
405         )
406         .unwrap();
407         let required_size = mem::size_of_val(info);
408         if *buffer_size < required_size {
409             *buffer_size = required_size;
410             Status::BUFFER_TOO_SMALL
411         } else {
412             unsafe {
413                 ptr::copy_nonoverlapping((info as *const FileInfo).cast(), buffer, required_size);
414             }
415             *buffer_size = required_size;
416             Status::SUCCESS
417         }
418     }
419 
stub_open( _this: *mut FileProtocolV1, _new_handle: *mut *mut FileProtocolV1, _filename: *const uefi_raw::Char16, _open_mode: uefi_raw::protocol::file_system::FileMode, _attributes: FileAttribute, ) -> Status420     extern "efiapi" fn stub_open(
421         _this: *mut FileProtocolV1,
422         _new_handle: *mut *mut FileProtocolV1,
423         _filename: *const uefi_raw::Char16,
424         _open_mode: uefi_raw::protocol::file_system::FileMode,
425         _attributes: FileAttribute,
426     ) -> Status {
427         Status::UNSUPPORTED
428     }
429 
stub_close(_this: *mut FileProtocolV1) -> Status430     extern "efiapi" fn stub_close(_this: *mut FileProtocolV1) -> Status {
431         Status::SUCCESS
432     }
433 
stub_delete(_this: *mut FileProtocolV1) -> Status434     extern "efiapi" fn stub_delete(_this: *mut FileProtocolV1) -> Status {
435         Status::UNSUPPORTED
436     }
437 
stub_read( _this: *mut FileProtocolV1, _buffer_size: *mut usize, _buffer: *mut c_void, ) -> Status438     extern "efiapi" fn stub_read(
439         _this: *mut FileProtocolV1,
440         _buffer_size: *mut usize,
441         _buffer: *mut c_void,
442     ) -> Status {
443         Status::UNSUPPORTED
444     }
445 
stub_write( _this: *mut FileProtocolV1, _buffer_size: *mut usize, _buffer: *const c_void, ) -> Status446     extern "efiapi" fn stub_write(
447         _this: *mut FileProtocolV1,
448         _buffer_size: *mut usize,
449         _buffer: *const c_void,
450     ) -> Status {
451         Status::UNSUPPORTED
452     }
453 
stub_get_position( _this: *const FileProtocolV1, _position: *mut u64, ) -> Status454     extern "efiapi" fn stub_get_position(
455         _this: *const FileProtocolV1,
456         _position: *mut u64,
457     ) -> Status {
458         Status::UNSUPPORTED
459     }
460 
stub_set_position(_this: *mut FileProtocolV1, _position: u64) -> Status461     extern "efiapi" fn stub_set_position(_this: *mut FileProtocolV1, _position: u64) -> Status {
462         Status::UNSUPPORTED
463     }
464 
stub_set_info( _this: *mut FileProtocolV1, _information_type: *const Guid, _buffer_size: usize, _buffer: *const c_void, ) -> Status465     extern "efiapi" fn stub_set_info(
466         _this: *mut FileProtocolV1,
467         _information_type: *const Guid,
468         _buffer_size: usize,
469         _buffer: *const c_void,
470     ) -> Status {
471         Status::UNSUPPORTED
472     }
473 
stub_flush(_this: *mut FileProtocolV1) -> Status474     extern "efiapi" fn stub_flush(_this: *mut FileProtocolV1) -> Status {
475         Status::UNSUPPORTED
476     }
477 }
478