dissect.hypervisor 3.21.dev2__py3-none-any.whl → 3.21.dev3__py3-none-any.whl

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.
@@ -1,22 +1,226 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from datetime import datetime
4
+ from functools import cached_property
3
5
  from typing import TYPE_CHECKING, TextIO
6
+ from uuid import UUID
4
7
 
5
8
  from defusedxml import ElementTree
6
9
 
7
10
  if TYPE_CHECKING:
8
- from collections.abc import Iterator
9
11
  from xml.etree.ElementTree import Element
10
12
 
13
+ NS = "{http://www.virtualbox.org/}"
14
+
11
15
 
12
16
  class VBox:
13
- VBOX_XML_NAMESPACE = "{http://www.virtualbox.org/}"
17
+ """VirtualBox XML descriptor parser.
18
+
19
+ Args:
20
+ fh: A file-like object of the VirtualBox XML descriptor.
21
+ """
14
22
 
15
23
  def __init__(self, fh: TextIO):
16
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)}>"
17
219
 
18
- def disks(self) -> Iterator[str]:
19
- for hdd_elem in self._xml.findall(f".//{self.VBOX_XML_NAMESPACE}HardDisk[@location][@type='Normal']"):
20
- # Allow format specifier to be case-insensitive (i.e. VDI, vdi)
21
- if (format := hdd_elem.get("format")) and format.lower() == "vdi":
22
- yield hdd_elem.attrib["location"]
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]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.hypervisor
3
- Version: 3.21.dev2
3
+ Version: 3.21.dev3
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
@@ -5,7 +5,7 @@ dissect/hypervisor/descriptor/c_hyperv.py,sha256=l_iW7CjK4DGJkg8NzNINFltA-txEPh-
5
5
  dissect/hypervisor/descriptor/hyperv.py,sha256=B71-TKGpndRfKHSRSaSDA1ak6rQiNlDLs97burrJwkw,17643
6
6
  dissect/hypervisor/descriptor/ovf.py,sha256=9TbUgY1F11EnSgUQYrkaJawGJeD-Swr1xHcH39Hpttc,1922
7
7
  dissect/hypervisor/descriptor/pvs.py,sha256=Ek8_m5PsyAWSFMD-q_E2j1fRqq5bb8g8ZiahlQSBcvI,701
8
- dissect/hypervisor/descriptor/vbox.py,sha256=TrfMN0BppjIoDeHVCMKlH1OS0IHO-V7uRFo3I75iFXw,744
8
+ dissect/hypervisor/descriptor/vbox.py,sha256=Kzls0YJsn02gFtMNFcAuShClTgji7qg5T7GzBzmoWrs,6650
9
9
  dissect/hypervisor/descriptor/vmx.py,sha256=6xhX7RlDqyn9XBkiq2NnQ5LrLL0nXwiPGOPy3WVVAtI,12647
10
10
  dissect/hypervisor/disk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  dissect/hypervisor/disk/asif.py,sha256=-HIXnoG8uFGTFj58kTRayVUqWtIPPKF04xMpiOYBL18,10840
@@ -30,10 +30,10 @@ dissect/hypervisor/tools/vmtar.py,sha256=bAf_rEhqUmeI8my22p7L9uV3SgB5C_61YQKfD3t
30
30
  dissect/hypervisor/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
31
  dissect/hypervisor/util/envelope.py,sha256=EhZjZh3EezqxkJ84OgqpptHIhpVWPcUkFMjdYU-iAnU,10442
32
32
  dissect/hypervisor/util/vmtar.py,sha256=pktC1mZIhh1E2uSyb2Z_pvn9H9LebJ6hTmZlYcdGrOA,4038
33
- dissect_hypervisor-3.21.dev2.dist-info/licenses/COPYRIGHT,sha256=EOOoIwk_inOMUD4c1ylpzMtYLjGzmc-MLEVAEdLLr20,305
34
- dissect_hypervisor-3.21.dev2.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
35
- dissect_hypervisor-3.21.dev2.dist-info/METADATA,sha256=SQL6nIw8ModKMOBmA5voEp5kwhBkAle4xX3C_2A3xSw,3470
36
- dissect_hypervisor-3.21.dev2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
- dissect_hypervisor-3.21.dev2.dist-info/entry_points.txt,sha256=kW36GnJ3G1dSRYFL4r8Bj32Al_CkGqST3bdHvo3pyiY,120
38
- dissect_hypervisor-3.21.dev2.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
39
- dissect_hypervisor-3.21.dev2.dist-info/RECORD,,
33
+ dissect_hypervisor-3.21.dev3.dist-info/licenses/COPYRIGHT,sha256=EOOoIwk_inOMUD4c1ylpzMtYLjGzmc-MLEVAEdLLr20,305
34
+ dissect_hypervisor-3.21.dev3.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
35
+ dissect_hypervisor-3.21.dev3.dist-info/METADATA,sha256=4cSxyF19taf_XVCWW8l7_DavK5BkDtvUIhl2N3bHzLo,3470
36
+ dissect_hypervisor-3.21.dev3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
37
+ dissect_hypervisor-3.21.dev3.dist-info/entry_points.txt,sha256=kW36GnJ3G1dSRYFL4r8Bj32Al_CkGqST3bdHvo3pyiY,120
38
+ dissect_hypervisor-3.21.dev3.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
39
+ dissect_hypervisor-3.21.dev3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5