Skip to content

Commit

Permalink
fix: use EFI spec mnemonics for attribute names
Browse files Browse the repository at this point in the history
  • Loading branch information
swysocki committed Jul 9, 2022
1 parent b45114d commit 0e62e24
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 88 deletions.
12 changes: 6 additions & 6 deletions gpt_image/disk.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def open(self):
+ self.geometry.header_length
]
backup_header_b = disk_bytes[
self.geometry.backup_header_byte : self.geometry.backup_header_byte
self.geometry.alternate_header_byte : self.geometry.alternate_header_byte
+ self.geometry.header_length
]
self.table.primary_header = Header(self.geometry)
Expand All @@ -47,11 +47,11 @@ def open(self):
# read the partition tables
primary_part_table_b = disk_bytes[
self.geometry.primary_array_byte : self.geometry.primary_array_byte
+ self.geometry.array_length
+ self.geometry.array_max_length
]
backup_part_table_b = disk_bytes[
self.geometry.backup_array_byte : self.geometry.backup_array_byte
+ self.geometry.array_length
self.geometry.alternate_array_byte : self.geometry.alternate_array_byte
+ self.geometry.array_max_length
]
if primary_part_table_b != backup_part_table_b:
raise TableReadError("primary and backup table do not match")
Expand Down Expand Up @@ -105,11 +105,11 @@ def write(self):
f.write(self.table.partitions.byte_structure)

# move to secondary header location and write
f.seek(self.geometry.backup_header_byte)
f.seek(self.geometry.alternate_header_byte)
f.write(self.table.secondary_header.byte_structure)

# write secondary partition table
f.seek(self.geometry.backup_array_byte)
f.seek(self.geometry.alternate_array_byte)
f.write(self.table.partitions.byte_structure)

def write_data(self, data: bytes, partition: Partition, offset: int = 0) -> None:
Expand Down
41 changes: 21 additions & 20 deletions gpt_image/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,25 @@ class Geometry:
"""Geometry of disk image
This is a convenience class that provides geometry calculations for
an underlying disk
Attributes:
sector_size: typically set to 512 bytes
total_bytes: disk size in bytes
total_sectors: number of sectors on the disk
total_lba: number of logical blocks on the disk
header_length: 92 bytes
array_length: partition array length in bytes (128*128)
partition_start_lba: logical block where the partitions start
partition_last_lba: logical block where the partitions end
primary_header_lba: logical block location of primary header
array_max_length: maximum partition array length in bytes (128*128)
first_usable_lba: logical block where the partitions start
last_usable_lba: logical block where the partitions end
my_lba: logical block location of primary header
primary_header_byte: byte where primary header starts
primary_array_lba: logical block where the primary partition array starts
partition_entry_lba: logical block where the primary partition array starts
primary_array_byte: byte where the primary partition array starts
backup_header_lba: logical block where the backup header starts
backup_header_byte: byte where the backup header starts
backup_array_lba: logical block where backup partition array starts
backup_array_byte: byte where the backup partition array starts
alternate_lba: logical block where the backup header starts
alternate_header_byte: byte where the backup header starts
alternate_array_lba: logical block where backup partition array starts
alternate_array_byte: byte where the backup partition array starts
"""

def __init__(self, size: int, sector_size: int = 512) -> None:
Expand All @@ -29,14 +30,14 @@ def __init__(self, size: int, sector_size: int = 512) -> None:
self.total_sectors = int(size / self.sector_size)
self.total_lba = int(size / self.sector_size)
self.header_length = 92
self.array_length = 128 * 128
self.partition_start_lba = 34
self.partition_last_lba = int(self.total_lba - 34)
self.primary_header_lba = 1
self.primary_header_byte = int(self.primary_header_lba * self.sector_size)
self.primary_array_lba = 2
self.primary_array_byte = int(self.primary_array_lba * self.sector_size)
self.backup_header_lba = int(self.total_lba - 1)
self.backup_header_byte = int(self.backup_header_lba * self.sector_size)
self.backup_array_lba = int(self.total_lba - 33)
self.backup_array_byte = int(self.backup_array_lba * self.sector_size)
self.array_max_length = 128 * 128
self.first_usable_lba = 34
self.last_usable_lba = int(self.total_lba - 34)
self.my_lba = 1
self.primary_header_byte = int(self.my_lba * self.sector_size)
self.partition_entry_lba = 2
self.primary_array_byte = int(self.partition_entry_lba * self.sector_size)
self.alternate_lba = int(self.total_lba - 1)
self.alternate_header_byte = int(self.alternate_lba * self.sector_size)
self.alternate_array_lba = int(self.total_lba - 33)
self.alternate_array_byte = int(self.alternate_array_lba * self.sector_size)
78 changes: 39 additions & 39 deletions gpt_image/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def __init__(self, geometry: Geometry):
self.start_chs = Entry(447, 3, 0) # ignore the start CHS
self.partition_type = Entry(450, 1, b"\xEE") # GPT partition type
self.end_chs = Entry(451, 3, 0) # ignore the end CHS
self.start_sector = Entry(454, 4, geometry.primary_header_lba)
self.start_sector = Entry(454, 4, geometry.my_lba)
# size, minus the protective MBR sector. This only works if the
# disk is under 2 TB
self.partition_size = Entry(458, 4, geometry.total_sectors - 1)
Expand Down Expand Up @@ -87,32 +87,32 @@ def __init__(
self.backup = is_backup

# all header values start with just offset and length, data is 0
self.header_sig = Entry(0, 8, b"EFI PART")
self.signature = Entry(0, 8, b"EFI PART")

self.revision = Entry(8, 4, b"\x00\x00\x01\x00")

self.header_size = Entry(12, 4, 92)
self.header_crc = Entry(16, 4, 0)
self.header_crc32 = Entry(16, 4, 0)
self.reserved = Entry(20, 4, 0)
self.primary_header_lba = Entry(24, 8, self.geometry.primary_header_lba)
self.my_lba = Entry(24, 8, self.geometry.my_lba)

self.secondary_header_lba = Entry(32, 8, self.geometry.backup_header_lba)
self.partition_start_lba = Entry(40, 8, self.geometry.partition_start_lba)
self.alternate_lba = Entry(32, 8, self.geometry.alternate_lba)
self.first_usable_lba = Entry(40, 8, self.geometry.first_usable_lba)

self.partition_last_lba = Entry(48, 8, self.geometry.partition_last_lba)
self.last_usable_lba = Entry(48, 8, self.geometry.last_usable_lba)
self.disk_guid = Entry(56, 16, self.guid.bytes_le)
self.partition_array_start = Entry(72, 8, self.geometry.primary_array_lba)
self.partition_array_length = Entry(80, 4, 128)
self.partition_entry_size = Entry(84, 4, 128)
self.partition_array_crc = Entry(88, 4, 0)
self.partition_entry_lba = Entry(72, 8, self.geometry.partition_entry_lba)
self.number_of_partition_entries = Entry(80, 4, 128)
self.size_of_partition_entries = Entry(84, 4, 128)
self.partition_entry_array_crc32 = Entry(88, 4, 0)
self.reserved_padding = Entry(92, 420, 0)

if self.backup:
self.primary_header_lba.data, self.secondary_header_lba.data = (
self.secondary_header_lba.data,
self.primary_header_lba.data,
self.my_lba.data, self.alternate_lba.data = (
self.alternate_lba.data,
self.my_lba.data,
)
self.partition_array_start.data = (self.geometry.backup_array_lba).to_bytes(
self.partition_entry_lba.data = (self.geometry.alternate_array_lba).to_bytes(
8, "little"
)

Expand All @@ -128,20 +128,20 @@ def __init__(
def byte_structure(self) -> bytes:
"""Convert the Header object to its byte structure"""
header_fields = [
self.header_sig,
self.signature,
self.revision,
self.header_size,
self.header_crc,
self.header_crc32,
self.reserved,
self.primary_header_lba,
self.secondary_header_lba,
self.partition_start_lba,
self.partition_last_lba,
self.my_lba,
self.alternate_lba,
self.first_usable_lba,
self.last_usable_lba,
self.disk_guid,
self.partition_array_start,
self.partition_array_length,
self.partition_entry_size,
self.partition_array_crc,
self.partition_entry_lba,
self.number_of_partition_entries,
self.size_of_partition_entries,
self.partition_entry_array_crc32,
]
byte_list = [x.data_bytes for x in header_fields]
return b"".join(byte_list)
Expand All @@ -152,29 +152,29 @@ def read(self, header_bytes: bytes) -> None:
def _to_int(field: Entry):
return int.from_bytes(header_bytes[field.offset : field.end], "little")

self.header_sig.data = header_bytes[
self.header_sig.offset : self.header_sig.offset + self.header_sig.length
self.signature.data = header_bytes[
self.signature.offset : self.signature.offset + self.signature.length
]
self.revision.data = header_bytes[
self.revision.offset : self.revision.offset + self.revision.length
]
self.header_size.data = _to_int(self.header_size)
self.header_crc.data = _to_int(self.header_crc)
self.header_crc32.data = _to_int(self.header_crc32)
self.reserved.data = _to_int(self.reserved)
self.primary_header_lba.data = _to_int(self.primary_header_lba)
self.secondary_header_lba.data = _to_int(self.secondary_header_lba)
self.partition_start_lba.data = _to_int(self.partition_start_lba)
self.partition_last_lba.data = _to_int(self.partition_last_lba)
self.my_lba.data = _to_int(self.my_lba)
self.alternate_lba.data = _to_int(self.alternate_lba)
self.first_usable_lba.data = _to_int(self.first_usable_lba)
self.last_usable_lba.data = _to_int(self.last_usable_lba)
# stored as little endian bytes. This will need to be converted to display in
# human readable form
# uuid.UUID(bytes=self.disk_guid.data)
self.disk_guid.data = header_bytes[
self.disk_guid.offset : self.disk_guid.offset + self.disk_guid.length
]
self.partition_array_start.data = _to_int(self.partition_array_start)
self.partition_array_length.data = _to_int(self.partition_array_length)
self.partition_entry_size.data = _to_int(self.partition_entry_size)
self.partition_array_crc.data = _to_int(self.partition_array_crc)
self.partition_entry_lba.data = _to_int(self.partition_entry_lba)
self.number_of_partition_entries.data = _to_int(self.number_of_partition_entries)
self.size_of_partition_entries.data = _to_int(self.size_of_partition_entries)
self.partition_entry_array_crc32.data = _to_int(self.partition_entry_array_crc32)


class Table:
Expand Down Expand Up @@ -207,7 +207,7 @@ def checksum_partitions(self, header: Header) -> None:
header: initialized GPT header object
"""
part_entry_bytes = self.partitions.byte_structure
header.partition_array_crc.data = binascii.crc32(part_entry_bytes)
header.partition_entry_array_crc32.data = binascii.crc32(part_entry_bytes)

def checksum_header(self, header: Header) -> None:
"""Checksum the table header
Expand All @@ -219,5 +219,5 @@ def checksum_header(self, header: Header) -> None:
header: initialized GPT header object
"""
# zero the old checksum before calculating
header.header_crc.data = 0
header.header_crc.data = binascii.crc32(header.byte_structure)
header.header_crc32.data = 0
header.header_crc32.data = binascii.crc32(header.byte_structure)
6 changes: 3 additions & 3 deletions tests/test_disk.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_disk_create(tmp_path):
assert disk.image_path == disk_path.resolve()
assert disk.sector_size == 512
disk.create(DISK_SIZE)
assert disk.table.primary_header.header_sig.data == b"EFI PART"
assert disk.table.primary_header.signature.data == b"EFI PART"
assert disk.table.primary_header.backup is False


Expand All @@ -37,8 +37,8 @@ def test_disk_open(new_image):
assert disk.size == DISK_SIZE * 2
assert disk.table.primary_header.backup is False
assert disk.table.secondary_header.backup is True
assert disk.table.primary_header.partition_array_start.data == 2
assert disk.table.secondary_header.partition_array_start.data == 8159
assert disk.table.primary_header.partition_entry_lba.data == 2
assert disk.table.secondary_header.partition_entry_lba.data == 8159
assert len(disk.table.partitions.entries) == 2
assert disk.table.partitions.entries[0].partition_name.data == "partition1"

Expand Down
10 changes: 5 additions & 5 deletions tests/test_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ def test_default_geometry():
geo = Geometry(disk_size)
assert geo.total_sectors == 4096
assert geo.total_lba == 4096
assert geo.partition_last_lba == 4062
assert geo.last_usable_lba == 4062
assert geo.primary_header_byte == 512
assert geo.primary_array_byte == 1024
assert geo.backup_header_lba == 4095
assert geo.backup_header_byte == 2096640
assert geo.backup_array_lba == 4063
assert geo.backup_array_byte == 2080256
assert geo.alternate_lba == 4095
assert geo.alternate_header_byte == 2096640
assert geo.alternate_array_lba == 4063
assert geo.alternate_array_byte == 2080256
30 changes: 15 additions & 15 deletions tests/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def test_protective_mbr_init(new_geometry):
assert pmbr_bytes[pmbr.partition_type.offset : pmbr.partition_type.end] == b"\xEE"
assert pmbr_bytes[
pmbr.start_sector.offset : pmbr.start_sector.end
] == geo.primary_header_lba.to_bytes(4, "little")
] == geo.my_lba.to_bytes(4, "little")
assert pmbr_bytes[pmbr.partition_size.offset : pmbr.partition_size.end] == (
geo.total_sectors - 1
).to_bytes(4, "little")
Expand All @@ -40,18 +40,18 @@ def test_header_init_primary(new_geometry):
geo = new_geometry
head = Header(geo, uuid.uuid4())
assert head.backup is False
assert head.primary_header_lba.data == geo.primary_header_lba
assert head.secondary_header_lba.data == geo.backup_header_lba
assert head.partition_array_start.data == geo.primary_array_lba
assert head.my_lba.data == geo.my_lba
assert head.alternate_lba.data == geo.alternate_lba
assert head.partition_entry_lba.data == geo.partition_entry_lba


def test_header_init_backup(new_geometry):
geo = new_geometry
head = Header(geo, uuid.uuid4(), is_backup=True)
assert head.backup is True
# ensure the header LBA has been swapped
assert head.primary_header_lba.data == geo.backup_header_lba
assert head.secondary_header_lba.data == geo.primary_header_lba
assert head.my_lba.data == geo.alternate_lba
assert head.alternate_lba.data == geo.my_lba
assert head.partition_entry_start_byte == 0
assert head.header_start_byte == int(32 * geo.sector_size)

Expand All @@ -62,7 +62,7 @@ def test_header_read(new_geometry):

head_ex = Header(new_geometry)
head_ex.read(head_b)
assert head_ex.header_sig.data == head.header_sig.data
assert head_ex.signature.data == head.signature.data
assert head_ex.revision.data == head.revision.data
assert head_ex.revision.data == head.revision.data
assert head_ex.disk_guid.data == head.disk_guid.data
Expand All @@ -78,27 +78,27 @@ def test_table_init(new_geometry):
def test_checksum_partitions(new_geometry):
table = Table(new_geometry)
# partition checksum will be blank before when initialized
assert table.primary_header.partition_array_crc.data == 0
assert table.primary_header.partition_entry_array_crc32.data == 0
table.checksum_partitions(table.primary_header)
# test that the checksum is no longer zeroed
assert (
table.primary_header.partition_array_crc.data == 2874462854
table.primary_header.partition_entry_array_crc32.data == 2874462854
) # predictable binascii.crc32


def test_checksum_header(new_geometry):
table = Table(new_geometry)
assert table.primary_header.header_crc.data == 0
assert table.primary_header.header_crc32.data == 0
table.checksum_header(table.primary_header)
# @TODO: (sjw) create header with static UUID
# the crc is not predictable because the Header UUID is dynamic
assert table.primary_header.header_crc.data > 0
assert table.primary_header.header_crc32.data > 0


def test_update(new_geometry):
table = Table(new_geometry)
table.update()
assert table.primary_header.header_crc != b"\x00" * 4
assert table.secondary_header.header_crc != b"\x00" * 4
assert table.primary_header.partition_array_crc != b"\x00" * 4
assert table.secondary_header.partition_array_crc != b"\x00" * 4
assert table.primary_header.header_crc32 != b"\x00" * 4
assert table.secondary_header.header_crc32 != b"\x00" * 4
assert table.primary_header.partition_entry_array_crc32 != b"\x00" * 4
assert table.secondary_header.partition_entry_array_crc32 != b"\x00" * 4

0 comments on commit 0e62e24

Please sign in to comment.