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