ManageIQ/manageiq-smartstate

View on GitHub
lib/VolumeManager/MiqLdm.rb

Summary

Maintainability
D
1 day
Test Coverage
F
15%
# encoding: US-ASCII

# Windows dynamic disks
#

require 'ostruct'
require 'binary_struct'
require 'uuidtools'

class BinaryStruct
  def self.stepDecode(data, format)
    type = format[0, 1]
    raise "unrecognized format: #{type}" if SIZES.key?(type) == false
    raise "unsupported  format: #{type}" if SIZES[type].nil?

    count = (format.size > 1) ? format[1, format.size - 1].to_i : 1
    raise "unsupported count: #{count}" if count.kind_of?(Numeric) == false

    size = (count * SIZES[type])

    data.slice!(0, size).unpack(format)[0]
  end
end

module LdmScanner
  LDM_SECTOR_SIZE   = 512
  LDM_PARTITION_TYPE  = 66
  PRIVHEAD_OFFSET   = 6 * LDM_SECTOR_SIZE

  #
  # On disk, all numbers are in big-endian format.
  #

  PRIVHEAD  = [
    'a8',     'signature',      # 8
    'N',      'unknown_1',      # 4
    'n',      'ver_major',      # 2
    'n',      'ver_minor',      # 2
    'Q',      'timestamp',      # 8
    'Q',      'unknown_2',      # 8 number ?
    'Q',      'unknown_3',      # 8 size ?
    'Q',      'unknown_4',      # 8 size ?
    'a64',      'disk_id',        # 64 zero padded
    'a64',      'host_id',        # 64 zero padded
    'a64',      'diskgroup_id',     # 64 zero padded
    'a32',      'diskgroup_name',   # 32 zero padded
    'a2',     'unknown_5',      # 2
    'a9',     'unknown_6',      # 9 zeros
    'N',      'logical_disk_start_H', # 8
    'N',      'logical_disk_start_L', # "
    'N',      'logical_disk_size_H',  # 8
    'N',      'logical_disk_size_L',  # "
    'N',      'db_start_H',     # 8
    'N',      'db_start_L',     # "
    'N',      'db_size_H',      # 8
    'N',      'db_size_L',      # "
    'N',      'num_tocs_H',     # 8
    'N',      'num_tocs_L',     # "
    'N',      'toc_size_H',     # 8
    'N',      'toc_size_L',     # "
    'N',      'num_configs',      # 4
    'N',      'num_logs',       # 4
    'N',      'config_size_H',    # 8
    'N',      'config_size_L',    # "
    'N',      'log_size_H',     # 8
    'N',      'log_size_L',     # "
    'N',      'disk_signature',   # 4
    'C16',      'disk_set_guid',    # 16
    'C16',      'disk_set_guid2',   # 16
    "C#{512 - 391}",  'padding'       # Pad to 512 bytes (sector size)
  ]

  TBLOCK_BLOCK  = [1, 2, 2045, 2046]
  TOCBLOCK  = [
    'a8',     'signature',
    'N',      'sequence1',
    'a4',     'unknown1',
    'N',      'sequence2',
    'a16',      'unknown2',
    'a10',      'bitmap1_name',
    'N',      'bitmap1_start_H',
    'N',      'bitmap1_start_L',
    'N',      'bitmap1_size_H',
    'N',      'bitmap1_size_L',
    'N',      'bitmap1_flags_H',
    'N',      'bitmap1_flags_L',
    'a10',      'bitmap2_name',
    'N',      'bitmap2_start_H',
    'N',      'bitmap2_start_L',
    'N',      'bitmap2_size_H',
    'N',      'bitmap2_size_L',
    'N',      'bitmap2_flags_H',
    'N',      'bitmap2_flags_L',
    "C#{512 - 104}",  'padding'
  ]

  VMDB  = [
    'a4',     'signature',
    'N',      'sequence',
    'N',      'vblk_size',
    'N',      'vblk_offset',
    'n',      'unknown1',
    'n',      'ver_major',
    'n',      'ver_minor',
    'a31',      'dg_name',
    'a64',      'dg_guid',
    'N',      'committed_seq_H',
    'N',      'committed_seq_L',
    'N',      'pending_seq_H',
    'N',      'pending_seq_L',
    'a56',      'unknown2',
    'N',      'timestamp',
    "C#{512 - 193}",  'padding'
  ]

  VBLK  = [
    'a4',     'signature',
    'N',      'vmdb_seq',
    'N',      'grpnum',
    'n',      'record',
    'n',      'nrecords',
    'n',      'update_status',
    'C',      'flags',
    'C',      'rec_type',
    'N',      'data_length',
    "a#{128 - 24}", 'padding'
  ]

  #
  # VBLOCK types.
  #
  VBT_NONE    = 0x00
  VBT_COMPONENT = 0x32
  VBT_PARTITION = 0x33
  VBT_DISKV1    = 0x34
  VBT_DISKGROUPV1 = 0x35
  VBT_DISKV2    = 0x44
  VBT_DISKGROUPV2 = 0x45
  VBT_VOLUME    = 0x51

  VBLK_TYPES  = {
    VBT_NONE        => "NONE",
    VBT_COMPONENT   => "Component",
    VBT_PARTITION   => "Partition",
    VBT_DISKV1      => "Disk v1",
    VBT_DISKGROUPV1 => "Disk Group v1",
    VBT_DISKV2      => "Disk v2",
    VBT_DISKGROUPV2 => "Disk Group v2",
    VBT_VOLUME      => "Volume"
  }

  def self.scan(d)
    return nil if d.partType != LDM_PARTITION_TYPE

    d.seek(PRIVHEAD_OFFSET)
    ph = readStruct(d, PRIVHEAD)
    # LdmScanner.dumpPrivhead(ph)
    return nil if ph.signature != "PRIVHEAD"

    ph.disk_id.delete!("\000")
    ph.diskgroup_id.delete!("\000")
    ph.diskgroup_name.delete!("\000")
    ph.host_id.delete!("\000")

    ph.lvm_type = "LDM"
    ph.pv_uuid = ph.disk_id
    ph.diskObj = d

    tb = nil
    TBLOCK_BLOCK.each do |tbo|
      tblock_offset = (ph.db_start + tbo) * LDM_SECTOR_SIZE
      d.seek(tblock_offset)
      tb = readStruct(d, TOCBLOCK)
      # LdmScanner.dumpTocblock(tb)
      break if tb.signature == "TOCBLOCK"
    end

    unless tb
      $log.warn "LdmScanner: could not find valid TOCBLOCK on LDM disk" if $log
      return nil
    end

    vmdb_offset = (ph.db_start + tb.bitmap1_start) * LDM_SECTOR_SIZE
    d.seek(vmdb_offset)
    vmdb = readStruct(d, VMDB)

    ph.vblkHash = {}
    ph.volumes  = []
    ph.diskVb = nil
    (0...vmdb.sequence).each { |_i| readVBLK(d, ph) }

    ph.vblkHash.each_value do |v|
      ph.vblkHash[v.parent_id].children << v if v.parent_id
      v.disk = ph.vblkHash[v.disk_id] if v.disk_id
    end
    (ph)
  end

  def self.readStruct(d, struct)
    h = BinaryStruct.decode(d.read(BinaryStruct.sizeof(struct)), struct)
    h.keys.delete_if { |k| !(/.*_H/ =~ k) }.each do |k|
      (nk = k.dup)[/_H$/] = ""
      lk = nk + "_L"
      h[nk] = (h[k] << 32) | h[lk]
    end
    OpenStruct.new(h)
  end # def self.readStruct

  def self.getChunk(data)
    len = data.slice!(0, 1).unpack("C")[0]
    data.slice!(0, len)
  end

  def self.getNum(data)
    n = 0
    getChunk(data).each_byte { |b| n = (n << 8) | b }
    (n)
  end

  def self.getNBO8(data)
    h, l = data.slice!(0, 8).unpack("N2")
    ((h << 32) | l)
  end

  def self.readVBLK(disk, ph)
    vblk = LdmScanner.readStruct(disk, LdmScanner::VBLK)
    return if vblk.data_length == 0
    buf = vblk.padding

    vblk.vobject_id   = getNum(buf)
    vblk.name     = getChunk(buf)

    case vblk.rec_type
    when VBT_NONE     # NONE
    when VBT_COMPONENT    # Component
      vblk.children   = []
      vblk.volume_state = getChunk(buf)
      vblk.component_type = BinaryStruct.stepDecode(buf, "C")
      BinaryStruct.stepDecode(buf, "C4")
      vblk.num_children = getNum(buf)
      BinaryStruct.stepDecode(buf, "C16")
      vblk.parent_id    = getNum(buf)
      BinaryStruct.stepDecode(buf, "C")
      if vblk.flags != 0
        vblk.stripe_size  = getNum(buf)
        vblk.num_col    = getNum(buf)
      end

    when VBT_PARTITION    # Partition
      BinaryStruct.stepDecode(buf, "C12")
      vblk.start      = getNBO8(buf)
      vblk.volume_offset  = getNBO8(buf)
      vblk.size     = getNum(buf)
      vblk.parent_id    = getNum(buf)
      vblk.disk_id    = getNum(buf)
      vblk.comp_part_idx  = getNum(buf) if vblk.flags == 0x08

    when VBT_DISKV1     # Disk v1
      vblk.disk_guid    = getChunk(buf)
      vblk.alt_name   = getChunk(buf)
      ph.diskVb     = vblk if ph.disk_id == vblk.disk_guid

    when VBT_DISKGROUPV1  # Disk Group v1
      vblk.dg_guid    = getChunk(buf)

    when VBT_DISKV2     # Disk v2
      vblk.disk_guid1   = UUIDTools::UUID.parse_raw(BinaryStruct.stepDecode(buf, "a16")).to_s
      vblk.disk_guid2   = UUIDTools::UUID.parse_raw(BinaryStruct.stepDecode(buf, "a16")).to_s
      ph.diskVb     = vblk if ph.disk_id == vblk.disk_guid1

    when VBT_DISKGROUPV2  # Disk Group v2
      vblk.dg_guid    = UUIDTools::UUID.parse_raw(BinaryStruct.stepDecode(buf, "a16")).to_s
      vblk.ds_guid    = UUIDTools::UUID.parse_raw(BinaryStruct.stepDecode(buf, "a16")).to_s

    when VBT_VOLUME     # Volume
      vblk.children   = []
      vblk.volume_type  = getChunk(buf)
      BinaryStruct.stepDecode(buf, "C")
      vblk.volume_state = BinaryStruct.stepDecode(buf, "a14").delete("\000")
      vblk.volume_typeN = BinaryStruct.stepDecode(buf, "C")
      BinaryStruct.stepDecode(buf, "C")
      vblk.volume_number  = BinaryStruct.stepDecode(buf, "C")
      BinaryStruct.stepDecode(buf, "C3")
      vblk.vol_flags    = BinaryStruct.stepDecode(buf, "C")
      vblk.num_children = getNum(buf)
      BinaryStruct.stepDecode(buf, "C16")
      vblk.size     = getNum(buf)
      BinaryStruct.stepDecode(buf, "C4")
      vblk.partition_type = BinaryStruct.stepDecode(buf, "C")
      vblk.volume_id    = UUIDTools::UUID.parse_raw(BinaryStruct.stepDecode(buf, "a16")).to_s

      vblk.id1      = getChunk(buf) if (vblk.flags & 0x08) != 0x0
      vblk.id2      = getChunk(buf) if (vblk.flags & 0x20) != 0x0
      vblk.csize      = getNum(buf) if (vblk.flags & 0x80) != 0x0
      vblk.drive_hint   = getChunk(buf) if (vblk.flags & 0x02) != 0x0

      if vblk.volume_state == "ACTIVE"
        if vblk.volume_type == "gen"
          ph.volumes << vblk
        else
          $log.warn "LdmScanner: unsupported volume type - #{vblk.volume_type}" if $log
        end
      end
    end

    vblk.delete_field("padding") if vblk.padding
    ph.vblkHash[vblk.vobject_id] = vblk
    (vblk)
  end

  def self.dumpPrivhead(ph)
    puts
    puts "PRIVHEAD:"
    puts "\tsignature:          #{ph.signature}"
    puts "\tver_major:          #{ph.ver_major}"
    puts "\tver_minor:          #{ph.ver_minor}"
    puts "\tdisk_id:            #{ph.disk_id}"
    puts "\thost_id:            #{ph.host_id}"
    puts "\tdiskgroup_id:       #{ph.diskgroup_id}"
    puts "\tdiskgroup_name:     #{ph.diskgroup_name}"
    puts "\tlogical_disk_start: #{ph.logical_disk_start}"
    puts "\tlogical_disk_size:  #{ph.logical_disk_size}"
    puts "\tdb_start:           #{ph.db_start}"
    puts "\tdb_size:            #{ph.db_size}"
    puts "\tnum_tocs:           #{ph.num_tocs}"
    puts "\ttoc_size:           #{ph.toc_size}"
    puts "\tnum_configs:        #{ph.num_configs}"
    puts "\tconfig_size:        #{ph.config_size}"
    puts "\tnum_logs:           #{ph.num_logs}"
    puts "\tlog_size:           #{ph.log_size}"
    puts "\tdisk_signature:     #{ph.disk_signature}"
    puts "\tdisk_set_guid:      #{ph.disk_set_guid}"
    puts "\tdisk_set_guid2:     #{ph.disk_set_guid2}"
  end

  def self.dumpTocblock(tb)
    puts
    puts "TOCBLOCK:"
    puts "\tsignature:          #{tb.signature}"
    puts "\tsequence1:          #{tb.sequence1}"
    puts "\tsequence2:          #{tb.sequence2}"
    puts "\tbitmap1_name:       #{tb.bitmap1_name}"
    puts "\tbitmap1_start:      #{tb.bitmap1_start}"
    puts "\tbitmap2_name:       #{tb.bitmap2_name}"
    puts "\tbitmap2_start:      #{tb.bitmap2_start}"
  end

  def self.dumpVmdb(vmdb)
    puts
    puts "VMDB:"
    puts "\tsignature:          #{vmdb.signature}"
    puts "\tsequence:           #{vmdb.sequence}"
    puts "\tvblk_size:          #{vmdb.vblk_size}"
    puts "\tvblk_offset:        #{vmdb.vblk_offset}"
    puts "\tver_major:          #{vmdb.ver_major}"
    puts "\tver_minor:          #{vmdb.ver_minor}"
    puts "\tdg_name:            #{vmdb.dg_name}"
    puts "\tdg_guid:            #{vmdb.dg_guid}"
    puts "\tcommitted_seq:      #{vmdb.committed_seq}"
    puts "\tpending_seq:        #{vmdb.pending_seq}"
  end

  def self.dumpVblk_old(vb)
    return if vb.data_length == 0
    puts
    puts "VBLK:"
    puts   "\tsignature:          #{vb.signature}"
    puts   "\tvmdb_seq:           #{vb.vmdb_seq}"
    puts   "\tgrpnum:             #{vb.grpnum}"
    puts   "\trecord:             #{vb.record}"
    puts   "\tnrecords:           #{vb.nrecords}"
    puts   "\tupdate_status:      #{vb.update_status}"
    printf("\trec_type:           0x%x\t%s\n", vb.rec_type & 0xff, VBLK_TYPES[vb.rec_type & 0xff])
    puts   "\tdata_length:        #{vb.data_length}"
    puts  "\tpadding.length:    #{vb.padding.length}"
  end

  def self.dumpVblk(vb, indent = "")
    return if vb.data_length == 0
    puts
    puts "#{indent}VBLK: #{VBLK_TYPES[vb.rec_type]}"
    vb.marshal_dump.each { |k, v| printf("#{indent}\t%-15s: #{v}\n", k) unless v.kind_of?(Array) || v.kind_of?(OpenStruct) }
  end
end # module LdmScanner

class LdmMdParser
  attr_reader :vgName

  def initialize(privhead, pvHdrs)
    @pvHdrs   = pvHdrs        # PV headers hashed by UUID
    @privhead = privhead
    @vgName   = privhead.diskgroup_name
  end

  def parse
    getVgObj
  end

  private

  def getVgObj
    vgObj = VolumeGroup.new(@privhead.diskgroup_id, @vgName, 1)
    vgObj.lvmType = "LDM"

    @pvHdrs.each_value do |pvh|
      next if pvh.diskgroup_name != @vgName
      vgObj.physicalVolumes[pvh.diskVb.name] = getPvObj(vgObj, pvh) if pvh.diskVb
    end
    @privhead.volumes.each do |v|
      vgObj.logicalVolumes[v.name] = getLvObj(vgObj, v)
    end
    (vgObj)
  end

  def getPvObj(vgObj, pvh)
    pvObj = PhysicalVolume.new(pvh.disk_id, pvh.diskVb.name, nil, pvh.logical_disk_size, pvh.logical_disk_start, pvh.logical_disk_size)
    pvObj.vgObj = vgObj
    pvObj.diskObj = pvh.diskObj
    pvObj.diskObj.pvObj = pvObj
    (pvObj)
  end

  def getLvObj(vgObj, vol)
    comp = vol.children.first
    lvObj = LogicalVolume.new(vol.volume_id, vol.name, comp.num_children)
    lvObj.vgObj = vgObj
    lvObj.driveHint = vol.drive_hint
    comp.children.each { |part| lvObj.segments << getSegObj(part) }
    lvObj.segments.sort! { |x, y| x.startExtent <=> y.startExtent }
    (lvObj)
  end

  def getSegObj(part)
    segObj = LvSegment.new(part.volume_offset, part.size, nil, 1)
    segObj.stripes << part.disk.name
    segObj.stripes << part.start
    (segObj)
  end
end # class LdmMdParser

if __FILE__ == $0
  SD = File.dirname(__FILE__)
  $: << File.join(SD, "../disk")

  require 'rubygems'
  require 'ostruct'
  require 'MiqDisk'

  require 'logger'
  $log = Logger.new(STDERR)
  $log.level = Logger::DEBUG

  # DISK = "/Volumes/WDpassport/Virtual Machines/cn071vcce130/cn071vcce130_3.vmdk"
  # DISK = "/Volumes/WDpassport/Virtual Machines/cn071vcce130/cn071vcce130.vmdk"
  DISK = "/Volumes/WDpassport/Virtual Machines/MIQAppliance-win2008x86/Win2008x86.vmdk"
  puts "VMDB size = #{BinaryStruct.sizeof(LdmScanner::VBLK)}"

  diskInfo = OpenStruct.new
  diskInfo.fileName = DISK

  disk = MiqDisk.getDisk(diskInfo)

  unless disk
    puts "Failed to open disk"
    exit(1)
  end

  # parts = disk.getPartitions

  puts "Disk type: #{disk.diskType}"
  puts "Disk partition type: #{disk.partType}"
  puts "Disk block size: #{disk.blockSize}"
  puts "Disk start LBA: #{disk.lbaStart}"
  puts "Disk end LBA: #{disk.lbaEnd}"
  puts "Disk start byte: #{disk.startByteAddr}"
  puts "Disk end byte: #{disk.endByteAddr}"

  # disk.seek(LdmScanner::PRIVHEAD_OFFSET)
  # ph = LdmScanner.readStruct(disk, LdmScanner::PRIVHEAD)
  # if ph.signature != "PRIVHEAD"
  #   puts "#{DISK} is not an LDM disk"
  #   exit
  # end
  # LdmScanner.dumpPrivhead(ph)
  #
  # tblock_offset = (ph.db_start + LdmScanner::TBLOCK_BLOCK) * LdmScanner::LDM_SECTOR_SIZE
  # disk.seek(tblock_offset)
  # tb = LdmScanner.readStruct(disk, LdmScanner::TOCBLOCK)
  # LdmScanner.dumpTocblock(tb)
  #
  # vmdb_offset = (ph.db_start + tb.bitmap1_start) * LdmScanner::LDM_SECTOR_SIZE
  # disk.seek(vmdb_offset)
  # vmdb = LdmScanner.readStruct(disk, LdmScanner::VMDB)
  # LdmScanner.dumpVmdb(vmdb)
  #
  # # puts
  # # puts "=============================================="
  # #
  # (0...vmdb.sequence).each do |i|
  #   next unless (vblk = LdmScanner.readVBLK(disk))
  #   LdmScanner.dumpVblk(vblk)
  # end
  #
  # LdmScanner.vblkHash.each_value do |v|
  #   LdmScanner.vblkHash[v.parent_id].children << v if v.parent_id
  #   v.disk = LdmScanner.vblkHash[v.disk_id] if v.disk_id
  # end

  puts
  puts "=============================================="

  unless (ph = LdmScanner.scan(disk))
    puts "#{disk.dInfo.fileName} is not an LDM disk"
    disk.close
    exit
  end

  if ph.volumes.empty?
    puts "#{disk.dInfo.fileName} has no volumes"
    disk.close
    exit
  end

  ph.volumes.each do |v|
    LdmScanner.dumpVblk(v)
    v.children.each do |c|
      LdmScanner.dumpVblk(c, "\t")
      c.children.each do |p|
        LdmScanner.dumpVblk(p, "\t\t")
        LdmScanner.dumpVblk(p.disk, "\t\t\t") if p.disk
      end
    end
  end

  disk.close
end