Skip to content

Instantly share code, notes, and snippets.

@movitto
Created November 7, 2014 20:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save movitto/d102ed247fbc2e942e31 to your computer and use it in GitHub Desktop.
Save movitto/d102ed247fbc2e942e31 to your computer and use it in GitHub Desktop.
FileSystem Soup
# 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
# 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