1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
|
# Copyright 2017 Citrix Systems
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import struct
from os_xenapi.client import exception as xenapi_except
LOG = logging.getLogger(__name__)
FMT_TO_LEN = {
'!B': 1,
'!H': 2,
'!I': 4,
'!Q': 8,
}
DISK_TYPE = {'None': 0,
'Reserved_1': 1,
'Fixed hard disk': 2,
'Dynamic hard disk': 3,
'Differencing hard disk': 4,
'Reserved_5': 5,
'Reserved_6': 6,
}
class VHDFileParser(object):
# This class supplies utils to parse different parts of a VHD file.
# It follows the following VHD spec:
# https://www.microsoft.com/en-us/download/confirmation.aspx?id=23850
def __init__(self, file_obj):
self.src_file = file_obj
self.cached_buff = b''
def get_disk_type_name(self, type_val):
for type_name in DISK_TYPE:
if (DISK_TYPE[type_name] == type_val):
return type_name
def cached_read(self, read_size):
# the data will be cached in the buffer.
data = self.src_file.read(read_size)
if data:
self.cached_buff += data
return data
def parse_vhd_footer(self):
footer_raw_data = self.cached_read(VHDFooter.VHD_HDF_SIZE)
return VHDFooter(footer_raw_data)
class VHDDynDiskParser(VHDFileParser):
"""The class presents the Dynamical Disk file:
The Dynamic Hard Disk Image format is as below:
+-----------------------------------------------+
|Mirror Image of Hard drive footer (512 bytes) |
+-----------------------------------------------+
|Dynamic Disk Header (1024 bytes) |
+-----------------------------------------------+
| padding bytes |
|(Table Offset in Dynamic Disk Header determines|
| where the BAT starts from) |
+-----------------------------------------------+
|BAT (Block Allocation Table) |
+-----------------------------------------------+
|Padding bytes to ensure the bitmap+Data blocks |
|start from 512-byte sector boundary. |
+-----------------------------------------------+
| bitmap 1 (512 bytes) |
| Data Block 1 |
+-----------------------------------------------+
| bitmap 2 (512 bytes) |
| Data Block 2 |
+-----------------------------------------------+
| ... |
+-----------------------------------------------+
| bitmap 1 (512 bytes) |
| Data Block n |
+-----------------------------------------------+
| Hard drive footer (512 bytes) |
+-----------------------------------------------+
"""
SIZE_OF_BITMAP = 512
def __init__(self, file_obj):
self.src_file = file_obj
self.cached_buff = b''
self.footer = self.parse_vhd_footer()
dyn_disk_type = DISK_TYPE['Dynamic hard disk']
if self.footer.disk_type != dyn_disk_type:
disk_type_name = self.get_disk_type_name(
self.footer.disk_type)
raise xenapi_except.VhdDiskTypeNotSupported(
disk_type=disk_type_name)
self.DynDiskHdr = self._get_dynamic_disk_header()
self.BatPaddingData = self._get_bat_padding()
self.Bat = self._get_block_allocation_table()
def _get_dynamic_disk_header(self):
ddh_raw_data = self.cached_read(VHDDynDiskHdr.VHD_DDH_SIZE)
return VHDDynDiskHdr(ddh_raw_data)
def _get_bat_padding(self):
PaddingData = None
len_padding = (self.DynDiskHdr.bat_offset - VHDFooter.VHD_HDF_SIZE -
VHDDynDiskHdr.VHD_DDH_SIZE)
if len_padding > 0:
PaddingData = self.cached_read(len_padding)
return PaddingData
def _get_block_allocation_table(self):
bat_ent_size = FMT_TO_LEN[VHDBlockAllocTable.FMT_BAT_ENT]
bat_size = bat_ent_size * self.DynDiskHdr.bat_max_entries
raw_data = self.cached_read(bat_size)
return VHDBlockAllocTable(raw_data)
def get_vhd_file_size(self):
# it will calculate the VHD file's size basing on the first
# non data block sections. It's useful in the scenario where
# the VHD file's data is passed via streaming. We can
# calculate the file size before we get all data. But please
# note it only works when the data blocks all are continuously
# places in the VHD file (no holes). The VHD files exported
# by invoking XenAPI should meet this prerequisite.
# The "bitmap+Data blocks" should start from the point which is
# after the Block Allocation Table and also meets the 512 bytes
# boundary.
bat_offset = self.DynDiskHdr.bat_offset
bat_size = len(self.Bat.raw_data)
data_offset = bat_offset + bat_size
if data_offset % 512 != 0:
data_offset = (data_offset / 512 + 1) * 512
bitmap_size = VHDDynDiskParser.SIZE_OF_BITMAP
block_size = self.DynDiskHdr.block_size
valid_blocks = self.Bat.num_valid_bat_entries
data_size = (bitmap_size + block_size) * valid_blocks
file_size = data_offset + data_size + VHDFooter.VHD_HDF_SIZE
LOG.debug("Calcuated file_size = {}: bat_offset = {}; "
"bat_size = {}; data_offset = {}; data_size = {}; "
"footer_size = {}".format(file_size, bat_offset, bat_size,
data_offset, data_size,
VHDFooter.VHD_HDF_SIZE))
return file_size
class VHDFooter(object):
# VHD Hard Disk Footer
VHD_HDF_SIZE = 512
HDF_LAYOUT = {
'current_size': {
'offset': 48,
'format': '!Q'},
'disk_type': {
'offset': 60,
'format': '!I'},
}
def __init__(self, raw_data):
self.raw_data = raw_data
self._parse_data()
def _parse_data(self):
hdf_layout = VHDFooter.HDF_LAYOUT
for field in hdf_layout:
format = hdf_layout[field]['format']
pos_start = hdf_layout[field]['offset']
pos_end = pos_start + FMT_TO_LEN[format]
(value, ) = struct.unpack(format,
self.raw_data[pos_start: pos_end])
setattr(self, field, value)
class VHDDynDiskHdr(object):
"""VHD Dynamic Disk Header:
The Dynamic Disk Header(DDH) layout is as below:
|**fields** | **size**|
|Cookie | 8 |
|Data Offset | 8 |
|*Table Offset* | 8 |
|Header Version | 4 |
|*Max Table Entries* | 4 |
|*Block Size* | 4 |
|Checksum | 4 |
|Parent Unique ID | 16 |
|Parent Time Stamp | 4 |
|Reserved | 4 |
|Parent Unicode Name | 512 |
|Parent Locator Entry 1 | 24 |
|Parent Locator Entry 2 | 24 |
|Parent Locator Entry 3 | 24 |
|Parent Locator Entry 4 | 24 |
|Parent Locator Entry 5 | 24 |
|Parent Locator Entry 6 | 24 |
|Parent Locator Entry 7 | 24 |
|Parent Locator Entry 8 | 24 |
|Reserved | 256 |
"""
VHD_DDH_SIZE = 1024
DDH_LAYOUT = {
'bat_offset': {
'offset': 16,
'format': '!Q'},
'bat_max_entries':
{'offset': 28,
'format': '!I'},
'block_size': {
'offset': 32,
'format': '!I'},
}
def __init__(self, raw_data):
self.raw_data = raw_data
self._parse_data()
def _parse_data(self):
ddh_layout = VHDDynDiskHdr.DDH_LAYOUT
for field in ddh_layout:
format = ddh_layout[field]['format']
pos_start = ddh_layout[field]['offset']
pos_end = pos_start + FMT_TO_LEN[format]
(value,) = struct.unpack(format,
self.raw_data[pos_start: pos_end])
setattr(self, field, value)
class VHDBlockAllocTable(object):
# VHD Block Allocation Table
FMT_BAT_ENT = '!I'
def __init__(self, raw_data):
self.raw_data = raw_data
self._parse_data()
def _parse_data(self):
self.num_valid_bat_entries = self.get_valid_bat_entries()
def get_valid_bat_entries(self):
# Calculate the number of valid BAT entries.
# It will go through all BAT entries. Those entries whose value is not
# the default value - 0xFFFFFFFF will be treated as valid.
num_of_valid_bat_ent = 0
size_of_bat_entry = FMT_TO_LEN[VHDBlockAllocTable.FMT_BAT_ENT]
for i in range(0, len(self.raw_data), size_of_bat_entry):
(value, ) = struct.unpack(VHDBlockAllocTable.FMT_BAT_ENT,
self.raw_data[i: i + size_of_bat_entry])
if value != 0xFFFFFFFF:
num_of_valid_bat_ent += 1
return num_of_valid_bat_ent
|