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