Created
November 7, 2014 20:58
-
-
Save movitto/d102ed247fbc2e942e31 to your computer and use it in GitHub Desktop.
FileSystem Soup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# FSoup - filesystem soup | |
# Disk and File System Factory | |
# | |
# Licensed under the MIT license | |
# Copyright (C) 2014 Red Hat Inc. | |
$include_factories = true | |
module FSoup | |
module Util | |
def to_byte_array(num) | |
result = [] | |
begin | |
result << (num & 0xff) | |
num >>= 8 | |
end until (num == 0 || num == -1) && (result.last[7] == num[7]) | |
result#.reverse # little-endian as is | |
end | |
def extract_bytes(val) | |
case val | |
when String then | |
val.unpack('C*') | |
when Integer then | |
to_byte_array(val) | |
when TrueClass | |
to_byte_array(1) | |
when FalseClass | |
to_byte_array(0) | |
when NilClass then | |
[] | |
else | |
Array.new(val) | |
end | |
end | |
def fix_length(arr, len, opts={}) | |
fill = opts[:fill] || 0 | |
default = opts[:default] | |
if arr.nil? | |
if default | |
dbytes = [default].pack('N').unpack('C*')[0...len] | |
dbytes.fill(fill, dbytes.length, len - dbytes.length) | |
else | |
Array.new(len) { fill } | |
end | |
elsif arr.length > len | |
arr[0..len] | |
elsif arr.length < len | |
Array.new(arr).fill(fill, arr.length, len - arr.length) | |
else | |
Array.new(arr) | |
end | |
end | |
end # module Util | |
class Disk | |
# path to write the disk | |
attr_accessor :path | |
attr_accessor :bytes | |
def to_bytes | |
(bytes ? bytes : []).pack('C*') | |
end | |
end | |
module DOS | |
class MBR | |
include Util | |
TOTAL_SIZE = 512 | |
BOOT_CODE_SIZE = 446 | |
NUM_PARTITIONS = 4 | |
HEADS_PER_CYLINDER = 16 | |
SECTORS_PER_TRACK = 63 | |
attr_accessor :boot_code | |
attr_accessor :partitions | |
# http://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion | |
def self.lba2chs(lba) | |
c = lba / (HEADS_PER_CYLINDER * SECTORS_PER_TRACK) | |
h = (lba / SECTORS_PER_TRACK) % HEADS_PER_CYLINDER | |
s = (lba % SECTORS_PER_TRACK) + 1 | |
(c << 16) | (h << 8) | s | |
end | |
def self.table_for(partitions) | |
parts = [] | |
0.upto(partitions.length-1) do |i| | |
partition = partitions[i] | |
start_lba = 1 | |
partitions[0...i].each { |p| start_lba += p.size + p.padding } | |
end_lba = start_lba + partition.size + partition.padding | |
start_chs = lba2chs(start_lba) | |
end_chs = lba2chs(end_lba) | |
dos_part = Partition.new | |
dos_part.type = partition.dos_type | |
dos_part.starting_chs = start_chs | |
dos_part.ending_chs = end_chs | |
dos_part.starting_lba = start_lba | |
dos_part.sectors = partition.sectors | |
parts << dos_part | |
end | |
parts | |
end | |
def boot_code_bytes | |
fix_length(extract_bytes(boot_code), BOOT_CODE_SIZE).pack('C*') | |
end | |
def partitions_bytes | |
parts = (partitions || []) | |
parts.fill(Partition.empty, parts.length, 4 - parts.length) if parts.length < 4 | |
parts.collect { |p| p.to_bytes }.join | |
end | |
def signature_bytes | |
[0x55, 0xAA].pack('C*') | |
end | |
def to_bytes | |
boot_code_bytes + partitions_bytes + signature_bytes | |
end | |
end # class MBR | |
class Partition | |
include Util | |
attr_accessor :bootable # 1 byte | |
attr_accessor :starting_chs # 3 bytes | |
attr_accessor :type # 1 byte | |
attr_accessor :ending_chs # 3 bytes | |
attr_accessor :starting_lba # 4 bytes | |
attr_accessor :sectors # 4 bytes | |
def self.empty | |
self.new | |
end | |
def bootable_bytes | |
fix_length(extract_bytes(bootable), 1).pack('C*') | |
end | |
def starting_chs_bytes | |
fix_length(extract_bytes(starting_chs), 3).pack('C*') | |
end | |
def type_bytes | |
fix_length(extract_bytes(type), 1).pack('C*') | |
end | |
def ending_chs_bytes | |
fix_length(extract_bytes(ending_chs), 3).pack('C*') | |
end | |
def starting_lba_bytes | |
fix_length(extract_bytes(starting_lba), 4).pack('C*') | |
end | |
def sectors_bytes | |
fix_length(extract_bytes(sectors), 4).pack('C*') | |
end | |
def to_bytes | |
bootable_bytes + starting_chs_bytes + type_bytes + | |
ending_chs_bytes + starting_lba_bytes + sectors_bytes | |
end | |
end # class Partition | |
end # module DOS | |
module FileSystem | |
attr_accessor :offset | |
alias :padding :offset | |
def offset_bytes | |
Array.new(offset, 0).pack('C*') | |
end | |
def filler_bytes | |
len = (fs_bytes.length + offset_bytes.length) | |
(size > len) ? Array.new(size - len, 0).pack('C*') : "" | |
end | |
def to_bytes | |
offset_bytes + fs_bytes + filler_bytes | |
end | |
attr_accessor :dos_type | |
attr_accessor :size | |
def expand_size! | |
if size.is_a?(String) | |
if size[-1] == 'K' | |
self.size = self.size[0...-1].to_i * 1024 | |
elsif size[-1] == 'M' | |
self.size = self.size[0...-1].to_i * 1024 * 1024 | |
elsif size[-1] == 'G' | |
self.size = self.size[0...-1].to_i * 1024 * 1024 * 1024 | |
else | |
self.size = self.size[0..-1].to_i | |
end | |
end | |
end | |
end # module FileSystem | |
class FAT | |
include FileSystem | |
class BootSector | |
DEFAULT_BYTES_PER_SECTOR = 512 | |
attr_accessor :bytes_per_sector | |
attr_accessor :sectors | |
def to_bytes | |
# TODO ... | |
Array.new(65) { 0 }.pack('C*') + [0x29].pack('C*') + | |
Array.new(15) { 0 }.pack('C*') + 'FAT32'.bytes.pack('C*') | |
end | |
end # class BootSector | |
attr_accessor :boot_sector | |
# attr_accessor :fats | |
# attr_accessor :root_dir ... | |
def sectors | |
boot_sector.sectors | |
end | |
def sectors=(val) | |
boot_sector.sectors = val | |
end | |
def bytes_per_sector | |
boot_sector.bytes_per_sector || FSoup::FAT::BootSector::DEFAULT_BYTES_PER_SECTOR | |
end | |
def boot_sector_bytes | |
(boot_sector || BootSector.new).to_bytes | |
end | |
def fs_bytes | |
boot_sector_bytes # + fats.collect { |f| f.to_bytes }.join + ... | |
end | |
end # class FAT | |
end # module FSoup | |
if $include_factories | |
require 'factory_girl' | |
FactoryGirl.define do | |
to_create { |instance| } | |
factory :disk, class: FSoup::Disk do | |
# handle creation by writing instance bytes to disk | |
to_create { |instance| File.write(instance.path, instance.to_bytes) } | |
ignore do | |
mbr nil | |
partitions [] | |
end | |
after(:build) do |disk, evaluator| | |
partitions = evaluator.partitions | |
# build mbr if symbol | |
mbr = evaluator.mbr.is_a?(Symbol) ? build(evaluator.mbr) : evaluator.mbr | |
# partitions -> mbr table | |
mbr.partitions = mbr.class.table_for(partitions) | |
# mbr / partitions -> bytes | |
disk.bytes = (mbr.to_bytes + partitions.collect { |p| p.to_bytes }.join).unpack('C*') | |
end | |
end | |
factory :dos, class: FSoup::DOS::MBR do | |
end | |
factory :fat, class: FSoup::FAT do | |
offset 0 | |
factory :fat12 do | |
dos_type 12 | |
end | |
factory :fat16 do | |
dos_type 16 | |
end | |
factory :fat32 do | |
dos_type 32 | |
end | |
after(:build) do |fs, evaluator| | |
fs.expand_size! | |
fs.boot_sector = FSoup::FAT::BootSector.new | |
fs.sectors = fs.size / fs.bytes_per_sector | |
end | |
end | |
#factory :ntfs do | |
#end | |
end | |
end # if $include_factories |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# invoke with: ruby -Ifsoup:manageiq/lib/disk usage.rb | |
require 'fsoup' | |
include FactoryGirl::Syntax::Methods | |
create(:disk, path: 'test.img', | |
mbr: :dos, | |
partitions: [create(:fat32, size: '66K')]) | |
# --- | |
require 'log4r' | |
require 'ostruct' | |
require 'MiqDisk' | |
require 'modules/MiqLargeFile' | |
$log = Log4r::Logger.new 'test' | |
diskInfo = OpenStruct.new | |
diskInfo.rawDisk = true | |
diskInfo.fileName = 'test.img' | |
disk = MiqDisk.getDisk(diskInfo, "RawDiskProbe") | |
parts = disk.getPartitions | |
puts parts.length # => 1 | |
puts parts.collect { |part| [part.partType, part.size] } # => [32, 65536] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment