dissect.hypervisor 3.16.dev1__tar.gz → 3.16.dev2__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 (90) hide show
  1. {dissect_hypervisor-3.16.dev1/dissect.hypervisor.egg-info → dissect_hypervisor-3.16.dev2}/PKG-INFO +1 -2
  2. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/__init__.py +0 -3
  3. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2/dissect.hypervisor.egg-info}/PKG-INFO +1 -2
  4. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect.hypervisor.egg-info/SOURCES.txt +0 -7
  5. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect.hypervisor.egg-info/entry_points.txt +0 -1
  6. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect.hypervisor.egg-info/requires.txt +0 -1
  7. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/pyproject.toml +0 -2
  8. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/conftest.py +0 -5
  9. dissect_hypervisor-3.16.dev1/dissect/hypervisor/backup/c_vma.py +0 -60
  10. dissect_hypervisor-3.16.dev1/dissect/hypervisor/backup/vma.py +0 -269
  11. dissect_hypervisor-3.16.dev1/dissect/hypervisor/backup/xva.py +0 -136
  12. dissect_hypervisor-3.16.dev1/dissect/hypervisor/tools/vma.py +0 -173
  13. dissect_hypervisor-3.16.dev1/tests/__init__.py +0 -0
  14. dissect_hypervisor-3.16.dev1/tests/data/test.vma.gz +0 -0
  15. dissect_hypervisor-3.16.dev1/tests/test_vma.py +0 -64
  16. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/COPYRIGHT +0 -0
  17. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/LICENSE +0 -0
  18. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/MANIFEST.in +0 -0
  19. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/README.md +0 -0
  20. {dissect_hypervisor-3.16.dev1/dissect/hypervisor/backup → dissect_hypervisor-3.16.dev2/dissect/hypervisor/descriptor}/__init__.py +0 -0
  21. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/descriptor/c_hyperv.py +0 -0
  22. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/descriptor/hyperv.py +0 -0
  23. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/descriptor/ovf.py +0 -0
  24. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/descriptor/pvs.py +0 -0
  25. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/descriptor/vbox.py +0 -0
  26. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/descriptor/vmx.py +0 -0
  27. {dissect_hypervisor-3.16.dev1/dissect/hypervisor/descriptor → dissect_hypervisor-3.16.dev2/dissect/hypervisor/disk}/__init__.py +0 -0
  28. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/disk/c_hdd.py +0 -0
  29. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/disk/c_qcow2.py +0 -0
  30. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/disk/c_vdi.py +0 -0
  31. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/disk/c_vhd.py +0 -0
  32. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/disk/c_vhdx.py +0 -0
  33. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/disk/c_vmdk.py +0 -0
  34. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/disk/hdd.py +0 -0
  35. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/disk/qcow2.py +0 -0
  36. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/disk/vdi.py +0 -0
  37. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/disk/vhd.py +0 -0
  38. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/disk/vhdx.py +0 -0
  39. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/disk/vmdk.py +0 -0
  40. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/exceptions.py +0 -0
  41. {dissect_hypervisor-3.16.dev1/dissect/hypervisor/disk → dissect_hypervisor-3.16.dev2/dissect/hypervisor/tools}/__init__.py +0 -0
  42. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/tools/envelope.py +0 -0
  43. {dissect_hypervisor-3.16.dev1/dissect/hypervisor/tools → dissect_hypervisor-3.16.dev2/dissect/hypervisor/util}/__init__.py +0 -0
  44. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/util/envelope.py +0 -0
  45. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect/hypervisor/util/vmtar.py +0 -0
  46. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect.hypervisor.egg-info/dependency_links.txt +0 -0
  47. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/dissect.hypervisor.egg-info/top_level.txt +0 -0
  48. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/setup.cfg +0 -0
  49. {dissect_hypervisor-3.16.dev1/dissect/hypervisor/util → dissect_hypervisor-3.16.dev2/tests}/__init__.py +0 -0
  50. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/differencing.avhdx.gz +0 -0
  51. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/dynamic.vhd.gz +0 -0
  52. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/dynamic.vhdx.gz +0 -0
  53. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/encrypted.vmx +0 -0
  54. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/encryption.info +0 -0
  55. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/expanding.hdd/DiskDescriptor.xml +0 -0
  56. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/expanding.hdd/expanding.hdd +0 -0
  57. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/expanding.hdd/expanding.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  58. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/fixed.vhd.gz +0 -0
  59. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/fixed.vhdx.gz +0 -0
  60. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/local.tgz.ve +0 -0
  61. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/plain.hdd/DiskDescriptor.xml +0 -0
  62. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/plain.hdd/plain.hdd +0 -0
  63. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/plain.hdd/plain.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  64. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/sesparse.vmdk.gz +0 -0
  65. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/split.hdd/DiskDescriptor.xml +0 -0
  66. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/split.hdd/split.hdd +0 -0
  67. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/split.hdd/split.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  68. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/split.hdd/split.hdd.1.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  69. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/split.hdd/split.hdd.2.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  70. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/split.hdd/split.hdd.3.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  71. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/split.hdd/split.hdd.4.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  72. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/split.hdd/split.hdd.5.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
  73. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/test.VMRS +0 -0
  74. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/test.vgz +0 -0
  75. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/data/test.vmcx +0 -0
  76. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/docs/Makefile +0 -0
  77. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/docs/conf.py +0 -0
  78. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/docs/index.rst +0 -0
  79. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/test_envelope.py +0 -0
  80. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/test_hdd.py +0 -0
  81. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/test_hyperv.py +0 -0
  82. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/test_ovf.py +0 -0
  83. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/test_pvs.py +0 -0
  84. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/test_vbox.py +0 -0
  85. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/test_vhd.py +0 -0
  86. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/test_vhdx.py +0 -0
  87. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/test_vmdk.py +0 -0
  88. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/test_vmtar.py +0 -0
  89. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tests/test_vmx.py +0 -0
  90. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev2}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.hypervisor
3
- Version: 3.16.dev1
3
+ Version: 3.16.dev2
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: Affero General Public License v3
@@ -27,7 +27,6 @@ Requires-Dist: dissect.cstruct<5,>=4
27
27
  Requires-Dist: dissect.util<4,>=3
28
28
  Provides-Extra: full
29
29
  Requires-Dist: pycryptodome; extra == "full"
30
- Requires-Dist: rich; extra == "full"
31
30
  Provides-Extra: dev
32
31
  Requires-Dist: dissect.hypervisor[full]; extra == "dev"
33
32
  Requires-Dist: dissect.cstruct<5.0.dev,>=4.0.dev; extra == "dev"
@@ -1,4 +1,3 @@
1
- from dissect.hypervisor.backup import vma, xva
2
1
  from dissect.hypervisor.descriptor import hyperv, ovf, pvs, vbox, vmx
3
2
  from dissect.hypervisor.disk import hdd, qcow2, vdi, vhd, vhdx, vmdk
4
3
  from dissect.hypervisor.util import envelope, vmtar
@@ -14,9 +13,7 @@ __all__ = [
14
13
  "vdi",
15
14
  "vhd",
16
15
  "vhdx",
17
- "vma",
18
16
  "vmdk",
19
17
  "vmtar",
20
18
  "vmx",
21
- "xva",
22
19
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.hypervisor
3
- Version: 3.16.dev1
3
+ Version: 3.16.dev2
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: Affero General Public License v3
@@ -27,7 +27,6 @@ Requires-Dist: dissect.cstruct<5,>=4
27
27
  Requires-Dist: dissect.util<4,>=3
28
28
  Provides-Extra: full
29
29
  Requires-Dist: pycryptodome; extra == "full"
30
- Requires-Dist: rich; extra == "full"
31
30
  Provides-Extra: dev
32
31
  Requires-Dist: dissect.hypervisor[full]; extra == "dev"
33
32
  Requires-Dist: dissect.cstruct<5.0.dev,>=4.0.dev; extra == "dev"
@@ -12,10 +12,6 @@ dissect.hypervisor.egg-info/requires.txt
12
12
  dissect.hypervisor.egg-info/top_level.txt
13
13
  dissect/hypervisor/__init__.py
14
14
  dissect/hypervisor/exceptions.py
15
- dissect/hypervisor/backup/__init__.py
16
- dissect/hypervisor/backup/c_vma.py
17
- dissect/hypervisor/backup/vma.py
18
- dissect/hypervisor/backup/xva.py
19
15
  dissect/hypervisor/descriptor/__init__.py
20
16
  dissect/hypervisor/descriptor/c_hyperv.py
21
17
  dissect/hypervisor/descriptor/hyperv.py
@@ -38,7 +34,6 @@ dissect/hypervisor/disk/vhdx.py
38
34
  dissect/hypervisor/disk/vmdk.py
39
35
  dissect/hypervisor/tools/__init__.py
40
36
  dissect/hypervisor/tools/envelope.py
41
- dissect/hypervisor/tools/vma.py
42
37
  dissect/hypervisor/util/__init__.py
43
38
  dissect/hypervisor/util/envelope.py
44
39
  dissect/hypervisor/util/vmtar.py
@@ -52,7 +47,6 @@ tests/test_pvs.py
52
47
  tests/test_vbox.py
53
48
  tests/test_vhd.py
54
49
  tests/test_vhdx.py
55
- tests/test_vma.py
56
50
  tests/test_vmdk.py
57
51
  tests/test_vmtar.py
58
52
  tests/test_vmx.py
@@ -67,7 +61,6 @@ tests/data/local.tgz.ve
67
61
  tests/data/sesparse.vmdk.gz
68
62
  tests/data/test.VMRS
69
63
  tests/data/test.vgz
70
- tests/data/test.vma.gz
71
64
  tests/data/test.vmcx
72
65
  tests/data/expanding.hdd/DiskDescriptor.xml
73
66
  tests/data/expanding.hdd/expanding.hdd
@@ -1,3 +1,2 @@
1
1
  [console_scripts]
2
2
  envelope-decrypt = dissect.hypervisor.tools.envelope:main
3
- vma-extract = dissect.hypervisor.tools.vma:main
@@ -9,4 +9,3 @@ dissect.util<4.0.dev,>=3.0.dev
9
9
 
10
10
  [full]
11
11
  pycryptodome
12
- rich
@@ -39,7 +39,6 @@ repository = "https://github.com/fox-it/dissect.hypervisor"
39
39
  [project.optional-dependencies]
40
40
  full = [
41
41
  "pycryptodome",
42
- "rich",
43
42
  ]
44
43
  dev = [
45
44
  "dissect.hypervisor[full]",
@@ -48,7 +47,6 @@ dev = [
48
47
  ]
49
48
 
50
49
  [project.scripts]
51
- vma-extract = "dissect.hypervisor.tools.vma:main"
52
50
  envelope-decrypt = "dissect.hypervisor.tools.envelope:main"
53
51
 
54
52
  [tool.black]
@@ -79,11 +79,6 @@ def split_hdd() -> Iterator[str]:
79
79
  yield absolute_path("data/split.hdd")
80
80
 
81
81
 
82
- @pytest.fixture
83
- def simple_vma() -> Iterator[BinaryIO]:
84
- yield from open_file_gz("data/test.vma.gz")
85
-
86
-
87
82
  @pytest.fixture
88
83
  def envelope() -> Iterator[BinaryIO]:
89
84
  yield from open_file("data/local.tgz.ve")
@@ -1,60 +0,0 @@
1
- from dissect.cstruct import cstruct
2
-
3
- vma_def = """
4
- #define VMA_BLOCK_BITS 12
5
- #define VMA_BLOCK_SIZE (1 << VMA_BLOCK_BITS)
6
- #define VMA_CLUSTER_BITS (VMA_BLOCK_BITS + 4)
7
- #define VMA_CLUSTER_SIZE (1 << VMA_CLUSTER_BITS)
8
-
9
- #define VMA_EXTENT_HEADER_SIZE 512
10
- #define VMA_BLOCKS_PER_EXTENT 59
11
- #define VMA_MAX_CONFIGS 256
12
-
13
- #define VMA_MAX_EXTENT_SIZE (VMA_EXTENT_HEADER_SIZE + VMA_CLUSTER_SIZE * VMA_BLOCKS_PER_EXTENT)
14
-
15
- /* File Format Definitions */
16
-
17
- struct VmaDeviceInfoHeader {
18
- uint32 devname_ptr; /* offset into blob_buffer table */
19
- uint32 reserved0;
20
- uint64 size; /* device size in bytes */
21
- uint64 reserved1;
22
- uint64 reserved2;
23
- };
24
-
25
- struct VmaHeader {
26
- char magic[4];
27
- uint32 version;
28
- char uuid[16];
29
- int64 ctime;
30
- char md5sum[16];
31
-
32
- uint32 blob_buffer_offset;
33
- uint32 blob_buffer_size;
34
- uint32 header_size;
35
-
36
- char _reserved1[1984];
37
-
38
- uint32 config_names[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
39
- uint32 config_data[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
40
-
41
- char _reserved2[4];
42
-
43
- VmaDeviceInfoHeader dev_info[256];
44
- };
45
-
46
- struct VmaExtentHeader {
47
- char magic[4];
48
- uint16 reserved1;
49
- uint16 block_count;
50
- char uuid[16];
51
- char md5sum[16];
52
- uint64 blockinfo[VMA_BLOCKS_PER_EXTENT];
53
- };
54
- """
55
-
56
- c_vma = cstruct(endian=">").load(vma_def)
57
-
58
-
59
- VMA_MAGIC = b"VMA\x00"
60
- VMA_EXTENT_MAGIC = b"VMAE"
@@ -1,269 +0,0 @@
1
- # References:
2
- # - https://git.proxmox.com/?p=pve-qemu.git;a=blob;f=vma_spec.txt
3
- # - https://lists.gnu.org/archive/html/qemu-devel/2013-02/msg03667.html
4
-
5
- import hashlib
6
- import struct
7
- from collections import defaultdict
8
- from functools import lru_cache
9
- from uuid import UUID
10
-
11
- from dissect.util import ts
12
- from dissect.util.stream import AlignedStream
13
-
14
- from dissect.hypervisor.backup.c_vma import VMA_EXTENT_MAGIC, VMA_MAGIC, c_vma
15
- from dissect.hypervisor.exceptions import InvalidHeaderError
16
-
17
-
18
- class VMA:
19
- """Proxmox VMA.
20
-
21
- Parse and provide a readable object for devices in a Proxmox VMA backup file.
22
- VMA is designed to be streamed for extraction, so we need to do some funny stuff to create a readable
23
- object from it. Performance is not optimal, so it's generally advised to extract a VMA instead.
24
- The vma-extract utility can be used for that.
25
- """
26
-
27
- def __init__(self, fh):
28
- self.fh = fh
29
-
30
- offset = fh.tell()
31
- self.header = c_vma.VmaHeader(fh)
32
- if self.header.magic != VMA_MAGIC:
33
- raise InvalidHeaderError("Invalid VMA header magic")
34
-
35
- fh.seek(offset)
36
- header_data = bytearray(fh.read(self.header.header_size))
37
- header_data[32:48] = b"\x00" * 16
38
- if hashlib.md5(header_data).digest() != self.header.md5sum:
39
- raise InvalidHeaderError("Invalid VMA checksum")
40
-
41
- self.version = self.header.version
42
- self.uuid = UUID(bytes=self.header.uuid)
43
-
44
- blob_start = self.header.blob_buffer_offset
45
- blob_end = self.header.blob_buffer_offset + self.header.blob_buffer_size
46
- self._blob = memoryview(bytes(header_data))[blob_start:blob_end]
47
-
48
- blob_offset = 1
49
- self._blob_data = {}
50
- while blob_offset + 2 <= self.header.blob_buffer_size:
51
- # The header is in big endian, but this is little...
52
- size = struct.unpack("<H", self._blob[blob_offset : blob_offset + 2])[0]
53
- if blob_offset + 2 + size <= blob_end:
54
- self._blob_data[blob_offset] = self._blob[blob_offset + 2 : blob_offset + 2 + size].tobytes()
55
- blob_offset += size + 2
56
-
57
- self._config = {}
58
- for conf_name, conf_data in zip(self.header.config_names, self.header.config_data):
59
- if (conf_name, conf_data) == (0, 0):
60
- continue
61
-
62
- self._config[self.blob_string(conf_name)] = self.blob_data(conf_data)
63
-
64
- self._devices = {}
65
- for dev_id, dev_info in enumerate(self.header.dev_info):
66
- if dev_id == 0 or dev_info.devname_ptr == 0:
67
- continue
68
-
69
- self._devices[dev_id] = Device(self, dev_id, self.blob_string(dev_info.devname_ptr), dev_info.size)
70
-
71
- self._extent = lru_cache(65536)(self._extent)
72
-
73
- @property
74
- def creation_time(self):
75
- return ts.from_unix(self.header.ctime)
76
-
77
- def blob_data(self, offset):
78
- if offset not in self._blob_data:
79
- raise KeyError(f"No blob data for offset {offset}")
80
- return self._blob_data[offset]
81
-
82
- def blob_string(self, offset):
83
- return self.blob_data(offset).decode().rstrip("\x00")
84
-
85
- def config(self, name):
86
- return self._config[name]
87
-
88
- def configs(self):
89
- return self._config
90
-
91
- def device(self, dev_id):
92
- return self._devices[dev_id]
93
-
94
- def devices(self):
95
- return list(self._devices.values())
96
-
97
- def _extent(self, offset):
98
- return Extent(self.fh, offset)
99
-
100
- def extents(self):
101
- offset = self.header.header_size
102
- while True:
103
- try:
104
- extent = self._extent(offset)
105
- except EOFError:
106
- break
107
-
108
- yield extent
109
-
110
- offset += c_vma.VMA_EXTENT_HEADER_SIZE + extent.size
111
-
112
-
113
- class Device:
114
- def __init__(self, vma, dev_id, name, size):
115
- self.vma = vma
116
- self.id = dev_id
117
- self.name = name
118
- self.size = size
119
-
120
- def __repr__(self):
121
- return f"<Device id={self.id} name={self.name} size={self.size}>"
122
-
123
- def open(self):
124
- return DeviceDataStream(self)
125
-
126
-
127
- class Extent:
128
- def __init__(self, fh, offset):
129
- self.fh = fh
130
- self.offset = offset
131
- self.data_offset = offset + c_vma.VMA_EXTENT_HEADER_SIZE
132
-
133
- self.fh.seek(offset)
134
- header_data = bytearray(fh.read(c_vma.VMA_EXTENT_HEADER_SIZE))
135
- self.header = c_vma.VmaExtentHeader(header_data)
136
- if self.header.magic != VMA_EXTENT_MAGIC:
137
- raise InvalidHeaderError("Invalid VMA extent header magic")
138
-
139
- header_data[24:40] = b"\x00" * 16
140
- if hashlib.md5(header_data).digest() != self.header.md5sum:
141
- raise InvalidHeaderError("Invalid VMA extent checksum")
142
-
143
- self.uuid = UUID(bytes=self.header.uuid)
144
- self.size = self.header.block_count * c_vma.VMA_BLOCK_SIZE
145
-
146
- # Keep track of the lowest and highest cluster we have for any device
147
- # We can use this to speed up extent lookup later on
148
- # There are at most 59 entries, so safe to parse ahead of use
149
- self._min = {}
150
- self._max = {}
151
- self.blocks = defaultdict(list)
152
- block_offset = self.data_offset
153
- for block_info in self.header.blockinfo:
154
- cluster_num = block_info & 0xFFFFFFFF
155
- dev_id = (block_info >> 32) & 0xFF
156
- mask = block_info >> (32 + 16)
157
-
158
- if dev_id == 0:
159
- continue
160
-
161
- if dev_id not in self._min:
162
- self._min[dev_id] = cluster_num
163
- self._max[dev_id] = cluster_num
164
- elif cluster_num < self._min[dev_id]:
165
- self._min[dev_id] = cluster_num
166
- elif cluster_num > self._max[dev_id]:
167
- self._max[dev_id] = cluster_num
168
-
169
- self.blocks[dev_id].append((cluster_num, mask, block_offset))
170
-
171
- if mask == 0xFFFF:
172
- block_offset += 16 * c_vma.VMA_BLOCK_SIZE
173
- elif mask == 0:
174
- pass
175
- else:
176
- block_offset += bin(mask).count("1") * c_vma.VMA_BLOCK_SIZE
177
-
178
- def __repr__(self):
179
- return f"<Extent offset=0x{self.offset:x} size=0x{self.size:x}>"
180
-
181
-
182
- class DeviceDataStream(AlignedStream):
183
- def __init__(self, device):
184
- self.device = device
185
- self.vma = device.vma
186
- super().__init__(size=device.size, align=c_vma.VMA_CLUSTER_SIZE)
187
-
188
- def _read(self, offset, length):
189
- cluster_offset = offset // c_vma.VMA_CLUSTER_SIZE
190
- cluster_count = (length + c_vma.VMA_CLUSTER_SIZE - 1) // c_vma.VMA_CLUSTER_SIZE
191
- block_count = (length + c_vma.VMA_BLOCK_SIZE - 1) // c_vma.VMA_BLOCK_SIZE
192
-
193
- result = []
194
- for _, mask, block_offset in _iter_clusters(self.vma, self.device.id, cluster_offset, cluster_count):
195
- read_count = min(block_count, 16)
196
-
197
- # Optimize reading fully set and fully sparse masks
198
- if mask == 0xFFFF:
199
- self.vma.fh.seek(block_offset)
200
- result.append(self.vma.fh.read(c_vma.VMA_BLOCK_SIZE * read_count))
201
- elif mask == 0:
202
- result.append(b"\x00" * read_count * c_vma.VMA_BLOCK_SIZE)
203
- else:
204
- self.vma.fh.seek(block_offset)
205
- for allocated, count in _iter_mask(mask, read_count):
206
- if allocated:
207
- result.append(self.vma.fh.read(c_vma.VMA_BLOCK_SIZE * count))
208
- else:
209
- result.append(b"\x00" * count * c_vma.VMA_BLOCK_SIZE)
210
-
211
- block_count -= read_count
212
- if block_count == 0:
213
- break
214
-
215
- return b"".join(result)
216
-
217
-
218
- def _iter_clusters(vma, dev_id, cluster, count):
219
- # Find clusters and starting offsets in all extents
220
- temp = {}
221
- end = cluster + count
222
-
223
- for extent in vma.extents():
224
- if dev_id not in extent.blocks:
225
- continue
226
-
227
- if end < extent._min[dev_id] or cluster > extent._max[dev_id]:
228
- continue
229
-
230
- for cluster_num, mask, block_offset in extent.blocks[dev_id]:
231
- if cluster_num == cluster:
232
- yield cluster_num, mask, block_offset
233
- cluster += 1
234
-
235
- while cluster in temp:
236
- yield temp[cluster]
237
- del temp[cluster]
238
- cluster += 1
239
- elif cluster < cluster_num <= end:
240
- temp[cluster_num] = (cluster_num, mask, block_offset)
241
-
242
- if cluster == end:
243
- break
244
-
245
- if cluster == end:
246
- break
247
-
248
- while cluster in temp:
249
- yield temp[cluster]
250
- del temp[cluster]
251
- cluster += 1
252
-
253
-
254
- def _iter_mask(mask, length):
255
- # Yield consecutive bitmask values
256
- current_status = mask & 1
257
- current_count = 0
258
-
259
- for bit_idx in range(length):
260
- status = (mask & (1 << bit_idx)) >> bit_idx
261
- if status == current_status:
262
- current_count += 1
263
- else:
264
- yield current_status, current_count
265
- current_status = status
266
- current_count = 1
267
-
268
- if current_count:
269
- yield current_status, current_count
@@ -1,136 +0,0 @@
1
- import hashlib
2
- import tarfile
3
- from bisect import bisect_right
4
- from xml.etree import ElementTree
5
-
6
- from dissect.util.stream import AlignedStream
7
-
8
- BLOCK_SIZE = 1024 * 1024
9
-
10
-
11
- class XVA:
12
- """XVA reader.
13
-
14
- XenCenter export format. Basically a tar file with "blocks" of 1MB.
15
- """
16
-
17
- def __init__(self, fh):
18
- # We don't have to cache tar members, tarfile already does that for us
19
- self.tar = tarfile.open(fileobj=fh)
20
- self._ova = None
21
-
22
- @property
23
- def ova(self):
24
- if not self._ova:
25
- ova_member = self.tar.getmember("ova.xml")
26
- ova_fh = self.tar.extractfile(ova_member)
27
- self._ova = ElementTree.fromstring(ova_fh.read())
28
- return self._ova
29
-
30
- def disks(self):
31
- return [
32
- el.text
33
- for el in self.ova.findall(
34
- "*//member/name[.='VDI']/../..//name[.='type']/..value[.='Disk']/../..//name[.='VDI']/../value"
35
- )
36
- ]
37
-
38
- def open(self, ref, verify=False):
39
- size = int(
40
- self.ova.find(f"*//member/name[.='id']/../value[.='{ref}']/../..//name[.='virtual_size']/../value").text
41
- )
42
- return XVAStream(self, ref, size, verify)
43
-
44
-
45
- class XVAStream(AlignedStream):
46
- """XVA stream.
47
-
48
- XenServer usually just streams an XVA file right into an output file, so our use-case requires a bit
49
- more trickery. We generally don't stream directly into an output file, but try to create a file-like
50
- object for other code to use.
51
-
52
- The numbers for the block files (weirdly) don't represent offsets. It's possible for a block file
53
- to be 0 sized, in which case you should "add" that block to the stream, and continue on to the next.
54
- The next block might have a number + 1 of what your current offset is, but it will still contain the
55
- data for that current offset. For this reason we build a lookup list with offsets.
56
- """
57
-
58
- def __init__(self, xva, ref, size, verify=False):
59
- self.xva = xva
60
- self.ref = ref
61
- self.verify = verify
62
-
63
- index = 0
64
- offset = 0
65
- self._lookup = []
66
- self._members = []
67
- for block_index, block_member, checksum_member in _iter_block_files(xva, ref):
68
- if block_index > index + 1:
69
- skipped = block_index - (index + 1)
70
- offset += skipped * BLOCK_SIZE
71
-
72
- if block_member.size != 0:
73
- self._lookup.append(offset)
74
- self._members.append((block_member, checksum_member))
75
-
76
- offset += block_member.size
77
-
78
- index = block_index
79
-
80
- super().__init__(size, align=BLOCK_SIZE)
81
-
82
- def _read(self, offset, length):
83
- result = []
84
-
85
- while length > 0:
86
- # This method is probably sub-optimal, but it's fairly low effort and we rarely encounter XVA anyway
87
- block_idx = bisect_right(self._lookup, offset)
88
- nearest_offset = self._lookup[block_idx - 1]
89
-
90
- if offset >= nearest_offset + BLOCK_SIZE:
91
- result.append(b"\x00" * BLOCK_SIZE)
92
- else:
93
- block_member, checksum_member = self._members[block_idx - 1]
94
- buf = self.xva.tar.extractfile(block_member).read()
95
-
96
- if self.verify:
97
- if checksum_member is None:
98
- raise ValueError(f"No checksum for {block_member.name}")
99
-
100
- if (
101
- checksum_member.name.endswith("checksum")
102
- and hashlib.sha1(buf).hexdigest() != self.xva.tar.extractfile(checksum_member).read().decode()
103
- ):
104
- raise ValueError(f"Invalid checksum for {checksum_member.name}")
105
- else:
106
- raise NotImplementedError(f"Unsupported checksum: {checksum_member.name}")
107
-
108
- result.append(buf)
109
-
110
- offset += BLOCK_SIZE
111
- length -= BLOCK_SIZE
112
-
113
- return b"".join(result)
114
-
115
-
116
- def _iter_block_files(xva, ref):
117
- member_index = None
118
- block_member = None
119
- checksum_member = None
120
-
121
- for member in xva.tar.getmembers():
122
- if not member.name.startswith(ref):
123
- continue
124
-
125
- index = int(member.name.split("/")[-1].split(".")[0])
126
- if member_index is None:
127
- member_index = index
128
-
129
- if member_index != index:
130
- yield (member_index, block_member, checksum_member)
131
- member_index = index
132
-
133
- if member.name.endswith(("checksum", "xxhash")):
134
- checksum_member = member
135
- else:
136
- block_member = member
@@ -1,173 +0,0 @@
1
- import argparse
2
- import logging
3
- import sys
4
- from pathlib import Path
5
-
6
- from dissect.hypervisor.backup.c_vma import c_vma
7
- from dissect.hypervisor.backup.vma import VMA, _iter_mask
8
-
9
- try:
10
- from rich.logging import RichHandler
11
- from rich.progress import (
12
- BarColumn,
13
- DownloadColumn,
14
- Progress,
15
- TextColumn,
16
- TimeRemainingColumn,
17
- TransferSpeedColumn,
18
- )
19
-
20
- progress = Progress(
21
- TextColumn("[bold blue]{task.fields[filename]}", justify="right"),
22
- BarColumn(bar_width=None),
23
- "[progress.percentage]{task.percentage:>3.1f}%",
24
- "•",
25
- DownloadColumn(),
26
- "•",
27
- TransferSpeedColumn(),
28
- "•",
29
- TimeRemainingColumn(),
30
- transient=True,
31
- )
32
- except ImportError:
33
- RichHandler = logging.StreamHandler
34
-
35
- class Progress:
36
- def __init__(self):
37
- self.filename = None
38
- self.total = None
39
-
40
- self._task_id = 0
41
- self._info = {}
42
-
43
- def __enter__(self):
44
- pass
45
-
46
- def __exit__(self, *args, **kwargs):
47
- sys.stderr.write("\n")
48
- sys.stderr.flush()
49
-
50
- def add_task(self, name, filename, total, **kwargs):
51
- task_id = self._task_id
52
- self._task_id += 1
53
-
54
- self._info[task_id] = {"filename": filename, "total": total, "position": 0}
55
-
56
- return task_id
57
-
58
- def update(self, task_id, advance):
59
- self._info[task_id]["position"] += advance
60
- self.draw()
61
-
62
- def draw(self):
63
- infos = []
64
- for info in self._info.values():
65
- infos.append(f"{info['filename']} {(info['position'] / info['total']) * 100:0.2f}%")
66
- sys.stderr.write("\r" + " | ".join(infos))
67
- sys.stderr.flush()
68
-
69
- progress = Progress()
70
-
71
-
72
- log = logging.getLogger(__name__)
73
-
74
-
75
- def setup_logging(logger, verbosity):
76
- if verbosity == 1:
77
- level = logging.ERROR
78
- elif verbosity == 2:
79
- level = logging.WARNING
80
- elif verbosity == 3:
81
- level = logging.INFO
82
- elif verbosity >= 4:
83
- level = logging.DEBUG
84
- else:
85
- level = logging.CRITICAL
86
-
87
- handler = RichHandler()
88
- handler.setFormatter(logging.Formatter("%(message)s"))
89
- handler.setLevel(level)
90
- logger.addHandler(handler)
91
- logger.setLevel(level)
92
-
93
-
94
- def main():
95
- parser = argparse.ArgumentParser(description="VMA extractor")
96
- parser.add_argument("input", type=Path, help="path to vma file")
97
- parser.add_argument("-o", "--output", type=Path, required=True, help="path to output directory")
98
- parser.add_argument("-v", "--verbose", action="count", default=3, help="increase output verbosity")
99
- args = parser.parse_args()
100
-
101
- setup_logging(log, args.verbose)
102
-
103
- in_file = args.input.resolve()
104
- if not in_file.exists():
105
- log.error("Input file does not exist: %s", in_file)
106
- parser.exit()
107
-
108
- out_dir = args.output.resolve()
109
- if not out_dir.exists():
110
- log.error("Output path does not exist: %s", out_dir)
111
- parser.exit()
112
-
113
- if not out_dir.is_dir():
114
- log.error("Output path is not a directory: %s", out_dir)
115
- parser.exit()
116
-
117
- with in_file.open("rb") as fh:
118
- vma = VMA(fh)
119
-
120
- log.info("Extracting config files")
121
- for config_name, config_data in vma.configs().items():
122
- out_file = out_dir.joinpath(config_name)
123
-
124
- log.info("%s -> %s (%d bytes)", config_name, out_file, len(config_data))
125
- out_file.write_bytes(config_data)
126
-
127
- log.info("Extracting device data")
128
- tasks = {}
129
- handles = {}
130
- for device in vma.devices():
131
- task_id = progress.add_task("extract", filename=device.name, total=device.size)
132
- tasks[device.id] = task_id
133
- handles[device.id] = out_dir.joinpath(device.name).open("wb")
134
-
135
- with progress:
136
- try:
137
- for extent in vma.extents():
138
- vma.fh.seek(extent.data_offset)
139
- for block_info in extent.header.blockinfo:
140
- cluster_num = block_info & 0xFFFFFFFF
141
- dev_id = (block_info >> 32) & 0xFF
142
- mask = block_info >> (32 + 16)
143
-
144
- if dev_id == 0:
145
- continue
146
-
147
- fh_out = handles[dev_id]
148
- fh_out.seek(cluster_num * c_vma.VMA_CLUSTER_SIZE)
149
-
150
- if mask == 0xFFFF:
151
- fh_out.write(vma.fh.read(c_vma.VMA_CLUSTER_SIZE))
152
- elif mask == 0:
153
- fh_out.write(b"\x00" * c_vma.VMA_CLUSTER_SIZE)
154
- else:
155
- for allocated, count in _iter_mask(mask, 16):
156
- if allocated:
157
- fh_out.write(vma.fh.read(count * c_vma.VMA_BLOCK_SIZE))
158
- else:
159
- fh_out.write(b"\x00" * count * c_vma.VMA_BLOCK_SIZE)
160
-
161
- progress.update(tasks[dev_id], advance=c_vma.VMA_CLUSTER_SIZE)
162
- except Exception:
163
- log.exception("Exception during extraction")
164
- finally:
165
- for handle in handles.values():
166
- handle.close()
167
-
168
-
169
- if __name__ == "__main__":
170
- try:
171
- sys.exit(main())
172
- except KeyboardInterrupt:
173
- pass
File without changes
@@ -1,64 +0,0 @@
1
- import hashlib
2
-
3
- from dissect.hypervisor.backup.vma import VMA, _iter_clusters
4
-
5
-
6
- def test_vma(simple_vma):
7
- vma = VMA(simple_vma)
8
-
9
- assert vma.version == 1
10
- assert str(vma.uuid) == "04fc12eb-0fed-4322-9aaa-f4e412f68096"
11
-
12
- assert vma.blob_string(1) == "qemu-server.conf"
13
- assert len(vma.blob_data(20)) == 417
14
- assert vma.blob_string(439) == "drive-scsi0"
15
-
16
- assert vma.config("qemu-server.conf") == vma.blob_data(20)
17
- assert len(vma.configs()) == 1
18
-
19
- assert len(vma.devices()) == 1
20
-
21
- device = vma.device(1)
22
- assert device.id == 1
23
- assert device.name == "drive-scsi0"
24
- assert device.size == 10737418240
25
-
26
- extents = list(vma.extents())
27
- # The test data is just a small piece of a real VMA file
28
- assert len(extents) == 2
29
-
30
- assert list(_iter_clusters(vma, device.id, 0, 23)) == [
31
- (0, 65535, 13312),
32
- (1, 0, 78848),
33
- (2, 0, 78848),
34
- (3, 0, 78848),
35
- (4, 0, 78848),
36
- (5, 0, 78848),
37
- (6, 0, 78848),
38
- (7, 0, 78848),
39
- (8, 0, 78848),
40
- (9, 0, 78848),
41
- (10, 0, 78848),
42
- (11, 0, 78848),
43
- (12, 0, 78848),
44
- (13, 0, 78848),
45
- (14, 0, 78848),
46
- (15, 0, 78848),
47
- (16, 65535, 79360),
48
- (17, 65535, 144896),
49
- (18, 65535, 210432),
50
- (19, 65535, 275968),
51
- (20, 65535, 341504),
52
- (21, 65535, 407040),
53
- (22, 65535, 472576),
54
- ]
55
-
56
- stream = device.open()
57
- buf = stream.read(65536)
58
- assert hashlib.sha256(buf).hexdigest() == "cf4adcf1933a8c9a0a3ff5588e1400e6beea8a32212b3a35ba08c7b08e4e6b1f"
59
-
60
- buf = stream.read(65536 * 15)
61
- assert buf.strip(b"\x00") == b""
62
-
63
- buf = stream.read(65536 * 7)
64
- assert hashlib.sha256(buf).hexdigest() == "8c989a3aa590795fa919ccb7d1f28651c85f8a0d9ba00ab22cdd9fb760fa7955"