Class: Minitar

Inherits:
Object
  • Object
show all
Defined in:
lib/minitar.rb,
lib/minitar/input.rb,
lib/minitar/output.rb,
lib/minitar/reader.rb,
lib/minitar/writer.rb,
lib/minitar/posix_header.rb

Overview

Synopsis

Using minitar is easy. The simplest case is:

require 'zlib'
require 'minitar'

# Packs everything that matches Find.find('tests').
# test.tar will automatically be closed by Minitar.pack.
Minitar.pack('tests', File.open('test.tar', 'wb'))

# Unpacks 'test.tar' to 'x', creating 'x' if necessary.
Minitar.unpack('test.tar', 'x')

A gzipped tar can be written with:

# test.tgz will be closed automatically.
Minitar.pack('tests', Zlib::GzipWriter.new(File.open('test.tgz', 'wb'))

# test.tgz will be closed automatically.
Minitar.unpack(Zlib::GzipReader.new(File.open('test.tgz', 'rb')), 'x')

As the case above shows, one need not write to a file. However, it will sometimes require that one dive a little deeper into the API, as in the case of StringIO objects. Note that I’m not providing a block with Minitar::Output, as Minitar::Output#close automatically closes both the Output object and the wrapped data stream object.

begin
  sgz = Zlib::GzipWriter.new(StringIO.new(""))
  tar = Minitar::Output.new(sgz)
  Find.find('tests') do |entry|
    Minitar.pack_file(entry, tar)
  end
ensure
    # Closes both tar and sgz.
  tar.close
end

Defined Under Namespace

Classes: Input, Output, PosixHeader, Reader, Writer

Constant Summary collapse

VERSION =

:nodoc:

"1.0.2".freeze
Error =

The base class for any minitar error.

Class.new(::StandardError)
NonSeekableStream =

Raised when a wrapped data stream class is not seekable.

Class.new(Error)
ClosedStream =

The exception raised when operations are performed on a stream that has previously been closed.

Class.new(Error)
FileNameTooLong =

The exception raised when a filename exceeds 256 bytes in length, the maximum supported by the standard Tar format.

Class.new(Error)
UnexpectedEOF =

The exception raised when a data stream ends before the amount of data expected in the archive’s PosixHeader.

Class.new(StandardError)
SecureRelativePathError =

The exception raised when a file contains a relative path in secure mode (the default for this version).

Class.new(Error)
InvalidTarStream =

The exception raised when a file contains an invalid Posix header.

Class.new(Error)

Class Method Summary collapse

Class Method Details

.dir?(path) ⇒ Boolean

Tests if path refers to a directory. Fixes an apparently corrupted stat() call on Windows.

Returns:

  • (Boolean)


68
69
70
# File 'lib/minitar.rb', line 68

def dir?(path)
  File.directory?((path[-1] == "/") ? path : "#{path}/")
end

.open(dest, mode = "r") ⇒ Object

A convenience method for wrapping Minitar::Input.open (mode r) and Minitar::Output.open (mode w). No other modes are currently supported.



75
76
77
78
79
80
81
82
83
84
# File 'lib/minitar.rb', line 75

def open(dest, mode = "r", &)
  case mode
  when "r"
    Minitar::Input.open(dest, &)
  when "w"
    Minitar::Output.open(dest, &block)
  else
    raise "Unknown open mode for Minitar.open."
  end
end

.pack(src, dest, recurse_dirs = true, &block) ⇒ Object

A convenience method to pack files specified by src into dest. If src is an Array, then each file detailed therein will be packed into the resulting Minitar::Output stream; if recurse_dirs is true, then directories will be recursed.

If src is not an Array, it will be treated as the result of Find.find; all files matching will be packed.



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/minitar.rb', line 233

def pack(src, dest, recurse_dirs = true, &block)
  require "find"
  Minitar::Output.open(dest) do |outp|
    if src.is_a?(Array)
      src.each do |entry|
        if dir?(entry) && recurse_dirs
          Find.find(entry) do |ee|
            pack_file(ee, outp, &block)
          end
        else
          pack_file(entry, outp, &block)
        end
      end
    else
      Find.find(src) do |entry|
        pack_file(entry, outp, &block)
      end
    end
  end
end

.pack_as_file(entry, data, outputter) ⇒ Object

A convenience method to pack the provided data as a file named entry. entry may either be a name or a Hash with the fields described below. When only a name is provided, or only some Hash fields are provided, the default values will apply.

:name

The filename to be packed into the archive. Required.

:mode

The mode to be applied. Defaults to 0o644 for files and 0o755 for directories.

:uid

The user owner of the file. Default is nil.

:gid

The group owner of the file. Default is nil.

:mtime

The modification Time of the file. Default is Time.now.

If data is nil, a directory will be created. Use an empty String for a normal empty file.



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/minitar.rb', line 103

def pack_as_file(entry, data, outputter) # :yields action, name, stats:
  if outputter.is_a?(Minitar::Output)
    outputter = outputter.tar
  end

  stats = {
    gid: nil,
    uid: nil,
    mtime: Time.now,
    size: data&.size || 0,
    mode: data ? 0o644 : 0o755
  }

  if entry.is_a?(Hash)
    name = entry.delete(:name)
    entry.each_pair { stats[_1] = _2 unless _2.nil? }
  else
    name = entry
  end

  if data.nil? # Create a directory
    yield :dir, name, stats if block_given?
    outputter.mkdir(name, stats)
  else
    outputter.add_file_simple(name, stats) do |os|
      stats[:current] = 0
      yield :file_start, name, stats if block_given?

      StringIO.open(data, "rb") do |ff|
        until ff.eof?
          stats[:currinc] = os.write(ff.read(4096))
          stats[:current] += stats[:currinc]
          yield :file_progress, name, stats if block_given?
        end
      end

      yield :file_done, name, stats if block_given?
    end
  end
end

.pack_file(entry, outputter) ⇒ Object

A convenience method to pack the file provided. entry may either be a filename (in which case various values for the file (see below) will be obtained from File#stat(entry) or a Hash with the fields:

:name

The filename to be packed into the archive. Required.

:mode

The mode to be applied.

:uid

The user owner of the file. (Ignored on Windows.)

:gid

The group owner of the file. (Ignored on Windows.)

:mtime

The modification Time of the file.

During packing, if a block is provided, #pack_file yields an action Symol, the full name of the file being packed, and a Hash of statistical information, just as with Minitar::Input#extract_entry.

The action will be one of:

:dir

The entry is a directory.

:file_start

The entry is a file; the extract of the file is just beginning.

:file_progress

Yielded every 4096 bytes during the extract of the entry.

:file_done

Yielded when the entry is completed.

The stats hash contains the following keys:

:current

The current total number of bytes read in the entry.

:currinc

The current number of bytes read in this read cycle.

:name

The filename to be packed into the tarchive. REQUIRED.

:mode

The mode to be applied.

:uid

The user owner of the file. (nil on Windows.)

:gid

The group owner of the file. (nil on Windows.)

:mtime

The modification Time of the file.



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/minitar.rb', line 177

def pack_file(entry, outputter) # :yields action, name, stats:
  if outputter.is_a?(Minitar::Output)
    outputter = outputter.tar
  end

  stats = {}

  if entry.is_a?(Hash)
    name = entry[:name]
    entry.each { |kk, vv| stats[kk] = vv unless vv.nil? }
  else
    name = entry
  end

  name = name.sub(%r{\./}, "")
  stat = File.stat(name)
  stats[:mode] ||= stat.mode
  stats[:mtime] ||= stat.mtime
  stats[:size] = stat.size

  if windows?
    stats[:uid] = nil
    stats[:gid] = nil
  else
    stats[:uid] ||= stat.uid
    stats[:gid] ||= stat.gid
  end

  if File.file?(name)
    outputter.add_file_simple(name, stats) do |os|
      stats[:current] = 0
      yield :file_start, name, stats if block_given?
      File.open(name, "rb") do |ff|
        until ff.eof?
          stats[:currinc] = os.write(ff.read(4096))
          stats[:current] += stats[:currinc]
          yield :file_progress, name, stats if block_given?
        end
      end
      yield :file_done, name, stats if block_given?
    end
  elsif dir?(name)
    yield :dir, name, stats if block_given?
    outputter.mkdir(name, stats)
  else
    raise "Don't yet know how to pack this type of file."
  end
end

.seekable?(io, methods = nil) ⇒ Boolean

Check whether io can seek without errors.

Returns:

  • (Boolean)


274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/minitar.rb', line 274

def seekable?(io, methods = nil)
  # The IO class throws an exception at runtime if we try to change
  # position on a non-regular file.
  if io.respond_to?(:stat)
    io.stat.file?
  else
    # Duck-type the rest of this.
    methods ||= [:pos, :pos=, :seek, :rewind]
    methods = [methods] unless methods.is_a?(Array)
    methods.all? { |m| io.respond_to?(m) }
  end
end

.unpack(src, dest, files = [], options = {}, &block) ⇒ Object

A convenience method to unpack files from src into the directory specified by dest. Only those files named explicitly in files will be extracted.



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/minitar.rb', line 257

def unpack(src, dest, files = [], options = {}, &block)
  Minitar::Input.open(src) do |inp|
    if File.exist?(dest) && !dir?(dest)
      raise "Can't unpack to a non-directory."
    end

    FileUtils.mkdir_p(dest) unless File.exist?(dest)

    inp.each do |entry|
      if files.empty? || files.include?(entry.full_name)
        inp.extract_entry(dest, entry, options, &block)
      end
    end
  end
end

.windows?Boolean

:nodoc:

Returns:

  • (Boolean)


86
87
88
# File 'lib/minitar.rb', line 86

def windows? # :nodoc:
  RbConfig::CONFIG["host_os"] =~ /^(mswin|mingw|cygwin)/
end