diff options
author | Martin Emde <martin.emde@gmail.com> | 2023-02-16 18:03:41 -0800 |
---|---|---|
committer | git <svn-admin@ruby-lang.org> | 2023-03-07 20:21:43 +0000 |
commit | 85a1738ab37b3348fc0b924804ca4b209f34fbf7 (patch) | |
tree | 92706ff6ce6f4ae5baa1b06095468678de236f5d /lib/rubygems/package/tar_reader | |
parent | 719a7726d17f0800b8f8ef42f3f48e7558d5a444 (diff) |
[rubygems/rubygems] Add TarReader::Entry#seek to seek within the tar file entry
TarReader#each previously implemented a partial version of seek.
This code moved to Entry#seek for use from TarReader#each.
Entry#close now returns nil instead of true, like IO#close.
Closing an Entry now seeks to the end of the Entry, seeking past
any remaining zero byte tar file padding and moving the io to the
correcty position to read the next file in the archive.
Uses seek for Entry#rewind and #pos=, fixing the tar->gzip->tar nested
rewind that would break previous to this change.
Add Entry.open that behaves more like File.open.
https://github.com/rubygems/rubygems/commit/f5149565d5
Diffstat (limited to 'lib/rubygems/package/tar_reader')
-rw-r--r-- | lib/rubygems/package/tar_reader/entry.rb | 85 |
1 files changed, 82 insertions, 3 deletions
diff --git a/lib/rubygems/package/tar_reader/entry.rb b/lib/rubygems/package/tar_reader/entry.rb index e7b0cd0602..9e7b327431 100644 --- a/lib/rubygems/package/tar_reader/entry.rb +++ b/lib/rubygems/package/tar_reader/entry.rb @@ -9,6 +9,20 @@ class Gem::Package::TarReader::Entry ## + # Creates a new tar entry for +header+ that will be read from +io+ + # If a block is given, the entry is yielded and then closed. + + def self.open(header, io, &block) + entry = new header, io + return entry unless block_given? + begin + yield entry + ensure + entry.close + end + end + + ## # Header for this tar entry attr_reader :header @@ -21,6 +35,7 @@ class Gem::Package::TarReader::Entry @header = header @io = io @orig_pos = @io.pos + @end_pos = @orig_pos + @header.size @read = 0 end @@ -39,7 +54,14 @@ class Gem::Package::TarReader::Entry # Closes the tar entry def close + return if closed? + # Seek to the end of the entry if it wasn't fully read + seek(0, IO::SEEK_END) + # discard trailing zeros + skip = (512 - (@header.size % 512)) % 512 + @io.read(skip) @closed = true + nil end ## @@ -117,6 +139,14 @@ class Gem::Package::TarReader::Entry bytes_read end + ## + # Seek to the position in the tar entry + + def pos=(new_pos) + seek(new_pos, IO::SEEK_SET) + new_pos + end + def size @header.size end @@ -158,12 +188,61 @@ class Gem::Package::TarReader::Entry end ## + # Seeks to +offset+ bytes into the tar file entry + # +whence+ can be IO::SEEK_SET, IO::SEEK_CUR, or IO::SEEK_END + + def seek(offset, whence = IO::SEEK_SET) + check_closed + + new_pos = + case whence + when IO::SEEK_SET then @orig_pos + offset + when IO::SEEK_CUR then @io.pos + offset + when IO::SEEK_END then @end_pos + offset + else + raise ArgumentError, "invalid whence" + end + + if new_pos < @orig_pos + new_pos = @orig_pos + elsif new_pos > @end_pos + new_pos = @end_pos + end + + pending = new_pos - @io.pos + + if @io.respond_to?(:seek) + begin + # avoid reading if the @io supports seeking + @io.seek new_pos, IO::SEEK_SET + pending = 0 + rescue Errno::EINVAL + end + end + + # if seeking isn't supported or failed + # negative seek requires that we rewind and read + if pending < 0 + @io.rewind + pending = new_pos + end + + while pending > 0 do + size_read = @io.read([pending, 4096].min).size + raise UnexpectedEOF if @io.eof? + pending -= size_read + end + + @read = @io.pos - @orig_pos + + 0 + end + + ## # Rewinds to the beginning of the tar file entry def rewind check_closed - - @io.pos = @orig_pos - @read = 0 + seek(0, IO::SEEK_SET) end end |