Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ internal sealed partial class TarHeader
private const int RootUidGid = 0;
private const string RootUNameGName = "root";

private void WriteWithSeekableDataStream(TarEntryFormat format, Stream archiveStream, Span<byte> buffer)
{
Debug.Assert(format is > TarEntryFormat.Unknown and <= TarEntryFormat.Gnu);
Debug.Assert(_dataStream is null || _dataStream.CanSeek);

_size = GetTotalDataBytesToWrite();
WriteFieldsToBuffer(format, buffer);
archiveStream.Write(buffer);

if (_dataStream is not null)
{
WriteData(archiveStream, _dataStream);
}
}

// Writes the entry in the order required to be able to obtain the seekable data stream size.
private async ValueTask WriteWithSeekableDataStreamCoreAsync<TAdapter>(TarEntryFormat format, Stream archiveStream, Memory<byte> buffer, CancellationToken cancellationToken)
where TAdapter : IReadWriteAdapter
Expand All @@ -50,6 +65,44 @@ private async ValueTask WriteWithSeekableDataStreamCoreAsync<TAdapter>(TarEntryF
}
}

private void WriteWithUnseekableDataStream(TarEntryFormat format, Stream destinationStream, Span<byte> buffer, bool shouldAdvanceToEnd)
{
Debug.Assert(destinationStream.CanSeek);
Debug.Assert(_dataStream is not null);
Debug.Assert(!_dataStream.CanSeek);

long headerStartPosition = destinationStream.Position;

ushort dataLocation = format switch
{
TarEntryFormat.V7 => FieldLocations.V7Data,
TarEntryFormat.Ustar or TarEntryFormat.Pax => FieldLocations.PosixData,
TarEntryFormat.Gnu => FieldLocations.GnuData,
_ => throw new ArgumentOutOfRangeException(nameof(format))
};

long dataStartPosition = headerStartPosition + dataLocation;
_dataOffset = dataStartPosition;

destinationStream.Seek(dataLocation, SeekOrigin.Current);
_dataStream.CopyTo(destinationStream);

long dataEndPosition = destinationStream.Position;
_size = dataEndPosition - dataStartPosition;
WriteEmptyPadding(destinationStream);

long endOfHeaderPosition = destinationStream.Position;
destinationStream.Position = headerStartPosition;

WriteFieldsToBuffer(format, buffer);
destinationStream.Write(buffer);

if (shouldAdvanceToEnd)
{
destinationStream.Position = endOfHeaderPosition;
}
}

// Writes into the specified destination stream the entry in the order required to be able to obtain the unseekable data stream size.
private async ValueTask WriteWithUnseekableDataStreamCoreAsync<TAdapter>(TarEntryFormat format, Stream destinationStream, Memory<byte> buffer, bool shouldAdvanceToEnd, CancellationToken cancellationToken)
where TAdapter : IReadWriteAdapter
Expand Down Expand Up @@ -127,6 +180,12 @@ private void WriteUstarFieldsToBuffer(Span<byte> buffer)
}

// Writes the current header as a PAX Global Extended Attributes entry into the archive stream.
internal void WriteAsPaxGlobalExtendedAttributes(Stream archiveStream, Span<byte> buffer, int globalExtendedAttributesEntryNumber)
{
VerifyGlobalExtendedAttributesDataIsValid(globalExtendedAttributesEntryNumber);
WriteAsPaxExtendedAttributes(archiveStream, buffer, ExtendedAttributes, isGea: true, globalExtendedAttributesEntryNumber);
}

internal ValueTask WriteAsPaxGlobalExtendedAttributesCoreAsync<TAdapter>(Stream archiveStream, Memory<byte> buffer, int globalExtendedAttributesEntryNumber, CancellationToken cancellationToken)
where TAdapter : IReadWriteAdapter
{
Expand All @@ -141,6 +200,20 @@ private void VerifyGlobalExtendedAttributesDataIsValid(int globalExtendedAttribu
Debug.Assert(globalExtendedAttributesEntryNumber >= 0);
}

internal void WriteAsV7(Stream archiveStream, Span<byte> buffer)
{
Debug.Assert(archiveStream.CanSeek || _dataStream is null || _dataStream.CanSeek);

if (archiveStream.CanSeek && _dataStream is { CanSeek: false })
{
WriteWithUnseekableDataStream(TarEntryFormat.V7, archiveStream, buffer, shouldAdvanceToEnd: true);
}
else
{
WriteWithSeekableDataStream(TarEntryFormat.V7, archiveStream, buffer);
}
}

internal ValueTask WriteAsV7CoreAsync<TAdapter>(Stream archiveStream, Memory<byte> buffer, CancellationToken cancellationToken)
where TAdapter : IReadWriteAdapter
{
Expand All @@ -155,6 +228,20 @@ internal ValueTask WriteAsV7CoreAsync<TAdapter>(Stream archiveStream, Memory<byt
return WriteWithSeekableDataStreamCoreAsync<TAdapter>(TarEntryFormat.V7, archiveStream, buffer, cancellationToken);
}

internal void WriteAsUstar(Stream archiveStream, Span<byte> buffer)
{
Debug.Assert(archiveStream.CanSeek || _dataStream is null || _dataStream.CanSeek);

if (archiveStream.CanSeek && _dataStream is { CanSeek: false })
{
WriteWithUnseekableDataStream(TarEntryFormat.Ustar, archiveStream, buffer, shouldAdvanceToEnd: true);
}
else
{
WriteWithSeekableDataStream(TarEntryFormat.Ustar, archiveStream, buffer);
}
}

internal ValueTask WriteAsUstarCoreAsync<TAdapter>(Stream archiveStream, Memory<byte> buffer, CancellationToken cancellationToken)
where TAdapter : IReadWriteAdapter
{
Expand All @@ -171,6 +258,37 @@ internal ValueTask WriteAsUstarCoreAsync<TAdapter>(Stream archiveStream, Memory<

// Writes the current header as a PAX entry into the archive stream.
// Makes sure to add the preceding extended attributes entry before the actual entry.
internal void WriteAsPax(Stream archiveStream, Span<byte> buffer)
{
Debug.Assert(archiveStream.CanSeek || _dataStream is null || _dataStream.CanSeek);
Debug.Assert(_typeFlag is not TarEntryType.GlobalExtendedAttributes);

TarHeader extendedAttributesHeader = new(TarEntryFormat.Pax);

if (archiveStream.CanSeek && _dataStream is { CanSeek: false })
{
using MemoryStream tempStream = new();
WriteWithUnseekableDataStream(TarEntryFormat.Pax, tempStream, buffer, shouldAdvanceToEnd: false);
tempStream.Position = 0;
buffer.Clear();

CollectExtendedAttributesFromStandardFieldsIfNeeded();
extendedAttributesHeader.WriteAsPaxExtendedAttributes(archiveStream, buffer, ExtendedAttributes, isGea: false, globalExtendedAttributesEntryNumber: -1);
buffer.Clear();

tempStream.CopyTo(archiveStream);
}
else
{
_size = GetTotalDataBytesToWrite();
CollectExtendedAttributesFromStandardFieldsIfNeeded();
extendedAttributesHeader.WriteAsPaxExtendedAttributes(archiveStream, buffer, ExtendedAttributes, isGea: false, globalExtendedAttributesEntryNumber: -1);
buffer.Clear();

WriteWithSeekableDataStream(TarEntryFormat.Pax, archiveStream, buffer);
}
}

internal async ValueTask WriteAsPaxCoreAsync<TAdapter>(Stream archiveStream, Memory<byte> buffer, CancellationToken cancellationToken)
where TAdapter : IReadWriteAdapter
{
Expand Down Expand Up @@ -222,6 +340,36 @@ internal async ValueTask WriteAsPaxCoreAsync<TAdapter>(Stream archiveStream, Mem

// Writes the current header as a Gnu entry into the archive stream.
// Makes sure to add the preceding LongLink and/or LongPath entries if necessary, before the actual entry.
internal void WriteAsGnu(Stream archiveStream, Span<byte> buffer)
{
Debug.Assert(archiveStream.CanSeek || _dataStream is null || _dataStream.CanSeek);

if (IsLinkNameTooLongForRegularField())
{
TarHeader longLinkHeader = GetGnuLongLinkMetadataHeader();
Debug.Assert(longLinkHeader._dataStream is not null && longLinkHeader._dataStream.CanSeek);
longLinkHeader.WriteWithSeekableDataStream(TarEntryFormat.Gnu, archiveStream, buffer);
buffer.Clear();
}

if (IsNameTooLongForRegularField())
{
TarHeader longPathHeader = GetGnuLongPathMetadataHeader();
Debug.Assert(longPathHeader._dataStream is not null && longPathHeader._dataStream.CanSeek);
longPathHeader.WriteWithSeekableDataStream(TarEntryFormat.Gnu, archiveStream, buffer);
buffer.Clear();
}

if (archiveStream.CanSeek && _dataStream is { CanSeek: false })
{
WriteWithUnseekableDataStream(TarEntryFormat.Gnu, archiveStream, buffer, shouldAdvanceToEnd: true);
}
else
{
WriteWithSeekableDataStream(TarEntryFormat.Gnu, archiveStream, buffer);
}
}

internal async ValueTask WriteAsGnuCoreAsync<TAdapter>(Stream archiveStream, Memory<byte> buffer, CancellationToken cancellationToken)
where TAdapter : IReadWriteAdapter
{
Expand Down Expand Up @@ -310,6 +458,13 @@ private void WriteGnuFieldsToBuffer(Span<byte> buffer)
}

// Writes the current header as a PAX Extended Attributes entry into the archive stream.
private void WriteAsPaxExtendedAttributes(Stream archiveStream, Span<byte> buffer, Dictionary<string, string> extendedAttributes, bool isGea, int globalExtendedAttributesEntryNumber)
{
WriteAsPaxExtendedAttributesShared(isGea, globalExtendedAttributesEntryNumber, extendedAttributes);
Debug.Assert(_dataStream is null || (extendedAttributes.Count > 0 && _dataStream.CanSeek));
WriteWithSeekableDataStream(TarEntryFormat.Pax, archiveStream, buffer);
}

private ValueTask WriteAsPaxExtendedAttributesCoreAsync<TAdapter>(Stream archiveStream, Memory<byte> buffer, Dictionary<string, string> extendedAttributes, bool isGea, int globalExtendedAttributesEntryNumber, CancellationToken cancellationToken)
where TAdapter : IReadWriteAdapter
{
Expand Down Expand Up @@ -607,6 +762,14 @@ private int WriteGnuFields(Span<byte> buffer)
return checksum;
}

private void WriteData(Stream archiveStream, Stream dataStream)
{
SetDataOffset(this, archiveStream);

dataStream.CopyTo(archiveStream);
WriteEmptyPadding(archiveStream);
}

// Writes the current header's data stream into the archive stream.
private async ValueTask WriteDataCoreAsync<TAdapter>(Stream archiveStream, Stream dataStream, CancellationToken cancellationToken)
where TAdapter : IReadWriteAdapter
Expand All @@ -618,6 +781,21 @@ private async ValueTask WriteDataCoreAsync<TAdapter>(Stream archiveStream, Strea
await WriteEmptyPaddingCoreAsync<TAdapter>(archiveStream, cancellationToken).ConfigureAwait(false);
}

private void WriteEmptyPadding(Stream archiveStream)
{
int paddingAfterData = TarHelpers.CalculatePadding(_size);
if (paddingAfterData != 0)
{
Debug.Assert(paddingAfterData <= TarHelpers.RecordSize);

Span<byte> zeros = stackalloc byte[TarHelpers.RecordSize];
zeros = zeros.Slice(0, paddingAfterData);
zeros.Clear();

archiveStream.Write(zeros);
}
}

// Calculates the padding for the current entry and writes it after the data.
private async ValueTask WriteEmptyPaddingCoreAsync<TAdapter>(Stream archiveStream, CancellationToken cancellationToken)
where TAdapter : IReadWriteAdapter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,7 @@ public void WriteEntry(TarEntry entry)
ArgumentNullException.ThrowIfNull(entry);
ValidateEntryLinkName(entry._header._typeFlag, entry._header._linkName);
ValidateStreamsSeekability(entry);

ValueTask vt = WriteEntryCoreAsync<SyncReadWriteAdapter>(entry, CancellationToken.None);
Debug.Assert(vt.IsCompleted, "Synchronous WriteEntry completed asynchronously.");
vt.GetAwaiter().GetResult();
WriteEntryInternal(entry);
}

/// <summary>
Expand Down Expand Up @@ -302,6 +299,44 @@ public Task WriteEntryAsync(TarEntry entry, CancellationToken cancellationToken
return WriteEntryCoreAsync<AsyncReadWriteAdapter>(entry, cancellationToken).AsTask();
}

private void WriteEntryInternal(TarEntry entry)
{
Span<byte> buffer = stackalloc byte[TarHelpers.RecordSize];
buffer.Clear();

switch (entry.Format)
{
case TarEntryFormat.V7:
entry._header.WriteAsV7(_archiveStream, buffer);
break;

case TarEntryFormat.Ustar:
entry._header.WriteAsUstar(_archiveStream, buffer);
break;

case TarEntryFormat.Pax:
if (entry._header._typeFlag is TarEntryType.GlobalExtendedAttributes)
{
entry._header.WriteAsPaxGlobalExtendedAttributes(_archiveStream, buffer, _nextGlobalExtendedAttributesEntryNumber++);
}
else
{
entry._header.WriteAsPax(_archiveStream, buffer);
}
break;

case TarEntryFormat.Gnu:
entry._header.WriteAsGnu(_archiveStream, buffer);
break;

default:
Debug.Assert(entry.Format == TarEntryFormat.Unknown, "Missing format handler");
throw new InvalidDataException(SR.Format(SR.TarInvalidFormat, Format));
}

_wroteEntries = true;
}

// Portion of the WriteEntry methods that rents a buffer and writes to the archive.
private async ValueTask WriteEntryCoreAsync<TAdapter>(TarEntry entry, CancellationToken cancellationToken)
where TAdapter : IReadWriteAdapter
Expand Down
Loading