virt_disk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +1 -0
- data/Rakefile +6 -0
- data/lib/virt_disk.rb +9 -0
- data/lib/virt_disk/block_file.rb +32 -0
- data/lib/virt_disk/client_head.rb +56 -0
- data/lib/virt_disk/disk.rb +21 -0
- data/lib/virt_disk/disk_unicode.rb +43 -0
- data/lib/virt_disk/disk_uuid.rb +22 -0
- data/lib/virt_disk/export_methods.rb +74 -0
- data/lib/virt_disk/file_io.rb +42 -0
- data/lib/virt_disk/partition.rb +39 -0
- data/lib/virt_disk/partition_type.rb +16 -0
- data/lib/virt_disk/partition_type/dos_partition.rb +113 -0
- data/lib/virt_disk/partition_type/gpt_partition.rb +71 -0
- data/lib/virt_disk/version.rb +3 -0
- data/spec/data/dos_partition.img +0 -0
- data/spec/data/gpt_partition.img +0 -0
- data/spec/data/no_partition.img +0 -0
- data/spec/dos_partition_spec.rb +49 -0
- data/spec/export_methods_spec.rb +257 -0
- data/spec/gpt_partition_spec.rb +58 -0
- data/spec/partition_shared_examples.rb +55 -0
- data/spec/partition_type_spec.rb +74 -0
- data/spec/spec_helper.rb +20 -0
- data/tasks/rspec.rake +3 -0
- data/tasks/yard.rake +7 -0
- data/virt_disk.gemspec +31 -0
- metadata +182 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'uuidtools'
|
2
|
+
|
3
|
+
module VirtDisk
|
4
|
+
module PartitionType
|
5
|
+
class GptPartition < Partition
|
6
|
+
GPT_HEADER = BinaryStruct.new([
|
7
|
+
'a8', :signature, # 00-07: Signature "EFI PART"
|
8
|
+
'a4', :version, # 08-11: Revision
|
9
|
+
'L', :header_size, # 12-15: Header size
|
10
|
+
'L', :crc32_header, # 16-19:
|
11
|
+
'L', :reserved, # 20-23:
|
12
|
+
'Q', :cur_lba, # 24-31:
|
13
|
+
'Q', :bak_lba, # 32-39:
|
14
|
+
'Q', :first_lba, # 40-47:
|
15
|
+
'Q', :last_lba, # 48-55:
|
16
|
+
'a16', :guid, # 56-71:
|
17
|
+
'Q', :startLBA, # 72-79:
|
18
|
+
'L', :part_num, # 80-83:
|
19
|
+
'L', :part_size, # 84-87:
|
20
|
+
'L', :part_array, # 88-91:
|
21
|
+
'a420', :reserved2, # 92-511:
|
22
|
+
])
|
23
|
+
|
24
|
+
GPT_PARTITION_ENTRY = BinaryStruct.new([
|
25
|
+
'a16', :ptype, # 00-15: partition type
|
26
|
+
'a16', :pguid, # 16-31: partition GUID
|
27
|
+
'Q', :first_lba, # 32-39: first LBA
|
28
|
+
'Q', :last_lba, # 40-47: last LBA
|
29
|
+
'a8', :attr_flag, # 48-55: attribute flag
|
30
|
+
'a72', :pname, # 56-127: partition name
|
31
|
+
])
|
32
|
+
|
33
|
+
include LogDecorator::Logging
|
34
|
+
include ExportMethods
|
35
|
+
|
36
|
+
def self.discover_partitions(disk) # rubocop:disable AbcSize
|
37
|
+
mbr = disk.mod_read(0, MBR_SIZE)
|
38
|
+
if mbr.length < MBR_SIZE
|
39
|
+
_log.info "<#{disk.object_id}> disk does not contain a master boot record"
|
40
|
+
return []
|
41
|
+
end
|
42
|
+
|
43
|
+
sig = mbr[510..511].unpack('H4')
|
44
|
+
|
45
|
+
pt_entry = DOS_PARTITION_ENTRY.decode(mbr[DOS_PT_START, PTE_LEN])
|
46
|
+
ptype = pt_entry[:ptype]
|
47
|
+
|
48
|
+
return [] if sig[0] != DOS_SIG || ptype != GPT_SIG
|
49
|
+
discover_gpt_partitions(disk)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.discover_gpt_partitions(disk) # rubocop:disable AbcSize
|
53
|
+
_log.info "Parsing GPT disk ..."
|
54
|
+
gpt_header = disk.mod_read(MBR_SIZE, GPT_HEADER.size)
|
55
|
+
header = GPT_HEADER.decode(gpt_header)
|
56
|
+
|
57
|
+
partitions = []
|
58
|
+
pte = GPT_HEADER.size + MBR_SIZE
|
59
|
+
(1..header[:part_num]).each do |n|
|
60
|
+
gpt = disk.mod_read(pte, GPT_PARTITION_ENTRY.size)
|
61
|
+
pt_entry = GPT_PARTITION_ENTRY.decode(gpt)
|
62
|
+
ptype = UUIDTools::UUID.parse_raw(pt_entry[:ptype]).to_s
|
63
|
+
|
64
|
+
partitions.push(new(disk, ptype, n, pt_entry[:first_lba], pt_entry[:last_lba])) if pt_entry[:first_lba] != 0
|
65
|
+
pte += header[:part_size]
|
66
|
+
end
|
67
|
+
partitions
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "partition_shared_examples"
|
3
|
+
|
4
|
+
describe "DOS Partition" do
|
5
|
+
let(:extpected_num_partitions) { 2 }
|
6
|
+
let(:expected_partition_class) { VirtDisk::PartitionType::DosPartition }
|
7
|
+
|
8
|
+
let(:per_partition_values) do
|
9
|
+
[
|
10
|
+
{
|
11
|
+
:ptype => 4,
|
12
|
+
:block_size => 512,
|
13
|
+
:start_lba => 63,
|
14
|
+
:end_lba => 575,
|
15
|
+
:start_byte_addr => 32256,
|
16
|
+
:end_byte_addr => 294400,
|
17
|
+
:size => 262144
|
18
|
+
},
|
19
|
+
{
|
20
|
+
:ptype => 4,
|
21
|
+
:block_size => 512,
|
22
|
+
:start_lba => 575,
|
23
|
+
:end_lba => 2048,
|
24
|
+
:start_byte_addr => 294400,
|
25
|
+
:end_byte_addr => 1048576,
|
26
|
+
:size => 754176
|
27
|
+
}
|
28
|
+
]
|
29
|
+
end
|
30
|
+
|
31
|
+
data_dir = File.join(__dir__, "data")
|
32
|
+
dos_partition_file = File.join(data_dir, "dos_partition.img")
|
33
|
+
file_mod = VirtDisk::FileIo.new(dos_partition_file)
|
34
|
+
disk = VirtDisk::Disk.new(file_mod)
|
35
|
+
|
36
|
+
it "should return an array of the expected length" do
|
37
|
+
expect(VirtDisk::PartitionType.partition_probe(disk).length).to eq(extpected_num_partitions)
|
38
|
+
end
|
39
|
+
|
40
|
+
VirtDisk::PartitionType.partition_probe(disk).each do |part|
|
41
|
+
describe "Partition: #{part.pnum}" do
|
42
|
+
before(:each) do
|
43
|
+
@partition = part
|
44
|
+
end
|
45
|
+
|
46
|
+
it_should_behave_like "common_partition"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "virt_disk"
|
3
|
+
|
4
|
+
def new_test_class(cname, methods_exported = [], methods_not_exported = [])
|
5
|
+
klass = Class.new { include VirtDisk::ExportMethods }
|
6
|
+
stub_const cname, klass
|
7
|
+
add_test_methods(klass, methods_exported, methods_not_exported)
|
8
|
+
klass
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_test_methods(klass, methods_exported, methods_not_exported)
|
12
|
+
klass.class_eval do
|
13
|
+
(methods_exported + methods_not_exported).each do |mn|
|
14
|
+
define_method(mn) { "In #{self.class.name}##{mn}" }
|
15
|
+
end
|
16
|
+
export(*methods_exported)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe VirtDisk::ExportMethods do
|
21
|
+
let(:group_a_methods) do
|
22
|
+
%i( method1 method2 method3 )
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:group_b_methods) do
|
26
|
+
%i( method4 method5 method6 )
|
27
|
+
end
|
28
|
+
|
29
|
+
let(:group_c_methods) do
|
30
|
+
%i( method7 )
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "defined methods" do
|
34
|
+
before(:each) do
|
35
|
+
new_test_class('TestMod1')
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "Class methods" do
|
39
|
+
it "should respond to export" do
|
40
|
+
expect(TestMod1.respond_to?(:export)).to be true
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should respond to exports" do
|
44
|
+
expect(TestMod1.respond_to?(:exports)).to be true
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should respond to exported?" do
|
48
|
+
expect(TestMod1.respond_to?(:exported?)).to be true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "Instance methods" do
|
53
|
+
let(:mod1_obj) { TestMod1.new }
|
54
|
+
|
55
|
+
it "should respond to exported?" do
|
56
|
+
expect(mod1_obj.respond_to?(:exported?)).to be true
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should respond to delegate=" do
|
60
|
+
expect(mod1_obj.respond_to?(:delegate=)).to be true
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should respond to delegate" do
|
64
|
+
expect(mod1_obj.respond_to?(:delegate)).to be true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "Class methods" do
|
70
|
+
describe "export" do
|
71
|
+
before(:each) do
|
72
|
+
new_test_class('TestMod1', group_a_methods, group_c_methods)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should raise an error when method isn't defined" do
|
76
|
+
expect do
|
77
|
+
TestMod1.export(:foo)
|
78
|
+
end.to raise_exception(RuntimeError, "Method not defined in class: foo")
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should return nil on success" do
|
82
|
+
expect(TestMod1.export(group_c_methods.first)).to be_nil
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should add the method to the exports list" do
|
86
|
+
meth = group_c_methods.first
|
87
|
+
expect(TestMod1.exports.include?(meth)).to be false
|
88
|
+
TestMod1.export(meth)
|
89
|
+
expect(TestMod1.exports.include?(meth)).to be true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "exports" do
|
94
|
+
before(:each) do
|
95
|
+
new_test_class('TestMod1', [], group_a_methods)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should return an empty array when nothing exported" do
|
99
|
+
expect(TestMod1.exports).to match_array([])
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should return the expected array" do
|
103
|
+
expect(TestMod1.exports).to match_array([])
|
104
|
+
TestMod1.export(*group_a_methods)
|
105
|
+
expect(TestMod1.exports).to match_array(group_a_methods)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "exported?" do
|
110
|
+
before(:each) do
|
111
|
+
new_test_class('TestMod1', [], group_a_methods)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should return false when nothing exported" do
|
115
|
+
expect(TestMod1.exported?(group_a_methods.first)).to be false
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should return false when method isn't exported" do
|
119
|
+
TestMod1.export(*group_a_methods)
|
120
|
+
expect(TestMod1.exported?(group_b_methods.first)).to be false
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should return true when method is exported" do
|
124
|
+
TestMod1.export(*group_a_methods)
|
125
|
+
expect(TestMod1.exported?(group_a_methods.first)).to be true
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "Instance methods" do
|
131
|
+
let(:no_export_obj) do
|
132
|
+
new_test_class('NoExportMod')
|
133
|
+
NoExportMod.new
|
134
|
+
end
|
135
|
+
|
136
|
+
let(:export_obj) do
|
137
|
+
new_test_class('ExportMod', group_a_methods)
|
138
|
+
ExportMod.new
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "exported?" do
|
142
|
+
it "should return false when nothing exported" do
|
143
|
+
expect(no_export_obj.exported?(group_a_methods.first)).to be false
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should return false when method isn't exported" do
|
147
|
+
expect(export_obj.exported?(group_b_methods.first)).to be false
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should return true when method is exported" do
|
151
|
+
expect(export_obj.exported?(group_a_methods.first)).to be true
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "delegate, delegate=" do
|
156
|
+
it "should return nil when no delegate" do
|
157
|
+
expect(no_export_obj.delegate).to be nil
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should return the object it is passed" do
|
161
|
+
expect(no_export_obj.delegate = export_obj).to eq(export_obj)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should set the delegate accordingly" do
|
165
|
+
no_export_obj.delegate = export_obj
|
166
|
+
expect(no_export_obj.delegate).to eq(export_obj)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe "Operation" do
|
172
|
+
let(:obja) do
|
173
|
+
new_test_class('ModA', group_a_methods)
|
174
|
+
ModA.new
|
175
|
+
end
|
176
|
+
|
177
|
+
let(:objb) do
|
178
|
+
new_test_class('ModB', group_b_methods)
|
179
|
+
ModB.new
|
180
|
+
end
|
181
|
+
|
182
|
+
let(:objc) do
|
183
|
+
new_test_class('ModC', group_c_methods)
|
184
|
+
ModC.new
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should 'respond_to?' top-level methods" do
|
188
|
+
expect(obja.respond_to?(group_a_methods.first)).to eq true
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should call top-level methods directly" do
|
192
|
+
method_name = group_a_methods.first
|
193
|
+
expect(obja.send(method_name)).to eq("In #{obja.class.name}##{method_name}")
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should 'respond_to?' 2nd-level methods" do
|
197
|
+
expect(obja.respond_to?(group_a_methods.first)).to eq true
|
198
|
+
expect(obja.respond_to?(group_b_methods.first)).to eq false
|
199
|
+
|
200
|
+
obja.delegate = objb
|
201
|
+
expect(obja.respond_to?(group_b_methods.first)).to eq true
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should call 2nd-level methods" do
|
205
|
+
method_a_name = group_a_methods.first
|
206
|
+
method_b_name = group_b_methods.first
|
207
|
+
|
208
|
+
expect(obja.send(method_a_name)).to eq("In #{obja.class.name}##{method_a_name}")
|
209
|
+
|
210
|
+
expect do
|
211
|
+
obja.send(method_b_name)
|
212
|
+
end.to raise_exception(NoMethodError, /undefined method `#{method_b_name}' for.*/)
|
213
|
+
|
214
|
+
obja.delegate = objb
|
215
|
+
expect(obja.send(method_b_name)).to eq("In #{objb.class.name}##{method_b_name}")
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should 'respond_to?' 3rd-level methods" do
|
219
|
+
expect(obja.respond_to?(group_a_methods.first)).to eq true
|
220
|
+
expect(obja.respond_to?(group_b_methods.first)).to eq false
|
221
|
+
expect(obja.respond_to?(group_c_methods.first)).to eq false
|
222
|
+
|
223
|
+
obja.delegate = objb
|
224
|
+
expect(obja.respond_to?(group_b_methods.first)).to eq true
|
225
|
+
expect(obja.respond_to?(group_c_methods.first)).to eq false
|
226
|
+
|
227
|
+
objb.delegate = objc
|
228
|
+
expect(obja.respond_to?(group_c_methods.first)).to eq true
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should call 3rd-level methods" do
|
232
|
+
method_a_name = group_a_methods.first
|
233
|
+
method_b_name = group_b_methods.first
|
234
|
+
method_c_name = group_c_methods.first
|
235
|
+
|
236
|
+
expect(obja.send(method_a_name)).to eq("In #{obja.class.name}##{method_a_name}")
|
237
|
+
|
238
|
+
expect do
|
239
|
+
obja.send(method_b_name)
|
240
|
+
end.to raise_exception(NoMethodError, /undefined method `#{method_b_name}' for.*/)
|
241
|
+
|
242
|
+
expect do
|
243
|
+
obja.send(method_c_name)
|
244
|
+
end.to raise_exception(NoMethodError, /undefined method `#{method_c_name}' for.*/)
|
245
|
+
|
246
|
+
obja.delegate = objb
|
247
|
+
expect(obja.send(method_b_name)).to eq("In #{objb.class.name}##{method_b_name}")
|
248
|
+
|
249
|
+
expect do
|
250
|
+
obja.send(method_c_name)
|
251
|
+
end.to raise_exception(NoMethodError, /undefined method `#{method_c_name}' for.*/)
|
252
|
+
|
253
|
+
objb.delegate = objc
|
254
|
+
expect(obja.send(method_c_name)).to eq("In #{objc.class.name}##{method_c_name}")
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "partition_shared_examples"
|
3
|
+
|
4
|
+
describe "GPT Partition" do
|
5
|
+
let(:extpected_num_partitions) { 3 }
|
6
|
+
let(:expected_partition_class) { VirtDisk::PartitionType::GptPartition }
|
7
|
+
|
8
|
+
let(:per_partition_values) do
|
9
|
+
[
|
10
|
+
{
|
11
|
+
:ptype => "af3dc60f-8384-7247-8e79-3d69d8477de4",
|
12
|
+
:block_size => 512,
|
13
|
+
:start_lba => 34,
|
14
|
+
:end_lba => 512,
|
15
|
+
:start_byte_addr => 17408,
|
16
|
+
:end_byte_addr => 262144,
|
17
|
+
:size => 244736
|
18
|
+
},
|
19
|
+
{
|
20
|
+
:ptype => "af3dc60f-8384-7247-8e79-3d69d8477de4",
|
21
|
+
:block_size => 512,
|
22
|
+
:start_lba => 513,
|
23
|
+
:end_lba => 1024,
|
24
|
+
:start_byte_addr => 262656,
|
25
|
+
:end_byte_addr => 524288,
|
26
|
+
:size => 261632
|
27
|
+
},
|
28
|
+
{
|
29
|
+
:ptype => "af3dc60f-8384-7247-8e79-3d69d8477de4",
|
30
|
+
:block_size => 512,
|
31
|
+
:start_lba => 1025,
|
32
|
+
:end_lba => 2014,
|
33
|
+
:start_byte_addr => 524800,
|
34
|
+
:end_byte_addr => 1031168,
|
35
|
+
:size => 506368
|
36
|
+
}
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
data_dir = File.join(__dir__, "data")
|
41
|
+
dos_partition_file = File.join(data_dir, "gpt_partition.img")
|
42
|
+
file_mod = VirtDisk::FileIo.new(dos_partition_file)
|
43
|
+
disk = VirtDisk::Disk.new(file_mod)
|
44
|
+
|
45
|
+
it "should return an array of the expected length" do
|
46
|
+
expect(VirtDisk::PartitionType.partition_probe(disk).length).to eq(extpected_num_partitions)
|
47
|
+
end
|
48
|
+
|
49
|
+
VirtDisk::PartitionType.partition_probe(disk).each do |part|
|
50
|
+
describe "Partition: #{part.pnum}" do
|
51
|
+
before(:each) do
|
52
|
+
@partition = part
|
53
|
+
end
|
54
|
+
|
55
|
+
it_should_behave_like "common_partition"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|