archive-tar 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/archive/tar.rb +110 -0
- data/lib/archive/tar/format.rb +110 -0
- data/lib/archive/tar/reader.rb +67 -0
- data/lib/archive/tar/stat.rb +16 -0
- data/lib/archive/tar/writer.rb +51 -0
- metadata +57 -0
data/lib/archive/tar.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
module Archive #:nodoc:
|
2
|
+
end
|
3
|
+
|
4
|
+
#
|
5
|
+
# == Extract Options
|
6
|
+
#
|
7
|
+
# * <tt>:permissions</tt> - If set to <tt>:preserve</tt>, then the Reader will
|
8
|
+
# attempt to chown and chmod the extracted to files to match the mode, UID
|
9
|
+
# and GID stored in the archive. For every other value, UID and GID will be
|
10
|
+
# taken from the executing user, and mode from that user's <tt>umask</tt>.
|
11
|
+
#
|
12
|
+
# <b>Note:</b> on most operating systems (notably UNIX/Linux) the only user
|
13
|
+
# with rights to change ownership of files and directory is the <tt>root</tt>
|
14
|
+
# user.
|
15
|
+
#
|
16
|
+
# == Archive Options
|
17
|
+
#
|
18
|
+
# * <tt>:compression</tt> - If set to <tt>:gzip</tt>, then the Reader will
|
19
|
+
# decompress the tar file on-the-fly (without any temp files) as it extracts
|
20
|
+
# the tarred files. For every other value, no decompression is done.
|
21
|
+
# <i>Bzip2 compression is not yet supported</i>.
|
22
|
+
#
|
23
|
+
module Archive::Tar
|
24
|
+
# :stopdoc:
|
25
|
+
UNIX_VERBOSE = Proc.new { |s, _|
|
26
|
+
if (s[:type] == :link)
|
27
|
+
print 'h'
|
28
|
+
else
|
29
|
+
print '-'
|
30
|
+
end
|
31
|
+
[ (s[:mode] & 0700) >> 6, (s[:mode] & 0070) >> 3, (s[:mode] & 0007) ].each do |p|
|
32
|
+
print p & 04 == 04 ? 'r' : '-'
|
33
|
+
print p & 02 == 02 ? 'w' : '-'
|
34
|
+
print p & 01 == 01 ? 'x' : '-'
|
35
|
+
end
|
36
|
+
print ' '
|
37
|
+
print "#{s[:user]}/#{s[:group]}".ljust(16, ' ')
|
38
|
+
print s[:size].to_s.rjust(8,' ')
|
39
|
+
print ' '
|
40
|
+
print Time.at(s[:mtime]).strftime("%Y-%m-%d %H:%M")
|
41
|
+
print ' '
|
42
|
+
print s[:path]
|
43
|
+
if (s[:type] == :link)
|
44
|
+
print ' link to '
|
45
|
+
print s[:dest]
|
46
|
+
end
|
47
|
+
puts
|
48
|
+
}
|
49
|
+
# :startdoc:
|
50
|
+
|
51
|
+
class << self
|
52
|
+
#
|
53
|
+
# Extract a tar archive (+source_file+) into +dest_dir+. +source_file+
|
54
|
+
# must exist and the executing user must be able to read it.
|
55
|
+
# Additionally, +dest_dir+ must exist, and the executing user must be
|
56
|
+
# allowed to write to it.
|
57
|
+
#
|
58
|
+
# +options+ is an optional hash of parameters that influence the nature
|
59
|
+
# of the extraction. See Extract Options and Archive Options (above)
|
60
|
+
# for information on possible values.
|
61
|
+
#
|
62
|
+
def extract(source_file, dest_dir, options = {})
|
63
|
+
Reader.new(source_file, options).extract(dest_dir, options)
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Creates a new tar archive in +dest_file+. A list of paths to store in the
|
68
|
+
# archive should be passed next. The last argument can optionally be a hash
|
69
|
+
# of options that will dictate how the archive is created (see Archive
|
70
|
+
# Options, above).
|
71
|
+
#
|
72
|
+
# Example:
|
73
|
+
#
|
74
|
+
# Archive::Tar.create(
|
75
|
+
# '~/ruby.tar.gz', # Where to put the archive
|
76
|
+
# Dir["~/code/**/*.rb"], # What to tar up
|
77
|
+
# :compression => :gzip, # Gzip it, to save some space
|
78
|
+
# )
|
79
|
+
#
|
80
|
+
def create(dest_file, filenames = [], options = {})
|
81
|
+
Writer.new(filenames).write(dest_file, options)
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Cycle through the entries in a tar archive, and pass each entry to a
|
86
|
+
# caller-supplied block. This is the easiest way to emulate the <tt>-t</tt>
|
87
|
+
# option to the canonical UNIX <tt>tar</tt> utility.
|
88
|
+
#
|
89
|
+
# Each time +block+ is invoked, it will be passed a symbolic-key hash of the
|
90
|
+
# header information (owner, size, last modification time, etc.) and the
|
91
|
+
# contents of the file. For all file types except a normal file, contents
|
92
|
+
# will be nil.
|
93
|
+
#
|
94
|
+
# To list the path names of all entries in an archive:
|
95
|
+
#
|
96
|
+
# Archive::Tar.traverse('ruby.tar.gz', :compression => :gzip) do |header, contents|
|
97
|
+
# puts header[:path]
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
def traverse(source_file, options = {}, &block) # :yields: header, contents
|
101
|
+
block ||= UNIX_VERBOSE
|
102
|
+
Reader.new(source_file, options).each(&block)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
require 'archive/tar/format'
|
108
|
+
require 'archive/tar/reader'
|
109
|
+
require 'archive/tar/writer'
|
110
|
+
require 'archive/tar/stat'
|
@@ -0,0 +1,110 @@
|
|
1
|
+
class Archive::Tar::Format # :nodoc:
|
2
|
+
DEC_TYPES = {
|
3
|
+
'0' => :file,
|
4
|
+
"\0" => :file,
|
5
|
+
'1' => :link,
|
6
|
+
'2' => :symlink,
|
7
|
+
'3' => :character,
|
8
|
+
'4' => :block,
|
9
|
+
'5' => :directory,
|
10
|
+
'6' => :fifo,
|
11
|
+
'7' => :contiguous
|
12
|
+
}
|
13
|
+
|
14
|
+
ENC_TYPES = {
|
15
|
+
:file => '0',
|
16
|
+
:link => '1',
|
17
|
+
:symlink => '2',
|
18
|
+
:character => '3',
|
19
|
+
:block => '4',
|
20
|
+
:directory => '5',
|
21
|
+
:fifo => '6',
|
22
|
+
:contiguous => '7'
|
23
|
+
}
|
24
|
+
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def next_block(io)
|
28
|
+
block = ''
|
29
|
+
loop do
|
30
|
+
block = io.read(512)
|
31
|
+
return block unless block == "\0" * 512
|
32
|
+
return nil if io.eof?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def unpack_header(block)
|
37
|
+
h = block.unpack("Z100 Z8 Z8 Z8 Z12 Z12 A8 a Z100 Z6 Z2 Z32 Z32 Z8 Z8 Z155")
|
38
|
+
{
|
39
|
+
:path => h.shift,
|
40
|
+
:mode => h.shift.oct,
|
41
|
+
:uid => h.shift.oct,
|
42
|
+
:gid => h.shift.oct,
|
43
|
+
:size => h.shift.oct,
|
44
|
+
:mtime => Time.at(h.shift.oct),
|
45
|
+
:cksum => h.shift.oct,
|
46
|
+
:type => DEC_TYPES[h.shift],
|
47
|
+
:dest => h.shift,
|
48
|
+
:ustar => h.shift.strip == 'ustar',
|
49
|
+
:uvers => h.shift.strip,
|
50
|
+
:user => h.shift,
|
51
|
+
:group => h.shift,
|
52
|
+
:major => h.shift.oct,
|
53
|
+
:minor => h.shift.oct,
|
54
|
+
:pre => h.shift
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def pack_header(h)
|
59
|
+
packed = [
|
60
|
+
h[:path],
|
61
|
+
"%07o" % h[:mode],
|
62
|
+
"%07o" % h[:uid],
|
63
|
+
"%07o" % h[:gid],
|
64
|
+
"%011o" % h[:size],
|
65
|
+
"%011o" % h[:mtime].to_i,
|
66
|
+
'',
|
67
|
+
ENC_TYPES[h[:type]],
|
68
|
+
h[:dest],
|
69
|
+
h[:ustar] ? "ustar" : '',
|
70
|
+
"00",
|
71
|
+
h[:user],
|
72
|
+
h[:group],
|
73
|
+
"%07o" % h[:major],
|
74
|
+
"%07o" % h[:minor],
|
75
|
+
h[:pre]
|
76
|
+
].pack("a100 a8 a8 a8 a12 a12 A8 a a100 a6 a2 a32 a32 a8 a8 a155 x12")
|
77
|
+
packed[148, 7] = "%06o\0" % packed.unpack("C*").inject { |total, byte| total + byte }
|
78
|
+
packed
|
79
|
+
end
|
80
|
+
|
81
|
+
def stat(path, inodes)
|
82
|
+
lstat = File.lstat(path)
|
83
|
+
dest = lstat.symlink? ? File.readlink(path) : inodes[lstat.ino]
|
84
|
+
|
85
|
+
inodes[lstat.ino] ||= path
|
86
|
+
|
87
|
+
size = lstat.size
|
88
|
+
size = 0 if dest || !lstat.file?
|
89
|
+
|
90
|
+
{
|
91
|
+
:path => path[0,1] == '/' ? path[1, path.size - 1] : path,
|
92
|
+
:mode => lstat.mode,
|
93
|
+
:uid => lstat.uid,
|
94
|
+
:gid => lstat.gid,
|
95
|
+
:size => size,
|
96
|
+
:mtime => lstat.mtime.to_i,
|
97
|
+
:type => (lstat.file? && dest ? :link : lstat.tar_type),
|
98
|
+
:dest => dest.to_s,
|
99
|
+
|
100
|
+
:ustar => true,
|
101
|
+
:uvers => "00",
|
102
|
+
:user => Etc.getpwuid(lstat.uid).name,
|
103
|
+
:group => Etc.getgrgid(lstat.gid).name,
|
104
|
+
:major => lstat.dev_major.to_i,
|
105
|
+
:minor => lstat.dev_minor.to_i,
|
106
|
+
:pre => '', # FIXME: implement
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class Archive::Tar::Reader
|
5
|
+
def initialize(source, options = {})
|
6
|
+
@source = source
|
7
|
+
|
8
|
+
@options = {
|
9
|
+
:compression => :none
|
10
|
+
}.merge(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def each(full = false, &block)
|
14
|
+
case @source
|
15
|
+
when IO: parse(@source, full); @source.rewind
|
16
|
+
else File.open(@source, 'r') { |f| parse(f, full) }
|
17
|
+
end
|
18
|
+
|
19
|
+
@index.each { |path| yield *(@records[path]) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def extract(dest, options = {})
|
23
|
+
options = {
|
24
|
+
:permissions => nil
|
25
|
+
}.merge(options)
|
26
|
+
|
27
|
+
raise "No such directory: #{dest}" unless File.directory?(dest)
|
28
|
+
|
29
|
+
each(true) do |header, body|
|
30
|
+
path = File.join(dest, header[:path])
|
31
|
+
FileUtils.mkdir_p( File.dirname(path) )
|
32
|
+
|
33
|
+
case header[:type]
|
34
|
+
when :file: File.open(path, 'w') { |fio| fio.write(body) }
|
35
|
+
when :directory: FileUtils.mkdir(path)
|
36
|
+
when :link: File.link( File.join(dest, header[:dest]), path )
|
37
|
+
when :symlink: File.symlink( header[:dest], path )
|
38
|
+
end
|
39
|
+
|
40
|
+
if options[:permissions] == :preserve && !File.symlink?(path)
|
41
|
+
File.chmod header[:mode], path
|
42
|
+
File.chown header[:uid].to_i, header[:gid].to_i, path
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse(io, full = false)
|
48
|
+
@index = []
|
49
|
+
@records = {}
|
50
|
+
|
51
|
+
io = Zlib::GzipReader.new(io) if @options[:compression] == :gzip
|
52
|
+
|
53
|
+
until io.eof?
|
54
|
+
hblock = Archive::Tar::Format.next_block(io)
|
55
|
+
break unless hblock
|
56
|
+
|
57
|
+
header = Archive::Tar::Format.unpack_header(hblock)
|
58
|
+
path = header[:path]
|
59
|
+
size = header[:size]
|
60
|
+
|
61
|
+
@index << path
|
62
|
+
@records[path] = [ header, io.read(size % 512 == 0 ? size : size + (512 - size % 512)) ]
|
63
|
+
@records[path][1] = nil unless full
|
64
|
+
end
|
65
|
+
end
|
66
|
+
private :parse
|
67
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Archive # :nodoc:
|
2
|
+
module Tar
|
3
|
+
module Stat # :nodoc:
|
4
|
+
def tar_type
|
5
|
+
case self.ftype
|
6
|
+
when 'file': :file
|
7
|
+
when 'directory': :directory
|
8
|
+
when 'blockSpecial': :block
|
9
|
+
when 'characterSpecial': :character
|
10
|
+
when 'link': :symlink
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
File::Stat.send(:include, Archive::Tar::Stat)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'etc'
|
2
|
+
require 'zlib'
|
3
|
+
|
4
|
+
class Archive::Tar::Writer
|
5
|
+
def initialize(filenames)
|
6
|
+
@inodes = {}
|
7
|
+
@records = {}
|
8
|
+
@index = []
|
9
|
+
filenames.each { |path| self << path }
|
10
|
+
end
|
11
|
+
|
12
|
+
def <<(filename)
|
13
|
+
@index << filename
|
14
|
+
@records[filename] = Archive::Tar::Format.stat(filename, @inodes)
|
15
|
+
if File.directory?(filename)
|
16
|
+
Dir.open(filename) do |dh|
|
17
|
+
dh.entries.each do |file|
|
18
|
+
self << File.join(filename, file) unless [".",".."].include? file
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def write(dest, options = {})
|
25
|
+
if dest.is_a? IO
|
26
|
+
write_to(dest, options)
|
27
|
+
else
|
28
|
+
File.open(dest, 'w') { |io| write_to(io, options) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def write_to(io, options = {})
|
33
|
+
options = {
|
34
|
+
:compression => :none
|
35
|
+
}.merge(options)
|
36
|
+
|
37
|
+
io = Zlib::GzipWriter.new(io) if options[:compression] == :gzip
|
38
|
+
|
39
|
+
@index.each do |path|
|
40
|
+
stat = @records[path]
|
41
|
+
io.write Archive::Tar::Format.pack_header(stat)
|
42
|
+
|
43
|
+
File.open(path, 'r') do |fio|
|
44
|
+
io.write [fio.read(512)].pack("Z512") until fio.eof?
|
45
|
+
end if stat[:dest].empty?
|
46
|
+
end
|
47
|
+
io.write("\0" * 1024)
|
48
|
+
io.close
|
49
|
+
end
|
50
|
+
private :write_to
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: archive-tar
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James R Hunt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-08-04 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: james@niftylogic.net
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/archive/tar.rb
|
26
|
+
- lib/archive/tar/reader.rb
|
27
|
+
- lib/archive/tar/writer.rb
|
28
|
+
- lib/archive/tar/format.rb
|
29
|
+
- lib/archive/tar/stat.rb
|
30
|
+
has_rdoc: true
|
31
|
+
homepage: http://gems.niftylogic.net/activedirectory
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: "0"
|
42
|
+
version:
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
requirements: []
|
50
|
+
|
51
|
+
rubyforge_project: archive-tar
|
52
|
+
rubygems_version: 1.2.0
|
53
|
+
signing_key:
|
54
|
+
specification_version: 2
|
55
|
+
summary: An interface library for reading and writing UNIX tar files.
|
56
|
+
test_files: []
|
57
|
+
|