diff --git a/crates/heal/src/heal/mrf_queue/snapshot/migration.rs b/crates/heal/src/heal/mrf_queue/snapshot/migration.rs
index d83f54612..f6359da15 100644
--- a/crates/heal/src/heal/mrf_queue/snapshot/migration.rs
+++ b/crates/heal/src/heal/mrf_queue/snapshot/migration.rs
@@ -29,6 +29,7 @@ const CLAIM: &str = ".heal-mrf-import-claim.bin";
pub struct MigrationLimits {
pub max_bytes: usize,
pub max_records: usize,
+ pub max_sources: usize,
}
#[derive(Debug, thiserror::Error)]
@@ -49,7 +50,7 @@ pub enum MigrationError {
Claimed,
}
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
enum LegacyPath {
Scoped,
Mirror,
@@ -107,9 +108,6 @@ impl PendingMigration {
offset = end;
}
}
- if records.is_empty() {
- return Err(MigrationError::Empty);
- }
Ok(records.into_iter().collect())
}
@@ -120,6 +118,14 @@ impl PendingMigration {
if self.sources.is_empty() || self.sources.len() > MAX_DISKS * 2 {
return Err(MigrationError::Invalid);
}
+ if self
+ .sources
+ .len()
+ .checked_add(self.inherited.len())
+ .is_none_or(|count| count > limits.max_sources)
+ {
+ return Err(SnapshotError::TooLarge.into());
+ }
// Bound the raw input before allocating the JSON representation.
let total = self
.sources
@@ -247,14 +253,21 @@ struct Staged {
slot: usize,
}
-async fn read_staged(disks: &[EcstoreDiskStore], limits: MigrationLimits) -> Result