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, Char16, Guid, Result, Status}; |
14 | use bitflags::bitflags; |
15 | use core::ffi::c_void; |
16 | use core::fmt::Debug; |
17 | use core::mem; |
18 | use core::ptr; |
19 | #[cfg (all(feature = "unstable" , feature = "alloc" ))] |
20 | use {alloc::alloc::Global, core::alloc::Allocator}; |
21 | #[cfg (feature = "alloc" )] |
22 | use {alloc::boxed::Box, uefi::mem::make_boxed}; |
23 | |
24 | pub use self::info::{FileInfo, FileProtocolInfo, FileSystemInfo, FileSystemVolumeLabel, FromUefi}; |
25 | pub 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. |
31 | pub 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. |
223 | trait FileInternal: File { |
224 | fn imp(&mut self) -> &mut FileImpl { |
225 | unsafe { &mut *self.handle().0 } |
226 | } |
227 | } |
228 | |
229 | impl<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)] |
240 | pub struct FileHandle(*mut FileImpl); |
241 | |
242 | impl 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 | |
284 | impl 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 | |
308 | impl 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)] |
318 | pub(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)] |
371 | pub 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)] |
384 | pub 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 | |
395 | bitflags! { |
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)] |
415 | mod 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 | |