virt_disk 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 78ec73c22b2c23d9ba2b2e92c673e81017de439a
4
+ data.tar.gz: 025ee06aa5d102e2f0bcc1c2b27ea419fd4b63d3
5
+ SHA512:
6
+ metadata.gz: e302b6b3a6d75bba33696d2e246db31516cf4be131ae0959b0f3bdedf55c3b54454834f705daca9973cf3f116d5b30b0fcbae06c6549c82f1fc3039cc95db1ea
7
+ data.tar.gz: 628a7c6ead32e9b48d37c908992d0347913f75b3c2e20c5cc82e4d619fc242894910394bafb19bc3ba099b335e71f383dcca649825b32501c0320cb8c98a35a1
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ before_install: gem install bundler -v 1.14.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in virtfs.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 ManageIQ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1 @@
1
+ # virt_disk
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ Dir.glob('tasks/**/*.rake').each(&method(:import))
4
+
5
+ task :test => :spec
6
+ task :default => :spec
@@ -0,0 +1,9 @@
1
+ require 'log_decorator'
2
+ require 'virt_disk/export_methods'
3
+ require 'virt_disk/client_head'
4
+ require 'virt_disk/disk'
5
+ require 'virt_disk/partition'
6
+ require 'virt_disk/partition_type'
7
+ require 'virt_disk/partition_type/dos_partition'
8
+ require 'virt_disk/partition_type/gpt_partition'
9
+ require 'virt_disk/file_io'
@@ -0,0 +1,32 @@
1
+ module VirtDisk
2
+ class BlockFile
3
+ attr_accessor :path
4
+
5
+ def initialize(path)
6
+ @path = path
7
+ @file = defined?(VirtFS) ? VirtFS::VFile.open(path) : File.open(path)
8
+ end
9
+
10
+ def block_size
11
+ 1
12
+ end
13
+
14
+ def size
15
+ @file.size
16
+ end
17
+
18
+ def close
19
+ @file.close
20
+ end
21
+
22
+ def raw_read(start, len)
23
+ @file.seek start
24
+ @file.read len
25
+ end
26
+
27
+ def raw_write(buf, start, len)
28
+ @file.seek start
29
+ @file.write buf, len
30
+ end
31
+ end # class BlockFile
32
+ end # module VirtDisk
@@ -0,0 +1,56 @@
1
+ module VirtDisk
2
+ class ClientHead
3
+ attr_reader :size, :start_byte_addr, :end_byte_addr, :seek_pos
4
+
5
+ include ExportMethods
6
+
7
+ def initialize(up_stream_module)
8
+ @up_stream_module = up_stream_module
9
+ @start_byte_addr = 0
10
+ @size = @up_stream_module.size
11
+ @end_byte_addr = @size - 1
12
+ @seek_pos = @start_byte_addr
13
+ self.delegate = @up_stream_module
14
+ end
15
+
16
+ def close
17
+ @up_stream_module.close
18
+ end
19
+
20
+ def seek_pos
21
+ @seek_pos - @start_byte_addr
22
+ end
23
+
24
+ def seek(amt, whence = IO::SEEK_SET)
25
+ case whence
26
+ when IO::SEEK_CUR
27
+ @seek_pos += amt
28
+ when IO::SEEK_END
29
+ @seek_pos = @endByteAddr + amt
30
+ when IO::SEEK_SET
31
+ @seek_pos = amt + @start_byte_addr
32
+ end
33
+ @seek_pos
34
+ end
35
+
36
+ def read(len)
37
+ rb = mod_read(@seek_pos, len)
38
+ @seek_pos += rb.length unless rb.nil?
39
+ rb
40
+ end
41
+
42
+ def write(buf, len)
43
+ nbytes = @up_stream_module.mod_write(@seek_pos, buf, len)
44
+ @seek_pos += nbytes
45
+ nbytes
46
+ end
47
+
48
+ def mod_read(offset, len)
49
+ @up_stream_module.mod_read(offset, len)
50
+ end
51
+
52
+ def mod_write(offset, buffer, len)
53
+ @up_stream_module.mod_write(offset, buffer, len)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,21 @@
1
+ module VirtDisk
2
+ class Disk < ClientHead
3
+ DEFAULT_BLOCK_SIZE = 512
4
+ DISK_SIG_OFFSET = 0x1B8
5
+ DISK_SIG_SIZE = 4
6
+
7
+ def initialize(up_stream_module)
8
+ super
9
+ end
10
+
11
+ def block_size
12
+ return @up_stream_module.block_size if @up_stream_module.respond_to?(:block_size)
13
+ DEFAULT_BLOCK_SIZE
14
+ end
15
+ export :block_size
16
+
17
+ def disk_sig
18
+ @disk_sig ||= @up_stream_module.mod_read(DISK_SIG_OFFSET, DISK_SIG_SIZE).unpack('L')[0]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,43 @@
1
+ class String
2
+ # See Programming Ruby 1.9 - The Pragmatic Programmers’ Guide
3
+ # Figure 17.1. "Encodings and Their Aliases" in the for available encodings.
4
+ def UnicodeToUtf8
5
+ dup.force_encoding("UTF-16LE").encode("UTF-8")
6
+ end
7
+
8
+ def UnicodeToUtf8!
9
+ force_encoding("UTF-16LE").encode!("UTF-8")
10
+ end
11
+
12
+ def Utf8ToUnicode
13
+ dup.force_encoding("UTF-8").encode("UTF-16LE")
14
+ end
15
+
16
+ def Utf8ToUnicode!
17
+ force_encoding("UTF-8").encode!("UTF-16LE")
18
+ end
19
+
20
+ def AsciiToUtf8
21
+ dup.force_encoding("ISO-8859-1").encode("UTF-8")
22
+ end
23
+
24
+ def AsciiToUtf8!
25
+ force_encoding("ISO-8859-1").encode!("UTF-8")
26
+ end
27
+
28
+ def Utf8ToAscii
29
+ dup.force_encoding("UTF-8").encode("ISO-8859-1")
30
+ end
31
+
32
+ def Utf8ToAscii!
33
+ force_encoding("UTF-8").encode!("ISO-8859-1")
34
+ end
35
+
36
+ def Ucs2ToAscii
37
+ dup.force_encoding("UTF-16LE").encode("ISO-8859-1")
38
+ end
39
+
40
+ def Ucs2ToAscii!
41
+ force_encoding("UTF-16LE").encode!("ISO-8859-1")
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ require 'uuidtools'
2
+
3
+ module DiskUUID
4
+ REGEX_FORMAT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
5
+
6
+ def self.clean_guid(guid)
7
+ return nil if guid.nil?
8
+ g = guid.to_s.downcase
9
+ return nil if g.strip.empty?
10
+ return g if g.length == 36 && g =~ REGEX_FORMAT
11
+ g.delete!('^0-9a-f')
12
+ g.sub!(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/, '\1-\2-\3-\4-\5')
13
+ end
14
+
15
+ def self.new_guid
16
+ UUIDTools::UUID.timestamp_create.to_s
17
+ end
18
+
19
+ def self.method_missing(m, *args)
20
+ UUIDTools::UUID.send(m, *args)
21
+ end
22
+ end
@@ -0,0 +1,74 @@
1
+ module VirtDisk
2
+ # Utility module used by VirtDisk plug-ins, allowing them to export methods upstream.
3
+ #
4
+ # Typically, VirtDisk plug-ins are linked in a chain - each plug-in communicating with
5
+ # the plug-in immediately upstream from it (where upstream is closer to the source of the data).
6
+ # The last downstream plug-in (the volume head) is the object accessed directly by the client.
7
+ #
8
+ # This module enables upstream plug-ins to export methods, so they can be called directly
9
+ # from the volume head.
10
+ #
11
+ # @note Every plug-in class must include this module, even if they don't export any methods.
12
+ module ExportMethods
13
+ module ClassMethods
14
+ # Export one or more methods, making them callable from the volume head.
15
+ #
16
+ # @param syms [Symbol] the names of one or more methods to be exported.
17
+ # @raise [RuntimeError] if any of the methods aren't defined in the class.
18
+ def export(*syms)
19
+ syms.each do |s|
20
+ sym = s.to_sym
21
+ raise "Method not defined in class: #{sym}" unless method_defined?(sym)
22
+ return nil if exports.include?(sym)
23
+ exports << sym
24
+ end
25
+ nil
26
+ end
27
+
28
+ # @return [Array<Symbol>] array of exported methods.
29
+ def exports
30
+ @__exported_methods ||= []
31
+ end
32
+
33
+ # @param sym [Symbol]
34
+ # @return [Boolean] - true if sym is exported by this class, false otherwise.
35
+ def exported?(sym)
36
+ exports.include?(sym.to_sym)
37
+ end
38
+ end
39
+
40
+ def self.included(host_class)
41
+ host_class.extend(ClassMethods)
42
+ end
43
+
44
+ # Set the module immediately upstream from this one.
45
+ #
46
+ # @param obj [Object] the upstream module.
47
+ def delegate=(obj)
48
+ @__delegate = obj
49
+ end
50
+
51
+ # The module immediately upstream from this one.
52
+ #
53
+ # @return [Object] the module immediately upstream from this one.
54
+ def delegate
55
+ @__delegate
56
+ end
57
+
58
+ # @param sym [Symbol]
59
+ # @return [Boolean] - true if sym is exported by this module's class, false otherwise.
60
+ def exported?(sym)
61
+ self.class.exported?(sym)
62
+ end
63
+
64
+ def respond_to_missing?(sym, include_all = false)
65
+ return false unless @__delegate
66
+ @__delegate.exported?(sym) || @__delegate.send(:respond_to_missing?, sym, include_all)
67
+ end
68
+
69
+ def method_missing(sym, *args)
70
+ super unless respond_to_missing?(sym)
71
+ @__delegate.send(sym, *args)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,42 @@
1
+ module VirtDisk
2
+ class FileIo
3
+ attr_accessor :path
4
+ alias_method :file_name, :path
5
+
6
+ include LogDecorator::Logging
7
+ include ExportMethods
8
+
9
+ export :path, :file_name
10
+
11
+ def initialize(path, *args)
12
+ @path = path
13
+ @file_lock = Mutex.new
14
+
15
+ _log.debug "Opening file - #{path}"
16
+ @file = File.open(path, *args)
17
+ end
18
+
19
+ def size
20
+ @file.size
21
+ end
22
+ export :size
23
+
24
+ def close
25
+ @file.close
26
+ end
27
+
28
+ def mod_read(start, len)
29
+ @file_lock.synchronize do
30
+ @file.seek start
31
+ @file.read len
32
+ end
33
+ end
34
+
35
+ def mod_write(buf, start, len)
36
+ @file_lock.synchronize do
37
+ @file.seek start
38
+ @file.write buf, len
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,39 @@
1
+ require "binary_struct"
2
+
3
+ module VirtDisk
4
+ class Partition < ClientHead
5
+ attr_reader :start_lba, :end_lba, :ptype, :pnum
6
+
7
+ MBR_SIZE = 512
8
+ DOS_SIG = "55aa"
9
+ GPT_SIG = 238
10
+
11
+ DOS_PARTITION_ENTRY = BinaryStruct.new([
12
+ 'C', :bootable,
13
+ 'C', :startCHS0,
14
+ 'C', :startCHS1,
15
+ 'C', :startCHS2,
16
+ 'C', :ptype,
17
+ 'C', :endCHS0,
18
+ 'C', :endCHS1,
19
+ 'C', :endCHS1,
20
+ 'L', :start_lba,
21
+ 'L', :part_size
22
+ ])
23
+ PTE_LEN = DOS_PARTITION_ENTRY.size
24
+ DOS_PT_START = 446
25
+
26
+ def initialize(disk, ptype, pnum, start_lba, end_lba)
27
+ super(disk)
28
+
29
+ @start_lba = start_lba
30
+ @end_lba = end_lba
31
+ @ptype = ptype
32
+ @pnum = pnum
33
+ @start_byte_addr = @start_lba * block_size
34
+ @end_byte_addr = @end_lba * block_size
35
+ @seek_pos = @start_byte_addr
36
+ @size = @end_byte_addr - @start_byte_addr
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,16 @@
1
+ module VirtDisk
2
+ module PartitionType
3
+ def self.partition_probe(disk)
4
+ partition_types.each do |partition_type|
5
+ partitions = partition_type.discover_partitions(disk)
6
+ return partitions unless partitions.empty?
7
+ end
8
+ []
9
+ end
10
+
11
+ def self.partition_types
12
+ constants.collect { |sym| const_get(sym) }
13
+ .find_all { |obj| obj.is_a?(Class) && obj.respond_to?(:discover_partitions) }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,113 @@
1
+ module VirtDisk
2
+ module PartitionType
3
+ class DosPartition < Partition
4
+ DOS_NPTE = 4
5
+ PTYPE_EXT_CHS = 0x05
6
+ PTYPE_EXT_LBA = 0x0f
7
+ PTYPE_LDM = 0x42
8
+
9
+ include LogDecorator::Logging
10
+ include ExportMethods
11
+
12
+ def initialize(disk, ptype, pnum, start_lba, size_in_blocks)
13
+ # Convert partition size to partition end LBA.
14
+ super(disk, ptype, pnum, start_lba, start_lba + size_in_blocks)
15
+ end
16
+
17
+ def self.discover_partitions(disk) # rubocop:disable AbcSize
18
+ _log.debug "<#{disk.object_id}> disk file: #{disk.file_name}" if disk.respond_to? :file_name
19
+ mbr = disk.mod_read(0, MBR_SIZE)
20
+
21
+ if mbr.length < MBR_SIZE
22
+ _log.info "<#{disk.object_id}> disk does not contain a master boot record"
23
+ return []
24
+ end
25
+
26
+ sig = mbr[510..511].unpack('H4')
27
+
28
+ pt_entry = DOS_PARTITION_ENTRY.decode(mbr[DOS_PT_START, PTE_LEN])
29
+ ptype = pt_entry[:ptype]
30
+
31
+ return [] if sig[0] != DOS_SIG || ptype == GPT_SIG
32
+ discover_dos_pri_partitions(disk, mbr)
33
+ end
34
+
35
+ def self.discover_dos_pri_partitions(disk, mbr) # rubocop:disable AbcSize
36
+ pte = DOS_PT_START
37
+ partitions = []
38
+ (1..DOS_NPTE).each do |n|
39
+ pt_entry = DOS_PARTITION_ENTRY.decode(mbr[pte, PTE_LEN])
40
+ pte += PTE_LEN
41
+ ptype = pt_entry[:ptype]
42
+
43
+ #
44
+ # If this os an LDM (dynamic) disk, then ignore any partitions.
45
+ #
46
+ if ptype == PTYPE_LDM
47
+ _log.debug "<#{disk.object_id}> detected LDM (dynamic) disk"
48
+ return([])
49
+ end
50
+
51
+ if ptype == PTYPE_EXT_CHS || ptype == PTYPE_EXT_LBA
52
+ partitions.concat(
53
+ discover_dos_ext_partitions(
54
+ disk,
55
+ pt_entry[:start_lba],
56
+ pt_entry[:start_lba],
57
+ DOS_NPTE + 1
58
+ )
59
+ )
60
+ next
61
+ end
62
+ partitions.push(new(disk, ptype, n, pt_entry[:start_lba], pt_entry[:part_size])) if ptype != 0
63
+ end
64
+ partitions
65
+ end
66
+
67
+ #
68
+ # Discover secondary file system partitions within a primary extended partition.
69
+ #
70
+ # pri_base_lba is the LBA of the primary extended partition.
71
+ # All pointers to secondary extended partitions are relative to this base.
72
+ #
73
+ # ptBaseLBA is the LBA of the partition table within the current extended partition.
74
+ # All pointers to secondary file system partitions are relative to this base.
75
+ #
76
+ def self.discover_dos_ext_partitions(disk, pri_base_lba, ptBaseLBA, pnum) # rubocop:disable AbcSize
77
+ ra = []
78
+ seek(ptBaseLBA * @blockSize, IO::SEEK_SET)
79
+ mbr = read(MBR_SIZE)
80
+
81
+ #
82
+ # Create and add disk object for secondary file system partition.
83
+ # NOTE: the start of the partition is relative to ptBaseLBA.
84
+ #
85
+ pte = DOS_PT_START
86
+ pt_entry = DOS_PARTITION_ENTRY.decode(mbr[pte, PTE_LEN])
87
+ ra << new(
88
+ disk,
89
+ pt_entry[:ptype],
90
+ pnum,
91
+ pt_entry[:start_lba] + ptBaseLBA,
92
+ pt_entry[:part_size]
93
+ ) if pt_entry[:ptype] != 0
94
+
95
+ #
96
+ # Follow the chain to the next secondary extended partition.
97
+ # NOTE: the start of the partition is relative to pri_base_lba.
98
+ #
99
+ pte += PTE_LEN
100
+ pt_entry = DOS_PARTITION_ENTRY.decode(mbr[pte, PTE_LEN])
101
+ ra.concat(
102
+ discover_dos_ext_partitions(
103
+ disk,
104
+ pri_base_lba,
105
+ pt_entry[:start_lba] + pri_base_lba,
106
+ pnum + 1
107
+ )
108
+ ) if pt_entry[:start_lba] != 0
109
+ ra
110
+ end
111
+ end
112
+ end
113
+ end