diff --git a/fuzz/fuzz_targets/map.rs b/fuzz/fuzz_targets/map.rs index 79609bd..4b98f51 100644 --- a/fuzz/fuzz_targets/map.rs +++ b/fuzz/fuzz_targets/map.rs @@ -34,6 +34,7 @@ struct Input { enum Op { Store(StoreOp), Fetch(u8), + Remove(u8), } #[derive(Arbitrary, Debug, Clone)] @@ -107,7 +108,11 @@ enum CacheType { fn fuzz(ops: Input, mut cache: impl KeyCacheImpl) { let mut flash = MockFlashBase::::new( - WriteCountCheck::OnceOnly, + if ops.ops.iter().any(|op| matches!(op, Op::Remove(_))) { + WriteCountCheck::Twice + } else { + WriteCountCheck::OnceOnly + }, Some(ops.fuel as u32), true, ); @@ -243,6 +248,62 @@ fn fuzz(ops: Input, mut cache: impl KeyCacheImpl) { Err(e) => panic!("{e:?}"), } } + Op::Remove(key) => { + match block_on(sequential_storage::map::remove_item::( + &mut flash, + FLASH_RANGE, + &mut cache, + &mut buf.0, + key, + )) { + Ok(()) => { + map.remove(&key); + } + Err(MapError::Storage { + value: MockFlashError::EarlyShutoff(_), + backtrace: _backtrace, + }) => { + match block_on(sequential_storage::map::fetch_item::( + &mut flash, + FLASH_RANGE, + &mut cache, + &mut buf.0, + key, + )) { + Ok(Some(_)) => { + #[cfg(fuzzing_repro)] + eprintln!("Early shutoff when removing item {key}! Originated from:\n{_backtrace:#}"); + } + _ => { + // Could not fetch the item we stored... + #[cfg(fuzzing_repro)] + eprintln!("Early shutoff when removing item {key}! (but it still removed fully). Originated from:\n{_backtrace:#}"); + // Even though we got a shutoff, it still managed to store well + map.remove(&key); + } + } + } + Err(MapError::Corrupted { + backtrace: _backtrace, + }) if !corruption_repaired => { + #[cfg(fuzzing_repro)] + eprintln!( + "### Encountered curruption while fetching! Repairing now. Originated from:\n{_backtrace:#}" + ); + + block_on(sequential_storage::map::try_repair::( + &mut flash, + FLASH_RANGE, + &mut cache, + &mut buf.0, + )) + .unwrap(); + corruption_repaired = true; + retry = true; + } + Err(e) => panic!("{e:?}"), + } + } } } } diff --git a/src/map.rs b/src/map.rs index c868d61..c0c34bc 100644 --- a/src/map.rs +++ b/src/map.rs @@ -481,6 +481,15 @@ pub async fn store_item( } } +/// Fully remove an item. Additional calls to fetch with the same key will return None until +/// a new one is stored again. +/// +///
+/// This is really slow! +/// +/// All items in flash have to be read and deserialized to find the items with the key. +/// This is unlikely to be cached well. +///
pub async fn remove_item( flash: &mut S, flash_range: Range, @@ -488,6 +497,8 @@ pub async fn remove_item( data_buffer: &mut [u8], search_key: I::Key, ) -> Result<(), MapError> { + cache.notice_key_erased(&search_key); + // Search for the last used page. We're gonna erase from the one after this first. // If we get an early shutoff or cancellation, this will make it so that we don't return // an old version of the key on the next fetch. @@ -545,6 +556,9 @@ pub async fn remove_item( } } + // We're done, we now know the cache is in a good state + cache.unmark_dirty(); + Ok(()) } @@ -1206,4 +1220,87 @@ mod tests { assert_eq!(item.value, vec![i as u8; LENGHT_PER_KEY[i]]); } } + + #[test] + async fn remove_items() { + let mut flash = mock_flash::MockFlashBase::<4, 1, 4096>::new( + mock_flash::WriteCountCheck::Twice, + None, + true, + ); + let mut data_buffer = AlignedBuf([0; 128]); + const FLASH_RANGE: Range = 0x0000..0x4000; + + // Add some data to flash + for j in 0..10 { + for i in 0..24 { + let item = MockStorageItem { + key: i as u8, + value: vec![i as u8; j + 2], + }; + + store_item::<_, _>( + &mut flash, + FLASH_RANGE, + cache::NoCache::new(), + &mut data_buffer, + &item, + ) + .await + .unwrap(); + } + } + + for j in (0..24).rev() { + // Are all things still in flash that we expect? + for i in 0..=j { + assert!(fetch_item::( + &mut flash, + FLASH_RANGE, + cache::NoCache::new(), + &mut data_buffer, + i + ) + .await + .unwrap() + .is_some()); + } + + // Remove the item + remove_item::( + &mut flash, + FLASH_RANGE, + cache::NoCache::new(), + &mut data_buffer, + j, + ) + .await + .unwrap(); + + // Are all things still in flash that we expect? + for i in 0..j { + assert!(fetch_item::( + &mut flash, + FLASH_RANGE, + cache::NoCache::new(), + &mut data_buffer, + i + ) + .await + .unwrap() + .is_some()); + } + + assert!(fetch_item::( + &mut flash, + FLASH_RANGE, + cache::NoCache::new(), + &mut data_buffer, + j + ) + .await + .unwrap() + .is_none()); + } + } }