diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9b4f63f --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +tests/_data/** filter=lfs diff=lfs merge=lfs -text diff --git a/MANIFEST.in b/MANIFEST.in index 9ae349b..23519f8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,4 @@ +exclude .gitattributes exclude .gitignore recursive-exclude .github/ * +recursive-exclude tests/_data/ * diff --git a/dissect/hypervisor/disk/c_vdi.py b/dissect/hypervisor/disk/c_vdi.py index e57c0d6..2e3d69e 100644 --- a/dissect/hypervisor/disk/c_vdi.py +++ b/dissect/hypervisor/disk/c_vdi.py @@ -2,90 +2,97 @@ from dissect.cstruct import cstruct -# https://www.virtualbox.org/browser/vbox/trunk/src/VBox/Storage/VDICore.h -# https://forums.virtualbox.org/viewtopic.php?t=8046 -# 0000 3C 3C 3C 20 53 75 6E 20 78 56 4D 20 56 69 72 74 <<< Sun xVM Virt -# 0010 75 61 6C 42 6F 78 20 44 69 73 6B 20 49 6D 61 67 ualBox Disk Imag -# 0020 65 20 3E 3E 3E 0A 00 00 00 00 00 00 00 00 00 00 e >>> -# 0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# -# 0040 7F 10 DA BE Image Signature -# 01 00 01 00 Version 1.1 -# 90 01 00 00 Size of Header(0x190) -# 01 00 00 00 Image Type (Dynamic VDI) -# 0050 00 00 00 00 Image Flags -# 00 00 00 00 00 00 00 00 00 00 00 00 Image Description -# 0060-001F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 0150 00 00 00 00 -# 00 02 00 00 offsetBlocks -# 00 20 00 00 offsetData -# 00 00 00 00 #Cylinders (0) -# 0160 00 00 00 00 #Heads (0) -# 00 00 00 00 #Sectors (0) -# 00 02 00 00 SectorSize (512) -# 00 00 00 00 -- unused -- -# 0170 00 00 00 78 00 00 00 00 DiskSize (Bytes) -# 00 00 10 00 BlockSize -# 00 00 00 00 Block Extra Data (0) -# 0180 80 07 00 00 #BlocksInHDD -# 0B 02 00 00 #BlocksAllocated -# 5A 08 62 27 A8 B6 69 44 UUID of this VDI -# 0190 A1 57 E2 B2 43 A5 8F CB -# 0C 5C B1 E3 C5 73 ED 40 UUID of last SNAP -# 01A0 AE F7 06 D6 20 69 0C 96 -# 00 00 00 00 00 00 00 00 UUID link -# 01B0 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 UUID Parent -# 01C0 00 00 00 00 00 00 00 00 -# CF 03 00 00 00 00 00 00 -- garbage / unused -- -# 01D0 3F 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 -- garbage / unused -- -# 01E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -- unused -- -# 01F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -- unused -- - +# https://github.com/VirtualBox/virtualbox/blob/main/src/VBox/Storage/VDICore.h vdi_def = """ -enum ImageType : uint32 { - Dynamic = 0x01, - Fixed = 0x02, - Undo = 0x03, - Differencing = 0x04 +enum VDI_IMAGE_TYPE { + /** Normal dynamically growing base image file. */ + NORMAL = 1, + /** Preallocated base image file of a fixed size. */ + FIXED, + /** Dynamically growing image file for undo/commit changes support. */ + UNDO, + /** Dynamically growing image file for differencing support. */ + DIFF, }; -flag ImageFlags : uint32 { - None = 0x00000000, - Split2G = 0x00000001, - ZeroExpand = 0x00000002 +flag VDI_IMAGE_FLAGS { + /** Fill new blocks with zeroes while expanding image file. Only valid + * for newly created images, never set for opened existing images. */ + ZERO_EXPAND = 0x0100, }; -struct HeaderDescriptor { - char FileInfo[64]; - uint32 Signature; - uint32 Version; - uint32 HeaderSize; - ImageType ImageType; - ImageFlags ImageFlags; - char ImageDescription[256]; - uint32 BlocksOffset; - uint32 DataOffset; - uint32 NumCylinders; - uint32 NumHeads; - uint32 NumSectors; - uint32 SectorSize; - uint32 Unused1; - uint64 DiskSize; - uint32 BlockSize; - uint32 BlockExtraData; - uint32 BlocksInHDD; - uint32 BlocksAllocated; - char UUIDVDI[16]; - char UUIDSNAP[16]; - char UUIDLink[16]; - char UUIDParent[16]; -}; +typedef struct VDIDISKGEOMETRY { + /** Cylinders. */ + uint32_t Cylinders; + /** Heads. */ + uint32_t Heads; + /** Sectors per track. */ + uint32_t Sectors; + /** Sector size. (bytes per sector) */ + uint32_t Sector; +} VDIDISKGEOMETRY, *PVDIDISKGEOMETRY; + +typedef struct VDIPREHEADER { + /** Just text info about image type, for eyes only. */ + char szFileInfo[64]; + /** The image signature (VDI_IMAGE_SIGNATURE). */ + uint32_t u32Signature; + /** The image version (VDI_IMAGE_VERSION). */ + uint32_t u32Version; +} VDIPREHEADER, *PVDIPREHEADER; + +/** + * Size of Comment field of HDD image header. + */ +#define VDI_IMAGE_COMMENT_SIZE 256 + +/* NOTE: All the header versions are additive, so just use the latest one. */ +typedef struct VDIHEADER1PLUS { + /** Size of this structure in bytes. */ + uint32_t cbHeader; + /** The image type (VDI_IMAGE_TYPE_*). */ + VDI_IMAGE_TYPE u32Type; + /** Image flags (VDI_IMAGE_FLAGS_*). */ + VDI_IMAGE_FLAGS fFlags; + /** Image comment. (UTF-8) */ + char szComment[VDI_IMAGE_COMMENT_SIZE]; + /** Offset of blocks array from the beginning of image file. + * Should be sector-aligned for HDD access optimization. */ + uint32_t offBlocks; + /** Offset of image data from the beginning of image file. + * Should be sector-aligned for HDD access optimization. */ + uint32_t offData; + /** Legacy image geometry (previous code stored PCHS there). */ + VDIDISKGEOMETRY LegacyGeometry; + /** Was BIOS HDD translation mode, now unused. */ + uint32_t u32Dummy; + /** Size of disk (in bytes). */ + uint64_t cbDisk; + /** Block size. (For instance VDI_IMAGE_BLOCK_SIZE.) Should be a power of 2! */ + uint32_t cbBlock; + /** Size of additional service information of every data block. + * Prepended before block data. May be 0. + * Should be a power of 2 and sector-aligned for optimization reasons. */ + uint32_t cbBlockExtra; + /** Number of blocks. */ + uint32_t cBlocks; + /** Number of allocated blocks. */ + uint32_t cBlocksAllocated; + /** UUID of image. */ + char uuidCreate[16]; + /** UUID of image's last modification. */ + char uuidModify[16]; + /** Only for secondary images - UUID of previous image. */ + char uuidLinkage[16]; + /** Only for secondary images - UUID of previous image's last modification. */ + char uuidParentModify[16]; + /** LCHS image geometry (new field in VDI1.2 version. */ + VDIDISKGEOMETRY Geometry; +} VDIHEADER1PLUS, *PVDIHEADER1PLUS; """ c_vdi = cstruct().load(vdi_def) -VDI_SIGNATURE = 0xBEDA107F - -UNALLOCATED = -1 -SPARSE = -2 +VDI_IMAGE_SIGNATURE = 0xBEDA107F +VDI_IMAGE_BLOCK_FREE = ~0 +VDI_IMAGE_BLOCK_ZERO = ~1 diff --git a/dissect/hypervisor/disk/c_vdi.pyi b/dissect/hypervisor/disk/c_vdi.pyi new file mode 100644 index 0000000..ee72da8 --- /dev/null +++ b/dissect/hypervisor/disk/c_vdi.pyi @@ -0,0 +1,100 @@ +# Generated by cstruct-stubgen +from typing import BinaryIO, Literal, TypeAlias, overload + +import dissect.cstruct as __cs__ + +class _c_vdi(__cs__.cstruct): + VDI_IMAGE_COMMENT_SIZE: Literal[256] = ... + class VDI_IMAGE_TYPE(__cs__.Enum): + NORMAL = ... + FIXED = ... + UNDO = ... + DIFF = ... + + class VDI_IMAGE_FLAGS(__cs__.Flag): + ZERO_EXPAND = ... + + class VDIDISKGEOMETRY(__cs__.Structure): + Cylinders: _c_vdi.uint32 + Heads: _c_vdi.uint32 + Sectors: _c_vdi.uint32 + Sector: _c_vdi.uint32 + @overload + def __init__( + self, + Cylinders: _c_vdi.uint32 | None = ..., + Heads: _c_vdi.uint32 | None = ..., + Sectors: _c_vdi.uint32 | None = ..., + Sector: _c_vdi.uint32 | None = ..., + ): ... + @overload + def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ... + + PVDIDISKGEOMETRY: TypeAlias = __cs__.Pointer[_c_vdi.VDIDISKGEOMETRY] + class VDIPREHEADER(__cs__.Structure): + szFileInfo: __cs__.CharArray + u32Signature: _c_vdi.uint32 + u32Version: _c_vdi.uint32 + @overload + def __init__( + self, + szFileInfo: __cs__.CharArray | None = ..., + u32Signature: _c_vdi.uint32 | None = ..., + u32Version: _c_vdi.uint32 | None = ..., + ): ... + @overload + def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ... + + PVDIPREHEADER: TypeAlias = __cs__.Pointer[_c_vdi.VDIPREHEADER] + class VDIHEADER1PLUS(__cs__.Structure): + cbHeader: _c_vdi.uint32 + u32Type: _c_vdi.VDI_IMAGE_TYPE + fFlags: _c_vdi.VDI_IMAGE_FLAGS + szComment: __cs__.CharArray + offBlocks: _c_vdi.uint32 + offData: _c_vdi.uint32 + LegacyGeometry: _c_vdi.VDIDISKGEOMETRY + u32Dummy: _c_vdi.uint32 + cbDisk: _c_vdi.uint64 + cbBlock: _c_vdi.uint32 + cbBlockExtra: _c_vdi.uint32 + cBlocks: _c_vdi.uint32 + cBlocksAllocated: _c_vdi.uint32 + uuidCreate: __cs__.CharArray + uuidModify: __cs__.CharArray + uuidLinkage: __cs__.CharArray + uuidParentModify: __cs__.CharArray + Geometry: _c_vdi.VDIDISKGEOMETRY + @overload + def __init__( + self, + cbHeader: _c_vdi.uint32 | None = ..., + u32Type: _c_vdi.VDI_IMAGE_TYPE | None = ..., + fFlags: _c_vdi.VDI_IMAGE_FLAGS | None = ..., + szComment: __cs__.CharArray | None = ..., + offBlocks: _c_vdi.uint32 | None = ..., + offData: _c_vdi.uint32 | None = ..., + LegacyGeometry: _c_vdi.VDIDISKGEOMETRY | None = ..., + u32Dummy: _c_vdi.uint32 | None = ..., + cbDisk: _c_vdi.uint64 | None = ..., + cbBlock: _c_vdi.uint32 | None = ..., + cbBlockExtra: _c_vdi.uint32 | None = ..., + cBlocks: _c_vdi.uint32 | None = ..., + cBlocksAllocated: _c_vdi.uint32 | None = ..., + uuidCreate: __cs__.CharArray | None = ..., + uuidModify: __cs__.CharArray | None = ..., + uuidLinkage: __cs__.CharArray | None = ..., + uuidParentModify: __cs__.CharArray | None = ..., + Geometry: _c_vdi.VDIDISKGEOMETRY | None = ..., + ): ... + @overload + def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ... + + PVDIHEADER1PLUS: TypeAlias = __cs__.Pointer[_c_vdi.VDIHEADER1PLUS] + +# Technically `c_vdi` is an instance of `_c_vdi`, but then we can't use it in type hints +c_vdi: TypeAlias = _c_vdi + +VDI_IMAGE_SIGNATURE: int +VDI_IMAGE_BLOCK_FREE: int +VDI_IMAGE_BLOCK_ZERO: int diff --git a/dissect/hypervisor/disk/vdi.py b/dissect/hypervisor/disk/vdi.py index 7da92c3..7ccbccc 100644 --- a/dissect/hypervisor/disk/vdi.py +++ b/dissect/hypervisor/disk/vdi.py @@ -1,62 +1,133 @@ from __future__ import annotations -import array -from typing import BinaryIO +from pathlib import Path +from typing import TYPE_CHECKING, BinaryIO from dissect.util.stream import AlignedStream +from dissect.util.xmemoryview import xmemoryview -from dissect.hypervisor.disk.c_vdi import SPARSE, UNALLOCATED, VDI_SIGNATURE, c_vdi +from dissect.hypervisor.disk.c_vdi import VDI_IMAGE_BLOCK_FREE, VDI_IMAGE_BLOCK_ZERO, VDI_IMAGE_SIGNATURE, c_vdi from dissect.hypervisor.exceptions import Error +if TYPE_CHECKING: + from types import TracebackType + + from typing_extensions import Self -class VDI(AlignedStream): - def __init__(self, fh: BinaryIO, parent: VDI | None = None): - self.fh = fh - self.parent = parent - self.header = c_vdi.HeaderDescriptor(fh) - if self.header.Signature != VDI_SIGNATURE: - raise Error("Not a VDI header") +class VDI: + """VirtualBox Virtual Disk Image (VDI) implementation. - fh.seek(-1, 2) - self.file_size = fh.tell() + Args: + fh: File-like object or path of the VDI file. + parent: Optional file-like object for the parent disk (for differencing disks). + """ - self.fh.seek(self.header.BlocksOffset) + def __init__(self, fh: BinaryIO | Path, parent: BinaryIO | None = None): + if isinstance(fh, Path): + self.path = fh + self.fh = self.path.open("rb") + else: + self.path = None + self.fh = fh - mapbuf = self.fh.read(4 * self.header.BlocksInHDD) - self.map = array.array("i") - try: - self.map.frombytes(mapbuf) - except AttributeError: - self.map.fromstring(mapbuf) + self.parent = parent - self.data_offset = self.header.DataOffset - self.block_size = self.header.BlockSize - self.sector_size = self.header.SectorSize - super().__init__(size=self.header.DiskSize) + self.fh.seek(0) + self.preheader = c_vdi.VDIPREHEADER(self.fh) + if self.preheader.u32Signature != VDI_IMAGE_SIGNATURE: + print(self.preheader) + raise Error( + f"Invalid VDI signature, expected {VDI_IMAGE_SIGNATURE:#08X}, got {self.preheader.u32Signature:#08X}" + ) + + self.header = c_vdi.VDIHEADER1PLUS(self.fh) + + def __enter__(self) -> Self: + return self + + def __exit__( + self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None + ) -> None: + self.close() + + @property + def type(self) -> c_vdi.VDI_IMAGE_TYPE: + """The type of the VDI file.""" + return self.header.u32Type + + @property + def flags(self) -> c_vdi.VDI_IMAGE_FLAGS: + """The flags of the VDI file.""" + return self.header.fFlags + + @property + def size(self) -> int: + """The size of the virtual disk.""" + return self.header.cbDisk + + @property + def block_size(self) -> int: + """The size of each block in the VDI file.""" + return self.header.cbBlock + + @property + def data_offset(self) -> int: + """The offset to the data blocks.""" + return self.header.offData + + @property + def blocks_offset(self) -> int: + """The offset to the block allocation table.""" + return self.header.offBlocks + + @property + def number_of_blocks(self) -> int: + """The number of blocks in the VDI file.""" + return self.header.cBlocks + + def open(self) -> VDIStream: + """Open a stream to read from the VDI file.""" + return VDIStream(self) + + def close(self) -> None: + """Close the VDI file handle.""" + if self.path is not None: + self.fh.close() + + +class VDIStream(AlignedStream): + def __init__(self, vdi: VDI): + self.vdi = vdi + self.block_size = vdi.block_size + + self.fh = self.vdi.fh + self.fh.seek(self.vdi.blocks_offset) + self.map = xmemoryview(self.fh.read(4 * self.vdi.number_of_blocks), " bytes: - block_idx, block_offset = divmod(offset, self.block_size) + result = [] - bytes_read = [] + block_idx, offset_in_block = divmod(offset, self.block_size) while length > 0: - read_len = min(length, max(length, self.block_size)) + read_len = min(length, max(length, self.block_size - offset_in_block)) block = self.map[block_idx] - - if block == UNALLOCATED: - if self.parent: - bytes_read.append(self.parent._read(offset, read_len)) + if block == VDI_IMAGE_BLOCK_FREE: + if self.vdi.parent is not None: + self.vdi.parent.seek(offset) + result.append(self.vdi.parent.read(read_len)) else: - bytes_read.append(b"\x00" * read_len) - elif block == SPARSE: - bytes_read.append(b"\x00" * read_len) + result.append(b"\x00" * read_len) + elif block == VDI_IMAGE_BLOCK_ZERO: + result.append(b"\x00" * read_len) else: - self.fh.seek(self.data_offset + (block * self.block_size) + block_offset) - bytes_read.append(self.fh.read(read_len)) + self.fh.seek(self.vdi.data_offset + (block * self.block_size) + offset_in_block) + result.append(self.fh.read(read_len)) offset += read_len length -= read_len block_idx += 1 - return b"".join(bytes_read) + return b"".join(result) diff --git a/pyproject.toml b/pyproject.toml index 3ea276d..c7055c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ test = [ lint = [ "ruff==0.13.1", "vermin", + "typing_extensions", ] build = [ "build", diff --git a/tests/_data/descriptor/hyperv/test.VMRS b/tests/_data/descriptor/hyperv/test.VMRS index 60e2889..271e9b9 100644 Binary files a/tests/_data/descriptor/hyperv/test.VMRS and b/tests/_data/descriptor/hyperv/test.VMRS differ diff --git a/tests/_data/descriptor/hyperv/test.vmcx b/tests/_data/descriptor/hyperv/test.vmcx index d01244c..ed77a95 100644 Binary files a/tests/_data/descriptor/hyperv/test.vmcx and b/tests/_data/descriptor/hyperv/test.vmcx differ diff --git a/tests/_data/descriptor/vmx/encrypted.vmx b/tests/_data/descriptor/vmx/encrypted.vmx index dc0a5b2..60d2e46 100644 --- a/tests/_data/descriptor/vmx/encrypted.vmx +++ b/tests/_data/descriptor/vmx/encrypted.vmx @@ -1,4 +1,3 @@ -.encoding = "UTF-8" -displayName = "Encrypted VM" -encryption.keySafe = "vmware:key/list/(pair/(phrase/JTHVQF8%2fBHU%3d/pass2key%3dPBKDF2%2dHMAC%2dSHA%2d1%3acipher%3dAES%2d256%3arounds%3d10000%3asalt%3dLKX%2fScQY5xvee11wl%2fCJ6g%253d%253d,HMAC%2dSHA%2d1,fk3ZzS%2b0A23m73olcAhAOfG4UNh8cLqU%2bNBLc9rzwVgRGVuWJN8K9ODmQjJCI%2fsCsvX%2f0fDDcBenFHazXpikDev0fzRCRrRQRIXJmMYTDLSBACcAJFh0T6XIuX6BdLlLP5Exr4sHp9Bf%2b%2fK8oMNr7LL4v7Y%3d))" -encryption.data = "Ynv+XzAuYX3BOGTCcG0zDnTpuNiXdQxfWyGkHOaFPUBfdREnb9EKOJgdtpS1+W/xt/7ecw9m6OxkweQD1S054IIOqt7TisNZQ4QNXgnIwxXCS5biz3q/6De72orkMJjk0KFoRMsEnUPSE3gRg3s79Ck7cogfV9kjSQgbUMBZ1BwHjezN/8vRwEuSU6cFyIbx2HTbBMkKvEEMn+cbH6oNEguio+0FofXqXZkoLfAKZf2ZHtI8wvqv3VOlg9236pA2y4fc4/9LufkC/pAcqamfXvXnc5GrjsdzbePBq95jGp18hZL6aJuq62Pcn3w9GMAfpTIOI+mSNs4Mu1jRDkLtqqsJDFO0EUjY4p43g3GbTyLb7rUi8vLGo2/spz96sgKJUfSJ5SufW35WaK5pVvlXb6O+BcnfheMdGs5v0Tn5JAqnjwaBUMvKDqclG0t6c9RF1YPLyRQHq6TJVGmK+CRfKIH7nvZMI8PduFZnrpp6fpG1aAxUTumQnoPF2dJa4f9CLONnPrqdmZq2jPAB6Qwb4HbPpL6d5wOlB0HMNmf1pMARaq0X9VMTDo7jyWih1spSq7e+hR+/WqHfC5q29s/lzsRUYlxCIhMk2zkymPjWHpPg92/T2vmFYRiGNVGreoJWd+AqIHtpa4ppEMFozKMNhdV5S5GYRg1P42Oa+bmTx6AlDBkZlGNsDnAc4XZMTW8YhwSJ7shdMMf4Oyc6nf8JNIWs7tOkV1NkYjC05V08ezBGJT7JrDVm+yXSr0tOKXi2RImJAcqTlhXMeZA3e7jzObiW10zzH0+00viXMQV/uG6Zq4NtN1OKzNalxvyZJ+r79hdLc4cOamVddZtcuEeVfAKaGxMiz8dQrhYcznGaUPjijxjs9P0tTgVS5OI79/GhrBEnOzw8zrdQG8UTmKAE2zM6dsY6jTrZk1zo84ev6+1wHdVgito/5SwL/1rPgawwI1ZVZrNP8zgAcehbaxgL6MgkrtZ8NQSpCgJcjie0pjnhqbamVai3r4KdYFql5VexG4XLGuJ1ARH48CmbXFRMq3SXIQgUxQyX8/u4ZbSbsOZ9LoO9gcf66Rg/OvkzrH20cao4559rbUDMocnyb017HxX0GAlxmlCajConWRcPBFP93C7I0ivN2SLDAtkO4HSaFzEqzc2uMdsRbADMLQs95K4H2BRAiEniy6b6IEvJJ49FC2WYYA+eut9RH4DzMu1Bkivo/l/JFBzzn3YOMO/s+EzmMWct3yT7I6bohTN8MJLq4fSYrHA/Y5KIywN3hEN/6oSPb9csHsQGw0UinXQ8XwifwlXYc0Cx2qKv2P2nPVeLyza+nSLpGS3cQwV8QxTe8N0HChbWIkDPmH3kQPdTiAPkgMAt8mEuZDRMDw1a90eAD7iFfN0m6nn3VhX8Goon+LtUC4twxoa2tFe7Hos2A/6kzPpjzUgvMNSD0xGQHdBnGNXmQ9cjgaHxMigLL43/8svoag7dK2B/CKTn9t2Wp/bohgpmgFujSPTuXtUTBxnzh/eymTZ0byOji5vz7wH1K+9QPX0HMPZjX1CAO/+ikbUWgrNdeQmxIqLsvQq72eSuywAyVU8IYYuFDWrkz3x8xbhyOHB0E5fDz8JPwHvLG7dR4dokmEv02rCxB7h0ul1B+Gg1P4Xd3BvTcyQQ92KmgZFWDJIf19fCfmzeMEnWFM9EascO59o7Sv8NO/u3UzmaQBWb5CBkgnDbMV7E+lkTdKrsfQnuo6KFgPaxB0QUnxyNnB/RrZxx7+VwVj/aX65DFCqgbGw3rqnoePgxH+PPqHgzyROKUcDqeVi5SvYcFYnkPRRBP+QGBo13kJBUVhshep1NDFeSusecre+8nCWcaRl7zlbBth+DzDqMPcoa9lK0Yh2GmCRcCNqOXHKcqA3hPd5jsyWlRLRg82a7AYd30QLXdYyt4jVZbqMDA3ehaC5POBdijbmlNFmLuFgLp4X69je85lapZAqWGpxd81I099PrP3uVZ5N+1vZQzQFIlobU7OzXeP1OpdKfwGWfnRWhvnjYQ5VHWETBO8g75M610MGpAtwRB+LTqiq6WyAwnF8kMS4i+LMN2RGuai4r9/j/K5bEn6bVfIMR352z4umczjOzRTSswYfMKwFTcCVbPWdJw/QTnM26qEE6JDj6tN5rnezM8FJnMoHMnpPTvlJfw9LfNNlxyVi8UfbqrWvnW/qnX6ZRVo1HFUTLZJc2cYVI+ppG8QaW7exRyxzBgr6jO3UnNXWKIFgxAf290tN0ZY4boH5mc2DJv5e6qSt7gd/pIioieBU6JuhRikJ5J6h/8ui378QI3ucurtTN9qKNMj54xC0abUAC4obSc1zP1q7U7vu/jVMaplWMKusOT3Bu9siuTOG4FLLh64CymLBKcB7+c1I/vmFVAYNPZ6kj19OXS3dC1j305aLHpLr0gfEaRMEOAeQYG/R/pJzNOuPfGpecOq9I8SvRDYektXDGuiIli6UDS644dz1z76gD1CA+SpCaQyOtJDFIB4NbVCJVIiCjApSG6ApuCdcR/Rnfvp4KXFicoY1TvsLNQ6A9p4HsOiUE3JwOr/1uKKPb+3VN2Mt50uZNFuMO686JBmWuAOaE8LsFotJtMSCPZEESI9OhrZGNiWhporQBQDor7zqbr1XpDOfDLJQnEetv66cbjMBzXw84lBS3os2D4+sR58aRX1Dvnn3w16uhRK/t9MBWtEo/Ltuo/Xy/VMIAy7bvyHcljLeb6gaXgh6CQUaemXRsoAcLf/izaN1sg/4RfhpRbzrBvxDFtxZGGuhuA3isZZKP2HfnogGaVsqAnMFEwg1mrNEto7p8iUxKTAFupGE91X7YsOnu3hsZB35saoadG1/8/2Y3zL8qr1grC3EktJjzs9L+29q6MyGDqAISQO8WQan9eQCMoPBW+PR8rFEMB5Xc2jV6zyIvUWHeZn28piFTiErAmboIuXlB/RjeWw5SkP0Wu0YJ4BoRIdybNMXI6//eud4ou2nH/rhlyWZNykEn2C5WCF3kjQOlRZrHhaOLltS4mh9iLSfAeI1z+vTcumH9oV9JLMrwL5GIL7Y6uaye3EdMWGyKNMoeKj4Z1KXed42q8NB9HM723WtQ6fGIapLunXHM9j7jWb5J4a5WRDLZ5MfjwlKy1C28K23t6HaQ6bWHY5LWOU8fK+YRVWXTjc58JJ0ra4KBfFpBV2j6fnsYjQBqL92r9x/cYHGL6fe5WOzYIRuRPtTCO2sJ5bc0ytBRbfy3dv06P8QAOA7MPlaTTJImM7yIQXADPClV68zZspZoCaMsMF3JTm2ypXUsoDb2fpTMBsXbO4LfIIEOrcYXRr0WpRzQqmUjm87IUOeUq8xHIEz8Isim7H9lgJn+sQNGBx7cbSiNSmp5hPfJeVqCykv9qZc4XxBL7Tj/YTrhQDSuLTgY8Z+ZLC3CRUYSi/ID33Xml6dSRPIgJZFB7h+ApjMqbV8aPOLOzorytvjeJXuAEP8NZQ9BbhR5RPi1jnG7fN8VLyeEb7NFP4tfU3+Gduec7DxqPNSIgE0xRbCmT2fsNKTzg4BX30b/miHga9B8jTTBdebE4TrtGIiqu4G+qkKRHOhjIenPO5Qvw2G8QkqbmB0BCbJpjCqzez1VYS2D+3FHKzjMDXJpsuC5TNTwTbmAQBZQleL1/G/gRT6cf1EkMgaE/ZCZ0DVKVqafxyU4o3smKU5xg+peccFN+XRHdOxgcciol2lDcCs6eoUjcb4QqdC3fs4pww8wUI4/ERIXluqY/xDrW9pUxB3HEgmV9JFDpLPvQ2UGxR92f8KGntc8qeipX0/NOS2lNeyQwT1PBiF+XrVsd9BZN0mPYn5LUAs5RvukPbsJfidT723rLQau2cxRgHmsUI7IQdbR4IWuh6rbYICTiPQjgs5EmNVM5a2WCvU92XecSSvDbJyqmaNHs+MwEGtUbZfaA423XTZkrmavjELBR6MlcccAVzyX9WlNO4L52326f9U5BvxXgychE0x9lP8WeYajT4t/um54VZa+ELC18dPEi8XOKe4Z12ZOul4pw9lMGip0QgaBuLonISxm28RfHW7TAH6GyIlW6cUp5HlTm6vUseInLjdEBACL9bHyVjn+UYcnQR9TyK2R9QR5GjN1KszUBOlvTEtKHSE6Y/K9Ogdy/i3H/F4TJlJfEWi/fV6a2K9+n41tMTsSt3lc4t9XAFLv9aylzRATvg7qaPeJxXT33ybV9EortPQ3c7wlni+W7l+qlxPJRx/nyRjEJjsf569N4X43XMWg86d+m+otVCm0UqQFrsafDyJpKMvGnXFhjHZXo0GwIE1GuExW1CkbXOKoOrUebj6UMulnCYooOVwcY7qyBSYQekuQyxutVYdf4SDV3ToWnWqq1Ef2DqC82fIQnBI/DdhY5g3sfIXPh5NGOoXdvBx85e/GdyKtKZWDoUawukjPTz09PeskVr27kMyAx7R7Wh5JlF6duU9Ewd+Oxg1n0yyu/6lOduPlkz7VHhXk8SrdiWjr1NuM0sbTno9U4qxu6F6/dziT5DLHXc+ishzbaeK+LLp+FY+Hu81pSVFetSCdrbJtFa+xrRRigVZ0WamTKXIPhAY3stv9FmtWqiftkQ+2cTYhy7XHE93wAwlwd3UaegHvx+z6ERFOvomK3TJVqmIlvzwc5dximSIGp37Ik+CIg89vlc22g3QV/S/K7V+LwxWiF3M0rLwH+t3kKmyYIGjm6IsCwoYR68FRozghljK/Q3Dyi7EaFZIX+MeuIEIT/qJU3HsG5rxqTAfSSTWLDMNYmps8V1m/SK/8bvMiKHONl23Xs4mZmdS3F0JThgWac/8imzmqLvr3ALekgdelm6c/ASC2kJjBgVQHMCs6cWCmGxyJ0ITAiWHPuSYUm0uxpS+OzHYtH283XuaFkqsTtuWXCnB3p1lgHB1rRWLdyrIJVJa93JlvpuZw6FgedxV3DMCi67JqOzqvekcrxcLH+UKDsUfjmbjCgLUuHnj72+8r2DH7oQ9papEAAmKjYU/KpoaaIGSxH2BEJq+roBaY3rzepAw2fj+2Fah9e7Civ1G+L5CJjDFUrRtRS7+AJcnQHYAJtEa3DJ8pTkbIpfA4iJWu+v4HMbUcJ6O1PqaUcP6cnz343eRKpIVkaMJ0s4MGNNFKaf/2n+YzaeSCjxbjbasPGoBkukkczM73lFkRT4wl1SiwFFBa6scuRDp04mfCX8MPl4nR1xru7FvOQZzRSJ8ek75r" +version https://git-lfs.github.com/spec/v1 +oid sha256:734f4f36a49a9a52f095ab85003b975790229db286596a00d0b19d7f5ee388fa +size 5679 diff --git a/tests/_data/disk/asif/basic.asif.gz b/tests/_data/disk/asif/basic.asif.gz index 6d53552..e735536 100644 Binary files a/tests/_data/disk/asif/basic.asif.gz and b/tests/_data/disk/asif/basic.asif.gz differ diff --git a/tests/_data/disk/hdd/expanding.hdd/DiskDescriptor.xml b/tests/_data/disk/hdd/expanding.hdd/DiskDescriptor.xml index cefe2ca..3746e74 100644 --- a/tests/_data/disk/hdd/expanding.hdd/DiskDescriptor.xml +++ b/tests/_data/disk/hdd/expanding.hdd/DiskDescriptor.xml @@ -1,52 +1,3 @@ - - - - 204800 - 400 - 4096 - 512 - 16 - 32 - 0 - - {00000000-0000-0000-0000-000000000000} - - - - {0610bb35-447e-4aae-aa79-f1571d969081} - expanding - - level2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - - - - - 0 - 204800 - 2048 - - {5fbaabe3-6958-40ff-92a7-860e329aab41} - Compressed - expanding.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds - - - - - - {5fbaabe3-6958-40ff-92a7-860e329aab41} - {00000000-0000-0000-0000-000000000000} - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:ee49c41e85342d3c07dc6b3b95b807256ef53252b345574c0babc674dc6282ff +size 1931 diff --git a/tests/_data/disk/hdd/expanding.hdd/expanding.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz b/tests/_data/disk/hdd/expanding.hdd/expanding.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz index 90789fd..730d28f 100644 Binary files a/tests/_data/disk/hdd/expanding.hdd/expanding.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz and b/tests/_data/disk/hdd/expanding.hdd/expanding.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz differ diff --git a/tests/_data/disk/hdd/plain.hdd/DiskDescriptor.xml b/tests/_data/disk/hdd/plain.hdd/DiskDescriptor.xml index c9653f3..6603216 100644 --- a/tests/_data/disk/hdd/plain.hdd/DiskDescriptor.xml +++ b/tests/_data/disk/hdd/plain.hdd/DiskDescriptor.xml @@ -1,52 +1,3 @@ - - - - 204800 - 400 - 4096 - 512 - 16 - 32 - 0 - - {00000000-0000-0000-0000-000000000000} - - - - {4be4afe0-ff6f-4544-b16c-d98d170a029c} - plain - - level2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - - - - - 0 - 204800 - 2048 - - {5fbaabe3-6958-40ff-92a7-860e329aab41} - Plain - plain.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds - - - - - - {5fbaabe3-6958-40ff-92a7-860e329aab41} - {00000000-0000-0000-0000-000000000000} - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:d0d338183b1c6c8aaf3c7470a18203d2e6dfcda92baddcc1e4a8bae481931a7a +size 1918 diff --git a/tests/_data/disk/hdd/plain.hdd/plain.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz b/tests/_data/disk/hdd/plain.hdd/plain.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz index 6a366d9..60bcfd3 100644 Binary files a/tests/_data/disk/hdd/plain.hdd/plain.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz and b/tests/_data/disk/hdd/plain.hdd/plain.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz differ diff --git a/tests/_data/disk/hdd/split.hdd/DiskDescriptor.xml b/tests/_data/disk/hdd/split.hdd/DiskDescriptor.xml index bdeaf83..b7bc596 100644 --- a/tests/_data/disk/hdd/split.hdd/DiskDescriptor.xml +++ b/tests/_data/disk/hdd/split.hdd/DiskDescriptor.xml @@ -1,102 +1,3 @@ - - - - 20971520 - 40960 - 4096 - 512 - 16 - 32 - 0 - - {00000000-0000-0000-0000-000000000000} - - - - {d6e2bfb7-109e-4f6b-954c-6e2e7ae60d5a} - split - - level2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - - - - - 0 - 3989504 - 2048 - - {5fbaabe3-6958-40ff-92a7-860e329aab41} - Compressed - split.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds - - - - 3989504 - 7979008 - 2048 - - {5fbaabe3-6958-40ff-92a7-860e329aab41} - Compressed - split.hdd.1.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds - - - - 7979008 - 11968512 - 2048 - - {5fbaabe3-6958-40ff-92a7-860e329aab41} - Compressed - split.hdd.2.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds - - - - 11968512 - 15958016 - 2048 - - {5fbaabe3-6958-40ff-92a7-860e329aab41} - Compressed - split.hdd.3.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds - - - - 15958016 - 19947520 - 2048 - - {5fbaabe3-6958-40ff-92a7-860e329aab41} - Compressed - split.hdd.4.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds - - - - 19947520 - 20971520 - 2048 - - {5fbaabe3-6958-40ff-92a7-860e329aab41} - Compressed - split.hdd.5.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds - - - - - - {5fbaabe3-6958-40ff-92a7-860e329aab41} - {00000000-0000-0000-0000-000000000000} - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:7e59a3ddfa30dc3ad3d6fa253fce9d3c9efc491205d54d335abee21102c3de6b +size 3815 diff --git a/tests/_data/disk/hdd/split.hdd/split.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz b/tests/_data/disk/hdd/split.hdd/split.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz index a1163e7..1ca66a4 100644 Binary files a/tests/_data/disk/hdd/split.hdd/split.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz and b/tests/_data/disk/hdd/split.hdd/split.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz differ diff --git a/tests/_data/disk/hdd/split.hdd/split.hdd.1.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz b/tests/_data/disk/hdd/split.hdd/split.hdd.1.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz index 25f67c6..7eb1053 100644 Binary files a/tests/_data/disk/hdd/split.hdd/split.hdd.1.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz and b/tests/_data/disk/hdd/split.hdd/split.hdd.1.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz differ diff --git a/tests/_data/disk/hdd/split.hdd/split.hdd.2.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz b/tests/_data/disk/hdd/split.hdd/split.hdd.2.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz index db4722f..afce4ba 100644 Binary files a/tests/_data/disk/hdd/split.hdd/split.hdd.2.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz and b/tests/_data/disk/hdd/split.hdd/split.hdd.2.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz differ diff --git a/tests/_data/disk/hdd/split.hdd/split.hdd.3.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz b/tests/_data/disk/hdd/split.hdd/split.hdd.3.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz index e92ddcb..d1999db 100644 Binary files a/tests/_data/disk/hdd/split.hdd/split.hdd.3.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz and b/tests/_data/disk/hdd/split.hdd/split.hdd.3.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz differ diff --git a/tests/_data/disk/hdd/split.hdd/split.hdd.4.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz b/tests/_data/disk/hdd/split.hdd/split.hdd.4.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz index b0451d0..1d83063 100644 Binary files a/tests/_data/disk/hdd/split.hdd/split.hdd.4.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz and b/tests/_data/disk/hdd/split.hdd/split.hdd.4.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz differ diff --git a/tests/_data/disk/hdd/split.hdd/split.hdd.5.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz b/tests/_data/disk/hdd/split.hdd/split.hdd.5.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz index 0bdc3c8..11f135f 100644 Binary files a/tests/_data/disk/hdd/split.hdd/split.hdd.5.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz and b/tests/_data/disk/hdd/split.hdd/split.hdd.5.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz differ diff --git a/tests/_data/disk/qcow2/backing-chain-1.qcow2.gz b/tests/_data/disk/qcow2/backing-chain-1.qcow2.gz index dfd1cbc..e85846f 100644 Binary files a/tests/_data/disk/qcow2/backing-chain-1.qcow2.gz and b/tests/_data/disk/qcow2/backing-chain-1.qcow2.gz differ diff --git a/tests/_data/disk/qcow2/backing-chain-2.qcow2.gz b/tests/_data/disk/qcow2/backing-chain-2.qcow2.gz index 7ac1acf..3725e84 100644 Binary files a/tests/_data/disk/qcow2/backing-chain-2.qcow2.gz and b/tests/_data/disk/qcow2/backing-chain-2.qcow2.gz differ diff --git a/tests/_data/disk/qcow2/backing-chain-3.qcow2.gz b/tests/_data/disk/qcow2/backing-chain-3.qcow2.gz index 1ba55da..c3c717d 100644 Binary files a/tests/_data/disk/qcow2/backing-chain-3.qcow2.gz and b/tests/_data/disk/qcow2/backing-chain-3.qcow2.gz differ diff --git a/tests/_data/disk/qcow2/basic-zstd.qcow2.gz b/tests/_data/disk/qcow2/basic-zstd.qcow2.gz index 33d3cca..36211d6 100644 Binary files a/tests/_data/disk/qcow2/basic-zstd.qcow2.gz and b/tests/_data/disk/qcow2/basic-zstd.qcow2.gz differ diff --git a/tests/_data/disk/qcow2/basic.qcow2.gz b/tests/_data/disk/qcow2/basic.qcow2.gz index f77ee8a..fe17ae1 100644 Binary files a/tests/_data/disk/qcow2/basic.qcow2.gz and b/tests/_data/disk/qcow2/basic.qcow2.gz differ diff --git a/tests/_data/disk/qcow2/data-file.bin.gz b/tests/_data/disk/qcow2/data-file.bin.gz index 617e387..abe142f 100644 Binary files a/tests/_data/disk/qcow2/data-file.bin.gz and b/tests/_data/disk/qcow2/data-file.bin.gz differ diff --git a/tests/_data/disk/qcow2/data-file.qcow2.gz b/tests/_data/disk/qcow2/data-file.qcow2.gz index a3fc3fa..a6d740d 100644 Binary files a/tests/_data/disk/qcow2/data-file.qcow2.gz and b/tests/_data/disk/qcow2/data-file.qcow2.gz differ diff --git a/tests/_data/disk/qcow2/snapshot.qcow2.gz b/tests/_data/disk/qcow2/snapshot.qcow2.gz index f62e130..56837e9 100644 Binary files a/tests/_data/disk/qcow2/snapshot.qcow2.gz and b/tests/_data/disk/qcow2/snapshot.qcow2.gz differ diff --git a/tests/_data/disk/vdi/basic.vdi.gz b/tests/_data/disk/vdi/basic.vdi.gz new file mode 100644 index 0000000..54337b3 --- /dev/null +++ b/tests/_data/disk/vdi/basic.vdi.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90373f35031056713dc31b2db98e45b7950bc8c4ac9ff83960ff8538c36f7235 +size 15017 diff --git a/tests/_data/disk/vhd/dynamic.vhd.gz b/tests/_data/disk/vhd/dynamic.vhd.gz index 9063f35..2d6e09f 100755 Binary files a/tests/_data/disk/vhd/dynamic.vhd.gz and b/tests/_data/disk/vhd/dynamic.vhd.gz differ diff --git a/tests/_data/disk/vhd/fixed.vhd.gz b/tests/_data/disk/vhd/fixed.vhd.gz index 3140864..5010c4a 100755 Binary files a/tests/_data/disk/vhd/fixed.vhd.gz and b/tests/_data/disk/vhd/fixed.vhd.gz differ diff --git a/tests/_data/disk/vhdx/differencing.avhdx.gz b/tests/_data/disk/vhdx/differencing.avhdx.gz index 6a431e4..f7562b6 100644 Binary files a/tests/_data/disk/vhdx/differencing.avhdx.gz and b/tests/_data/disk/vhdx/differencing.avhdx.gz differ diff --git a/tests/_data/disk/vhdx/dynamic.vhdx.gz b/tests/_data/disk/vhdx/dynamic.vhdx.gz index b28a77c..c5a385b 100755 Binary files a/tests/_data/disk/vhdx/dynamic.vhdx.gz and b/tests/_data/disk/vhdx/dynamic.vhdx.gz differ diff --git a/tests/_data/disk/vhdx/fixed.vhdx.gz b/tests/_data/disk/vhdx/fixed.vhdx.gz index 2b0ea20..06ddc6d 100755 Binary files a/tests/_data/disk/vhdx/fixed.vhdx.gz and b/tests/_data/disk/vhdx/fixed.vhdx.gz differ diff --git a/tests/_data/disk/vmdk/sesparse.vmdk.gz b/tests/_data/disk/vmdk/sesparse.vmdk.gz index 6b2455a..6eba027 100644 Binary files a/tests/_data/disk/vmdk/sesparse.vmdk.gz and b/tests/_data/disk/vmdk/sesparse.vmdk.gz differ diff --git a/tests/_data/util/envelope/encryption.info b/tests/_data/util/envelope/encryption.info index 7b38fbb..d7a672d 100644 --- a/tests/_data/util/envelope/encryption.info +++ b/tests/_data/util/envelope/encryption.info @@ -1,4 +1,3 @@ -.encoding = "UTF-8" -includeKeyCache = "FALSE" -mode = "NONE" -ConfigEncData = "keyId=fmLOxWrvTX6Di8rjLu/SUQ%3d%3d:data1=Uz8MFZfqTbWN2jIHbFPhag%3d%3d:data2=IDcZE41aR+uxDv7Iz8zJSA%3d%3d:version=1" +version https://git-lfs.github.com/spec/v1 +oid sha256:d50379b4f406d16694e4b2896c1ddc571c0a4cd5991ffb2498f1d3a7eb53072d +size 193 diff --git a/tests/_data/util/envelope/local.tgz.ve b/tests/_data/util/envelope/local.tgz.ve index a8f3200..147533d 100644 Binary files a/tests/_data/util/envelope/local.tgz.ve and b/tests/_data/util/envelope/local.tgz.ve differ diff --git a/tests/_data/util/vmtar/test.vgz b/tests/_data/util/vmtar/test.vgz index 701e273..658c9ee 100644 Binary files a/tests/_data/util/vmtar/test.vgz and b/tests/_data/util/vmtar/test.vgz differ diff --git a/tests/_tools/disk/vdi/generate.sh b/tests/_tools/disk/vdi/generate.sh new file mode 100755 index 0000000..b6cd8b9 --- /dev/null +++ b/tests/_tools/disk/vdi/generate.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -euo pipefail + +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly TESTS_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" +readonly OUT_DIR="${TESTS_ROOT}/_data/disk/vdi" + +log() { printf '[INFO] %s\n' "$*" >&2; } +warn() { printf '[WARN] %s\n' "$*" >&2; } +error() { printf '[ERROR] %s\n' "$*" >&2; } + +have() { command -v "$1" >/dev/null 2>&1; } + +require_tools() { + local -a tools=(qemu-img pigz xxd dd) + local missing=0 + + for t in "${tools[@]}"; do + if ! have "$t"; then + error "Missing required tool: $t" + missing=1 + fi + done + + if (( missing != 0 )); then + error "One or more required tools are missing. Aborting." + exit 1 + fi +} + +pattern() { + local size="$1" + + stream() { + while true; do + for i in $(seq 0 255); do + printf "`printf '%02x' "${i}"`%.0s" {0..4095} + done + done + } + + stream | xxd -r -ps | head -c "${size}" || true +} + +generate() { + local name="$1" + local size="$2" + + local raw="$(mktemp -t raw.XXXXXX)" + + pattern "${size}" > "${raw}" + # Create a hole at the start for testing sparse files + dd if=/dev/zero bs=1M count=1 seek=0 of="${raw}" conv=notrunc + + local outpath="${OUT_DIR}/${name}.vdi" + + log "Converting RAW -> VDI (${name})" + qemu-img convert -f raw -O vdi "${raw}" "${outpath}" + + log "Compressing ${outpath} -> ${outpath}.gz" + cat "${outpath}" | pigz -c > "${outpath}.gz" + + log "Generated: ${outpath}.gz" +} + +main() { + require_tools + + mkdir -p "${OUT_DIR}" + + # TODO: Snapshots/differencing disks + generate "basic" "$((10 * 1024 * 1024))" + + log "All test cases generated under: ${OUT_DIR}" +} + +main "$@" diff --git a/tests/disk/test_vdi.py b/tests/disk/test_vdi.py new file mode 100644 index 0000000..2f2659b --- /dev/null +++ b/tests/disk/test_vdi.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +import gzip +from pathlib import Path +from typing import BinaryIO +from unittest.mock import patch + +from dissect.hypervisor.disk.c_vdi import c_vdi +from dissect.hypervisor.disk.vdi import VDI +from tests.conftest import absolute_path + + +def mock_open_gz(self: Path, *args, **kwargs) -> BinaryIO: + return gzip.open(self) + + +def test_vdi() -> None: + """Test a basic VDI file.""" + with gzip.open(absolute_path("_data/disk/vdi/basic.vdi.gz"), "rb") as fh: + vdi = VDI(fh) + + assert vdi.type == c_vdi.VDI_IMAGE_TYPE.NORMAL + assert vdi.flags == c_vdi.VDI_IMAGE_FLAGS(0) + assert vdi.size == 10 * 1024 * 1024 + assert vdi.block_size == 1048576 + + stream = vdi.open() + assert stream.read(1 * 1024 * 1024) == bytes([0] * (1 * 1024 * 1024)) + + for i in range((1 * 1024 * 1024) // 4096, stream.size // 4096): + expected = bytes([i % 256] * 4096) + assert stream.read(4096) == expected, f"Mismatch at offset {i * 4096:#x}" + + assert stream.read() == b"" + + +def test_vdi_context_manager() -> None: + """Test VDI context manager.""" + with patch.object(Path, "open", gzip.open), VDI(absolute_path("_data/disk/vdi/basic.vdi.gz")) as vdi: + assert vdi.path is not None + assert vdi.fh.closed + + with gzip.open(absolute_path("_data/disk/vdi/basic.vdi.gz"), "rb") as fh: + with VDI(fh) as vdi: + assert vdi.path is None + assert fh.closed is False