minitar 0.5.4 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ Redistribution and use in source and binary forms, with or without
2
+ modification, are permitted provided that the following conditions are met:
3
+
4
+ 1. Redistributions of source code must retain the above copyright notice, this
5
+ list of conditions and the following disclaimer.
6
+ 2. Redistributions in binary form must reproduce the above copyright notice,
7
+ this list of conditions and the following disclaimer in the documentation
8
+ and/or other materials provided with the distribution.
9
+
10
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
11
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
12
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
13
+ DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
14
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
15
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
16
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
17
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
18
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
19
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,56 @@
1
+ Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
2
+ You can redistribute it and/or modify it under either the terms of the
3
+ 2-clause BSDL (see the file BSDL), or the conditions below:
4
+
5
+ 1. You may make and give away verbatim copies of the source form of the
6
+ software without restriction, provided that you duplicate all of the
7
+ original copyright notices and associated disclaimers.
8
+
9
+ 2. You may modify your copy of the software in any way, provided that
10
+ you do at least ONE of the following:
11
+
12
+ a) place your modifications in the Public Domain or otherwise
13
+ make them Freely Available, such as by posting said
14
+ modifications to Usenet or an equivalent medium, or by allowing
15
+ the author to include your modifications in the software.
16
+
17
+ b) use the modified software only within your corporation or
18
+ organization.
19
+
20
+ c) give non-standard binaries non-standard names, with
21
+ instructions on where to get the original software distribution.
22
+
23
+ d) make other distribution arrangements with the author.
24
+
25
+ 3. You may distribute the software in object code or binary form,
26
+ provided that you do at least ONE of the following:
27
+
28
+ a) distribute the binaries and library files of the software,
29
+ together with instructions (in the manual page or equivalent)
30
+ on where to get the original distribution.
31
+
32
+ b) accompany the distribution with the machine-readable source of
33
+ the software.
34
+
35
+ c) give non-standard binaries non-standard names, with
36
+ instructions on where to get the original software distribution.
37
+
38
+ d) make other distribution arrangements with the author.
39
+
40
+ 4. You may modify and include the part of the software into any other
41
+ software (possibly commercial). But some files in the distribution
42
+ are not written by the author, so that they are not under these terms.
43
+
44
+ For the list of those files and their copying conditions, see the
45
+ file LEGAL.
46
+
47
+ 5. The scripts and library files supplied as input to or produced as
48
+ output from the software do not automatically fall under the
49
+ copyright of the software, but belong to whomever generated them,
50
+ and may be sold commercially, and may be aggregated with this
51
+ software.
52
+
53
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
54
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
55
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
56
+ PURPOSE.
@@ -0,0 +1,3 @@
1
+ # coding: utf-8
2
+
3
+ require 'archive/tar/minitar'
@@ -1,913 +1,189 @@
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
- #++
1
+ # coding: utf-8
14
2
 
3
+ ##
15
4
  module Archive; end
5
+ ##
16
6
  module Archive::Tar; end
17
7
 
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])
8
+ require 'fileutils'
9
+ require 'rbconfig'
10
+
11
+ class << Archive::Tar #:nodoc:
12
+ def const_missing(const) #:nodoc:
13
+ case const
14
+ when :PosixHeader
15
+ warn 'Archive::Tar::PosixHeader has been renamed to ' \
16
+ 'Archive::Tar::Minitar::PosixHeader'
17
+ const_set :PosixHeader, Archive::Tar::Minitar::PosixHeader
18
+ else
19
+ super
116
20
  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
21
  end
134
22
 
135
23
  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
24
 
144
- def calculate_checksum(hdr)
145
- hdr.unpack("C*").inject { |aa, bb| aa + bb }
25
+ def included(mod)
26
+ return if modules.include?(mod)
27
+ warn "Including Minitar via the #{self} namespace is deprecated."
28
+ modules.add mod
146
29
  end
147
30
 
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)
31
+ def modules
32
+ require 'set'
33
+ @modules ||= Set.new
154
34
  end
155
35
  end
156
36
 
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.
37
+ # == Synopsis
38
+ #
39
+ # Using minitar is easy. The simplest case is:
40
+ #
41
+ # require 'zlib'
42
+ # require 'minitar'
43
+ #
44
+ # # Packs everything that matches Find.find('tests').
45
+ # # test.tar will automatically be closed by Minitar.pack.
46
+ # Minitar.pack('tests', File.open('test.tar', 'wb'))
47
+ #
48
+ # # Unpacks 'test.tar' to 'x', creating 'x' if necessary.
49
+ # Minitar.unpack('test.tar', 'x')
50
+ #
51
+ # A gzipped tar can be written with:
52
+ #
53
+ # # test.tgz will be closed automatically.
54
+ # Minitar.pack('tests', Zlib::GzipWriter.new(File.open('test.tgz', 'wb'))
55
+ #
56
+ # # test.tgz will be closed automatically.
57
+ # Minitar.unpack(Zlib::GzipReader.new(File.open('test.tgz', 'rb')), 'x')
58
+ #
59
+ # As the case above shows, one need not write to a file. However, it will
60
+ # sometimes require that one dive a little deeper into the API, as in the case
61
+ # of StringIO objects. Note that I'm not providing a block with
62
+ # Minitar::Output, as Minitar::Output#close automatically closes both the
63
+ # Output object and the wrapped data stream object.
64
+ #
65
+ # begin
66
+ # sgz = Zlib::GzipWriter.new(StringIO.new(""))
67
+ # tar = Output.new(sgz)
68
+ # Find.find('tests') do |entry|
69
+ # Minitar.pack_file(entry, tar)
70
+ # end
71
+ # ensure
72
+ # # Closes both tar and sgz.
73
+ # tar.close
74
+ # end
233
75
  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
76
+ VERSION = '0.6'.freeze # :nodoc:
77
+
78
+ # The base class for any minitar error.
79
+ Error = Class.new(::StandardError)
80
+ # Raised when a wrapped data stream class is not seekable.
81
+ NonSeekableStream = Class.new(Error)
82
+ # The exception raised when operations are performed on a stream that has
83
+ # previously been closed.
84
+ ClosedStream = Class.new(Error)
85
+ # The exception raised when a filename exceeds 256 bytes in length, the
86
+ # maximum supported by the standard Tar format.
87
+ FileNameTooLong = Class.new(Error)
88
+ # The exception raised when a data stream ends before the amount of data
89
+ # expected in the archive's PosixHeader.
90
+ UnexpectedEOF = Class.new(StandardError)
91
+ # The exception raised when a file contains a relative path in secure mode
92
+ # (the default for this version).
93
+ SecureRelativePathError = Class.new(Error)
834
94
 
835
95
  class << self
836
- # Tests if +path+ refers to a directory. Fixes an apparently
837
- # corrupted <tt>stat()</tt> call on Windows.
96
+ # Tests if +path+ refers to a directory. Fixes an apparently
97
+ # corrupted <tt>stat()</tt> call on Windows.
838
98
  def dir?(path)
839
- File.directory?((path[-1] == ?/) ? path : "#{path}/")
99
+ # rubocop:disable Style/CharacterLiteral
100
+ File.directory?(path[-1] == ?/ ? path : "#{path}/")
101
+ # rubocop:enable Style/CharacterLiteral
840
102
  end
841
103
 
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)
104
+ # A convenience method for wrapping Archive::Tar::Minitar::Input.open
105
+ # (mode +r+) and Archive::Tar::Minitar::Output.open (mode +w+). No other
106
+ # modes are currently supported.
107
+ def open(dest, mode = 'r', &block)
846
108
  case mode
847
- when "r"
109
+ when 'r'
848
110
  Input.open(dest, &block)
849
- when "w"
111
+ when 'w'
850
112
  Output.open(dest, &block)
851
113
  else
852
- raise "Unknown open mode for Archive::Tar::Minitar.open."
114
+ raise 'Unknown open mode for Archive::Tar::Minitar.open.'
853
115
  end
854
116
  end
855
117
 
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.
118
+ def const_missing(c) #:nodoc:
119
+ case c
120
+ when :BlockRequired
121
+ warn 'This constant has been removed.'
122
+ const_set(:BlockRequired, Class.new(StandardError))
123
+ else
124
+ super
125
+ end
126
+ end
127
+
128
+ def windows? #:nodoc:
129
+ RbConfig::CONFIG['host_os'] =~ /^(mswin|mingw|cygwin)/
130
+ end
131
+
132
+ # A convenience method to pack the file provided. +entry+ may either be a
133
+ # filename (in which case various values for the file (see below) will be
134
+ # obtained from <tt>File#stat(entry)</tt> or a Hash with the fields:
135
+ #
136
+ # <tt>:name</tt>:: The filename to be packed into the archive. Required.
137
+ # <tt>:mode</tt>:: The mode to be applied.
138
+ # <tt>:uid</tt>:: The user owner of the file. (Ignored on Windows.)
139
+ # <tt>:gid</tt>:: The group owner of the file. (Ignored on Windows.)
140
+ # <tt>:mtime</tt>:: The modification Time of the file.
141
+ #
142
+ # During packing, if a block is provided, #pack_file yields an +action+
143
+ # Symol, the full name of the file being packed, and a Hash of
144
+ # statistical information, just as with
145
+ # Archive::Tar::Minitar::Input#extract_entry.
146
+ #
147
+ # The +action+ will be one of:
148
+ # <tt>:dir</tt>:: The +entry+ is a directory.
149
+ # <tt>:file_start</tt>:: The +entry+ is a file; the extract of the
150
+ # file is just beginning.
151
+ # <tt>:file_progress</tt>:: Yielded every 4096 bytes during the extract
152
+ # of the +entry+.
153
+ # <tt>:file_done</tt>:: Yielded when the +entry+ is completed.
154
+ #
155
+ # The +stats+ hash contains the following keys:
156
+ # <tt>:current</tt>:: The current total number of bytes read in the
157
+ # +entry+.
158
+ # <tt>:currinc</tt>:: The current number of bytes read in this read
159
+ # cycle.
160
+ # <tt>:name</tt>:: The filename to be packed into the tarchive.
161
+ # *REQUIRED*.
162
+ # <tt>:mode</tt>:: The mode to be applied.
163
+ # <tt>:uid</tt>:: The user owner of the file. (+nil+ on Windows.)
164
+ # <tt>:gid</tt>:: The group owner of the file. (+nil+ on Windows.)
165
+ # <tt>:mtime</tt>:: The modification Time of the file.
891
166
  def pack_file(entry, outputter) #:yields action, name, stats:
892
- outputter = outputter.tar if outputter.kind_of?(Archive::Tar::Minitar::Output)
167
+ if outputter.kind_of?(Archive::Tar::Minitar::Output)
168
+ outputter = outputter.tar
169
+ end
893
170
 
894
171
  stats = {}
895
172
 
896
173
  if entry.kind_of?(Hash)
897
174
  name = entry[:name]
898
-
899
175
  entry.each { |kk, vv| stats[kk] = vv unless vv.nil? }
900
176
  else
901
177
  name = entry
902
178
  end
903
-
179
+
904
180
  name = name.sub(%r{\./}, '')
905
181
  stat = File.stat(name)
906
182
  stats[:mode] ||= stat.mode
907
183
  stats[:mtime] ||= stat.mtime
908
- stats[:size] = stat.size
184
+ stats[:size] = stat.size
909
185
 
910
- if RUBY_PLATFORM =~ /win32/
186
+ if windows?
911
187
  stats[:uid] = nil
912
188
  stats[:gid] = nil
913
189
  else
@@ -915,12 +191,11 @@ module Archive::Tar::Minitar
915
191
  stats[:gid] ||= stat.gid
916
192
  end
917
193
 
918
- case
919
- when File.file?(name)
194
+ if File.file?(name)
920
195
  outputter.add_file_simple(name, stats) do |os|
921
196
  stats[:current] = 0
922
197
  yield :file_start, name, stats if block_given?
923
- File.open(name, "rb") do |ff|
198
+ File.open(name, 'rb') do |ff|
924
199
  until ff.eof?
925
200
  stats[:currinc] = os.write(ff.read(4096))
926
201
  stats[:current] += stats[:currinc]
@@ -929,30 +204,32 @@ module Archive::Tar::Minitar
929
204
  end
930
205
  yield :file_done, name, stats if block_given?
931
206
  end
932
- when dir?(name)
207
+ elsif dir?(name)
933
208
  yield :dir, name, stats if block_given?
934
209
  outputter.mkdir(name, stats)
935
210
  else
936
- raise "Don't yet know how to pack this type of file."
211
+ raise %q(Don't yet know how to pack this type of file.)
937
212
  end
938
213
  end
939
214
 
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.
215
+ # A convenience method to pack files specified by +src+ into +dest+. If
216
+ # +src+ is an Array, then each file detailed therein will be packed into
217
+ # the resulting Archive::Tar::Minitar::Output stream; if +recurse_dirs+ is
218
+ # true, then directories will be recursed.
219
+ #
220
+ # If +src+ is an Array, it will be treated as the result of Find.find; all
221
+ # files matching will be packed.
947
222
  def pack(src, dest, recurse_dirs = true, &block)
223
+ require 'find'
948
224
  Output.open(dest) do |outp|
949
225
  if src.kind_of?(Array)
950
226
  src.each do |entry|
951
- pack_file(entry, outp, &block)
952
227
  if dir?(entry) and recurse_dirs
953
- Dir["#{entry}/**/**"].each do |ee|
228
+ Find.find(entry) do |ee|
954
229
  pack_file(ee, outp, &block)
955
230
  end
231
+ else
232
+ pack_file(entry, outp, &block)
956
233
  end
957
234
  end
958
235
  else
@@ -963,17 +240,17 @@ module Archive::Tar::Minitar
963
240
  end
964
241
  end
965
242
 
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.
243
+ # A convenience method to unpack files from +src+ into the directory
244
+ # specified by +dest+. Only those files named explicitly in +files+
245
+ # will be extracted.
969
246
  def unpack(src, dest, files = [], &block)
970
247
  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)
248
+ if File.exist?(dest) and !dir?(dest)
249
+ raise %q(Can't unpack to a non-directory.)
975
250
  end
976
251
 
252
+ FileUtils.mkdir_p(dest) unless File.exist?(dest)
253
+
977
254
  inp.each do |entry|
978
255
  if files.empty? or files.include?(entry.full_name)
979
256
  inp.extract_entry(dest, entry, &block)
@@ -981,5 +258,36 @@ module Archive::Tar::Minitar
981
258
  end
982
259
  end
983
260
  end
261
+
262
+ # Check whether +io+ can seek without errors.
263
+ def seekable?(io, methods = nil)
264
+ # The IO class throws an exception at runtime if we try to change
265
+ # position on a non-regular file.
266
+ if io.respond_to?(:stat)
267
+ io.stat.file?
268
+ else
269
+ # Duck-type the rest of this.
270
+ methods ||= [ :pos, :pos=, :seek, :rewind ]
271
+ methods = [ methods ] unless methods.kind_of?(Array)
272
+ methods.all? { |m| io.respond_to?(m) }
273
+ end
274
+ end
275
+
276
+ private
277
+
278
+ def included(mod)
279
+ return if modules.include?(mod)
280
+ warn "Including #{self} has been deprecated."
281
+ modules << mod
282
+ end
283
+
284
+ def modules
285
+ require 'set'
286
+ @modules ||= Set.new
287
+ end
984
288
  end
985
289
  end
290
+
291
+ require 'archive/tar/minitar/posix_header'
292
+ require 'archive/tar/minitar/input'
293
+ require 'archive/tar/minitar/output'