1 //! Module for [`FileSystem`]. 2 3 use crate::fs::*; 4 use crate::Status; 5 use alloc::boxed::Box; 6 use alloc::string::String; 7 use alloc::vec; 8 use alloc::vec::Vec; 9 use core::fmt; 10 use core::fmt::{Debug, Formatter}; 11 use uefi::boot::ScopedProtocol; 12 13 /// Return type for public [`FileSystem`] operations. 14 pub type FileSystemResult<T> = Result<T, Error>; 15 16 /// High-level file-system abstraction for UEFI volumes with an API that is 17 /// close to `std::fs`. It acts as convenient accessor around the 18 /// [`SimpleFileSystemProtocol`]. 19 /// 20 /// Please refer to the [module documentation] for more information. 21 /// 22 /// [module documentation]: uefi::fs 23 pub struct FileSystem(ScopedProtocol<SimpleFileSystemProtocol>); 24 25 impl FileSystem { 26 /// Constructor. 27 #[must_use] new(proto: impl Into<Self>) -> Self28 pub fn new(proto: impl Into<Self>) -> Self { 29 proto.into() 30 } 31 32 /// Returns `Ok(true)` if the path points at an existing file. 33 /// 34 /// If the file does not exist, `Ok(false)` is returned. If it cannot be 35 /// determined whether the file exists or not, an error is returned. try_exists(&mut self, path: impl AsRef<Path>) -> FileSystemResult<bool>36 pub fn try_exists(&mut self, path: impl AsRef<Path>) -> FileSystemResult<bool> { 37 match self.open(path.as_ref(), UefiFileMode::Read, false) { 38 Ok(_) => Ok(true), 39 Err(Error::Io(err)) => { 40 if err.uefi_error.status() == Status::NOT_FOUND { 41 Ok(false) 42 } else { 43 Err(Error::Io(err)) 44 } 45 } 46 Err(err) => Err(err), 47 } 48 } 49 50 /// Copies the contents of one file to another. Creates the destination file 51 /// if it doesn't exist and overwrites any content, if it exists. copy( &mut self, src_path: impl AsRef<Path>, dest_path: impl AsRef<Path>, ) -> FileSystemResult<()>52 pub fn copy( 53 &mut self, 54 src_path: impl AsRef<Path>, 55 dest_path: impl AsRef<Path>, 56 ) -> FileSystemResult<()> { 57 let src_path = src_path.as_ref(); 58 let dest_path = dest_path.as_ref(); 59 60 // Open the source file for reading. 61 let mut src = self 62 .open(src_path, UefiFileMode::Read, false)? 63 .into_regular_file() 64 .ok_or(Error::Io(IoError { 65 path: src_path.to_path_buf(), 66 context: IoErrorContext::NotAFile, 67 uefi_error: Status::INVALID_PARAMETER.into(), 68 }))?; 69 70 // Get the source file's size in bytes. 71 let src_size = { 72 let src_info = src.get_boxed_info::<UefiFileInfo>().map_err(|err| { 73 Error::Io(IoError { 74 path: src_path.to_path_buf(), 75 context: IoErrorContext::Metadata, 76 uefi_error: err, 77 }) 78 })?; 79 src_info.file_size() 80 }; 81 82 // Try to delete the destination file in case it already exists. Allow 83 // this to fail, since it might not exist. Or it might exist, but be a 84 // directory, in which case the error will be caught when trying to 85 // create the file. 86 let _ = self.remove_file(dest_path); 87 88 // Create and open the destination file. 89 let mut dest = self 90 .open(dest_path, UefiFileMode::CreateReadWrite, false)? 91 .into_regular_file() 92 .ok_or(Error::Io(IoError { 93 path: dest_path.to_path_buf(), 94 context: IoErrorContext::OpenError, 95 uefi_error: Status::INVALID_PARAMETER.into(), 96 }))?; 97 98 // 1 MiB copy buffer. 99 let mut chunk = vec![0; 1024 * 1024]; 100 101 // Read chunks from the source file and write to the destination file. 102 let mut remaining_size = src_size; 103 while remaining_size > 0 { 104 // Read one chunk. 105 let num_bytes_read = src.read(&mut chunk).map_err(|err| { 106 Error::Io(IoError { 107 path: src_path.to_path_buf(), 108 context: IoErrorContext::ReadFailure, 109 uefi_error: err.to_err_without_payload(), 110 }) 111 })?; 112 113 // If the read returned no bytes, but `remaining_size > 0`, return 114 // an error. 115 if num_bytes_read == 0 { 116 return Err(Error::Io(IoError { 117 path: src_path.to_path_buf(), 118 context: IoErrorContext::ReadFailure, 119 uefi_error: Status::ABORTED.into(), 120 })); 121 } 122 123 // Copy the bytes read out to the destination file. 124 dest.write(&chunk[..num_bytes_read]).map_err(|err| { 125 Error::Io(IoError { 126 path: dest_path.to_path_buf(), 127 context: IoErrorContext::WriteFailure, 128 uefi_error: err.to_err_without_payload(), 129 }) 130 })?; 131 132 remaining_size -= u64::try_from(num_bytes_read).unwrap(); 133 } 134 135 dest.flush().map_err(|err| { 136 Error::Io(IoError { 137 path: dest_path.to_path_buf(), 138 context: IoErrorContext::FlushFailure, 139 uefi_error: err, 140 }) 141 })?; 142 143 Ok(()) 144 } 145 146 /// Creates a new, empty directory at the provided path create_dir(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()>147 pub fn create_dir(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()> { 148 let path = path.as_ref(); 149 self.open(path, UefiFileMode::CreateReadWrite, true) 150 .map(|_| ()) 151 } 152 153 /// Recursively create a directory and all of its parent components if they 154 /// are missing. create_dir_all(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()>155 pub fn create_dir_all(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()> { 156 let path = path.as_ref(); 157 158 // Collect all relevant sub paths in a vector. 159 let mut dirs_to_create = vec![path.to_path_buf()]; 160 while let Some(parent) = dirs_to_create.last().unwrap().parent() { 161 dirs_to_create.push(parent) 162 } 163 // Now reverse, so that we have something like this: 164 // - a 165 // - a\\b 166 // - a\\b\\c 167 dirs_to_create.reverse(); 168 169 for parent in dirs_to_create { 170 if !self.try_exists(&parent)? { 171 self.create_dir(parent)?; 172 } 173 } 174 175 Ok(()) 176 } 177 178 /// Given a path, query the file system to get information about a file, 179 /// directory, etc. Returns [`UefiFileInfo`]. metadata(&mut self, path: impl AsRef<Path>) -> FileSystemResult<Box<UefiFileInfo>>180 pub fn metadata(&mut self, path: impl AsRef<Path>) -> FileSystemResult<Box<UefiFileInfo>> { 181 let path = path.as_ref(); 182 let mut file = self.open(path, UefiFileMode::Read, false)?; 183 file.get_boxed_info().map_err(|err| { 184 Error::Io(IoError { 185 path: path.to_path_buf(), 186 context: IoErrorContext::Metadata, 187 uefi_error: err, 188 }) 189 }) 190 } 191 192 /// Read the entire contents of a file into a bytes vector. read(&mut self, path: impl AsRef<Path>) -> FileSystemResult<Vec<u8>>193 pub fn read(&mut self, path: impl AsRef<Path>) -> FileSystemResult<Vec<u8>> { 194 let path = path.as_ref(); 195 196 let mut file = self 197 .open(path, UefiFileMode::Read, false)? 198 .into_regular_file() 199 .ok_or(Error::Io(IoError { 200 path: path.to_path_buf(), 201 context: IoErrorContext::NotAFile, 202 // We do not have a real UEFI error here as we have a logical 203 // problem. 204 uefi_error: Status::INVALID_PARAMETER.into(), 205 }))?; 206 207 let info = file.get_boxed_info::<UefiFileInfo>().map_err(|err| { 208 Error::Io(IoError { 209 path: path.to_path_buf(), 210 context: IoErrorContext::Metadata, 211 uefi_error: err, 212 }) 213 })?; 214 215 let mut vec = vec![0; info.file_size() as usize]; 216 let read_bytes = file.read(vec.as_mut_slice()).map_err(|err| { 217 Error::Io(IoError { 218 path: path.to_path_buf(), 219 context: IoErrorContext::ReadFailure, 220 uefi_error: err.to_err_without_payload(), 221 }) 222 })?; 223 224 // we read the whole file at once! 225 if read_bytes != info.file_size() as usize { 226 log::error!("Did only read {}/{} bytes", info.file_size(), read_bytes); 227 } 228 229 Ok(vec) 230 } 231 232 /// Returns an iterator over the entries within a directory. read_dir(&mut self, path: impl AsRef<Path>) -> FileSystemResult<UefiDirectoryIter>233 pub fn read_dir(&mut self, path: impl AsRef<Path>) -> FileSystemResult<UefiDirectoryIter> { 234 let path = path.as_ref(); 235 let dir = self 236 .open(path, UefiFileMode::Read, false)? 237 .into_directory() 238 .ok_or(Error::Io(IoError { 239 path: path.to_path_buf(), 240 context: IoErrorContext::NotADirectory, 241 // We do not have a real UEFI error here as we have a logical 242 // problem. 243 uefi_error: Status::INVALID_PARAMETER.into(), 244 }))?; 245 Ok(UefiDirectoryIter::new(dir)) 246 } 247 248 /// Read the entire contents of a file into a Rust string. read_to_string(&mut self, path: impl AsRef<Path>) -> FileSystemResult<String>249 pub fn read_to_string(&mut self, path: impl AsRef<Path>) -> FileSystemResult<String> { 250 String::from_utf8(self.read(path)?).map_err(Error::Utf8Encoding) 251 } 252 253 /// Removes an empty directory. remove_dir(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()>254 pub fn remove_dir(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()> { 255 let path = path.as_ref(); 256 257 let file = self 258 .open(path, UefiFileMode::ReadWrite, false)? 259 .into_type() 260 .unwrap(); 261 262 match file { 263 UefiFileType::Dir(dir) => dir.delete().map_err(|err| { 264 Error::Io(IoError { 265 path: path.to_path_buf(), 266 context: IoErrorContext::CantDeleteDirectory, 267 uefi_error: err, 268 }) 269 }), 270 UefiFileType::Regular(_) => { 271 Err(Error::Io(IoError { 272 path: path.to_path_buf(), 273 context: IoErrorContext::NotADirectory, 274 // We do not have a real UEFI error here as we have a logical 275 // problem. 276 uefi_error: Status::INVALID_PARAMETER.into(), 277 })) 278 } 279 } 280 } 281 282 /// Removes a directory at this path, after removing all its contents. Use 283 /// carefully! remove_dir_all(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()>284 pub fn remove_dir_all(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()> { 285 let path = path.as_ref(); 286 for file_info in self 287 .read_dir(path)? 288 .filter_map(|file_info_result| file_info_result.ok()) 289 { 290 if COMMON_SKIP_DIRS.contains(&file_info.file_name()) { 291 continue; 292 } 293 294 let mut abs_entry_path = PathBuf::new(); 295 abs_entry_path.push(path); 296 abs_entry_path.push(file_info.file_name()); 297 if file_info.is_directory() { 298 // delete all inner files 299 // This recursion is fine as there are no links in UEFI/FAT file 300 // systems. No cycles possible. 301 self.remove_dir_all(&abs_entry_path)?; 302 } else { 303 self.remove_file(abs_entry_path)?; 304 } 305 } 306 // Now that the dir is empty, we delete it as final step. 307 self.remove_dir(path)?; 308 Ok(()) 309 } 310 311 /// Removes a file from the filesystem. remove_file(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()>312 pub fn remove_file(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()> { 313 let path = path.as_ref(); 314 315 let file = self 316 .open(path, UefiFileMode::ReadWrite, false)? 317 .into_type() 318 .unwrap(); 319 320 match file { 321 UefiFileType::Regular(file) => file.delete().map_err(|err| { 322 Error::Io(IoError { 323 path: path.to_path_buf(), 324 context: IoErrorContext::CantDeleteFile, 325 uefi_error: err, 326 }) 327 }), 328 UefiFileType::Dir(_) => Err(Error::Io(IoError { 329 path: path.to_path_buf(), 330 context: IoErrorContext::NotAFile, 331 // We do not have a real UEFI error here as we have a logical 332 // problem. 333 uefi_error: Status::INVALID_PARAMETER.into(), 334 })), 335 } 336 } 337 338 /// Rename a file or directory to a new name, replacing the original file if 339 /// it already exists. rename( &mut self, src_path: impl AsRef<Path>, dest_path: impl AsRef<Path>, ) -> FileSystemResult<()>340 pub fn rename( 341 &mut self, 342 src_path: impl AsRef<Path>, 343 dest_path: impl AsRef<Path>, 344 ) -> FileSystemResult<()> { 345 self.copy(&src_path, dest_path)?; 346 self.remove_file(src_path) 347 } 348 349 /// Write a slice as the entire contents of a file. This function will 350 /// create a file if it does not exist, and will entirely replace its 351 /// contents if it does. write( &mut self, path: impl AsRef<Path>, content: impl AsRef<[u8]>, ) -> FileSystemResult<()>352 pub fn write( 353 &mut self, 354 path: impl AsRef<Path>, 355 content: impl AsRef<[u8]>, 356 ) -> FileSystemResult<()> { 357 let path = path.as_ref(); 358 359 // since there is no .truncate() in UEFI, we delete the file first it it 360 // exists. 361 if self.try_exists(path)? { 362 self.remove_file(path)?; 363 } 364 365 let mut handle = self 366 .open(path, UefiFileMode::CreateReadWrite, false)? 367 .into_regular_file() 368 .unwrap(); 369 370 handle.write(content.as_ref()).map_err(|err| { 371 Error::Io(IoError { 372 path: path.to_path_buf(), 373 context: IoErrorContext::WriteFailure, 374 uefi_error: err.to_err_without_payload(), 375 }) 376 })?; 377 handle.flush().map_err(|err| { 378 Error::Io(IoError { 379 path: path.to_path_buf(), 380 context: IoErrorContext::FlushFailure, 381 uefi_error: err, 382 }) 383 })?; 384 Ok(()) 385 } 386 387 /// Opens a fresh handle to the root directory of the volume. open_root(&mut self) -> FileSystemResult<UefiDirectoryHandle>388 fn open_root(&mut self) -> FileSystemResult<UefiDirectoryHandle> { 389 self.0.open_volume().map_err(|err| { 390 Error::Io(IoError { 391 path: { 392 let mut path = PathBuf::new(); 393 path.push(SEPARATOR_STR); 394 path 395 }, 396 context: IoErrorContext::CantOpenVolume, 397 uefi_error: err, 398 }) 399 }) 400 } 401 402 /// Wrapper around [`Self::open_root`] that opens the provided path as 403 /// absolute path. 404 /// 405 /// May create a file if [`UefiFileMode::CreateReadWrite`] is set. May 406 /// create a directory if [`UefiFileMode::CreateReadWrite`] and `create_dir` 407 /// is set. The parameter `create_dir` is ignored otherwise. open( &mut self, path: &Path, mode: UefiFileMode, create_dir: bool, ) -> FileSystemResult<UefiFileHandle>408 fn open( 409 &mut self, 410 path: &Path, 411 mode: UefiFileMode, 412 create_dir: bool, 413 ) -> FileSystemResult<UefiFileHandle> { 414 validate_path(path)?; 415 416 let attr = if mode == UefiFileMode::CreateReadWrite && create_dir { 417 UefiFileAttribute::DIRECTORY 418 } else { 419 UefiFileAttribute::empty() 420 }; 421 422 self.open_root()? 423 .open(path.to_cstr16(), mode, attr) 424 .map_err(|err| { 425 Error::Io(IoError { 426 path: path.to_path_buf(), 427 context: IoErrorContext::OpenError, 428 uefi_error: err, 429 }) 430 }) 431 } 432 } 433 434 impl Debug for FileSystem { fmt(&self, f: &mut Formatter<'_>) -> fmt::Result435 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 436 let ptr: *const _ = &self.0; 437 f.debug_tuple("FileSystem").field(&ptr).finish() 438 } 439 } 440 441 impl From<uefi::boot::ScopedProtocol<SimpleFileSystemProtocol>> for FileSystem { from(proto: uefi::boot::ScopedProtocol<SimpleFileSystemProtocol>) -> Self442 fn from(proto: uefi::boot::ScopedProtocol<SimpleFileSystemProtocol>) -> Self { 443 Self(proto) 444 } 445 } 446