diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs index 2673bd4e21aa16..fc9a4183a48571 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs @@ -44,19 +44,24 @@ internal sealed partial class TarHeader // The four supported formats have a header that fits in the default record size byte[] rented = ArrayPool.Shared.Rent(minimumLength: TarHelpers.RecordSize); - Memory buffer = rented.AsMemory(0, TarHelpers.RecordSize); // minimumLength means the array could've been larger + try + { + Memory buffer = rented.AsMemory(0, TarHelpers.RecordSize); // minimumLength means the array could've been larger - await archiveStream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false); + await archiveStream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false); - TarHeader? header = TryReadAttributes(initialFormat, buffer.Span, archiveStream); - if (header != null && processDataBlock) + TarHeader? header = TryReadAttributes(initialFormat, buffer.Span, archiveStream); + if (header != null && processDataBlock) + { + await header.ProcessDataBlockAsync(archiveStream, copyData, cancellationToken).ConfigureAwait(false); + } + + return header; + } + finally { - await header.ProcessDataBlockAsync(archiveStream, copyData, cancellationToken).ConfigureAwait(false); + ArrayPool.Shared.Return(rented); } - - ArrayPool.Shared.Return(rented); - - return header; } private static TarHeader? TryReadAttributes(TarEntryFormat initialFormat, ReadOnlySpan buffer, Stream archiveStream) @@ -691,17 +696,22 @@ private void ReadExtendedAttributesBlock(Stream archiveStream) ValidateSize(); byte[]? buffer = null; - Span span = (ulong)size <= 256 ? - stackalloc byte[256] : - (buffer = ArrayPool.Shared.Rent((int)size)); - span = span.Slice(0, (int)size); - - archiveStream.ReadExactly(span); - ReadExtendedAttributesFromBuffer(span, _name); + try + { + Span span = (ulong)size <= 256 ? + stackalloc byte[256] : + (buffer = ArrayPool.Shared.Rent((int)size)); + span = span.Slice(0, (int)size); - if (buffer is not null) + archiveStream.ReadExactly(span); + ReadExtendedAttributesFromBuffer(span, _name); + } + finally { - ArrayPool.Shared.Return(buffer); + if (buffer is not null) + { + ArrayPool.Shared.Return(buffer); + } } } } @@ -716,12 +726,17 @@ private async ValueTask ReadExtendedAttributesBlockAsync(Stream archiveStream, C { ValidateSize(); byte[] buffer = ArrayPool.Shared.Rent((int)_size); - Memory memory = buffer.AsMemory(0, (int)_size); - - await archiveStream.ReadExactlyAsync(memory, cancellationToken).ConfigureAwait(false); - ReadExtendedAttributesFromBuffer(memory.Span, _name); + try + { + Memory memory = buffer.AsMemory(0, (int)_size); - ArrayPool.Shared.Return(buffer); + await archiveStream.ReadExactlyAsync(memory, cancellationToken).ConfigureAwait(false); + ReadExtendedAttributesFromBuffer(memory.Span, _name); + } + finally + { + ArrayPool.Shared.Return(buffer); + } } } @@ -765,17 +780,22 @@ private void ReadGnuLongPathDataBlock(Stream archiveStream) ValidateSize(); byte[]? buffer = null; - Span span = (ulong)size <= 256 ? - stackalloc byte[256] : - (buffer = ArrayPool.Shared.Rent((int)size)); - span = span.Slice(0, (int)size); - - archiveStream.ReadExactly(span); - ReadGnuLongPathDataFromBuffer(span); + try + { + Span span = (ulong)size <= 256 ? + stackalloc byte[256] : + (buffer = ArrayPool.Shared.Rent((int)size)); + span = span.Slice(0, (int)size); - if (buffer is not null) + archiveStream.ReadExactly(span); + ReadGnuLongPathDataFromBuffer(span); + } + finally { - ArrayPool.Shared.Return(buffer); + if (buffer is not null) + { + ArrayPool.Shared.Return(buffer); + } } } } @@ -791,12 +811,17 @@ private async ValueTask ReadGnuLongPathDataBlockAsync(Stream archiveStream, Canc { ValidateSize(); byte[] buffer = ArrayPool.Shared.Rent((int)_size); - Memory memory = buffer.AsMemory(0, (int)_size); - - await archiveStream.ReadExactlyAsync(memory, cancellationToken).ConfigureAwait(false); - ReadGnuLongPathDataFromBuffer(memory.Span); + try + { + Memory memory = buffer.AsMemory(0, (int)_size); - ArrayPool.Shared.Return(buffer); + await archiveStream.ReadExactlyAsync(memory, cancellationToken).ConfigureAwait(false); + ReadGnuLongPathDataFromBuffer(memory.Span); + } + finally + { + ArrayPool.Shared.Return(buffer); + } } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs index c3d166da29389f..cac44f3a716552 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs @@ -851,11 +851,16 @@ private async Task WriteDataAsync(Stream archiveStream, Stream dataStream, Cance if (paddingAfterData != 0) { byte[] buffer = ArrayPool.Shared.Rent(paddingAfterData); - Array.Clear(buffer, 0, paddingAfterData); - - await archiveStream.WriteAsync(buffer.AsMemory(0, paddingAfterData), cancellationToken).ConfigureAwait(false); + try + { + Array.Clear(buffer, 0, paddingAfterData); - ArrayPool.Shared.Return(buffer); + await archiveStream.WriteAsync(buffer.AsMemory(0, paddingAfterData), cancellationToken).ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(buffer); + } } } @@ -868,58 +873,63 @@ private async Task WriteDataAsync(Stream archiveStream, Stream dataStream, Cance byte[]? buffer = null; Span span = stackalloc byte[512]; - if (extendedAttributes.Count > 0) + try { - dataStream = new MemoryStream(); - - foreach ((string attribute, string value) in extendedAttributes) + if (extendedAttributes.Count > 0) { - // Generates an extended attribute key value pair string saved into a byte array, following the ISO/IEC 10646-1:2000 standard UTF-8 encoding format. - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html - - // The format is: - // "XX attribute=value\n" - // where "XX" is the number of characters in the entry, including those required for the count itself. - // If prepending the length digits increases the number of digits, we need to expand. - int length = 3 + Encoding.UTF8.GetByteCount(attribute) + Encoding.UTF8.GetByteCount(value); - int originalDigitCount = CountDigits(length), newDigitCount; - length += originalDigitCount; - while ((newDigitCount = CountDigits(length)) != originalDigitCount) - { - length += newDigitCount - originalDigitCount; - originalDigitCount = newDigitCount; - } - Debug.Assert(length == CountDigits(length) + 3 + Encoding.UTF8.GetByteCount(attribute) + Encoding.UTF8.GetByteCount(value)); + dataStream = new MemoryStream(); - // Get a large enough buffer if we don't already have one. - if (span.Length < length) + foreach ((string attribute, string value) in extendedAttributes) { - if (buffer is not null) + // Generates an extended attribute key value pair string saved into a byte array, following the ISO/IEC 10646-1:2000 standard UTF-8 encoding format. + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html + + // The format is: + // "XX attribute=value\n" + // where "XX" is the number of characters in the entry, including those required for the count itself. + // If prepending the length digits increases the number of digits, we need to expand. + int length = 3 + Encoding.UTF8.GetByteCount(attribute) + Encoding.UTF8.GetByteCount(value); + int originalDigitCount = CountDigits(length), newDigitCount; + length += originalDigitCount; + while ((newDigitCount = CountDigits(length)) != originalDigitCount) { - ArrayPool.Shared.Return(buffer); + length += newDigitCount - originalDigitCount; + originalDigitCount = newDigitCount; } - span = buffer = ArrayPool.Shared.Rent(length); + Debug.Assert(length == CountDigits(length) + 3 + Encoding.UTF8.GetByteCount(attribute) + Encoding.UTF8.GetByteCount(value)); + + // Get a large enough buffer if we don't already have one. + if (span.Length < length) + { + if (buffer is not null) + { + ArrayPool.Shared.Return(buffer); + } + span = buffer = ArrayPool.Shared.Rent(length); + } + + // Format the contents. + bool formatted = Utf8Formatter.TryFormat(length, span, out int bytesWritten); + Debug.Assert(formatted); + span[bytesWritten++] = (byte)' '; + bytesWritten += Encoding.UTF8.GetBytes(attribute, span.Slice(bytesWritten)); + span[bytesWritten++] = (byte)'='; + bytesWritten += Encoding.UTF8.GetBytes(value, span.Slice(bytesWritten)); + span[bytesWritten++] = (byte)'\n'; + + // Write it to the stream. + dataStream.Write(span.Slice(0, bytesWritten)); } - // Format the contents. - bool formatted = Utf8Formatter.TryFormat(length, span, out int bytesWritten); - Debug.Assert(formatted); - span[bytesWritten++] = (byte)' '; - bytesWritten += Encoding.UTF8.GetBytes(attribute, span.Slice(bytesWritten)); - span[bytesWritten++] = (byte)'='; - bytesWritten += Encoding.UTF8.GetBytes(value, span.Slice(bytesWritten)); - span[bytesWritten++] = (byte)'\n'; - - // Write it to the stream. - dataStream.Write(span.Slice(0, bytesWritten)); + dataStream.Position = 0; // Ensure it gets written into the archive from the beginning } - - dataStream.Position = 0; // Ensure it gets written into the archive from the beginning } - - if (buffer is not null) + finally { - ArrayPool.Shared.Return(buffer); + if (buffer is not null) + { + ArrayPool.Shared.Return(buffer); + } } return dataStream; diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs index 18f4d56f7ddb03..20309e5573b32c 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs @@ -58,13 +58,19 @@ internal static void AdvanceStream(Stream archiveStream, long bytesToDiscard) else if (bytesToDiscard > 0) { byte[] buffer = ArrayPool.Shared.Rent(minimumLength: (int)Math.Min(MaxBufferLength, bytesToDiscard)); - while (bytesToDiscard > 0) + try { - int currentLengthToRead = (int)Math.Min(MaxBufferLength, bytesToDiscard); - archiveStream.ReadExactly(buffer.AsSpan(0, currentLengthToRead)); - bytesToDiscard -= currentLengthToRead; + while (bytesToDiscard > 0) + { + int currentLengthToRead = (int)Math.Min(MaxBufferLength, bytesToDiscard); + archiveStream.ReadExactly(buffer.AsSpan(0, currentLengthToRead)); + bytesToDiscard -= currentLengthToRead; + } + } + finally + { + ArrayPool.Shared.Return(buffer); } - ArrayPool.Shared.Return(buffer); } } @@ -80,13 +86,19 @@ internal static async ValueTask AdvanceStreamAsync(Stream archiveStream, long by else if (bytesToDiscard > 0) { byte[] buffer = ArrayPool.Shared.Rent(minimumLength: (int)Math.Min(MaxBufferLength, bytesToDiscard)); - while (bytesToDiscard > 0) + try { - int currentLengthToRead = (int)Math.Min(MaxBufferLength, bytesToDiscard); - await archiveStream.ReadExactlyAsync(buffer, 0, currentLengthToRead, cancellationToken).ConfigureAwait(false); - bytesToDiscard -= currentLengthToRead; + while (bytesToDiscard > 0) + { + int currentLengthToRead = (int)Math.Min(MaxBufferLength, bytesToDiscard); + await archiveStream.ReadExactlyAsync(buffer, 0, currentLengthToRead, cancellationToken).ConfigureAwait(false); + bytesToDiscard -= currentLengthToRead; + } + } + finally + { + ArrayPool.Shared.Return(buffer); } - ArrayPool.Shared.Return(buffer); } } @@ -94,14 +106,20 @@ internal static async ValueTask AdvanceStreamAsync(Stream archiveStream, long by internal static void CopyBytes(Stream origin, Stream destination, long bytesToCopy) { byte[] buffer = ArrayPool.Shared.Rent(minimumLength: (int)Math.Min(MaxBufferLength, bytesToCopy)); - while (bytesToCopy > 0) + try { - int currentLengthToRead = (int)Math.Min(MaxBufferLength, bytesToCopy); - origin.ReadExactly(buffer.AsSpan(0, currentLengthToRead)); - destination.Write(buffer.AsSpan(0, currentLengthToRead)); - bytesToCopy -= currentLengthToRead; + while (bytesToCopy > 0) + { + int currentLengthToRead = (int)Math.Min(MaxBufferLength, bytesToCopy); + origin.ReadExactly(buffer.AsSpan(0, currentLengthToRead)); + destination.Write(buffer.AsSpan(0, currentLengthToRead)); + bytesToCopy -= currentLengthToRead; + } + } + finally + { + ArrayPool.Shared.Return(buffer); } - ArrayPool.Shared.Return(buffer); } // Asynchronously helps copy a specific number of bytes from one stream into another. @@ -110,15 +128,21 @@ internal static async ValueTask CopyBytesAsync(Stream origin, Stream destination cancellationToken.ThrowIfCancellationRequested(); byte[] buffer = ArrayPool.Shared.Rent(minimumLength: (int)Math.Min(MaxBufferLength, bytesToCopy)); - while (bytesToCopy > 0) + try { - int currentLengthToRead = (int)Math.Min(MaxBufferLength, bytesToCopy); - Memory memory = buffer.AsMemory(0, currentLengthToRead); - await origin.ReadExactlyAsync(buffer, 0, currentLengthToRead, cancellationToken).ConfigureAwait(false); - await destination.WriteAsync(memory, cancellationToken).ConfigureAwait(false); - bytesToCopy -= currentLengthToRead; + while (bytesToCopy > 0) + { + int currentLengthToRead = (int)Math.Min(MaxBufferLength, bytesToCopy); + Memory memory = buffer.AsMemory(0, currentLengthToRead); + await origin.ReadExactlyAsync(buffer, 0, currentLengthToRead, cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(memory, cancellationToken).ConfigureAwait(false); + bytesToCopy -= currentLengthToRead; + } + } + finally + { + ArrayPool.Shared.Return(buffer); } - ArrayPool.Shared.Return(buffer); } // Returns the number of bytes until the next multiple of the record size. diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs index 31a860cd993a26..4deb3a5d1fe8c5 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs @@ -358,23 +358,28 @@ private async Task WriteEntryAsyncInternal(TarEntry entry, CancellationToken can cancellationToken.ThrowIfCancellationRequested(); byte[] rented = ArrayPool.Shared.Rent(minimumLength: TarHelpers.RecordSize); - Memory buffer = rented.AsMemory(0, TarHelpers.RecordSize); // minimumLength means the array could've been larger - buffer.Span.Clear(); // Rented arrays aren't clean - - Task task = entry.Format switch + try { - TarEntryFormat.V7 => entry._header.WriteAsV7Async(_archiveStream, buffer, cancellationToken), - TarEntryFormat.Ustar => entry._header.WriteAsUstarAsync(_archiveStream, buffer, cancellationToken), - TarEntryFormat.Pax when entry._header._typeFlag is TarEntryType.GlobalExtendedAttributes => entry._header.WriteAsPaxGlobalExtendedAttributesAsync(_archiveStream, buffer, _nextGlobalExtendedAttributesEntryNumber++, cancellationToken), - TarEntryFormat.Pax => entry._header.WriteAsPaxAsync(_archiveStream, buffer, cancellationToken), - TarEntryFormat.Gnu => entry._header.WriteAsGnuAsync(_archiveStream, buffer, cancellationToken), - _ => throw new InvalidDataException(SR.Format(SR.TarInvalidFormat, Format)), - }; - await task.ConfigureAwait(false); - - _wroteEntries = true; + Memory buffer = rented.AsMemory(0, TarHelpers.RecordSize); // minimumLength means the array could've been larger + buffer.Span.Clear(); // Rented arrays aren't clean - ArrayPool.Shared.Return(rented); + Task task = entry.Format switch + { + TarEntryFormat.V7 => entry._header.WriteAsV7Async(_archiveStream, buffer, cancellationToken), + TarEntryFormat.Ustar => entry._header.WriteAsUstarAsync(_archiveStream, buffer, cancellationToken), + TarEntryFormat.Pax when entry._header._typeFlag is TarEntryType.GlobalExtendedAttributes => entry._header.WriteAsPaxGlobalExtendedAttributesAsync(_archiveStream, buffer, _nextGlobalExtendedAttributesEntryNumber++, cancellationToken), + TarEntryFormat.Pax => entry._header.WriteAsPaxAsync(_archiveStream, buffer, cancellationToken), + TarEntryFormat.Gnu => entry._header.WriteAsGnuAsync(_archiveStream, buffer, cancellationToken), + _ => throw new InvalidDataException(SR.Format(SR.TarInvalidFormat, Format)), + }; + await task.ConfigureAwait(false); + + _wroteEntries = true; + } + finally + { + ArrayPool.Shared.Return(rented); + } } // The spec indicates that the end of the archive is indicated @@ -396,11 +401,16 @@ private async ValueTask WriteFinalRecordsAsync() const int TwoRecordSize = TarHelpers.RecordSize * 2; byte[] twoEmptyRecords = ArrayPool.Shared.Rent(TwoRecordSize); - Array.Clear(twoEmptyRecords, 0, TwoRecordSize); - - await _archiveStream.WriteAsync(twoEmptyRecords.AsMemory(0, TwoRecordSize), cancellationToken: default).ConfigureAwait(false); + try + { + Array.Clear(twoEmptyRecords, 0, TwoRecordSize); - ArrayPool.Shared.Return(twoEmptyRecords); + await _archiveStream.WriteAsync(twoEmptyRecords.AsMemory(0, TwoRecordSize), cancellationToken: default).ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(twoEmptyRecords); + } } private (string, string) ValidateWriteEntryArguments(string fileName, string? entryName)