diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 129ffd3..4b2e6bb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: # Always run MSRV too! - rust: ["stable", "1.86"] + rust: ["stable", "1.87"] features: ['log', 'defmt-log', '""'] steps: - uses: actions/checkout@v4 diff --git a/Cargo.toml b/Cargo.toml index 6bb514b..6dbaee6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/rust-embedded-community/embedded-sdmmc-rs" version = "0.9.0" # Make sure to update the CI too! -rust-version = "1.86" +rust-version = "1.87" [dependencies] byteorder = {version = "1", default-features = false} diff --git a/src/lib.rs b/src/lib.rs index e8ebbab..eaab01d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -191,8 +191,10 @@ where OpenedDirAsFile, /// You can't open a file as a directory OpenedFileAsDir, - /// You can't delete a directory as a file + /// You can't delete a directory as a file [no longer being emitted] DeleteDirAsFile, + /// You can't delete a non-empty directory + DeleteNonEmptyDir, /// You can't close a volume with open files or directories VolumeStillInUse, /// You can't open a volume twice @@ -255,6 +257,7 @@ impl embedded_io::Error for Error { Error::OpenedDirAsFile | Error::OpenedFileAsDir | Error::DeleteDirAsFile + | Error::DeleteNonEmptyDir | Error::BadCluster | Error::ConversionError | Error::UnterminatedFatChain => ErrorKind::InvalidData, @@ -294,6 +297,7 @@ where Error::OpenedDirAsFile => write!(f, "cannot open directory as file"), Error::OpenedFileAsDir => write!(f, "cannot open file as directory"), Error::DeleteDirAsFile => write!(f, "cannot delete directory as file"), + Error::DeleteNonEmptyDir => write!(f, "cannot delete a non-empty directory"), Error::VolumeStillInUse => write!(f, "volume is still in use"), Error::VolumeAlreadyOpen => write!(f, "cannot open volume twice"), Error::Unsupported => write!(f, "unsupported operation"), diff --git a/src/volume_mgr.rs b/src/volume_mgr.rs index 9dcb7ed..d5b53f1 100644 --- a/src/volume_mgr.rs +++ b/src/volume_mgr.rs @@ -665,26 +665,62 @@ where let data = data.deref_mut(); let dir_idx = data.get_dir_by_id(directory)?; - let dir_info = &data.open_dirs[dir_idx]; - let volume_idx = data.get_volume_by_id(dir_info.raw_volume)?; + let parent_dir_info = &data.open_dirs[dir_idx]; + let volume_idx = data.get_volume_by_id(parent_dir_info.raw_volume)?; let sfn = name.to_short_filename().map_err(Error::FilenameError)?; let dir_entry = match &data.open_volumes[volume_idx].volume_type { - VolumeType::Fat(fat) => fat.find_directory_entry(&mut data.block_cache, dir_info, &sfn), + VolumeType::Fat(fat) => { + fat.find_directory_entry(&mut data.block_cache, parent_dir_info, &sfn) + } }?; if dir_entry.attributes.is_directory() { - return Err(Error::DeleteDirAsFile); - } - - if data.file_is_open(dir_info.raw_volume, &dir_entry) { + // Find the directory to be deleted, so that we can check its contents. + let dir_info = if data + .open_dirs + .iter() + .find(|dir_info| dir_info.cluster == dir_entry.cluster) + .is_some() + { + // Subdirectory is already open. + return Err(Error::DirAlreadyOpen); + } else { + // The subdirectory isn't yet open. Open it in order to be able to list it. + let raw_directory = RawDirectory(data.id_generator.generate()); + DirectoryInfo { + raw_directory, + raw_volume: data.open_volumes[volume_idx].raw_volume, + cluster: dir_entry.cluster, + } + }; + // Can only delete directories that are already empty. + let mut count = 0; + // Equivalent to `self.iterate_dir(raw_dir, |_| count += 1)?;`, without locking again. + match &data.open_volumes[volume_idx].volume_type { + VolumeType::Fat(fat) => { + fat.iterate_dir(&mut data.block_cache, &dir_info, |de| { + // Hide all the LFN directory entries + if !de.attributes.is_lfn() + && de.name != ShortFileName::this_dir() + && de.name != ShortFileName::parent_dir() + { + count += 1; + } + })?; + } + } + if count != 0 { + return Err(Error::DeleteNonEmptyDir); + } + } else if data.file_is_open(parent_dir_info.raw_volume, &dir_entry) { return Err(Error::FileAlreadyOpen); } - let volume_idx = data.get_volume_by_id(dir_info.raw_volume)?; + let volume_idx = data.get_volume_by_id(parent_dir_info.raw_volume)?; match &data.open_volumes[volume_idx].volume_type { VolumeType::Fat(fat) => { - fat.delete_directory_entry(&mut data.block_cache, dir_info, &sfn)? + fat.delete_directory_entry(&mut data.block_cache, parent_dir_info, &sfn)? } } diff --git a/tests/directories.rs b/tests/directories.rs index e10a901..b18a04b 100644 --- a/tests/directories.rs +++ b/tests/directories.rs @@ -592,6 +592,49 @@ fn make_directory() { volume_mgr.close_file(new_file).expect("close file"); } +#[test] +fn delete_directory() { + let time_source = utils::make_time_source(); + let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); + let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); + + let fat32_volume = volume_mgr + .open_raw_volume(embedded_sdmmc::VolumeIdx(1)) + .expect("open volume 1"); + + let root_dir = volume_mgr + .open_root_dir(fat32_volume) + .expect("open root dir"); + + volume_mgr.make_dir_in_dir(root_dir, "FOOBAR").unwrap(); + + let dir = volume_mgr.open_dir(root_dir, "FOOBAR").unwrap(); + + assert!(matches!( + volume_mgr.delete_file_in_dir(root_dir, "FOOBAR"), + Err(embedded_sdmmc::Error::DirAlreadyOpen) + )); + + assert!(matches!( + volume_mgr.delete_file_in_dir(root_dir, "FOO"), + Err(embedded_sdmmc::Error::NotFound) + )); + + volume_mgr.close_dir(dir).unwrap(); + + volume_mgr.delete_file_in_dir(root_dir, "FOOBAR").unwrap(); + + assert!(matches!( + volume_mgr.delete_file_in_dir(root_dir, "FOOBAR"), + Err(embedded_sdmmc::Error::NotFound) + )); + + assert!(matches!( + volume_mgr.open_dir(root_dir, "FOOBAR"), + Err(embedded_sdmmc::Error::NotFound) + )); +} + // **************************************************************************** // // End Of File