From 163d56bccaf874a8f98f9701ba8f397bdf75925b Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Tue, 14 Mar 2023 21:16:58 +0800 Subject: [PATCH] Reworking the read-only/read-write logic --- lib/vfs/src/host_fs.rs | 2 - lib/vfs/src/overlay_fs.rs | 111 +++++++++++++++++++++++++------------- 2 files changed, 74 insertions(+), 39 deletions(-) diff --git a/lib/vfs/src/host_fs.rs b/lib/vfs/src/host_fs.rs index 253f191b9e3..ef4f3fc4633 100644 --- a/lib/vfs/src/host_fs.rs +++ b/lib/vfs/src/host_fs.rs @@ -1365,7 +1365,5 @@ mod tests { .join("hello.txt")), "canonicalizing a crazily stupid path name", ); - - let _ = fs_extra::remove_items(&["./test_canonicalize"]); } } diff --git a/lib/vfs/src/overlay_fs.rs b/lib/vfs/src/overlay_fs.rs index 989d788896d..d27a99b4f95 100644 --- a/lib/vfs/src/overlay_fs.rs +++ b/lib/vfs/src/overlay_fs.rs @@ -43,11 +43,7 @@ use crate::{ /// /// A more complex example is #[derive(Clone, PartialEq, Eq)] -pub struct OverlayFileSystem -where - P: FileSystem, - S: for<'a> FileSystems<'a>, -{ +pub struct OverlayFileSystem { primary: P, secondaries: S, } @@ -136,10 +132,10 @@ where } if had_at_least_one_success { - // Note: this sort is guaranteed to be stable, so it will prefer - // filesystems "higher up" the chain. + // Note: this sort is guaranteed to be stable, so filesystems + // "higher up" the chain will be further towards the start. entries.sort_by(|a, b| a.path.cmp(&b.path)); - // Make sure earlier entries shadow layer ones. + // Make sure later entries are removed in favour of earlier ones. entries.dedup_by(|a, b| a.path == b.path); Ok(ReadDir::new(entries)) @@ -149,15 +145,51 @@ where } fn create_dir(&self, path: &Path) -> Result<(), FsError> { - self.for_each(|fs| fs.create_dir(path)) + match self.primary.create_dir(path) { + Ok(()) => return Ok(()), + Err(e) if should_continue(e) => {} + Err(e) => return Err(e), + } + + for fs in self.secondaries.iter_filesystems() { + if fs.is_dir(path) { + return Err(FsError::PermissionDenied); + } + } + + Err(FsError::EntryNotFound) } fn remove_dir(&self, path: &Path) -> Result<(), FsError> { - self.for_each(|fs| fs.remove_dir(path)) + match self.primary.remove_dir(path) { + Ok(()) => return Ok(()), + Err(e) if should_continue(e) => {} + Err(e) => return Err(e), + } + + for fs in self.secondaries.iter_filesystems() { + if fs.is_dir(path) { + return Err(FsError::PermissionDenied); + } + } + + Err(FsError::EntryNotFound) } fn rename(&self, from: &Path, to: &Path) -> Result<(), FsError> { - self.for_each(|fs| fs.rename(from, to)) + match self.primary.rename(from, to) { + Ok(()) => return Ok(()), + Err(e) if should_continue(e) => {} + Err(e) => return Err(e), + } + + for fs in self.secondaries.iter_filesystems() { + if fs.exists(from) { + return Err(FsError::PermissionDenied); + } + } + + Err(FsError::EntryNotFound) } fn metadata(&self, path: &Path) -> Result { @@ -165,7 +197,19 @@ where } fn remove_file(&self, path: &Path) -> Result<(), FsError> { - self.for_each(|fs| fs.remove_file(path)) + match self.primary.remove_file(path) { + Ok(()) => return Ok(()), + Err(e) if should_continue(e) => {} + Err(e) => return Err(e), + } + + for fs in self.secondaries.iter_filesystems() { + if fs.exists(path) { + return Err(FsError::PermissionDenied); + } + } + + Err(FsError::EntryNotFound) } fn new_open_options(&self) -> OpenOptions<'_> { @@ -183,31 +227,9 @@ where path: &Path, conf: &OpenOptionsConfig, ) -> Result, FsError> { - // FIXME: There is probably a smarter way to do this without the extra - // FileSystem::metadata() calls or the risk of TOCTOU issues. - - if (conf.create || conf.create_new) && !self.exists(path) { - if let Some(parent) = path.parent() { - // As a special case, we want to direct all newly created files - // to the primary filesystem so it just *looks* like they are - // created alongside secondary filesystems. - let would_normally_be_created_on_a_secondary_fs = self - .secondaries - .iter_filesystems() - .into_iter() - .any(|fs| fs.exists(parent)); - - if would_normally_be_created_on_a_secondary_fs { - self.primary.create_dir_all(parent)?; - return self - .primary - .new_open_options() - .options(conf.clone()) - .open(path); - } - } - } - + // TODO: Re-work this method so that trying to create a file inside a + // secondary filesystem will actually create the file on the primary + // filesystem, running create_dir_all() if necessary. self.for_each(|fs| fs.new_open_options().options(conf.clone()).open(path)) } } @@ -358,6 +380,21 @@ mod tests { assert_ne!(content, "This is shadowed"); } + #[test] + fn create_file_that_looks_like_it_is_in_a_secondary_filesystem_folder() { + let primary = MemFS::default(); + let secondary = MemFS::default(); + secondary.create_dir_all("/path/to/").unwrap(); + assert!(!primary.is_dir("/path/to/")); + let fs = OverlayFileSystem::new(primary, [secondary]); + + fs.touch("/path/to/file.txt").unwrap(); + + assert!(fs.primary.is_dir("/path/to/")); + assert!(fs.primary.is_file("/path/to/file.txt")); + assert!(!fs.secondaries[0].is_file("/path/to/file.txt")); + } + #[tokio::test] async fn listed_files_appear_overlayed() { let primary = MemFS::default();