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