dissect.hypervisor 3.21.dev3__tar.gz → 3.21.dev5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. dissect_hypervisor-3.21.dev5/MANIFEST.in +4 -0
  2. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/PKG-INFO +1 -1
  3. dissect_hypervisor-3.21.dev5/dissect/hypervisor/disk/c_vdi.py +98 -0
  4. dissect_hypervisor-3.21.dev5/dissect/hypervisor/disk/c_vdi.pyi +100 -0
  5. dissect_hypervisor-3.21.dev5/dissect/hypervisor/disk/vdi.py +140 -0
  6. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/vmdk.py +4 -2
  7. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect.hypervisor.egg-info/PKG-INFO +1 -1
  8. dissect_hypervisor-3.21.dev5/dissect.hypervisor.egg-info/SOURCES.txt +69 -0
  9. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/pyproject.toml +1 -0
  10. dissect_hypervisor-3.21.dev5/tests/_tools/disk/vdi/generate.sh +77 -0
  11. dissect_hypervisor-3.21.dev5/tests/disk/test_vdi.py +46 -0
  12. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/disk/test_vmdk.py +40 -1
  13. dissect_hypervisor-3.21.dev3/MANIFEST.in +0 -2
  14. dissect_hypervisor-3.21.dev3/dissect/hypervisor/disk/c_vdi.py +0 -91
  15. dissect_hypervisor-3.21.dev3/dissect/hypervisor/disk/vdi.py +0 -62
  16. dissect_hypervisor-3.21.dev3/dissect.hypervisor.egg-info/SOURCES.txt +0 -103
  17. dissect_hypervisor-3.21.dev3/tests/_data/descriptor/hyperv/test.VMRS +0 -0
  18. dissect_hypervisor-3.21.dev3/tests/_data/descriptor/hyperv/test.vmcx +0 -0
  19. dissect_hypervisor-3.21.dev3/tests/_data/descriptor/vbox/GOAD-DC01.vbox +0 -201
  20. dissect_hypervisor-3.21.dev3/tests/_data/descriptor/vbox/encrypted.vbox +0 -103
  21. dissect_hypervisor-3.21.dev3/tests/_data/descriptor/vmx/encrypted.vmx +0 -4
  22. dissect_hypervisor-3.21.dev3/tests/_data/disk/asif/basic.asif.gz +0 -0
  23. dissect_hypervisor-3.21.dev3/tests/_data/disk/hdd/expanding.hdd/DiskDescriptor.xml +0 -52
  24. dissect_hypervisor-3.21.dev3/tests/_data/disk/hdd/expanding.hdd/expanding.hdd +0 -0
  25. dissect_hypervisor-3.21.dev3/tests/_data/disk/hdd/expanding.hdd/expanding.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  26. dissect_hypervisor-3.21.dev3/tests/_data/disk/hdd/plain.hdd/DiskDescriptor.xml +0 -52
  27. dissect_hypervisor-3.21.dev3/tests/_data/disk/hdd/plain.hdd/plain.hdd +0 -0
  28. dissect_hypervisor-3.21.dev3/tests/_data/disk/hdd/plain.hdd/plain.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  29. dissect_hypervisor-3.21.dev3/tests/_data/disk/hdd/split.hdd/DiskDescriptor.xml +0 -102
  30. dissect_hypervisor-3.21.dev3/tests/_data/disk/hdd/split.hdd/split.hdd +0 -0
  31. dissect_hypervisor-3.21.dev3/tests/_data/disk/hdd/split.hdd/split.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  32. dissect_hypervisor-3.21.dev3/tests/_data/disk/hdd/split.hdd/split.hdd.1.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  33. dissect_hypervisor-3.21.dev3/tests/_data/disk/hdd/split.hdd/split.hdd.2.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  34. dissect_hypervisor-3.21.dev3/tests/_data/disk/hdd/split.hdd/split.hdd.3.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  35. dissect_hypervisor-3.21.dev3/tests/_data/disk/hdd/split.hdd/split.hdd.4.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  36. dissect_hypervisor-3.21.dev3/tests/_data/disk/hdd/split.hdd/split.hdd.5.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  37. dissect_hypervisor-3.21.dev3/tests/_data/disk/qcow2/backing-chain-1.qcow2.gz +0 -0
  38. dissect_hypervisor-3.21.dev3/tests/_data/disk/qcow2/backing-chain-2.qcow2.gz +0 -0
  39. dissect_hypervisor-3.21.dev3/tests/_data/disk/qcow2/backing-chain-3.qcow2.gz +0 -0
  40. dissect_hypervisor-3.21.dev3/tests/_data/disk/qcow2/basic-zstd.qcow2.gz +0 -0
  41. dissect_hypervisor-3.21.dev3/tests/_data/disk/qcow2/basic.qcow2.gz +0 -0
  42. dissect_hypervisor-3.21.dev3/tests/_data/disk/qcow2/data-file.bin.gz +0 -0
  43. dissect_hypervisor-3.21.dev3/tests/_data/disk/qcow2/data-file.qcow2.gz +0 -0
  44. dissect_hypervisor-3.21.dev3/tests/_data/disk/qcow2/snapshot.qcow2.gz +0 -0
  45. dissect_hypervisor-3.21.dev3/tests/_data/disk/vhd/dynamic.vhd.gz +0 -0
  46. dissect_hypervisor-3.21.dev3/tests/_data/disk/vhd/fixed.vhd.gz +0 -0
  47. dissect_hypervisor-3.21.dev3/tests/_data/disk/vhdx/differencing.avhdx.gz +0 -0
  48. dissect_hypervisor-3.21.dev3/tests/_data/disk/vhdx/dynamic.vhdx.gz +0 -0
  49. dissect_hypervisor-3.21.dev3/tests/_data/disk/vhdx/fixed.vhdx.gz +0 -0
  50. dissect_hypervisor-3.21.dev3/tests/_data/disk/vmdk/sesparse.vmdk.gz +0 -0
  51. dissect_hypervisor-3.21.dev3/tests/_data/util/envelope/encryption.info +0 -4
  52. dissect_hypervisor-3.21.dev3/tests/_data/util/envelope/local.tgz.ve +0 -0
  53. dissect_hypervisor-3.21.dev3/tests/_data/util/vmtar/test.vgz +0 -0
  54. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/.git-blame-ignore-revs +0 -0
  55. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/COPYRIGHT +0 -0
  56. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/LICENSE +0 -0
  57. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/README.md +0 -0
  58. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/__init__.py +0 -0
  59. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/descriptor/__init__.py +0 -0
  60. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/descriptor/c_hyperv.py +0 -0
  61. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/descriptor/hyperv.py +0 -0
  62. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/descriptor/ovf.py +0 -0
  63. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/descriptor/pvs.py +0 -0
  64. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/descriptor/vbox.py +0 -0
  65. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/descriptor/vmx.py +0 -0
  66. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/__init__.py +0 -0
  67. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/asif.py +0 -0
  68. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/c_asif.py +0 -0
  69. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/c_asif.pyi +0 -0
  70. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/c_hdd.py +0 -0
  71. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/c_qcow2.py +0 -0
  72. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/c_qcow2.pyi +0 -0
  73. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/c_vhd.py +0 -0
  74. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/c_vhdx.py +0 -0
  75. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/c_vmdk.py +0 -0
  76. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/hdd.py +0 -0
  77. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/qcow2.py +0 -0
  78. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/vhd.py +0 -0
  79. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/disk/vhdx.py +0 -0
  80. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/exceptions.py +0 -0
  81. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/tools/__init__.py +0 -0
  82. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/tools/envelope.py +0 -0
  83. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/tools/vmtar.py +0 -0
  84. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/util/__init__.py +0 -0
  85. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/util/envelope.py +0 -0
  86. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect/hypervisor/util/vmtar.py +0 -0
  87. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect.hypervisor.egg-info/dependency_links.txt +0 -0
  88. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect.hypervisor.egg-info/entry_points.txt +0 -0
  89. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect.hypervisor.egg-info/requires.txt +0 -0
  90. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/dissect.hypervisor.egg-info/top_level.txt +0 -0
  91. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/setup.cfg +0 -0
  92. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/__init__.py +0 -0
  93. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/_docs/Makefile +0 -0
  94. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/_docs/conf.py +0 -0
  95. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/_docs/index.rst +0 -0
  96. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/_util.py +0 -0
  97. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/descriptor/__init__.py +0 -0
  98. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/descriptor/test_hyperv.py +0 -0
  99. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/descriptor/test_ovf.py +0 -0
  100. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/descriptor/test_pvs.py +0 -0
  101. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/descriptor/test_vbox.py +0 -0
  102. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/descriptor/test_vmx.py +0 -0
  103. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/disk/__init__.py +0 -0
  104. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/disk/test_asif.py +0 -0
  105. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/disk/test_hdd.py +0 -0
  106. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/disk/test_qcow2.py +0 -0
  107. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/disk/test_vhd.py +0 -0
  108. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/disk/test_vhdx.py +0 -0
  109. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/util/__init__.py +0 -0
  110. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/util/test_envelope.py +0 -0
  111. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tests/util/test_vmtar.py +0 -0
  112. {dissect_hypervisor-3.21.dev3 → dissect_hypervisor-3.21.dev5}/tox.ini +0 -0
@@ -0,0 +1,4 @@
1
+ exclude .gitattributes
2
+ exclude .gitignore
3
+ recursive-exclude .github/ *
4
+ recursive-exclude tests/_data/ *
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.hypervisor
3
- Version: 3.21.dev3
3
+ Version: 3.21.dev5
4
4
  Summary: A Dissect module implementing parsers for various hypervisor disk, backup and configuration files
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+
3
+ from dissect.cstruct import cstruct
4
+
5
+ # https://github.com/VirtualBox/virtualbox/blob/main/src/VBox/Storage/VDICore.h
6
+ vdi_def = """
7
+ enum VDI_IMAGE_TYPE {
8
+ /** Normal dynamically growing base image file. */
9
+ NORMAL = 1,
10
+ /** Preallocated base image file of a fixed size. */
11
+ FIXED,
12
+ /** Dynamically growing image file for undo/commit changes support. */
13
+ UNDO,
14
+ /** Dynamically growing image file for differencing support. */
15
+ DIFF,
16
+ };
17
+
18
+ flag VDI_IMAGE_FLAGS {
19
+ /** Fill new blocks with zeroes while expanding image file. Only valid
20
+ * for newly created images, never set for opened existing images. */
21
+ ZERO_EXPAND = 0x0100,
22
+ };
23
+
24
+ typedef struct VDIDISKGEOMETRY {
25
+ /** Cylinders. */
26
+ uint32_t Cylinders;
27
+ /** Heads. */
28
+ uint32_t Heads;
29
+ /** Sectors per track. */
30
+ uint32_t Sectors;
31
+ /** Sector size. (bytes per sector) */
32
+ uint32_t Sector;
33
+ } VDIDISKGEOMETRY, *PVDIDISKGEOMETRY;
34
+
35
+ typedef struct VDIPREHEADER {
36
+ /** Just text info about image type, for eyes only. */
37
+ char szFileInfo[64];
38
+ /** The image signature (VDI_IMAGE_SIGNATURE). */
39
+ uint32_t u32Signature;
40
+ /** The image version (VDI_IMAGE_VERSION). */
41
+ uint32_t u32Version;
42
+ } VDIPREHEADER, *PVDIPREHEADER;
43
+
44
+ /**
45
+ * Size of Comment field of HDD image header.
46
+ */
47
+ #define VDI_IMAGE_COMMENT_SIZE 256
48
+
49
+ /* NOTE: All the header versions are additive, so just use the latest one. */
50
+ typedef struct VDIHEADER1PLUS {
51
+ /** Size of this structure in bytes. */
52
+ uint32_t cbHeader;
53
+ /** The image type (VDI_IMAGE_TYPE_*). */
54
+ VDI_IMAGE_TYPE u32Type;
55
+ /** Image flags (VDI_IMAGE_FLAGS_*). */
56
+ VDI_IMAGE_FLAGS fFlags;
57
+ /** Image comment. (UTF-8) */
58
+ char szComment[VDI_IMAGE_COMMENT_SIZE];
59
+ /** Offset of blocks array from the beginning of image file.
60
+ * Should be sector-aligned for HDD access optimization. */
61
+ uint32_t offBlocks;
62
+ /** Offset of image data from the beginning of image file.
63
+ * Should be sector-aligned for HDD access optimization. */
64
+ uint32_t offData;
65
+ /** Legacy image geometry (previous code stored PCHS there). */
66
+ VDIDISKGEOMETRY LegacyGeometry;
67
+ /** Was BIOS HDD translation mode, now unused. */
68
+ uint32_t u32Dummy;
69
+ /** Size of disk (in bytes). */
70
+ uint64_t cbDisk;
71
+ /** Block size. (For instance VDI_IMAGE_BLOCK_SIZE.) Should be a power of 2! */
72
+ uint32_t cbBlock;
73
+ /** Size of additional service information of every data block.
74
+ * Prepended before block data. May be 0.
75
+ * Should be a power of 2 and sector-aligned for optimization reasons. */
76
+ uint32_t cbBlockExtra;
77
+ /** Number of blocks. */
78
+ uint32_t cBlocks;
79
+ /** Number of allocated blocks. */
80
+ uint32_t cBlocksAllocated;
81
+ /** UUID of image. */
82
+ char uuidCreate[16];
83
+ /** UUID of image's last modification. */
84
+ char uuidModify[16];
85
+ /** Only for secondary images - UUID of previous image. */
86
+ char uuidLinkage[16];
87
+ /** Only for secondary images - UUID of previous image's last modification. */
88
+ char uuidParentModify[16];
89
+ /** LCHS image geometry (new field in VDI1.2 version. */
90
+ VDIDISKGEOMETRY Geometry;
91
+ } VDIHEADER1PLUS, *PVDIHEADER1PLUS;
92
+ """
93
+
94
+ c_vdi = cstruct().load(vdi_def)
95
+
96
+ VDI_IMAGE_SIGNATURE = 0xBEDA107F
97
+ VDI_IMAGE_BLOCK_FREE = ~0
98
+ VDI_IMAGE_BLOCK_ZERO = ~1
@@ -0,0 +1,100 @@
1
+ # Generated by cstruct-stubgen
2
+ from typing import BinaryIO, Literal, TypeAlias, overload
3
+
4
+ import dissect.cstruct as __cs__
5
+
6
+ class _c_vdi(__cs__.cstruct):
7
+ VDI_IMAGE_COMMENT_SIZE: Literal[256] = ...
8
+ class VDI_IMAGE_TYPE(__cs__.Enum):
9
+ NORMAL = ...
10
+ FIXED = ...
11
+ UNDO = ...
12
+ DIFF = ...
13
+
14
+ class VDI_IMAGE_FLAGS(__cs__.Flag):
15
+ ZERO_EXPAND = ...
16
+
17
+ class VDIDISKGEOMETRY(__cs__.Structure):
18
+ Cylinders: _c_vdi.uint32
19
+ Heads: _c_vdi.uint32
20
+ Sectors: _c_vdi.uint32
21
+ Sector: _c_vdi.uint32
22
+ @overload
23
+ def __init__(
24
+ self,
25
+ Cylinders: _c_vdi.uint32 | None = ...,
26
+ Heads: _c_vdi.uint32 | None = ...,
27
+ Sectors: _c_vdi.uint32 | None = ...,
28
+ Sector: _c_vdi.uint32 | None = ...,
29
+ ): ...
30
+ @overload
31
+ def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...
32
+
33
+ PVDIDISKGEOMETRY: TypeAlias = __cs__.Pointer[_c_vdi.VDIDISKGEOMETRY]
34
+ class VDIPREHEADER(__cs__.Structure):
35
+ szFileInfo: __cs__.CharArray
36
+ u32Signature: _c_vdi.uint32
37
+ u32Version: _c_vdi.uint32
38
+ @overload
39
+ def __init__(
40
+ self,
41
+ szFileInfo: __cs__.CharArray | None = ...,
42
+ u32Signature: _c_vdi.uint32 | None = ...,
43
+ u32Version: _c_vdi.uint32 | None = ...,
44
+ ): ...
45
+ @overload
46
+ def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...
47
+
48
+ PVDIPREHEADER: TypeAlias = __cs__.Pointer[_c_vdi.VDIPREHEADER]
49
+ class VDIHEADER1PLUS(__cs__.Structure):
50
+ cbHeader: _c_vdi.uint32
51
+ u32Type: _c_vdi.VDI_IMAGE_TYPE
52
+ fFlags: _c_vdi.VDI_IMAGE_FLAGS
53
+ szComment: __cs__.CharArray
54
+ offBlocks: _c_vdi.uint32
55
+ offData: _c_vdi.uint32
56
+ LegacyGeometry: _c_vdi.VDIDISKGEOMETRY
57
+ u32Dummy: _c_vdi.uint32
58
+ cbDisk: _c_vdi.uint64
59
+ cbBlock: _c_vdi.uint32
60
+ cbBlockExtra: _c_vdi.uint32
61
+ cBlocks: _c_vdi.uint32
62
+ cBlocksAllocated: _c_vdi.uint32
63
+ uuidCreate: __cs__.CharArray
64
+ uuidModify: __cs__.CharArray
65
+ uuidLinkage: __cs__.CharArray
66
+ uuidParentModify: __cs__.CharArray
67
+ Geometry: _c_vdi.VDIDISKGEOMETRY
68
+ @overload
69
+ def __init__(
70
+ self,
71
+ cbHeader: _c_vdi.uint32 | None = ...,
72
+ u32Type: _c_vdi.VDI_IMAGE_TYPE | None = ...,
73
+ fFlags: _c_vdi.VDI_IMAGE_FLAGS | None = ...,
74
+ szComment: __cs__.CharArray | None = ...,
75
+ offBlocks: _c_vdi.uint32 | None = ...,
76
+ offData: _c_vdi.uint32 | None = ...,
77
+ LegacyGeometry: _c_vdi.VDIDISKGEOMETRY | None = ...,
78
+ u32Dummy: _c_vdi.uint32 | None = ...,
79
+ cbDisk: _c_vdi.uint64 | None = ...,
80
+ cbBlock: _c_vdi.uint32 | None = ...,
81
+ cbBlockExtra: _c_vdi.uint32 | None = ...,
82
+ cBlocks: _c_vdi.uint32 | None = ...,
83
+ cBlocksAllocated: _c_vdi.uint32 | None = ...,
84
+ uuidCreate: __cs__.CharArray | None = ...,
85
+ uuidModify: __cs__.CharArray | None = ...,
86
+ uuidLinkage: __cs__.CharArray | None = ...,
87
+ uuidParentModify: __cs__.CharArray | None = ...,
88
+ Geometry: _c_vdi.VDIDISKGEOMETRY | None = ...,
89
+ ): ...
90
+ @overload
91
+ def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...
92
+
93
+ PVDIHEADER1PLUS: TypeAlias = __cs__.Pointer[_c_vdi.VDIHEADER1PLUS]
94
+
95
+ # Technically `c_vdi` is an instance of `_c_vdi`, but then we can't use it in type hints
96
+ c_vdi: TypeAlias = _c_vdi
97
+
98
+ VDI_IMAGE_SIGNATURE: int
99
+ VDI_IMAGE_BLOCK_FREE: int
100
+ VDI_IMAGE_BLOCK_ZERO: int
@@ -0,0 +1,140 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING, BinaryIO
5
+
6
+ from dissect.util.stream import AlignedStream
7
+ from dissect.util.xmemoryview import xmemoryview
8
+
9
+ from dissect.hypervisor.disk.c_vdi import VDI_IMAGE_BLOCK_FREE, VDI_IMAGE_BLOCK_ZERO, VDI_IMAGE_SIGNATURE, c_vdi
10
+ from dissect.hypervisor.exceptions import Error
11
+
12
+ if TYPE_CHECKING:
13
+ from types import TracebackType
14
+
15
+ from typing_extensions import Self
16
+
17
+
18
+ class VDI:
19
+ """VirtualBox Virtual Disk Image (VDI) implementation.
20
+
21
+ Use :method:`open` to get a stream for reading from the VDI file. The stream will handle reading
22
+ from the parent disk if necessary (and provided).
23
+
24
+ If provided with a file-like object, the caller is responsible for closing it.
25
+ When provided with a path, the VDI class will manage the file handle.
26
+
27
+ If providing a parent file-like object, the caller is responsible for the lifecycle of that object.
28
+
29
+ Args:
30
+ fh: File-like object or path of the VDI file.
31
+ parent: Optional file-like object for the parent disk (for differencing disks).
32
+ """
33
+
34
+ def __init__(self, fh: BinaryIO | Path, parent: BinaryIO | None = None):
35
+ if isinstance(fh, Path):
36
+ self.path = fh
37
+ self.fh = self.path.open("rb")
38
+ else:
39
+ self.path = None
40
+ self.fh = fh
41
+
42
+ self.parent = parent
43
+
44
+ self.fh.seek(0)
45
+ self.preheader = c_vdi.VDIPREHEADER(self.fh)
46
+ if self.preheader.u32Signature != VDI_IMAGE_SIGNATURE:
47
+ raise Error(
48
+ f"Invalid VDI signature, expected {VDI_IMAGE_SIGNATURE:#08X}, got {self.preheader.u32Signature:#08X}"
49
+ )
50
+
51
+ self.header = c_vdi.VDIHEADER1PLUS(self.fh)
52
+
53
+ def __enter__(self) -> Self:
54
+ return self
55
+
56
+ def __exit__(
57
+ self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
58
+ ) -> None:
59
+ self.close()
60
+
61
+ @property
62
+ def type(self) -> c_vdi.VDI_IMAGE_TYPE:
63
+ """The type of the VDI file."""
64
+ return self.header.u32Type
65
+
66
+ @property
67
+ def flags(self) -> c_vdi.VDI_IMAGE_FLAGS:
68
+ """The flags of the VDI file."""
69
+ return self.header.fFlags
70
+
71
+ @property
72
+ def size(self) -> int:
73
+ """The size of the virtual disk."""
74
+ return self.header.cbDisk
75
+
76
+ @property
77
+ def block_size(self) -> int:
78
+ """The size of each block in the VDI file."""
79
+ return self.header.cbBlock
80
+
81
+ @property
82
+ def data_offset(self) -> int:
83
+ """The offset to the data blocks."""
84
+ return self.header.offData
85
+
86
+ @property
87
+ def blocks_offset(self) -> int:
88
+ """The offset to the block allocation table."""
89
+ return self.header.offBlocks
90
+
91
+ @property
92
+ def number_of_blocks(self) -> int:
93
+ """The number of blocks in the VDI file."""
94
+ return self.header.cBlocks
95
+
96
+ def open(self) -> VDIStream:
97
+ """Open a stream to read from the VDI file."""
98
+ return VDIStream(self)
99
+
100
+ def close(self) -> None:
101
+ """Close the VDI file handle."""
102
+ if self.path is not None:
103
+ self.fh.close()
104
+
105
+
106
+ class VDIStream(AlignedStream):
107
+ def __init__(self, vdi: VDI):
108
+ self.vdi = vdi
109
+ self.block_size = vdi.block_size
110
+
111
+ self.fh = self.vdi.fh
112
+ self.fh.seek(self.vdi.blocks_offset)
113
+ self.map = xmemoryview(self.fh.read(4 * self.vdi.number_of_blocks), "<i")
114
+ super().__init__(vdi.size, align=self.block_size)
115
+
116
+ def _read(self, offset: int, length: int) -> bytes:
117
+ result = []
118
+
119
+ block_idx, offset_in_block = divmod(offset, self.block_size)
120
+ while length > 0:
121
+ read_len = min(length, max(length, self.block_size - offset_in_block))
122
+
123
+ block = self.map[block_idx]
124
+ if block == VDI_IMAGE_BLOCK_FREE:
125
+ if self.vdi.parent is not None:
126
+ self.vdi.parent.seek(offset)
127
+ result.append(self.vdi.parent.read(read_len))
128
+ else:
129
+ result.append(b"\x00" * read_len)
130
+ elif block == VDI_IMAGE_BLOCK_ZERO:
131
+ result.append(b"\x00" * read_len)
132
+ else:
133
+ self.fh.seek(self.vdi.data_offset + (block * self.block_size) + offset_in_block)
134
+ result.append(self.fh.read(read_len))
135
+
136
+ offset += read_len
137
+ length -= read_len
138
+ block_idx += 1
139
+
140
+ return b"".join(result)
@@ -535,8 +535,10 @@ def open_parent(path: Path, filename_hint: str) -> VMDK:
535
535
  hint_path, _, filename = filename_hint.rpartition("/")
536
536
  filepath = path.joinpath(filename)
537
537
  if not filepath.exists():
538
- _, _, hint_path_name = hint_path.rpartition("/")
539
- filepath = path.parent.joinpath(hint_path_name).joinpath(filename)
538
+ filepath = path.joinpath(filename_hint)
539
+ if not filepath.exists():
540
+ _, _, hint_path_name = hint_path.rpartition("/")
541
+ filepath = path.parent.joinpath(hint_path_name).joinpath(filename)
540
542
  vmdk = VMDK(filepath)
541
543
  except Exception as err:
542
544
  raise IOError(f"Failed to open parent disk with hint {filename_hint} from path {path}: {err}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.hypervisor
3
- Version: 3.21.dev3
3
+ Version: 3.21.dev5
4
4
  Summary: A Dissect module implementing parsers for various hypervisor disk, backup and configuration files
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -0,0 +1,69 @@
1
+ .git-blame-ignore-revs
2
+ COPYRIGHT
3
+ LICENSE
4
+ MANIFEST.in
5
+ README.md
6
+ pyproject.toml
7
+ tox.ini
8
+ dissect.hypervisor.egg-info/PKG-INFO
9
+ dissect.hypervisor.egg-info/SOURCES.txt
10
+ dissect.hypervisor.egg-info/dependency_links.txt
11
+ dissect.hypervisor.egg-info/entry_points.txt
12
+ dissect.hypervisor.egg-info/requires.txt
13
+ dissect.hypervisor.egg-info/top_level.txt
14
+ dissect/hypervisor/__init__.py
15
+ dissect/hypervisor/exceptions.py
16
+ dissect/hypervisor/descriptor/__init__.py
17
+ dissect/hypervisor/descriptor/c_hyperv.py
18
+ dissect/hypervisor/descriptor/hyperv.py
19
+ dissect/hypervisor/descriptor/ovf.py
20
+ dissect/hypervisor/descriptor/pvs.py
21
+ dissect/hypervisor/descriptor/vbox.py
22
+ dissect/hypervisor/descriptor/vmx.py
23
+ dissect/hypervisor/disk/__init__.py
24
+ dissect/hypervisor/disk/asif.py
25
+ dissect/hypervisor/disk/c_asif.py
26
+ dissect/hypervisor/disk/c_asif.pyi
27
+ dissect/hypervisor/disk/c_hdd.py
28
+ dissect/hypervisor/disk/c_qcow2.py
29
+ dissect/hypervisor/disk/c_qcow2.pyi
30
+ dissect/hypervisor/disk/c_vdi.py
31
+ dissect/hypervisor/disk/c_vdi.pyi
32
+ dissect/hypervisor/disk/c_vhd.py
33
+ dissect/hypervisor/disk/c_vhdx.py
34
+ dissect/hypervisor/disk/c_vmdk.py
35
+ dissect/hypervisor/disk/hdd.py
36
+ dissect/hypervisor/disk/qcow2.py
37
+ dissect/hypervisor/disk/vdi.py
38
+ dissect/hypervisor/disk/vhd.py
39
+ dissect/hypervisor/disk/vhdx.py
40
+ dissect/hypervisor/disk/vmdk.py
41
+ dissect/hypervisor/tools/__init__.py
42
+ dissect/hypervisor/tools/envelope.py
43
+ dissect/hypervisor/tools/vmtar.py
44
+ dissect/hypervisor/util/__init__.py
45
+ dissect/hypervisor/util/envelope.py
46
+ dissect/hypervisor/util/vmtar.py
47
+ tests/__init__.py
48
+ tests/_util.py
49
+ tests/_docs/Makefile
50
+ tests/_docs/conf.py
51
+ tests/_docs/index.rst
52
+ tests/_tools/disk/vdi/generate.sh
53
+ tests/descriptor/__init__.py
54
+ tests/descriptor/test_hyperv.py
55
+ tests/descriptor/test_ovf.py
56
+ tests/descriptor/test_pvs.py
57
+ tests/descriptor/test_vbox.py
58
+ tests/descriptor/test_vmx.py
59
+ tests/disk/__init__.py
60
+ tests/disk/test_asif.py
61
+ tests/disk/test_hdd.py
62
+ tests/disk/test_qcow2.py
63
+ tests/disk/test_vdi.py
64
+ tests/disk/test_vhd.py
65
+ tests/disk/test_vhdx.py
66
+ tests/disk/test_vmdk.py
67
+ tests/util/__init__.py
68
+ tests/util/test_envelope.py
69
+ tests/util/test_vmtar.py
@@ -54,6 +54,7 @@ test = [
54
54
  lint = [
55
55
  "ruff==0.13.1",
56
56
  "vermin",
57
+ "typing_extensions",
57
58
  ]
58
59
  build = [
59
60
  "build",
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ readonly TESTS_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
6
+ readonly OUT_DIR="${TESTS_ROOT}/_data/disk/vdi"
7
+
8
+ log() { printf '[INFO] %s\n' "$*" >&2; }
9
+ warn() { printf '[WARN] %s\n' "$*" >&2; }
10
+ error() { printf '[ERROR] %s\n' "$*" >&2; }
11
+
12
+ have() { command -v "$1" >/dev/null 2>&1; }
13
+
14
+ require_tools() {
15
+ local -a tools=(qemu-img pigz xxd dd)
16
+ local missing=0
17
+
18
+ for t in "${tools[@]}"; do
19
+ if ! have "$t"; then
20
+ error "Missing required tool: $t"
21
+ missing=1
22
+ fi
23
+ done
24
+
25
+ if (( missing != 0 )); then
26
+ error "One or more required tools are missing. Aborting."
27
+ exit 1
28
+ fi
29
+ }
30
+
31
+ pattern() {
32
+ local size="$1"
33
+
34
+ stream() {
35
+ while true; do
36
+ for i in $(seq 0 255); do
37
+ printf "`printf '%02x' "${i}"`%.0s" {0..4095}
38
+ done
39
+ done
40
+ }
41
+
42
+ stream | xxd -r -ps | head -c "${size}" || true
43
+ }
44
+
45
+ generate() {
46
+ local name="$1"
47
+ local size="$2"
48
+
49
+ local raw="$(mktemp -t raw.XXXXXX)"
50
+
51
+ pattern "${size}" > "${raw}"
52
+ # Create a hole at the start for testing sparse files
53
+ dd if=/dev/zero bs=1M count=1 seek=0 of="${raw}" conv=notrunc
54
+
55
+ local outpath="${OUT_DIR}/${name}.vdi"
56
+
57
+ log "Converting RAW -> VDI (${name})"
58
+ qemu-img convert -f raw -O vdi "${raw}" "${outpath}"
59
+
60
+ log "Compressing ${outpath} -> ${outpath}.gz"
61
+ cat "${outpath}" | pigz -c > "${outpath}.gz"
62
+
63
+ log "Generated: ${outpath}.gz"
64
+ }
65
+
66
+ main() {
67
+ require_tools
68
+
69
+ mkdir -p "${OUT_DIR}"
70
+
71
+ # TODO: Snapshots/differencing disks
72
+ generate "basic" "$((10 * 1024 * 1024))"
73
+
74
+ log "All test cases generated under: ${OUT_DIR}"
75
+ }
76
+
77
+ main "$@"
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ import gzip
4
+ from pathlib import Path
5
+ from typing import BinaryIO
6
+ from unittest.mock import patch
7
+
8
+ from dissect.hypervisor.disk.c_vdi import c_vdi
9
+ from dissect.hypervisor.disk.vdi import VDI
10
+ from tests._util import absolute_path
11
+
12
+
13
+ def mock_open_gz(self: Path, *args, **kwargs) -> BinaryIO:
14
+ return gzip.open(self)
15
+
16
+
17
+ def test_vdi() -> None:
18
+ """Test a basic VDI file."""
19
+ with gzip.open(absolute_path("_data/disk/vdi/basic.vdi.gz"), "rb") as fh:
20
+ vdi = VDI(fh)
21
+
22
+ assert vdi.type == c_vdi.VDI_IMAGE_TYPE.NORMAL
23
+ assert vdi.flags == c_vdi.VDI_IMAGE_FLAGS(0)
24
+ assert vdi.size == 10 * 1024 * 1024
25
+ assert vdi.block_size == 1048576
26
+
27
+ stream = vdi.open()
28
+ assert stream.read(1 * 1024 * 1024) == bytes([0] * (1 * 1024 * 1024))
29
+
30
+ for i in range((1 * 1024 * 1024) // 4096, stream.size // 4096):
31
+ expected = bytes([i % 256] * 4096)
32
+ assert stream.read(4096) == expected, f"Mismatch at offset {i * 4096:#x}"
33
+
34
+ assert stream.read() == b""
35
+
36
+
37
+ def test_vdi_context_manager() -> None:
38
+ """Test VDI context manager."""
39
+ with patch.object(Path, "open", gzip.open), VDI(absolute_path("_data/disk/vdi/basic.vdi.gz")) as vdi:
40
+ assert vdi.path is not None
41
+ assert vdi.fh.closed
42
+
43
+ with gzip.open(absolute_path("_data/disk/vdi/basic.vdi.gz"), "rb") as fh:
44
+ with VDI(fh) as vdi:
45
+ assert vdi.path is None
46
+ assert fh.closed is False
@@ -1,11 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import gzip
4
+ from pathlib import Path
5
+ from unittest.mock import MagicMock
4
6
 
5
7
  import pytest
6
8
 
7
9
  from dissect.hypervisor.disk.c_vmdk import c_vmdk
8
- from dissect.hypervisor.disk.vmdk import VMDK, DiskDescriptor, ExtentDescriptor
10
+ from dissect.hypervisor.disk.vmdk import VMDK, DiskDescriptor, ExtentDescriptor, open_parent
9
11
  from tests._util import absolute_path
10
12
 
11
13
 
@@ -203,3 +205,40 @@ def test_vmdk_extent_description(extent_description: str, expected_extents: list
203
205
 
204
206
  descriptor = DiskDescriptor.parse(extent_description)
205
207
  assert descriptor.extents == expected_extents
208
+
209
+
210
+ def test_open_parent_all_cases(monkeypatch: pytest.MonkeyPatch) -> None:
211
+ """Test open_parent handles absolute and relative filename_hint paths."""
212
+
213
+ # Mock Path.exists to simulate file existence
214
+ def mock_exists(path: Path) -> bool:
215
+ return str(path) in {
216
+ "/a/b/c/d.vmdk", # Case: absolute path
217
+ "base/relative/hint.vmdk", # Case: full relative path
218
+ "base/hint.vmdk", # Case: basename in same dir
219
+ "../sibling/hint.vmdk", # Case: fallback to sibling
220
+ }
221
+
222
+ with monkeypatch.context() as m:
223
+ m.setattr("pathlib.Path.exists", mock_exists)
224
+
225
+ # Mock VMDK to avoid real file I/O
226
+ mock_vmdk = MagicMock()
227
+ m.setattr("dissect.hypervisor.disk.vmdk.VMDK", lambda path: mock_vmdk)
228
+ mock_vmdk.path = "mocked-path"
229
+
230
+ # Case: Absolute path — should use /a/b/c/d.vmdk directly
231
+ vmdk = open_parent(Path("base"), "/a/b/c/d.vmdk")
232
+ assert str(vmdk.path) == "mocked-path"
233
+
234
+ # Case: Full relative path — try base/relative/hint.vmdk
235
+ vmdk = open_parent(Path("base"), "relative/hint.vmdk")
236
+ assert str(vmdk.path) == "mocked-path"
237
+
238
+ # Case: Basename only — fall back to base/hint.vmdk
239
+ vmdk = open_parent(Path("base"), "hint.vmdk")
240
+ assert str(vmdk.path) == "mocked-path"
241
+
242
+ # Case: Fallback to sibling — try ../sibling/hint.vmdk
243
+ vmdk = open_parent(Path("base"), "sibling/hint.vmdk")
244
+ assert str(vmdk.path) == "mocked-path"
@@ -1,2 +0,0 @@
1
- exclude .gitignore
2
- recursive-exclude .github/ *