class TarWriter

This class creates (or appends to) a tar archive. API is designed to be compatible with Archive::Tar::Minitar::Writer (www.rubydoc.info/gems/minitar/Archive/Tar/Minitar/Writer).

Attributes

pos[R]

byte position in the TarWriter stream

Public Class Methods

new(file, mode = 'w') click to toggle source

same as ::open but does not yield the instance.

# File lib/tarwriter.rb, line 34
def initialize file, mode = 'w'
  @io = if IO === file
  then
    file
  else
    case mode
    when 'x'
      File.open(file, WRONLY|CREAT|EXCL|TRUNC|BINARY).set_encoding('BINARY')
    when 'a'
      File.open(file, RDWR|CREAT|BINARY).set_encoding('BINARY')
    when 'w'
      File.open(file, WRONLY|CREAT|TRUNC|BINARY).set_encoding('BINARY')
    else
      raise "unsupported mode=#{mode}"
    end
  end
  @pos = 0
  find_eof if mode == 'a'
  @blocking_factor = 20
  @pool = []
end
open(fnam, mode) { |tar| ... } click to toggle source

opens a POSIX tar file or writing.

@param [String] fnam the filename to open. @param [String] mode

'a':: append - created if missing,
      and positioned at the EOF record (double NUL records)
'w':: writing - created if missing, and truncated if existing.
'x':: exclusive - similar to 'w' but raises Errno::EEXIST if the file exists.

@return [TarWriter] new instance opened, which is yielded if a block is given.

# File lib/tarwriter.rb, line 21
def TarWriter::open fnam, mode
  tar = TarWriter.new(fnam, mode)
  return tar unless block_given?
  begin
    yield tar
  ensure
    tar.close
  end
  fnam
end

Public Instance Methods

add(fnam, content, time = Time.now) click to toggle source

add a file to the TarWriter stream @param [String] fnam filename @param [String] content content of the file @param [Time] time mtime @return [Integer] byte position of the record added. Version 1.2.0 and beore returned the pos of the record *next to* the record added.

# File lib/tarwriter.rb, line 90
def add fnam, content, time = Time.now
  bfnam = String.new(fnam, encoding: "BINARY")
  bcontent = String.new(content, encoding: "BINARY")
  testhdr = header(bfnam, bcontent.size, time)
  cksum = 0
  testhdr.each_byte {|b| cksum += b }
  hdr = header(bfnam, bcontent.size, time, cksum)
  recpos = @io.pos + 512 * @pool.size
  block_write(hdr)
  ofs = 0
  while blk = bcontent.byteslice(ofs, 512)
    break if blk.empty?
    block_write([blk].pack('a512'))
    ofs += 512
  end
  @pos = @io.pos
  recpos
end
block_write(str) click to toggle source
# File lib/tarwriter.rb, line 152
def block_write str
  @pool.push [str].pack('a512')
  if @pool.size >= @blocking_factor
    @io.write @pool.join
    @pool = []
  end
end
close() click to toggle source
# File lib/tarwriter.rb, line 167
def close
  block_write ''
  block_write ''
  flush
  @io.close
end
find_eof() click to toggle source

set the positon at the EOF records

# File lib/tarwriter.rb, line 115
def find_eof
  @io.seek(0, IO::SEEK_END)
  base = @io.pos
  base -= base % 10240
  loop do
    if base.zero?
      STDERR.puts "empty file" if $DEBUG
      @io.pos = 0
      return 0
    end
    base -= 10240
    @io.pos = base
    STDERR.puts "read #{base}+20b" if $DEBUG
    buf = @io.read(10240)
    19.downto(0) {|i|
      magic = buf[512 * i + 257, 5]
      next unless magic == 'ustar'
      recpos = base + 512 * i
      STDERR.puts "ustar found at #{recpos}" if $DEBUG
      hdr = buf[512 * i, 500]
      cksum = hdr[148, 8].unpack('A*').first.to_i(8)
      hdr[148, 8] = ' ' * 8
      s = 0
      hdr.each_byte{|c| s += c}
      next unless cksum == s
      STDERR.puts "checksum #{s} matches at #{recpos}" if $DEBUG
      size = hdr[124, 12].unpack('A*').first.to_i(8)
      size -= 1
      size -= size % 512
      size += 512
      @io.pos = (recpos + 512 + size)
      @pos = @io.pos
      return @io
    }
  end
end
flush() click to toggle source
# File lib/tarwriter.rb, line 160
def flush
  while not @pool.empty?
    block_write ''
  end
  @io.flush
end
header(bfnam, size, time, cksum = nil) click to toggle source

creates a POSIX tar header (String w/BINARY encoding). intended to be used internally, but works without side effect. @param [String] bfnam filename @param [Integer] size size of file @param [Integer] time mtime, seconds since UNIX Era @param [Integer] cksum optional checksum (NUL filled by default to compute checksum) @return [String] binary header

# File lib/tarwriter.rb, line 64
def header bfnam, size, time, cksum = nil
  raise "too long filename #{bfnam}" if bfnam.size >= 100
  mode = sprintf("%07o", 0664)
  uid = gid = sprintf("%07o", 99)
  csize = sprintf("%011o", size)
  cks = cksum ? sprintf("%06o\0", cksum) : ""
  mtime = sprintf("%011o", time)
  typeflag = '0'
  linkname = ''
  magic = 'ustar'
  version = '00'
  uname = gname = 'nobody'
  devmajor = devminor = sprintf('%07o', 0)
  prefix = ''
  fmt = "a100 a8 a8 a8 a12 a12 A8 a1 a100 a6 a2 a32 a32 a8 a8 a155"
  [bfnam, mode, uid, gid, csize, mtime, cks, typeflag, linkname, magic,
    version, uname, gname, devmajor, devminor, prefix].pack(fmt)
end