summaryrefslogtreecommitdiff
path: root/lib/rubygems/package/tar_reader
diff options
context:
space:
mode:
authorMartin Emde <martin.emde@gmail.com>2023-02-16 18:03:41 -0800
committergit <svn-admin@ruby-lang.org>2023-03-07 20:21:43 +0000
commit85a1738ab37b3348fc0b924804ca4b209f34fbf7 (patch)
tree92706ff6ce6f4ae5baa1b06095468678de236f5d /lib/rubygems/package/tar_reader
parent719a7726d17f0800b8f8ef42f3f48e7558d5a444 (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.rb85
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