dissect.hypervisor 3.20.dev2__tar.gz → 3.21__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.
- dissect_hypervisor-3.21/MANIFEST.in +4 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/PKG-INFO +2 -1
- dissect_hypervisor-3.21/dissect/hypervisor/descriptor/vbox.py +226 -0
- dissect_hypervisor-3.21/dissect/hypervisor/disk/c_vdi.py +98 -0
- dissect_hypervisor-3.21/dissect/hypervisor/disk/c_vdi.pyi +100 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/qcow2.py +6 -12
- dissect_hypervisor-3.21/dissect/hypervisor/disk/vdi.py +140 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/vmdk.py +5 -3
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect.hypervisor.egg-info/PKG-INFO +2 -1
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect.hypervisor.egg-info/SOURCES.txt +4 -35
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect.hypervisor.egg-info/requires.txt +3 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/pyproject.toml +2 -1
- dissect_hypervisor-3.21/tests/_tools/disk/vdi/generate.sh +77 -0
- dissect_hypervisor-3.21/tests/_util.py +7 -0
- dissect_hypervisor-3.21/tests/descriptor/test_hyperv.py +64 -0
- dissect_hypervisor-3.21/tests/descriptor/test_vbox.py +69 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/descriptor/test_vmx.py +3 -4
- dissect_hypervisor-3.21/tests/disk/test_asif.py +19 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/disk/test_hdd.py +18 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/disk/test_qcow2.py +44 -30
- dissect_hypervisor-3.21/tests/disk/test_vdi.py +46 -0
- dissect_hypervisor-3.21/tests/disk/test_vhd.py +65 -0
- dissect_hypervisor-3.21/tests/disk/test_vhdx.py +110 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/disk/test_vmdk.py +55 -14
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/util/test_envelope.py +12 -9
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/util/test_vmtar.py +19 -18
- dissect_hypervisor-3.20.dev2/MANIFEST.in +0 -2
- dissect_hypervisor-3.20.dev2/dissect/hypervisor/descriptor/vbox.py +0 -22
- dissect_hypervisor-3.20.dev2/dissect/hypervisor/disk/c_vdi.py +0 -91
- dissect_hypervisor-3.20.dev2/dissect/hypervisor/disk/vdi.py +0 -62
- dissect_hypervisor-3.20.dev2/tests/_data/descriptor/hyperv/test.VMRS +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/descriptor/hyperv/test.vmcx +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/descriptor/vmx/encrypted.vmx +0 -4
- dissect_hypervisor-3.20.dev2/tests/_data/disk/asif/basic.asif.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/hdd/expanding.hdd/DiskDescriptor.xml +0 -52
- dissect_hypervisor-3.20.dev2/tests/_data/disk/hdd/expanding.hdd/expanding.hdd +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/hdd/expanding.hdd/expanding.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/hdd/plain.hdd/DiskDescriptor.xml +0 -52
- dissect_hypervisor-3.20.dev2/tests/_data/disk/hdd/plain.hdd/plain.hdd +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/hdd/plain.hdd/plain.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/hdd/split.hdd/DiskDescriptor.xml +0 -102
- dissect_hypervisor-3.20.dev2/tests/_data/disk/hdd/split.hdd/split.hdd +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/hdd/split.hdd/split.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/hdd/split.hdd/split.hdd.1.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/hdd/split.hdd/split.hdd.2.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/hdd/split.hdd/split.hdd.3.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/hdd/split.hdd/split.hdd.4.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/hdd/split.hdd/split.hdd.5.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/qcow2/backing-chain-1.qcow2.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/qcow2/backing-chain-2.qcow2.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/qcow2/backing-chain-3.qcow2.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/qcow2/basic.qcow2.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/qcow2/data-file.bin.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/qcow2/data-file.qcow2.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/qcow2/snapshot.qcow2.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/vhd/dynamic.vhd.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/vhd/fixed.vhd.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/vhdx/differencing.avhdx.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/vhdx/dynamic.vhdx.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/vhdx/fixed.vhdx.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/disk/vmdk/sesparse.vmdk.gz +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/util/envelope/encryption.info +0 -4
- dissect_hypervisor-3.20.dev2/tests/_data/util/envelope/local.tgz.ve +0 -0
- dissect_hypervisor-3.20.dev2/tests/_data/util/vmtar/test.vgz +0 -0
- dissect_hypervisor-3.20.dev2/tests/conftest.py +0 -128
- dissect_hypervisor-3.20.dev2/tests/descriptor/test_hyperv.py +0 -63
- dissect_hypervisor-3.20.dev2/tests/descriptor/test_vbox.py +0 -43
- dissect_hypervisor-3.20.dev2/tests/disk/test_asif.py +0 -17
- dissect_hypervisor-3.20.dev2/tests/disk/test_vhd.py +0 -62
- dissect_hypervisor-3.20.dev2/tests/disk/test_vhdx.py +0 -104
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/.git-blame-ignore-revs +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/COPYRIGHT +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/LICENSE +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/README.md +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/__init__.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/descriptor/__init__.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/descriptor/c_hyperv.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/descriptor/hyperv.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/descriptor/ovf.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/descriptor/pvs.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/descriptor/vmx.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/__init__.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/asif.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/c_asif.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/c_asif.pyi +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/c_hdd.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/c_qcow2.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/c_qcow2.pyi +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/c_vhd.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/c_vhdx.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/c_vmdk.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/hdd.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/vhd.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/disk/vhdx.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/exceptions.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/tools/__init__.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/tools/envelope.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/tools/vmtar.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/util/__init__.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/util/envelope.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect/hypervisor/util/vmtar.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect.hypervisor.egg-info/dependency_links.txt +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect.hypervisor.egg-info/entry_points.txt +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect.hypervisor.egg-info/top_level.txt +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/setup.cfg +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/__init__.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/_docs/Makefile +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/_docs/conf.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/_docs/index.rst +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/descriptor/__init__.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/descriptor/test_ovf.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/descriptor/test_pvs.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/disk/__init__.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tests/util/__init__.py +0 -0
- {dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.hypervisor
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.21
|
|
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
|
|
@@ -26,6 +26,7 @@ Requires-Dist: dissect.cstruct<5,>=4
|
|
|
26
26
|
Requires-Dist: dissect.util<4,>=3
|
|
27
27
|
Provides-Extra: full
|
|
28
28
|
Requires-Dist: pycryptodome; extra == "full"
|
|
29
|
+
Requires-Dist: backports.zstd; python_version < "3.14" and extra == "full"
|
|
29
30
|
Provides-Extra: dev
|
|
30
31
|
Requires-Dist: dissect.hypervisor[full]; extra == "dev"
|
|
31
32
|
Requires-Dist: dissect.cstruct<5.0.dev,>=4.0.dev; extra == "dev"
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from typing import TYPE_CHECKING, TextIO
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from defusedxml import ElementTree
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from xml.etree.ElementTree import Element
|
|
12
|
+
|
|
13
|
+
NS = "{http://www.virtualbox.org/}"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class VBox:
|
|
17
|
+
"""VirtualBox XML descriptor parser.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
fh: A file-like object of the VirtualBox XML descriptor.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, fh: TextIO):
|
|
24
|
+
self._xml: Element = ElementTree.fromstring(fh.read())
|
|
25
|
+
if self._xml.tag != f"{NS}VirtualBox":
|
|
26
|
+
raise ValueError("Invalid VirtualBox XML descriptor: root element is not VirtualBox")
|
|
27
|
+
|
|
28
|
+
if (machine := self._xml.find(f"./{NS}Machine")) is None:
|
|
29
|
+
raise ValueError("Invalid VirtualBox XML descriptor: no Machine element found")
|
|
30
|
+
|
|
31
|
+
if machine.find(f"./{NS}Hardware") is None:
|
|
32
|
+
raise ValueError("Invalid VirtualBox XML descriptor: no Hardware element found")
|
|
33
|
+
|
|
34
|
+
self.machine = Machine(self, machine)
|
|
35
|
+
|
|
36
|
+
def __repr__(self) -> str:
|
|
37
|
+
return f"<VBox uuid={self.uuid} name={self.name}>"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def uuid(self) -> UUID | None:
|
|
41
|
+
"""The VM UUID."""
|
|
42
|
+
return self.machine.uuid
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def name(self) -> str | None:
|
|
46
|
+
"""The VM name."""
|
|
47
|
+
return self.machine.name
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def media(self) -> dict[UUID, HardDisk]:
|
|
51
|
+
"""The media (disks) registry."""
|
|
52
|
+
return self.machine.media
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def hardware(self) -> Hardware:
|
|
56
|
+
"""The current machine hardware state."""
|
|
57
|
+
return self.machine.hardware
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def snapshots(self) -> dict[UUID, Snapshot]:
|
|
61
|
+
"""All snapshots."""
|
|
62
|
+
return self.machine.snapshots
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Machine:
|
|
66
|
+
def __init__(self, vbox: VBox, element: Element):
|
|
67
|
+
self.vbox = vbox
|
|
68
|
+
self.element = element
|
|
69
|
+
|
|
70
|
+
def __repr__(self) -> str:
|
|
71
|
+
return f"<Machine uuid={self.uuid} name={self.name}>"
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def uuid(self) -> UUID:
|
|
75
|
+
"""The machine UUID."""
|
|
76
|
+
return UUID(self.element.get("uuid").strip("{}"))
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def name(self) -> str:
|
|
80
|
+
"""The machine name."""
|
|
81
|
+
return self.element.get("name")
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def current_snapshot(self) -> UUID | None:
|
|
85
|
+
"""The current snapshot UUID."""
|
|
86
|
+
if (value := self.element.get("currentSnapshot")) is not None:
|
|
87
|
+
return UUID(value.strip("{}"))
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
@cached_property
|
|
91
|
+
def media(self) -> dict[UUID, HardDisk]:
|
|
92
|
+
"""The media (disks) registry."""
|
|
93
|
+
result = {}
|
|
94
|
+
|
|
95
|
+
stack = [(None, element) for element in self.element.find(f"./{NS}MediaRegistry/{NS}HardDisks")]
|
|
96
|
+
while stack:
|
|
97
|
+
parent, element = stack.pop()
|
|
98
|
+
hdd = HardDisk(self, element, parent)
|
|
99
|
+
result[hdd.uuid] = hdd
|
|
100
|
+
|
|
101
|
+
stack.extend([(hdd, child) for child in element.findall(f"./{NS}HardDisk")])
|
|
102
|
+
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
@cached_property
|
|
106
|
+
def hardware(self) -> Hardware:
|
|
107
|
+
"""The machine hardware state."""
|
|
108
|
+
return Hardware(self.vbox, self.element.find(f"./{NS}Hardware"))
|
|
109
|
+
|
|
110
|
+
@cached_property
|
|
111
|
+
def snapshots(self) -> dict[UUID, Snapshot]:
|
|
112
|
+
"""All snapshots."""
|
|
113
|
+
result = {}
|
|
114
|
+
|
|
115
|
+
if (element := self.element.find(f"./{NS}Snapshot")) is None:
|
|
116
|
+
return result
|
|
117
|
+
|
|
118
|
+
stack = [(None, element)]
|
|
119
|
+
while stack:
|
|
120
|
+
parent, element = stack.pop()
|
|
121
|
+
snapshot = Snapshot(self.vbox, element, parent)
|
|
122
|
+
result[snapshot.uuid] = snapshot
|
|
123
|
+
|
|
124
|
+
if (snapshots := element.find(f"./{NS}Snapshots")) is not None:
|
|
125
|
+
stack.extend([(snapshot, child) for child in list(snapshots)])
|
|
126
|
+
|
|
127
|
+
return result
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def parent(self) -> Snapshot | None:
|
|
131
|
+
if (uuid := self.current_snapshot) is not None:
|
|
132
|
+
return self.vbox.snapshots[uuid]
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class HardDisk:
|
|
137
|
+
def __init__(self, vbox: VBox, element: Element, parent: HardDisk | None = None):
|
|
138
|
+
self.vbox = vbox
|
|
139
|
+
self.element = element
|
|
140
|
+
self.parent = parent
|
|
141
|
+
|
|
142
|
+
def __repr__(self) -> str:
|
|
143
|
+
return f"<HardDisk uuid={self.uuid} location={self.location}>"
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def uuid(self) -> UUID:
|
|
147
|
+
"""The disk UUID."""
|
|
148
|
+
return UUID(self.element.get("uuid").strip("{}"))
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def location(self) -> str:
|
|
152
|
+
"""The disk location."""
|
|
153
|
+
return self.element.get("location")
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def type(self) -> str | None:
|
|
157
|
+
"""The disk type."""
|
|
158
|
+
return self.element.get("type")
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def format(self) -> str:
|
|
162
|
+
"""The disk format."""
|
|
163
|
+
return self.element.get("format")
|
|
164
|
+
|
|
165
|
+
@cached_property
|
|
166
|
+
def properties(self) -> dict[str, str]:
|
|
167
|
+
"""The disk properties."""
|
|
168
|
+
return {prop.get("name"): prop.get("value") for prop in self.element.findall(f"./{NS}Property")}
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def is_encrypted(self) -> bool:
|
|
172
|
+
"""Whether the disk is encrypted."""
|
|
173
|
+
disk = self
|
|
174
|
+
while disk is not None:
|
|
175
|
+
if "CRYPT/KeyId" in disk.properties or "CRYPT/KeyStore" in disk.properties:
|
|
176
|
+
return True
|
|
177
|
+
disk = disk.parent
|
|
178
|
+
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class Snapshot:
|
|
183
|
+
def __init__(self, vbox: VBox, element: Element, parent: Snapshot | Machine | None = None):
|
|
184
|
+
self.vbox = vbox
|
|
185
|
+
self.element = element
|
|
186
|
+
self.parent = parent
|
|
187
|
+
|
|
188
|
+
def __repr__(self) -> str:
|
|
189
|
+
return f"<Snapshot uuid={self.uuid} name={self.name}>"
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def uuid(self) -> UUID:
|
|
193
|
+
"""The snapshot UUID."""
|
|
194
|
+
return UUID(self.element.get("uuid").strip("{}"))
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def name(self) -> str:
|
|
198
|
+
"""The snapshot name."""
|
|
199
|
+
return self.element.get("name")
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def ts(self) -> datetime:
|
|
203
|
+
"""The snapshot timestamp."""
|
|
204
|
+
return datetime.strptime(self.element.get("timeStamp"), "%Y-%m-%dT%H:%M:%S%z")
|
|
205
|
+
|
|
206
|
+
@cached_property
|
|
207
|
+
def hardware(self) -> Hardware:
|
|
208
|
+
"""The snapshot hardware state."""
|
|
209
|
+
return Hardware(self.vbox, self.element.find(f"./{NS}Hardware"))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class Hardware:
|
|
213
|
+
def __init__(self, vbox: VBox, element: Element):
|
|
214
|
+
self.vbox = vbox
|
|
215
|
+
self.element = element
|
|
216
|
+
|
|
217
|
+
def __repr__(self) -> str:
|
|
218
|
+
return f"<Hardware disks={len(self.disks)}>"
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def disks(self) -> list[HardDisk]:
|
|
222
|
+
"""All attached hard disks."""
|
|
223
|
+
images = self.element.findall(
|
|
224
|
+
f"./{NS}StorageControllers/{NS}StorageController/{NS}AttachedDevice[@type='HardDisk']/{NS}Image"
|
|
225
|
+
)
|
|
226
|
+
return [self.vbox.media[UUID(image.get("uuid").strip("{}"))] for image in images]
|
|
@@ -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
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
# - https://github.com/qemu/qemu/blob/master/docs/interop/qcow2.txt
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
import sys
|
|
6
7
|
import zlib
|
|
7
8
|
from functools import cached_property, lru_cache
|
|
8
|
-
from io import BytesIO
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import TYPE_CHECKING, BinaryIO
|
|
11
11
|
|
|
@@ -28,8 +28,10 @@ if TYPE_CHECKING:
|
|
|
28
28
|
from collections.abc import Iterator
|
|
29
29
|
|
|
30
30
|
try:
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
if sys.version_info >= (3, 14):
|
|
32
|
+
from compression import zstd # novermin
|
|
33
|
+
else:
|
|
34
|
+
from backports import zstd
|
|
33
35
|
HAS_ZSTD = True
|
|
34
36
|
except ImportError:
|
|
35
37
|
HAS_ZSTD = False
|
|
@@ -384,16 +386,8 @@ class QCow2Stream(AlignedStream):
|
|
|
384
386
|
return dctx.decompress(buf, self.qcow2.cluster_size)
|
|
385
387
|
|
|
386
388
|
if self.qcow2.compression_type == c_qcow2.QCOW2_COMPRESSION_TYPE_ZSTD:
|
|
387
|
-
result = []
|
|
388
|
-
|
|
389
389
|
dctx = zstd.ZstdDecompressor()
|
|
390
|
-
|
|
391
|
-
while reader.tell() < self.qcow2.cluster_size:
|
|
392
|
-
chunk = reader.read(self.qcow2.cluster_size - reader.tell())
|
|
393
|
-
if not chunk:
|
|
394
|
-
break
|
|
395
|
-
result.append(chunk)
|
|
396
|
-
return b"".join(result)
|
|
390
|
+
return dctx.decompress(buf, self.qcow2.cluster_size)
|
|
397
391
|
|
|
398
392
|
raise Error(f"Invalid compression type: {self.qcow2.compression_type}")
|
|
399
393
|
|
|
@@ -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)
|
|
@@ -407,7 +407,7 @@ RE_EXTENT_DESCRIPTOR = re.compile(
|
|
|
407
407
|
^
|
|
408
408
|
(?P<access_mode>RW|RDONLY|NOACCESS)\s
|
|
409
409
|
(?P<sectors>\d+)\s
|
|
410
|
-
(?P<type>SPARSE|ZERO|FLAT|VMFS|VMFSSPARSE|VMFSRDM|VMFSRAW)
|
|
410
|
+
(?P<type>SESPARSE|SPARSE|ZERO|FLAT|VMFS|VMFSSPARSE|VMFSRDM|VMFSRAW)
|
|
411
411
|
(\s(?P<filename>\".+\"))?
|
|
412
412
|
(\s(?P<start_sector>\d+))?
|
|
413
413
|
(\s(?P<partition_uuid>\S+))?
|
|
@@ -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
|
-
|
|
539
|
-
|
|
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}")
|
{dissect_hypervisor-3.20.dev2 → dissect_hypervisor-3.21}/dissect.hypervisor.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.hypervisor
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.21
|
|
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
|
|
@@ -26,6 +26,7 @@ Requires-Dist: dissect.cstruct<5,>=4
|
|
|
26
26
|
Requires-Dist: dissect.util<4,>=3
|
|
27
27
|
Provides-Extra: full
|
|
28
28
|
Requires-Dist: pycryptodome; extra == "full"
|
|
29
|
+
Requires-Dist: backports.zstd; python_version < "3.14" and extra == "full"
|
|
29
30
|
Provides-Extra: dev
|
|
30
31
|
Requires-Dist: dissect.hypervisor[full]; extra == "dev"
|
|
31
32
|
Requires-Dist: dissect.cstruct<5.0.dev,>=4.0.dev; extra == "dev"
|