Skip to content

Commit f491ca8

Browse files
committed
Fix LZMA bounded-output stream progress
Keep processing header notifications while output space remains, drain buffered output with empty input, and retain capped-output state for needs_input. Assisted-by: OpenAI Codex: model version unavailable
1 parent a605d1e commit f491ca8

1 file changed

Lines changed: 40 additions & 33 deletions

File tree

  • crates/common/src/compression

‎crates/common/src/compression/lzma.rs‎

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -350,14 +350,17 @@ impl LzmaStream {
350350
}
351351
}
352352

353-
fn process(&mut self, input: &[u8], output: &mut Vec<u8>) -> Result<Status, XzError> {
353+
fn process(&mut self, input: &[u8], output: &mut [u8]) -> Result<Status, XzError> {
354+
let previous_in = self.stream.total_in();
355+
let result = self.stream.process(input, output, Action::Run);
356+
let consumed = (self.stream.total_in() - previous_in) as usize;
354357
if self.track_header && self.header_collected < 8 {
355-
let count = (8 - usize::from(self.header_collected)).min(input.len());
358+
let count = (8 - usize::from(self.header_collected)).min(consumed);
356359
let start = usize::from(self.header_collected);
357360
self.header_buf[start..start + count].copy_from_slice(&input[..count]);
358361
self.header_collected += count as u8;
359362
}
360-
match self.stream.process_vec(input, output, Action::Run) {
363+
match result {
361364
Ok(Status::GetCheck) => {
362365
if self.header_collected >= 8 {
363366
self.check = i32::from(self.header_buf[7] & 0x0f);
@@ -377,35 +380,39 @@ fn decompress_chunks(
377380
chunks: &mut Chunker<'_>,
378381
stream: &mut LzmaStream,
379382
max_length: Option<usize>,
380-
) -> Result<(Vec<u8>, bool), XzError> {
381-
// No input to feed the decoder: not a stream end, so `eof` stays false and
382-
// the caller can still supply compressed data later.
383-
if chunks.is_empty() {
384-
return Ok((Vec::new(), false));
385-
}
383+
) -> Result<(Vec<u8>, bool, bool), Error> {
386384
let max_length = max_length.unwrap_or(usize::MAX);
387385
let mut output = Vec::new();
388-
'outer: loop {
386+
let mut block = vec![0; BUFSIZ.min(max_length)];
387+
loop {
389388
let chunk = chunks.chunk();
390-
loop {
391-
let additional = BUFSIZ.min(max_length - output.capacity());
392-
if additional == 0 {
393-
return Ok((output, false));
394-
}
395-
output.reserve_exact(additional);
396-
let previous_in = stream.stream.total_in();
397-
let result = stream.process(chunk, &mut output);
398-
let consumed = (stream.stream.total_in() - previous_in) as usize;
399-
chunks.advance(consumed);
400-
let status = result?;
401-
if status == Status::StreamEnd || chunks.is_empty() {
402-
output.shrink_to_fit();
403-
return Ok((output, status == Status::StreamEnd));
404-
}
405-
if !chunk.is_empty() && consumed == 0 {
406-
continue;
389+
let available = block.len().min(max_length - output.len());
390+
let previous_in = stream.stream.total_in();
391+
let previous_out = stream.stream.total_out();
392+
let result = stream.process(chunk, &mut block[..available]);
393+
let consumed = (stream.stream.total_in() - previous_in) as usize;
394+
let produced = (stream.stream.total_out() - previous_out) as usize;
395+
chunks.advance(consumed);
396+
let status = result?;
397+
output.extend_from_slice(&block[..produced]);
398+
let filled = produced == available;
399+
400+
// liblzma reports BUF_ERROR after consecutive calls without progress.
401+
// Exhausted input with output space is just a request for more input.
402+
if status == Status::MemNeeded && !(consumed == chunk.len() && !filled) {
403+
return Err(Error::Lzma("Insufficient buffer space".to_owned()));
404+
}
405+
if status == Status::StreamEnd {
406+
return Ok((output, true, false));
407+
}
408+
// Check output before input: liblzma may have buffered output even
409+
// after consuming the last supplied input byte.
410+
if filled {
411+
if output.len() == max_length {
412+
return Ok((output, false, true));
407413
}
408-
continue 'outer;
414+
} else if chunks.is_empty() {
415+
return Ok((output, false, false));
409416
}
410417
}
411418
}
@@ -477,9 +484,9 @@ impl Decompressor {
477484
let mut chunks = Chunker::chain(input_buffer, data);
478485
let previous_len = chunks.len();
479486
let result = decompress_chunks(&mut chunks, stream, max_length);
480-
let stream_end = match &result {
481-
Ok((_, stream_end)) => *stream_end,
482-
Err(_) => false,
487+
let (stream_end, capped) = match &result {
488+
Ok((_, stream_end, capped)) => (*stream_end, *capped),
489+
Err(_) => (false, false),
483490
};
484491
let consumed = previous_len - chunks.len();
485492
self.eof |= stream_end;
@@ -490,7 +497,7 @@ impl Decompressor {
490497
}
491498
} else if chunks.is_empty() {
492499
input_buffer.clear();
493-
self.needs_input = true;
500+
self.needs_input = !capped;
494501
} else {
495502
self.needs_input = false;
496503
if let Some(consumed_from_data) = consumed.checked_sub(input_buffer.len()) {
@@ -501,7 +508,7 @@ impl Decompressor {
501508
input_buffer.extend_from_slice(data);
502509
}
503510
}
504-
result.map(|(output, _)| output).map_err(Error::from)
511+
result.map(|(output, _, _)| output)
505512
}
506513

507514
#[must_use]

0 commit comments

Comments
 (0)