minitar-jmazzi 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a52a3043eaf1cdd0ec224149057cdac31346d637
4
+ data.tar.gz: 787da0605678bd5b1a879669079cc0ec832d233c
5
+ SHA512:
6
+ metadata.gz: a1331e8f215ef4d246e89a438fb3d41714181c937d23cf11f1111f4b3b1cce3a93359492d18501a64c1238aea9c304d0de76ed87807f2339d26539ae8372c8d7
7
+ data.tar.gz: 5bb1269c83934947f38fb3e58131a25792ebaa0b869211b66979f8164d2c8eb67cae089130642448b34bbd1343f03902ec7978228ce4461eb21a665b25caa77f
@@ -0,0 +1,17 @@
1
+ Revision history for Ruby library Archive::Tar::Minitar. Unless explicitly
2
+ noted otherwise, all changes are produced by Austin Ziegler
3
+ <austin@rubyforge.org>.
4
+
5
+ == 0.5.3
6
+ * Fix for long names (http://rubyforge.org/tracker/index.php?func=detail&aid=22628&group_id=84&atid=407)
7
+
8
+ == 0.5.2
9
+ * Fixed a Ruby 1.9 compatibility error.
10
+
11
+ == 0.5.1
12
+ * Fixed a variable name error.
13
+
14
+ == Archive::Tar::Minitar 0.5.0
15
+ * Initial release. Does files and directories. Command does create, extract,
16
+ * and list.
17
+
data/Install ADDED
@@ -0,0 +1,6 @@
1
+ Installing this package is as simple as:
2
+
3
+ % ruby install.rb
4
+
5
+ Alternatively, you can use the RubyGem version of Archive::Tar::Minitar
6
+ available as archive-tar-minitar-0.5.2.gem from the usual sources.
data/README ADDED
@@ -0,0 +1,68 @@
1
+ Archive::Tar::Minitar README
2
+ ============================
3
+ Archive::Tar::Minitar is a pure-Ruby library and command-line utility that
4
+ provides the ability to deal with POSIX tar(1) archive files. The
5
+ implementation is based heavily on Mauricio Ferna'ndez's implementation in
6
+ rpa-base, but has been reorganised to promote reuse in other projects.
7
+ Antoine Toulme forked the original project on rubyforge to place it on github, under
8
+ http://www.github.com/atoulme/minitar
9
+
10
+ This release is version 0.5.2, offering a Ruby 1.9 compatibility bugfix over
11
+ version 0.5.1. The library can only handle files and directories at this
12
+ point. A future version will be expanded to handle symbolic links and hard
13
+ links in a portable manner. The command line utility, minitar, can only create
14
+ archives, extract from archives, and list archive contents.
15
+
16
+ Using this library is easy. The simplest case is:
17
+
18
+ require 'zlib'
19
+ require 'archive/tar/minitar'
20
+ include Archive::Tar
21
+
22
+ # Packs everything that matches Find.find('tests')
23
+ File.open('test.tar', 'wb') { |tar| Minitar.pack('tests', tar) }
24
+ # Unpacks 'test.tar' to 'x', creating 'x' if necessary.
25
+ Minitar.unpack('test.tar', 'x')
26
+
27
+ A gzipped tar can be written with:
28
+
29
+ tgz = Zlib::GzipWriter.new(File.open('test.tgz', 'wb'))
30
+ # Warning: tgz will be closed!
31
+ Minitar.pack('tests', tgz)
32
+
33
+ tgz = Zlib::GzipReader.new(File.open('test.tgz', 'rb'))
34
+ # Warning: tgz will be closed!
35
+ Minitar.unpack(tgz, 'x')
36
+
37
+ As the case above shows, one need not write to a file. However, it will
38
+ sometimes require that one dive a little deeper into the API, as in the case
39
+ of StringIO objects. Note that I'm not providing a block with Minitar::Output,
40
+ as Minitar::Output#close automatically closes both the Output object and the
41
+ wrapped data stream object.
42
+
43
+ begin
44
+ sgz = Zlib::GzipWriter.new(StringIO.new(""))
45
+ tar = Output.new(sgz)
46
+ Find.find('tests') do |entry|
47
+ Minitar.pack_file(entry, tar)
48
+ end
49
+ ensure
50
+ # Closes both tar and sgz.
51
+ tar.close
52
+ end
53
+
54
+ Copyright
55
+ =========
56
+ # Copyright 2004 Mauricio Julio Ferna'ndez Pradier and Austin Ziegler
57
+ #
58
+ # This program is based on and incorporates parts of RPA::Package from
59
+ # rpa-base (lib/rpa/package.rb and lib/rpa/util.rb) by Mauricio and has been
60
+ # adapted to be more generic by Austin.
61
+ #
62
+ # 'minitar' contains an adaptation of Ruby/ProgressBar by Satoru
63
+ # Takabayashi <satoru@namazu.org>, copyright 2001 - 2004.
64
+ #
65
+ # This program is free software. It may be redistributed and/or modified
66
+ # under the terms of the GPL version 2 (or later) or Ruby's licence.
67
+ #
68
+ # $Id$
@@ -0,0 +1,113 @@
1
+ #! /usr/bin/env rake
2
+ $LOAD_PATH.unshift('lib')
3
+
4
+ require 'rubygems'
5
+ require 'rake/gempackagetask'
6
+ require 'rake/contrib/rubyforgepublisher'
7
+ require 'archive/tar/minitar'
8
+ require 'zlib'
9
+
10
+ DISTDIR = "archive-tar-minitar-#{Archive::Tar::Minitar::VERSION}"
11
+ TARDIST = "../#{DISTDIR}.tar.gz"
12
+
13
+ DATE_RE = %r<(\d{4})[./-]?(\d{2})[./-]?(\d{2})(?:[\sT]?(\d{2})[:.]?(\d{2})[:.]?(\d{2})?)?>
14
+
15
+ if ENV['RELEASE_DATE']
16
+ year, month, day, hour, minute, second = DATE_RE.match(ENV['RELEASE_DATE']).captures
17
+ year ||= 0
18
+ month ||= 0
19
+ day ||= 0
20
+ hour ||= 0
21
+ minute ||= 0
22
+ second ||= 0
23
+ ReleaseDate = Time.mktime(year, month, day, hour, minute, second)
24
+ else
25
+ ReleaseDate = nil
26
+ end
27
+
28
+ task :test do |t|
29
+ require 'test/unit/testsuite'
30
+ require 'test/unit/ui/console/testrunner'
31
+
32
+ runner = Test::Unit::UI::Console::TestRunner
33
+
34
+ $LOAD_PATH.unshift('tests')
35
+ Dir['tests/tc_*.rb'].each do |testcase|
36
+ load testcase
37
+ end
38
+
39
+ suite = Test::Unit::TestSuite.new
40
+
41
+ ObjectSpace.each_object(Class) do |testcase|
42
+ suite << testcase.suite if testcase < Test::Unit::TestCase
43
+ end
44
+
45
+ runner.run(suite)
46
+ end
47
+
48
+ spec = eval(File.read("archive-tar-minitar.gemspec"))
49
+ desc "Build the RubyGem for Archive::Tar::Minitar."
50
+ task :gem => [ :test ]
51
+ Rake::GemPackageTask.new(spec) do |g|
52
+ g.need_tar = false
53
+ g.need_zip = false
54
+ g.package_dir = ".."
55
+ end
56
+
57
+ desc "Build an Archive::Tar::Minitar .tar.gz distribution."
58
+ task :tar => [ TARDIST ]
59
+ file TARDIST do |t|
60
+ current = File.basename(Dir.pwd)
61
+ Dir.chdir("..") do
62
+ begin
63
+ files = Dir["#{current}/**/*"].select { |dd| dd !~ %r{(?:/CVS/?|~$)} }
64
+ files.map! do |dd|
65
+ ddnew = dd.gsub(/^#{current}/, DISTDIR)
66
+ mtime = ReleaseDate || File.stat(dd).mtime
67
+ if File.directory?(dd)
68
+ { :name => ddnew, :mode => 0755, :dir => true, :mtime => mtime }
69
+ else
70
+ if dd =~ %r{bin/}
71
+ mode = 0755
72
+ else
73
+ mode = 0644
74
+ end
75
+ data = File.read(dd)
76
+ { :name => ddnew, :mode => mode, :data => data, :size => data.size,
77
+ :mtime => mtime }
78
+ end
79
+ end
80
+
81
+ ff = File.open(t.name.gsub(%r{^\.\./}o, ''), "wb")
82
+ gz = Zlib::GzipWriter.new(ff)
83
+ tw = Archive::Tar::Minitar::Writer.new(gz)
84
+
85
+ files.each do |entry|
86
+ if entry[:dir]
87
+ tw.mkdir(entry[:name], entry)
88
+ else
89
+ tw.add_file_simple(entry[:name], entry) { |os| os.write(entry[:data]) }
90
+ end
91
+ end
92
+ ensure
93
+ tw.close if tw
94
+ gz.close if gz
95
+ end
96
+ end
97
+ end
98
+ task TARDIST => [ :test ]
99
+
100
+ def sign(file)
101
+ sh %("C:\\Program Files\\Windows Privacy Tools\\GnuPG\\Gpg.exe" -ba #{file})
102
+ end
103
+
104
+ task :signtar => [ :tar ] do
105
+ sign TARDIST
106
+ end
107
+ task :signgem => [ :gem ] do
108
+ sign "../#{DISTDIR}.gem"
109
+ end
110
+
111
+ desc "Build everything."
112
+ task :default => [ :signtar, :signgem ] do
113
+ end
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ #--
3
+ # Archive::Tar::Minitar 0.5.2
4
+ # Copyright 2004 Mauricio Julio Ferna'ndez Pradier and Austin Ziegler
5
+ #
6
+ # This program is based on and incorporates parts of RPA::Package from
7
+ # rpa-base (lib/rpa/package.rb and lib/rpa/util.rb) by Mauricio and has been
8
+ # adapted to be more generic by Austin.
9
+ #
10
+ # It is licensed under the GNU General Public Licence or Ruby's licence.
11
+ #
12
+ # $Id$
13
+ #++
14
+
15
+ # 1) Try to load Archive::Tar::Minitar from the gem.
16
+ # 2) Try to load Archive::Tar::Minitar from $LOAD_PATH.
17
+ begin
18
+ require 'rubygems'
19
+ require_gem 'archive-tar-minitar', '= 0.5.2'
20
+ rescue LoadError
21
+ nil
22
+ end
23
+
24
+ require 'archive/tar/minitar'
25
+ require 'archive/tar/minitar/command'
26
+
27
+ exit Archive::Tar::Minitar::Command.run(ARGV)
@@ -0,0 +1,985 @@
1
+ #!/usr/bin/env ruby
2
+ #--
3
+ # Archive::Tar::Minitar 0.5.2
4
+ # Copyright 2004 Mauricio Julio Ferna'ndez Pradier and Austin Ziegler
5
+ #
6
+ # This program is based on and incorporates parts of RPA::Package from
7
+ # rpa-base (lib/rpa/package.rb and lib/rpa/util.rb) by Mauricio and has been
8
+ # adapted to be more generic by Austin.
9
+ #
10
+ # It is licensed under the GNU General Public Licence or Ruby's licence.
11
+ #
12
+ # $Id$
13
+ #++
14
+
15
+ module Archive; end
16
+ module Archive::Tar; end
17
+
18
+ # = Archive::Tar::PosixHeader
19
+ # Implements the POSIX tar header as a Ruby class. The structure of
20
+ # the POSIX tar header is:
21
+ #
22
+ # struct tarfile_entry_posix
23
+ # { // pack/unpack
24
+ # char name[100]; // ASCII (+ Z unless filled) a100/Z100
25
+ # char mode[8]; // 0 padded, octal, null a8 /A8
26
+ # char uid[8]; // ditto a8 /A8
27
+ # char gid[8]; // ditto a8 /A8
28
+ # char size[12]; // 0 padded, octal, null a12 /A12
29
+ # char mtime[12]; // 0 padded, octal, null a12 /A12
30
+ # char checksum[8]; // 0 padded, octal, null, space a8 /A8
31
+ # char typeflag[1]; // see below a /a
32
+ # char linkname[100]; // ASCII + (Z unless filled) a100/Z100
33
+ # char magic[6]; // "ustar\0" a6 /A6
34
+ # char version[2]; // "00" a2 /A2
35
+ # char uname[32]; // ASCIIZ a32 /Z32
36
+ # char gname[32]; // ASCIIZ a32 /Z32
37
+ # char devmajor[8]; // 0 padded, octal, null a8 /A8
38
+ # char devminor[8]; // 0 padded, octal, null a8 /A8
39
+ # char prefix[155]; // ASCII (+ Z unless filled) a155/Z155
40
+ # };
41
+ #
42
+ # The +typeflag+ may be one of the following known values:
43
+ #
44
+ # <tt>"0"</tt>:: Regular file. NULL should be treated as a synonym, for
45
+ # compatibility purposes.
46
+ # <tt>"1"</tt>:: Hard link.
47
+ # <tt>"2"</tt>:: Symbolic link.
48
+ # <tt>"3"</tt>:: Character device node.
49
+ # <tt>"4"</tt>:: Block device node.
50
+ # <tt>"5"</tt>:: Directory.
51
+ # <tt>"6"</tt>:: FIFO node.
52
+ # <tt>"7"</tt>:: Reserved.
53
+ #
54
+ # POSIX indicates that "A POSIX-compliant implementation must treat any
55
+ # unrecognized typeflag value as a regular file."
56
+ class Archive::Tar::PosixHeader
57
+ FIELDS = %w(name mode uid gid size mtime checksum typeflag linkname) +
58
+ %w(magic version uname gname devmajor devminor prefix)
59
+
60
+ FIELDS.each { |field| attr_reader field.intern }
61
+
62
+ HEADER_PACK_FORMAT = "a100a8a8a8a12a12a7aaa100a6a2a32a32a8a8a155"
63
+ HEADER_UNPACK_FORMAT = "Z100A8A8A8A12A12A8aZ100A6A2Z32Z32A8A8Z155"
64
+
65
+ # Creates a new PosixHeader from a data stream.
66
+ def self.new_from_stream(stream, long_name = nil)
67
+ data = stream.read(512)
68
+ fields = data.unpack(HEADER_UNPACK_FORMAT)
69
+ name = fields.shift
70
+ mode = fields.shift.oct
71
+ uid = fields.shift.oct
72
+ gid = fields.shift.oct
73
+ size = fields.shift.oct
74
+ mtime = fields.shift.oct
75
+ checksum = fields.shift.oct
76
+ typeflag = fields.shift
77
+ linkname = fields.shift
78
+ magic = fields.shift
79
+ version = fields.shift.oct
80
+ uname = fields.shift
81
+ gname = fields.shift
82
+ devmajor = fields.shift.oct
83
+ devminor = fields.shift.oct
84
+ prefix = fields.shift
85
+
86
+ empty = (data == "\0" * 512)
87
+
88
+ if typeflag == 'L' && name == '././@LongLink'
89
+ long_name = stream.read(512).rstrip
90
+ return new_from_stream(stream, long_name)
91
+ end
92
+
93
+ new(:name => long_name || name,
94
+ :mode => mode, :uid => uid, :gid => gid,
95
+ :size => size, :mtime => mtime, :checksum => checksum,
96
+ :typeflag => typeflag, :magic => magic, :version => version,
97
+ :uname => uname, :gname => gname, :devmajor => devmajor,
98
+ :devminor => devminor, :prefix => prefix, :empty => empty)
99
+ end
100
+
101
+ # Creates a new PosixHeader. A PosixHeader cannot be created unless the
102
+ # #name, #size, #prefix, and #mode are provided.
103
+ def initialize(vals)
104
+ unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode]
105
+ raise ArgumentError
106
+ end
107
+
108
+ vals[:mtime] ||= 0
109
+ vals[:checksum] ||= ""
110
+ vals[:typeflag] ||= "0"
111
+ vals[:magic] ||= "ustar"
112
+ vals[:version] ||= "00"
113
+
114
+ FIELDS.each do |field|
115
+ instance_variable_set("@#{field}", vals[field.intern])
116
+ end
117
+ @empty = vals[:empty]
118
+ end
119
+
120
+ def empty?
121
+ @empty
122
+ end
123
+
124
+ def to_s
125
+ update_checksum
126
+ header(@checksum)
127
+ end
128
+
129
+ # Update the checksum field.
130
+ def update_checksum
131
+ hh = header(" " * 8)
132
+ @checksum = oct(calculate_checksum(hh), 6)
133
+ end
134
+
135
+ private
136
+ def oct(num, len)
137
+ if num.nil?
138
+ "\0" * (len + 1)
139
+ else
140
+ "%0#{len}o" % num
141
+ end
142
+ end
143
+
144
+ def calculate_checksum(hdr)
145
+ hdr.unpack("C*").inject { |aa, bb| aa + bb }
146
+ end
147
+
148
+ def header(chksum)
149
+ arr = [name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11),
150
+ oct(mtime, 11), chksum, " ", typeflag, linkname, magic, version,
151
+ uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix]
152
+ str = arr.pack(HEADER_PACK_FORMAT)
153
+ str + "\0" * ((512 - str.size) % 512)
154
+ end
155
+ end
156
+
157
+ require 'fileutils'
158
+ require 'find'
159
+
160
+ # = Archive::Tar::Minitar 0.5.2
161
+ # Archive::Tar::Minitar is a pure-Ruby library and command-line
162
+ # utility that provides the ability to deal with POSIX tar(1) archive
163
+ # files. The implementation is based heavily on Mauricio Ferna'ndez's
164
+ # implementation in rpa-base, but has been reorganised to promote
165
+ # reuse in other projects.
166
+ #
167
+ # This tar class performs a subset of all tar (POSIX tape archive)
168
+ # operations. We can only deal with typeflags 0, 1, 2, and 5 (see
169
+ # Archive::Tar::PosixHeader). All other typeflags will be treated as
170
+ # normal files.
171
+ #
172
+ # NOTE::: support for typeflags 1 and 2 is not yet implemented in this
173
+ # version.
174
+ #
175
+ # This release is version 0.5.2. The library can only handle files and
176
+ # directories at this point. A future version will be expanded to
177
+ # handle symbolic links and hard links in a portable manner. The
178
+ # command line utility, minitar, can only create archives, extract
179
+ # from archives, and list archive contents.
180
+ #
181
+ # == Synopsis
182
+ # Using this library is easy. The simplest case is:
183
+ #
184
+ # require 'zlib'
185
+ # require 'archive/tar/minitar'
186
+ # include Archive::Tar
187
+ #
188
+ # # Packs everything that matches Find.find('tests')
189
+ # File.open('test.tar', 'wb') { |tar| Minitar.pack('tests', tar) }
190
+ # # Unpacks 'test.tar' to 'x', creating 'x' if necessary.
191
+ # Minitar.unpack('test.tar', 'x')
192
+ #
193
+ # A gzipped tar can be written with:
194
+ #
195
+ # tgz = Zlib::GzipWriter.new(File.open('test.tgz', 'wb'))
196
+ # # Warning: tgz will be closed!
197
+ # Minitar.pack('tests', tgz)
198
+ #
199
+ # tgz = Zlib::GzipReader.new(File.open('test.tgz', 'rb'))
200
+ # # Warning: tgz will be closed!
201
+ # Minitar.unpack(tgz, 'x')
202
+ #
203
+ # As the case above shows, one need not write to a file. However, it
204
+ # will sometimes require that one dive a little deeper into the API,
205
+ # as in the case of StringIO objects. Note that I'm not providing a
206
+ # block with Minitar::Output, as Minitar::Output#close automatically
207
+ # closes both the Output object and the wrapped data stream object.
208
+ #
209
+ # begin
210
+ # sgz = Zlib::GzipWriter.new(StringIO.new(""))
211
+ # tar = Output.new(sgz)
212
+ # Find.find('tests') do |entry|
213
+ # Minitar.pack_file(entry, tar)
214
+ # end
215
+ # ensure
216
+ # # Closes both tar and sgz.
217
+ # tar.close
218
+ # end
219
+ #
220
+ # == Copyright
221
+ # Copyright 2004 Mauricio Julio Ferna'ndez Pradier and Austin Ziegler
222
+ #
223
+ # This program is based on and incorporates parts of RPA::Package from
224
+ # rpa-base (lib/rpa/package.rb and lib/rpa/util.rb) by Mauricio and
225
+ # has been adapted to be more generic by Austin.
226
+ #
227
+ # 'minitar' contains an adaptation of Ruby/ProgressBar by Satoru
228
+ # Takabayashi <satoru@namazu.org>, copyright 2001 - 2004.
229
+ #
230
+ # This program is free software. It may be redistributed and/or
231
+ # modified under the terms of the GPL version 2 (or later) or Ruby's
232
+ # licence.
233
+ module Archive::Tar::Minitar
234
+ VERSION = "0.5.4"
235
+
236
+ # The exception raised when a wrapped data stream class is expected to
237
+ # respond to #rewind or #pos but does not.
238
+ class NonSeekableStream < StandardError; end
239
+ # The exception raised when a block is required for proper operation of
240
+ # the method.
241
+ class BlockRequired < ArgumentError; end
242
+ # The exception raised when operations are performed on a stream that has
243
+ # previously been closed.
244
+ class ClosedStream < StandardError; end
245
+ # The exception raised when a filename exceeds 256 bytes in length,
246
+ # the maximum supported by the standard Tar format.
247
+ class FileNameTooLong < StandardError; end
248
+ # The exception raised when a data stream ends before the amount of data
249
+ # expected in the archive's PosixHeader.
250
+ class UnexpectedEOF < StandardError; end
251
+
252
+ # The class that writes a tar format archive to a data stream.
253
+ class Writer
254
+ # A stream wrapper that can only be written to. Any attempt to read
255
+ # from this restricted stream will result in a NameError being thrown.
256
+ class RestrictedStream
257
+ def initialize(anIO)
258
+ @io = anIO
259
+ end
260
+
261
+ def write(data)
262
+ @io.write(data)
263
+ end
264
+ end
265
+
266
+ # A RestrictedStream that also has a size limit.
267
+ class BoundedStream < Archive::Tar::Minitar::Writer::RestrictedStream
268
+ # The exception raised when the user attempts to write more data to
269
+ # a BoundedStream than has been allocated.
270
+ class FileOverflow < RuntimeError; end
271
+
272
+ # The maximum number of bytes that may be written to this data
273
+ # stream.
274
+ attr_reader :limit
275
+ # The current total number of bytes written to this data stream.
276
+ attr_reader :written
277
+
278
+ def initialize(io, limit)
279
+ @io = io
280
+ @limit = limit
281
+ @written = 0
282
+ end
283
+
284
+ def write(data)
285
+ raise FileOverflow if (data.size + @written) > @limit
286
+ @io.write(data)
287
+ @written += data.size
288
+ data.size
289
+ end
290
+ end
291
+
292
+ # With no associated block, +Writer::open+ is a synonym for
293
+ # +Writer::new+. If the optional code block is given, it will be
294
+ # passed the new _writer_ as an argument and the Writer object will
295
+ # automatically be closed when the block terminates. In this instance,
296
+ # +Writer::open+ returns the value of the block.
297
+ def self.open(anIO)
298
+ writer = Writer.new(anIO)
299
+
300
+ return writer unless block_given?
301
+
302
+ begin
303
+ res = yield writer
304
+ ensure
305
+ writer.close
306
+ end
307
+
308
+ res
309
+ end
310
+
311
+ # Creates and returns a new Writer object.
312
+ def initialize(anIO)
313
+ @io = anIO
314
+ @closed = false
315
+ end
316
+
317
+ # Adds a file to the archive as +name+. +opts+ must contain the
318
+ # following values:
319
+ #
320
+ # <tt>:mode</tt>:: The Unix file permissions mode value.
321
+ # <tt>:size</tt>:: The size, in bytes.
322
+ #
323
+ # +opts+ may contain the following values:
324
+ #
325
+ # <tt>:uid</tt>: The Unix file owner user ID number.
326
+ # <tt>:gid</tt>: The Unix file owner group ID number.
327
+ # <tt>:mtime</tt>:: The *integer* modification time value.
328
+ #
329
+ # It will not be possible to add more than <tt>opts[:size]</tt> bytes
330
+ # to the file.
331
+ def add_file_simple(name, opts = {}) # :yields BoundedStream:
332
+ raise Archive::Tar::Minitar::BlockRequired unless block_given?
333
+ raise Archive::Tar::ClosedStream if @closed
334
+
335
+ name, prefix = split_name(name)
336
+
337
+ header = { :name => name, :mode => opts[:mode], :mtime => opts[:mtime],
338
+ :size => opts[:size], :gid => opts[:gid], :uid => opts[:uid],
339
+ :prefix => prefix }
340
+ header = Archive::Tar::PosixHeader.new(header).to_s
341
+ @io.write(header)
342
+
343
+ os = BoundedStream.new(@io, opts[:size])
344
+ yield os
345
+ # FIXME: what if an exception is raised in the block?
346
+
347
+ min_padding = opts[:size] - os.written
348
+ @io.write("\0" * min_padding)
349
+ remainder = (512 - (opts[:size] % 512)) % 512
350
+ @io.write("\0" * remainder)
351
+ end
352
+
353
+ # Adds a file to the archive as +name+. +opts+ must contain the
354
+ # following value:
355
+ #
356
+ # <tt>:mode</tt>:: The Unix file permissions mode value.
357
+ #
358
+ # +opts+ may contain the following values:
359
+ #
360
+ # <tt>:uid</tt>: The Unix file owner user ID number.
361
+ # <tt>:gid</tt>: The Unix file owner group ID number.
362
+ # <tt>:mtime</tt>:: The *integer* modification time value.
363
+ #
364
+ # The file's size will be determined from the amount of data written
365
+ # to the stream.
366
+ #
367
+ # For #add_file to be used, the Archive::Tar::Minitar::Writer must be
368
+ # wrapping a stream object that is seekable (e.g., it responds to
369
+ # #pos=). Otherwise, #add_file_simple must be used.
370
+ #
371
+ # +opts+ may be modified during the writing to the stream.
372
+ def add_file(name, opts = {}) # :yields RestrictedStream, +opts+:
373
+ raise Archive::Tar::Minitar::BlockRequired unless block_given?
374
+ raise Archive::Tar::Minitar::ClosedStream if @closed
375
+ raise Archive::Tar::Minitar::NonSeekableStream unless @io.respond_to?(:pos=)
376
+
377
+ name, prefix = split_name(name)
378
+ init_pos = @io.pos
379
+ @io.write("\0" * 512) # placeholder for the header
380
+
381
+ yield RestrictedStream.new(@io), opts
382
+ # FIXME: what if an exception is raised in the block?
383
+
384
+ size = @io.pos - (init_pos + 512)
385
+ remainder = (512 - (size % 512)) % 512
386
+ @io.write("\0" * remainder)
387
+
388
+ final_pos = @io.pos
389
+ @io.pos = init_pos
390
+
391
+ header = { :name => name, :mode => opts[:mode], :mtime => opts[:mtime],
392
+ :size => size, :gid => opts[:gid], :uid => opts[:uid],
393
+ :prefix => prefix }
394
+ header = Archive::Tar::PosixHeader.new(header).to_s
395
+ @io.write(header)
396
+ @io.pos = final_pos
397
+ end
398
+
399
+ # Creates a directory in the tar.
400
+ def mkdir(name, opts = {})
401
+ raise ClosedStream if @closed
402
+ name, prefix = split_name(name)
403
+ header = { :name => name, :mode => opts[:mode], :typeflag => "5",
404
+ :size => 0, :gid => opts[:gid], :uid => opts[:uid],
405
+ :mtime => opts[:mtime], :prefix => prefix }
406
+ header = Archive::Tar::PosixHeader.new(header).to_s
407
+ @io.write(header)
408
+ nil
409
+ end
410
+
411
+ # Passes the #flush method to the wrapped stream, used for buffered
412
+ # streams.
413
+ def flush
414
+ raise ClosedStream if @closed
415
+ @io.flush if @io.respond_to?(:flush)
416
+ end
417
+
418
+ # Closes the Writer.
419
+ def close
420
+ return if @closed
421
+ @io.write("\0" * 1024)
422
+ @closed = true
423
+ end
424
+
425
+ private
426
+ def split_name(name)
427
+ raise FileNameTooLong if name.size > 256
428
+ if name.size <= 100
429
+ prefix = ""
430
+ else
431
+ parts = name.split(/\//)
432
+ newname = parts.pop
433
+
434
+ nxt = ""
435
+
436
+ loop do
437
+ nxt = parts.pop
438
+ break if newname.size + 1 + nxt.size > 100
439
+ newname = "#{nxt}/#{newname}"
440
+ end
441
+
442
+ prefix = (parts + [nxt]).join("/")
443
+
444
+ name = newname
445
+
446
+ raise FileNameTooLong if name.size > 100 || prefix.size > 155
447
+ end
448
+ return name, prefix
449
+ end
450
+ end
451
+
452
+ # The class that reads a tar format archive from a data stream. The data
453
+ # stream may be sequential or random access, but certain features only work
454
+ # with random access data streams.
455
+ class Reader
456
+ # This marks the EntryStream closed for reading without closing the
457
+ # actual data stream.
458
+ module InvalidEntryStream
459
+ def read(len = nil); raise ClosedStream; end
460
+ def getc; raise ClosedStream; end
461
+ def rewind; raise ClosedStream; end
462
+ end
463
+
464
+ # EntryStreams are pseudo-streams on top of the main data stream.
465
+ class EntryStream
466
+ Archive::Tar::PosixHeader::FIELDS.each do |field|
467
+ attr_reader field.intern
468
+ end
469
+
470
+ def initialize(header, anIO)
471
+ @io = anIO
472
+ @name = header.name
473
+ @mode = header.mode
474
+ @uid = header.uid
475
+ @gid = header.gid
476
+ @size = header.size
477
+ @mtime = header.mtime
478
+ @checksum = header.checksum
479
+ @typeflag = header.typeflag
480
+ @linkname = header.linkname
481
+ @magic = header.magic
482
+ @version = header.version
483
+ @uname = header.uname
484
+ @gname = header.gname
485
+ @devmajor = header.devmajor
486
+ @devminor = header.devminor
487
+ @prefix = header.prefix
488
+ @read = 0
489
+ @orig_pos = @io.pos
490
+ end
491
+
492
+ # Reads +len+ bytes (or all remaining data) from the entry. Returns
493
+ # +nil+ if there is no more data to read.
494
+ def read(len = nil)
495
+ return nil if @read >= @size
496
+ len ||= @size - @read
497
+ max_read = [len, @size - @read].min
498
+ ret = @io.read(max_read)
499
+ @read += ret.size
500
+ ret
501
+ end
502
+
503
+ # Reads one byte from the entry. Returns +nil+ if there is no more data
504
+ # to read.
505
+ def getc
506
+ return nil if @read >= @size
507
+ ret = @io.getc
508
+ @read += 1 if ret
509
+ ret
510
+ end
511
+
512
+ # Returns +true+ if the entry represents a directory.
513
+ def directory?
514
+ @typeflag == "5"
515
+ end
516
+ alias_method :directory, :directory?
517
+
518
+ # Returns +true+ if the entry represents a plain file.
519
+ def file?
520
+ @typeflag == "0" or @typeflag == "\0"
521
+ end
522
+ alias_method :file, :file?
523
+
524
+ # Returns +true+ if the current read pointer is at the end of the
525
+ # EntryStream data.
526
+ def eof?
527
+ @read >= @size
528
+ end
529
+
530
+ # Returns the current read pointer in the EntryStream.
531
+ def pos
532
+ @read
533
+ end
534
+
535
+ # Sets the current read pointer to the beginning of the EntryStream.
536
+ def rewind
537
+ raise NonSeekableStream unless @io.respond_to?(:pos=)
538
+ @io.pos = @orig_pos
539
+ @read = 0
540
+ end
541
+
542
+ def bytes_read
543
+ @read
544
+ end
545
+
546
+ # Returns the full and proper name of the entry.
547
+ def full_name
548
+ if @prefix != ""
549
+ File.join(@prefix, @name)
550
+ else
551
+ @name
552
+ end
553
+ end
554
+
555
+ # Closes the entry.
556
+ def close
557
+ invalidate
558
+ end
559
+
560
+ private
561
+ def invalidate
562
+ extend InvalidEntryStream
563
+ end
564
+ end
565
+
566
+ # With no associated block, +Reader::open+ is a synonym for
567
+ # +Reader::new+. If the optional code block is given, it will be passed
568
+ # the new _writer_ as an argument and the Reader object will
569
+ # automatically be closed when the block terminates. In this instance,
570
+ # +Reader::open+ returns the value of the block.
571
+ def self.open(anIO)
572
+ reader = Reader.new(anIO)
573
+
574
+ return reader unless block_given?
575
+
576
+ begin
577
+ res = yield reader
578
+ ensure
579
+ reader.close
580
+ end
581
+
582
+ res
583
+ end
584
+
585
+ # Creates and returns a new Reader object.
586
+ def initialize(anIO)
587
+ @io = anIO
588
+ @init_pos = anIO.pos
589
+ end
590
+
591
+ # Iterates through each entry in the data stream.
592
+ def each(&block)
593
+ each_entry(&block)
594
+ end
595
+
596
+ # Resets the read pointer to the beginning of data stream. Do not call
597
+ # this during a #each or #each_entry iteration. This only works with
598
+ # random access data streams that respond to #rewind and #pos.
599
+ def rewind
600
+ if @init_pos == 0
601
+ raise NonSeekableStream unless @io.respond_to?(:rewind)
602
+ @io.rewind
603
+ else
604
+ raise NonSeekableStream unless @io.respond_to?(:pos=)
605
+ @io.pos = @init_pos
606
+ end
607
+ end
608
+
609
+ # Iterates through each entry in the data stream.
610
+ def each_entry
611
+ loop do
612
+ return if @io.eof?
613
+
614
+ header = Archive::Tar::PosixHeader.new_from_stream(@io)
615
+ return if header.empty?
616
+
617
+ entry = EntryStream.new(header, @io)
618
+ size = entry.size
619
+
620
+ yield entry
621
+
622
+ skip = (512 - (size % 512)) % 512
623
+
624
+ if @io.respond_to?(:seek)
625
+ # avoid reading...
626
+ @io.seek(size - entry.bytes_read, IO::SEEK_CUR)
627
+ else
628
+ pending = size - entry.bytes_read
629
+ while pending > 0
630
+ bread = @io.read([pending, 4096].min).size
631
+ raise UnexpectedEOF if @io.eof?
632
+ pending -= bread
633
+ end
634
+ end
635
+ @io.read(skip) # discard trailing zeros
636
+ # make sure nobody can use #read, #getc or #rewind anymore
637
+ entry.close
638
+ end
639
+ end
640
+
641
+ def close
642
+ end
643
+ end
644
+
645
+ # Wraps a Archive::Tar::Minitar::Reader with convenience methods and
646
+ # wrapped stream management; Input only works with random access data
647
+ # streams. See Input::new for details.
648
+ class Input
649
+ include Enumerable
650
+
651
+ # With no associated block, +Input::open+ is a synonym for
652
+ # +Input::new+. If the optional code block is given, it will be passed
653
+ # the new _writer_ as an argument and the Input object will
654
+ # automatically be closed when the block terminates. In this instance,
655
+ # +Input::open+ returns the value of the block.
656
+ def self.open(input)
657
+ stream = Input.new(input)
658
+ return stream unless block_given?
659
+
660
+ begin
661
+ res = yield stream
662
+ ensure
663
+ stream.close
664
+ end
665
+
666
+ res
667
+ end
668
+
669
+ # Creates a new Input object. If +input+ is a stream object that responds
670
+ # to #read), then it will simply be wrapped. Otherwise, one will be
671
+ # created and opened using Kernel#open. When Input#close is called, the
672
+ # stream object wrapped will be closed.
673
+ def initialize(input)
674
+ if input.respond_to?(:read)
675
+ @io = input
676
+ else
677
+ @io = open(input, "rb")
678
+ end
679
+ @tarreader = Archive::Tar::Minitar::Reader.new(@io)
680
+ end
681
+
682
+ # Iterates through each entry and rewinds to the beginning of the stream
683
+ # when finished.
684
+ def each(&block)
685
+ @tarreader.each { |entry| yield entry }
686
+ ensure
687
+ @tarreader.rewind
688
+ end
689
+
690
+ # Extracts the current +entry+ to +destdir+. If a block is provided, it
691
+ # yields an +action+ Symbol, the full name of the file being extracted
692
+ # (+name+), and a Hash of statistical information (+stats+).
693
+ #
694
+ # The +action+ will be one of:
695
+ # <tt>:dir</tt>:: The +entry+ is a directory.
696
+ # <tt>:file_start</tt>:: The +entry+ is a file; the extract of the
697
+ # file is just beginning.
698
+ # <tt>:file_progress</tt>:: Yielded every 4096 bytes during the extract
699
+ # of the +entry+.
700
+ # <tt>:file_done</tt>:: Yielded when the +entry+ is completed.
701
+ #
702
+ # The +stats+ hash contains the following keys:
703
+ # <tt>:current</tt>:: The current total number of bytes read in the
704
+ # +entry+.
705
+ # <tt>:currinc</tt>:: The current number of bytes read in this read
706
+ # cycle.
707
+ # <tt>:entry</tt>:: The entry being extracted; this is a
708
+ # Reader::EntryStream, with all methods thereof.
709
+ def extract_entry(destdir, entry) # :yields action, name, stats:
710
+ stats = {
711
+ :current => 0,
712
+ :currinc => 0,
713
+ :entry => entry
714
+ }
715
+
716
+ if entry.directory?
717
+ dest = File.join(destdir, entry.full_name)
718
+
719
+ yield :dir, entry.full_name, stats if block_given?
720
+
721
+ if Archive::Tar::Minitar.dir?(dest)
722
+ begin
723
+ FileUtils.chmod(entry.mode, dest)
724
+ rescue Exception
725
+ nil
726
+ end
727
+ else
728
+ FileUtils.mkdir_p(dest, :mode => entry.mode)
729
+ FileUtils.chmod(entry.mode, dest)
730
+ end
731
+
732
+ fsync_dir(dest)
733
+ fsync_dir(File.join(dest, ".."))
734
+ return
735
+ else # it's a file
736
+ destdir = File.join(destdir, File.dirname(entry.full_name))
737
+ FileUtils.mkdir_p(destdir, :mode => 0755)
738
+
739
+ destfile = File.join(destdir, File.basename(entry.full_name))
740
+ FileUtils.chmod(0600, destfile) rescue nil # Errno::ENOENT
741
+
742
+ yield :file_start, entry.full_name, stats if block_given?
743
+
744
+ File.open(destfile, "wb", entry.mode) do |os|
745
+ loop do
746
+ data = entry.read(4096)
747
+ break unless data
748
+
749
+ stats[:currinc] = os.write(data)
750
+ stats[:current] += stats[:currinc]
751
+
752
+ yield :file_progress, entry.full_name, stats if block_given?
753
+ end
754
+ os.fsync
755
+ end
756
+
757
+ FileUtils.chmod(entry.mode, destfile)
758
+ fsync_dir(File.dirname(destfile))
759
+ fsync_dir(File.join(File.dirname(destfile), ".."))
760
+
761
+ yield :file_done, entry.full_name, stats if block_given?
762
+ end
763
+ end
764
+
765
+ # Returns the Reader object for direct access.
766
+ def tar
767
+ @tarreader
768
+ end
769
+
770
+ # Closes the Reader object and the wrapped data stream.
771
+ def close
772
+ @io.close
773
+ @tarreader.close
774
+ end
775
+
776
+ private
777
+ def fsync_dir(dirname)
778
+ # make sure this hits the disc
779
+ dir = open(dirname, 'rb')
780
+ dir.fsync
781
+ rescue # ignore IOError if it's an unpatched (old) Ruby
782
+ nil
783
+ ensure
784
+ dir.close if dir rescue nil
785
+ end
786
+ end
787
+
788
+ # Wraps a Archive::Tar::Minitar::Writer with convenience methods and
789
+ # wrapped stream management; Output only works with random access data
790
+ # streams. See Output::new for details.
791
+ class Output
792
+ # With no associated block, +Output::open+ is a synonym for
793
+ # +Output::new+. If the optional code block is given, it will be passed
794
+ # the new _writer_ as an argument and the Output object will
795
+ # automatically be closed when the block terminates. In this instance,
796
+ # +Output::open+ returns the value of the block.
797
+ def self.open(output)
798
+ stream = Output.new(output)
799
+ return stream unless block_given?
800
+
801
+ begin
802
+ res = yield stream
803
+ ensure
804
+ stream.close
805
+ end
806
+
807
+ res
808
+ end
809
+
810
+ # Creates a new Output object. If +output+ is a stream object that
811
+ # responds to #read), then it will simply be wrapped. Otherwise, one will
812
+ # be created and opened using Kernel#open. When Output#close is called,
813
+ # the stream object wrapped will be closed.
814
+ def initialize(output)
815
+ if output.respond_to?(:write)
816
+ @io = output
817
+ else
818
+ @io = ::File.open(output, "wb")
819
+ end
820
+ @tarwriter = Archive::Tar::Minitar::Writer.new(@io)
821
+ end
822
+
823
+ # Returns the Writer object for direct access.
824
+ def tar
825
+ @tarwriter
826
+ end
827
+
828
+ # Closes the Writer object and the wrapped data stream.
829
+ def close
830
+ @tarwriter.close
831
+ @io.close
832
+ end
833
+ end
834
+
835
+ class << self
836
+ # Tests if +path+ refers to a directory. Fixes an apparently
837
+ # corrupted <tt>stat()</tt> call on Windows.
838
+ def dir?(path)
839
+ File.directory?((path[-1] == ?/) ? path : "#{path}/")
840
+ end
841
+
842
+ # A convenience method for wrapping Archive::Tar::Minitar::Input.open
843
+ # (mode +r+) and Archive::Tar::Minitar::Output.open (mode +w+). No other
844
+ # modes are currently supported.
845
+ def open(dest, mode = "r", &block)
846
+ case mode
847
+ when "r"
848
+ Input.open(dest, &block)
849
+ when "w"
850
+ Output.open(dest, &block)
851
+ else
852
+ raise "Unknown open mode for Archive::Tar::Minitar.open."
853
+ end
854
+ end
855
+
856
+ # A convenience method to packs the file provided. +entry+ may either be
857
+ # a filename (in which case various values for the file (see below) will
858
+ # be obtained from <tt>File#stat(entry)</tt> or a Hash with the fields:
859
+ #
860
+ # <tt>:name</tt>:: The filename to be packed into the tarchive.
861
+ # *REQUIRED*.
862
+ # <tt>:mode</tt>:: The mode to be applied.
863
+ # <tt>:uid</tt>:: The user owner of the file. (Ignored on Windows.)
864
+ # <tt>:gid</tt>:: The group owner of the file. (Ignored on Windows.)
865
+ # <tt>:mtime</tt>:: The modification Time of the file.
866
+ #
867
+ # During packing, if a block is provided, #pack_file yields an +action+
868
+ # Symol, the full name of the file being packed, and a Hash of
869
+ # statistical information, just as with
870
+ # Archive::Tar::Minitar::Input#extract_entry.
871
+ #
872
+ # The +action+ will be one of:
873
+ # <tt>:dir</tt>:: The +entry+ is a directory.
874
+ # <tt>:file_start</tt>:: The +entry+ is a file; the extract of the
875
+ # file is just beginning.
876
+ # <tt>:file_progress</tt>:: Yielded every 4096 bytes during the extract
877
+ # of the +entry+.
878
+ # <tt>:file_done</tt>:: Yielded when the +entry+ is completed.
879
+ #
880
+ # The +stats+ hash contains the following keys:
881
+ # <tt>:current</tt>:: The current total number of bytes read in the
882
+ # +entry+.
883
+ # <tt>:currinc</tt>:: The current number of bytes read in this read
884
+ # cycle.
885
+ # <tt>:name</tt>:: The filename to be packed into the tarchive.
886
+ # *REQUIRED*.
887
+ # <tt>:mode</tt>:: The mode to be applied.
888
+ # <tt>:uid</tt>:: The user owner of the file. (+nil+ on Windows.)
889
+ # <tt>:gid</tt>:: The group owner of the file. (+nil+ on Windows.)
890
+ # <tt>:mtime</tt>:: The modification Time of the file.
891
+ def pack_file(entry, outputter) #:yields action, name, stats:
892
+ outputter = outputter.tar if outputter.kind_of?(Archive::Tar::Minitar::Output)
893
+
894
+ stats = {}
895
+
896
+ if entry.kind_of?(Hash)
897
+ name = entry[:name]
898
+
899
+ entry.each { |kk, vv| stats[kk] = vv unless vv.nil? }
900
+ else
901
+ name = entry
902
+ end
903
+
904
+ name = name.sub(%r{\./}, '')
905
+ stat = File.stat(name)
906
+ stats[:mode] ||= stat.mode
907
+ stats[:mtime] ||= stat.mtime
908
+ stats[:size] = stat.size
909
+
910
+ if RUBY_PLATFORM =~ /win32/
911
+ stats[:uid] = nil
912
+ stats[:gid] = nil
913
+ else
914
+ stats[:uid] ||= stat.uid
915
+ stats[:gid] ||= stat.gid
916
+ end
917
+
918
+ case
919
+ when File.file?(name)
920
+ outputter.add_file_simple(name, stats) do |os|
921
+ stats[:current] = 0
922
+ yield :file_start, name, stats if block_given?
923
+ File.open(name, "rb") do |ff|
924
+ until ff.eof?
925
+ stats[:currinc] = os.write(ff.read(4096))
926
+ stats[:current] += stats[:currinc]
927
+ yield :file_progress, name, stats if block_given?
928
+ end
929
+ end
930
+ yield :file_done, name, stats if block_given?
931
+ end
932
+ when dir?(name)
933
+ yield :dir, name, stats if block_given?
934
+ outputter.mkdir(name, stats)
935
+ else
936
+ raise "Don't yet know how to pack this type of file."
937
+ end
938
+ end
939
+
940
+ # A convenience method to pack files specified by +src+ into +dest+. If
941
+ # +src+ is an Array, then each file detailed therein will be packed into
942
+ # the resulting Archive::Tar::Minitar::Output stream; if +recurse_dirs+
943
+ # is true, then directories will be recursed.
944
+ #
945
+ # If +src+ is an Array, it will be treated as the argument to Find.find;
946
+ # all files matching will be packed.
947
+ def pack(src, dest, recurse_dirs = true, &block)
948
+ Output.open(dest) do |outp|
949
+ if src.kind_of?(Array)
950
+ src.each do |entry|
951
+ pack_file(entry, outp, &block)
952
+ if dir?(entry) and recurse_dirs
953
+ Dir["#{entry}/**/**"].each do |ee|
954
+ pack_file(ee, outp, &block)
955
+ end
956
+ end
957
+ end
958
+ else
959
+ Find.find(src) do |entry|
960
+ pack_file(entry, outp, &block)
961
+ end
962
+ end
963
+ end
964
+ end
965
+
966
+ # A convenience method to unpack files from +src+ into the directory
967
+ # specified by +dest+. Only those files named explicitly in +files+
968
+ # will be extracted.
969
+ def unpack(src, dest, files = [], &block)
970
+ Input.open(src) do |inp|
971
+ if File.exist?(dest) and (not dir?(dest))
972
+ raise "Can't unpack to a non-directory."
973
+ elsif not File.exist?(dest)
974
+ FileUtils.mkdir_p(dest)
975
+ end
976
+
977
+ inp.each do |entry|
978
+ if files.empty? or files.include?(entry.full_name)
979
+ inp.extract_entry(dest, entry, &block)
980
+ end
981
+ end
982
+ end
983
+ end
984
+ end
985
+ end