From e481679bd4b461270990acc911002a28fbb2ac5e Mon Sep 17 00:00:00 2001 From: someotherself Date: Thu, 3 Jul 2025 20:58:13 +0300 Subject: [PATCH 1/7] handle_path function plus tests --- src/crypto/read.rs | 1 + src/crypto/write.rs | 1 + src/encryptedfs.rs | 64 ++++++-- src/encryptedfs/test.rs | 328 ++++++++++++++++++++++++++++++++++++++++ src/test_common.rs | 7 +- 5 files changed, 381 insertions(+), 20 deletions(-) diff --git a/src/crypto/read.rs b/src/crypto/read.rs index 92e81193..f6c861f2 100644 --- a/src/crypto/read.rs +++ b/src/crypto/read.rs @@ -179,6 +179,7 @@ impl RingCryptoRead { impl Seek for RingCryptoRead { #[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_sign_loss)] + #[allow(clippy::manual_is_multiple_of)] fn seek(&mut self, pos: SeekFrom) -> io::Result { let plaintext_len = self.get_plaintext_len()?; let new_pos = match pos { diff --git a/src/crypto/write.rs b/src/crypto/write.rs index 901e0091..09cfa627 100644 --- a/src/crypto/write.rs +++ b/src/crypto/write.rs @@ -351,6 +351,7 @@ impl RingCryptoWrite { impl Seek for RingCryptoWrite { #[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_sign_loss)] + #[allow(clippy::manual_is_multiple_of)] fn seek(&mut self, pos: SeekFrom) -> io::Result { let new_pos = match pos { SeekFrom::Start(pos) => pos as i64, diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index 7c2da5a4..ce4fbddf 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -668,6 +668,39 @@ impl EncryptedFs { } } + // Must be given a path (as SecretString) and the root ino for the path + // Parses the path for its components and checks if each component exists EXCLUDING the last one. + // The last file/directory must be checked separately. + // If given only a file/directory name returns them again. + // When given a path, returns the file/directory name and the new ino + async fn handle_path(&self, parent: u64, name: SecretString) -> FsResult<(u64, SecretString)> { + let input = name.expose_secret().to_string(); + let path = input.strip_prefix(".").unwrap_or(&input).to_owned(); + let mut path_segments = vec![]; + for segment in path.split("/") { + if segment.is_empty() { + continue; + } + let segment = SecretString::from_str(segment).unwrap(); + path_segments.push(segment); + } + let file_name = path_segments + .last() + .ok_or_else(|| FsError::InvalidInput("No filename"))? + .clone(); + let mut current_ino: u64 = parent; + if path_segments.len() > 1 { + for segment in path_segments.iter().take(path_segments.len() - 1) { + current_ino = self + .find_by_name(current_ino, &segment.clone()) + .await? + .ok_or_else(|| FsError::InodeNotFound)? + .ino; + } + } + Ok((current_ino, file_name)) + } + /// Create a new node in the filesystem #[allow(clippy::missing_panics_doc)] #[allow(clippy::missing_errors_doc)] @@ -689,10 +722,10 @@ impl EncryptedFs { if !self.exists(parent) { return Err(FsError::InodeNotFound); } - if self.exists_by_name(parent, name)? { + let (parent, name) = self.handle_path(parent, name.clone()).await?; + if self.exists_by_name(parent, &name)? { return Err(FsError::AlreadyExists); } - self.validate_filename(name)?; // spawn on a dedicated runtime to not interfere with other higher priority tasks let self_clone = self @@ -891,12 +924,12 @@ impl EncryptedFs { return Err(FsError::InvalidInodeType); } - if !self.exists_by_name(parent, name)? { + let (parent, name) = self.handle_path(parent, name.clone()).await?; + if !self.exists_by_name(parent, &name)? { return Err(FsError::NotFound("name not found")); } - let attr = self - .find_by_name(parent, name) + .find_by_name(parent, &name) .await? .ok_or(FsError::NotFound("name not found"))?; if !matches!(attr.kind, FileType::Directory) { @@ -967,12 +1000,13 @@ impl EncryptedFs { if !self.is_dir(parent) { return Err(FsError::InvalidInodeType); } - if !self.exists_by_name(parent, name)? { + let (parent, name) = self.handle_path(parent, name.clone()).await?; + if !self.exists_by_name(parent, &name)? { return Err(FsError::NotFound("name not found")); } let attr = self - .find_by_name(parent, name) + .find_by_name(parent, &name) .await? .ok_or(FsError::NotFound("name not found"))?; if !matches!(attr.kind, FileType::RegularFile) { @@ -2011,10 +2045,11 @@ impl EncryptedFs { if !self.is_dir(new_parent) { return Err(FsError::InvalidInodeType); } - if !self.exists_by_name(parent, name)? { + let (parent, name) = self.handle_path(parent, name.clone()).await?; + if !self.exists_by_name(parent, &name)? { return Err(FsError::NotFound("name not found")); } - self.validate_filename(new_name)?; + let (new_parent, new_name) = self.handle_path(new_parent, new_name.clone()).await?; if parent == new_parent && name.expose_secret() == new_name.expose_secret() { // no-op @@ -2022,21 +2057,21 @@ impl EncryptedFs { } // Only overwrite an existing directory if it's empty - if let Ok(Some(new_attr)) = self.find_by_name(new_parent, new_name).await { + if let Ok(Some(new_attr)) = self.find_by_name(new_parent, &new_name).await { if new_attr.kind == FileType::Directory && self.len(new_attr.ino)? > 0 { return Err(FsError::NotEmpty); } } let attr = self - .find_by_name(parent, name) + .find_by_name(parent, &name) .await? .ok_or(FsError::NotFound("name not found"))?; // remove from parent contents - self.remove_directory_entry(parent, name).await?; + self.remove_directory_entry(parent, &name).await?; // remove from new_parent contents, if exists - if self.exists_by_name(new_parent, new_name)? { - self.remove_directory_entry(new_parent, new_name).await?; + if self.exists_by_name(new_parent, &new_name)? { + self.remove_directory_entry(new_parent, &new_name).await?; } // add to new parent contents self.insert_directory_entry( @@ -2421,6 +2456,7 @@ impl EncryptedFs { self.data_dir.join(CONTENTS_DIR).join(ino.to_string()) } + // TO-DO-handle-path-maybe async fn remove_directory_entry(&self, parent: u64, name: &SecretString) -> FsResult<()> { let parent_path = self.contents_path(parent); // remove from HASH diff --git a/src/encryptedfs/test.rs b/src/encryptedfs/test.rs index 81e33d05..1c38b709 100644 --- a/src/encryptedfs/test.rs +++ b/src/encryptedfs/test.rs @@ -1016,6 +1016,7 @@ async fn test_remove_dir() { }, async { let fs = get_fs().await; + // directories in root for dir in ["test-dir", "test-dir_", "test-dir-"] { let test_dir = SecretString::from_str(dir).unwrap(); let _ = fs @@ -1044,6 +1045,58 @@ async fn test_remove_dir() { .count() ); } + + // nested directories in root + let test_dir = SecretString::from_str("test-dir").unwrap(); + let (_, attr1) = fs + .create( + ROOT_INODE, + &test_dir, + create_attr(FileType::Directory), + false, + false, + ) + .await + .unwrap(); + + let test_dir1 = SecretString::from_str("test-dir/test-dir-1").unwrap(); + let (_, attr2) = fs + .create( + ROOT_INODE, + &test_dir1, + create_attr(FileType::Directory), + false, + false, + ) + .await + .unwrap(); + + let test_dir2 = SecretString::from_str("test-dir/test-dir-1/test-dir-2").unwrap(); + let (_, _) = fs + .create( + ROOT_INODE, + &test_dir2, + create_attr(FileType::Directory), + false, + false, + ) + .await + .unwrap(); + // Remove nested folder, not empty + assert!(matches!( + fs.remove_dir(ROOT_INODE, &test_dir1).await, + Err(FsError::NotEmpty) + )); + + // Remove nested folders + fs.remove_dir(ROOT_INODE, &test_dir2).await.unwrap(); + assert!(!fs.exists_by_name(attr2.ino, &test_dir2).unwrap()); + + fs.remove_dir(ROOT_INODE, &test_dir1).await.unwrap(); + assert!(!fs.exists_by_name(attr1.ino, &test_dir1).unwrap()); + + fs.remove_dir(ROOT_INODE, &test_dir).await.unwrap(); + assert!(!fs.exists_by_name(ROOT_INODE, &test_dir).unwrap()); }, ) .await; @@ -1090,6 +1143,48 @@ async fn test_remove_file() { .count() ); } + + // nested directories in root + let test_dir = SecretString::from_str("test-dir").unwrap(); + let (_, _) = fs + .create( + ROOT_INODE, + &test_dir, + create_attr(FileType::Directory), + false, + false, + ) + .await + .unwrap(); + + let test_dir1 = SecretString::from_str("test-dir/test-dir-1").unwrap(); + let (_, _) = fs + .create( + ROOT_INODE, + &test_dir1, + create_attr(FileType::Directory), + false, + false, + ) + .await + .unwrap(); + + // File in nested directory + let test_file = SecretString::from_str("test-dir/test-dir-1/test-file").unwrap(); + let (_, _) = fs + .create( + ROOT_INODE, + &test_file, + create_attr(FileType::RegularFile), + false, + false, + ) + .await + .unwrap(); + + // Remove nested folders + fs.remove_file(ROOT_INODE, &test_file).await.unwrap(); + assert!(!fs.exists_by_name(ROOT_INODE, &test_file).unwrap()); }, ) .await; @@ -1349,6 +1444,128 @@ async fn test_create() { fs.find_by_name(parent, &test_dir_2).await.unwrap().unwrap() ); + // directory in another directory from ROOT + let test_dir_3 = SecretString::from_str("test-dir/test-dir-3").unwrap(); + let (_fh, attr) = fs + .create( + ROOT_INODE, + &test_dir_3, + create_attr(FileType::Directory), + false, + false, + ) + .await + .unwrap(); + + let test_dir_3 = SecretString::from_str("test-dir-3").unwrap(); + let _ = fs.find_by_name(attr.ino, &test_dir_3).await.unwrap(); + + let test_dir_4 = SecretString::from_str("test-dir/test-dir-3/test-dir-4").unwrap(); + let (_fh, _attr) = fs + .create( + ROOT_INODE, + &test_dir_4, + create_attr(FileType::Directory), + false, + false, + ) + .await + .unwrap(); + + // existing dir + assert!(matches!( + fs.create( + ROOT_INODE, + &test_dir_4, + create_attr(FileType::Directory), + false, + false + ) + .await, + Err(FsError::AlreadyExists) + )); + + // incorrect path + let test_dir_bogus = + SecretString::from_str("test-dir/test-dir-3/test-dir-bogus/test-dir-5").unwrap(); + assert!(matches!( + fs.create( + ROOT_INODE, + &test_dir_bogus, + create_attr(FileType::Directory), + false, + false + ) + .await, + Err(FsError::InodeNotFound) + )); + // File in a nested directory from root + let test_file_2 = SecretString::from_str("test-dir/test-file-1").unwrap(); + let (_fh, _attr) = fs + .create( + ROOT_INODE, + &test_file_2, + create_attr(FileType::RegularFile), + true, + false, + ) + .await + .unwrap(); + + // existing file + assert!(matches!( + fs.create( + ROOT_INODE, + &test_file_2, + create_attr(FileType::RegularFile), + false, + false + ) + .await, + Err(FsError::AlreadyExists) + )); + + // File in deeper nested dir from root + let test_file_3 = + SecretString::from_str("test-dir/test-dir-3/test-dir-4/test-file-1").unwrap(); + let (_fh, _attr) = fs + .create( + ROOT_INODE, + &test_file_3, + create_attr(FileType::RegularFile), + true, + false, + ) + .await + .unwrap(); + + // File in a nested directory from another nested directory + let partial_path_file_2 = SecretString::from_str("test-dir-3/test-dir-4/test-file-2").unwrap(); + let partial_path_ino = fs.find_by_name(ROOT_INODE, &test_dir).await.unwrap().unwrap().ino; + let (_fh, _attr) = fs + .create( + partial_path_ino, + &partial_path_file_2, + create_attr(FileType::RegularFile), + true, + false, + ) + .await + .unwrap(); + + // existing file + assert!(matches!( + fs.create( + ROOT_INODE, + &test_file_3, + create_attr(FileType::RegularFile), + false, + false + ) + .await, + Err(FsError::AlreadyExists) + )); + // existing file assert!(matches!( fs.create( @@ -1579,6 +1796,117 @@ async fn test_rename() { 1 ); + // new file to another directory with absolute paths + let nested_a_dir = SecretString::from_str("nested_A_dir").unwrap(); + let (_, _attr) = fs + .create( + ROOT_INODE, + &nested_a_dir, + create_attr(FileType::Directory), + false, + false, + ) + .await + .unwrap(); + let nested_a_dir_1 = SecretString::from_str("nested_A_dir/nested_A_dir_1").unwrap(); + let (_, _attr) = fs + .create( + ROOT_INODE, + &nested_a_dir_1, + create_attr(FileType::Directory), + false, + false, + ) + .await + .unwrap(); + let nested_a_dir_2 = + SecretString::from_str("nested_A_dir/nested_A_dir_1/nested_A_dir_2").unwrap(); + let (_, nested_source_attr) = fs + .create( + ROOT_INODE, + &nested_a_dir_2, + create_attr(FileType::Directory), + false, + false, + ) + .await + .unwrap(); + + let nested_b_dir = SecretString::from_str("nested_B_dir").unwrap(); + let (_, _attr) = fs + .create( + ROOT_INODE, + &nested_b_dir, + create_attr(FileType::Directory), + false, + false, + ) + .await + .unwrap(); + let nested_b_dir_1 = SecretString::from_str("nested_B_dir/nested_B_dir_1").unwrap(); + let (_, _attr) = fs + .create( + ROOT_INODE, + &nested_b_dir_1, + create_attr(FileType::Directory), + false, + false, + ) + .await + .unwrap(); + let nested_b_dir_2 = + SecretString::from_str("nested_B_dir/nested_B_dir_1/nested_B_dir_2").unwrap(); + let (_, nested_dest_attr) = fs + .create( + ROOT_INODE, + &nested_b_dir_2, + create_attr(FileType::Directory), + false, + false, + ) + .await + .unwrap(); + + let nested_file = + SecretString::from_str("nested_A_dir/nested_A_dir_1/nested_A_dir_2/nested_file") + .unwrap(); + let nested_file_alone = SecretString::from_str("nested_file").unwrap(); + let (_, _attr) = fs + .create( + ROOT_INODE, + &nested_file, + create_attr(FileType::RegularFile), + false, + false, + ) + .await + .unwrap(); + + let nested_file_new = SecretString::from_str( + "nested_B_dir/nested_B_dir_1/nested_B_dir_2/nested_file_new", + ) + .unwrap(); + let nested_file_new_alone = SecretString::from_str("nested_file_new").unwrap(); + fs.rename(ROOT_INODE, &nested_file, ROOT_INODE, &nested_file_new) + .await + .unwrap(); + assert!(fs + .exists_by_name(nested_dest_attr.ino, &nested_file_new_alone) + .unwrap()); + assert!(!fs + .exists_by_name(nested_source_attr.ino, &nested_file_alone) + .unwrap()); + + // rename existing nested directory + let nested_b_dir_2_new = + SecretString::from_str("nested_B_dir/nested_B_dir_1/nested_B_dir_2_new").unwrap(); + fs.rename(ROOT_INODE, &nested_b_dir_2, ROOT_INODE, &nested_b_dir_2_new) + .await + .unwrap(); + assert!(fs + .exists_by_name(nested_dest_attr.ino, &nested_file_new_alone) + .unwrap()); + // new directory to another directory let new_parent = new_parent_attr.ino; let (_, attr) = fs diff --git a/src/test_common.rs b/src/test_common.rs index f8e18611..8806892e 100644 --- a/src/test_common.rs +++ b/src/test_common.rs @@ -188,12 +188,7 @@ pub async fn read_exact(fs: &EncryptedFs, ino: u64, offset: u64, buf: &mut [u8], } #[allow(dead_code)] -pub fn bench( - key: &'static str, - worker_threads: usize, - read_only: bool, - f: F, -) { +pub fn bench(key: &'static str, worker_threads: usize, read_only: bool, f: F) { block_on( async { run_test(TestSetup { key, read_only }, f).await; From f7746c83e36e4a498bb9016948ff388d7eb6b0c4 Mon Sep 17 00:00:00 2001 From: someotherself Date: Thu, 3 Jul 2025 21:21:37 +0300 Subject: [PATCH 2/7] fixed formatting --- src/encryptedfs/test.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/encryptedfs/test.rs b/src/encryptedfs/test.rs index 1c38b709..c135ca83 100644 --- a/src/encryptedfs/test.rs +++ b/src/encryptedfs/test.rs @@ -1540,8 +1540,14 @@ async fn test_create() { .unwrap(); // File in a nested directory from another nested directory - let partial_path_file_2 = SecretString::from_str("test-dir-3/test-dir-4/test-file-2").unwrap(); - let partial_path_ino = fs.find_by_name(ROOT_INODE, &test_dir).await.unwrap().unwrap().ino; + let partial_path_file_2 = + SecretString::from_str("test-dir-3/test-dir-4/test-file-2").unwrap(); + let partial_path_ino = fs + .find_by_name(ROOT_INODE, &test_dir) + .await + .unwrap() + .unwrap() + .ino; let (_fh, _attr) = fs .create( partial_path_ino, From fb92a8e8696631124bbad8be8106bd4e7e8f79a6 Mon Sep 17 00:00:00 2001 From: someotherself Date: Fri, 11 Jul 2025 10:38:38 +0300 Subject: [PATCH 3/7] comment removed --- src/encryptedfs.rs | 1 - src/test_common.rs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index ce4fbddf..f06d5a55 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -2456,7 +2456,6 @@ impl EncryptedFs { self.data_dir.join(CONTENTS_DIR).join(ino.to_string()) } - // TO-DO-handle-path-maybe async fn remove_directory_entry(&self, parent: u64, name: &SecretString) -> FsResult<()> { let parent_path = self.contents_path(parent); // remove from HASH diff --git a/src/test_common.rs b/src/test_common.rs index 8806892e..bd963445 100644 --- a/src/test_common.rs +++ b/src/test_common.rs @@ -73,6 +73,7 @@ impl PasswordProvider for PasswordProviderImpl { } } #[allow(dead_code)] + async fn setup(setup: TestSetup) -> SetupResult { let path = TESTS_DATA_DIR.join(setup.key); let read_only = setup.read_only; From 6e1fa7bca5f316699a769effb176b912cf6da35d Mon Sep 17 00:00:00 2001 From: someotherself Date: Fri, 11 Jul 2025 10:38:59 +0300 Subject: [PATCH 4/7] / replaced with std::path::MAIN_SEPARATOR --- src/encryptedfs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index f06d5a55..a6e8979e 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -677,7 +677,7 @@ impl EncryptedFs { let input = name.expose_secret().to_string(); let path = input.strip_prefix(".").unwrap_or(&input).to_owned(); let mut path_segments = vec![]; - for segment in path.split("/") { + for segment in path.split(std::path::MAIN_SEPARATOR) { if segment.is_empty() { continue; } From bdb563252fd707d05715f1e4d46f0cdfa93068ec Mon Sep 17 00:00:00 2001 From: someotherself Date: Fri, 11 Jul 2025 10:43:05 +0300 Subject: [PATCH 5/7] improved handle_path --- src/encryptedfs.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index a6e8979e..f09467ef 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -674,16 +674,13 @@ impl EncryptedFs { // If given only a file/directory name returns them again. // When given a path, returns the file/directory name and the new ino async fn handle_path(&self, parent: u64, name: SecretString) -> FsResult<(u64, SecretString)> { - let input = name.expose_secret().to_string(); + let input = name.expose_secret(); let path = input.strip_prefix(".").unwrap_or(&input).to_owned(); - let mut path_segments = vec![]; - for segment in path.split(std::path::MAIN_SEPARATOR) { - if segment.is_empty() { - continue; - } - let segment = SecretString::from_str(segment).unwrap(); - path_segments.push(segment); - } + let path_segments: Vec = path + .split(std::path::MAIN_SEPARATOR) + .filter(|s| !s.is_empty()) + .map(|s| SecretString::from_str(s).unwrap()) + .collect(); let file_name = path_segments .last() .ok_or_else(|| FsError::InvalidInput("No filename"))? @@ -692,7 +689,7 @@ impl EncryptedFs { if path_segments.len() > 1 { for segment in path_segments.iter().take(path_segments.len() - 1) { current_ino = self - .find_by_name(current_ino, &segment.clone()) + .find_by_name(current_ino, segment) .await? .ok_or_else(|| FsError::InodeNotFound)? .ino; From d5bc47a97213519d78a5c5292b6ca834e7e47ed8 Mon Sep 17 00:00:00 2001 From: someotherself Date: Fri, 11 Jul 2025 10:45:17 +0300 Subject: [PATCH 6/7] re-added validate_filename --- src/encryptedfs.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index f09467ef..fa71e040 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -720,6 +720,8 @@ impl EncryptedFs { return Err(FsError::InodeNotFound); } let (parent, name) = self.handle_path(parent, name.clone()).await?; + self.validate_filename(&name)?; + if self.exists_by_name(parent, &name)? { return Err(FsError::AlreadyExists); } @@ -922,6 +924,8 @@ impl EncryptedFs { } let (parent, name) = self.handle_path(parent, name.clone()).await?; + self.validate_filename(&name)?; + if !self.exists_by_name(parent, &name)? { return Err(FsError::NotFound("name not found")); } @@ -998,6 +1002,8 @@ impl EncryptedFs { return Err(FsError::InvalidInodeType); } let (parent, name) = self.handle_path(parent, name.clone()).await?; + self.validate_filename(&name)?; + if !self.exists_by_name(parent, &name)? { return Err(FsError::NotFound("name not found")); } @@ -2043,10 +2049,12 @@ impl EncryptedFs { return Err(FsError::InvalidInodeType); } let (parent, name) = self.handle_path(parent, name.clone()).await?; + self.validate_filename(&name)?; if !self.exists_by_name(parent, &name)? { return Err(FsError::NotFound("name not found")); } let (new_parent, new_name) = self.handle_path(new_parent, new_name.clone()).await?; + self.validate_filename(&new_name)?; if parent == new_parent && name.expose_secret() == new_name.expose_secret() { // no-op From 99219cab46ce84506cfcba1eb9ef7c82167d9237 Mon Sep 17 00:00:00 2001 From: someotherself Date: Fri, 11 Jul 2025 11:01:37 +0300 Subject: [PATCH 7/7] clippy --- src/test_common.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test_common.rs b/src/test_common.rs index bd963445..a78307b1 100644 --- a/src/test_common.rs +++ b/src/test_common.rs @@ -72,8 +72,8 @@ impl PasswordProvider for PasswordProviderImpl { Some(SecretString::from_str("password").unwrap()) } } -#[allow(dead_code)] +#[allow(dead_code)] async fn setup(setup: TestSetup) -> SetupResult { let path = TESTS_DATA_DIR.join(setup.key); let read_only = setup.read_only;