class Archive::Reader

Attributes

strip_components[R]

Public Class Methods

new(params = {}) click to toggle source
Calls superclass method Archive::BaseArchive::new
# File lib/ffi-libarchive/reader.rb, line 46
def initialize(params = {})
  super C.method(:archive_read_new), C.method(:archive_read_finish)

  if params[:strip_components]
    raise ArgumentError, "Expected Integer as strip_components" unless params[:strip_components].is_a?(Integer)

    @strip_components = params[:strip_components]
  else
    @strip_components = 0
  end

  if params[:command]
    cmd = params[:command]
    raise Error, @archive if C.archive_read_support_compression_program(archive, cmd) != C::OK
  else
    raise Error, @archive if C.archive_read_support_compression_all(archive) != C::OK
  end

  raise Error, @archive if C.archive_read_support_format_all(archive) != C::OK

  case
  when params[:file_name]
    raise Error, @archive if C.archive_read_open_filename(archive, params[:file_name], 1024) != C::OK
  when params[:memory]
    str = params[:memory]
    @data = FFI::MemoryPointer.new(str.bytesize + 1)
    @data.write_string str, str.bytesize
    raise Error, @archive if C.archive_read_open_memory(archive, @data, str.bytesize) != C::OK
  when params[:reader]
    @reader = params[:reader]
    @buffer = nil

    @read_callback = FFI::Function.new(:int, %i{pointer pointer pointer}) do |_, _, archive_data|
      data = @reader.call || ""
      @buffer = FFI::MemoryPointer.new(:char, data.size) if @buffer.nil? || @buffer.size < data.size
      @buffer.write_bytes(data)
      archive_data.write_pointer(@buffer)
      data.size
    end
    C.archive_read_set_read_callback(archive, @read_callback)

    if @reader.respond_to?(:skip)
      @skip_callback = FFI::Function.new(:int, %i{pointer pointer int64}) do |_, _, offset|
        @reader.skip(offset)
      end
      C.archive_read_set_skip_callback(archive, @skip_callback)
    end

    if @reader.respond_to?(:seek)
      @seek_callback = FFI::Function.new(:int, %i{pointer pointer int64 int}) do |_, _, offset, whence|
        @reader.seek(offset, whence)
      end
      C.archive_read_set_seek_callback(archive, @seek_callback)
    end

    # Required or open1 will segfault, even though the callback data is not used.
    C.archive_read_set_callback_data(archive, nil)
    raise Error, @archive if C.archive_read_open1(archive) != C::OK
  end
rescue
  close
  raise
end
open_filename(file_name, command = nil, strip_components: 0) { |reader| ... } click to toggle source
# File lib/ffi-libarchive/reader.rb, line 5
def self.open_filename(file_name, command = nil, strip_components: 0)
  if block_given?
    reader = open_filename file_name, command, strip_components: strip_components
    begin
      yield reader
    ensure
      reader.close
    end
  else
    new file_name: file_name, command: command, strip_components: strip_components
  end
end
open_memory(string, command = nil) { |reader| ... } click to toggle source
# File lib/ffi-libarchive/reader.rb, line 18
def self.open_memory(string, command = nil)
  if block_given?
    reader = open_memory string, command
    begin
      yield reader
    ensure
      reader.close
    end
  else
    new memory: string, command: command
  end
end
open_stream(reader) { |reader| ... } click to toggle source
# File lib/ffi-libarchive/reader.rb, line 31
def self.open_stream(reader)
  if block_given?
    reader = new reader: reader
    begin
      yield reader
    ensure
      reader.close
    end
  else
    new reader: reader
  end
end

Private Class Methods

new(alloc, free) click to toggle source
# File lib/ffi-libarchive/archive.rb, line 322
def initialize(alloc, free)
  @archive = nil
  @archive_free = nil
  @archive = alloc.call
  @archive_free = [nil]
  raise Error, @archive unless @archive

  @archive_free[0] = free
  ObjectSpace.define_finalizer(self, BaseArchive.finalizer(@archive, @archive_free))
end

Public Instance Methods

each_entry() { |entry| ... } click to toggle source
# File lib/ffi-libarchive/reader.rb, line 142
def each_entry
  while (entry = next_header)
    next if strip_entry_components!(entry).nil?

    yield entry
  end
end
each_entry_with_data(_size = C::DATA_BUFFER_SIZE) { |entry, read_data| ... } click to toggle source
# File lib/ffi-libarchive/reader.rb, line 150
def each_entry_with_data(_size = C::DATA_BUFFER_SIZE)
  while (entry = next_header)
    next if strip_entry_components!(entry).nil?

    yield entry, read_data
  end
end
extract(entry, flags = 0, destination: nil) click to toggle source
# File lib/ffi-libarchive/reader.rb, line 110
def extract(entry, flags = 0, destination: nil)
  raise ArgumentError, "Expected Archive::Entry as first argument" unless entry.is_a? Entry
  raise ArgumentError, "Expected Integer as second argument" unless flags.is_a? Integer
  raise ArgumentError, "Expected String as destination" if destination && !destination.is_a?(String)

  if destination
    # We update the pathname here so this will change for the caller as a side effect, but this seems convenient and accurate?
    pathname = C.archive_entry_pathname(entry.entry)
    C.archive_entry_set_pathname(entry.entry, "#{destination}/#{pathname}")
  end

  flags |= EXTRACT_FFLAGS
  raise Error, @archive if C.archive_read_extract(archive, entry.entry, flags) != C::OK
end
header_position() click to toggle source
# File lib/ffi-libarchive/reader.rb, line 125
def header_position
  raise Error, @archive if C.archive_read_header_position archive
end
next_header(clone_entry: false) click to toggle source
# File lib/ffi-libarchive/reader.rb, line 129
def next_header(clone_entry: false)
  entry_ptr = FFI::MemoryPointer.new(:pointer)
  case C.archive_read_next_header(archive, entry_ptr)
  when C::OK
    Entry.from_pointer entry_ptr.read_pointer, clone: clone_entry
  when C::EOF
    @eof = true
    nil
  else
    raise Error, @archive
  end
end
read_data(size = C::DATA_BUFFER_SIZE) { |get_bytes| ... } click to toggle source
# File lib/ffi-libarchive/reader.rb, line 158
def read_data(size = C::DATA_BUFFER_SIZE)
  raise ArgumentError, "Buffer size must be > 0 (was: #{size})" unless size.is_a?(Integer) && size > 0

  data = nil

  buffer = FFI::MemoryPointer.new(size)
  len = 0
  while (n = C.archive_read_data(archive, buffer, size)) > 0
    case n
    when C::FATAL, C::WARN, C::RETRY
      raise Error, @archive
    else
      if block_given?
        yield buffer.get_bytes(0, n)
      else
        data ||= ""
        data.concat(buffer.get_bytes(0, n))
      end
    end
    len += n
  end

  data || len
end
save_data(file_name) click to toggle source
# File lib/ffi-libarchive/reader.rb, line 183
def save_data(file_name)
  IO.sysopen(file_name, "wb") do |fd|
    raise Error, @archive if C.archive_read_data_into_fd(archive, fd) != C::OK
  end
end

Private Instance Methods

strip_entry_components!(entry) click to toggle source

See:

1. https://github.com/libarchive/libarchive/blob/6a9dcf9fc429e2dc9fb08e669bf7b0bed4d5edf9/tar/read.c#L346
2. https://github.com/libarchive/libarchive/blob/a11f15860ae39ecdc8173243a211cdafc8ac893c/tar/util.c#L523-L535
3. https://github.com/libarchive/libarchive/blob/a11f15860ae39ecdc8173243a211cdafc8ac893c/tar/util.c#L554-L560

@param entry [Archive::Entry]

@return [Archive::Entry, nil] entry stripped or nil if entry is no longer relevant due to stripping

# File lib/ffi-libarchive/reader.rb, line 201
def strip_entry_components!(entry)
  if strip_components > 0
    name = entry.pathname
    original_name = name.dup
    hardlink_name = entry.hardlink
    original_hardlink_name = hardlink_name.dup

    strip_path_components!(name)
    return if name.empty?

    unless hardlink_name.nil?
      strip_path_components!(hardlink_name)
      return if hardlink_name.empty?
    end

    if name != original_name
      entry.copy_pathname(name)
    end
    entry.copy_hardlink(hardlink_name) if hardlink_name != original_hardlink_name
  end

  entry
end
strip_path_components!(path) click to toggle source

@param path [String]

@return [String]

# File lib/ffi-libarchive/reader.rb, line 230
def strip_path_components!(path)
  if strip_components > 0
    is_dir = path.end_with?("/")
    updated_path = path.split("/").drop(strip_components).join("/")
    updated_path = is_dir && updated_path != "" ? updated_path + "/" : updated_path
    path.gsub!(path, updated_path)
  else
    path
  end
end