// Asynchronously moves the underlying archive stream position pointer to the beginning of the next header. internal async ValueTask AdvanceDataStreamIfNeededAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (_previouslyReadEntry == null) { return; } if (_archiveStream.CanSeek) { Debug.Assert(_previouslyReadEntry._header._endOfHeaderAndDataAndBlockAlignment > 0); _archiveStream.Position = _previouslyReadEntry._header._endOfHeaderAndDataAndBlockAlignment; } else if (_previouslyReadEntry._header._size > 0) { // When working with seekable streams, every time we return an entry, we avoid advancing the pointer beyond the data section // This is so the user can read the data if desired. But if the data was not read by the user, we need to advance the pointer // here until it's located at the beginning of the next entry header. // This should only be done if the previous entry came from a TarReader and it still had its original SubReadStream or SeekableSubReadStream. if (_previouslyReadEntry._header._dataStream is not SubReadStream dataStream) { return; } if (!dataStream.HasReachedEnd) { // If the user did not advance the position, we need to make sure the position // pointer is located at the beginning of the next header. if (dataStream.Position < (_previouslyReadEntry._header._size - 1)) { long bytesToSkip = _previouslyReadEntry._header._size - dataStream.Position; await TarHelpers.AdvanceStreamAsync(_archiveStream, bytesToSkip, cancellationToken).ConfigureAwait(false); await TarHelpers.SkipBlockAlignmentPaddingAsync(_archiveStream, _previouslyReadEntry._header._size, cancellationToken).ConfigureAwait(false); dataStream.HasReachedEnd = true; // Now the pointer is beyond the limit, so any read attempts should throw } } } }
private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, CancellationToken cancellationToken) { bool skipBlockAlignmentPadding = true; switch (_typeFlag) { case TarEntryType.ExtendedAttributes or TarEntryType.GlobalExtendedAttributes: await ReadExtendedAttributesBlockAsync(archiveStream, cancellationToken).ConfigureAwait(false); break; case TarEntryType.LongLink or TarEntryType.LongPath: await ReadGnuLongPathDataBlockAsync(archiveStream, cancellationToken).ConfigureAwait(false); break; case TarEntryType.BlockDevice: case TarEntryType.CharacterDevice: case TarEntryType.Directory: case TarEntryType.Fifo: case TarEntryType.HardLink: case TarEntryType.SymbolicLink: // No data section if (_size > 0) { throw new FormatException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag)); } break; case TarEntryType.RegularFile: case TarEntryType.V7RegularFile: // Treated as regular file case TarEntryType.ContiguousFile: // Treated as regular file case TarEntryType.DirectoryList: // Contains the list of filesystem entries in the data section case TarEntryType.MultiVolume: // Contains portion of a file case TarEntryType.RenamedOrSymlinked: // Might contain data case TarEntryType.SparseFile: // Contains portion of a file case TarEntryType.TapeVolume: // Might contain data default: // Unrecognized entry types could potentially have a data section _dataStream = await GetDataStreamAsync(archiveStream, copyData, _size, cancellationToken).ConfigureAwait(false); if (_dataStream is SeekableSubReadStream) { await TarHelpers.AdvanceStreamAsync(archiveStream, _size, cancellationToken).ConfigureAwait(false); } else if (_dataStream is SubReadStream) { // This stream gives the user the chance to optionally read the data section // when the underlying archive stream is unseekable skipBlockAlignmentPadding = false; } break; } if (skipBlockAlignmentPadding) { if (_size > 0) { await TarHelpers.SkipBlockAlignmentPaddingAsync(archiveStream, _size, cancellationToken).ConfigureAwait(false); } if (archiveStream.CanSeek) { _endOfHeaderAndDataAndBlockAlignment = archiveStream.Position; } } }