diff --git a/dissect/hypervisor/disk/vmdk.py b/dissect/hypervisor/disk/vmdk.py index 590f8c7..394ebc0 100644 --- a/dissect/hypervisor/disk/vmdk.py +++ b/dissect/hypervisor/disk/vmdk.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import ctypes import io import logging @@ -7,7 +5,6 @@ import textwrap import zlib from bisect import bisect_right -from dataclasses import dataclass from functools import lru_cache from pathlib import Path @@ -62,13 +59,13 @@ def __init__(self, fh): if self.descriptor.attr["parentCID"] != "ffffffff": self.parent = open_parent(path.parent, self.descriptor.attr["parentFileNameHint"]) - for extent in self.descriptor.extents: - if extent.type in ["SPARSE", "VMFSSPARSE", "SESPARSE"]: - sdisk_fh = path.with_name(extent.filename).open("rb") + for _, size, extent_type, filename in self.descriptor.extents: + if extent_type in ["SPARSE", "VMFSSPARSE", "SESPARSE"]: + sdisk_fh = path.with_name(filename).open("rb") self.disks.append(SparseDisk(sdisk_fh, parent=self.parent)) - elif extent.type in ["VMFS", "FLAT"]: - rdisk_fh = path.with_name(extent.filename).open("rb") - self.disks.append(RawDisk(rdisk_fh, extent.sectors * SECTOR_SIZE)) + elif extent_type in ["VMFS", "FLAT"]: + rdisk_fh = path.with_name(filename).open("rb") + self.disks.append(RawDisk(rdisk_fh, size * SECTOR_SIZE)) elif magic in (COWD_MAGIC, VMDK_MAGIC, SESPARSE_MAGIC): sparse_disk = SparseDisk(fh) @@ -401,37 +398,8 @@ def __getattr__(self, attr): return getattr(self.hdr, attr) -@dataclass -class ExtentDescriptor: - access_mode: str - sectors: int - type: str - filename: str | None = None - start_sector: int | None = None - partition_uuid: str | None = None - device_identifier: str | None = None - - def __post_init__(self) -> None: - self._raw = " ".join(map(str, [v for v in self.__dict__.values() if v is not None])) - self.sectors = int(self.sectors) - - if self.filename: - self.filename = self.filename.strip('"') - - if self.start_sector: - self.start_sector = int(self.start_sector) - - def __repr__(self) -> str: - return f"" - - def __str__(self) -> str: - return self._raw - - class DiskDescriptor: - def __init__( - self, attr: dict, extents: list[ExtentDescriptor], disk_db: dict, sectors: int, raw_config: str | None = None - ): + def __init__(self, attr, extents, disk_db, sectors, raw_config=None): self.attr = attr self.extents = extents self.ddb = disk_db @@ -439,15 +407,9 @@ def __init__( self.raw = raw_config @classmethod - def parse(cls, vmdk_config: str) -> DiskDescriptor: - """Return :class:`DiskDescriptor` based on the provided ``vmdk_config``. - - Resources: - - https://github.com/libyal/libvmdk/blob/main/documentation/VMWare%20Virtual%20Disk%20Format%20(VMDK).asciidoc - """ # noqa: E501 - + def parse(cls, vmdk_config): descriptor_settings = {} - extents: list[ExtentDescriptor] = [] + extents = [] disk_db = {} sectors = 0 @@ -458,16 +420,11 @@ def parse(cls, vmdk_config: str) -> DiskDescriptor: continue if line.startswith("RW ") or line.startswith("RDONLY ") or line.startswith("NOACCESS "): - # Extent descriptors can have up to seven values according to libvmdk documentation. - parts = line.split(" ", maxsplit=6) - - if len(parts) < 3: - log.warning("Unexpected ExtentDescriptor format in vmdk config: %s, ignoring", line) - continue - - extent = ExtentDescriptor(*parts) - sectors += extent.sectors - extents.append(extent) + access_type, size, extent_type, filename = line.split(" ", 3) + filename = filename.strip('"') + size = int(size) + sectors += size + extents.append([access_type, size, extent_type, filename]) continue setting, _, value = line.partition("=") @@ -481,33 +438,35 @@ def parse(cls, vmdk_config: str) -> DiskDescriptor: return cls(descriptor_settings, extents, disk_db, sectors, vmdk_config) - def __str__(self) -> str: - str_template = textwrap.dedent( - """\ - # Disk DescriptorFile - version=1 - {} - - # Extent Description - {} + def __str__(self): + str_template = """\ + # Disk DescriptorFile + version=1 + {} - # The Disk Data Base - #DDB + # Extent Description + {} - {}""" - ) + # The Disk Data Base + #DDB + {}""" + str_template = textwrap.dedent(str_template) descriptor_settings = [] for setting, value in self.attr.items(): - if setting != "version": - descriptor_settings.append(f"{setting}={value}") + if setting == "version": + continue + descriptor_settings.append("{}={}".format(setting, value)) descriptor_settings = "\n".join(descriptor_settings) - extents = "\n".join(map(str, self.extents)) + extents = [] + for access_type, size, extent_type, filename in self.extents: + extents.append('{} {} {} "{}"'.format(access_type, size, extent_type, filename)) + extents = "\n".join(extents) disk_db = [] for setting, value in self.ddb.items(): - disk_db.append(f'{setting} = "{value}"') + disk_db.append('{} = "{}"'.format(setting, value)) disk_db = "\n".join(disk_db) return str_template.format(descriptor_settings, extents, disk_db) diff --git a/tests/test_vmdk.py b/tests/test_vmdk.py index 2a5cb34..de99aaa 100644 --- a/tests/test_vmdk.py +++ b/tests/test_vmdk.py @@ -1,7 +1,5 @@ -import pytest - from dissect.hypervisor.disk.c_vmdk import c_vmdk -from dissect.hypervisor.disk.vmdk import VMDK, DiskDescriptor, ExtentDescriptor +from dissect.hypervisor.disk.vmdk import VMDK def test_vmdk_sesparse(sesparse_vmdk): @@ -20,74 +18,3 @@ def test_vmdk_sesparse(sesparse_vmdk): assert header.version == 0x200000001 assert vmdk.read(0x1000000) == b"a" * 0x1000000 - - -@pytest.mark.parametrize( - "extent_description, expected_extents", - [ - ( - 'RW 123456789 SPARSE "disk.vmdk"', - [ - ExtentDescriptor( - access_mode="RW", - sectors=123456789, - type="SPARSE", - filename="disk.vmdk", - partition_uuid=None, - device_identifier=None, - ), - ], - ), - ( - 'RW 123456789 FLAT "disk-flat.vmdk" 0', - [ - ExtentDescriptor( - access_mode="RW", - sectors=123456789, - type="FLAT", - filename="disk-flat.vmdk", - start_sector=0, - partition_uuid=None, - device_identifier=None, - ) - ], - ), - ( - "RDONLY 0 ZERO", - [ - ExtentDescriptor( - access_mode="RDONLY", - sectors=0, - type="ZERO", - ), - ], - ), - ( - 'NOACCESS 123456789 SPARSE "disk-sparse.vmdk" 123 partition-uuid device-id', - [ - ExtentDescriptor( - access_mode="NOACCESS", - sectors=123456789, - type="SPARSE", - filename="disk-sparse.vmdk", - start_sector=123, - partition_uuid="partition-uuid", - device_identifier="device-id", - ), - ], - ), - ("RW 1234567890", []), - ('RDONLY "file.vmdk"', []), - ("NOACCESS", []), - ], - ids=("sparse", "flat", "zero", "sparse-ids", "bad-1", "bad-2", "bad-3"), -) -def test_vmdk_extent_description(extent_description: str, expected_extents: list) -> None: - """test if we correctly parse VMDK sparse and flat extent descriptions. - - Resources: - - https://github.com/libyal/libvmdk/blob/main/documentation/VMWare%20Virtual%20Disk%20Format%20(VMDK).asciidoc#22-extent-descriptions - """ # noqa: E501 - - descriptor = DiskDescriptor.parse(extent_description) - assert descriptor.extents == expected_extents