Preserve frozen stream recordings during TAR probing

This commit is contained in:
Adam Hathcock
2026-10-16 15:57:21 +01:00
parent 9bb74557d5
commit a83022ca17
2 changed files with 88 additions and 22 deletions

View File

@@ -15,6 +15,24 @@ public partial class TarReader
: IReaderOpenable
#endif
{
private static SharpCompressStream CreateReaderProbeStream(Stream stream, ReaderOptions options)
{
if (stream is SharpCompressStream { IsPassthrough: false })
{
// Keep the caller's recording scope and logical position intact, including
// frozen recordings whose buffered bytes have not been replayed yet.
return new SeekableSharpCompressStream(stream);
}
return SharpCompressStream.Create(
stream,
bufferSize: Math.Max(
options.RewindableBufferSize ?? 0,
TarWrapper.MaximumRewindBufferSize
)
);
}
private static Stream CreateProbeDecompressionStream(
Stream stream,
CompressionType compressionType,
@@ -101,20 +119,11 @@ public partial class TarReader
stream = SharpCompressStream.CreateNonDisposing(stream);
readerOptions = readerOptions with { LeaveStreamOpen = false };
}
var sharpCompressStream = SharpCompressStream.Create(
stream,
bufferSize: Math.Max(
readerOptions.RewindableBufferSize ?? 0,
TarWrapper.MaximumRewindBufferSize
)
);
var sharpCompressStream = CreateReaderProbeStream(stream, readerOptions);
var transferred = false;
try
{
if (!sharpCompressStream.IsRecording)
{
sharpCompressStream.StartRecording();
}
sharpCompressStream.StartRecording();
long pos = sharpCompressStream.Position;
foreach (var wrapper in TarWrapper.Wrappers)
{
@@ -235,17 +244,8 @@ public partial class TarReader
stream = SharpCompressStream.CreateNonDisposing(stream);
readerOptions = readerOptions with { LeaveStreamOpen = false };
}
var sharpCompressStream = SharpCompressStream.Create(
stream,
bufferSize: Math.Max(
readerOptions.RewindableBufferSize ?? 0,
TarWrapper.MaximumRewindBufferSize
)
);
if (!sharpCompressStream.IsRecording)
{
sharpCompressStream.StartRecording();
}
var sharpCompressStream = CreateReaderProbeStream(stream, readerOptions);
sharpCompressStream.StartRecording();
long pos = sharpCompressStream.Position;
foreach (var wrapper in TarWrapper.Wrappers)
{

View File

@@ -46,6 +46,72 @@ public class TarLifetimeTests : TestBase
}
}
[Theory]
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
[InlineData(true, true, true)]
public async Task Reader_Preserves_Frozen_Recording(
bool async,
bool stopRecording,
bool leaveOpen
)
{
using var archive = new MemoryStream();
using (
var writer = WriterFactory.OpenWriter(
archive,
ArchiveType.Tar,
new WriterOptions(CompressionType.None) { LeaveStreamOpen = true }
)
)
{
writer.Write("empty.txt", Stream.Null);
}
archive.Position = 0;
using var input = new ForwardOnlyStream(archive);
using var buffered = SharpCompressStream.Create(input, TarWrapper.MaximumRewindBufferSize);
buffered.StartRecording();
var header = new byte[512];
Assert.Equal(header.Length, buffered.Read(header, 0, header.Length));
if (stopRecording)
{
buffered.StopRecording();
}
else
{
buffered.Rewind(true);
}
Assert.Equal(0, buffered.Position);
var options = new ReaderOptions { LeaveStreamOpen = leaveOpen };
if (async)
{
await using var reader = await TarReader.OpenAsyncReader(
buffered,
options,
CancellationToken.None
);
Assert.True(await reader.MoveToNextEntryAsync(CancellationToken.None));
Assert.Equal("empty.txt", reader.Entry.Key);
Assert.Equal(0, reader.Entry.Size);
Assert.False(await reader.MoveToNextEntryAsync(CancellationToken.None));
}
else
{
using var reader = TarReader.OpenReader(buffered, options);
Assert.True(reader.MoveToNextEntry());
Assert.Equal("empty.txt", reader.Entry.Key);
Assert.Equal(0, reader.Entry.Size);
Assert.False(reader.MoveToNextEntry());
}
Assert.Equal(leaveOpen, input.CanRead);
Assert.Equal(leaveOpen, buffered.CanRead);
}
[Theory]
[InlineData("invalid", false)]
[InlineData("invalid", true)]