dissect.volume 3.12.dev2__tar.gz → 3.13.dev1__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 (137) hide show
  1. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/.gitattributes +1 -0
  2. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/PKG-INFO +1 -1
  3. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/ddf/ddf.py +24 -16
  4. dissect_volume-3.13.dev1/dissect/volume/md/__init__.py +6 -0
  5. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/md/md.py +38 -36
  6. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/raid/raid.py +6 -3
  7. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/raid/stream.py +20 -22
  8. dissect_volume-3.13.dev1/dissect/volume/vinum/c_vinum.py +83 -0
  9. dissect_volume-3.13.dev1/dissect/volume/vinum/config.py +425 -0
  10. dissect_volume-3.13.dev1/dissect/volume/vinum/vinum.py +349 -0
  11. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/PKG-INFO +1 -1
  12. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/SOURCES.txt +19 -0
  13. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/conftest.py +54 -0
  14. dissect_volume-3.13.dev1/tests/data/vinum/vinum-concat_diska.bin.gz +0 -0
  15. dissect_volume-3.13.dev1/tests/data/vinum/vinum-concat_diskb.bin.gz +0 -0
  16. dissect_volume-3.13.dev1/tests/data/vinum/vinum-mirror_diska.bin.gz +0 -0
  17. dissect_volume-3.13.dev1/tests/data/vinum/vinum-mirror_diskb.bin.gz +0 -0
  18. dissect_volume-3.13.dev1/tests/data/vinum/vinum-raid5_diska.bin.gz +0 -0
  19. dissect_volume-3.13.dev1/tests/data/vinum/vinum-raid5_diskb.bin.gz +0 -0
  20. dissect_volume-3.13.dev1/tests/data/vinum/vinum-raid5_diskc.bin.gz +0 -0
  21. dissect_volume-3.13.dev1/tests/data/vinum/vinum-raid5_diskd.bin.gz +0 -0
  22. dissect_volume-3.13.dev1/tests/data/vinum/vinum-striped_diska.bin.gz +0 -0
  23. dissect_volume-3.13.dev1/tests/data/vinum/vinum-striped_diskb.bin.gz +0 -0
  24. dissect_volume-3.13.dev1/tests/data/vinum/vinum-stripedmirror_diska.bin.gz +0 -0
  25. dissect_volume-3.13.dev1/tests/data/vinum/vinum-stripedmirror_diskb.bin.gz +0 -0
  26. dissect_volume-3.13.dev1/tests/data/vinum/vinum-stripedmirror_diskc.bin.gz +0 -0
  27. dissect_volume-3.13.dev1/tests/data/vinum/vinum-stripedmirror_diskd.bin.gz +0 -0
  28. dissect_volume-3.13.dev1/tests/test_vinum.py +238 -0
  29. dissect_volume-3.13.dev1/tests/test_vinum_config.py +460 -0
  30. dissect_volume-3.12.dev2/dissect/volume/md/__init__.py +0 -6
  31. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/COPYRIGHT +0 -0
  32. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/LICENSE +0 -0
  33. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/MANIFEST.in +0 -0
  34. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/README.md +0 -0
  35. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/__init__.py +0 -0
  36. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/ddf/__init__.py +0 -0
  37. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/ddf/c_ddf.py +0 -0
  38. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/disk/__init__.py +0 -0
  39. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/disk/disk.py +0 -0
  40. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/disk/partition.py +0 -0
  41. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/__init__.py +0 -0
  42. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/apm.py +0 -0
  43. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/bsd.py +0 -0
  44. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/gpt.py +0 -0
  45. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/mbr.py +0 -0
  46. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/dm/__init__.py +0 -0
  47. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/dm/btree.py +0 -0
  48. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/dm/c_dm.py +0 -0
  49. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/dm/thin.py +0 -0
  50. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/exceptions.py +0 -0
  51. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/ldm.py +0 -0
  52. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/lvm/__init__.py +0 -0
  53. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/lvm/c_lvm2.py +0 -0
  54. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/lvm/lvm2.py +0 -0
  55. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/lvm/metadata.py +0 -0
  56. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/lvm/physical.py +0 -0
  57. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/md/c_md.py +0 -0
  58. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/raid/__init__.py +0 -0
  59. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect/volume/vss.py +0 -0
  60. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/dependency_links.txt +0 -0
  61. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/requires.txt +0 -0
  62. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/top_level.txt +0 -0
  63. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/pyproject.toml +0 -0
  64. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/setup.cfg +0 -0
  65. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/__init__.py +0 -0
  66. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/apm.bin +0 -0
  67. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/bsd.bin +0 -0
  68. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/bsd64.bin.gz +0 -0
  69. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-1.bin.gz +0 -0
  70. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-2.bin.gz +0 -0
  71. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-3.bin.gz +0 -0
  72. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-raid1-1.bin.gz +0 -0
  73. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-raid1-2.bin.gz +0 -0
  74. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid1-1.bin.gz +0 -0
  75. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid1-2.bin.gz +0 -0
  76. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid10-1.bin.gz +0 -0
  77. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid10-2.bin.gz +0 -0
  78. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid10-3.bin.gz +0 -0
  79. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid10-4.bin.gz +0 -0
  80. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid4-1.bin.gz +0 -0
  81. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid4-2.bin.gz +0 -0
  82. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid5-1.bin.gz +0 -0
  83. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid5-2.bin.gz +0 -0
  84. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid5-3.bin.gz +0 -0
  85. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid6-1.bin.gz +0 -0
  86. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid6-2.bin.gz +0 -0
  87. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid6-3.bin.gz +0 -0
  88. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid6-4.bin.gz +0 -0
  89. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/dm/dm-thin-data.bin.gz +0 -0
  90. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/dm/dm-thin-empty-data.bin.gz +0 -0
  91. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/dm/dm-thin-empty-metadata.bin.gz +0 -0
  92. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/dm/dm-thin-metadata.bin.gz +0 -0
  93. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/gpt.bin +0 -0
  94. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/gpt_4k.bin +0 -0
  95. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/gpt_esxi.bin +0 -0
  96. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/gpt_hybrid.bin +0 -0
  97. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/gpt_no_name_xff.bin +0 -0
  98. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/lvm/lvm-mirror-1.bin.gz +0 -0
  99. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/lvm/lvm-mirror-2.bin.gz +0 -0
  100. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/lvm/lvm-thin.bin.gz +0 -0
  101. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/lvm/lvm.bin.gz +0 -0
  102. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/mbr.bin +0 -0
  103. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-90-1.bin.gz +0 -0
  104. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-90-2.bin.gz +0 -0
  105. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-90-raid1-1.bin.gz +0 -0
  106. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-90-raid1-2.bin.gz +0 -0
  107. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-linear-1.bin.gz +0 -0
  108. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-linear-2.bin.gz +0 -0
  109. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid0-1.bin.gz +0 -0
  110. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid0-2.bin.gz +0 -0
  111. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid0-3.bin.gz +0 -0
  112. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid1-1.bin.gz +0 -0
  113. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid1-2.bin.gz +0 -0
  114. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid10-1.bin.gz +0 -0
  115. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid10-2.bin.gz +0 -0
  116. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid4-1.bin.gz +0 -0
  117. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid4-2.bin.gz +0 -0
  118. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid4-3.bin.gz +0 -0
  119. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid5-1.bin.gz +0 -0
  120. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid5-2.bin.gz +0 -0
  121. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid5-3.bin.gz +0 -0
  122. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid6-1.bin.gz +0 -0
  123. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid6-2.bin.gz +0 -0
  124. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid6-3.bin.gz +0 -0
  125. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/data/md/md-raid6-4.bin.gz +0 -0
  126. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/docs/Makefile +0 -0
  127. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/docs/conf.py +0 -0
  128. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/docs/index.rst +0 -0
  129. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/test_apm.py +0 -0
  130. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/test_bsd.py +0 -0
  131. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/test_ddf.py +0 -0
  132. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/test_dm.py +0 -0
  133. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/test_gpt.py +0 -0
  134. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/test_lvm.py +0 -0
  135. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/test_mbr.py +0 -0
  136. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tests/test_md.py +0 -0
  137. {dissect_volume-3.12.dev2 → dissect_volume-3.13.dev1}/tox.ini +0 -0
@@ -2,3 +2,4 @@ tests/data/dm/* filter=lfs diff=lfs merge=lfs -text
2
2
  tests/data/md/* filter=lfs diff=lfs merge=lfs -text
3
3
  tests/data/ddf/* filter=lfs diff=lfs merge=lfs -text
4
4
  tests/data/lvm/* filter=lfs diff=lfs merge=lfs -text
5
+ tests/data/vinum/* filter=lfs diff=lfs merge=lfs -text
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.volume
3
- Version: 3.12.dev2
3
+ Version: 3.13.dev1
4
4
  Summary: A Dissect module implementing a parser for different disk volume and partition systems, for example LVM2, GPT and MBR
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -1,43 +1,51 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import io
4
- from typing import BinaryIO, Union
4
+ from typing import TYPE_CHECKING, BinaryIO
5
5
 
6
6
  from dissect.util import ts
7
7
 
8
8
  from dissect.volume.ddf.c_ddf import DEFAULT_SECTOR_SIZE, c_ddf
9
9
  from dissect.volume.exceptions import DDFError
10
- from dissect.volume.raid.raid import RAID, Configuration, PhysicalDisk, VirtualDisk
10
+ from dissect.volume.raid.raid import (
11
+ RAID,
12
+ Configuration,
13
+ DiskMap,
14
+ PhysicalDisk,
15
+ VirtualDisk,
16
+ )
11
17
  from dissect.volume.raid.stream import Layout, Level
12
18
 
19
+ if TYPE_CHECKING:
20
+ DDFPhysicalDiskDescriptor = BinaryIO | "DDFPhysicalDisk"
21
+
13
22
  DECADE = 3600 * 24 * (365 * 10 + 2)
14
23
 
15
24
 
16
25
  class DDF(RAID):
17
- def __init__(self, fh: list[Union[BinaryIO, DDFPhysicalDisk]], sector_size: int = DEFAULT_SECTOR_SIZE):
26
+ def __init__(
27
+ self,
28
+ fh: list[DDFPhysicalDiskDescriptor] | DDFPhysicalDiskDescriptor,
29
+ sector_size: int = DEFAULT_SECTOR_SIZE,
30
+ ):
18
31
  fhs = [fh] if not isinstance(fh, list) else fh
19
- self.disks = [DDFPhysicalDisk(f, sector_size) if not isinstance(f, DDFPhysicalDisk) else f for f in fhs]
20
- self.sector_size = sector_size
32
+ physical_disks = [DDFPhysicalDisk(f, sector_size) if not isinstance(f, DDFPhysicalDisk) else f for f in fhs]
21
33
 
22
34
  config_map = {}
23
- for pd in self.disks:
35
+ for pd in physical_disks:
24
36
  config_map.setdefault(pd.anchor.DDF_Header_GUID, []).append(pd)
25
37
 
26
38
  super().__init__([DDFConfiguration(disks) for disks in config_map.values()])
27
39
 
28
40
 
29
41
  class DDFConfiguration(Configuration):
30
- def __init__(self, fh: list[Union[BinaryIO, PhysicalDisk]], sector_size: int = DEFAULT_SECTOR_SIZE):
31
- fhs = [fh] if not isinstance(fh, list) else fh
32
- self.disks = [DDFPhysicalDisk(f, sector_size) if not isinstance(f, DDFPhysicalDisk) else f for f in fhs]
33
- self.sector_size = sector_size
34
-
42
+ def __init__(self, physical_disks: list[DDFPhysicalDisk]):
35
43
  pd_map: dict[int, DDFPhysicalDisk] = {}
36
44
  vde_map: dict[bytes, VirtualDiskRecord] = {}
37
45
  vdcr_map: dict[bytes, VirtualDiskConfigurationRecord] = {}
38
46
  vdcr_uniq: dict[tuple[bytes, int], VirtualDiskConfigurationRecord] = {}
39
47
 
40
- for pd in self.disks:
48
+ for pd in physical_disks:
41
49
  pd_map[pd.reference] = pd
42
50
  vde_map.update({vde.guid: vde for vde in pd.virtual_disk_records})
43
51
  vdcr_map.update({vdcr.guid: vdcr for vdcr in pd.virtual_disk_configuration_records})
@@ -61,7 +69,7 @@ class DDFConfiguration(Configuration):
61
69
  i += 1
62
70
 
63
71
  virtual_disks = [DDFVirtualDisk(vdcr_map[guid], vde_map[guid], vd_map[guid]) for guid in vd_map.keys()]
64
- super().__init__(self.disks, virtual_disks)
72
+ super().__init__(physical_disks, virtual_disks)
65
73
 
66
74
 
67
75
  class DDFVirtualDisk(VirtualDisk):
@@ -69,14 +77,14 @@ class DDFVirtualDisk(VirtualDisk):
69
77
  self,
70
78
  vdcr: VirtualDiskConfigurationRecord,
71
79
  vdr: VirtualDiskRecord,
72
- disks: dict[int, tuple[int, DDFPhysicalDisk]],
80
+ disks: DiskMap,
73
81
  ):
74
82
  self.vdcr = vdcr
75
83
  self.vdr = vdr
76
84
  self.disks = disks
77
85
 
78
86
  if (block_size := self.vdcr.block_size) == 0xFFFF:
79
- block_size = list(self.disks.values())[0][1].block_size
87
+ block_size = list(disks.values())[0][1].block_size
80
88
 
81
89
  level, layout, num_disks = _convert_raid_layout(
82
90
  vdcr.primary_raid_level,
@@ -218,7 +226,7 @@ class VirtualDiskRecord:
218
226
  self.state = self.header.VD_State
219
227
  self.init_state = self.header.Init_State
220
228
  name = self.header.VD_Name.split(b"\x00")[0]
221
- self.name = name.decode("utf-8") if self.type & 0x02 else name.decode()
229
+ self.name = name.decode(encoding="utf-8") if self.type & 0x02 else name.decode(encoding="ascii")
222
230
 
223
231
  def __repr__(self) -> str:
224
232
  return f"<VirtualDiskRecord guid={self.guid} number={self.number} type={self.type:#x} name={self.name!r}>"
@@ -0,0 +1,6 @@
1
+ from dissect.volume.md.md import MD, MDPhysicalDisk
2
+
3
+ __all__ = [
4
+ "MD",
5
+ "MDPhysicalDisk",
6
+ ]
@@ -4,7 +4,7 @@ import datetime
4
4
  import io
5
5
  import operator
6
6
  import struct
7
- from typing import BinaryIO, Union
7
+ from typing import TYPE_CHECKING, BinaryIO
8
8
  from uuid import UUID
9
9
 
10
10
  from dissect.util import ts
@@ -13,7 +13,8 @@ from dissect.volume.md.c_md import SECTOR_SIZE, c_md
13
13
  from dissect.volume.raid.raid import RAID, Configuration, PhysicalDisk, VirtualDisk
14
14
  from dissect.volume.raid.stream import Level
15
15
 
16
- DeviceDescriptor = Union[BinaryIO, "Device"]
16
+ if TYPE_CHECKING:
17
+ MDPhysicalDiskDescriptor = BinaryIO | "MDPhysicalDisk"
17
18
 
18
19
 
19
20
  class MD(RAID):
@@ -22,62 +23,63 @@ class MD(RAID):
22
23
  Use this class to read from a RAID set.
23
24
 
24
25
  Args:
25
- fh: A single file-like object or :class:`Device`, or a list of multiple belonging to the same RAID set.
26
+ fh: A single file-like object or :class:`MDPhysicalDisk`, or a list of multiple belonging to the same RAID set.
26
27
  """
27
28
 
28
- def __init__(self, fh: Union[list[DeviceDescriptor], DeviceDescriptor]):
29
+ def __init__(self, fh: list[MDPhysicalDiskDescriptor] | MDPhysicalDiskDescriptor):
29
30
  fhs = [fh] if not isinstance(fh, list) else fh
30
- self.devices = [Device(fh) if not isinstance(fh, Device) else fh for fh in fhs]
31
+ physical_disks = [MDPhysicalDisk(fh) if not isinstance(fh, MDPhysicalDisk) else fh for fh in fhs]
31
32
 
32
33
  config_map = {}
33
- for dev in self.devices:
34
- config_map.setdefault(dev.set_uuid, []).append(dev)
34
+ for disk in physical_disks:
35
+ config_map.setdefault(disk.set_uuid, []).append(disk)
35
36
 
36
- super().__init__([MDConfiguration(devices) for devices in config_map.values()])
37
+ super().__init__([MDConfiguration(disks) for disks in config_map.values()])
37
38
 
38
39
 
39
40
  class MDConfiguration(Configuration):
40
- def __init__(self, devices: list[DeviceDescriptor]):
41
- devices = [Device(fh) if not isinstance(fh, Device) else fh for fh in devices]
41
+ def __init__(self, physical_disks: list[MDPhysicalDisk]):
42
+ physical_disks = sorted(physical_disks, key=operator.attrgetter("raid_disk"))
42
43
 
43
- self.devices = sorted(devices, key=operator.attrgetter("raid_disk"))
44
- if len({dev.set_uuid for dev in self.devices}) != 1:
45
- raise ValueError("Multiple MD sets detected, supply only the devices of a single set")
44
+ if len({disk.set_uuid for disk in physical_disks}) != 1:
45
+ raise ValueError("Multiple MD sets detected, supply only the disks of a single set")
46
46
 
47
- virtual_disk = MDDisk(self)
48
- super().__init__(self.devices, [virtual_disk])
47
+ virtual_disks = [MDVirtualDisk(physical_disks)]
48
+ super().__init__(physical_disks, virtual_disks)
49
49
 
50
50
 
51
- class MDDisk(VirtualDisk):
52
- def __init__(self, configuration: MDConfiguration):
53
- self.configuration = configuration
54
- reference_dev = sorted(configuration.devices, key=operator.attrgetter("events"), reverse=True)[0]
55
- disks = {dev.raid_disk: (0, dev) for dev in self.configuration.devices if dev.raid_disk is not None}
51
+ class MDVirtualDisk(VirtualDisk):
52
+ def __init__(self, physical_disks: list[MDPhysicalDisk]):
53
+ reference_disk = sorted(physical_disks, key=operator.attrgetter("events"), reverse=True)[0]
54
+ disk_map = {disk.raid_disk: (0, disk) for disk in physical_disks if disk.raid_disk is not None}
56
55
 
57
- if reference_dev.level == Level.LINEAR:
58
- size = sum(disk.size for _, disk in disks.values())
59
- elif reference_dev.level == Level.RAID0:
56
+ if reference_disk.level == Level.LINEAR:
57
+ size = sum(disk.size for _, disk in disk_map.values())
58
+ elif reference_disk.level == Level.RAID0:
60
59
  size = 0
61
- for _, disk in disks.values():
62
- size += disk.size & ~(reference_dev.chunk_size - 1)
63
- elif reference_dev.level in (Level.RAID1, Level.RAID4, Level.RAID5, Level.RAID6, Level.RAID10):
64
- size = reference_dev.sb.size * SECTOR_SIZE
60
+ for _, disk in disk_map.values():
61
+ size += disk.size & ~(reference_disk.chunk_size - 1)
62
+ elif reference_disk.level in (Level.RAID1, Level.RAID4, Level.RAID5, Level.RAID6, Level.RAID10):
63
+ size = reference_disk.sb.size * SECTOR_SIZE
65
64
  else:
66
- raise ValueError("Invalid MD RAID configuration: No valid RAID level found for the reference disk")
65
+ raise ValueError(
66
+ "Invalid MD RAID configuration: No valid RAID level found for the reference disk, found: %d",
67
+ reference_disk.level,
68
+ )
67
69
 
68
70
  super().__init__(
69
- reference_dev.set_name,
70
- reference_dev.set_uuid,
71
+ reference_disk.set_name,
72
+ reference_disk.set_uuid,
71
73
  size,
72
- reference_dev.level,
73
- reference_dev.layout,
74
- reference_dev.chunk_size,
75
- reference_dev.raid_disks,
76
- disks,
74
+ reference_disk.level,
75
+ reference_disk.layout,
76
+ reference_disk.chunk_size,
77
+ reference_disk.raid_disks,
78
+ disk_map,
77
79
  )
78
80
 
79
81
 
80
- class Device(PhysicalDisk):
82
+ class MDPhysicalDisk(PhysicalDisk):
81
83
  """Parse metadata from an MD device.
82
84
 
83
85
  Supports 0.90 and 1.x metadata.
@@ -12,6 +12,9 @@ from dissect.volume.raid.stream import (
12
12
  RAID456Stream,
13
13
  )
14
14
 
15
+ DiskMap = dict[int, tuple[int, "PhysicalDisk"]]
16
+ """A dict of the structure: {disk_idx: {data_offset: PhysicalDisk}}"""
17
+
15
18
 
16
19
  class RAID:
17
20
  def __init__(self, configurations: list[Configuration]):
@@ -45,7 +48,7 @@ class VirtualDisk:
45
48
  layout: int,
46
49
  stripe_size: int,
47
50
  num_disks: int,
48
- physical_disks: dict[int, tuple[int, PhysicalDisk]],
51
+ disk_map: DiskMap,
49
52
  ):
50
53
  self.name = name
51
54
  self.uuid = uuid
@@ -54,7 +57,7 @@ class VirtualDisk:
54
57
  self.layout = layout
55
58
  self.stripe_size = stripe_size
56
59
  self.num_disks = num_disks
57
- self.physical_disks = physical_disks
60
+ self.disk_map = disk_map
58
61
 
59
62
  def open(self) -> BinaryIO:
60
63
  """Return a file-like object of the RAID volume in this set."""
@@ -64,7 +67,7 @@ class VirtualDisk:
64
67
  return RAID0Stream(self)
65
68
  elif self.level == Level.RAID1:
66
69
  # Don't really care which mirror to read from, so just open the first disk
67
- return self.physical_disks[0][1].open()
70
+ return self.disk_map[0][1].open()
68
71
  elif self.level in (Level.RAID4, Level.RAID5, Level.RAID6):
69
72
  return RAID456Stream(self)
70
73
  elif self.level == Level.RAID10:
@@ -8,7 +8,7 @@ from dissect.util.stream import AlignedStream, MappingStream
8
8
  from dissect.volume.exceptions import RAIDError
9
9
 
10
10
  if TYPE_CHECKING:
11
- from dissect.volume.raid.raid import PhysicalDisk, VirtualDisk
11
+ from dissect.volume.raid.raid import DiskMap, PhysicalDisk, VirtualDisk
12
12
 
13
13
 
14
14
  class Level(IntEnum):
@@ -54,13 +54,13 @@ class LinearStream(MappingStream):
54
54
  def __init__(self, virtual_disk: VirtualDisk):
55
55
  super().__init__()
56
56
  self.virtual_disk = virtual_disk
57
+ self.disk_map: DiskMap = dict(sorted(virtual_disk.disk_map.items()))
57
58
 
58
- physical_disks: dict[int, tuple[int, PhysicalDisk]] = dict(sorted(virtual_disk.physical_disks.items()))
59
- if len(physical_disks) != virtual_disk.num_disks:
59
+ if len(self.disk_map) != virtual_disk.num_disks:
60
60
  raise RAIDError(f"Missing disks in linear RAID set {virtual_disk.uuid} ({virtual_disk.name})")
61
61
 
62
62
  offset = 0
63
- for disk_offset, disk in physical_disks.values():
63
+ for disk_offset, disk in self.disk_map.values():
64
64
  self.add(offset, disk.size, disk.open(), disk_offset)
65
65
  offset += disk.size
66
66
 
@@ -68,7 +68,7 @@ class LinearStream(MappingStream):
68
68
  class Zone(NamedTuple):
69
69
  zone_end: int
70
70
  dev_start: int
71
- devices: list[tuple[int, PhysicalDisk]]
71
+ devices: DiskMap
72
72
 
73
73
 
74
74
  class RAID0Stream(AlignedStream):
@@ -76,27 +76,25 @@ class RAID0Stream(AlignedStream):
76
76
 
77
77
  def __init__(self, virtual_disk: VirtualDisk):
78
78
  self.virtual_disk = virtual_disk
79
+ self.disk_map = dict(sorted(self.virtual_disk.disk_map.items()))
79
80
 
80
- disks = self.virtual_disk.physical_disks
81
- if len(disks) != virtual_disk.num_disks:
81
+ if len(self.disk_map) != virtual_disk.num_disks:
82
82
  raise RAIDError(f"Missing disks in RAID0 set {virtual_disk.uuid} ({virtual_disk.name})")
83
83
 
84
84
  # Determine how many strip zones we need to construct
85
85
  # If a RAID0 set consists of devices with different sizes, additional strip zones
86
86
  # may exist on the larger devices but not on the smaller ones
87
87
  # Reference: create_strip_zones
88
- disks: dict[int, tuple[int, PhysicalDisk]] = dict(sorted(disks.items()))
89
- rounded_sizes = {}
90
-
88
+ rounded_sizes: dict[PhysicalDisk, int] = {}
91
89
  stripe_size = virtual_disk.stripe_size
92
90
  num_strip_zones = 0
93
- for idx1, (_, dev1) in disks.items():
91
+ for idx1, (_, dev1) in self.disk_map.items():
94
92
  rounded_sizes[dev1] = (dev1.size // stripe_size) * stripe_size
95
93
 
96
94
  has_same_size = False
97
95
  # Check if dev1 is unequal in size to the sizes of any of the previous devices
98
96
  # If so, this means an extra strip zone is present
99
- for idx2, (_, dev2) in disks.items():
97
+ for idx2, (_, dev2) in self.disk_map.items():
100
98
  if idx1 == idx2:
101
99
  break
102
100
 
@@ -109,25 +107,25 @@ class RAID0Stream(AlignedStream):
109
107
 
110
108
  # Determine the smallest device
111
109
  smallest = None
112
- for _, dev in disks.values():
110
+ for _, dev in self.disk_map.values():
113
111
  if not smallest or rounded_sizes[dev] < rounded_sizes[smallest]:
114
112
  smallest = dev
115
113
 
116
114
  # Construct the strip zones
117
- zones = [Zone(rounded_sizes[smallest] * len(disks), 0, disks)]
115
+ zones = [Zone(rounded_sizes[smallest] * len(self.disk_map), 0, self.disk_map)]
118
116
 
119
117
  cur_zone_end = zones[0].zone_end
120
118
  for _ in range(1, num_strip_zones):
121
- zone_devices = []
119
+ zone_devices = {}
122
120
  dev_start = rounded_sizes[smallest]
123
121
  smallest = None
124
122
 
125
123
  # Look for the next smallest device, that is: the smallest device that is larger than the "dev_start" device
126
- for _, dev in disks.values():
124
+ for disk_idx, (data_offset, dev) in self.disk_map.items():
127
125
  if rounded_sizes[dev] <= dev_start:
128
126
  continue
129
127
 
130
- zone_devices.append(dev)
128
+ zone_devices[disk_idx] = (data_offset, dev)
131
129
  if not smallest or rounded_sizes[dev] < rounded_sizes[smallest]:
132
130
  smallest = dev
133
131
 
@@ -192,8 +190,8 @@ class RAID456Stream(AlignedStream):
192
190
  self.algorithm = self.virtual_disk.layout
193
191
  self.max_degraded = 2 if self.level == 6 else 1
194
192
 
195
- self.disks = self.virtual_disk.physical_disks
196
- if len(self.disks) < self.virtual_disk.num_disks - self.max_degraded:
193
+ self.disk_map = self.virtual_disk.disk_map
194
+ if len(self.disk_map) < self.virtual_disk.num_disks - self.max_degraded:
197
195
  raise RAIDError(f"Missing disks in RAID{self.level} set {virtual_disk.uuid} ({virtual_disk.name})")
198
196
 
199
197
  super().__init__(self.virtual_disk.size, self.virtual_disk.stripe_size)
@@ -362,7 +360,7 @@ class RAID456Stream(AlignedStream):
362
360
  while length:
363
361
  stripe, offset_in_stripe, dd_idx, pd_idx, qd_idx, ddf_layout = self._get_stripe_read_info(offset)
364
362
  offset_in_device = stripe * stripe_size + offset_in_stripe
365
- dd_start, dd_dev = self.disks[dd_idx]
363
+ dd_start, dd_dev = self.disk_map[dd_idx]
366
364
 
367
365
  stripe_remaining = stripe_size - offset_in_stripe
368
366
  read_length = min(length, stripe_remaining)
@@ -383,7 +381,7 @@ class RAID10Stream(AlignedStream):
383
381
  def __init__(self, virtual_disk: VirtualDisk):
384
382
  self.virtual_disk = virtual_disk
385
383
  self.raid_disks = self.virtual_disk.num_disks
386
- self.devices = virtual_disk.physical_disks
384
+ self.disk_map = virtual_disk.disk_map
387
385
 
388
386
  # Reference: setup_geo
389
387
  layout = virtual_disk.layout
@@ -420,7 +418,7 @@ class RAID10Stream(AlignedStream):
420
418
 
421
419
  if self.far_offset:
422
420
  stripe *= self.far_copies
423
- device_start, device = self.devices[dev]
421
+ device_start, device = self.disk_map[dev]
424
422
 
425
423
  stripe_remaining = stripe_size - offset_in_stripe
426
424
  read_length = min(length, stripe_remaining)
@@ -0,0 +1,83 @@
1
+ from dissect.cstruct import cstruct
2
+
3
+ # Structures are copied from:
4
+ # https://github.com/freebsd/freebsd-src/blob/f21a6a6a8fc59393173d9a537ed8cebbdbd6343c/sys/geom/vinum/geom_vinum_var.h
5
+
6
+ vinum_def = """
7
+ struct timeval {
8
+ uint64 sec;
9
+ uint64 usec;
10
+ };
11
+
12
+ typedef uint64 off_t;
13
+
14
+ /*
15
+ * Slice header
16
+ *
17
+ * Vinum drives start with this structure:
18
+ *
19
+ *\ Sector
20
+ * |--------------------------------------|
21
+ * | PDP-11 memorial boot block | 0
22
+ * |--------------------------------------|
23
+ * | Disk label, maybe | 1
24
+ * |--------------------------------------|
25
+ * | Slice definition (vinum_hdr) | 8
26
+ * |--------------------------------------|
27
+ * | |
28
+ * | Configuration info, first copy | 9
29
+ * | |
30
+ * |--------------------------------------|
31
+ * | |
32
+ * | Configuration info, second copy | 9 + size of config
33
+ * | |
34
+ * |--------------------------------------|
35
+ */
36
+
37
+ /* Sizes and offsets of our information. */
38
+ #define GV_HDR_OFFSET 4096 /* Offset of vinum header. */
39
+ #define GV_HDR_LEN 512 /* Size of vinum header. */
40
+ #define GV_CFG_OFFSET 4608 /* Offset of first config copy. */
41
+ #define GV_CFG_LEN 65536 /* Size of config copy. */
42
+
43
+ /* This is where the actual data starts. */
44
+ #define GV_DATA_START (GV_CFG_LEN * 2 + GV_CFG_OFFSET)
45
+ /* #define GV_DATA_START (GV_CFG_LEN * 2 + GV_HDR_LEN) */
46
+
47
+ #define GV_MAXDRIVENAME 32 /* Maximum length of a device name. */
48
+
49
+ /*
50
+ * hostname is 256 bytes long, but we don't need to shlep multiple copies in
51
+ * vinum. We use the host name just to identify this system, and 32 bytes
52
+ * should be ample for that purpose.
53
+ */
54
+
55
+ #define GV_HOSTNAME_LEN 32
56
+ struct gv_label {
57
+ char sysname[GV_HOSTNAME_LEN]; /* System name at creation time. */
58
+ char name[GV_MAXDRIVENAME]; /* Our name of the drive. */
59
+ struct timeval date_of_birth; /* The time it was created ... */
60
+ struct timeval last_update; /* ... and the time of last update. */
61
+ off_t drive_size; /* Total size incl. headers. */
62
+ };
63
+
64
+ #define GV_OLD_MAGIC 0x494E2056494E4F00LL
65
+ #define GV_OLD_NOMAGIC 0x4E4F2056494E4F00LL
66
+ #define GV_MAGIC 0x56494E554D2D3100LL
67
+ #define GV_NOMAGIC 0x56494E554D2D2D00LL
68
+
69
+ /* The 'header' of each valid vinum drive. */
70
+ struct gv_hdr {
71
+ uint64_t magic;
72
+ uint64_t config_length;
73
+ struct gv_label label;
74
+ } header;
75
+ """ # noqa W605
76
+
77
+ c_vinum = cstruct(endian=">").load(vinum_def)
78
+
79
+ # Not really needed as this size is hardcoded in the various GV_*_OFFSET and related values
80
+ SECTOR_SIZE = 512
81
+
82
+ MAGIC_ACTIVE = {c_vinum.GV_OLD_MAGIC, c_vinum.GV_MAGIC}
83
+ MAGIC_INACTIVE = {c_vinum.GV_OLD_NOMAGIC, c_vinum.GV_NOMAGIC}