gem 0.0.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ module Gem::Tar
2
+ end
3
+
4
+ require 'gem/tar/header'
5
+ require 'gem/tar/entry'
6
+ require 'gem/tar/reader'
7
+ require 'gem/tar/writer'
@@ -0,0 +1,145 @@
1
+ # -*- coding: utf-8 -*-
2
+ #++
3
+ # Copyright (C) 2004 Mauricio Julio Fernández Pradier
4
+ # See LICENSE.txt for additional licensing information.
5
+ #--
6
+
7
+ ##
8
+ # Class for reading entries out of a tar file
9
+
10
+ class Gem::Tar::Entry
11
+
12
+ ##
13
+ # Header for this tar entry
14
+
15
+ attr_reader :header
16
+
17
+ ##
18
+ # Creates a new tar entry for +header+ that will be read from +io+
19
+
20
+ def initialize(header, io)
21
+ @closed = false
22
+ @header = header
23
+ @io = io
24
+ @orig_pos = @io.pos
25
+ @read = 0
26
+ end
27
+
28
+ def check_closed # :nodoc:
29
+ raise IOError, "closed #{self.class}" if closed?
30
+ end
31
+
32
+ ##
33
+ # Number of bytes read out of the tar entry
34
+
35
+ def bytes_read
36
+ @read
37
+ end
38
+
39
+ ##
40
+ # Closes the tar entry
41
+
42
+ def close
43
+ @closed = true
44
+ end
45
+
46
+ ##
47
+ # Is the tar entry closed?
48
+
49
+ def closed?
50
+ @closed
51
+ end
52
+
53
+ ##
54
+ # Are we at the end of the tar entry?
55
+
56
+ def eof?
57
+ check_closed
58
+
59
+ @read >= @header.size
60
+ end
61
+
62
+ ##
63
+ # Full name of the tar entry
64
+
65
+ def full_name
66
+ if @header.prefix != "" then
67
+ File.join @header.prefix, @header.name
68
+ else
69
+ @header.name
70
+ end
71
+ rescue ArgumentError => e
72
+ raise unless e.message == 'string contains null byte'
73
+ raise InvalidError,
74
+ 'tar is corrupt, name contains null byte'
75
+ end
76
+
77
+ ##
78
+ # Read one byte from the tar entry
79
+
80
+ def getc
81
+ check_closed
82
+
83
+ return nil if @read >= @header.size
84
+
85
+ ret = @io.getc
86
+ @read += 1 if ret
87
+
88
+ ret
89
+ end
90
+
91
+ ##
92
+ # Is this tar entry a directory?
93
+
94
+ def directory?
95
+ @header.typeflag == "5"
96
+ end
97
+
98
+ ##
99
+ # Is this tar entry a file?
100
+
101
+ def file?
102
+ @header.typeflag == "0"
103
+ end
104
+
105
+ ##
106
+ # The position in the tar entry
107
+
108
+ def pos
109
+ check_closed
110
+
111
+ bytes_read
112
+ end
113
+
114
+ ##
115
+ # Reads +len+ bytes from the tar file entry, or the rest of the entry if
116
+ # nil
117
+
118
+ def read(len = nil)
119
+ check_closed
120
+
121
+ return nil if @read >= @header.size
122
+
123
+ len ||= @header.size - @read
124
+ max_read = [len, @header.size - @read].min
125
+
126
+ ret = @io.read max_read
127
+ @read += ret.size
128
+
129
+ ret
130
+ end
131
+
132
+ ##
133
+ # Rewinds to the beginning of the tar file entry
134
+
135
+ def rewind
136
+ check_closed
137
+
138
+ raise NonSeekableIO unless @io.respond_to? :pos=
139
+
140
+ @io.pos = @orig_pos
141
+ @read = 0
142
+ end
143
+
144
+ end
145
+
@@ -0,0 +1,266 @@
1
+ # -*- coding: utf-8 -*-
2
+ #--
3
+ # Copyright (C) 2004 Mauricio Julio Fernández Pradier
4
+ # See LICENSE.txt for additional licensing information.
5
+ #++
6
+
7
+ ##
8
+ #--
9
+ # struct tarfile_entry_posix {
10
+ # char name[100]; # ASCII + (Z unless filled)
11
+ # char mode[8]; # 0 padded, octal, null
12
+ # char uid[8]; # ditto
13
+ # char gid[8]; # ditto
14
+ # char size[12]; # 0 padded, octal, null
15
+ # char mtime[12]; # 0 padded, octal, null
16
+ # char checksum[8]; # 0 padded, octal, null, space
17
+ # char typeflag[1]; # file: "0" dir: "5"
18
+ # char linkname[100]; # ASCII + (Z unless filled)
19
+ # char magic[6]; # "ustar\0"
20
+ # char version[2]; # "00"
21
+ # char uname[32]; # ASCIIZ
22
+ # char gname[32]; # ASCIIZ
23
+ # char devmajor[8]; # 0 padded, octal, null
24
+ # char devminor[8]; # o padded, octal, null
25
+ # char prefix[155]; # ASCII + (Z unless filled)
26
+ # };
27
+ #++
28
+ # A header for a tar file
29
+
30
+ class Gem::Tar::Header
31
+
32
+ ##
33
+ # Fields in the tar header
34
+
35
+ FIELDS = [
36
+ :checksum,
37
+ :devmajor,
38
+ :devminor,
39
+ :gid,
40
+ :gname,
41
+ :linkname,
42
+ :magic,
43
+ :mode,
44
+ :mtime,
45
+ :name,
46
+ :prefix,
47
+ :size,
48
+ :typeflag,
49
+ :uid,
50
+ :uname,
51
+ :version,
52
+ ]
53
+
54
+ ##
55
+ # Pack format for a tar header
56
+
57
+ PACK_FORMAT = 'a100' + # name
58
+ 'a8' + # mode
59
+ 'a8' + # uid
60
+ 'a8' + # gid
61
+ 'a12' + # size
62
+ 'a12' + # mtime
63
+ 'a7a' + # chksum
64
+ 'a' + # typeflag
65
+ 'a100' + # linkname
66
+ 'a6' + # magic
67
+ 'a2' + # version
68
+ 'a32' + # uname
69
+ 'a32' + # gname
70
+ 'a8' + # devmajor
71
+ 'a8' + # devminor
72
+ 'a155' # prefix
73
+
74
+ ##
75
+ # Unpack format for a tar header
76
+
77
+ UNPACK_FORMAT = 'A100' + # name
78
+ 'A8' + # mode
79
+ 'A8' + # uid
80
+ 'A8' + # gid
81
+ 'A12' + # size
82
+ 'A12' + # mtime
83
+ 'A8' + # checksum
84
+ 'A' + # typeflag
85
+ 'A100' + # linkname
86
+ 'A6' + # magic
87
+ 'A2' + # version
88
+ 'A32' + # uname
89
+ 'A32' + # gname
90
+ 'A8' + # devmajor
91
+ 'A8' + # devminor
92
+ 'A155' # prefix
93
+
94
+ attr_reader(*FIELDS)
95
+
96
+ ##
97
+ # Creates a tar header from IO +stream+
98
+
99
+ def self.from(stream)
100
+ header = stream.read 512
101
+ empty = (header == "\0" * 512)
102
+
103
+ fields = header.unpack UNPACK_FORMAT
104
+
105
+ name = fields.shift
106
+ mode = fields.shift.oct
107
+ uid = fields.shift.oct
108
+ gid = fields.shift.oct
109
+ size = fields.shift.oct
110
+ mtime = fields.shift.oct
111
+ checksum = fields.shift.oct
112
+ typeflag = fields.shift
113
+ linkname = fields.shift
114
+ magic = fields.shift
115
+ version = fields.shift.oct
116
+ uname = fields.shift
117
+ gname = fields.shift
118
+ devmajor = fields.shift.oct
119
+ devminor = fields.shift.oct
120
+ prefix = fields.shift
121
+
122
+ new :name => name,
123
+ :mode => mode,
124
+ :uid => uid,
125
+ :gid => gid,
126
+ :size => size,
127
+ :mtime => mtime,
128
+ :checksum => checksum,
129
+ :typeflag => typeflag,
130
+ :linkname => linkname,
131
+ :magic => magic,
132
+ :version => version,
133
+ :uname => uname,
134
+ :gname => gname,
135
+ :devmajor => devmajor,
136
+ :devminor => devminor,
137
+ :prefix => prefix,
138
+
139
+ :empty => empty
140
+
141
+ # HACK unfactor for Rubinius
142
+ #new :name => fields.shift,
143
+ # :mode => fields.shift.oct,
144
+ # :uid => fields.shift.oct,
145
+ # :gid => fields.shift.oct,
146
+ # :size => fields.shift.oct,
147
+ # :mtime => fields.shift.oct,
148
+ # :checksum => fields.shift.oct,
149
+ # :typeflag => fields.shift,
150
+ # :linkname => fields.shift,
151
+ # :magic => fields.shift,
152
+ # :version => fields.shift.oct,
153
+ # :uname => fields.shift,
154
+ # :gname => fields.shift,
155
+ # :devmajor => fields.shift.oct,
156
+ # :devminor => fields.shift.oct,
157
+ # :prefix => fields.shift,
158
+
159
+ # :empty => empty
160
+ end
161
+
162
+ ##
163
+ # Creates a new TarHeader using +vals+
164
+
165
+ def initialize(vals)
166
+ unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] then
167
+ raise ArgumentError, ":name, :size, :prefix and :mode required"
168
+ end
169
+
170
+ vals[:uid] ||= 0
171
+ vals[:gid] ||= 0
172
+ vals[:mtime] ||= 0
173
+ vals[:checksum] ||= ""
174
+ vals[:typeflag] ||= "0"
175
+ vals[:magic] ||= "ustar"
176
+ vals[:version] ||= "00"
177
+ vals[:uname] ||= "wheel"
178
+ vals[:gname] ||= "wheel"
179
+ vals[:devmajor] ||= 0
180
+ vals[:devminor] ||= 0
181
+
182
+ FIELDS.each do |name|
183
+ instance_variable_set "@#{name}", vals[name]
184
+ end
185
+
186
+ @empty = vals[:empty]
187
+ end
188
+
189
+ ##
190
+ # Is the tar entry empty?
191
+
192
+ def empty?
193
+ @empty
194
+ end
195
+
196
+ def ==(other) # :nodoc:
197
+ self.class === other and
198
+ @checksum == other.checksum and
199
+ @devmajor == other.devmajor and
200
+ @devminor == other.devminor and
201
+ @gid == other.gid and
202
+ @gname == other.gname and
203
+ @linkname == other.linkname and
204
+ @magic == other.magic and
205
+ @mode == other.mode and
206
+ @mtime == other.mtime and
207
+ @name == other.name and
208
+ @prefix == other.prefix and
209
+ @size == other.size and
210
+ @typeflag == other.typeflag and
211
+ @uid == other.uid and
212
+ @uname == other.uname and
213
+ @version == other.version
214
+ end
215
+
216
+ def to_s # :nodoc:
217
+ update_checksum
218
+ header
219
+ end
220
+
221
+ ##
222
+ # Updates the TarHeader's checksum
223
+
224
+ def update_checksum
225
+ header = header " " * 8
226
+ @checksum = oct calculate_checksum(header), 6
227
+ end
228
+
229
+ private
230
+
231
+ def calculate_checksum(header)
232
+ header.unpack("C*").inject { |a, b| a + b }
233
+ end
234
+
235
+ def header(checksum = @checksum)
236
+ header = [
237
+ name,
238
+ oct(mode, 7),
239
+ oct(uid, 7),
240
+ oct(gid, 7),
241
+ oct(size, 11),
242
+ oct(mtime, 11),
243
+ checksum,
244
+ " ",
245
+ typeflag,
246
+ linkname,
247
+ magic,
248
+ oct(version, 2),
249
+ uname,
250
+ gname,
251
+ oct(devmajor, 7),
252
+ oct(devminor, 7),
253
+ prefix
254
+ ]
255
+
256
+ header = header.pack PACK_FORMAT
257
+
258
+ header << ("\0" * ((512 - header.size) % 512))
259
+ end
260
+
261
+ def oct(num, len)
262
+ "%0#{len}o" % num
263
+ end
264
+
265
+ end
266
+
@@ -0,0 +1,101 @@
1
+ # -*- coding: utf-8 -*-
2
+ #--
3
+ # Copyright (C) 2004 Mauricio Julio Fernández Pradier
4
+ # See LICENSE.txt for additional licensing information.
5
+ #++
6
+
7
+ ##
8
+ # TarReader reads tar files and allows iteration over their items
9
+
10
+ class Gem::Tar::Reader
11
+
12
+ ##
13
+ # Raised if the tar IO is not seekable
14
+
15
+ class UnexpectedEOF < StandardError; end
16
+
17
+ ##
18
+ # Creates a new TarReader on +io+ and yields it to the block, if given.
19
+
20
+ def self.new(io)
21
+ reader = super
22
+
23
+ return reader unless block_given?
24
+
25
+ begin
26
+ yield reader
27
+ ensure
28
+ reader.close
29
+ end
30
+
31
+ nil
32
+ end
33
+
34
+ ##
35
+ # Creates a new tar file reader on +io+ which needs to respond to #pos,
36
+ # #eof?, #read, #getc and #pos=
37
+
38
+ def initialize(io)
39
+ @io = io
40
+ @init_pos = io.pos
41
+ end
42
+
43
+ ##
44
+ # Close the tar file
45
+
46
+ def close
47
+ end
48
+
49
+ ##
50
+ # Iterates over files in the tarball yielding each entry
51
+
52
+ def each
53
+ loop do
54
+ return if @io.eof?
55
+
56
+ header = Gem::Tar::Header.from @io
57
+ return if header.empty?
58
+
59
+ entry = Gem::Tar::Entry.new header, @io
60
+ size = entry.header.size
61
+
62
+ yield entry
63
+
64
+ skip = (512 - (size % 512)) % 512
65
+ pending = size - entry.bytes_read
66
+
67
+ begin
68
+ # avoid reading...
69
+ @io.seek pending, IO::SEEK_CUR
70
+ pending = 0
71
+ rescue Errno::EINVAL, NameError
72
+ while pending > 0 do
73
+ bytes_read = @io.read([pending, 4096].min).size
74
+ raise UnexpectedEOF if @io.eof?
75
+ pending -= bytes_read
76
+ end
77
+ end
78
+
79
+ @io.read skip # discard trailing zeros
80
+
81
+ # make sure nobody can use #read, #getc or #rewind anymore
82
+ entry.close
83
+ end
84
+ end
85
+
86
+ alias each_entry each
87
+
88
+ ##
89
+ # NOTE: Do not call #rewind during #each
90
+
91
+ def rewind
92
+ if @init_pos == 0 then
93
+ raise NonSeekableIO unless @io.respond_to? :rewind
94
+ @io.rewind
95
+ else
96
+ raise NonSeekableIO unless @io.respond_to? :pos=
97
+ @io.pos = @init_pos
98
+ end
99
+ end
100
+
101
+ end