archive-tar 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|