@@ -75,6 +75,7 @@ impl SnapshotStore {
7575 message : Option < & str > ,
7676 ) -> Result < ConfigSnapshot , SnapshotError > {
7777 self . validate_root ( ) ?;
78+ self . ensure_store_capacity ( ) ?;
7879 config. validate ( ) . map_err ( |error| {
7980 SnapshotError :: Config ( crate :: config:: ConfigLoadError :: Validate ( error) )
8081 } ) ?;
@@ -260,6 +261,39 @@ impl SnapshotStore {
260261 Ok ( ( ) )
261262 }
262263
264+ fn ensure_store_capacity ( & self ) -> Result < ( ) , SnapshotError > {
265+ if !self . safe_existing_root ( ) ? || !self . safe_existing_configs_dir ( ) ? {
266+ return Ok ( ( ) ) ;
267+ }
268+
269+ let mut snapshots = 0_usize ;
270+ for entry in fs:: read_dir ( self . configs_dir ( ) ) . map_err ( SnapshotError :: Io ) ? {
271+ let entry = entry. map_err ( SnapshotError :: Io ) ?;
272+ let path = entry. path ( ) ;
273+ if path. extension ( ) . and_then ( |extension| extension. to_str ( ) ) != Some ( "toml" ) {
274+ continue ;
275+ }
276+ let Some ( stem) = path. file_stem ( ) . and_then ( |stem| stem. to_str ( ) ) else {
277+ continue ;
278+ } ;
279+ if stem. ends_with ( ".meta" ) {
280+ continue ;
281+ }
282+ snapshots = snapshots. saturating_add ( 1 ) ;
283+ if snapshots >= MAX_SNAPSHOT_STORE_ENTRIES {
284+ return Err ( SnapshotError :: Io ( io:: Error :: new (
285+ io:: ErrorKind :: StorageFull ,
286+ format ! (
287+ "snapshot store {} reached the {} snapshot limit" ,
288+ self . root. display( ) ,
289+ MAX_SNAPSHOT_STORE_ENTRIES
290+ ) ,
291+ ) ) ) ;
292+ }
293+ }
294+ Ok ( ( ) )
295+ }
296+
263297 fn validate_root ( & self ) -> Result < ( ) , SnapshotError > {
264298 if self . root . as_os_str ( ) . is_empty ( )
265299 || self
@@ -755,6 +789,44 @@ mod tests {
755789 ) ;
756790 }
757791
792+ #[ test]
793+ fn snapshot_config_rejects_full_snapshot_store_before_writing ( ) {
794+ let dir = TestDir :: new ( "snapshot-capacity-before-write" ) ;
795+ let store = SnapshotStore :: new ( dir. path ( ) ) ;
796+ let configs = dir. child ( "configs" ) ;
797+ std:: fs:: create_dir ( & configs) . unwrap ( ) ;
798+ for index in 0 ..super :: MAX_SNAPSHOT_STORE_ENTRIES {
799+ std:: fs:: write ( safe_child_path ( & configs, & format ! ( "s{index:04}.toml" ) ) , b"" ) . unwrap ( ) ;
800+ }
801+
802+ let error = store
803+ . snapshot_config ( & Config :: default ( ) , Some ( "should not write" ) )
804+ . unwrap_err ( ) ;
805+
806+ assert ! (
807+ matches!( error, SnapshotError :: Io ( error) if error. kind( ) == std:: io:: ErrorKind :: StorageFull )
808+ ) ;
809+ let created_configs = std:: fs:: read_dir ( & configs)
810+ . unwrap ( )
811+ . filter ( |entry| {
812+ entry
813+ . as_ref ( )
814+ . ok ( )
815+ . and_then ( |entry| {
816+ entry
817+ . path ( )
818+ . extension ( )
819+ . and_then ( |value| value. to_str ( ) )
820+ . map ( str:: to_owned)
821+ } )
822+ . as_deref ( )
823+ == Some ( "toml" )
824+ } )
825+ . count ( ) ;
826+ assert_eq ! ( created_configs, super :: MAX_SNAPSHOT_STORE_ENTRIES ) ;
827+ assert ! ( store. current_id( ) . unwrap( ) . is_none( ) ) ;
828+ }
829+
758830 #[ test]
759831 fn current_snapshot_loads_current_pointer ( ) {
760832 let dir = TestDir :: new ( "snapshot-current-load" ) ;
0 commit comments