@@ -193,6 +193,7 @@ pub async fn unencapsulate(repo: &ostree::Repo, imgref: &OstreeImageReference) -
193
193
194
194
pub ( crate ) struct Decompressor {
195
195
inner : Box < dyn Read + Send + ' static > ,
196
+ finished : bool ,
196
197
}
197
198
198
199
impl Read for Decompressor {
@@ -203,6 +204,50 @@ impl Read for Decompressor {
203
204
204
205
impl Drop for Decompressor {
205
206
fn drop ( & mut self ) {
207
+ if self . finished {
208
+ return ;
209
+ }
210
+
211
+ // We really should not get here; users are required to call
212
+ // `finish()` to clean up the stream. But we'll give
213
+ // best-effort to clean things up nonetheless. If things go
214
+ // wrong, then panic, because we're in a bad state and it's
215
+ // likely that we end up with a broken pipe error or a
216
+ // deadlock.
217
+ self . _finish ( ) . expect ( "Decompressor::finish MUST be called" )
218
+ }
219
+ }
220
+
221
+ impl Decompressor {
222
+ /// Create a decompressor for this MIME type, given a stream of input.
223
+ pub ( crate ) fn new (
224
+ media_type : & oci_image:: MediaType ,
225
+ src : impl Read + Send + ' static ,
226
+ ) -> Result < Self > {
227
+ let r: Box < dyn std:: io:: Read + Send + ' static > = match media_type {
228
+ oci_image:: MediaType :: ImageLayerZstd => {
229
+ Box :: new ( zstd:: stream:: read:: Decoder :: new ( src) ?)
230
+ }
231
+ oci_image:: MediaType :: ImageLayerGzip => Box :: new ( flate2:: bufread:: GzDecoder :: new (
232
+ std:: io:: BufReader :: new ( src) ,
233
+ ) ) ,
234
+ oci_image:: MediaType :: ImageLayer => Box :: new ( src) ,
235
+ oci_image:: MediaType :: Other ( t) if t. as_str ( ) == DOCKER_TYPE_LAYER_TAR => Box :: new ( src) ,
236
+ o => anyhow:: bail!( "Unhandled layer type: {}" , o) ,
237
+ } ;
238
+ Ok ( Self {
239
+ inner : r,
240
+ finished : false ,
241
+ } )
242
+ }
243
+
244
+ pub ( crate ) fn finish ( mut self ) -> Result < ( ) > {
245
+ self . _finish ( )
246
+ }
247
+
248
+ fn _finish ( & mut self ) -> Result < ( ) > {
249
+ self . finished = true ;
250
+
206
251
// We need to make sure to flush out the decompressor and/or
207
252
// tar stream here. For tar, we might not read through the
208
253
// entire stream, because the archive has zero-block-markers
@@ -219,29 +264,14 @@ impl Drop for Decompressor {
219
264
// https://github.com/bootc-dev/bootc/issues/1204
220
265
221
266
let mut sink = std:: io:: sink ( ) ;
222
- match std:: io:: copy ( & mut self . inner , & mut sink) {
223
- Err ( e ) => tracing :: debug! ( "Ignoring error while dropping decompressor: {e}" ) ,
224
- Ok ( 0 ) => { /* We already read everything and are happy */ }
225
- Ok ( n ) => tracing:: debug!( "Read extra {n} bytes at end of decompressor stream" ) ,
267
+ let n = std:: io:: copy ( & mut self . inner , & mut sink) ? ;
268
+
269
+ if n > 0 {
270
+ tracing:: debug!( "Read extra {n} bytes at end of decompressor stream" ) ;
226
271
}
227
- }
228
- }
229
272
230
- /// Create a decompressor for this MIME type, given a stream of input.
231
- pub ( crate ) fn decompressor (
232
- media_type : & oci_image:: MediaType ,
233
- src : impl Read + Send + ' static ,
234
- ) -> Result < Decompressor > {
235
- let r: Box < dyn std:: io:: Read + Send + ' static > = match media_type {
236
- oci_image:: MediaType :: ImageLayerZstd => Box :: new ( zstd:: stream:: read:: Decoder :: new ( src) ?) ,
237
- oci_image:: MediaType :: ImageLayerGzip => Box :: new ( flate2:: bufread:: GzDecoder :: new (
238
- std:: io:: BufReader :: new ( src) ,
239
- ) ) ,
240
- oci_image:: MediaType :: ImageLayer => Box :: new ( src) ,
241
- oci_image:: MediaType :: Other ( t) if t. as_str ( ) == DOCKER_TYPE_LAYER_TAR => Box :: new ( src) ,
242
- o => anyhow:: bail!( "Unhandled layer type: {}" , o) ,
243
- } ;
244
- Ok ( Decompressor { inner : r } )
273
+ Ok ( ( ) )
274
+ }
245
275
}
246
276
247
277
/// A wrapper for [`get_blob`] which fetches a layer and decompresses it.
@@ -305,3 +335,31 @@ pub(crate) async fn fetch_layer<'a>(
305
335
Ok ( ( Box :: new ( blob) , Either :: Right ( driver) , media_type) )
306
336
}
307
337
}
338
+
339
+ #[ cfg( test) ]
340
+ mod tests {
341
+ use super :: * ;
342
+
343
+ struct BrokenPipe ;
344
+
345
+ impl Read for BrokenPipe {
346
+ fn read ( & mut self , _buf : & mut [ u8 ] ) -> std:: io:: Result < usize > {
347
+ std:: io:: Result :: Err ( std:: io:: ErrorKind :: BrokenPipe . into ( ) )
348
+ }
349
+ }
350
+
351
+ #[ test]
352
+ #[ should_panic( expected = "Decompressor::finish MUST be called" ) ]
353
+ fn test_drop_decompressor_with_finish_error_should_panic ( ) {
354
+ let broken = BrokenPipe ;
355
+ let d = Decompressor :: new ( & oci_image:: MediaType :: ImageLayer , broken) . unwrap ( ) ;
356
+ drop ( d)
357
+ }
358
+
359
+ #[ test]
360
+ fn test_drop_decompressor_with_successful_finish ( ) {
361
+ let empty = std:: io:: empty ( ) ;
362
+ let d = Decompressor :: new ( & oci_image:: MediaType :: ImageLayer , empty) . unwrap ( ) ;
363
+ drop ( d)
364
+ }
365
+ }
0 commit comments