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.
@@ -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