excavate 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/release.yml +36 -0
- data/.github/workflows/rspec.yml +47 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/Gemfile +14 -0
- data/LICENSE.adoc +13 -0
- data/README.adoc +102 -0
- data/Rakefile +4 -0
- data/excavate.gemspec +34 -0
- data/lib/excavate.rb +12 -0
- data/lib/excavate/archive.rb +96 -0
- data/lib/excavate/extractors.rb +9 -0
- data/lib/excavate/extractors/cab_extractor.rb +33 -0
- data/lib/excavate/extractors/cpio/cpio.rb +199 -0
- data/lib/excavate/extractors/cpio/cpio_old_format.rb +436 -0
- data/lib/excavate/extractors/cpio_extractor.rb +44 -0
- data/lib/excavate/extractors/extractor.rb +13 -0
- data/lib/excavate/extractors/gzip_extractor.rb +13 -0
- data/lib/excavate/extractors/ole_extractor.rb +66 -0
- data/lib/excavate/extractors/rpm_extractor.rb +33 -0
- data/lib/excavate/extractors/seven_zip_extractor.rb +13 -0
- data/lib/excavate/extractors/tar_extractor.rb +29 -0
- data/lib/excavate/extractors/zip_extractor.rb +17 -0
- data/lib/excavate/file_magic.rb +18 -0
- data/lib/excavate/utils.rb +14 -0
- data/lib/excavate/version.rb +5 -0
- metadata +143 -0
@@ -0,0 +1,199 @@
|
|
1
|
+
# code is obtained from https://github.com/jordansissel/ruby-arr-pm/blob/8071591173ebb90dea27d5acfdde69a37dcb2744/cpio.rb
|
2
|
+
# rubocop:disable all
|
3
|
+
class BoundedIO
|
4
|
+
attr_reader :length
|
5
|
+
attr_reader :remaining
|
6
|
+
|
7
|
+
def initialize(io, length, &eof_callback)
|
8
|
+
@io = io
|
9
|
+
@length = length
|
10
|
+
@remaining = length
|
11
|
+
|
12
|
+
@eof_callback = eof_callback
|
13
|
+
@eof = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def read(size=nil)
|
17
|
+
return nil if eof?
|
18
|
+
size = @remaining if size.nil?
|
19
|
+
data = @io.read(size)
|
20
|
+
@remaining -= data.bytesize
|
21
|
+
eof?
|
22
|
+
data
|
23
|
+
end
|
24
|
+
|
25
|
+
def sysread(size)
|
26
|
+
raise EOFError, "end of file reached" if eof?
|
27
|
+
read(size)
|
28
|
+
end
|
29
|
+
|
30
|
+
def eof?
|
31
|
+
return false if @remaining > 0
|
32
|
+
return @eof if @eof
|
33
|
+
|
34
|
+
@eof_callback.call
|
35
|
+
@eof = true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module CPIO
|
40
|
+
FIELDS = [
|
41
|
+
:magic, :ino, :mode, :uid, :gid, :nlink, :mtime, :filesize, :devmajor,
|
42
|
+
:devminor, :rdevmajor, :rdevminor, :namesize, :check
|
43
|
+
]
|
44
|
+
end
|
45
|
+
|
46
|
+
class CPIO::ASCIIReader
|
47
|
+
FIELD_SIZES = {
|
48
|
+
:magic => 6,
|
49
|
+
:ino => 8,
|
50
|
+
:mode => 8,
|
51
|
+
:uid => 8,
|
52
|
+
:gid => 8,
|
53
|
+
:nlink => 8,
|
54
|
+
:mtime => 8,
|
55
|
+
:filesize => 8,
|
56
|
+
:devmajor => 8,
|
57
|
+
:devminor => 8,
|
58
|
+
:rdevmajor => 8,
|
59
|
+
:rdevminor => 8,
|
60
|
+
:namesize => 8,
|
61
|
+
:check => 8
|
62
|
+
}
|
63
|
+
HEADER_LENGTH = FIELD_SIZES.reduce(0) { |m, (_, v)| m + v }
|
64
|
+
HEADER_PACK = FIELD_SIZES.collect { |_, v| "A#{v}" }.join
|
65
|
+
|
66
|
+
FIELD_ORDER = [
|
67
|
+
:magic, :ino, :mode, :uid, :gid, :nlink, :mtime, :filesize, :devmajor,
|
68
|
+
:devminor, :rdevmajor, :rdevminor, :namesize, :check
|
69
|
+
]
|
70
|
+
|
71
|
+
def initialize(io)
|
72
|
+
@io = io
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def io
|
78
|
+
@io
|
79
|
+
end
|
80
|
+
|
81
|
+
def each(&block)
|
82
|
+
while true
|
83
|
+
entry = read
|
84
|
+
break if entry.nil?
|
85
|
+
# The CPIO format has the end-of-stream marker as a file called "TRAILER!!!"
|
86
|
+
break if entry.name == "TRAILER!!!"
|
87
|
+
block.call(entry, entry.file)
|
88
|
+
verify_correct_read(entry) unless entry.directory?
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def verify_correct_read(entry)
|
93
|
+
# Read and throw away the whole file if not read at all.
|
94
|
+
entry.file.tap do |file|
|
95
|
+
if file.nil? || file.remaining == 0
|
96
|
+
# All OK! :)
|
97
|
+
elsif file.remaining == file.length
|
98
|
+
file.read(16384) while !file.eof?
|
99
|
+
else
|
100
|
+
# The file was only partially read? This should be an error by the
|
101
|
+
# user.
|
102
|
+
consumed = file.length - file.remaining
|
103
|
+
raise BadState, "Only #{consumed} bytes were read of the #{file.length} byte file: #{entry.name}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def read
|
109
|
+
entry = CPIOEntry.new
|
110
|
+
header = io.read(HEADER_LENGTH)
|
111
|
+
return nil if header.nil?
|
112
|
+
FIELD_ORDER.zip(header.unpack(HEADER_PACK)).each do |field, value|
|
113
|
+
entry.send("#{field}=", value.to_i(16))
|
114
|
+
end
|
115
|
+
|
116
|
+
entry.validate
|
117
|
+
entry.mtime = Time.at(entry.mtime)
|
118
|
+
read_name(entry, @io)
|
119
|
+
read_file(entry, @io)
|
120
|
+
entry
|
121
|
+
end
|
122
|
+
|
123
|
+
def read_name(entry, io)
|
124
|
+
entry.name = io.read(entry.namesize - 1) # - 1 for null terminator
|
125
|
+
nul = io.read(1)
|
126
|
+
raise ArgumentError, "Corrupt CPIO or bug? Name null terminator was not null: #{nul.inspect}" if nul != "\0"
|
127
|
+
padding_data = io.read(padding_name(entry))
|
128
|
+
# Padding should be all null bytes
|
129
|
+
if padding_data != ("\0" * padding_data.bytesize)
|
130
|
+
raise ArgumentError, "Corrupt CPIO or bug? Name null padding was #{padding_name(entry)} bytes: #{padding_data.inspect}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def read_file(entry, io)
|
135
|
+
if entry.directory?
|
136
|
+
entry.file = nil
|
137
|
+
#read_file_padding(entry, io)
|
138
|
+
nil
|
139
|
+
else
|
140
|
+
entry.file = BoundedIO.new(io, entry.filesize) do
|
141
|
+
read_file_padding(entry, io)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def read_file_padding(entry, io)
|
147
|
+
padding_data = io.read(padding_file(entry))
|
148
|
+
if padding_data != ("\0" * padding_data.bytesize)
|
149
|
+
raise ArgumentError, "Corrupt CPIO or bug? File null padding was #{padding_file(entry)} bytes: #{padding_data.inspect}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def padding_name(entry)
|
154
|
+
# name padding is padding up to a multiple of 4 after header+namesize
|
155
|
+
-(HEADER_LENGTH + entry.namesize) % 4
|
156
|
+
end
|
157
|
+
|
158
|
+
def padding_file(entry)
|
159
|
+
(-(HEADER_LENGTH + entry.filesize + 2) % 4)
|
160
|
+
end
|
161
|
+
public(:each)
|
162
|
+
end
|
163
|
+
|
164
|
+
class CPIOEntry
|
165
|
+
CPIO::FIELDS.each do |field|
|
166
|
+
attr_accessor field
|
167
|
+
end
|
168
|
+
|
169
|
+
attr_accessor :name
|
170
|
+
attr_accessor :file
|
171
|
+
|
172
|
+
DIRECTORY_FLAG = 0040000
|
173
|
+
|
174
|
+
def validate
|
175
|
+
raise "Invalid magic #{magic.inspect}" if magic != 0x070701
|
176
|
+
raise "Invalid ino #{ino.inspect}" if ino < 0
|
177
|
+
raise "Invalid mode #{mode.inspect}" if mode < 0
|
178
|
+
raise "Invalid uid #{uid.inspect}" if uid < 0
|
179
|
+
raise "Invalid gid #{gid.inspect}" if gid < 0
|
180
|
+
raise "Invalid nlink #{nlink.inspect}" if nlink < 0
|
181
|
+
raise "Invalid mtime #{mtime.inspect}" if mtime < 0
|
182
|
+
raise "Invalid filesize #{filesize.inspect}" if filesize < 0
|
183
|
+
raise "Invalid devmajor #{devmajor.inspect}" if devmajor < 0
|
184
|
+
raise "Invalid devminor #{devminor.inspect}" if devminor < 0
|
185
|
+
raise "Invalid rdevmajor #{rdevmajor.inspect}" if rdevmajor < 0
|
186
|
+
raise "Invalid rdevminor #{rdevminor.inspect}" if rdevminor < 0
|
187
|
+
raise "Invalid namesize #{namesize.inspect}" if namesize < 0
|
188
|
+
raise "Invalid check #{check.inspect}" if check < 0
|
189
|
+
end # def validate
|
190
|
+
|
191
|
+
def read(*args)
|
192
|
+
return nil if directory?
|
193
|
+
file.read(*args)
|
194
|
+
end
|
195
|
+
|
196
|
+
def directory?
|
197
|
+
mode & DIRECTORY_FLAG > 0
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,436 @@
|
|
1
|
+
# code is obtained from https://github.com/thumblemonks/cpio/blob/bad40c293280bb3c1678251c66f0f1f6fb1cc03e/cpio.rb
|
2
|
+
# rubocop:disable all
|
3
|
+
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
module CPIO
|
7
|
+
class ArchiveFormatError < IOError; end
|
8
|
+
|
9
|
+
class ArchiveHeader
|
10
|
+
Magic = '070707'
|
11
|
+
Fields = [[6, :magic ],
|
12
|
+
[6, :dev ],
|
13
|
+
[6, :inode ],
|
14
|
+
[6, :mode ],
|
15
|
+
[6, :uid ],
|
16
|
+
[6, :gid ],
|
17
|
+
[6, :numlinks],
|
18
|
+
[6, :rdev ],
|
19
|
+
[11, :mtime ],
|
20
|
+
[6, :namesize],
|
21
|
+
[11, :filesize]]
|
22
|
+
|
23
|
+
FieldDefaults = {:magic => Integer(Magic),
|
24
|
+
:dev => 0777777,
|
25
|
+
:inode => 0,
|
26
|
+
:mode => 0100444,
|
27
|
+
:uid => 0,
|
28
|
+
:gid => 0,
|
29
|
+
:numlinks => 1,
|
30
|
+
:rdev => 0,
|
31
|
+
:mtime => lambda { Time.now.to_i }}
|
32
|
+
|
33
|
+
FieldMaxValues = Fields.inject({}) do |map,(width,name)|
|
34
|
+
map[name] = Integer("0#{'7' * width}")
|
35
|
+
map
|
36
|
+
end
|
37
|
+
|
38
|
+
HeaderSize = Fields.inject(0) do |sum,(size,name)|
|
39
|
+
sum + size
|
40
|
+
end
|
41
|
+
|
42
|
+
HeaderUnpackFormat = Fields.collect do |size,name|
|
43
|
+
"a%s" % size
|
44
|
+
end.join('')
|
45
|
+
|
46
|
+
Fields.each do |(size,name)|
|
47
|
+
define_method(name) { @attrs[name.to_sym] }
|
48
|
+
end
|
49
|
+
|
50
|
+
class << self
|
51
|
+
private :new
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(attrs)
|
55
|
+
@attrs = attrs
|
56
|
+
check_attrs
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.from(io)
|
60
|
+
data = io.read(HeaderSize)
|
61
|
+
verify_size(data)
|
62
|
+
verify_magic(data)
|
63
|
+
new(unpack_data(data))
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.with_defaults(opts)
|
67
|
+
name = opts[:name]
|
68
|
+
defaults = FieldDefaults.merge(:mode => opts[:mode], :filesize => opts[:filesize], :namesize => name.size + 1)
|
69
|
+
new(defaults)
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_data
|
73
|
+
Fields.collect do |(width,name)|
|
74
|
+
raise ArchiveFormatError, "Expected header to have key #{name}" unless @attrs.has_key?(name)
|
75
|
+
val = @attrs[name].respond_to?(:to_proc) ? @attrs[name].call : @attrs[name]
|
76
|
+
raise ArchiveFormatError, "Header value for #{name} exceeds max length of #{FieldMaxValues[name]}" if val > FieldMaxValues[name]
|
77
|
+
sprintf("%0*o", Fields.rassoc(name).first, val)
|
78
|
+
end.join('')
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def check_attrs
|
84
|
+
[:mode, :namesize, :filesize].each do |attr|
|
85
|
+
raise ArgumentError, "#{attr.inspect} must be given" if !@attrs.has_key?(attr)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.verify_size(data)
|
90
|
+
unless data.size == HeaderSize
|
91
|
+
raise ArchiveFormatError, "Header is not long enough to be a valid CPIO archive with ASCII headers."
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.verify_magic(data)
|
96
|
+
unless data[0..Magic.size - 1] == Magic
|
97
|
+
raise ArchiveFormatError, "Archive does not seem to be a valid CPIO archive with ASCII headers."
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.unpack_data(data)
|
102
|
+
contents = {}
|
103
|
+
data.unpack(HeaderUnpackFormat).zip(Fields) do |(chunk,(size,name))|
|
104
|
+
contents[name] = Integer("0#{chunk}")
|
105
|
+
end
|
106
|
+
contents
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
class ArchiveEntry
|
112
|
+
TrailerMagic = "TRAILER!!!"
|
113
|
+
S_IFMT = 0170000 # bitmask for the file type bitfields
|
114
|
+
S_IFREG = 0100000 # regular file
|
115
|
+
S_IFDIR = 0040000 # directory
|
116
|
+
|
117
|
+
ExecutableMask = (0100 | # Owner executable
|
118
|
+
0010 | # Group executable
|
119
|
+
0001) # Other executable
|
120
|
+
|
121
|
+
attr_reader :filename, :data
|
122
|
+
|
123
|
+
class << self
|
124
|
+
private :new
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.from(io)
|
128
|
+
header = ArchiveHeader.from(io)
|
129
|
+
filename = read_filename(header, io)
|
130
|
+
data = read_data(header, io)
|
131
|
+
new(header, filename, data)
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.new_directory(opts)
|
135
|
+
mode = S_IFDIR | opts[:mode]
|
136
|
+
header = ArchiveHeader.with_defaults(:mode => mode, :name => opts[:name], :filesize => 0)
|
137
|
+
new(header, opts[:name], '')
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.new_file(opts)
|
141
|
+
mode = S_IFREG | opts[:mode]
|
142
|
+
header = ArchiveHeader.with_defaults(:mode => mode, :name => opts[:name], :filesize => opts[:io].size)
|
143
|
+
opts[:io].rewind
|
144
|
+
new(header, opts[:name], opts[:io].read)
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.new_trailer
|
148
|
+
header = ArchiveHeader.with_defaults(:mode => S_IFREG, :name => TrailerMagic, :filesize => 0)
|
149
|
+
new(header, TrailerMagic, '')
|
150
|
+
end
|
151
|
+
|
152
|
+
def initialize(header, filename, data)
|
153
|
+
@header = header
|
154
|
+
@filename = filename
|
155
|
+
@data = data
|
156
|
+
end
|
157
|
+
|
158
|
+
def trailer?
|
159
|
+
@filename == TrailerMagic && @data.size == 0
|
160
|
+
end
|
161
|
+
|
162
|
+
def directory?
|
163
|
+
mode & S_IFMT == S_IFDIR
|
164
|
+
end
|
165
|
+
|
166
|
+
def file?
|
167
|
+
mode & S_IFMT == S_IFREG
|
168
|
+
end
|
169
|
+
|
170
|
+
def executable?
|
171
|
+
(mode & ExecutableMask) != 0
|
172
|
+
end
|
173
|
+
|
174
|
+
def mode
|
175
|
+
@mode ||= sprintf('%o', @header.mode).to_s.oct
|
176
|
+
end
|
177
|
+
|
178
|
+
def to_data
|
179
|
+
sprintf("%s%s\000%s", @header.to_data, filename, data)
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
def self.read_filename(header, io)
|
185
|
+
fname = io.read(header.namesize)
|
186
|
+
if fname.size != header.namesize
|
187
|
+
raise ArchiveFormatError, "Archive header seems to innacurately contain length of filename"
|
188
|
+
end
|
189
|
+
fname.chomp("\000")
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.read_data(header, io)
|
193
|
+
data = io.read(header.filesize)
|
194
|
+
if data.size != header.filesize
|
195
|
+
raise ArchiveFormatError, "Archive header seems to inaccurately contain length of the entry"
|
196
|
+
end
|
197
|
+
data
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
class ArchiveWriter
|
203
|
+
class ArchiveFinalizedError < RuntimeError; end
|
204
|
+
|
205
|
+
def initialize(io)
|
206
|
+
@io = io
|
207
|
+
@open = false
|
208
|
+
end
|
209
|
+
|
210
|
+
def open?
|
211
|
+
@open
|
212
|
+
end
|
213
|
+
|
214
|
+
def open
|
215
|
+
raise ArchiveFinalizedError, "This archive has already been finalized" if @finalized
|
216
|
+
@open = true
|
217
|
+
yield(self)
|
218
|
+
ensure
|
219
|
+
close
|
220
|
+
finalize
|
221
|
+
end
|
222
|
+
|
223
|
+
def mkdir(name, mode = 0555)
|
224
|
+
entry = ArchiveEntry.new_directory(:name => name, :mode => mode)
|
225
|
+
@io.write(entry.to_data)
|
226
|
+
end
|
227
|
+
|
228
|
+
def add_file(name, mode = 0444)
|
229
|
+
file = StringIO.new
|
230
|
+
yield(file)
|
231
|
+
entry = ArchiveEntry.new_file(:name => name, :mode => mode, :io => file)
|
232
|
+
@io.write(entry.to_data)
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
|
237
|
+
def add_entry(opts)
|
238
|
+
end
|
239
|
+
|
240
|
+
def write_trailer
|
241
|
+
entry = ArchiveEntry.new_trailer
|
242
|
+
@io.write(entry.to_data)
|
243
|
+
end
|
244
|
+
|
245
|
+
def finalize
|
246
|
+
write_trailer
|
247
|
+
@finalized = true
|
248
|
+
end
|
249
|
+
|
250
|
+
def check_open
|
251
|
+
raise "#{self.class.name} not open for writing" unless open?
|
252
|
+
end
|
253
|
+
|
254
|
+
def close
|
255
|
+
@open = false
|
256
|
+
end
|
257
|
+
|
258
|
+
end # ArchiveWriter
|
259
|
+
|
260
|
+
class ArchiveReader
|
261
|
+
|
262
|
+
def initialize(io)
|
263
|
+
@io = io
|
264
|
+
end
|
265
|
+
|
266
|
+
def each_entry
|
267
|
+
@io.rewind
|
268
|
+
while (entry = ArchiveEntry.from(@io)) && !entry.trailer?
|
269
|
+
yield(entry)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
end # ArchiveReader
|
274
|
+
|
275
|
+
end # CPIO
|
276
|
+
|
277
|
+
if $PROGRAM_NAME == __FILE__
|
278
|
+
require 'stringio'
|
279
|
+
require 'test/unit'
|
280
|
+
require 'digest/sha1'
|
281
|
+
|
282
|
+
class CPIOArchiveReaderTest < Test::Unit::TestCase
|
283
|
+
CPIOFixture = StringIO.new(DATA.read)
|
284
|
+
# These are SHA1 hashes
|
285
|
+
ExpectedFixtureHashes = { 'cpio_test/test_executable' => '97bd38305a81f2d89b5f3aa44500ec964b87cf8a',
|
286
|
+
'cpio_test/test_dir/test_file' => 'e7f1aa55a7f83dc99c9978b91072d01a3f5c812e' }
|
287
|
+
|
288
|
+
def test_given_a_archive_with_a_bad_magic_number_should_raise
|
289
|
+
assert_raises(CPIO::ArchiveFormatError) do
|
290
|
+
CPIO::ArchiveReader.new(StringIO.new('foo')).each_entry { }
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def test_given_a_archive_with_a_valid_magic_number_should_not_raise
|
295
|
+
archive = CPIO::ArchiveReader.new(CPIOFixture)
|
296
|
+
assert_nil archive.each_entry { }
|
297
|
+
end
|
298
|
+
|
299
|
+
def test_given_a_valid_archive_should_have_the_expected_number_of_entries
|
300
|
+
archive = CPIO::ArchiveReader.new(CPIOFixture)
|
301
|
+
entries = 4
|
302
|
+
archive.each_entry { |ent| entries -= 1 }
|
303
|
+
assert_equal 0, entries, "Expected #{entries} in the archive."
|
304
|
+
end
|
305
|
+
|
306
|
+
def test_given_a_valid_archive_should_have_the_expected_entry_filenames
|
307
|
+
expected = %w[cpio_test cpio_test/test_dir cpio_test/test_dir/test_file cpio_test/test_executable]
|
308
|
+
archive = CPIO::ArchiveReader.new(CPIOFixture)
|
309
|
+
archive.each_entry { |ent| expected.delete(ent.filename) }
|
310
|
+
assert_equal 0, expected.size, "The expected array should be empty but we still have: #{expected.inspect}"
|
311
|
+
end
|
312
|
+
|
313
|
+
def test_given_a_valid_archive_should_have_the_expected_number_of_directories
|
314
|
+
expected = 2
|
315
|
+
archive = CPIO::ArchiveReader.new(CPIOFixture)
|
316
|
+
archive.each_entry { |ent| expected -= 1 if ent.directory? }
|
317
|
+
assert_equal 0, expected
|
318
|
+
end
|
319
|
+
|
320
|
+
def test_given_a_valid_archive_should_have_the_expected_number_of_regular_files
|
321
|
+
expected = 1
|
322
|
+
archive = CPIO::ArchiveReader.new(CPIOFixture)
|
323
|
+
archive.each_entry { |ent| expected -= 1 if ent.file? && !ent.executable? }
|
324
|
+
assert_equal 0, expected
|
325
|
+
end
|
326
|
+
|
327
|
+
def test_given_a_valid_archive_should_have_the_expected_number_of_executable_files
|
328
|
+
expected = 1
|
329
|
+
archive = CPIO::ArchiveReader.new(CPIOFixture)
|
330
|
+
archive.each_entry { |ent| expected -= 1 if ent.file? && ent.executable? }
|
331
|
+
assert_equal 0, expected
|
332
|
+
end
|
333
|
+
|
334
|
+
def test_given_a_valid_archive_should_have_correct_file_contents
|
335
|
+
expected = ExpectedFixtureHashes.size
|
336
|
+
archive = CPIO::ArchiveReader.new(CPIOFixture)
|
337
|
+
archive.each_entry do |ent|
|
338
|
+
if (sha1_hash = ExpectedFixtureHashes[ent.filename]) && Digest::SHA1.hexdigest(ent.data) == sha1_hash
|
339
|
+
expected -= 1
|
340
|
+
end
|
341
|
+
end
|
342
|
+
assert_equal 0, expected, "Expected all files in the archive to hash correctly."
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
|
347
|
+
class CPIOArchiveWriterTest < Test::Unit::TestCase
|
348
|
+
|
349
|
+
def test_making_directories_should_work
|
350
|
+
expected = 2
|
351
|
+
io = StringIO.new
|
352
|
+
archiver = CPIO::ArchiveWriter.new(io)
|
353
|
+
archiver.open do |arch|
|
354
|
+
arch.mkdir "foo"
|
355
|
+
arch.mkdir "bar"
|
356
|
+
end
|
357
|
+
CPIO::ArchiveReader.new(io).each_entry { |ent| expected -= 1 if ent.directory? }
|
358
|
+
assert_equal 0, expected
|
359
|
+
end
|
360
|
+
|
361
|
+
def test_making_files_should_work
|
362
|
+
expected = 2
|
363
|
+
io = StringIO.new
|
364
|
+
archiver = CPIO::ArchiveWriter.new(io)
|
365
|
+
archiver.open do |arch|
|
366
|
+
arch.add_file("foo") { |sio| sio.write("foobar") }
|
367
|
+
arch.add_file("barfoo") { |sio| sio.write("barfoo") }
|
368
|
+
end
|
369
|
+
CPIO::ArchiveReader.new(io).each_entry { |ent| expected -= 1 if ent.file? }
|
370
|
+
assert_equal 0, expected
|
371
|
+
end
|
372
|
+
|
373
|
+
def test_making_files_and_directories_should_work
|
374
|
+
expected = 4
|
375
|
+
io = StringIO.new
|
376
|
+
archiver = CPIO::ArchiveWriter.new(io)
|
377
|
+
archiver.open do |arch|
|
378
|
+
arch.mkdir "blah"
|
379
|
+
arch.add_file("foo") { |sio| sio.write("foobar") }
|
380
|
+
arch.add_file("barfoo") { |sio| sio.write("barfoo") }
|
381
|
+
arch.add_file("barfoobaz", 0111) { |sio| sio.write("wee") }
|
382
|
+
end
|
383
|
+
CPIO::ArchiveReader.new(io).each_entry { |ent| expected -= 1 }
|
384
|
+
assert_equal 0, expected
|
385
|
+
end
|
386
|
+
|
387
|
+
def test_adding_empty_files_should_work
|
388
|
+
expected = 1
|
389
|
+
io = StringIO.new
|
390
|
+
archiver = CPIO::ArchiveWriter.new(io)
|
391
|
+
archiver.open do |arch|
|
392
|
+
arch.add_file("barfoo", 0111) { |sio| }
|
393
|
+
end
|
394
|
+
CPIO::ArchiveReader.new(io).each_entry { |ent| expected -= 1 if ent.file? }
|
395
|
+
assert_equal 0, expected
|
396
|
+
end
|
397
|
+
|
398
|
+
def test_adding_a_file_with_an_excessively_long_name_should_raise
|
399
|
+
archiver = CPIO::ArchiveWriter.new(StringIO.new)
|
400
|
+
assert_raise(CPIO::ArchiveFormatError) do
|
401
|
+
archiver.open do |arch|
|
402
|
+
name = "fffff" * (CPIO::ArchiveHeader::FieldMaxValues[:namesize])
|
403
|
+
arch.add_file(name) { |sio| }
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def test_adding_a_non_executable_file_should_preserve_said_mode
|
409
|
+
io = StringIO.new
|
410
|
+
archiver = CPIO::ArchiveWriter.new(io)
|
411
|
+
archiver.open do |arch|
|
412
|
+
arch.add_file("barfoo", 0444) { |sio| }
|
413
|
+
end
|
414
|
+
CPIO::ArchiveReader.new(io).each_entry do |ent|
|
415
|
+
assert !ent.executable? && ent.file?
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def test_adding_an_executable_file_should_preserve_said_mode
|
420
|
+
io = StringIO.new
|
421
|
+
archiver = CPIO::ArchiveWriter.new(io)
|
422
|
+
archiver.open do |arch|
|
423
|
+
arch.add_file("barfoo", 0500) { |sio| }
|
424
|
+
end
|
425
|
+
CPIO::ArchiveReader.new(io).each_entry do |ent|
|
426
|
+
assert ent.executable? && ent.file?
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
end
|
432
|
+
|
433
|
+
__END__
|
434
|
+
0707077777770465470407550007650000240000040000001130242405100001200000000000cpio_test0707077777770465520407550007650000240000030000001130242404300002300000000000cpio_test/test_dir0707077777770465531006440007650000240000010000001130242637200003500000000016cpio_test/test_dir/test_filefoobarbazbeep
|
435
|
+
0707077777770465541007550007650000240000010000001130242636000003200000000012cpio_test/test_executablefoobarbaz
|
436
|
+
0707070000000000000000000000000000000000010000000000000000000001300000000000TRAILER!!!
|