Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for being installed via PyPi and migration to Python 3. #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build
dist
*.egg-info
*.pyc
File renamed without changes.
149 changes: 80 additions & 69 deletions pyvhd.py → pyvhd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,29 @@
#
# This creates the dynamic VHD format but does not make any attempt to detect
# large zereoed sections of disk. As a result, the output is slightly larger
# than the input RAW file.
# than the input RAW file.

import struct
import sys
import uuid
import math


def divro(num, den):
# Divide always rounding up and returning an integer
# Is there some nicer way to do this?
return int(math.ceil((1.0*num)/(1.0*den)))
return int(math.ceil(num / den))


def vhd_checksum(string):
# This is the checksum defined in the MS spec
# sum up all bytes in the checked structure then take the ones compliment
checksum = 0
for byte in string:
checksum += ord(byte)
checksum += byte
return (checksum ^ 0xFFFFFFFF)


def vhd_chs(size):
# CHS calculation as defined by the VHD spec
sectors = divro(size, SECTORSIZE)
Expand All @@ -55,36 +58,38 @@ def vhd_chs(size):

if sectors >= 65535 * 16 * 63:
spt = 255
cth = sectors / spt
cth = sectors // spt
heads = 16
else:
spt = 17
cth = sectors / spt
heads = (cth + 1023) / 1024
cth = sectors // spt
heads = (cth + 1023) // 1024

if heads < 4:
heads = 4

if (cth >= (heads * 1024)) or (heads > 16):
spt = 31
cth = sectors / spt
cth = sectors // spt
heads = 16

if cth >= (heads * 1024):
spt = 63
cth = sectors / spt
cth = sectors // spt
heads = 16

cylinders = cth / heads
cylinders = cth // heads

return (cylinders, heads, spt)


def zerostring(len):
zs = ""
zs = b''
for i in range(1, len):
zs += '\0'
zs += b'\0'
return zs


# Header/Footer - From MS doco
# 512 bytes - early versions had a 511 byte footer for no obvious reason

Expand Down Expand Up @@ -136,7 +141,7 @@ def zerostring(len):

DYNAMIC_FMT = ">8sQQIIII16sII512s192s256s"

# BAT header
# BAT header
# This is not in the Microsoft spec but is present in the Xen code
# This is a bitmap of the BAT where "1" indicates the BAT entry is valid
# and "0" indicates that it is unallocated.
Expand All @@ -149,98 +154,102 @@ def zerostring(len):

BAT_HDR_FMT = ">8sQIII"

VHD_BLOCKSIZE = 2 * 1024 * 1024 # Default blocksize 2 MB
VHD_BLOCKSIZE = 2 * 1024 * 1024 # Default blocksize 2 MB
SECTORSIZE = 512
VHD_BLOCKSIZE_SECTORS = VHD_BLOCKSIZE/SECTORSIZE
VHD_BLOCKSIZE_SECTORS = VHD_BLOCKSIZE // SECTORSIZE
VHD_HEADER_SIZE = struct.calcsize(HEADER_FMT)
VHD_DYN_HEADER_SIZE = struct.calcsize(DYNAMIC_FMT)
SECTOR_BITMAP_SIZE = VHD_BLOCKSIZE / SECTORSIZE / 8
FULL_SECTOR_BITMAP = ""
for i in range(0,SECTOR_BITMAP_SIZE):
FULL_SECTOR_BITMAP += chr(0xFF)
SECTOR_BITMAP_SIZE = VHD_BLOCKSIZE // SECTORSIZE // 8
FULL_SECTOR_BITMAP = b''
for i in range(0, SECTOR_BITMAP_SIZE):
FULL_SECTOR_BITMAP += b'\xff'
SECTOR_BITMAP_SECTORS = divro(SECTOR_BITMAP_SIZE, SECTORSIZE)
# vhd-util has a bug that pads an additional 7 sectors on to each bloc
# at the end. I suspect this is due to miscalculating the size of the
# bitmap. Specifically, forgetting to divide the number of bits by 8.
BLOCK_PAD_SECTORS = 7 # Bug for bug compat with vhd-util
BLOCK_PAD_SECTORS = 7 # Bug for bug compat with vhd-util


def do_vhd_convert(infile, outfile):
# infile - open file object containing raw input flie
# outfile - open for writing file object to which output is written
infile.seek(0,2)
infile.seek(0, 2)
insize = infile.tell()
infile.seek(0)

bat_entries = divro(insize, VHD_BLOCKSIZE)
# Block Allocation Table (BAT) size in sectors
bat_sectors = divro(bat_entries*4, SECTORSIZE)
bat_sectors = divro(bat_entries * 4, SECTORSIZE)

# OK - cannot quite figure out why vhd-util adds more
# pad than needed in some cases - I will just put the
# first block safely past the batmap
batmap_size_sectors = divro(divro(bat_entries,8),SECTORSIZE)
batmap_size_sectors = divro(divro(bat_entries, 8), SECTORSIZE)
first_block_sector = 3 + bat_sectors + 1 + batmap_size_sectors

current_block_sector = first_block_sector
total_block_sectors = SECTOR_BITMAP_SECTORS + VHD_BLOCKSIZE_SECTORS + BLOCK_PAD_SECTORS

bat_values = [ ]
for i in range(0,bat_entries):
bat_values.append(current_block_sector)
current_block_sector += total_block_sectors
bat_values = []
for i in range(0, bat_entries):
bat_values.append(current_block_sector)
current_block_sector += total_block_sectors

bat=""
bat = b''
for sector in bat_values:
bat += struct.pack(">I", ( sector ) )
bat += struct.pack(">I", (sector))

# The Xen code adds yet another sector map, this one of the BAT itself.
# This converter code pre-allocates everything, so we just need a string
# full of set bits of the correct size

batmap=""
for i in range(0,bat_entries/8):
batmap += chr(0xFF)
batmap = b''
for i in range(0, bat_entries // 8):
batmap += chr(0xFF)

extra_bits = bat_entries % 8
if extra_bits != 0:
batmap += chr((0xFF << (8-extra_bits)) & 0xFF)
batmap += bytes([(0xFF << (8 - extra_bits)) & 0xFF])

cookie3 = "tdbatmap"
cookie3 = b"tdbatmap"
# 3 sectors for the other headers plus one sector for this
batmap_offset = (3 + bat_sectors + 1) * SECTORSIZE
batmap_size = batmap_size_sectors
batmap_version = 0x00010002 # from vhd-util
batmap_version = 0x00010002 # from vhd-util
batmap_checksum = vhd_checksum(batmap)

batmap_vals = ( cookie3, batmap_offset, batmap_size, batmap_version, batmap_checksum)
batmap_vals = (cookie3, batmap_offset, batmap_size, batmap_version,
batmap_checksum)

batmap_hdr = struct.pack(BAT_HDR_FMT, *batmap_vals)

batmap_hdr_location = 3 + bat_sectors

cookie = "conectix"
features = 2 # Set by convention - means nothing
cookie = b"conectix"
features = 2 # Set by convention - means nothing
fmt_version = 0x00010000
data_offset = 512 # location of dynamic header
data_offset = 512 # location of dynamic header
# This is a problematic field - vhd-util interprets it as local
# time and will reject images that have a stamp in the future
# set it to 24 hours ago to be safe or EPOCH (zero) to be safer
timestamp = 0
creator_app = "tap"
creator_ver = 0x10003 # match vhd-util
creator_os = 0 # match vhd-util
timestamp = 0
creator_app = b"tap"
creator_ver = 0x10003 # match vhd-util
creator_os = 0 # match vhd-util
orig_size = insize
curr_size = insize
(disk_c, disk_h, disk_s) = vhd_chs(curr_size)
disk_type = 3 # Dynamic
checksum = 0 # calculated later
my_uuid = uuid.uuid4().get_bytes()
saved_state= 0
disk_type = 3 # Dynamic
checksum = 0 # calculated later
my_uuid = uuid.uuid4().bytes
saved_state = 0
reserved = zerostring(427)

header_vals = [ cookie, features, fmt_version, data_offset, timestamp,
creator_app, creator_ver, creator_os, orig_size, curr_size, disk_c, disk_h,
disk_s, disk_type, checksum, my_uuid, saved_state, reserved ]
header_vals = [
cookie, features, fmt_version, data_offset, timestamp, creator_app,
creator_ver, creator_os, orig_size, curr_size, disk_c, disk_h, disk_s,
disk_type, checksum, my_uuid, saved_state, reserved
]

header = struct.pack(HEADER_FMT, *tuple(header_vals))

Expand All @@ -250,30 +259,31 @@ def do_vhd_convert(infile, outfile):

final_header = struct.pack(HEADER_FMT, *tuple(header_vals))

cookie2 = "cxsparse"
data_offset2 = 0xFFFFFFFFFFFFFFFF
cookie2 = b"cxsparse"
data_offset2 = 0xFFFFFFFFFFFFFFFF
table_offset = 1536
header_version = 0x00010000 # match vhd-util
header_version = 0x00010000 # match vhd-util
max_table_entries = bat_entries
block_size = VHD_BLOCKSIZE
checksum2 = 0 # calculated later
parent_uuid=zerostring(16)
checksum2 = 0 # calculated later
parent_uuid = zerostring(16)
parent_timestamp = 0
reserved2 = 0
parent_name = zerostring(512)
parent_locents = zerostring(192)
reserved3 = zerostring(256)

dyn_vals = [ cookie2, data_offset2, table_offset, header_version,
max_table_entries, block_size, checksum2, parent_uuid, parent_timestamp,
reserved2, parent_name, parent_locents, reserved3 ]
dyn_vals = [
cookie2, data_offset2, table_offset, header_version, max_table_entries,
block_size, checksum2, parent_uuid, parent_timestamp, reserved2,
parent_name, parent_locents, reserved3
]

dyn_header = struct.pack(DYNAMIC_FMT, *tuple(dyn_vals))
checksum2 = vhd_checksum(dyn_header)
dyn_vals[6] = checksum2
final_dyn_header = struct.pack(DYNAMIC_FMT, *tuple(dyn_vals))


outfile.write(final_header)
outfile.write(final_dyn_header)
outfile.write(bat)
Expand All @@ -283,22 +293,23 @@ def do_vhd_convert(infile, outfile):
outfile.write(batmap)

for block_start in bat_values:
outfile.seek(block_start * SECTORSIZE)
outfile.write(FULL_SECTOR_BITMAP)
outfile.seek( (SECTOR_BITMAP_SECTORS + block_start) * SECTORSIZE)
outfile.write(infile.read(VHD_BLOCKSIZE))

outfile.seek( (block_start + SECTOR_BITMAP_SECTORS + VHD_BLOCKSIZE_SECTORS) * SECTORSIZE)
outfile.seek(block_start * SECTORSIZE)
outfile.write(FULL_SECTOR_BITMAP)
outfile.seek((SECTOR_BITMAP_SECTORS + block_start) * SECTORSIZE)
outfile.write(infile.read(VHD_BLOCKSIZE))

outfile.seek(
(block_start + SECTOR_BITMAP_SECTORS + VHD_BLOCKSIZE_SECTORS) *
SECTORSIZE)
outfile.write(final_header)


if __name__ == '__main__':
if len(sys.argv) != 3:
print "usage: %s <raw_input_file> <vhd_output_file>" % sys.argv[0]
print("usage: %s <raw_input_file> <vhd_output_file>" % sys.argv[0])
sys.exit(1)
infile = open(sys.argv[1], "r")
outfile = open(sys.argv[2], "w")
infile = open(sys.argv[1], "rb")
outfile = open(sys.argv[2], "wb")
do_vhd_convert(infile, outfile)
infile.close()
outfile.close()


22 changes: 22 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import setuptools

with open("README.md", "r") as fh:
long_description = fh.read()

setuptools.setup(
name="pyvhd",
version="0.0.1",
author="Ian McLeod",
author_email="[email protected]",
description="Pure python code to create dynamic VHD files for Xen/Xenserver imports",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/lungj/pyvhd",
packages=setuptools.find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)",
"Operating System :: OS Independent",
],
python_requires='>=3.2',
)
Empty file added tests/.keep
Empty file.