archive-tar 0.9.0

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