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..fa71e040 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -668,6 +668,36 @@ 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(); + let path = input.strip_prefix(".").unwrap_or(&input).to_owned(); + 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"))? + .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) + .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 +719,12 @@ 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?; + self.validate_filename(&name)?; + + 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 +923,14 @@ impl EncryptedFs { return Err(FsError::InvalidInodeType); } - if !self.exists_by_name(parent, name)? { + 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 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 +1001,15 @@ 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?; + self.validate_filename(&name)?; + + 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 +2048,13 @@ 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?; + self.validate_filename(&name)?; + 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?; + self.validate_filename(&new_name)?; if parent == new_parent && name.expose_secret() == new_name.expose_secret() { // no-op @@ -2022,21 +2062,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( diff --git a/src/encryptedfs/test.rs b/src/encryptedfs/test.rs index 81e33d05..c135ca83 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,134 @@ 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 +1802,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..a78307b1 100644 --- a/src/test_common.rs +++ b/src/test_common.rs @@ -72,6 +72,7 @@ impl PasswordProvider for PasswordProviderImpl { Some(SecretString::from_str("password").unwrap()) } } + #[allow(dead_code)] async fn setup(setup: TestSetup) -> SetupResult { let path = TESTS_DATA_DIR.join(setup.key); @@ -188,12 +189,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;