// Determines what kind of stream needs to be saved for the data section. // - Metadata typeflag entries (Extended Attributes and Global Extended Attributes in PAX, LongLink and LongPath in GNU) // will get all the data section read and the stream pointer positioned at the beginning of the next header. // - Block, Character, Directory, Fifo, HardLink and SymbolicLink typeflag entries have no data section so the archive stream pointer will be positioned at the beginning of the next header. // - All other typeflag entries with a data section will generate a stream wrapping the data section: SeekableSubReadStream for seekable archive streams, and SubReadStream for unseekable archive streams. private void ProcessDataBlock(Stream archiveStream, bool copyData) { bool skipBlockAlignmentPadding = true; switch (_typeFlag) { case TarEntryType.ExtendedAttributes or TarEntryType.GlobalExtendedAttributes: ReadExtendedAttributesBlock(archiveStream); break; case TarEntryType.LongLink or TarEntryType.LongPath: ReadGnuLongPathDataBlock(archiveStream); break; case TarEntryType.BlockDevice: case TarEntryType.CharacterDevice: case TarEntryType.Directory: case TarEntryType.Fifo: case TarEntryType.HardLink: case TarEntryType.SymbolicLink: // No data section 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 = GetDataStream(archiveStream, copyData); if (_dataStream is SeekableSubReadStream) { TarHelpers.AdvanceStream(archiveStream, _size); } 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) { TarHelpers.SkipBlockAlignmentPadding(archiveStream, _size); } if (archiveStream.CanSeek) { _endOfHeaderAndDataAndBlockAlignment = archiveStream.Position; } } }
// Moves the underlying archive stream position pointer to the beginning of the next header. internal void AdvanceDataStreamIfNeeded() { 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; TarHelpers.AdvanceStream(_archiveStream, bytesToSkip); TarHelpers.SkipBlockAlignmentPadding(_archiveStream, _previouslyReadEntry._header._size); dataStream.HasReachedEnd = true; // Now the pointer is beyond the limit, so any read attempts should throw } } } }