@@ -25,18 +25,25 @@ struct GenerationState {
2525 hmac_sha256 : Option < String > ,
2626}
2727
28+ struct GenerationObservation {
29+ maximum : u64 ,
30+ legacy_ids : Vec < String > ,
31+ allow_missing_state : bool ,
32+ }
33+
2834impl SnapshotStore {
2935 pub ( crate ) fn allocate_generation_unlocked ( & self ) -> Result < u64 , SnapshotError > {
30- let observed_max = self . observed_max_generation ( true ) ?;
36+ let observation = self . observe_generations ( ) ?;
3137 let current = match read_optional_regular_file_to_string_with_limit (
3238 & self . generation_path ( ) ,
3339 MAX_GENERATION_STATE_BYTES as u64 ,
3440 ) ? {
3541 Some ( raw) => self . decode_generation_state ( & raw ) ?,
36- None if observed_max == 0 => 0 ,
42+ None if observation. maximum == 0 => 0 ,
43+ None if observation. allow_missing_state => observation. maximum ,
3744 None => return Err ( SnapshotError :: GenerationStateInvalid ) ,
3845 } ;
39- if current < observed_max {
46+ if current < observation . maximum {
4047 return Err ( SnapshotError :: GenerationStateInvalid ) ;
4148 }
4249 let next = current
@@ -47,24 +54,29 @@ impl SnapshotStore {
4754 return Err ( SnapshotError :: GenerationStateInvalid ) ;
4855 }
4956 write_atomically ( & self . generation_path ( ) , raw. as_bytes ( ) ) ?;
57+ if let Some ( key) = self . integrity . as_deref ( ) {
58+ for id in observation. legacy_ids {
59+ self . migrate_legacy_manifest ( & id, key) ?;
60+ }
61+ }
5062 Ok ( next)
5163 }
5264
5365 pub ( crate ) fn verify_generation_state ( & self ) -> Result < ( ) , SnapshotError > {
54- let observed_max = self . observed_max_generation ( false ) ?;
66+ let observation = self . observe_generations ( ) ?;
5567 let Some ( raw) = read_optional_regular_file_to_string_with_limit (
5668 & self . generation_path ( ) ,
5769 MAX_GENERATION_STATE_BYTES as u64 ,
5870 ) ?
5971 else {
60- return if observed_max == 0 {
72+ return if observation . maximum == 0 || observation . allow_missing_state {
6173 Ok ( ( ) )
6274 } else {
6375 Err ( SnapshotError :: GenerationStateInvalid )
6476 } ;
6577 } ;
6678 let persisted = self . decode_generation_state ( & raw ) ?;
67- if persisted < observed_max {
79+ if persisted < observation . maximum {
6880 return Err ( SnapshotError :: GenerationStateInvalid ) ;
6981 }
7082 Ok ( ( ) )
@@ -122,22 +134,31 @@ impl SnapshotStore {
122134 self . root ( ) . join ( "generation.toml" )
123135 }
124136
125- fn observed_max_generation ( & self , migrate_legacy : bool ) -> Result < u64 , SnapshotError > {
137+ fn observe_generations ( & self ) -> Result < GenerationObservation , SnapshotError > {
126138 if !self . safe_existing_configs_dir ( ) ? {
127- return Ok ( 0 ) ;
139+ return Ok ( GenerationObservation {
140+ maximum : 0 ,
141+ legacy_ids : Vec :: new ( ) ,
142+ allow_missing_state : false ,
143+ } ) ;
128144 }
129145 if let Some ( key) = self . integrity . as_deref ( ) {
130- return self . observed_authenticated_generation ( key, migrate_legacy ) ;
146+ return self . observe_authenticated_generations ( key) ;
131147 }
132- self . observed_unverified_generation ( )
148+ Ok ( GenerationObservation {
149+ maximum : self . observed_unverified_generation ( ) ?,
150+ legacy_ids : Vec :: new ( ) ,
151+ allow_missing_state : false ,
152+ } )
133153 }
134154
135- fn observed_authenticated_generation (
155+ fn observe_authenticated_generations (
136156 & self ,
137157 key : & SnapshotIntegrityKey ,
138- migrate_legacy : bool ,
139- ) -> Result < u64 , SnapshotError > {
158+ ) -> Result < GenerationObservation , SnapshotError > {
140159 let mut maximum = 0 ;
160+ let mut legacy_ids = Vec :: new ( ) ;
161+ let mut snapshot_count = 0_usize ;
141162 for entry in fs:: read_dir ( self . configs_dir ( ) ) . map_err ( SnapshotError :: Io ) ? {
142163 let path = entry. map_err ( SnapshotError :: Io ) ?. path ( ) ;
143164 if path. extension ( ) . and_then ( |extension| extension. to_str ( ) ) != Some ( "toml" ) {
@@ -149,6 +170,7 @@ impl SnapshotStore {
149170 if id. ends_with ( ".meta" ) || id. ends_with ( ".integrity" ) {
150171 continue ;
151172 }
173+ snapshot_count = snapshot_count. saturating_add ( 1 ) ;
152174 validate_snapshot_id ( id) . map_err ( |_| SnapshotError :: GenerationStateInvalid ) ?;
153175 if !regular_snapshot_file_exists ( & path) ? {
154176 return Err ( SnapshotError :: GenerationStateInvalid ) ;
@@ -164,21 +186,27 @@ impl SnapshotStore {
164186 key. verify_generation_witness ( id, witness)
165187 }
166188 SnapshotIntegrityManifest :: V1 ( _) => {
167- self . verify_legacy_generation ( id, key, & manifest, migrate_legacy)
189+ let generation = self . verify_legacy_generation ( id, key, & manifest) ?;
190+ legacy_ids. push ( id. to_owned ( ) ) ;
191+ Ok ( generation)
168192 }
169193 }
170194 . map_err ( generation_scan_error) ?;
171195 maximum = maximum. max ( generation) ;
172196 }
173- Ok ( maximum)
197+ let allow_missing_state = snapshot_count != 0 && legacy_ids. len ( ) == snapshot_count;
198+ Ok ( GenerationObservation {
199+ maximum,
200+ legacy_ids,
201+ allow_missing_state,
202+ } )
174203 }
175204
176205 fn verify_legacy_generation (
177206 & self ,
178207 id : & str ,
179208 key : & SnapshotIntegrityKey ,
180209 manifest : & SnapshotIntegrityManifest ,
181- migrate : bool ,
182210 ) -> Result < u64 , SnapshotError > {
183211 let config = read_regular_file_to_string_with_limit (
184212 & self . config_path ( id) ,
@@ -198,22 +226,54 @@ impl SnapshotStore {
198226 metadata. generation ,
199227 manifest,
200228 ) ?;
201- if migrate {
202- let witness = key. manifest (
203- id,
204- config. as_bytes ( ) ,
205- metadata_raw. as_bytes ( ) ,
206- metadata. generation ,
207- ) ?;
208- let raw = toml:: to_string_pretty ( & witness) . map_err ( SnapshotError :: Encode ) ?;
209- if raw. len ( ) as u64 > MAX_INTEGRITY_MANIFEST_BYTES {
210- return Err ( SnapshotError :: GenerationStateInvalid ) ;
211- }
212- write_atomically ( & self . integrity_path ( id) , raw. as_bytes ( ) ) ?;
213- }
214229 Ok ( metadata. generation )
215230 }
216231
232+ fn migrate_legacy_manifest (
233+ & self ,
234+ id : & str ,
235+ key : & SnapshotIntegrityKey ,
236+ ) -> Result < ( ) , SnapshotError > {
237+ let raw = read_regular_file_to_string_with_limit (
238+ & self . integrity_path ( id) ,
239+ MAX_INTEGRITY_MANIFEST_BYTES ,
240+ ) ?;
241+ let manifest: SnapshotIntegrityManifest =
242+ toml:: from_str ( & raw ) . map_err ( SnapshotError :: Decode ) ?;
243+ if matches ! ( manifest, SnapshotIntegrityManifest :: V2 ( _) ) {
244+ return Ok ( ( ) ) ;
245+ }
246+ let config = read_regular_file_to_string_with_limit (
247+ & self . config_path ( id) ,
248+ crate :: store_fs:: MAX_SNAPSHOT_FILE_BYTES ,
249+ ) ?;
250+ let metadata_raw = read_regular_file_to_string_with_limit (
251+ & self . metadata_path ( id) ,
252+ MAX_SNAPSHOT_METADATA_BYTES ,
253+ ) ?;
254+ let metadata: SnapshotMetadata =
255+ toml:: from_str ( & metadata_raw) . map_err ( SnapshotError :: Decode ) ?;
256+ validate_snapshot_metadata ( & metadata, id) ?;
257+ key. verify (
258+ id,
259+ config. as_bytes ( ) ,
260+ metadata_raw. as_bytes ( ) ,
261+ metadata. generation ,
262+ & manifest,
263+ ) ?;
264+ let witness = key. manifest (
265+ id,
266+ config. as_bytes ( ) ,
267+ metadata_raw. as_bytes ( ) ,
268+ metadata. generation ,
269+ ) ?;
270+ let raw = toml:: to_string_pretty ( & witness) . map_err ( SnapshotError :: Encode ) ?;
271+ if raw. len ( ) as u64 > MAX_INTEGRITY_MANIFEST_BYTES {
272+ return Err ( SnapshotError :: GenerationStateInvalid ) ;
273+ }
274+ write_atomically ( & self . integrity_path ( id) , raw. as_bytes ( ) )
275+ }
276+
217277 fn observed_unverified_generation ( & self ) -> Result < u64 , SnapshotError > {
218278 let mut maximum = 0 ;
219279 for entry in fs:: read_dir ( self . configs_dir ( ) ) . map_err ( SnapshotError :: Io ) ? {
0 commit comments