dissect.volume 3.19.dev2__tar.gz → 3.19.dev3__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 (153) hide show
  1. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/PKG-INFO +1 -1
  2. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/lvm/metadata.py +40 -7
  3. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/lvm/physical.py +10 -5
  4. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect.volume.egg-info/PKG-INFO +1 -1
  5. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect.volume.egg-info/SOURCES.txt +2 -0
  6. dissect_volume-3.19.dev3/tests/_data/lvm/lvm-inconsistent-sizes.bin.gz +0 -0
  7. dissect_volume-3.19.dev3/tests/_tools/lvm/generate.sh +80 -0
  8. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/conftest.py +5 -0
  9. dissect_volume-3.19.dev3/tests/lvm/test_lvm.py +158 -0
  10. dissect_volume-3.19.dev2/tests/lvm/test_lvm.py +0 -84
  11. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/.git-blame-ignore-revs +0 -0
  12. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/.gitattributes +0 -0
  13. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/COPYRIGHT +0 -0
  14. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/LICENSE +0 -0
  15. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/MANIFEST.in +0 -0
  16. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/README.md +0 -0
  17. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/__init__.py +0 -0
  18. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/ddf/__init__.py +0 -0
  19. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/ddf/c_ddf.py +0 -0
  20. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/ddf/c_ddf.pyi +0 -0
  21. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/ddf/ddf.py +0 -0
  22. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/disk/__init__.py +0 -0
  23. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/disk/disk.py +0 -0
  24. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/disk/partition.py +0 -0
  25. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/disk/schemes/__init__.py +0 -0
  26. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/disk/schemes/apm.py +0 -0
  27. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/disk/schemes/bsd.py +0 -0
  28. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/disk/schemes/gpt.py +0 -0
  29. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/disk/schemes/mbr.py +0 -0
  30. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/dm/__init__.py +0 -0
  31. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/dm/btree.py +0 -0
  32. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/dm/c_dm.py +0 -0
  33. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/dm/c_dm.pyi +0 -0
  34. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/dm/thin.py +0 -0
  35. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/exceptions.py +0 -0
  36. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/ldm.py +0 -0
  37. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/lvm/__init__.py +0 -0
  38. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/lvm/c_lvm2.py +0 -0
  39. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/lvm/c_lvm2.pyi +0 -0
  40. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/lvm/lvm2.py +0 -0
  41. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/md/__init__.py +0 -0
  42. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/md/c_md.py +0 -0
  43. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/md/c_md.pyi +0 -0
  44. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/md/md.py +0 -0
  45. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/raid/__init__.py +0 -0
  46. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/raid/raid.py +0 -0
  47. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/raid/stream.py +0 -0
  48. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/vinum/__init__.py +0 -0
  49. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/vinum/c_vinum.py +0 -0
  50. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/vinum/c_vinum.pyi +0 -0
  51. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/vinum/config.py +0 -0
  52. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/vinum/vinum.py +0 -0
  53. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect/volume/vss.py +0 -0
  54. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect.volume.egg-info/dependency_links.txt +0 -0
  55. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect.volume.egg-info/requires.txt +0 -0
  56. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/dissect.volume.egg-info/top_level.txt +0 -0
  57. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/pyproject.toml +0 -0
  58. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/setup.cfg +0 -0
  59. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/__init__.py +0 -0
  60. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid0-1.bin.gz +0 -0
  61. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid0-2.bin.gz +0 -0
  62. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid0-3.bin.gz +0 -0
  63. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid0-raid1-1.bin.gz +0 -0
  64. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid0-raid1-2.bin.gz +0 -0
  65. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid1-1.bin.gz +0 -0
  66. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid1-2.bin.gz +0 -0
  67. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid10-1.bin.gz +0 -0
  68. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid10-2.bin.gz +0 -0
  69. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid10-3.bin.gz +0 -0
  70. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid10-4.bin.gz +0 -0
  71. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid4-1.bin.gz +0 -0
  72. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid4-2.bin.gz +0 -0
  73. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid5-1.bin.gz +0 -0
  74. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid5-2.bin.gz +0 -0
  75. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid5-3.bin.gz +0 -0
  76. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid6-1.bin.gz +0 -0
  77. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid6-2.bin.gz +0 -0
  78. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid6-3.bin.gz +0 -0
  79. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/ddf/ddf-raid6-4.bin.gz +0 -0
  80. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/disk/apm.bin +0 -0
  81. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/disk/bsd.bin +0 -0
  82. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/disk/bsd64.bin.gz +0 -0
  83. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/disk/gpt.bin +0 -0
  84. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/disk/gpt_4k.bin +0 -0
  85. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/disk/gpt_esxi.bin +0 -0
  86. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/disk/gpt_hybrid.bin +0 -0
  87. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/disk/gpt_no_name_xff.bin +0 -0
  88. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/disk/mbr.bin +0 -0
  89. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/dm/dm-thin-data.bin.gz +0 -0
  90. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/dm/dm-thin-empty-data.bin.gz +0 -0
  91. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/dm/dm-thin-empty-metadata.bin.gz +0 -0
  92. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/dm/dm-thin-metadata.bin.gz +0 -0
  93. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/lvm/lvm-mirror-1.bin.gz +0 -0
  94. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/lvm/lvm-mirror-2.bin.gz +0 -0
  95. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/lvm/lvm-thin.bin.gz +0 -0
  96. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/lvm/lvm.bin.gz +0 -0
  97. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-90-1.bin.gz +0 -0
  98. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-90-2.bin.gz +0 -0
  99. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-90-raid1-1.bin.gz +0 -0
  100. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-90-raid1-2.bin.gz +0 -0
  101. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-linear-1.bin.gz +0 -0
  102. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-linear-2.bin.gz +0 -0
  103. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid0-1.bin.gz +0 -0
  104. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid0-2.bin.gz +0 -0
  105. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid0-3.bin.gz +0 -0
  106. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid1-1.bin.gz +0 -0
  107. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid1-2.bin.gz +0 -0
  108. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid10-1.bin.gz +0 -0
  109. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid10-2.bin.gz +0 -0
  110. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid4-1.bin.gz +0 -0
  111. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid4-2.bin.gz +0 -0
  112. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid4-3.bin.gz +0 -0
  113. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid5-1.bin.gz +0 -0
  114. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid5-2.bin.gz +0 -0
  115. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid5-3.bin.gz +0 -0
  116. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid6-1.bin.gz +0 -0
  117. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid6-2.bin.gz +0 -0
  118. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid6-3.bin.gz +0 -0
  119. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/md/md-raid6-4.bin.gz +0 -0
  120. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/vinum/vinum-concat_diska.bin.gz +0 -0
  121. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/vinum/vinum-concat_diskb.bin.gz +0 -0
  122. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/vinum/vinum-mirror_diska.bin.gz +0 -0
  123. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/vinum/vinum-mirror_diskb.bin.gz +0 -0
  124. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/vinum/vinum-raid5_diska.bin.gz +0 -0
  125. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/vinum/vinum-raid5_diskb.bin.gz +0 -0
  126. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/vinum/vinum-raid5_diskc.bin.gz +0 -0
  127. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/vinum/vinum-raid5_diskd.bin.gz +0 -0
  128. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/vinum/vinum-striped_diska.bin.gz +0 -0
  129. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/vinum/vinum-striped_diskb.bin.gz +0 -0
  130. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/vinum/vinum-stripedmirror_diska.bin.gz +0 -0
  131. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/vinum/vinum-stripedmirror_diskb.bin.gz +0 -0
  132. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/vinum/vinum-stripedmirror_diskc.bin.gz +0 -0
  133. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_data/vinum/vinum-stripedmirror_diskd.bin.gz +0 -0
  134. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_docs/Makefile +0 -0
  135. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_docs/__init__.py +0 -0
  136. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_docs/conf.py +0 -0
  137. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/_docs/index.rst +0 -0
  138. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/ddf/__init__.py +0 -0
  139. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/ddf/test_ddf.py +0 -0
  140. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/disk/__init__.py +0 -0
  141. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/disk/test_apm.py +0 -0
  142. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/disk/test_bsd.py +0 -0
  143. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/disk/test_gpt.py +0 -0
  144. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/disk/test_mbr.py +0 -0
  145. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/dm/__init__.py +0 -0
  146. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/dm/test_dm.py +0 -0
  147. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/lvm/__init__.py +0 -0
  148. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/md/__init__.py +0 -0
  149. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/md/test_md.py +0 -0
  150. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/vinum/__init__.py +0 -0
  151. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/vinum/test_config.py +0 -0
  152. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tests/vinum/test_vinum.py +0 -0
  153. {dissect_volume-3.19.dev2 → dissect_volume-3.19.dev3}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.volume
3
- Version: 3.19.dev2
3
+ Version: 3.19.dev3
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-Expression: AGPL-3.0-or-later
@@ -4,7 +4,7 @@ from dataclasses import dataclass
4
4
  from datetime import datetime # noqa: TC003
5
5
  from functools import cache
6
6
  from types import UnionType
7
- from typing import BinaryIO, get_args, get_origin, get_type_hints
7
+ from typing import TYPE_CHECKING, BinaryIO, get_args, get_origin, get_type_hints
8
8
 
9
9
  from dissect.util import ts
10
10
  from dissect.util.stream import MappingStream
@@ -14,6 +14,9 @@ from dissect.volume.exceptions import LVM2Error
14
14
  from dissect.volume.lvm.c_lvm2 import SECTOR_SIZE
15
15
  from dissect.volume.lvm.physical import LVM2Device # noqa: TC001
16
16
 
17
+ if TYPE_CHECKING:
18
+ from dissect.util.stream import RunlistStream
19
+
17
20
 
18
21
  @dataclass(init=False)
19
22
  class MetaBase:
@@ -131,11 +134,46 @@ class PhysicalVolume(MetaBase):
131
134
  def dev(self) -> LVM2Device | None:
132
135
  return self._dev
133
136
 
137
+ @property
138
+ def size(self) -> int:
139
+ """Return the size of the physical volume in bytes.
140
+
141
+ This can sometimes be larger than what the LVM2Device dictates, in which case the VolumeGroup overwrites it:
142
+ https://github.com/lvmteam/lvm2/blob/6e208b81ec587434097de3207fbd1ecd7a0afb8c/lib/format_text/layout.h#L44
143
+
144
+ From the code, this is also the value the pvck tool uses to update pv_header.device_size_xl.
145
+ """
146
+ # This size is also displayed when showing physical device information with pvdisplay:
147
+ # https://github.com/lvmteam/lvm2/blob/6e208b81ec587434097de3207fbd1ecd7a0afb8c/lib/display/display.c#L291
148
+ if self.dev_size:
149
+ return self.dev_size * self.vg.extent_size
150
+ # The size of the stripes on this device + size of the metadata
151
+ return (self.pe_start + self.pe_count * self.vg.extent_size) * SECTOR_SIZE
152
+
134
153
  def _from_dict(self, obj: dict, name: str | None = None, parent: MetaBase | None = None) -> None:
135
154
  super()._from_dict(obj, name=name, parent=parent)
136
155
  self._name = name
137
156
  self._volume_group = parent
138
157
 
158
+ def open(self) -> BinaryIO:
159
+ """Opens the physical device.
160
+
161
+ Automatically uses the corect size as determined by :attr:`size`
162
+ """
163
+ if self.dev is None:
164
+ raise LVM2Error(f"Physical volume not found: {self.name} (id={self.id}, device={self.device})")
165
+
166
+ stream: RunlistStream = self.dev.open()
167
+ if stream.size >= self.size:
168
+ return stream
169
+
170
+ size = max(stream.size, self.size)
171
+ area = self.dev._data_area_descriptors[0]
172
+
173
+ stream.runlist = [(area.offset // SECTOR_SIZE, size // SECTOR_SIZE)]
174
+ stream.size = size
175
+ return stream
176
+
139
177
 
140
178
  @dataclass(init=False)
141
179
  class LogicalVolume(MetaBase):
@@ -343,12 +381,7 @@ class StripedSegment(Segment):
343
381
  offset = 0
344
382
  for pv_name, extent_offset in self.stripes:
345
383
  if pv_name not in opened_pv:
346
- if (pv_dev := pv[pv_name].dev) is not None:
347
- pv_fh = pv_dev.open()
348
- else:
349
- raise LVM2Error(
350
- f"Physical volume not found: {pv_name} (id={pv[pv_name].id}, device={pv[pv_name].device})"
351
- )
384
+ pv_fh = pv[pv_name].open()
352
385
  opened_pv[pv_name] = pv_fh
353
386
  else:
354
387
  pv_fh = opened_pv[pv_name]
@@ -30,11 +30,17 @@ class LVM2Device:
30
30
  raise LVM2Error("Can't find physical volume label header")
31
31
 
32
32
  fh.seek(self.label_offset + self.label.offset)
33
+
33
34
  self.header = c_lvm.pv_header(fh)
34
35
  self.id = self.header.pv_uuid.decode()
35
36
  self.size = self.header.device_size
36
37
 
37
38
  self._data_area_descriptors = _read_descriptors(fh, c_lvm.disk_locn)
39
+
40
+ if len(self._data_area_descriptors) != 1:
41
+ # According to the LVM2 code, there should only be 1 data area descriptor for each Physical Volume
42
+ raise RuntimeError(f"There should be 1 data area descriptor, found {len(self._data_area_descriptors)}")
43
+
38
44
  self._metadata_area_descriptors = _read_descriptors(fh, c_lvm.disk_locn)
39
45
 
40
46
  self._metadata_areas = []
@@ -88,11 +94,10 @@ class LVM2Device:
88
94
  return b"".join(r)
89
95
 
90
96
  def open(self) -> BinaryIO:
91
- runlist = [
92
- (area.offset // SECTOR_SIZE, (area.size or self.size) // SECTOR_SIZE)
93
- for area in self._data_area_descriptors
94
- ]
95
-
97
+ # Only accounts for one data area descriptor
98
+ # When multiple descriptors get allowed in the future, this code should be adjusted to reflect that
99
+ area = self._data_area_descriptors[0]
100
+ runlist = [(area.offset // SECTOR_SIZE, self.size // SECTOR_SIZE)]
96
101
  return RunlistStream(self.fh, runlist, self.size, SECTOR_SIZE)
97
102
 
98
103
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.volume
3
- Version: 3.19.dev2
3
+ Version: 3.19.dev3
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-Expression: AGPL-3.0-or-later
@@ -85,6 +85,7 @@ tests/_data/dm/dm-thin-data.bin.gz
85
85
  tests/_data/dm/dm-thin-empty-data.bin.gz
86
86
  tests/_data/dm/dm-thin-empty-metadata.bin.gz
87
87
  tests/_data/dm/dm-thin-metadata.bin.gz
88
+ tests/_data/lvm/lvm-inconsistent-sizes.bin.gz
88
89
  tests/_data/lvm/lvm-mirror-1.bin.gz
89
90
  tests/_data/lvm/lvm-mirror-2.bin.gz
90
91
  tests/_data/lvm/lvm-thin.bin.gz
@@ -130,6 +131,7 @@ tests/_docs/Makefile
130
131
  tests/_docs/__init__.py
131
132
  tests/_docs/conf.py
132
133
  tests/_docs/index.rst
134
+ tests/_tools/lvm/generate.sh
133
135
  tests/ddf/__init__.py
134
136
  tests/ddf/test_ddf.py
135
137
  tests/disk/__init__.py
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ readonly TESTS_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
6
+ readonly OUT_DIR="${TESTS_ROOT}/_data/lvm"
7
+
8
+ log() { printf '[INFO] %s\n' "$*" >&2; }
9
+ warn() { printf '[WARN] %s\n' "$*" >&2; }
10
+ error() { printf '[ERROR] %s\n' "$*" >&2; }
11
+ have() { command -v "$1" >/dev/null 2>&1; }
12
+
13
+ require_tools() {
14
+ local -a tools=(pvcreate mkfs.ext4 dd xxd)
15
+ local missing=0
16
+
17
+ for t in "${tools[@]}"; do
18
+ if ! have "$t"; then
19
+ error "Missing required tool: $t"
20
+ missing=1
21
+ fi
22
+ done
23
+
24
+ if (( missing != 0 )); then
25
+ error "One or more required tools are missing. Aborting."
26
+ exit 1
27
+ fi
28
+ }
29
+
30
+ function _create_lvm_inconsistent_sizes {
31
+ local filename="$1"
32
+ local uid=$(id -u)
33
+ dd if=/dev/zero of="$filename" bs=1M count=8 >&/dev/null
34
+ log "Created block device $filename"
35
+
36
+ local lo=$(sudo losetup --partscan --show --find "$filename")
37
+
38
+ log "Created loopback device $lo"
39
+ sudo pvcreate "$lo" >/dev/null
40
+ sudo vgcreate vghelp "$lo" >/dev/null
41
+ sudo lvcreate -l 100%FREE -n lv vghelp >/dev/null
42
+ sudo mkfs.ext4 /dev/vghelp/lv >&/dev/null
43
+
44
+ log "Created lvm2 structure"
45
+
46
+
47
+ sudo mount /dev/vghelp/lv /mnt/tmp-lvm --mkdir
48
+ sudo chmod -R o+rw /mnt/tmp-lvm
49
+ fallocate -l "2448KiB" /mnt/tmp-lvm/large-file
50
+ echo "A small file at the end of the disk" > /mnt/tmp-lvm/small-file
51
+
52
+ log "Created files on disk"
53
+
54
+ log "Cleanup lvm devices"
55
+ sudo umount /mnt/tmp-lvm
56
+ sudo lvchange -an vghelp/lv
57
+ sudo vgchange -an vghelp >/dev/null
58
+ sudo losetup -d "$lo"
59
+
60
+
61
+ log "Update size of the pv header"
62
+ # Update the size at this offset
63
+ printf "00000240: 0000 3000" | xxd -r - "$filename"
64
+
65
+ # Compress the resulting file
66
+ gzip "$filename"
67
+ }
68
+
69
+
70
+ main() {
71
+ require_tools
72
+
73
+ mkdir -p "${OUT_DIR}"
74
+
75
+ _create_lvm_inconsistent_sizes "${OUT_DIR}/lvm-inconsistent-sizes.bin"
76
+
77
+ log "All test cases generated under: ${OUT_DIR}"
78
+ }
79
+
80
+ main
@@ -45,6 +45,11 @@ def lvm_mirror() -> Iterator[list[BinaryIO]]:
45
45
  yield from open_files_gz(["_data/lvm/lvm-mirror-1.bin.gz", "_data/lvm/lvm-mirror-2.bin.gz"])
46
46
 
47
47
 
48
+ @pytest.fixture
49
+ def lvm_inconsistent_sizes() -> Iterator[BinaryIO]:
50
+ yield from open_file_gz("_data/lvm/lvm-inconsistent-sizes.bin.gz")
51
+
52
+
48
53
  @pytest.fixture
49
54
  def mbr() -> Iterator[BinaryIO]:
50
55
  yield from open_file("_data/disk/mbr.bin")
@@ -0,0 +1,158 @@
1
+ from __future__ import annotations
2
+
3
+ import io
4
+ import re
5
+ from typing import BinaryIO
6
+
7
+ import pytest
8
+
9
+ from dissect.volume.exceptions import LVM2Error
10
+ from dissect.volume.lvm import LVM2
11
+ from dissect.volume.lvm.c_lvm2 import c_lvm
12
+ from dissect.volume.lvm.metadata import StripedSegment
13
+
14
+
15
+ def test_lvm(lvm: BinaryIO) -> None:
16
+ vs = LVM2(lvm)
17
+
18
+ vg = vs.vg
19
+ assert vg.name == "vg_test"
20
+ assert vg.id == "8HfEjs-9DNH-0dy1-U5u8-EYBF-Vce4-8BcSWU"
21
+ assert len(vg.lv) == 1
22
+ assert len(vg.pv) == 1
23
+
24
+ lv = vg.lv[0]
25
+ assert lv.name == "lv_test"
26
+ assert lv.id == "TnYdWo-zRE9-wf2T-5nt0-M1aD-vtoP-fASCxK"
27
+ assert lv.type == "striped"
28
+ assert len(lv.segments) == 1
29
+
30
+ seg = lv.segments[0]
31
+ assert isinstance(seg, StripedSegment)
32
+ assert len(seg.stripes) == seg.stripe_count
33
+ assert seg.extent_count == 1
34
+ assert seg.extent_count * vg.extent_size == 8192
35
+
36
+ fh = lv.open()
37
+ assert fh.read(1024) == b"\xde\xad\xbe\xef" * 256
38
+ assert fh.read(1024) == b"\x00" * 1024
39
+
40
+ vg.pv[0]._dev = None
41
+ with pytest.raises(
42
+ LVM2Error,
43
+ match=re.escape(
44
+ "Physical volume not found: pv0 (id=2Svcy0-cRH2-3Xrz-87Fv-zNUI-9CoI-Ycoyql, device=/dev/loop1)"
45
+ ),
46
+ ):
47
+ fh = lv.open()
48
+
49
+
50
+ def test_lvm_invalid() -> None:
51
+ buf = io.BytesIO(4096 * b"\x00")
52
+
53
+ with pytest.raises(LVM2Error):
54
+ LVM2(buf)
55
+
56
+
57
+ def test_lvm_thin(lvm_thin: BinaryIO) -> None:
58
+ lvm = LVM2(lvm_thin)
59
+
60
+ for lv_name in ["lv-1", "lv-2"]:
61
+ lv = lvm.vg.logical_volumes[lv_name]
62
+ assert lv.type == "thin"
63
+
64
+ fh = lv.open()
65
+ for i in range(1, 513):
66
+ assert fh.read(4096) == i.to_bytes(2, "little") * 2048
67
+
68
+ pool = lvm.vg.logical_volumes["data"]
69
+ assert pool.type == "thin-pool"
70
+
71
+ with pytest.raises(
72
+ RuntimeError, match="Opening a thin-pool for reading is not possible, use open_pool\\(\\) instead"
73
+ ):
74
+ pool.open()
75
+
76
+
77
+ def test_lvm_mirror(lvm_mirror: list[BinaryIO]) -> None:
78
+ lvm = LVM2(lvm_mirror)
79
+ lv = lvm.vg.logical_volumes["mirrormirror"]
80
+
81
+ assert lv.type == "mirror"
82
+
83
+ fh = lv.open()
84
+ for i in range(1, 513):
85
+ assert fh.read(4096) == i.to_bytes(2, "little") * 2048
86
+
87
+
88
+ def test_lvm_multiple_data_areas() -> None:
89
+ """Test if LVM2 raises an Error if more than 1 data area descriptor is specified.
90
+
91
+ This test can be removed once lvm2 allows for multiple area descriptors
92
+ """
93
+ lvm_data = [
94
+ c_lvm.label_header(id=b"LABELONE", offset=len(c_lvm.label_header)),
95
+ c_lvm.pv_header(),
96
+ c_lvm.disk_locn(offset=1),
97
+ c_lvm.disk_locn(offset=2),
98
+ c_lvm.disk_locn(),
99
+ ]
100
+ data = io.BytesIO(b"".join(struct.dumps() for struct in lvm_data))
101
+ with pytest.raises(RuntimeError, match="There should be 1 data area descriptor, found 2"):
102
+ LVM2(data)
103
+
104
+
105
+ def test_lvm_sizes_mismatch(lvm_inconsistent_sizes: BinaryIO) -> None:
106
+ """Test if opening the LVM2Device uses PhysicalVolume.size when LVM2Device.size < PhysicalVolume.size."""
107
+ lvm = LVM2(lvm_inconsistent_sizes)
108
+
109
+ lv = lvm.vg.logical_volumes["lv"]
110
+
111
+ # Size manually adjusted to be smaller than the actual data
112
+ # This is the offset the data of a text file starts
113
+ assert next(iter(lvm.devices.values())).size == 0x300000
114
+ physical_volume = lvm.vg.pv[0]
115
+ assert physical_volume.size == 0x8000000
116
+
117
+ fh = lv.open()
118
+ # Size of the stripe
119
+ assert fh.size == 0x400000
120
+
121
+ segment_stream = fh._runs[0][2]
122
+ pv_stream = segment_stream._runs[0][2]
123
+ # Check whether the underlying pv stream size is correct (The non adjusted size)
124
+ assert pv_stream.size == 0x8000000
125
+
126
+ fh.seek(0x3B8800)
127
+
128
+ expected_data = b"A small file at the end of the disk"
129
+ assert fh.read(len(expected_data)) == expected_data
130
+
131
+
132
+ def test_lvm_sizes_mismatch_missing_dev_size(lvm_inconsistent_sizes: BinaryIO) -> None:
133
+ """Test if opening the LVM2Device uses the calculated disk size when PhysicalVolume.dev_size is missing."""
134
+ lvm = LVM2(lvm_inconsistent_sizes)
135
+
136
+ lv = lvm.vg.logical_volumes["lv"]
137
+
138
+ # Size manually adjusted to be smaller than the actual data
139
+ # This is the offset the data of a text file starts
140
+ assert next(iter(lvm.devices.values())).size == 0x300000
141
+ physical_volume = lvm.vg.pv[0]
142
+ # physical_volume.size will calculate size based on the the data offset
143
+ # and size of the stripes if dev_size is not defined
144
+ physical_volume.dev_size = None
145
+ assert physical_volume.size == 0x500000
146
+
147
+ fh = lv.open()
148
+ # Size of the stripe
149
+ assert fh.size == 0x400000
150
+
151
+ segment_stream = fh._runs[0][2]
152
+ pv_stream = segment_stream._runs[0][2]
153
+ assert pv_stream.size == 0x500000
154
+
155
+ fh.seek(0x3B8800)
156
+
157
+ expected_data = b"A small file at the end of the disk"
158
+ assert fh.read(len(expected_data)) == expected_data
@@ -1,84 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import io
4
- import re
5
- from typing import BinaryIO
6
-
7
- import pytest
8
-
9
- from dissect.volume.exceptions import LVM2Error
10
- from dissect.volume.lvm import LVM2
11
- from dissect.volume.lvm.metadata import StripedSegment
12
-
13
-
14
- def test_lvm(lvm: BinaryIO) -> None:
15
- vs = LVM2(lvm)
16
-
17
- vg = vs.vg
18
- assert vg.name == "vg_test"
19
- assert vg.id == "8HfEjs-9DNH-0dy1-U5u8-EYBF-Vce4-8BcSWU"
20
- assert len(vg.lv) == 1
21
- assert len(vg.pv) == 1
22
-
23
- lv = vg.lv[0]
24
- assert lv.name == "lv_test"
25
- assert lv.id == "TnYdWo-zRE9-wf2T-5nt0-M1aD-vtoP-fASCxK"
26
- assert lv.type == "striped"
27
- assert len(lv.segments) == 1
28
-
29
- seg = lv.segments[0]
30
- assert isinstance(seg, StripedSegment)
31
- assert len(seg.stripes) == seg.stripe_count
32
- assert seg.extent_count == 1
33
- assert seg.extent_count * vg.extent_size == 8192
34
-
35
- fh = lv.open()
36
- assert fh.read(1024) == b"\xde\xad\xbe\xef" * 256
37
- assert fh.read(1024) == b"\x00" * 1024
38
-
39
- vg.pv[0]._dev = None
40
- with pytest.raises(
41
- LVM2Error,
42
- match=re.escape(
43
- "Physical volume not found: pv0 (id=2Svcy0-cRH2-3Xrz-87Fv-zNUI-9CoI-Ycoyql, device=/dev/loop1)"
44
- ),
45
- ):
46
- fh = lv.open()
47
-
48
-
49
- def test_lvm_invalid() -> None:
50
- buf = io.BytesIO(4096 * b"\x00")
51
-
52
- with pytest.raises(LVM2Error):
53
- LVM2(buf)
54
-
55
-
56
- def test_lvm_thin(lvm_thin: BinaryIO) -> None:
57
- lvm = LVM2(lvm_thin)
58
-
59
- for lv_name in ["lv-1", "lv-2"]:
60
- lv = lvm.vg.logical_volumes[lv_name]
61
- assert lv.type == "thin"
62
-
63
- fh = lv.open()
64
- for i in range(1, 513):
65
- assert fh.read(4096) == i.to_bytes(2, "little") * 2048
66
-
67
- pool = lvm.vg.logical_volumes["data"]
68
- assert pool.type == "thin-pool"
69
-
70
- with pytest.raises(
71
- RuntimeError, match="Opening a thin-pool for reading is not possible, use open_pool\\(\\) instead"
72
- ):
73
- pool.open()
74
-
75
-
76
- def test_lvm_mirror(lvm_mirror: list[BinaryIO]) -> None:
77
- lvm = LVM2(lvm_mirror)
78
- lv = lvm.vg.logical_volumes["mirrormirror"]
79
-
80
- assert lv.type == "mirror"
81
-
82
- fh = lv.open()
83
- for i in range(1, 513):
84
- assert fh.read(4096) == i.to_bytes(2, "little") * 2048